This commit is contained in:
Bozhidar 2024-05-13 18:23:58 +03:00
parent 8ff96c332e
commit 2d217d93fb
7 changed files with 425 additions and 125 deletions

View file

@ -2,141 +2,136 @@
namespace App\Livewire; namespace App\Livewire;
use App\Helpers; use App\Models\FileItem;
use App\Models\Domain; use Filament\Forms\Components\FileUpload;
use App\Models\HostingSubscription; use Filament\Forms\Components\TextInput;
use Illuminate\Support\Str; use Filament\Pages\Page;
use Filament\Tables\Actions\Action;
use Filament\Tables\Actions\BulkAction;
use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\ViewAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Table;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Number;
use Livewire\Attributes\Url;
use Livewire\Component; use Livewire\Component;
class FileManager extends Component class FileManager extends Page implements HasTable
{ {
public $hostingSubscriptionId; use InteractsWithTable;
public $hostingSubscriptionSystemUsername; protected static ?string $navigationIcon = 'heroicon-o-folder-open';
public $domainHomeRoot; protected static string $view = 'filament.pages.file-manager';
public $currentRealPath; protected string $disk = 'local';
public $currentPath; #[Url(except: '')]
public string $path = '';
public $folderName; protected $listeners = ['updatePath' => '$refresh'];
public $canIBack = false; public function table(Table $table): Table
public function mount($hostingSubscriptionId)
{ {
$this->hostingSubscriptionId = $hostingSubscriptionId; return $table
->heading($this->path ?: 'Root')
->query(
FileItem::queryForDiskAndPath($this->disk, $this->path)
)
->paginated(false)
->columns([
TextColumn::make('name')
->icon(fn ($record): string => match ($record->type) {
'Folder' => 'heroicon-o-folder',
default => 'heroicon-o-document'
})
->iconColor(fn ($record): string => match ($record->type) {
'Folder' => 'warning',
default => 'gray',
})
->action(function (FileItem $record) {
if ($record->isFolder()) {
$this->path = $record->path;
$findHostingSubscription = HostingSubscription::where('id', $this->hostingSubscriptionId)->first(); $this->dispatch('updatePath');
$findDomain = Domain::where('hosting_subscription_id', $this->hostingSubscriptionId) }
->where('is_main', 1) }),
->first(); TextColumn::make('dateModified')
->dateTime(),
TextColumn::make('size')
->formatStateUsing(fn ($state) => $state ? Number::fileSize($state) : ''),
TextColumn::make('type'),
])
->actions([
ViewAction::make('open')
->label('Open')
->hidden(fn (FileItem $record): bool => ! $record->canOpen())
->url(fn (FileItem $record): string => Storage::disk($this->disk)->url($record->path))
->openUrlInNewTab(),
Action::make('download')
->label('Download')
->icon('heroicon-o-document-arrow-down')
->hidden(fn (FileItem $record): bool => $record->isFolder())
->action(fn (FileItem $record) => Storage::disk($this->disk)->download($record->path)),
DeleteAction::make('delete')
->successNotificationTitle('File deleted')
->hidden(fn (FileItem $record): bool => $record->isPreviousPath())
->action(function (FileItem $record, Action $action) {
if ($record->delete()) {
$action->sendSuccessNotification();
}
if (!$findHostingSubscription || !$findDomain) { }),
throw new \Exception('Hosting subscription not found'); ])
} ->bulkActions([
BulkAction::make('delete')
->icon('heroicon-o-trash')
->color('danger')
->requiresConfirmation()
->successNotificationTitle('Files deleted')
->deselectRecordsAfterCompletion()
->action(function (Collection $records, BulkAction $action) {
$records->each(fn (FileItem $record) => $record->delete());
$action->sendSuccessNotification();
}),
])
->checkIfRecordIsSelectableUsing(fn (FileItem $record): bool => ! $record->isPreviousPath())
->headerActions([
Action::make('create_folder')
->label('Create Folder')
->icon('heroicon-o-folder-plus')
->form([
TextInput::make('name')
->label('Folder name')
->placeholder('Folder name')
->required(),
])
->successNotificationTitle('Folder created')
->action(function (array $data, Component $livewire, Action $action): void {
Storage::disk($livewire->disk)
->makeDirectory($livewire->path.'/'.$data['name']);
$this->hostingSubscriptionSystemUsername = $findHostingSubscription->system_username; $this->resetTable();
$this->domainHomeRoot = $findDomain->home_root; $action->sendSuccessNotification();
}),
} Action::make('upload_file')
->label('Upload files')
public function openDeleteModal() ->icon('heroicon-o-document-arrow-up')
{ ->color('info')
$this->dispatch('open-modal', id: 'delete-file'); ->form([
} FileUpload::make('files')
->required()
public function goto($dirOrFile) ->multiple()
{ ->previewable(false)
$newPath = $this->currentRealPath . '/' . $dirOrFile; ->preserveFilenames()
if (is_dir($newPath)) { ->disk($this->disk)
$this->currentRealPath = $newPath; ->directory($this->path),
} ]),
]);
}
public function back()
{
$this->canIBack = false;
$newRealPath = dirname($this->currentRealPath);
if (Str::startsWith($newRealPath, $this->domainHomeRoot)) {
$this->currentRealPath = $newRealPath;
}
}
public function canIAccess($realPath, $systemUsername)
{
$checkOwner = posix_getpwuid(fileowner($realPath));
if (isset($checkOwner['name']) && $checkOwner['name'] == $systemUsername) {
return true;
}
return false;
}
public function createFolder()
{
$this->folderName = Str::slug($this->folderName);
$newPath = $this->currentRealPath . '/' . $this->folderName;
if (!is_dir($newPath)) {
mkdir($newPath);
$this->folderName = '';
$this->dispatch('close-modal', id: 'create-folder');
}
}
public function render()
{
if (!$this->currentRealPath) {
$this->currentRealPath = $this->domainHomeRoot;
}
$all = [];
$files = [];
$folders = [];
if ($this->currentRealPath) {
if (Str::startsWith(dirname($this->currentRealPath), $this->domainHomeRoot)) {
$this->canIBack = true;
}
$scanFiles = scandir($this->currentRealPath);
foreach ($scanFiles as $scanFile) {
if ($scanFile == '.' || $scanFile == '..') {
continue;
}
try {
$append = [
'extension' => pathinfo($scanFile, PATHINFO_EXTENSION),
'name' => $scanFile,
'path' => $this->currentRealPath . '/' . $scanFile,
'is_dir' => is_dir($this->currentRealPath . '/' . $scanFile),
'permission' => substr(sprintf('%o', fileperms($this->currentRealPath . '/' . $scanFile)), -4),
'owner' => posix_getpwuid(fileowner($this->currentRealPath . '/' . $scanFile))['name'],
'group' => posix_getgrgid(filegroup($this->currentRealPath . '/' . $scanFile))['name'],
'size' => Helpers::getHumanReadableSize(filesize($this->currentRealPath . '/' . $scanFile)),
'last_modified' => date('Y-m-d H:i:s', filemtime($this->currentRealPath . '/' . $scanFile)),
'type' => filetype($this->currentRealPath . '/' . $scanFile),
];
if ($append['is_dir']) {
$folders[] = $append;
} else {
$files[] = $append;
}
} catch (\Exception $e) {
continue;
}
}
}
$all = array_merge($folders, $files);
return view('livewire.file-manager.index', [
'files'=>$all
]);
} }
} }

