diff --git a/app/Actions/PHP/InstallNewPHP.php b/app/Actions/PHP/InstallNewPHP.php index b104975..3b82a06 100755 --- a/app/Actions/PHP/InstallNewPHP.php +++ b/app/Actions/PHP/InstallNewPHP.php @@ -5,16 +5,12 @@ use App\Enums\ServiceStatus; use App\Models\Server; use App\Models\Service; -use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; -use Illuminate\Validation\ValidationException; class InstallNewPHP { public function install(Server $server, array $input): void { - $this->validate($server, $input); - $php = new Service([ 'server_id' => $server->id, 'type' => 'php', @@ -38,22 +34,14 @@ public function install(Server $server, array $input): void })->onConnection('ssh'); } - /** - * @throws ValidationException - */ - private function validate(Server $server, array $input): void + public static function rules(Server $server): array { - Validator::make($input, [ + return [ 'version' => [ 'required', Rule::in(config('core.php_versions')), + Rule::notIn($server->installedPHPVersions()), ], - ])->validate(); - - if (in_array($input['version'], $server->installedPHPVersions())) { - throw ValidationException::withMessages( - ['version' => __('This version is already installed')] - ); - } + ]; } } diff --git a/app/Actions/PHP/InstallPHPExtension.php b/app/Actions/PHP/InstallPHPExtension.php index adf3a43..754aa06 100755 --- a/app/Actions/PHP/InstallPHPExtension.php +++ b/app/Actions/PHP/InstallPHPExtension.php @@ -2,10 +2,10 @@ namespace App\Actions\PHP; +use App\Exceptions\SSHCommandError; use App\Models\Server; use App\Models\Service; use App\SSH\Services\PHP\PHP; -use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; @@ -13,54 +13,53 @@ class InstallPHPExtension { public function install(Server $server, array $input): Service { - $this->validate($server, $input); - /** @var Service $service */ $service = $server->php($input['version']); + + if (in_array($input['extension'], $service->type_data['extensions'] ?? [])) { + throw ValidationException::withMessages([ + 'extension' => 'The extension is already installed.', + ]); + } + $typeData = $service->type_data; $typeData['extensions'] = $typeData['extensions'] ?? []; $typeData['extensions'][] = $input['extension']; $service->type_data = $typeData; $service->save(); - dispatch(function () use ($service, $input) { - /** @var PHP $handler */ - $handler = $service->handler(); - $handler->installExtension($input['extension']); - })->catch(function () use ($service, $input) { - $service->refresh(); - $typeData = $service->type_data; - $typeData['extensions'] = array_values(array_diff($typeData['extensions'], [$input['extension']])); - $service->type_data = $typeData; - $service->save(); - })->onConnection('ssh'); + dispatch( + /** + * @throws SSHCommandError + */ + function () use ($service, $input) { + /** @var PHP $handler */ + $handler = $service->handler(); + $handler->installExtension($input['extension']); + })->catch(function () use ($service, $input) { + $service->refresh(); + $typeData = $service->type_data; + $typeData['extensions'] = array_values(array_diff($typeData['extensions'], [$input['extension']])); + $service->type_data = $typeData; + $service->save(); + })->onConnection('ssh'); return $service; } - /** - * @throws ValidationException - */ - private function validate(Server $server, array $input): void + public static function rules(Server $server): array { - Validator::make($input, [ + return [ 'extension' => [ 'required', - 'in:'.implode(',', config('core.php_extensions')), + Rule::in(config('core.php_extensions')), ], 'version' => [ 'required', - Rule::in($server->installedPHPVersions()), + Rule::exists('services', 'version') + ->where('server_id', $server->id) + ->where('type', 'php'), ], - ])->validate(); - - /** @var Service $service */ - $service = $server->php($input['version']); - - if (in_array($input['extension'], $service->type_data['extensions'])) { - throw ValidationException::withMessages( - ['extension' => __('This extension already installed')] - )->errorBag('installPHPExtension'); - } + ]; } } diff --git a/app/Actions/PHP/UpdatePHPIni.php b/app/Actions/PHP/UpdatePHPIni.php index 2a356c5..1d5521e 100755 --- a/app/Actions/PHP/UpdatePHPIni.php +++ b/app/Actions/PHP/UpdatePHPIni.php @@ -6,7 +6,6 @@ use App\Models\Server; use Illuminate\Filesystem\FilesystemAdapter; use Illuminate\Support\Facades\Storage; -use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; @@ -19,8 +18,6 @@ class UpdatePHPIni */ public function update(Server $server, array $input): void { - $this->validate($server, $input); - $service = $server->php($input['version']); $tmpName = Str::random(10).strtotime('now'); @@ -51,24 +48,23 @@ private function deleteTempFile(string $name): void } } - public function validate(Server $server, array $input): void + public static function rules(Server $server): array { - Validator::make($input, [ + return [ 'ini' => [ 'required', 'string', ], - 'version' => 'required|string', + 'version' => [ + 'required', + Rule::exists('services', 'version') + ->where('server_id', $server->id) + ->where('type', 'php'), + ], 'type' => [ 'required', Rule::in([PHPIniType::CLI, PHPIniType::FPM]), ], - ])->validate(); - - if (! in_array($input['version'], $server->installedPHPVersions())) { - throw ValidationException::withMessages( - ['version' => __('This version is not installed')] - ); - } + ]; } } diff --git a/app/Models/ServerLog.php b/app/Models/ServerLog.php index 36c66f5..a537239 100755 --- a/app/Models/ServerLog.php +++ b/app/Models/ServerLog.php @@ -2,11 +2,13 @@ namespace App\Models; +use Exception; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; +use Symfony\Component\HttpFoundation\StreamedResponse; /** * @property int $server_id @@ -46,7 +48,7 @@ public static function boot(): void if (Storage::disk($log->disk)->exists($log->name)) { Storage::disk($log->disk)->delete($log->name); } - } catch (\Exception $e) { + } catch (Exception $e) { Log::error($e->getMessage(), ['exception' => $e]); } }); @@ -67,6 +69,11 @@ public function site(): BelongsTo return $this->belongsTo(Site::class); } + public function download(): StreamedResponse + { + return Storage::disk($this->disk)->download($this->name); + } + public static function getRemote($query, bool $active = true, ?Site $site = null) { $query->where('is_remote', $active); diff --git a/app/Models/Service.php b/app/Models/Service.php index 39d0d8c..4ae3e95 100755 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Actions\Service\Manage; +use App\Enums\ServiceStatus; use App\Exceptions\ServiceInstallationFailed; use App\SSH\Services\ServiceInterface; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -54,6 +55,21 @@ public static function boot(): void }); } + public static array $statusColors = [ + ServiceStatus::READY => 'success', + ServiceStatus::INSTALLING => 'warning', + ServiceStatus::INSTALLATION_FAILED => 'danger', + ServiceStatus::UNINSTALLING => 'warning', + ServiceStatus::FAILED => 'danger', + ServiceStatus::STARTING => 'warning', + ServiceStatus::STOPPING => 'warning', + ServiceStatus::RESTARTING => 'warning', + ServiceStatus::STOPPED => 'danger', + ServiceStatus::ENABLING => 'warning', + ServiceStatus::DISABLING => 'warning', + ServiceStatus::DISABLED => 'gray', + ]; + public function server(): BelongsTo { return $this->belongsTo(Server::class); @@ -72,7 +88,7 @@ public function handler(): ServiceInterface public function validateInstall($result): void { if (! Str::contains($result, 'Active: active')) { - throw new ServiceInstallationFailed(); + throw new ServiceInstallationFailed; } } diff --git a/app/Policies/ServerLogPolicy.php b/app/Policies/ServerLogPolicy.php new file mode 100644 index 0000000..a80e590 --- /dev/null +++ b/app/Policies/ServerLogPolicy.php @@ -0,0 +1,38 @@ +isAdmin() || $server->project->users->contains($user); + } + + public function view(User $user, ServerLog $serverLog): bool + { + return $user->isAdmin() || $serverLog->server->project->users->contains($user); + } + + public function create(User $user, Server $server): bool + { + return $user->isAdmin() || $server->project->users->contains($user); + } + + public function update(User $user, ServerLog $serverLog): bool + { + return $user->isAdmin() || $serverLog->server->project->users->contains($user); + } + + public function delete(User $user, ServerLog $serverLog): bool + { + return $user->isAdmin() || $serverLog->server->project->users->contains($user); + } +} diff --git a/app/Policies/ServicePolicy.php b/app/Policies/ServicePolicy.php new file mode 100644 index 0000000..24626d6 --- /dev/null +++ b/app/Policies/ServicePolicy.php @@ -0,0 +1,38 @@ +isAdmin() || $server->project->users->contains($user); + } + + public function view(User $user, Service $service): bool + { + return $user->isAdmin() || $service->server->project->users->contains($user); + } + + public function create(User $user, Server $server): bool + { + return $user->isAdmin() || $server->project->users->contains($user); + } + + public function update(User $user, Service $service): bool + { + return $user->isAdmin() || $service->server->project->users->contains($user); + } + + public function delete(User $user, Service $service): bool + { + return $user->isAdmin() || $service->server->project->users->contains($user); + } +} diff --git a/app/Providers/WebServiceProvider.php b/app/Providers/WebServiceProvider.php index b0f446e..f8b2c68 100644 --- a/app/Providers/WebServiceProvider.php +++ b/app/Providers/WebServiceProvider.php @@ -41,7 +41,7 @@ public function boot(): void ); FilamentColor::register([ 'slate' => Color::Slate, - 'gray' => Color::Zinc, + 'gray' => Color::Gray, 'red' => Color::Red, 'orange' => Color::Orange, 'amber' => Color::Amber, diff --git a/app/Web/Pages/Servers/Index.php b/app/Web/Pages/Servers/Index.php index a43906e..09d5e68 100644 --- a/app/Web/Pages/Servers/Index.php +++ b/app/Web/Pages/Servers/Index.php @@ -24,6 +24,11 @@ public static function getNavigationItemActiveRoutePattern(): string return static::getRouteName().'*'; } + public static function canAccess(): bool + { + return auth()->user()?->can('viewAny', Server::class) ?? false; + } + public function getWidgets(): array { return [ @@ -36,6 +41,7 @@ protected function getHeaderActions(): array return [ Action::make('create') ->label('Create a Server') + ->icon('heroicon-o-plus') ->url(Create::getUrl()) ->authorize('create', Server::class), ]; diff --git a/app/Web/Pages/Servers/Logs/Index.php b/app/Web/Pages/Servers/Logs/Index.php new file mode 100644 index 0000000..3342b59 --- /dev/null +++ b/app/Web/Pages/Servers/Logs/Index.php @@ -0,0 +1,41 @@ +user()?->can('viewAny', [ServerLog::class, static::getServerFromRoute()]) ?? false; + } + + public function getWidgets(): array + { + return [ + [LogsList::class, ['server' => $this->server]], + ]; + } + + protected function getHeaderActions(): array + { + return []; + } +} diff --git a/app/Web/Pages/Servers/Logs/Widgets/LogsList.php b/app/Web/Pages/Servers/Logs/Widgets/LogsList.php new file mode 100644 index 0000000..240cbdf --- /dev/null +++ b/app/Web/Pages/Servers/Logs/Widgets/LogsList.php @@ -0,0 +1,91 @@ +where('server_id', $this->server->id); + } + + protected static ?string $heading = 'Logs'; + + protected function getTableColumns(): array + { + return [ + TextColumn::make('name') + ->searchable() + ->sortable(), + TextColumn::make('created_at_by_timezone') + ->label('Created At') + ->sortable(), + ]; + } + + protected function applyDefaultSortingToTableQuery(Builder $query): Builder + { + return $query->latest('created_at'); + } + + /** + * @throws Exception + */ + public function getTable(): Table + { + return $this->table + ->filters([ + Filter::make('created_at') + ->form([ + DatePicker::make('created_from'), + DatePicker::make('created_until'), + ]) + ->query(function (Builder $query, array $data): Builder { + return $query + ->when( + $data['created_from'], + fn (Builder $query, $date): Builder => $query->whereDate('created_at', '>=', $date), + ) + ->when( + $data['created_until'], + fn (Builder $query, $date): Builder => $query->whereDate('created_at', '<=', $date), + ); + }), + ]) + ->actions([ + Action::make('view') + ->label('View') + ->icon('heroicon-o-eye') + ->authorize(fn ($record) => auth()->user()->can('view', $record)) + ->modalHeading('View Log') + ->modalContent(function (ServerLog $record) { + return view('components.console-view', [ + 'slot' => $record->getContent(), + 'attributes' => new ComponentAttributeBag, + ]); + }) + ->modalSubmitAction(false) + ->modalCancelActionLabel('Close'), + Action::make('download') + ->label('Download') + ->color('secondary') + ->icon('heroicon-o-archive-box-arrow-down') + ->authorize(fn ($record) => auth()->user()->can('view', $record)) + ->action(fn (ServerLog $record) => $record->download()), + ]); + } +} diff --git a/app/Web/Pages/Servers/PHP/Index.php b/app/Web/Pages/Servers/PHP/Index.php new file mode 100644 index 0000000..628bf09 --- /dev/null +++ b/app/Web/Pages/Servers/PHP/Index.php @@ -0,0 +1,73 @@ +user()?->can('viewAny', [Service::class, static::getServerFromRoute()]) ?? false; + } + + public function getWidgets(): array + { + return [ + [PHPList::class, ['server' => $this->server]], + ]; + } + + protected function getHeaderActions(): array + { + $phps = []; + foreach (config('core.php_versions') as $version) { + if (! $this->server->service('php', $version) && $version !== 'none') { + $phps[] = Action::make($version) + ->label($version) + ->requiresConfirmation() + ->modalHeading('Install PHP '.$version) + ->modalSubmitActionLabel('Install') + ->action(function () use ($version) { + app(InstallNewPHP::class)->install($this->server, ['version' => $version]); + + $this->dispatch('$refresh'); + }); + } + } + + return [ + ActionGroup::make($phps) + ->authorize(fn () => auth()->user()?->can('create', [Service::class, $this->server])) + ->label('Install PHP') + ->icon('heroicon-o-plus') + ->dropdownPlacement('bottom-end') + ->color('primary') + ->button(), + ]; + } +} diff --git a/app/Web/Pages/Servers/PHP/Widgets/PHPList.php b/app/Web/Pages/Servers/PHP/Widgets/PHPList.php new file mode 100644 index 0000000..c572c9f --- /dev/null +++ b/app/Web/Pages/Servers/PHP/Widgets/PHPList.php @@ -0,0 +1,231 @@ +where('type', 'php')->where('server_id', $this->server->id); + } + + protected static ?string $heading = ''; + + protected function getTableColumns(): array + { + return [ + TextColumn::make('version') + ->sortable(), + TextColumn::make('status') + ->label('Status') + ->badge() + ->color(fn (Service $service) => Service::$statusColors[$service->status]) + ->sortable(), + TextColumn::make('is_default') + ->label('Default Cli') + ->badge() + ->color(fn (Service $service) => $service->is_default ? 'primary' : 'gray') + ->state(fn (Service $service) => $service->is_default ? 'Yes' : 'No') + ->sortable(), + TextColumn::make('created_at_by_timezone') + ->label('Installed At'), + ]; + } + + /** + * @throws Exception + */ + public function getTable(): Table + { + return $this->table + ->actions([ + ActionGroup::make([ + $this->installExtensionAction(), + $this->editPHPIniAction(PHPIniType::CLI), + $this->editPHPIniAction(PHPIniType::FPM), + $this->defaultPHPCliAction(), + $this->restartFPMAction(), + $this->uninstallAction(), + ]), + ]); + } + + private function installExtensionAction(): Action + { + return Action::make('install-extension') + ->authorize(fn (Service $php) => auth()->user()?->can('update', [$php, $this->server])) + ->label('Install Extension') + ->modalHeading('Install PHP Extension') + ->modalWidth(MaxWidth::Large) + ->modalSubmitActionLabel('Install') + ->form([ + Hidden::make('version') + ->default(fn (Service $service) => $service->version) + ->rules(InstallPHPExtension::rules($this->server)['version']), + Select::make('extension') + ->options( + collect(config('core.php_extensions')) + ->mapWithKeys(fn ($extension) => [$extension => $extension]) + ) + ->rules(InstallPHPExtension::rules($this->server)['extension']), + ]) + ->action(function (array $data) { + $this->validate(); + + try { + app(InstallPHPExtension::class)->install($this->server, $data); + } catch (Exception $e) { + Notification::make() + ->danger() + ->title($e->getMessage()) + ->send(); + + throw $e; + } + + $this->dispatch('$refresh'); + }); + } + + private function defaultPHPCliAction(): Action + { + return Action::make('default-php-cli') + ->authorize(fn (Service $php) => auth()->user()?->can('update', [$php, $this->server])) + ->label('Make Default CLI') + ->hidden(fn (Service $service) => $service->is_default) + ->action(function (Service $service) { + try { + app(ChangeDefaultCli::class)->change($this->server, ['version' => $service->version]); + + Notification::make() + ->success() + ->title('Default PHP CLI changed!') + ->send(); + } catch (Exception $e) { + Notification::make() + ->danger() + ->title($e->getMessage()) + ->send(); + + throw $e; + } + + $this->dispatch('$refresh'); + }); + } + + private function editPHPIniAction(string $type): Action + { + return Action::make('php-ini-'.$type) + ->authorize(fn (Service $php) => auth()->user()?->can('update', [$php, $this->server])) + ->label('Update PHP ini ('.$type.')') + ->modalWidth(MaxWidth::TwoExtraLarge) + ->modalSubmitActionLabel('Save') + ->form([ + Hidden::make('type') + ->default($type) + ->rules(UpdatePHPIni::rules($this->server)['type']), + Hidden::make('version') + ->default(fn (Service $service) => $service->version) + ->rules(UpdatePHPIni::rules($this->server)['version']), + Textarea::make('ini') + ->label('PHP ini') + ->rows(20) + ->rules(UpdatePHPIni::rules($this->server)['ini']) + ->default(fn (Service $service) => app(GetPHPIni::class)->getIni($this->server, [ + 'type' => $type, + 'version' => $service->version, + ])), + ]) + ->action(function (array $data) { + $this->validate(); + + try { + app(UpdatePHPIni::class)->update($this->server, $data); + + Notification::make() + ->success() + ->title('PHP ini updated!') + ->body('Restarting PHP...') + ->send(); + } catch (Exception $e) { + Notification::make() + ->danger() + ->title($e->getMessage()) + ->send(); + + throw $e; + } + + $this->dispatch('$refresh'); + }); + } + + private function restartFPMAction(): Action + { + return Action::make('restart-fpm') + ->authorize(fn (Service $php) => auth()->user()?->can('update', [$php, $this->server])) + ->label('Restart PHP FPM') + ->action(function (Service $service) { + try { + app(Manage::class)->restart($service); + } catch (Exception $e) { + Notification::make() + ->danger() + ->title($e->getMessage()) + ->send(); + + throw $e; + } + + $this->dispatch('$refresh'); + }); + } + + private function uninstallAction(): Action + { + return Action::make('uninstall') + ->authorize(fn (Service $php) => auth()->user()?->can('delete', [$php, $this->server])) + ->label('Uninstall') + ->color('danger') + ->requiresConfirmation() + ->action(function (Service $service) { + try { + app(Uninstall::class)->uninstall($service); + } catch (Exception $e) { + Notification::make() + ->danger() + ->title($e->getMessage()) + ->send(); + + throw $e; + } + + $this->dispatch('$refresh'); + }); + } +} diff --git a/app/Web/Pages/Servers/Settings.php b/app/Web/Pages/Servers/Settings.php index 0fc860d..1247f64 100644 --- a/app/Web/Pages/Servers/Settings.php +++ b/app/Web/Pages/Servers/Settings.php @@ -30,7 +30,7 @@ class Settings extends Page public static function canAccess(): bool { - return auth()->user()?->can('update', request()->route('server')) ?? false; + return auth()->user()?->can('update', static::getServerFromRoute()) ?? false; } public function getWidgets(): array diff --git a/app/Web/Pages/Servers/Sites/Index.php b/app/Web/Pages/Servers/Sites/Index.php index 6b85947..cc86d61 100644 --- a/app/Web/Pages/Servers/Sites/Index.php +++ b/app/Web/Pages/Servers/Sites/Index.php @@ -23,6 +23,11 @@ class Index extends Page public Server $server; + public static function canAccess(): bool + { + return auth()->user()?->can('viewAny', [Site::class, static::getServerFromRoute()]) ?? false; + } + public function getWidgets(): array { return [ @@ -36,7 +41,8 @@ protected function getHeaderActions(): array CreateAction::make() ->authorize(fn () => auth()->user()?->can('create', [Site::class, $this->server])) ->createAnother(false) - ->label('Create a Site'), + ->label('Create a Site') + ->icon('heroicon-o-plus'), ]; } } diff --git a/app/Web/Pages/Servers/View.php b/app/Web/Pages/Servers/View.php index d9f614d..1437b30 100644 --- a/app/Web/Pages/Servers/View.php +++ b/app/Web/Pages/Servers/View.php @@ -3,7 +3,10 @@ namespace App\Web\Pages\Servers; use App\Models\Server; +use App\Models\ServerLog; +use App\Web\Pages\Servers\Logs\Widgets\LogsList; use App\Web\Pages\Servers\Widgets\Installing; +use App\Web\Pages\Servers\Widgets\ServerStats; use App\Web\Traits\PageHasServer; use App\Web\Traits\PageHasWidgets; use Filament\Pages\Page; @@ -31,7 +34,7 @@ public function mount(): void public static function canAccess(): bool { - return auth()->user()?->can('view', request()->route('server')) ?? false; + return auth()->user()?->can('view', static::getServerFromRoute()) ?? false; } #[On('$refresh')] @@ -48,12 +51,18 @@ public function refresh(): void public function getWidgets(): array { + $widgets = []; + if ($this->server->isInstalling()) { - return [ - [Installing::class, ['server' => $this->server]], - ]; + $widgets[] = [Installing::class, ['server' => $this->server]]; + } else { + $widgets[] = [ServerStats::class, ['server' => $this->server]]; } - return []; + if (auth()->user()->can('viewAny', [ServerLog::class, $this->server])) { + $widgets[] = [LogsList::class, ['server' => $this->server]]; + } + + return $widgets; } } diff --git a/app/Web/Pages/Servers/Widgets/ServerStats.php b/app/Web/Pages/Servers/Widgets/ServerStats.php new file mode 100644 index 0000000..fdce5dd --- /dev/null +++ b/app/Web/Pages/Servers/Widgets/ServerStats.php @@ -0,0 +1,36 @@ +server->webserver()) { + $stats[] = Stat::make('Sites', $this->server->sites()->count()) + ->icon('heroicon-o-globe-alt'); + } + + if ($this->server->database()) { + $stats[] = Stat::make('Databases', $this->server->databases()->count()) + ->icon('heroicon-o-circle-stack'); + } + + if ($this->server->defaultService('php')) { + $stats[] = Stat::make('PHP Version', $this->server->defaultService('php')->version) + ->icon('heroicon-o-command-line'); + } + + return $stats; + } +} diff --git a/app/Web/Pages/Settings/Projects/Settings.php b/app/Web/Pages/Settings/Projects/Settings.php index 961efa9..6555ef1 100644 --- a/app/Web/Pages/Settings/Projects/Settings.php +++ b/app/Web/Pages/Settings/Projects/Settings.php @@ -60,6 +60,7 @@ protected function getHeaderActions(): array DeleteAction::make() ->record($this->project) ->label('Delete Project') + ->icon('heroicon-o-trash') ->modalHeading('Delete Project') ->modalDescription('Are you sure you want to delete this project? This action will delete all associated data and cannot be undone.') ->using(function (Project $record) { diff --git a/app/Web/Pages/Settings/ServerProviders/Index.php b/app/Web/Pages/Settings/ServerProviders/Index.php index 67b26b7..2b9b380 100644 --- a/app/Web/Pages/Settings/ServerProviders/Index.php +++ b/app/Web/Pages/Settings/ServerProviders/Index.php @@ -39,6 +39,7 @@ protected function getHeaderActions(): array return [ CreateAction::make() ->label('Connect') + ->icon('heroicon-o-wifi') ->modalHeading('Connect to a Server Provider') ->modalSubmitActionLabel('Connect') ->createAnother(false) diff --git a/app/Web/Pages/Settings/Users/Index.php b/app/Web/Pages/Settings/Users/Index.php index 99f8a8b..e2b29f7 100644 --- a/app/Web/Pages/Settings/Users/Index.php +++ b/app/Web/Pages/Settings/Users/Index.php @@ -43,6 +43,7 @@ protected function getHeaderActions(): array return [ CreateAction::make() ->label('Create User') + ->icon('heroicon-o-plus') ->authorize('create', User::class) ->action(function (array $data) { $user = app(CreateUser::class)->create($data); diff --git a/app/Web/Traits/PageHasServer.php b/app/Web/Traits/PageHasServer.php index 656b132..f09f7ba 100644 --- a/app/Web/Traits/PageHasServer.php +++ b/app/Web/Traits/PageHasServer.php @@ -2,13 +2,17 @@ namespace App\Web\Traits; -use App\Models\Site; -use App\Web\Pages\Servers\Settings; -use App\Web\Pages\Servers\Sites\Index; -use App\Web\Pages\Servers\View; +use App\Models\Server; +use App\Web\Pages\Servers\Logs\Index as LogsIndex; +use App\Web\Pages\Servers\PHP\Index as PHPIndex; +use App\Web\Pages\Servers\Settings as ServerSettings; +use App\Web\Pages\Servers\Sites\Index as SitesIndex; +use App\Web\Pages\Servers\View as ServerView; use App\Web\Pages\Servers\Widgets\ServerSummary; use Filament\Navigation\NavigationItem; use Illuminate\Contracts\Support\Htmlable; +use Illuminate\Support\Facades\Request; +use Illuminate\Support\Facades\Route; trait PageHasServer { @@ -21,25 +25,39 @@ public function getSubNavigation(): array { $items = []; - if (auth()->user()?->can('view', $this->server)) { + if (ServerView::canAccess()) { $items[] = NavigationItem::make('Overview') ->icon('heroicon-o-chart-pie') - ->isActiveWhen(fn () => request()->routeIs(View::getRouteName())) - ->url(View::getUrl(parameters: ['server' => $this->server])); + ->isActiveWhen(fn () => request()->routeIs(ServerView::getRouteName())) + ->url(ServerView::getUrl(parameters: ['server' => $this->server])); } - if (auth()->user()?->can('viewAny', [Site::class, $this->server])) { + if (SitesIndex::canAccess()) { $items[] = NavigationItem::make('Sites') ->icon('heroicon-o-globe-alt') - ->isActiveWhen(fn () => request()->routeIs(Index::getRouteName().'*')) - ->url(Index::getUrl(parameters: ['server' => $this->server])); + ->isActiveWhen(fn () => request()->routeIs(SitesIndex::getRouteName().'*')) + ->url(SitesIndex::getUrl(parameters: ['server' => $this->server])); } - if (auth()->user()?->can('update', $this->server)) { + if (PHPIndex::canAccess()) { + $items[] = NavigationItem::make('PHP') + ->icon('heroicon-o-code-bracket') + ->isActiveWhen(fn () => request()->routeIs(PHPIndex::getRouteName().'*')) + ->url(PHPIndex::getUrl(parameters: ['server' => $this->server])); + } + + if (LogsIndex::canAccess()) { + $items[] = NavigationItem::make('Logs') + ->icon('heroicon-o-square-3-stack-3d') + ->isActiveWhen(fn () => request()->routeIs(LogsIndex::getRouteName().'*')) + ->url(LogsIndex::getUrl(parameters: ['server' => $this->server])); + } + + if (ServerSettings::canAccess()) { $items[] = NavigationItem::make('Settings') ->icon('heroicon-o-cog-6-tooth') - ->isActiveWhen(fn () => request()->routeIs(Settings::getRouteName().'*')) - ->url(Settings::getUrl(parameters: ['server' => $this->server])); + ->isActiveWhen(fn () => request()->routeIs(ServerSettings::getRouteName().'*')) + ->url(ServerSettings::getUrl(parameters: ['server' => $this->server])); } return $items; @@ -54,6 +72,25 @@ protected function getHeaderWidgets(): array ]; } + protected static function getServerFromRoute(): ?Server + { + $server = request()->route('server'); + + if (! $server) { + $server = Route::getRoutes()->match(Request::create(url()->previous()))->parameter('server'); + } + + if ($server instanceof Server) { + return $server; + } + + if ($server) { + return Server::query()->find($server); + } + + return null; + } + public function getHeaderWidgetsColumns(): int|string|array { return 1;