backup_limit; return $server->backups->count() . ($limit === 0 ? '' : ' / ' . $limit); } public static function getNavigationBadgeColor(): ?string { /** @var Server $server */ $server = Filament::getTenant(); $limit = $server->backup_limit; $count = $server->backups->count(); if ($limit === 0) { return null; } return $count >= $limit ? 'danger' : ($count >= $limit * self::WARNING_THRESHOLD ? 'warning' : 'success'); } public static function form(Form $form): Form { return $form ->schema([ TextInput::make('name') ->label('Name') ->columnSpanFull(), TextArea::make('ignored') ->columnSpanFull() ->label('Ignored Files & Directories'), Toggle::make('is_locked') ->label('Lock?') ->helperText('Prevents this backup from being deleted until explicitly unlocked.'), ]); } public static function table(Table $table): Table { /** @var Server $server */ $server = Filament::getTenant(); return $table ->columns([ TextColumn::make('name') ->searchable(), BytesColumn::make('bytes') ->label('Size'), DateTimeColumn::make('created_at') ->label('Created') ->since() ->sortable(), TextColumn::make('status') ->label('Status') ->badge(), IconColumn::make('is_locked') ->visibleFrom('md') ->label('Lock Status') ->trueIcon('tabler-lock') ->falseIcon('tabler-lock-open'), ]) ->actions([ ActionGroup::make([ Action::make('lock') ->icon(fn (Backup $backup) => !$backup->is_locked ? 'tabler-lock' : 'tabler-lock-open') ->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) ->label(fn (Backup $backup) => !$backup->is_locked ? 'Lock' : 'Unlock') ->action(fn (BackupController $backupController, Backup $backup, Request $request) => $backupController->toggleLock($request, $server, $backup)) ->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful), Action::make('download') ->color('primary') ->icon('tabler-download') ->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_DOWNLOAD, $server)) ->url(fn (DownloadLinkService $downloadLinkService, Backup $backup, Request $request) => $downloadLinkService->handle($backup, $request->user()), true) ->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful), Action::make('restore') ->color('success') ->icon('tabler-folder-up') ->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_RESTORE, $server)) ->form([ Placeholder::make('') ->helperText('Your server will be stopped. You will not be able to control the power state, access the file manager, or create additional backups until this process is completed.'), Checkbox::make('truncate') ->label('Delete all files before restoring backup?'), ]) ->action(function (Backup $backup, $data, DaemonBackupRepository $daemonRepository, DownloadLinkService $downloadLinkService) use ($server) { if (!is_null($server->status)) { return Notification::make() ->danger() ->title('Backup Restore Failed') ->body('This server is not currently in a state that allows for a backup to be restored.') ->send(); } if (!$backup->is_successful && is_null($backup->completed_at)) { //TODO Change to Notifications return Notification::make() ->danger() ->title('Backup Restore Failed') ->body('This backup cannot be restored at this time: not completed or failed.') ->send(); } $log = Activity::event('server:backup.restore') ->subject($backup) ->property(['name' => $backup->name, 'truncate' => $data['truncate']]); $log->transaction(function () use ($downloadLinkService, $daemonRepository, $backup, $server, $data) { // If the backup is for an S3 file we need to generate a unique Download link for // it that will allow daemon to actually access the file. if ($backup->disk === Backup::ADAPTER_AWS_S3) { $url = $downloadLinkService->handle($backup, auth()->user()); } // Update the status right away for the server so that we know not to allow certain // actions against it via the Panel API. $server->update(['status' => ServerState::RestoringBackup]); $daemonRepository->setServer($server)->restore($backup, $url ?? null, $data['truncate']); }); return Notification::make() ->title('Restoring Backup') ->send(); }) ->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful), DeleteAction::make('delete') ->disabled(fn (Backup $backup) => $backup->is_locked) ->modalDescription(fn (Backup $backup) => 'Do you wish to delete, ' . $backup->name . '?') ->modalSubmitActionLabel('Delete Backup') ->action(fn (BackupController $backupController, Backup $backup, Request $request) => $backupController->delete($request, $server, $backup)) ->visible(fn (Backup $backup) => $backup->status !== BackupStatus::InProgress), ]), ]); } // TODO: find better way handle server conflict state public static function canAccess(): bool { /** @var Server $server */ $server = Filament::getTenant(); if ($server->isInConflictState()) { return false; } return parent::canAccess(); } public static function canViewAny(): bool { return auth()->user()->can(Permission::ACTION_BACKUP_READ, Filament::getTenant()); } public static function canCreate(): bool { return auth()->user()->can(Permission::ACTION_BACKUP_CREATE, Filament::getTenant()); } public static function canDelete(Model $record): bool { return auth()->user()->can(Permission::ACTION_BACKUP_DELETE, Filament::getTenant()); } public static function getPages(): array { return [ 'index' => Pages\ListBackups::route('/'), ]; } }