TenantAtlas/app/Filament/System/Pages/Ops/Runs.php
2026-03-30 20:34:01 +02:00

134 lines
5.6 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Filament\System\Pages\Ops;
use App\Models\OperationRun;
use App\Models\PlatformUser;
use App\Support\Auth\PlatformCapabilities;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use App\Support\OperationCatalog;
use App\Support\System\SystemOperationRunLinks;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceType;
use Filament\Actions\Action;
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 Runs extends Page implements HasTable
{
use InteractsWithTable;
protected static ?string $navigationLabel = 'Operations';
protected static ?string $title = 'Operations';
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-queue-list';
protected static string|\UnitEnum|null $navigationGroup = 'Ops';
protected static ?string $slug = 'ops/runs';
protected string $view = 'filament.system.pages.ops.runs';
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
{
return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::RunLog, ActionSurfaceType::ReadOnlyRegistryReport)
->satisfy(ActionSurfaceSlot::ListHeader, 'The page header exposes Go to runbooks while row clicks remain the only inspect model.')
->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'System operations remain scan-first and intentionally omit bulk actions.')
->satisfy(ActionSurfaceSlot::ListEmptyState, 'The empty state explains when no operations have been queued yet and repeats the Go to runbooks CTA.')
->exempt(ActionSurfaceSlot::DetailHeader, 'Row navigation opens the canonical system run detail page, which owns header actions.');
}
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();
}
/**
* @return array<Action>
*/
protected function getHeaderActions(): array
{
return [
Action::make('go_to_runbooks')
->label('Go to runbooks')
->url(Runbooks::getUrl(panel: 'system')),
];
}
public function table(Table $table): Table
{
return $table
->defaultSort('id', 'desc')
->paginated(\App\Support\Filament\TablePaginationProfiles::customPage())
->query(function (): Builder {
return OperationRun::query()
->with(['tenant', 'workspace']);
})
->columns([
TextColumn::make('id')
->label('ID')
->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('initiator_name')->label('Initiator'),
TextColumn::make('created_at')->label('Started')->since(),
])
->recordUrl(fn (OperationRun $record): string => SystemOperationRunLinks::view($record))
->actions([])
->emptyStateHeading('No operations yet')
->emptyStateDescription('Operations from all workspaces will appear here when they are queued.')
->emptyStateActions([
Action::make('go_to_runbooks_empty')
->label('Go to runbooks')
->url(Runbooks::getUrl(panel: 'system'))
->button(),
])
->bulkActions([]);
}
}