fix: add missing single-row restore/actions

This commit is contained in:
Ahmed Darrazi 2025-12-25 14:26:50 +01:00
parent 160c5e42a9
commit 5b59988d48
4 changed files with 227 additions and 0 deletions

View File

@ -71,6 +71,32 @@ public static function table(Table $table): Table
->url(fn (BackupSet $record) => static::getUrl('view', ['record' => $record])) ->url(fn (BackupSet $record) => static::getUrl('view', ['record' => $record]))
->openUrlInNewTab(false), ->openUrlInNewTab(false),
ActionGroup::make([ ActionGroup::make([
Actions\Action::make('restore')
->label('Restore')
->color('success')
->icon('heroicon-o-arrow-uturn-left')
->requiresConfirmation()
->visible(fn (BackupSet $record) => $record->trashed())
->action(function (BackupSet $record, AuditLogger $auditLogger) {
$record->restore();
$record->items()->withTrashed()->restore();
if ($record->tenant) {
$auditLogger->log(
tenant: $record->tenant,
action: 'backup.restored',
resourceType: 'backup_set',
resourceId: (string) $record->id,
status: 'success',
context: ['metadata' => ['name' => $record->name]]
);
}
Notification::make()
->title('Backup set restored')
->success()
->send();
}),
Actions\Action::make('archive') Actions\Action::make('archive')
->label('Archive') ->label('Archive')
->color('danger') ->color('danger')

View File

@ -14,6 +14,7 @@
use App\Services\Intune\PolicyNormalizer; use App\Services\Intune\PolicyNormalizer;
use BackedEnum; use BackedEnum;
use Filament\Actions; use Filament\Actions;
use Filament\Actions\ActionGroup;
use Filament\Actions\BulkAction; use Filament\Actions\BulkAction;
use Filament\Actions\BulkActionGroup; use Filament\Actions\BulkActionGroup;
use Filament\Forms; use Filament\Forms;
@ -332,6 +333,70 @@ public static function table(Table $table): Table
]) ])
->actions([ ->actions([
Actions\ViewAction::make(), Actions\ViewAction::make(),
ActionGroup::make([
Actions\Action::make('ignore')
->label('Ignore')
->icon('heroicon-o-trash')
->color('danger')
->requiresConfirmation()
->visible(fn (Policy $record) => $record->ignored_at === null)
->action(function (Policy $record) {
$record->ignore();
Notification::make()
->title('Policy ignored')
->success()
->send();
}),
Actions\Action::make('restore')
->label('Restore')
->icon('heroicon-o-arrow-uturn-left')
->color('success')
->requiresConfirmation()
->visible(fn (Policy $record) => $record->ignored_at !== null)
->action(function (Policy $record) {
$record->unignore();
Notification::make()
->title('Policy restored')
->success()
->send();
}),
Actions\Action::make('sync')
->label('Sync')
->icon('heroicon-o-arrow-path')
->color('primary')
->requiresConfirmation()
->visible(fn (Policy $record) => $record->ignored_at === null)
->action(function (Policy $record) {
$tenant = Tenant::current();
$user = auth()->user();
$service = app(BulkOperationService::class);
$run = $service->createRun($tenant, $user, 'policy', 'sync', [$record->id], 1);
BulkPolicySyncJob::dispatchSync($run->id);
}),
Actions\Action::make('export')
->label('Export to Backup')
->icon('heroicon-o-archive-box-arrow-down')
->visible(fn (Policy $record) => $record->ignored_at === null)
->form([
Forms\Components\TextInput::make('backup_name')
->label('Backup Name')
->required()
->default(fn () => 'Backup '.now()->toDateTimeString()),
])
->action(function (Policy $record, array $data) {
$tenant = Tenant::current();
$user = auth()->user();
$service = app(BulkOperationService::class);
$run = $service->createRun($tenant, $user, 'policy', 'export', [$record->id], 1);
BulkPolicyExportJob::dispatchSync($run->id, $data['backup_name']);
}),
])->icon('heroicon-o-ellipsis-vertical'),
]) ])
->bulkActions([ ->bulkActions([
BulkActionGroup::make([ BulkActionGroup::make([

View File

@ -182,6 +182,31 @@ public static function table(Table $table): Table
->actions([ ->actions([
Actions\ViewAction::make(), Actions\ViewAction::make(),
ActionGroup::make([ ActionGroup::make([
Actions\Action::make('restore')
->label('Restore')
->color('success')
->icon('heroicon-o-arrow-uturn-left')
->requiresConfirmation()
->visible(fn (RestoreRun $record) => $record->trashed())
->action(function (RestoreRun $record, \App\Services\Intune\AuditLogger $auditLogger) {
$record->restore();
if ($record->tenant) {
$auditLogger->log(
tenant: $record->tenant,
action: 'restore_run.restored',
resourceType: 'restore_run',
resourceId: (string) $record->id,
status: 'success',
context: ['metadata' => ['backup_set_id' => $record->backup_set_id]]
);
}
Notification::make()
->title('Restore run restored')
->success()
->send();
}),
Actions\Action::make('archive') Actions\Action::make('archive')
->label('Archive') ->label('Archive')
->color('danger') ->color('danger')

View File

@ -1,6 +1,7 @@
<?php <?php
use App\Filament\Resources\BackupSetResource\Pages\ListBackupSets; use App\Filament\Resources\BackupSetResource\Pages\ListBackupSets;
use App\Filament\Resources\PolicyResource\Pages\ListPolicies;
use App\Filament\Resources\PolicyVersionResource\Pages\ListPolicyVersions; use App\Filament\Resources\PolicyVersionResource\Pages\ListPolicyVersions;
use App\Filament\Resources\RestoreRunResource\Pages\ListRestoreRuns; use App\Filament\Resources\RestoreRunResource\Pages\ListRestoreRuns;
use App\Filament\Resources\TenantResource\Pages\ListTenants; use App\Filament\Resources\TenantResource\Pages\ListTenants;
@ -125,6 +126,45 @@
]); ]);
}); });
test('backup set can be restored when archived', function () {
$tenant = Tenant::create([
'tenant_id' => 'tenant-restore-backup-set',
'name' => 'Tenant Restore Backup Set',
]);
$backupSet = BackupSet::create([
'tenant_id' => $tenant->id,
'name' => 'Set restore',
'status' => 'completed',
]);
BackupItem::create([
'tenant_id' => $tenant->id,
'backup_set_id' => $backupSet->id,
'policy_id' => null,
'policy_identifier' => 'policy-restore',
'policy_type' => 'deviceConfiguration',
'platform' => 'windows',
'payload' => ['id' => 'policy-restore'],
]);
$user = User::factory()->create();
$this->actingAs($user);
Livewire::test(ListBackupSets::class)
->callTableAction('archive', $backupSet)
->set('tableFilters.trashed.value', 1)
->callTableAction('restore', $backupSet);
$this->assertDatabaseHas('backup_sets', ['id' => $backupSet->id, 'deleted_at' => null]);
$this->assertDatabaseHas('backup_items', ['backup_set_id' => $backupSet->id, 'deleted_at' => null]);
$this->assertDatabaseHas('audit_logs', [
'resource_type' => 'backup_set',
'resource_id' => (string) $backupSet->id,
'action' => 'backup.restored',
]);
});
test('restore run can be archived and force deleted', function () { test('restore run can be archived and force deleted', function () {
$tenant = Tenant::create([ $tenant = Tenant::create([
'tenant_id' => 'tenant-restore-run', 'tenant_id' => 'tenant-restore-run',
@ -161,6 +201,77 @@
]); ]);
}); });
test('restore run can be restored when archived', function () {
$tenant = Tenant::create([
'tenant_id' => 'tenant-restore-restore-run',
'name' => 'Tenant Restore Restore Run',
]);
$backupSet = BackupSet::create([
'tenant_id' => $tenant->id,
'name' => 'Set for restore run restore',
'status' => 'completed',
'item_count' => 1,
]);
$restoreRun = RestoreRun::create([
'tenant_id' => $tenant->id,
'backup_set_id' => $backupSet->id,
'status' => 'completed',
'is_dry_run' => true,
]);
$user = User::factory()->create();
$this->actingAs($user);
Livewire::test(ListRestoreRuns::class)
->callTableAction('archive', $restoreRun)
->set('tableFilters.trashed.value', 1)
->callTableAction('restore', $restoreRun);
$this->assertDatabaseHas('restore_runs', ['id' => $restoreRun->id, 'deleted_at' => null]);
$this->assertDatabaseHas('audit_logs', [
'resource_type' => 'restore_run',
'resource_id' => (string) $restoreRun->id,
'action' => 'restore_run.restored',
]);
});
test('policy can be ignored and restored via row actions', function () {
$tenant = Tenant::create([
'tenant_id' => 'tenant-policy-row-actions',
'name' => 'Tenant Policy Row Actions',
'metadata' => [],
]);
$tenant->makeCurrent();
$policy = Policy::create([
'tenant_id' => $tenant->id,
'external_id' => 'policy-row-1',
'policy_type' => 'deviceConfiguration',
'display_name' => 'Row Action Policy',
'platform' => 'windows',
'last_synced_at' => now(),
]);
$user = User::factory()->create();
$this->actingAs($user);
Livewire::test(ListPolicies::class)
->callTableAction('ignore', $policy);
$policy->refresh();
expect($policy->ignored_at)->not->toBeNull();
Livewire::test(ListPolicies::class)
->set('tableFilters.visibility.value', 'ignored')
->callTableAction('restore', $policy);
$policy->refresh();
expect($policy->ignored_at)->toBeNull();
});
test('policy version can be archived with audit log', function () { test('policy version can be archived with audit log', function () {
$tenant = Tenant::create([ $tenant = Tenant::create([
'tenant_id' => 'tenant-3', 'tenant_id' => 'tenant-3',