TenantAtlas/app/Filament/Resources/BackupSetResource/RelationManagers/BackupItemsRelationManager.php
Ahmed Darrazi f314703ac6 fix: capture assignments when adding policies to backup sets
- Add includeAssignments parameter to addPoliciesToSet method
- Add 'Include Assignments' checkbox to UI (default: true)
- Fix AssignmentFetcher to use request() instead of non-existent get()
- Fix GroupResolver to use request() instead of non-existent post()
- Replace GraphLogger calls with Laravel Log facade
- Add tests for addPoliciesToSet with/without assignments
2025-12-22 17:17:52 +01:00

183 lines
7.9 KiB
PHP

<?php
namespace App\Filament\Resources\BackupSetResource\RelationManagers;
use App\Filament\Resources\PolicyResource;
use App\Models\BackupItem;
use App\Models\Policy;
use App\Models\Tenant;
use App\Services\Intune\AuditLogger;
use App\Services\Intune\BackupService;
use Filament\Actions;
use Filament\Forms;
use Filament\Notifications\Notification;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
class BackupItemsRelationManager extends RelationManager
{
protected static string $relationship = 'items';
public function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('policy.display_name')
->label('Policy')
->sortable()
->searchable(),
Tables\Columns\TextColumn::make('policy_type')
->label('Type')
->badge()
->formatStateUsing(fn (?string $state) => static::typeMeta($state)['label'] ?? $state),
Tables\Columns\TextColumn::make('restore_mode')
->label('Restore')
->badge()
->state(fn (BackupItem $record) => static::typeMeta($record->policy_type)['restore'] ?? 'enabled')
->color(fn (?string $state) => $state === 'preview-only' ? 'warning' : 'success'),
Tables\Columns\TextColumn::make('risk')
->label('Risk')
->badge()
->state(fn (BackupItem $record) => static::typeMeta($record->policy_type)['risk'] ?? 'n/a')
->color(fn (?string $state) => str_contains((string) $state, 'high') ? 'danger' : 'gray'),
Tables\Columns\TextColumn::make('policy_identifier')
->label('Policy ID')
->copyable(),
Tables\Columns\TextColumn::make('platform')->badge(),
Tables\Columns\TextColumn::make('metadata.assignment_count')
->label('Assignments')
->default('0')
->badge()
->color('info'),
Tables\Columns\TextColumn::make('metadata.scope_tag_names')
->label('Scope Tags')
->badge()
->separator(',')
->default('—')
->formatStateUsing(function ($state) {
if (empty($state)) {
return '—';
}
if (is_array($state)) {
return implode(', ', $state);
}
return $state;
}),
Tables\Columns\TextColumn::make('captured_at')->dateTime(),
Tables\Columns\TextColumn::make('created_at')->since(),
])
->filters([])
->headerActions([
Actions\Action::make('addPolicies')
->label('Add Policies')
->icon('heroicon-o-plus')
->form([
Forms\Components\Select::make('policy_ids')
->label('Policies')
->multiple()
->required()
->searchable()
->options(function (RelationManager $livewire) {
$backupSet = $livewire->getOwnerRecord();
$tenantId = $backupSet?->tenant_id ?? Tenant::current()->getKey();
$existing = $backupSet
? $backupSet->items()->pluck('policy_id')->filter()->all()
: [];
return Policy::query()
->where('tenant_id', $tenantId)
->where('last_synced_at', '>', now()->subDays(7)) // Hide deleted policies (Feature 005 workaround)
->when($existing, fn (Builder $query) => $query->whereNotIn('id', $existing))
->orderBy('display_name')
->pluck('display_name', 'id');
}),
Forms\Components\Checkbox::make('include_assignments')
->label('Include Assignments')
->default(true)
->helperText('Capture policy assignments and scope tags'),
])
->action(function (array $data, BackupService $service) {
if (empty($data['policy_ids'])) {
Notification::make()
->title('No policies selected')
->warning()
->send();
return;
}
$backupSet = $this->getOwnerRecord();
$tenant = $backupSet?->tenant ?? Tenant::current();
$service->addPoliciesToSet(
tenant: $tenant,
backupSet: $backupSet,
policyIds: $data['policy_ids'],
actorEmail: auth()->user()?->email,
actorName: auth()->user()?->name,
includeAssignments: $data['include_assignments'] ?? false,
);
Notification::make()
->title('Policies added to backup')
->success()
->send();
}),
])
->actions([
Actions\ViewAction::make()
->label('View policy')
->url(fn ($record) => $record->policy_id ? PolicyResource::getUrl('view', ['record' => $record->policy_id]) : null)
->hidden(fn ($record) => ! $record->policy_id)
->openUrlInNewTab(true),
Actions\Action::make('remove')
->label('Remove')
->color('danger')
->icon('heroicon-o-x-mark')
->requiresConfirmation()
->action(function (BackupItem $record, AuditLogger $auditLogger) {
$record->delete();
if ($record->backupSet) {
$record->backupSet->update([
'item_count' => $record->backupSet->items()->count(),
]);
}
if ($record->tenant) {
$auditLogger->log(
tenant: $record->tenant,
action: 'backup.item_removed',
resourceType: 'backup_set',
resourceId: (string) $record->backup_set_id,
status: 'success',
context: ['metadata' => ['policy_id' => $record->policy_id]]
);
}
Notification::make()
->title('Policy removed from backup')
->success()
->send();
}),
])
->bulkActions([]);
}
/**
* @return array{label:?string,category:?string,restore:?string,risk:?string}|array<string,mixed>
*/
private static function typeMeta(?string $type): array
{
if ($type === null) {
return [];
}
return collect(config('tenantpilot.supported_policy_types', []))
->firstWhere('type', $type) ?? [];
}
}