This commit is contained in:
Charles 2025-08-01 12:44:52 -04:00
parent 71602cb0cc
commit 604515ae66
8 changed files with 73 additions and 250 deletions

View File

@ -191,7 +191,6 @@ class Console extends Page
->label(trans('server/console.power_actions.kill')) ->label(trans('server/console.power_actions.kill'))
->color('danger') ->color('danger')
->tooltip(trans('server/console.power_actions.kill_tooltip')) ->tooltip(trans('server/console.power_actions.kill_tooltip'))
->size(ActionSize::ExtraLarge)
->requiresConfirmation() ->requiresConfirmation()
->size(Size::ExtraLarge) ->size(Size::ExtraLarge)
->action(fn () => $this->dispatch('setServerState', state: 'kill', uuid: $server->uuid)) ->action(fn () => $this->dispatch('setServerState', state: 'kill', uuid: $server->uuid))

View File

@ -188,7 +188,7 @@ class Settings extends ServerFormPage
TextEntry::make('password') TextEntry::make('password')
->label(trans('server/setting.node_info.sftp.password')) ->label(trans('server/setting.node_info.sftp.password'))
->columnSpan(1) ->columnSpan(1)
->content(trans('server/setting.node_info.sftp.password_body')), ->label(trans('server/setting.node_info.sftp.password_body')),
]), ]),
]), ]),
Section::make(trans('server/setting.reinstall.title')) Section::make(trans('server/setting.reinstall.title'))

View File

@ -249,7 +249,7 @@ class Startup extends ServerFormPage
return null; return null;
} }
public function getTitle(): string|Htmlable public function getTitle(): string
{ {
return trans('server/startup.title'); return trans('server/startup.title');
} }

View File

