mirror of
https://github.com/PhyreApps/PhyrePanel.git
synced 2024-11-21 23:20:24 +00:00
update
This commit is contained in:
parent
8ff96c332e
commit
2d217d93fb
7 changed files with 425 additions and 125 deletions
|
@ -2,141 +2,136 @@
|
|||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Helpers;
|
||||
use App\Models\Domain;
|
||||
use App\Models\HostingSubscription;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Models\FileItem;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
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;
|
||||
|
||||
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 mount($hostingSubscriptionId)
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
$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();
|
||||
$findDomain = Domain::where('hosting_subscription_id', $this->hostingSubscriptionId)
|
||||
->where('is_main', 1)
|
||||
->first();
|
||||
|
||||
if (!$findHostingSubscription || !$findDomain) {
|
||||
throw new \Exception('Hosting subscription not found');
|
||||
$this->dispatch('updatePath');
|
||||
}
|
||||
}),
|
||||
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();
|
||||
}
|
||||
|
||||
$this->hostingSubscriptionSystemUsername = $findHostingSubscription->system_username;
|
||||
$this->domainHomeRoot = $findDomain->home_root;
|
||||
}),
|
||||
])
|
||||
->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->resetTable();
|
||||
$action->sendSuccessNotification();
|
||||
}),
|
||||
|
||||
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
|
||||
Action::make('upload_file')
|
||||
->label('Upload files')
|
||||
->icon('heroicon-o-document-arrow-up')
|
||||
->color('info')
|
||||
->form([
|
||||
FileUpload::make('files')
|
||||
->required()
|
||||
->multiple()
|
||||
->previewable(false)
|
||||
->preserveFilenames()
|
||||
->disk($this->disk)
|
||||
->directory($this->path),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
142
web/app/Livewire/FileManagerOld.php
Normal file
142
web/app/Livewire/FileManagerOld.php
Normal 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
104
web/app/Models/FileItem.php
Normal 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();
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
"php": "^8.1",
|
||||
"acmephp/core": "*",
|
||||
"archilex/filament-toggle-icon-column": "^3.1",
|
||||
"calebporzio/sushi": "^2.5",
|
||||
"coolsam/modules": "^3.0@beta",
|
||||
"darkaonline/l5-swagger": "^8.6",
|
||||
"filament/filament": "^3.0",
|
||||
|
|
58
web/composer.lock
generated
58
web/composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "bc8f7e167f22d774934a3b1001c2fd83",
|
||||
"content-hash": "f079cdfb4cd2f67cf4a0db0a51639be9",
|
||||
"packages": [
|
||||
{
|
||||
"name": "acmephp/core",
|
||||
|
@ -487,6 +487,60 @@
|
|||
],
|
||||
"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",
|
||||
"version": "2.1.0",
|
||||
|
@ -12434,5 +12488,5 @@
|
|||
"php": "^8.1"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.2.0"
|
||||
"plugin-api-version": "2.3.0"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<x-filament-panels::page>
|
||||
{{ $this->table }}
|
||||
</x-filament-panels::page>
|
|
@ -1,7 +1,8 @@
|
|||
<x-filament-panels::page>
|
||||
|
||||
<div>
|
||||
<livewire:file-manager hostingSubscriptionId="{{$this->data['id']}}" />
|
||||
{{-- hostingSubscriptionId="{{$this->data['id']}}"--}}
|
||||
<livewire:file-manager />
|
||||
</div>
|
||||
|
||||
</x-filament-panels::page>
|
||||
|
|
Loading…
Reference in a new issue