Implements Spec 114 System Console Control Tower pages, widgets, triage actions, directory views, and enterprise polish (badges, repair workspace owners table, health indicator).
191 lines
7.7 KiB
PHP
191 lines
7.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Filament\System\Pages\Ops;
|
|
|
|
use App\Models\OperationRun;
|
|
use App\Models\PlatformUser;
|
|
use App\Services\SystemConsole\OperationRunTriageService;
|
|
use App\Support\Auth\PlatformCapabilities;
|
|
use App\Support\Badges\BadgeDomain;
|
|
use App\Support\Badges\BadgeRenderer;
|
|
use App\Support\OperationCatalog;
|
|
use App\Support\OperationRunOutcome;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\OpsUx\OperationUxPresenter;
|
|
use App\Support\System\SystemOperationRunLinks;
|
|
use Filament\Actions\Action;
|
|
use Filament\Forms\Components\Textarea;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Pages\Page;
|
|
use Filament\Tables\Columns\TextColumn;
|
|
use Filament\Tables\Concerns\InteractsWithTable;
|
|
use Filament\Tables\Contracts\HasTable;
|
|
use Filament\Tables\Table;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
|
|
class Failures extends Page implements HasTable
|
|
{
|
|
use InteractsWithTable;
|
|
|
|
protected static ?string $navigationLabel = 'Failures';
|
|
|
|
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-exclamation-triangle';
|
|
|
|
protected static string|\UnitEnum|null $navigationGroup = 'Ops';
|
|
|
|
protected static ?string $slug = 'ops/failures';
|
|
|
|
protected string $view = 'filament.system.pages.ops.failures';
|
|
|
|
public static function getNavigationBadge(): ?string
|
|
{
|
|
$count = OperationRun::query()
|
|
->where('status', OperationRunStatus::Completed->value)
|
|
->where('outcome', OperationRunOutcome::Failed->value)
|
|
->count();
|
|
|
|
return $count > 0 ? (string) $count : null;
|
|
}
|
|
|
|
public static function getNavigationBadgeColor(): string|array|null
|
|
{
|
|
return 'danger';
|
|
}
|
|
|
|
public static function canAccess(): bool
|
|
{
|
|
$user = auth('platform')->user();
|
|
|
|
if (! $user instanceof PlatformUser) {
|
|
return false;
|
|
}
|
|
|
|
return $user->hasCapability(PlatformCapabilities::OPERATIONS_VIEW)
|
|
|| ($user->hasCapability(PlatformCapabilities::OPS_VIEW) && $user->hasCapability(PlatformCapabilities::RUNBOOKS_VIEW));
|
|
}
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->mountInteractsWithTable();
|
|
}
|
|
|
|
public function table(Table $table): Table
|
|
{
|
|
return $table
|
|
->defaultSort('id', 'desc')
|
|
->query(function (): Builder {
|
|
return OperationRun::query()
|
|
->with(['tenant', 'workspace'])
|
|
->where('status', OperationRunStatus::Completed->value)
|
|
->where('outcome', OperationRunOutcome::Failed->value);
|
|
})
|
|
->columns([
|
|
TextColumn::make('id')
|
|
->label('Run')
|
|
->state(fn (OperationRun $record): string => '#'.$record->getKey()),
|
|
TextColumn::make('status')
|
|
->badge()
|
|
->formatStateUsing(BadgeRenderer::label(BadgeDomain::OperationRunStatus))
|
|
->color(BadgeRenderer::color(BadgeDomain::OperationRunStatus))
|
|
->icon(BadgeRenderer::icon(BadgeDomain::OperationRunStatus))
|
|
->iconColor(BadgeRenderer::iconColor(BadgeDomain::OperationRunStatus)),
|
|
TextColumn::make('outcome')
|
|
->badge()
|
|
->formatStateUsing(BadgeRenderer::label(BadgeDomain::OperationRunOutcome))
|
|
->color(BadgeRenderer::color(BadgeDomain::OperationRunOutcome))
|
|
->icon(BadgeRenderer::icon(BadgeDomain::OperationRunOutcome))
|
|
->iconColor(BadgeRenderer::iconColor(BadgeDomain::OperationRunOutcome)),
|
|
TextColumn::make('type')
|
|
->label('Operation')
|
|
->formatStateUsing(fn (?string $state): string => OperationCatalog::label((string) $state))
|
|
->searchable(),
|
|
TextColumn::make('workspace.name')
|
|
->label('Workspace')
|
|
->toggleable(),
|
|
TextColumn::make('tenant.name')
|
|
->label('Tenant')
|
|
->formatStateUsing(fn (?string $state): string => $state ?: 'Tenantless')
|
|
->toggleable(),
|
|
TextColumn::make('created_at')->label('Started')->since(),
|
|
])
|
|
->recordUrl(fn (OperationRun $record): string => SystemOperationRunLinks::view($record))
|
|
->actions([
|
|
Action::make('retry')
|
|
->label('Retry')
|
|
->requiresConfirmation()
|
|
->visible(fn (OperationRun $record): bool => $this->canManageOperations() && app(OperationRunTriageService::class)->canRetry($record))
|
|
->action(function (OperationRun $record, OperationRunTriageService $triageService): void {
|
|
$user = $this->requireManageUser();
|
|
$retryRun = $triageService->retry($record, $user);
|
|
|
|
OperationUxPresenter::queuedToast((string) $retryRun->type)
|
|
->actions([
|
|
\Filament\Actions\Action::make('view_run')
|
|
->label('View run')
|
|
->url(SystemOperationRunLinks::view($retryRun)),
|
|
])
|
|
->send();
|
|
}),
|
|
Action::make('cancel')
|
|
->label('Cancel')
|
|
->color('danger')
|
|
->requiresConfirmation()
|
|
->visible(fn (OperationRun $record): bool => $this->canManageOperations() && app(OperationRunTriageService::class)->canCancel($record))
|
|
->action(function (OperationRun $record, OperationRunTriageService $triageService): void {
|
|
$user = $this->requireManageUser();
|
|
$triageService->cancel($record, $user);
|
|
|
|
Notification::make()
|
|
->title('Run cancelled')
|
|
->success()
|
|
->send();
|
|
}),
|
|
Action::make('mark_investigated')
|
|
->label('Mark investigated')
|
|
->requiresConfirmation()
|
|
->visible(fn (): bool => $this->canManageOperations())
|
|
->form([
|
|
Textarea::make('reason')
|
|
->label('Reason')
|
|
->required()
|
|
->minLength(5)
|
|
->maxLength(500)
|
|
->rows(4),
|
|
])
|
|
->action(function (OperationRun $record, array $data, OperationRunTriageService $triageService): void {
|
|
$user = $this->requireManageUser();
|
|
$triageService->markInvestigated($record, $user, (string) ($data['reason'] ?? ''));
|
|
|
|
Notification::make()
|
|
->title('Run marked as investigated')
|
|
->success()
|
|
->send();
|
|
}),
|
|
])
|
|
->emptyStateHeading('No failed runs found')
|
|
->emptyStateDescription('Failed operations will appear here for triage.')
|
|
->bulkActions([]);
|
|
}
|
|
|
|
private function canManageOperations(): bool
|
|
{
|
|
$user = auth('platform')->user();
|
|
|
|
return $user instanceof PlatformUser
|
|
&& $user->hasCapability(PlatformCapabilities::OPERATIONS_MANAGE);
|
|
}
|
|
|
|
private function requireManageUser(): PlatformUser
|
|
{
|
|
$user = auth('platform')->user();
|
|
|
|
if (! $user instanceof PlatformUser || ! $user->hasCapability(PlatformCapabilities::OPERATIONS_MANAGE)) {
|
|
abort(403);
|
|
}
|
|
|
|
return $user;
|
|
}
|
|
}
|