View file

@ -0,0 +1,142 @@
<?php
namespace App\Livewire;
use App\Helpers;
use App\Models\Domain;
use App\Models\HostingSubscription;
use Illuminate\Support\Str;
use Livewire\Component;
class FileManagerOld extends Component
{
public $hostingSubscriptionId;
public $hostingSubscriptionSystemUsername;
public $domainHomeRoot;
public $currentRealPath;
public $currentPath;
public $folderName;
public $canIBack = false;
public function mount($hostingSubscriptionId)
{
$this->hostingSubscriptionId = $hostingSubscriptionId;
$findHostingSubscription = HostingSubscription::where('id', $this->hostingSubscriptionId)->first();
$findDomain = Domain::where('hosting_subscription_id', $this->hostingSubscriptionId)
->where('is_main', 1)
->first();
if (!$findHostingSubscription || !$findDomain) {
throw new \Exception('Hosting subscription not found');
}
$this->hostingSubscriptionSystemUsername = $findHostingSubscription->system_username;
$this->domainHomeRoot = $findDomain->home_root;
}
public function openDeleteModal()
{
$this->dispatch('open-modal', id: 'delete-file');
}
public function goto($dirOrFile)
{
$newPath = $this->currentRealPath . '/' . $dirOrFile;
if (is_dir($newPath)) {
$this->currentRealPath = $newPath;
}
}
public function back()
{
$this->canIBack = false;
$newRealPath = dirname($this->currentRealPath);
if (Str::startsWith($newRealPath, $this->domainHomeRoot)) {
$this->currentRealPath = $newRealPath;
}
}
public function canIAccess($realPath, $systemUsername)
{
$checkOwner = posix_getpwuid(fileowner($realPath));
if (isset($checkOwner['name']) && $checkOwner['name'] == $systemUsername) {
return true;
}
return false;
}
public function createFolder()
{
$this->folderName = Str::slug($this->folderName);
$newPath = $this->currentRealPath . '/' . $this->folderName;
if (!is_dir($newPath)) {
mkdir($newPath);
$this->folderName = '';
$this->dispatch('close-modal', id: 'create-folder');
}
}
public function render()
{
if (!$this->currentRealPath) {
$this->currentRealPath = $this->domainHomeRoot;
}
$all = [];
$files = [];
$folders = [];
if ($this->currentRealPath) {
if (Str::startsWith(dirname($this->currentRealPath), $this->domainHomeRoot)) {
$this->canIBack = true;
}
$scanFiles = scandir($this->currentRealPath);
foreach ($scanFiles as $scanFile) {
if ($scanFile == '.' || $scanFile == '..') {
continue;
}
try {
$append = [
'extension' => pathinfo($scanFile, PATHINFO_EXTENSION),
'name' => $scanFile,
'path' => $this->currentRealPath . '/' . $scanFile,
'is_dir' => is_dir($this->currentRealPath . '/' . $scanFile),
'permission' => substr(sprintf('%o', fileperms($this->currentRealPath . '/' . $scanFile)), -4),
'owner' => posix_getpwuid(fileowner($this->currentRealPath . '/' . $scanFile))['name'],
'group' => posix_getgrgid(filegroup($this->currentRealPath . '/' . $scanFile))['name'],
'size' => Helpers::getHumanReadableSize(filesize($this->currentRealPath . '/' . $scanFile)),
'last_modified' => date('Y-m-d H:i:s', filemtime($this->currentRealPath . '/' . $scanFile)),
'type' => filetype($this->currentRealPath . '/' . $scanFile),
];
if ($append['is_dir']) {
$folders[] = $append;
} else {
$files[] = $append;
}
} catch (\Exception $e) {
continue;
}
}
}
$all = array_merge($folders, $files);
return view('livewire.file-manager.index', [
'files'=>$all
]);
}
}

