TenantAtlas/apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php
ahmido 94877c9a66 feat(ui): implement diagnostic surface separation (#444)
Applied the decision-first diagnostic surface IA contract to EnvironmentDiagnostics and SupportDiagnostics bundles. Added recommended_first_check and separated technical metadata as per Spec 373.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #444
2026-06-12 20:31:17 +00:00

206 lines
8.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Filament\Pages;
use App\Filament\Concerns\ResolvesPanelTenantContext;
use App\Models\User;
use App\Services\Auth\ManagedEnvironmentDiagnosticsService;
use App\Services\Auth\ManagedEnvironmentMembershipManager;
use App\Support\Auth\Capabilities;
use App\Support\Rbac\Actions\ResolvesUiActionContext;
use App\Support\Rbac\Actions\UiActionContext;
use App\Support\Rbac\UiEnforcement;
use App\Support\Rbac\UiTooltips;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot;
use Filament\Actions\Action;
use Filament\Pages\Page;
class EnvironmentDiagnostics extends Page
{
use ResolvesPanelTenantContext;
use ResolvesUiActionContext;
protected static bool $shouldRegisterNavigation = false;
protected static ?string $slug = 'diagnostics';
protected string $view = 'filament.pages.environment-diagnostics';
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
{
return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly)
->satisfy(ActionSurfaceSlot::ListHeader, 'Header exposes capability-gated tenant repair actions when inconsistent membership state is detected.')
->exempt(ActionSurfaceSlot::InspectAffordance, 'ManagedEnvironment diagnostics is already the singleton diagnostic surface for the active tenant.')
->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'The diagnostics page does not render row-level secondary actions.')
->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The diagnostics page does not expose bulk actions.')
->exempt(ActionSurfaceSlot::ListEmptyState, 'Diagnostics content is always rendered instead of a list-style empty state.');
}
public bool $missingOwner = false;
public bool $hasDuplicateMembershipsForCurrentUser = false;
public function mount(): void
{
$tenant = static::resolveTenantContextForCurrentPanelOrFail();
$this->missingOwner = app(ManagedEnvironmentDiagnosticsService::class)->tenantHasNoOwners($tenant);
$user = auth()->user();
if (! $user instanceof User) {
abort(403, 'Not allowed');
}
$this->hasDuplicateMembershipsForCurrentUser = app(ManagedEnvironmentDiagnosticsService::class)
->userHasDuplicateMemberships($tenant, $user);
}
/**
* @return array{
* headline: string,
* body: string,
* status: string,
* color: string,
* impact: string,
* next_check: string,
* primary_action_label: ?string,
* secondary_action_label: ?string,
* blockers: list<array{
* key: string,
* label: string,
* failed_condition: string,
* impact: string,
* next_check: string,
* action_label: string,
* action_role: string
* }>
* }
*/
public function diagnosticSummary(): array
{
$blockers = [];
if ($this->missingOwner) {
$blockers[] = [
'key' => 'missing_owner',
'label' => 'Missing owner',
'failed_condition' => 'No Owner membership is currently visible for this managed environment.',
'impact' => 'Tenant repair and accountability workflows need an owner before support can treat access as complete.',
'next_check' => 'Confirm workspace role recovery, then use Bootstrap owner only when the current administrator is authorized to repair tenant scope.',
'action_label' => 'Bootstrap owner',
'action_role' => 'Primary repair path',
];
}
if ($this->hasDuplicateMembershipsForCurrentUser) {
$blockers[] = [
'key' => 'duplicate_memberships',
'label' => 'Duplicate memberships',
'failed_condition' => 'The current user has more than one tenant membership row for this managed environment.',
'impact' => 'Duplicate access scope rows make authorization support harder to reason about for this user.',
'next_check' => 'Merge the duplicate rows, then reload the diagnostics page to confirm only one membership remains.',
'action_label' => 'Merge duplicate access scopes',
'action_role' => count($blockers) === 0 ? 'Primary repair path' : 'Secondary repair path',
];
}
$blockerCount = count($blockers);
if ($blockerCount === 0) {
return [
'headline' => 'No diagnostic action is required',
'body' => 'No actionable tenant membership defect is visible for the current managed-environment context.',
'status' => 'No action required',
'color' => 'gray',
'impact' => 'This page did not find a tenant membership repair to run for the current user and environment.',
'next_check' => 'Review provider, operation, or audit surfaces only when another page reports a blocker.',
'primary_action_label' => null,
'secondary_action_label' => null,
'blockers' => [],
];
}
$primaryBlocker = $blockers[0];
$secondaryBlocker = $blockers[1] ?? null;
return [
'headline' => $blockerCount === 1
? '1 diagnostic blocker needs attention'
: $blockerCount.' diagnostic blockers need attention',
'body' => 'Resolve the highest-impact tenant membership blocker first; lower repair paths remain visible for context.',
'status' => $blockerCount === 1 ? 'Action needed' : $blockerCount.' blockers',
'color' => 'warning',
'impact' => $primaryBlocker['impact'],
'next_check' => $primaryBlocker['next_check'],
'primary_action_label' => $primaryBlocker['action_label'],
'secondary_action_label' => $secondaryBlocker['action_label'] ?? null,
'blockers' => $blockers,
];
}
/**
* @return array<Action>
*/
protected function getHeaderActions(): array
{
return [
UiEnforcement::forScopedAction(
Action::make('bootstrapOwner')
->label('Bootstrap owner')
->requiresConfirmation()
->action(fn () => $this->bootstrapOwner()),
fn (): UiActionContext => static::tenantUiActionContext(),
)
->requireCapability(Capabilities::TENANT_MANAGE)
->destructive()
->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
->apply()
->visible(fn (): bool => $this->missingOwner),
UiEnforcement::forScopedAction(
Action::make('mergeDuplicateMemberships')
->label('Merge duplicate access scopes')
->requiresConfirmation()
->action(fn () => $this->mergeDuplicateMemberships()),
fn (): UiActionContext => static::tenantUiActionContext(),
)
->requireCapability(Capabilities::TENANT_MANAGE)
->destructive()
->tooltip(UiTooltips::INSUFFICIENT_PERMISSION)
->apply()
->visible(fn (): bool => $this->hasDuplicateMembershipsForCurrentUser),
];
}
public function bootstrapOwner(): void
{
$tenant = static::resolveTenantContextForCurrentPanelOrFail();
$user = auth()->user();
if (! $user instanceof User) {
abort(403, 'Not allowed');
}
app(ManagedEnvironmentMembershipManager::class)->grantScope($tenant, $user, $user, sourceRef: 'diagnostic');
$this->mount();
}
public function mergeDuplicateMemberships(): void
{
$tenant = static::resolveTenantContextForCurrentPanelOrFail();
$user = auth()->user();
if (! $user instanceof User) {
abort(403, 'Not allowed');
}
app(ManagedEnvironmentDiagnosticsService::class)->mergeDuplicateMembershipsForUser($tenant, $user, $user);
$this->mount();
}
}