@ -2,12 +2,21 @@
namespace App\Filament\Server\Resources\BackupResource\Pages; namespace App\Filament\Server\Resources\BackupResource\Pages;
use App\Facades\Activity;
use App\Filament\Server\Resources\BackupResource; use App\Filament\Server\Resources\BackupResource;
use App\Models\Permission;
use App\Models\Server;
use App\Services\Backups\InitiateBackupService;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Actions\ActionGroup; use Filament\Actions\ActionGroup;
use Filament\Actions\CreateAction;
use Filament\Facades\Filament;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
use Filament\Support\Enums\IconSize;
use Symfony\Component\HttpKernel\Exception\HttpException;
class ListBackups extends ListRecords class ListBackups extends ListRecords
{ {

View File

@ -2,6 +2,7 @@
namespace App\Filament\Server\Resources; namespace App\Filament\Server\Resources;
use App\Filament\Components\Actions\CopyAction;
use App\Filament\Components\Actions\RotateDatabasePasswordAction; use App\Filament\Components\Actions\RotateDatabasePasswordAction;
use App\Filament\Server\Resources\DatabaseResource\Pages\ListDatabases; use App\Filament\Server\Resources\DatabaseResource\Pages\ListDatabases;
use App\Filament\Components\Tables\Columns\DateTimeColumn; use App\Filament\Components\Tables\Columns\DateTimeColumn;

View File

@ -2,7 +2,6 @@
namespace App\Filament\Server\Resources\FileResource\Pages; namespace App\Filament\Server\Resources\FileResource\Pages;
use App\Exceptions\Repository\FileExistsException;
use Exception; use Exception;
use App\Facades\Activity; use App\Facades\Activity;
use App\Filament\Server\Resources\FileResource; use App\Filament\Server\Resources\FileResource;
@ -12,7 +11,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 App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions\Action; use Filament\Actions\Action;
@ -24,22 +22,17 @@ use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction; use Filament\Actions\EditAction;
use Filament\Facades\Filament; use Filament\Facades\Filament;
use Filament\Forms\Components\CheckboxList; use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\CodeEditor;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry; use Filament\Infolists\Components\TextEntry;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
use Filament\Panel; use Filament\Panel;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
use Filament\Resources\Pages\PageRegistration; use Filament\Resources\Pages\PageRegistration;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Components\Utilities\Get; use Filament\Schemas\Components\Utilities\Get;
use Filament\Support\Enums\IconSize; use Filament\Support\Enums\IconSize;
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\UploadedFile;
use Illuminate\Routing\Route; use Illuminate\Routing\Route;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Route as RouteFacade; use Illuminate\Support\Facades\Route as RouteFacade;
@ -350,10 +343,10 @@ class ListFiles extends ListRecords
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server)) ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
->schema([ ->schema([
TextInput::make('location') TextInput::make('location')
->label(trans('server/file.actions.move.directory')) ->label(trans('server/file.actions.move.directory'))
->hint(trans('server/file.actions.move.directory_hint')) ->hint(trans('server/file.actions.move.directory_hint'))
->required() ->required()
->live(), ->live(),
TextEntry::make('new_location') TextEntry::make('new_location')
->state(fn (Get $get) => resolve_path('./' . join_paths($this->path, $get('location') ?? ''))), ->state(fn (Get $get) => resolve_path('./' . join_paths($this->path, $get('location') ?? ''))),
]) ])
@ -368,21 +361,21 @@ class ListFiles extends ListRecords
->property('files', $files) ->property('files', $files)
->log(); ->log();
Notification::make() Notification::make()
->title(trans('server/file.actions.move.bulk_notification', ['count' => count($files), 'directory' => resolve_path(join_paths($this->path, $location))])) ->title(trans('server/file.actions.move.bulk_notification', ['count' => count($files), 'directory' => 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))
->form([ ->schema([
TextInput::make('name') TextInput::make('name')
->label(trans('server/file.actions.archive.archive_name')) ->label(trans('server/file.actions.archive.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']);
@ -392,11 +385,11 @@ class ListFiles extends ListRecords
->property('files', $files) ->property('files', $files)
->log(); ->log();
Notification::make() Notification::make()
->title(trans('server/file.actions.archive.notification')) ->title(trans('server/file.actions.archive.notification'))
->body($archive['name']) ->body($archive['name'])
->success() ->success()
->send(); ->send();
return redirect(ListFiles::getUrl(['path' => $this->path])); return redirect(ListFiles::getUrl(['path' => $this->path]));
}), }),
@ -411,207 +404,15 @@ class ListFiles extends ListRecords
->property('files', $files) ->property('files', $files)
->log(); ->log();
Notification::make() Notification::make()
->title(trans('server/file.actions.delete.bulk_notification', ['count' => count($files)])) ->title(trans('server/file.actions.delete.bulk_notification', ['count' => count($files)]))
->success() ->success()
->send();
}),
]);
}
Activity::event('server:file.write')
->property('file', join_paths($path, $data['name']))
->log();
} catch (FileExistsException) {
AlertBanner::make()
->title('<code>' . $path . '</code> already exists!')
->danger()
->closable()
->send(); ->send();
}),
return [
HeaderAction::make('new_file')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server))
->tooltip(trans('server/file.actions.new_file.title'))
->hiddenLabel()->icon('tabler-file-plus')->iconButton()->iconSize(IconSize::Large)
->color('primary')
->modalSubmitActionLabel(trans('server/file.actions.new_file.create'))
->action(function ($data) {
$path = join_paths($this->path, $data['name']);
try {
$this->getDaemonFileRepository()->putContent($path, $data['editor'] ?? '');
Activity::event('server:file.write')
->property('file', join_paths($path, $data['name']))
->log();
} catch (FileExistsException) {
AlertBanner::make('file_already_exists')
->title('<code>' . $path . '</code> already exists!')
->danger()
->closable()
->send();
$this->redirect(self::getUrl(['path' => dirname($path)]));
}
})
->form([
TextInput::make('name')
->label(trans('server/file.actions.new_file.file_name'))
->required(),
Select::make('lang')
->label(trans('server/file.actions.new_file.syntax'))
->searchable()
->native(false)
->live()
->options(EditorLanguages::class)
->selectablePlaceholder(false)
->afterStateUpdated(fn ($state) => $this->dispatch('setLanguage', lang: $state))
->default(EditorLanguages::plaintext->value),
MonacoEditor::make('editor')
->label('')
->view('filament.plugins.monaco-editor')
->language(fn (Get $get) => $get('lang') ?? 'plaintext'),
]), ]),
HeaderAction::make('new_folder')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server))
->hiddenLabel()->icon('tabler-folder-plus')->iconButton()->iconSize(IconSize::Large)
->tooltip(trans('server/file.actions.new_folder.title'))
->color('primary')
->action(function ($data) {
try {
$this->getDaemonFileRepository()->createDirectory($data['name'], $this->path);
Activity::event('server:file.create-directory')
->property(['directory' => $this->path, 'name' => $data['name']])
->log();
} catch (FileExistsException) {
$path = join_paths($this->path, $data['name']);
AlertBanner::make('folder_already_exists')
->title('<code>' . $path . '</code> already exists!')
->danger()
->closable()
->send();
$this->redirect(self::getUrl(['path' => dirname($path)]));
}
})
->form([
TextInput::make('name')
->label(trans('server/file.actions.new_folder.folder_name'))
->required(),
]),
HeaderAction::make('upload')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server))
->hiddenLabel()->icon('tabler-upload')->iconButton()->iconSize(IconSize::Large)
->tooltip(trans('server/file.actions.upload.title'))
->color('success')
->action(function ($data) {
if (count($data['files']) > 0 && !isset($data['url'])) {
/** @var UploadedFile $file */
foreach ($data['files'] as $file) {
$this->getDaemonFileRepository()->putContent(join_paths($this->path, $file->getClientOriginalName()), $file->getContent());
Activity::event('server:file.uploaded')
->property('directory', $this->path)
->log();
}
return redirect(ListFiles::getUrl(['path' => $this->path]));
})
->schema([
Tabs::make()
->contained(false)
->schema([
Tab::make('Upload Files')
->live()
->schema([
FileUpload::make('files')
->storeFiles(false)
->previewable(false)
->preserveFilenames()
->maxSize((int) round($server->node->upload_size * (config('panel.use_binary_prefix') ? 1.048576 * 1024 : 1000)))
->multiple(),
]),
Tab::make('Upload From URL')
->live()
->disabled(fn (Get $get) => count($get('files')) > 0)
->schema([
TextInput::make('url')
->label('URL')
->url(),
]),
]),
]),
Action::make('search')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_READ, $server))
->hiddenLabel()->iconButton()->iconSize(IconSize::ExtraLarge)
->tooltip('Global Search')
->color('primary')
->icon('tabler-world-search')
->modalSubmitActionLabel('Search')
->schema([
TextInput::make('searchTerm')
->placeholder('Enter a search term, e.g. *.txt')
->required()
->regex('/^[^*]*\*?[^*]*$/')
->minValue(3),
])
->action(fn ($data) => redirect(SearchFiles::getUrl([
'searchTerm' => $data['searchTerm'],
'path' => $this->path,
]))),
]); ]);
} }
return redirect(ListFiles::getUrl(['path' => $this->path]));
})
->form([
Tabs::make()
->contained(false)
->schema([
Tab::make(trans('server/file.actions.upload.from_files'))
->live()
->schema([
FileUpload::make('files')
->storeFiles(false)
->previewable(false)
->preserveFilenames()
->maxSize((int) round($server->node->upload_size * (config('panel.use_binary_prefix') ? 1.048576 * 1024 : 1000)))
->multiple(),
]),
Tab::make(trans('server/file.actions.upload.url'))
->live()
->disabled(fn (Get $get) => count($get('files')) > 0)
->schema([
TextInput::make('url')
->label(trans('server/file.actions.upload.url'))
->url(),
]),
]),
]),
HeaderAction::make('search')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_READ, $server))
->hiddenLabel()->iconButton()->iconSize(IconSize::Large)
->tooltip(trans('server/file.actions.global_search.title'))
->color('primary')
->icon('tabler-world-search')
->modalHeading(trans('server/file.actions.global_search.title'))
->modalSubmitActionLabel(trans('server/file.actions.global_search.search'))
->form([
TextInput::make('searchTerm')
->label(trans('server/file.actions.global_search.search_term'))
->placeholder(trans('server/file.actions.global_search.search_term_placeholder'))
->required()
->regex('/^[^*]*\*?[^*]*$/')
->minValue(3),
])
->action(fn ($data) => redirect(SearchFiles::getUrl([
'searchTerm' => $data['searchTerm'],
'path' => $this->path,
]))),
];
}
/** /**
* @return string[] * @return string[]
*/ */