104
web/app/Models/FileItem.php Normal file
View file

@ -0,0 +1,104 @@
<?php
namespace App\Models;
use Illuminate\Contracts\Filesystem\Filesystem as FilesystemContract;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Sushi\Sushi;
class FileItem extends Model
{
use Sushi;
protected static string $disk;
protected static string $path;
protected array $schema = [
'name' => 'string',
'dateModified' => 'datetime',
'size' => 'integer',
'type' => 'string',
];
public static function queryForDiskAndPath(string $disk = 'public', string $path = ''): Builder
{
static::$disk = $disk;
static::$path = $path;
return static::query();
}
public function isFolder(): bool
{
return $this->type === 'Folder'
&& is_dir(Storage::disk(static::$disk)->path($this->path));
}
public function isPreviousPath(): bool
{
return $this->name === '..';
}
public function delete(): bool
{
if ($this->isFolder()) {
return Storage::disk(static::$disk)->deleteDirectory($this->path);
}
return Storage::disk(static::$disk)->delete($this->path);
}
public function canOpen(): bool
{
return $this->type !== 'Folder'
&& Storage::disk(static::$disk)->exists($this->path)
&& Storage::disk(static::$disk)->getVisibility($this->path) === FilesystemContract::VISIBILITY_PUBLIC;
}
public function getRows(): array
{
$backPath = [];
if (self::$path) {
$path = Str::of(self::$path)->explode('/');
$backPath = [
[
'name' => '..',
'dateModified' => null,
'size' => null,
'type' => 'Folder',
'path' => $path->count() > 1 ? $path->take($path->count() - 1)->join('/') : '',
],
];
}
$storage = Storage::disk(static::$disk);
return collect($backPath)->push(
...collect($storage->directories(static::$path))
->sort()
->map(fn (string $directory): array => [
'name' => Str::remove(self::$path.'/', $directory),
'dateModified' => $storage->lastModified($directory),
'size' => null,
'type' => 'Folder',
'path' => $directory,
]
),
...collect($storage->files(static::$path))
->sort()
->map(fn (string $file): array => [
'name' => Str::remove(self::$path.'/', $file),
'dateModified' => $storage->lastModified($file),
'size' => $storage->size($file),
'type' => $storage->mimeType($file) ?: null,
'path' => $file,
]
)
)->toArray();
}
}

