## Summary - codify Spec 193 as an explicit monitoring/workbench surface inventory with validator and guard coverage - refactor the Finding Exceptions Queue, Operations landing, and tenantless operation viewer into clearer context, navigation, utility, drilldown, and focused-work lanes - align Alerts, Audit Log, and Alert Deliveries with quiet origin-context handling while preserving calm reference surfaces and the explicit Tenant Diagnostics exception - add focused feature coverage, guard coverage, browser smoke coverage, and the full spec artifacts for Spec 193 ## Verification - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Guards/ActionSurfaceContractTest.php tests/Feature/Guards/ActionSurfaceValidatorTest.php tests/Feature/Guards/Spec193MonitoringSurfaceHierarchyGuardTest.php tests/Feature/OpsUx/OperateHubShellTest.php tests/Feature/Operations/TenantlessOperationRunViewerTest.php tests/Feature/Monitoring/FindingExceptionsQueueHierarchyTest.php tests/Browser/Spec193MonitoringSurfaceHierarchySmokeTest.php` - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` - integrated-browser smoke pass over queue, operations, operation detail, alerts, audit log, and tenant diagnostics ## Notes - Livewire v4 / Filament v5 stack unchanged - no provider-registration changes; Laravel 11+ provider registration remains in `bootstrap/providers.php` - no new global-search behavior was introduced - destructive and governance-changing actions keep their existing confirmation and authorization semantics - no new assets or migrations were added Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #227
117 lines
4.0 KiB
PHP
117 lines
4.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Filament\Pages\Monitoring;
|
|
|
|
use App\Filament\Clusters\Monitoring\AlertsCluster;
|
|
use App\Filament\Widgets\Alerts\AlertsKpiHeader;
|
|
use App\Models\User;
|
|
use App\Models\Workspace;
|
|
use App\Services\Auth\WorkspaceCapabilityResolver;
|
|
use App\Support\Auth\Capabilities;
|
|
use App\Support\Navigation\CanonicalNavigationContext;
|
|
use App\Support\OperateHub\OperateHubShell;
|
|
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
|
|
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
|
|
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot;
|
|
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceType;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
use BackedEnum;
|
|
use Filament\Actions\Action;
|
|
use Filament\Facades\Filament;
|
|
use Filament\Pages\Page;
|
|
use UnitEnum;
|
|
|
|
class Alerts extends Page
|
|
{
|
|
protected static ?string $cluster = AlertsCluster::class;
|
|
|
|
protected static ?int $navigationSort = 20;
|
|
|
|
protected static string|UnitEnum|null $navigationGroup = 'Monitoring';
|
|
|
|
protected static ?string $navigationLabel = 'Overview';
|
|
|
|
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-bell-alert';
|
|
|
|
protected static ?string $slug = 'overview';
|
|
|
|
protected static ?string $title = 'Alerts';
|
|
|
|
protected string $view = 'filament.pages.monitoring.alerts';
|
|
|
|
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
|
|
{
|
|
return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly, ActionSurfaceType::ReadOnlyRegistryReport)
|
|
->satisfy(ActionSurfaceSlot::ListHeader, 'Header keeps alerts scope and origin navigation quiet on the page-level overview.')
|
|
->exempt(ActionSurfaceSlot::InspectAffordance, 'The alerts overview is a page-level monitoring summary and does not inspect records inline.')
|
|
->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'The alerts overview does not render row-level secondary actions.')
|
|
->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The alerts overview does not expose bulk actions.')
|
|
->exempt(ActionSurfaceSlot::ListEmptyState, 'The overview always renders KPI widgets and downstream drilldown navigation instead of a list-style empty state.');
|
|
}
|
|
|
|
public static function canAccess(): bool
|
|
{
|
|
if (Filament::getCurrentPanel()?->getId() !== 'admin') {
|
|
return false;
|
|
}
|
|
|
|
$user = auth()->user();
|
|
|
|
if (! $user instanceof User) {
|
|
return false;
|
|
}
|
|
|
|
$workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request());
|
|
|
|
if (! is_int($workspaceId)) {
|
|
return false;
|
|
}
|
|
|
|
$workspace = Workspace::query()->whereKey($workspaceId)->first();
|
|
|
|
if (! $workspace instanceof Workspace) {
|
|
return false;
|
|
}
|
|
|
|
/** @var WorkspaceCapabilityResolver $resolver */
|
|
$resolver = app(WorkspaceCapabilityResolver::class);
|
|
|
|
return $resolver->isMember($user, $workspace)
|
|
&& $resolver->can($user, $workspace, Capabilities::ALERTS_VIEW);
|
|
}
|
|
|
|
protected function getHeaderWidgets(): array
|
|
{
|
|
return [
|
|
AlertsKpiHeader::class,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<Action>
|
|
*/
|
|
protected function getHeaderActions(): array
|
|
{
|
|
$actions = app(OperateHubShell::class)->headerActions(
|
|
scopeActionName: 'operate_hub_scope_alerts',
|
|
returnActionName: 'operate_hub_return_alerts',
|
|
);
|
|
|
|
$navigationContext = CanonicalNavigationContext::fromRequest(request());
|
|
|
|
if ($navigationContext?->backLinkLabel !== null && $navigationContext->backLinkUrl !== null) {
|
|
array_splice($actions, 1, 0, [
|
|
Action::make('operate_hub_back_to_origin_alerts')
|
|
->label($navigationContext->backLinkLabel)
|
|
->icon('heroicon-o-arrow-left')
|
|
->color('gray')
|
|
->url($navigationContext->backLinkUrl),
|
|
]);
|
|
}
|
|
|
|
return $actions;
|
|
}
|
|
}
|