View File

@ -2,12 +2,15 @@
namespace App\Filament\Server\Resources\ScheduleResource\Pages; namespace App\Filament\Server\Resources\ScheduleResource\Pages;
use App\Filament\Components\Actions\ImportScheduleAction;
use App\Filament\Server\Resources\ScheduleResource; use App\Filament\Server\Resources\ScheduleResource;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Actions\ActionGroup; use Filament\Actions\ActionGroup;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
use Filament\Support\Enums\IconSize;
class ListSchedules extends ListRecords class ListSchedules extends ListRecords
{ {

View File

@ -2,12 +2,28 @@
namespace App\Filament\Server\Resources\UserResource\Pages; namespace App\Filament\Server\Resources\UserResource\Pages;
use App\Facades\Activity;
use App\Filament\Server\Resources\UserResource; use App\Filament\Server\Resources\UserResource;
use App\Models\Permission;
use App\Models\Server;
use App\Services\Subusers\SubuserCreationService;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Exception;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Actions\ActionGroup; use Filament\Actions\ActionGroup;
use Filament\Actions\CreateAction;
use Filament\Facades\Filament;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Support\Enums\IconSize; use Filament\Support\Enums\IconSize;
use Illuminate\Contracts\Support\Htmlable; use Illuminate\Contracts\Support\Htmlable;
@ -18,7 +34,9 @@ class ListUsers extends ListRecords
protected static string $resource = UserResource::class; protected static string $resource = UserResource::class;
/** @return array<Action|ActionGroup> */ /** @return array<Action|ActionGroup>
* @throws Exception
*/
protected function getDefaultHeaderActions(): array protected function getDefaultHeaderActions(): array
{ {
/** @var Server $server */ /** @var Server $server */
@ -54,13 +72,13 @@ class ListUsers extends ListRecords
} }
return [ return [
Actions\CreateAction::make('invite') CreateAction::make('invite')
->hiddenLabel()->iconButton()->iconSize(IconSize::Large) ->hiddenLabel()->iconButton()->iconSize(IconSize::Large)
->icon('tabler-user-plus') ->icon('tabler-user-plus')
->tooltip(trans('server/user.invite_user')) ->tooltip(trans('server/user.invite_user'))
->createAnother(false) ->createAnother(false)
->authorize(fn () => auth()->user()->can(Permission::ACTION_USER_CREATE, $server)) ->authorize(fn () => auth()->user()->can(Permission::ACTION_USER_CREATE, $server))
->form([ ->schema([
Grid::make() Grid::make()
->columnSpanFull() ->columnSpanFull()
->columns([ ->columns([
@ -81,23 +99,15 @@ class ListUsers extends ListRecords
'lg' => 5, 'lg' => 5,
]) ])
->required(), ->required(),
assignAll::make([ Action::make('assignAll')
Action::make('assignAll') ->label(trans('server/user.assign_all'))
->label(trans('server/user.assign_all')) ->action(function (Set $set, Get $get) use ($permissionsArray) {
->action(function (Set $set, Get $get) use ($permissionsArray) { $permissions = $permissionsArray;
$permissions = $permissionsArray; foreach ($permissions as $key => $value) {
foreach ($permissions as $key => $value) { $allValues = array_unique($value);
$allValues = array_unique($value); $set($key, $allValues);
$set($key, $allValues); }
} }),
}),
])
->columnSpan([
'default' => 1,
'sm' => 1,
'md' => 1,
'lg' => 1,
]),
Tabs::make() Tabs::make()
->columnSpanFull() ->columnSpanFull()
->schema($tabs), ->schema($tabs),