Improve file error handling (#1314)

* improve file error handling

* small cleanup

* fix typo
This commit is contained in:
Boy132 2025-04-29 17:05:29 +02:00 committed by GitHub
parent 2046fa453a
commit 92c23451af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 83 additions and 118 deletions

View File

@ -26,6 +26,7 @@ use Filament\Resources\Pages\Page;
use Filament\Resources\Pages\PageRegistration; use Filament\Resources\Pages\PageRegistration;
use Filament\Support\Enums\Alignment; use Filament\Support\Enums\Alignment;
use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Routing\Route; use Illuminate\Routing\Route;
use Illuminate\Support\Facades\Route as RouteFacade; use Illuminate\Support\Facades\Route as RouteFacade;
use Livewire\Attributes\Locked; use Livewire\Attributes\Locked;
@ -128,31 +129,33 @@ class EditFiles extends Page
return $this->getDaemonFileRepository()->getContent($this->path, config('panel.files.max_edit_size')); return $this->getDaemonFileRepository()->getContent($this->path, config('panel.files.max_edit_size'));
} catch (FileSizeTooLargeException) { } catch (FileSizeTooLargeException) {
AlertBanner::make() AlertBanner::make()
->title('File too large!') ->title('<code>' . basename($this->path) . '</code> is too large!')
->body('<code>' . $this->path . '</code> Max is ' . convert_bytes_to_readable(config('panel.files.max_edit_size'))) ->body('Max is ' . convert_bytes_to_readable(config('panel.files.max_edit_size')))
->danger() ->danger()
->closable() ->closable()
->send(); ->send();
$this->redirect(ListFiles::getUrl()); $this->redirect(ListFiles::getUrl(['path' => dirname($this->path)]));
} catch (FileNotFoundException) { } catch (FileNotFoundException) {
AlertBanner::make() AlertBanner::make()
->title('File Not found!') ->title('<code>' . basename($this->path) . '</code> not found!')
->body('<code>' . $this->path . '</code>')
->danger() ->danger()
->closable() ->closable()
->send(); ->send();
$this->redirect(ListFiles::getUrl()); $this->redirect(ListFiles::getUrl(['path' => dirname($this->path)]));
} catch (FileNotEditableException) { } catch (FileNotEditableException) {
AlertBanner::make() AlertBanner::make()
->title('Could not edit directory!') ->title('<code>' . basename($this->path) . '</code> is a directory')
->body('<code>' . $this->path . '</code>')
->danger() ->danger()
->closable() ->closable()
->send(); ->send();
$this->redirect(ListFiles::getUrl()); $this->redirect(ListFiles::getUrl(['path' => dirname($this->path)]));
} catch (ConnectionException) {
// Alert banner for this one will be handled by ListFiles
$this->redirect(ListFiles::getUrl(['path' => dirname($this->path)]));
} }
}) })
->language(fn (Get $get) => $get('lang')) ->language(fn (Get $get) => $get('lang'))

View File

