mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-02 22:46:16 +00:00
2.x - backups
This commit is contained in:
@ -2,12 +2,22 @@
|
||||
|
||||
namespace App\Web\Pages\Servers\Databases;
|
||||
|
||||
use App\Actions\Database\CreateBackup;
|
||||
use App\Models\Backup;
|
||||
use App\Models\Server;
|
||||
use App\Models\StorageProvider;
|
||||
use App\Web\Components\Page;
|
||||
use App\Web\Contracts\HasSecondSubNav;
|
||||
use App\Web\Pages\Settings\StorageProviders\Actions\Create;
|
||||
use App\Web\Traits\PageHasServer;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
|
||||
class Backups extends Page
|
||||
class Backups extends Page implements HasSecondSubNav
|
||||
{
|
||||
use PageHasServer;
|
||||
use Traits\Navigation;
|
||||
@ -28,7 +38,73 @@ public static function canAccess(): bool
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('Create backup')
|
||||
->icon('heroicon-o-plus')
|
||||
->modalWidth(MaxWidth::Large)
|
||||
->authorize(fn () => auth()->user()?->can('create', [Backup::class, $this->server]))
|
||||
->form([
|
||||
Select::make('database')
|
||||
->label('Database')
|
||||
->options($this->server->databases()->pluck('name', 'id')->toArray())
|
||||
->rules(fn (callable $get) => CreateBackup::rules($this->server, $get())['database'])
|
||||
->searchable(),
|
||||
Select::make('storage')
|
||||
->label('Storage')
|
||||
->options(StorageProvider::getByProjectId($this->server->project_id)->pluck('profile', 'id')->toArray())
|
||||
->rules(fn (callable $get) => CreateBackup::rules($this->server, $get())['storage'])
|
||||
->suffixAction(
|
||||
\Filament\Forms\Components\Actions\Action::make('connect')
|
||||
->form(Create::form())
|
||||
->modalHeading('Connect to a new storage provider')
|
||||
->modalSubmitActionLabel('Connect')
|
||||
->icon('heroicon-o-wifi')
|
||||
->tooltip('Connect to a new storage provider')
|
||||
->modalWidth(MaxWidth::Medium)
|
||||
->authorize(fn () => auth()->user()->can('create', StorageProvider::class))
|
||||
->action(fn (array $data) => Create::action($data))
|
||||
),
|
||||
Select::make('interval')
|
||||
->label('Interval')
|
||||
->options(config('core.cronjob_intervals'))
|
||||
->reactive()
|
||||
->rules(fn (callable $get) => CreateBackup::rules($this->server, $get())['interval']),
|
||||
TextInput::make('custom_interval')
|
||||
->label('Custom Interval (Cron)')
|
||||
->rules(fn (callable $get) => CreateBackup::rules($this->server, $get())['custom_interval'])
|
||||
->visible(fn (callable $get) => $get('interval') === 'custom')
|
||||
->placeholder('0 * * * *'),
|
||||
TextInput::make('keep')
|
||||
->label('Backups to Keep')
|
||||
->rules(fn (callable $get) => CreateBackup::rules($this->server, $get())['keep'])
|
||||
->helperText('How many backups to keep before deleting the oldest one'),
|
||||
])
|
||||
->modalSubmitActionLabel('Create')
|
||||
->action(function (array $data) {
|
||||
try {
|
||||
app(CreateBackup::class)->create($this->server, $data);
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Backup created!')
|
||||
->send();
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
[Widgets\BackupsList::class, ['server' => $this->server]],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
use App\Models\Database;
|
||||
use App\Models\Server;
|
||||
use App\Web\Components\Page;
|
||||
use App\Web\Contracts\HasSecondSubNav;
|
||||
use App\Web\Traits\PageHasServer;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
@ -14,7 +15,7 @@
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
|
||||
class Index extends Page
|
||||
class Index extends Page implements HasSecondSubNav
|
||||
{
|
||||
use PageHasServer;
|
||||
use Traits\Navigation;
|
||||
|
@ -10,8 +10,6 @@
|
||||
|
||||
trait Navigation
|
||||
{
|
||||
public bool $hasSecondSubNavigation = true;
|
||||
|
||||
public function getSecondSubNavigation(): array
|
||||
{
|
||||
$items = [];
|
||||
@ -32,7 +30,7 @@ public function getSecondSubNavigation(): array
|
||||
|
||||
if (Backups::canAccess()) {
|
||||
$items[] = NavigationItem::make(Backups::getNavigationLabel())
|
||||
->icon('heroicon-o-circle-stack')
|
||||
->icon('heroicon-o-cloud')
|
||||
->isActiveWhen(fn () => request()->routeIs(Backups::getRouteName()))
|
||||
->url(Backups::getUrl(parameters: ['server' => $this->server]));
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
use App\Models\DatabaseUser;
|
||||
use App\Models\Server;
|
||||
use App\Web\Components\Page;
|
||||
use App\Web\Contracts\HasSecondSubNav;
|
||||
use App\Web\Traits\PageHasServer;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
@ -15,7 +16,7 @@
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
|
||||
class Users extends Page
|
||||
class Users extends Page implements HasSecondSubNav
|
||||
{
|
||||
use PageHasServer;
|
||||
use Traits\Navigation;
|
||||
|
118
app/Web/Pages/Servers/Databases/Widgets/BackupFilesList.php
Normal file
118
app/Web/Pages/Servers/Databases/Widgets/BackupFilesList.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Databases\Widgets;
|
||||
|
||||
use App\Actions\Database\RestoreBackup;
|
||||
use App\Models\Backup;
|
||||
use App\Models\BackupFile;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget as Widget;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class BackupFilesList extends Widget
|
||||
{
|
||||
public Backup $backup;
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
protected function getTableQuery(): Builder
|
||||
{
|
||||
return BackupFile::query()->where('backup_id', $this->backup->id);
|
||||
}
|
||||
|
||||
protected static ?string $heading = '';
|
||||
|
||||
protected function getTableColumns(): array
|
||||
{
|
||||
return [
|
||||
TextColumn::make('name')
|
||||
->searchable(),
|
||||
TextColumn::make('created_at')
|
||||
->formatStateUsing(fn (BackupFile $record) => $record->created_at_by_timezone)
|
||||
->sortable(),
|
||||
TextColumn::make('restored_to')
|
||||
->searchable(),
|
||||
TextColumn::make('restored_at')
|
||||
->formatStateUsing(fn (BackupFile $record) => $record->getDateTimeByTimezone($record->restored_at))
|
||||
->sortable(),
|
||||
TextColumn::make('status')
|
||||
->badge()
|
||||
->color(fn ($state) => BackupFile::$statusColors[$state])
|
||||
->sortable(),
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
protected function applyDefaultSortingToTableQuery(Builder $query): Builder
|
||||
{
|
||||
return $query->latest('created_at');
|
||||
}
|
||||
|
||||
public function getTable(): Table
|
||||
{
|
||||
return $this->table
|
||||
->actions([
|
||||
Action::make('restore')
|
||||
->hiddenLabel()
|
||||
->icon('heroicon-o-arrow-path')
|
||||
->modalHeading('Restore Backup')
|
||||
->tooltip('Restore Backup')
|
||||
->authorize(fn (BackupFile $record) => auth()->user()->can('update', $record->backup->database))
|
||||
->form([
|
||||
Select::make('database')
|
||||
->label('Restore to')
|
||||
->options($this->backup->server->databases()->pluck('name', 'id'))
|
||||
->rules(RestoreBackup::rules()['database'])
|
||||
->native(false),
|
||||
])
|
||||
->modalWidth(MaxWidth::Large)
|
||||
->action(function (BackupFile $record, array $data) {
|
||||
try {
|
||||
app(RestoreBackup::class)->restore($record, $data);
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Backup is being restored')
|
||||
->send();
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
Action::make('delete')
|
||||
->hiddenLabel()
|
||||
->icon('heroicon-o-trash')
|
||||
->modalHeading('Delete Database')
|
||||
->color('danger')
|
||||
->tooltip('Delete')
|
||||
->authorize(fn (BackupFile $record) => auth()->user()->can('delete', $record))
|
||||
->requiresConfirmation()
|
||||
->action(function (BackupFile $record) {
|
||||
try {
|
||||
$record->delete();
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
113
app/Web/Pages/Servers/Databases/Widgets/BackupsList.php
Normal file
113
app/Web/Pages/Servers/Databases/Widgets/BackupsList.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Databases\Widgets;
|
||||
|
||||
use App\Actions\Database\RunBackup;
|
||||
use App\Models\Backup;
|
||||
use App\Models\BackupFile;
|
||||
use App\Models\Server;
|
||||
use Exception;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget as Widget;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class BackupsList extends Widget
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
protected function getTableQuery(): Builder
|
||||
{
|
||||
return Backup::query()->where('server_id', $this->server->id);
|
||||
}
|
||||
|
||||
protected static ?string $heading = '';
|
||||
|
||||
protected function getTableColumns(): array
|
||||
{
|
||||
return [
|
||||
TextColumn::make('database.name')
|
||||
->label('Database')
|
||||
->searchable(),
|
||||
TextColumn::make('storage.profile')
|
||||
->label('Storage')
|
||||
->searchable(),
|
||||
TextColumn::make('status')
|
||||
->label('Status')
|
||||
->badge()
|
||||
->color(fn (Backup $backup) => Backup::$statusColors[$backup->status])
|
||||
->sortable(),
|
||||
TextColumn::make('lastFile.status')
|
||||
->label('Last file status')
|
||||
->badge()
|
||||
->color(fn ($state) => BackupFile::$statusColors[$state])
|
||||
->sortable(),
|
||||
TextColumn::make('created_at')
|
||||
->label('Created At')
|
||||
->formatStateUsing(fn ($record) => $record->created_at_by_timezone)
|
||||
->sortable(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getTable(): Table
|
||||
{
|
||||
return $this->table
|
||||
->actions([
|
||||
Action::make('files')
|
||||
->hiddenLabel()
|
||||
->icon('heroicon-o-rectangle-stack')
|
||||
->modalHeading('Backup Files')
|
||||
->color('secondary')
|
||||
->tooltip('Show backup files')
|
||||
->authorize(fn (Backup $record) => auth()->user()->can('viewAny', [BackupFile::class, $record]))
|
||||
->modalContent(fn (Backup $record) => view('web.components.dynamic-widget', [
|
||||
'widget' => BackupFilesList::class,
|
||||
'params' => [
|
||||
'backup' => $record,
|
||||
],
|
||||
]))
|
||||
->modalWidth(MaxWidth::FiveExtraLarge)
|
||||
->slideOver()
|
||||
->modalSubmitAction(false)
|
||||
->modalCancelActionLabel('Close')
|
||||
->modalFooterActions([
|
||||
Action::make('backup')
|
||||
->label('Run Backup')
|
||||
->icon('heroicon-o-play')
|
||||
->color('primary')
|
||||
->action(function (Backup $record) {
|
||||
app(RunBackup::class)->run($record);
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
]),
|
||||
Action::make('delete')
|
||||
->hiddenLabel()
|
||||
->icon('heroicon-o-trash')
|
||||
->modalHeading('Delete Database')
|
||||
->color('danger')
|
||||
->tooltip('Delete')
|
||||
->authorize(fn (Backup $record) => auth()->user()->can('delete', $record))
|
||||
->requiresConfirmation()
|
||||
->action(function (Backup $record) {
|
||||
try {
|
||||
$record->delete();
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user