View file

@ -11,6 +11,7 @@
"php": "^8.1", "php": "^8.1",
"acmephp/core": "*", "acmephp/core": "*",
"archilex/filament-toggle-icon-column": "^3.1", "archilex/filament-toggle-icon-column": "^3.1",
"calebporzio/sushi": "^2.5",
"coolsam/modules": "^3.0@beta", "coolsam/modules": "^3.0@beta",
"darkaonline/l5-swagger": "^8.6", "darkaonline/l5-swagger": "^8.6",
"filament/filament": "^3.0", "filament/filament": "^3.0",

58
web/composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "bc8f7e167f22d774934a3b1001c2fd83", "content-hash": "f079cdfb4cd2f67cf4a0db0a51639be9",
"packages": [ "packages": [
{ {
"name": "acmephp/core", "name": "acmephp/core",
@ -487,6 +487,60 @@
], ],
"time": "2023-11-29T23:19:16+00:00" "time": "2023-11-29T23:19:16+00:00"
}, },
{
"name": "calebporzio/sushi",
"version": "v2.5.2",
"source": {
"type": "git",
"url": "https://github.com/calebporzio/sushi.git",
"reference": "01dd34fe3374f5fb7ce63756c0419385e31cd532"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/calebporzio/sushi/zipball/01dd34fe3374f5fb7ce63756c0419385e31cd532",
"reference": "01dd34fe3374f5fb7ce63756c0419385e31cd532",
"shasum": ""
},
"require": {
"ext-pdo_sqlite": "*",
"ext-sqlite3": "*",
"illuminate/database": "^5.8 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0",
"illuminate/support": "^5.8 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0",
"php": "^7.1.3|^8.0"
},
"require-dev": {
"doctrine/dbal": "^2.9 || ^3.1.4",
"orchestra/testbench": "3.8.* || 3.9.* || ^4.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0",
"phpunit/phpunit": "^7.5 || ^8.4 || ^9.0 || ^10.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Sushi\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Caleb Porzio",
"email": "calebporzio@gmail.com"
}
],
"description": "Eloquent's missing \"array\" driver.",
"support": {
"source": "https://github.com/calebporzio/sushi/tree/v2.5.2"
},
"funding": [
{
"url": "https://github.com/calebporzio",
"type": "github"
}
],
"time": "2024-04-24T15:23:03+00:00"
},
{ {
"name": "carbonphp/carbon-doctrine-types", "name": "carbonphp/carbon-doctrine-types",
"version": "2.1.0", "version": "2.1.0",
@ -12434,5 +12488,5 @@
"php": "^8.1" "php": "^8.1"
}, },
"platform-dev": [], "platform-dev": [],
"plugin-api-version": "2.2.0" "plugin-api-version": "2.3.0"
} }

View file

@ -0,0 +1,3 @@
<x-filament-panels::page>
{{ $this->table }}
</x-filament-panels::page>

View file

@ -1,7 +1,8 @@
<x-filament-panels::page> <x-filament-panels::page>
<div> <div>
<livewire:file-manager hostingSubscriptionId="{{$this->data['id']}}" /> {{-- hostingSubscriptionId="{{$this->data['id']}}"--}}
<livewire:file-manager />
</div> </div>
</x-filament-panels::page> </x-filament-panels::page>