@ -12,7 +12,6 @@ use App\Models\Server;
use App\Repositories\Daemon\DaemonFileRepository; use App\Repositories\Daemon\DaemonFileRepository;
use App\Filament\Components\Tables\Columns\BytesColumn; use App\Filament\Components\Tables\Columns\BytesColumn;
use App\Filament\Components\Tables\Columns\DateTimeColumn; use App\Filament\Components\Tables\Columns\DateTimeColumn;
use App\Livewire\AlertBanner;
use Filament\Actions\Action as HeaderAction; use Filament\Actions\Action as HeaderAction;
use Filament\Facades\Filament; use Filament\Facades\Filament;
use Filament\Forms\Components\CheckboxList; use Filament\Forms\Components\CheckboxList;
@ -30,14 +29,12 @@ use Filament\Resources\Pages\PageRegistration;
use Filament\Tables\Actions\Action; use Filament\Tables\Actions\Action;
use Filament\Tables\Actions\ActionGroup; use Filament\Tables\Actions\ActionGroup;
use Filament\Tables\Actions\BulkAction; use Filament\Tables\Actions\BulkAction;
use Filament\Tables\Actions\BulkActionGroup;
use Filament\Tables\Actions\DeleteAction; use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\DeleteBulkAction; use Filament\Tables\Actions\DeleteBulkAction;
use Filament\Tables\Actions\EditAction; use Filament\Tables\Actions\EditAction;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table; use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Illuminate\Routing\Route; use Illuminate\Routing\Route;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
@ -53,24 +50,11 @@ class ListFiles extends ListRecords
private DaemonFileRepository $fileRepository; private DaemonFileRepository $fileRepository;
private bool $isDisabled = false;
public function mount(?string $path = null): void public function mount(?string $path = null): void
{ {
parent::mount(); parent::mount();
$this->path = $path ?? '/'; $this->path = $path ?? '/';
try {
$this->getDaemonFileRepository()->getDirectory('/');
} catch (ConnectionException) {
$this->isDisabled = true;
AlertBanner::make('node_connection_error')
->title('Could not connect to the node!')
->danger()
->send();
}
} }
public function getBreadcrumbs(): array public function getBreadcrumbs(): array
@ -130,21 +114,18 @@ class ListFiles extends ListRecords
->actions([ ->actions([
Action::make('view') Action::make('view')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_READ, $server)) ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_READ, $server))
->disabled($this->isDisabled)
->label('Open') ->label('Open')
->icon('tabler-eye') ->icon('tabler-eye')
->visible(fn (File $file) => $file->is_directory) ->visible(fn (File $file) => $file->is_directory)
->url(fn (File $file) => self::getUrl(['path' => join_paths($this->path, $file->name)])), ->url(fn (File $file) => self::getUrl(['path' => join_paths($this->path, $file->name)])),
EditAction::make('edit') EditAction::make('edit')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_READ_CONTENT, $server)) ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_READ_CONTENT, $server))
->disabled($this->isDisabled)
->icon('tabler-edit') ->icon('tabler-edit')
->visible(fn (File $file) => $file->canEdit()) ->visible(fn (File $file) => $file->canEdit())
->url(fn (File $file) => EditFiles::getUrl(['path' => join_paths($this->path, $file->name)])), ->url(fn (File $file) => EditFiles::getUrl(['path' => join_paths($this->path, $file->name)])),
ActionGroup::make([ ActionGroup::make([
Action::make('rename') Action::make('rename')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server)) ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
->disabled($this->isDisabled)
->label('Rename') ->label('Rename')
->icon('tabler-forms') ->icon('tabler-forms')
->form([ ->form([
@ -173,7 +154,6 @@ class ListFiles extends ListRecords
}), }),
Action::make('copy') Action::make('copy')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server)) ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server))
->disabled($this->isDisabled)
->label('Copy') ->label('Copy')
->icon('tabler-copy') ->icon('tabler-copy')
->visible(fn (File $file) => $file->is_file) ->visible(fn (File $file) => $file->is_file)
@ -193,14 +173,12 @@ class ListFiles extends ListRecords
}), }),
Action::make('download') Action::make('download')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_READ_CONTENT, $server)) ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_READ_CONTENT, $server))
->disabled($this->isDisabled)
->label('Download') ->label('Download')
->icon('tabler-download') ->icon('tabler-download')
->visible(fn (File $file) => $file->is_file) ->visible(fn (File $file) => $file->is_file)
->url(fn (File $file) => DownloadFiles::getUrl(['path' => join_paths($this->path, $file->name)]), true), ->url(fn (File $file) => DownloadFiles::getUrl(['path' => join_paths($this->path, $file->name)]), true),
Action::make('move') Action::make('move')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server)) ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
->disabled($this->isDisabled)
->label('Move') ->label('Move')
->icon('tabler-replace') ->icon('tabler-replace')
->form([ ->form([
@ -236,7 +214,6 @@ class ListFiles extends ListRecords
}), }),
Action::make('permissions') Action::make('permissions')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server)) ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
->disabled($this->isDisabled)
->label('Permissions') ->label('Permissions')
->icon('tabler-license') ->icon('tabler-license')
->form([ ->form([
@ -293,7 +270,6 @@ class ListFiles extends ListRecords
}), }),
Action::make('archive') Action::make('archive')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_ARCHIVE, $server)) ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_ARCHIVE, $server))
->disabled($this->isDisabled)
->label('Archive') ->label('Archive')
->icon('tabler-archive') ->icon('tabler-archive')
->form([ ->form([
@ -321,7 +297,6 @@ class ListFiles extends ListRecords
}), }),
Action::make('unarchive') Action::make('unarchive')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_ARCHIVE, $server)) ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_ARCHIVE, $server))
->disabled($this->isDisabled)
->label('Unarchive') ->label('Unarchive')
->icon('tabler-archive') ->icon('tabler-archive')
->visible(fn (File $file) => $file->isArchive()) ->visible(fn (File $file) => $file->isArchive())
@ -343,7 +318,6 @@ class ListFiles extends ListRecords
]), ]),
DeleteAction::make() DeleteAction::make()
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_DELETE, $server)) ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_DELETE, $server))
->disabled($this->isDisabled)
->label('') ->label('')
->icon('tabler-trash') ->icon('tabler-trash')
->requiresConfirmation() ->requiresConfirmation()
@ -358,83 +332,77 @@ class ListFiles extends ListRecords
->log(); ->log();
}), }),
]) ])
->bulkActions([ ->groupedBulkActions([
BulkActionGroup::make([ BulkAction::make('move')
BulkAction::make('move') ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server)) ->form([
->disabled($this->isDisabled) TextInput::make('location')
->form([ ->label('Directory')
TextInput::make('location') ->hint('Enter the new directory, relative to the current directory.')
->label('Directory') ->required()
->hint('Enter the new directory, relative to the current directory.') ->live(),
->required() Placeholder::make('new_location')
->live(), ->content(fn (Get $get) => resolve_path('./' . join_paths($this->path, $get('location') ?? ''))),
Placeholder::make('new_location') ])
->content(fn (Get $get) => resolve_path('./' . join_paths($this->path, $get('location') ?? ''))), ->action(function (Collection $files, $data) {
]) $location = rtrim($data['location'], '/');
->action(function (Collection $files, $data) {
$location = rtrim($data['location'], '/');
$files = $files->map(fn ($file) => ['to' => join_paths($location, $file['name']), 'from' => $file['name']])->toArray(); $files = $files->map(fn ($file) => ['to' => join_paths($location, $file['name']), 'from' => $file['name']])->toArray();
$this->getDaemonFileRepository() $this->getDaemonFileRepository()->renameFiles($this->path, $files);
->renameFiles($this->path, $files);
Activity::event('server:file.rename') Activity::event('server:file.rename')
->property('directory', $this->path) ->property('directory', $this->path)
->property('files', $files) ->property('files', $files)
->log(); ->log();
Notification::make() Notification::make()
->title(count($files) . ' Files were moved to ' . resolve_path(join_paths($this->path, $location))) ->title(count($files) . ' Files were moved to ' . resolve_path(join_paths($this->path, $location)))
->success() ->success()
->send(); ->send();
}), }),
BulkAction::make('archive') BulkAction::make('archive')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_ARCHIVE, $server)) ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_ARCHIVE, $server))
->disabled($this->isDisabled) ->form([
->form([ TextInput::make('name')
TextInput::make('name') ->label('Archive name')
->label('Archive name') ->placeholder(fn () => 'archive-' . str(Carbon::now()->toRfc3339String())->replace(':', '')->before('+0000') . 'Z')
->placeholder(fn () => 'archive-' . str(Carbon::now()->toRfc3339String())->replace(':', '')->before('+0000') . 'Z') ->suffix('.tar.gz'),
->suffix('.tar.gz'), ])
]) ->action(function ($data, Collection $files) {
->action(function ($data, Collection $files) { $files = $files->map(fn ($file) => $file['name'])->toArray();
$files = $files->map(fn ($file) => $file['name'])->toArray();
$archive = $this->getDaemonFileRepository()->compressFiles($this->path, $files, $data['name']); $archive = $this->getDaemonFileRepository()->compressFiles($this->path, $files, $data['name']);
Activity::event('server:file.compress') Activity::event('server:file.compress')
->property('name', $archive['name']) ->property('name', $archive['name'])
->property('directory', $this->path) ->property('directory', $this->path)
->property('files', $files) ->property('files', $files)
->log(); ->log();
Notification::make() Notification::make()
->title('Archive created') ->title('Archive created')
->body($archive['name']) ->body($archive['name'])
->success() ->success()
->send(); ->send();
return redirect(ListFiles::getUrl(['path' => $this->path])); return redirect(ListFiles::getUrl(['path' => $this->path]));
}), }),
DeleteBulkAction::make() DeleteBulkAction::make()
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_DELETE, $server)) ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_DELETE, $server))
->disabled($this->isDisabled) ->action(function (Collection $files) {
->action(function (Collection $files) { $files = $files->map(fn ($file) => $file['name'])->toArray();
$files = $files->map(fn ($file) => $file['name'])->toArray(); $this->getDaemonFileRepository()->deleteFiles($this->path, $files);
$this->getDaemonFileRepository()->deleteFiles($this->path, $files);
Activity::event('server:file.delete') Activity::event('server:file.delete')
->property('directory', $this->path) ->property('directory', $this->path)
->property('files', $files) ->property('files', $files)
->log(); ->log();
Notification::make() Notification::make()
->title(count($files) . ' Files deleted.') ->title(count($files) . ' Files deleted.')
->success() ->success()
->send(); ->send();
}), }),
]),
]); ]);
} }
@ -446,7 +414,6 @@ class ListFiles extends ListRecords
return [ return [
HeaderAction::make('new_file') HeaderAction::make('new_file')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server)) ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server))
->disabled($this->isDisabled)
->label('New File') ->label('New File')
->color('gray') ->color('gray')
->keyBindings('') ->keyBindings('')
@ -478,7 +445,6 @@ class ListFiles extends ListRecords
]), ]),
HeaderAction::make('new_folder') HeaderAction::make('new_folder')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server)) ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server))
->disabled($this->isDisabled)
->label('New Folder') ->label('New Folder')
->color('gray') ->color('gray')
->action(function ($data) { ->action(function ($data) {
@ -495,7 +461,6 @@ class ListFiles extends ListRecords
]), ]),
HeaderAction::make('upload') HeaderAction::make('upload')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server)) ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server))
->disabled($this->isDisabled)
->label('Upload') ->label('Upload')
->action(function ($data) { ->action(function ($data) {
if (count($data['files']) > 0 && !isset($data['url'])) { if (count($data['files']) > 0 && !isset($data['url'])) {
@ -545,7 +510,6 @@ class ListFiles extends ListRecords
]), ]),
HeaderAction::make('search') HeaderAction::make('search')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_READ, $server)) ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_READ, $server))
->disabled($this->isDisabled)
->label('Global Search') ->label('Global Search')
->modalSubmitActionLabel('Search') ->modalSubmitActionLabel('Search')
->form([ ->form([

View File

@ -153,16 +153,10 @@ class File extends Model
try { try {
$fileRepository = (new DaemonFileRepository())->setServer(self::$server); $fileRepository = (new DaemonFileRepository())->setServer(self::$server);
$contents = []; if (!is_null(self::$searchTerm)) {
$contents = cache()->remember('file_search_' . self::$path . '_' . self::$searchTerm, now()->addMinute(), fn () => $fileRepository->search(self::$searchTerm, self::$path));
try { } else {
if (!is_null(self::$searchTerm)) { $contents = $fileRepository->getDirectory(self::$path ?? '/');
$contents = cache()->remember('file_search_' . self::$path . '_' . self::$searchTerm, now()->addMinute(), fn () => $fileRepository->search(self::$searchTerm, self::$path));
} else {
$contents = $fileRepository->getDirectory(self::$path ?? '/');
}
} catch (ConnectionException $exception) {
report($exception);
} }
if (isset($contents['error'])) { if (isset($contents['error'])) {
@ -199,8 +193,12 @@ class File extends Model
$message = $message->after('cURL error 7: ')->before(' after '); $message = $message->after('cURL error 7: ')->before(' after ');
} }
if ($exception instanceof ConnectionException) {
$message = str('Node connection failed');
}
AlertBanner::make() AlertBanner::make()
->title('Could not load files') ->title('Could not load files!')
->body($message->toString()) ->body($message->toString())
->danger() ->danger()
->send(); ->send();