*/ public array $supportDiagnosticsAuditKeys = []; public function getTitle(): string { return __('localization.dashboard.tenant_title'); } /** * @param array $parameters */ public static function getUrl(array $parameters = [], bool $isAbsolute = true, ?string $panel = null, ?Model $tenant = null, bool $shouldGuessMissingParameters = false): string { return parent::getUrl($parameters, $isAbsolute, $panel ?? 'tenant', $tenant, $shouldGuessMissingParameters); } /** * @return array | WidgetConfiguration> */ public function getWidgets(): array { return [ TenantTriageArrivalContinuity::class, RecoveryReadiness::class, DashboardKpis::class, NeedsAttention::class, BaselineCompareNow::class, RecentDriftFindings::class, RecentOperations::class, ]; } public function getColumns(): int|array { return 2; } /** * @return array */ protected function getHeaderActions(): array { return [ $this->requestSupportAction(), $this->openSupportDiagnosticsAction(), ]; } public function authorizeTenantSupportRequest(): void { $this->resolveCurrentTenantForCapability(Capabilities::SUPPORT_REQUESTS_CREATE); } private function requestSupportAction(): Action { $action = Action::make('requestSupport') ->label(__('localization.dashboard.request_support')) ->icon('heroicon-o-paper-airplane') ->color('gray') ->slideOver() ->stickyModalHeader() ->modalHeading(__('localization.dashboard.support_request_heading')) ->modalDescription(__('localization.dashboard.support_request_description')) ->modalSubmitActionLabel(__('localization.dashboard.submit_request')) ->form([ Placeholder::make('included_context') ->label(__('localization.dashboard.included_context')) ->content(fn (): string => $this->tenantSupportRequestAttachmentSummary()) ->columnSpanFull(), Placeholder::make('latest_external_handoff') ->label(__('localization.dashboard.latest_external_handoff')) ->content(fn (): string => $this->tenantLatestSupportRequestHandoffSummary()) ->columnSpanFull(), Select::make('external_handoff_mode') ->label(__('localization.dashboard.external_handoff_mode')) ->options(fn (): array => $this->supportHandoffModeOptions()) ->default(SupportRequest::EXTERNAL_HANDOFF_MODE_INTERNAL_ONLY) ->helperText(fn (): string => $this->supportDeskTargetAvailable() ? __('localization.dashboard.external_handoff_mode_helper_available') : __('localization.dashboard.external_handoff_mode_helper_unavailable')) ->required() ->live() ->native(false), Placeholder::make('handoff_mutation_scope') ->label(__('localization.dashboard.handoff_mutation_scope')) ->content(fn (Get $get): string => $this->externalHandoffMutationScope($get('external_handoff_mode'))) ->columnSpanFull(), TextInput::make('external_ticket_reference') ->label(__('localization.dashboard.external_ticket_reference')) ->helperText(__('localization.dashboard.external_ticket_reference_helper')) ->required(fn (Get $get): bool => $get('external_handoff_mode') === SupportRequest::EXTERNAL_HANDOFF_MODE_LINK_EXISTING_TICKET) ->visible(fn (Get $get): bool => $this->supportDeskTargetAvailable() && $get('external_handoff_mode') === SupportRequest::EXTERNAL_HANDOFF_MODE_LINK_EXISTING_TICKET), TextInput::make('external_ticket_url') ->label(__('localization.dashboard.external_ticket_url')) ->helperText(__('localization.dashboard.external_ticket_url_helper')) ->url() ->visible(fn (Get $get): bool => $this->supportDeskTargetAvailable() && $get('external_handoff_mode') === SupportRequest::EXTERNAL_HANDOFF_MODE_LINK_EXISTING_TICKET) ->columnSpanFull(), Select::make('severity') ->label(__('localization.dashboard.severity')) ->options(SupportRequest::severityOptions()) ->default(SupportRequest::SEVERITY_NORMAL) ->required() ->native(false), TextInput::make('summary') ->label(__('localization.dashboard.summary')) ->required() ->columnSpanFull(), Textarea::make('reproduction_notes') ->label(__('localization.dashboard.reproduction_notes')) ->rows(4) ->columnSpanFull(), TextInput::make('contact_name') ->label(__('localization.dashboard.contact_name')) ->default(fn (): ?string => $this->resolveDashboardActor()->name), TextInput::make('contact_email') ->label(__('localization.dashboard.contact_email')) ->email() ->default(fn (): ?string => $this->resolveDashboardActor()->email), ]) ->action(function (array $data): void { $actor = $this->resolveDashboardActor(); $tenant = $this->resolveCurrentTenantForCapability(Capabilities::SUPPORT_REQUESTS_CREATE); $supportRequest = app(SupportRequestSubmissionService::class)->submitForTenant($tenant, $actor, $data); Notification::make() ->title(__('localization.dashboard.support_request_submitted')) ->body($this->supportRequestNotificationBody($supportRequest)) ->when( $supportRequest->hasExternalHandoffFailure(), fn (Notification $notification): Notification => $notification->warning(), fn (Notification $notification): Notification => $notification->success(), ) ->when( $supportRequest->external_ticket_url !== null, fn (Notification $notification): Notification => $notification->actions([ Action::make('openExternalTicket') ->label(__('localization.dashboard.open_external_ticket')) ->url((string) $supportRequest->external_ticket_url, shouldOpenInNewTab: true), ]), ) ->send(); }); return UiEnforcement::forAction($action) ->requireCapability(Capabilities::SUPPORT_REQUESTS_CREATE) ->apply(); } private function openSupportDiagnosticsAction(): Action { $action = Action::make('openSupportDiagnostics') ->label(__('localization.dashboard.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::forAction($action) ->requireCapability(Capabilities::SUPPORT_DIAGNOSTICS_VIEW) ->apply(); } /** * @return array */ public function tenantSupportDiagnosticBundle(): array { $user = $this->resolveDashboardActor(); $tenant = $this->resolveCurrentTenantForCapability(Capabilities::SUPPORT_DIAGNOSTICS_VIEW); return app(SupportDiagnosticBundleBuilder::class)->forTenant($tenant, $user); } private function auditTenantSupportDiagnosticsOpen(): void { $user = $this->resolveDashboardActor(); $tenant = $this->resolveCurrentTenantForCapability(Capabilities::SUPPORT_DIAGNOSTICS_VIEW); $this->recordSupportDiagnosticsOpened( tenant: $tenant, bundle: $this->tenantSupportDiagnosticBundle(), user: $user, ); } /** * @param array $bundle */ private function recordSupportDiagnosticsOpened(Tenant $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' => 'tenant_dashboard', ], ); $this->supportDiagnosticsAuditKeys[] = $auditKey; } private function resolveDashboardActor(): User { $user = auth()->user(); if (! $user instanceof User) { abort(404); } return $user; } private function resolveCurrentTenantForCapability(string $capability): Tenant { $user = $this->resolveDashboardActor(); $tenant = Filament::getTenant(); if (! $tenant instanceof Tenant) { abort(404); } $resolver = app(CapabilityResolver::class); if (! $resolver->isMember($user, $tenant)) { abort(404); } if (! $resolver->can($user, $tenant, $capability)) { abort(403); } return $tenant; } private function tenantSupportRequestAttachmentSummary(): string { $tenant = Filament::getTenant(); $user = auth()->user(); if (! $tenant instanceof Tenant || ! $user instanceof User) { return 'Only canonical redacted tenant context will be attached.'; } $resolver = app(CapabilityResolver::class); if (! $resolver->isMember($user, $tenant)) { return 'Only canonical redacted tenant context will be attached.'; } return $resolver->can($user, $tenant, Capabilities::SUPPORT_DIAGNOSTICS_VIEW) ? 'A redacted diagnostic snapshot and the canonical tenant context will be attached.' : 'Only the canonical redacted tenant context will be attached because you cannot view support diagnostics.'; } private function tenantLatestSupportRequestHandoffSummary(): string { $tenant = $this->resolveCurrentTenantForCapability(Capabilities::SUPPORT_REQUESTS_CREATE); $user = $this->resolveDashboardActor(); $summary = app(SupportRequestSubmissionService::class)->latestTenantHandoffSummary($tenant, $user); return $this->formatLatestHandoffSummary($summary); } /** * @return array */ private function supportHandoffModeOptions(): array { if (! $this->supportDeskTargetAvailable()) { return [ SupportRequest::EXTERNAL_HANDOFF_MODE_INTERNAL_ONLY => __('localization.dashboard.handoff_mode_internal_only'), ]; } return [ SupportRequest::EXTERNAL_HANDOFF_MODE_INTERNAL_ONLY => __('localization.dashboard.handoff_mode_internal_only'), SupportRequest::EXTERNAL_HANDOFF_MODE_CREATE_EXTERNAL_TICKET => __('localization.dashboard.handoff_mode_create_external_ticket'), SupportRequest::EXTERNAL_HANDOFF_MODE_LINK_EXISTING_TICKET => __('localization.dashboard.handoff_mode_link_existing_ticket'), ]; } private function supportDeskTargetAvailable(): bool { return app(ExternalSupportDeskHandoffService::class)->targetIsConfigured(); } private function externalHandoffMutationScope(mixed $mode): string { return match ($mode) { SupportRequest::EXTERNAL_HANDOFF_MODE_CREATE_EXTERNAL_TICKET => __('localization.dashboard.mutation_scope_external_create'), SupportRequest::EXTERNAL_HANDOFF_MODE_LINK_EXISTING_TICKET => __('localization.dashboard.mutation_scope_external_link'), default => __('localization.dashboard.mutation_scope_internal_only'), }; } /** * @param array|null $summary */ private function formatLatestHandoffSummary(?array $summary): string { if ($summary === null) { return __('localization.dashboard.latest_external_handoff_none'); } $internalReference = (string) $summary['internal_reference']; if (($summary['has_failure'] ?? false) === true) { return __('localization.dashboard.latest_external_handoff_failed', [ 'reference' => $internalReference, 'failure' => (string) $summary['external_handoff_failure_summary'], ]); } if (($summary['has_external_link'] ?? false) === true) { return __('localization.dashboard.latest_external_handoff_linked', [ 'reference' => $internalReference, 'external' => (string) $summary['external_ticket_reference'], ]); } return __('localization.dashboard.latest_external_handoff_internal_only', [ 'reference' => $internalReference, ]); } private function supportRequestNotificationBody(SupportRequest $supportRequest): string { return match ($supportRequest->externalHandoffOutcome()) { SupportRequest::HANDOFF_OUTCOME_EXTERNAL_TICKET_CREATED => __('localization.dashboard.support_request_submitted_created', [ 'reference' => $supportRequest->internal_reference, 'external' => $supportRequest->external_ticket_reference, ]), SupportRequest::HANDOFF_OUTCOME_EXTERNAL_TICKET_LINKED => __('localization.dashboard.support_request_submitted_linked', [ 'reference' => $supportRequest->internal_reference, 'external' => $supportRequest->external_ticket_reference, ]), SupportRequest::HANDOFF_OUTCOME_EXTERNAL_HANDOFF_FAILED => __('localization.dashboard.support_request_submitted_failed', [ 'reference' => $supportRequest->internal_reference, 'failure' => $supportRequest->external_handoff_failure_summary, ]), default => __('localization.dashboard.support_request_submitted_internal_only', [ 'reference' => $supportRequest->internal_reference, ]), }; } }