TenantAtlas/apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php
Ahmed Darrazi c125fd48fd
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 3m58s
feat(ui): implement diagnostic entry point consolidation
Applied diagnostic surface contract rules to Audit Log inspect modal and Support Diagnostics action context, consolidating raw diagnostic data into safe modals according to Spec 374.
2026-06-13 03:06:33 +02:00

321 lines
12 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Filament\Pages;
use App\Filament\Concerns\ResolvesPanelTenantContext;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Services\Audit\WorkspaceAuditLogger;
use App\Services\Auth\ManagedEnvironmentDiagnosticsService;
use App\Services\Auth\ManagedEnvironmentMembershipManager;
use App\Support\Auth\Capabilities;
use App\Support\ProductTelemetry\ProductTelemetryRecorder;
use App\Support\ProductTelemetry\ProductUsageEventCatalog;
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\SupportDiagnostics\SupportDiagnosticBundleBuilder;
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;
use Illuminate\Contracts\View\View;
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 function getTitle(): string
{
return 'Repair diagnostics';
}
public function getSubheading(): string
{
return 'Checks supported TenantPilot access and membership repair conditions only.';
}
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
{
return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly)
->satisfy(ActionSurfaceSlot::ListHeader, 'Header exposes a read-only support diagnostics modal plus capability-gated tenant repair actions when inconsistent membership state is detected.')
->exempt(ActionSurfaceSlot::InspectAffordance, 'Repair diagnostics is already the singleton repair diagnostics 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;
/**
* @var list<string>
*/
public array $supportDiagnosticsAuditKeys = [];
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 repair diagnostics are active',
'body' => 'No supported access or membership repair is active for this managed environment.',
'status' => 'No repair action',
'color' => 'gray',
'impact' => 'Repair diagnostics only checks existing TenantPilot access and membership repair conditions; it is not a generic environment health hub.',
'next_check' => 'Use Open support diagnostics for broader provider, operation, evidence, or audit context.',
'primary_action_label' => null,
'secondary_action_label' => null,
'blockers' => [],
];
}
$primaryBlocker = $blockers[0];
$secondaryBlocker = $blockers[1] ?? null;
return [
'headline' => $blockerCount === 1
? '1 repair diagnostic needs attention'
: $blockerCount.' repair diagnostics need attention',
'body' => 'Repair diagnostics checks supported TenantPilot access and membership defects only. Resolve the highest-impact 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 [
$this->openSupportDiagnosticsAction(),
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),
];
}
private function openSupportDiagnosticsAction(): Action
{
$action = Action::make('openSupportDiagnostics')
->label('Open support diagnostics')
->icon('heroicon-o-lifebuoy')
->color('gray')
->modal()
->slideOver()
->stickyModalHeader()
->modalHeading(__('localization.dashboard.support_diagnostics'))
->modalDescription(__('localization.dashboard.support_diagnostics_description'))
->modalSubmitAction(false)
->modalCancelAction(fn (Action $action): Action => $action->label(__('localization.dashboard.close')))
->mountUsing(function (): void {
$this->auditTenantSupportDiagnosticsOpen();
})
->modalContent(fn (): View => view('filament.modals.support-diagnostic-bundle', [
'bundle' => $this->tenantSupportDiagnosticBundle(),
]));
return UiEnforcement::forScopedAction(
$action,
fn (): UiActionContext => static::tenantUiActionContext(),
)
->requireCapability(Capabilities::SUPPORT_DIAGNOSTICS_VIEW)
->apply();
}
/**
* @return array<string, mixed>
*/
public function tenantSupportDiagnosticBundle(): array
{
$tenant = static::resolveTenantContextForCurrentPanelOrFail();
$user = auth()->user();
if (! $user instanceof User) {
abort(403, 'Not allowed');
}
return app(SupportDiagnosticBundleBuilder::class)->forTenant($tenant, $user);
}
private function auditTenantSupportDiagnosticsOpen(): void
{
$tenant = static::resolveTenantContextForCurrentPanelOrFail();
$user = auth()->user();
if (! $user instanceof User) {
abort(403, 'Not allowed');
}
$this->recordSupportDiagnosticsOpened(
tenant: $tenant,
bundle: $this->tenantSupportDiagnosticBundle(),
user: $user,
);
}
/**
* @param array<string, mixed> $bundle
*/
private function recordSupportDiagnosticsOpened(ManagedEnvironment $tenant, array $bundle, User $user): void
{
$auditKey = 'tenant:'.$tenant->getKey();
if (in_array($auditKey, $this->supportDiagnosticsAuditKeys, true)) {
return;
}
app(WorkspaceAuditLogger::class)->logSupportDiagnosticsOpened(
tenant: $tenant,
contextType: 'tenant',
bundle: $bundle,
actor: $user,
);
app(ProductTelemetryRecorder::class)->record(
eventName: ProductUsageEventCatalog::SUPPORT_DIAGNOSTICS_OPENED,
workspaceId: (int) $tenant->workspace_id,
tenantId: (int) $tenant->getKey(),
userId: (int) $user->getKey(),
subjectType: 'tenant',
subjectId: (int) $tenant->getKey(),
metadata: [
'source_surface' => 'repair_diagnostics',
],
);
$this->supportDiagnosticsAuditKeys[] = $auditKey;
}
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();
}
}