Some checks failed
Main Confidence / confidence (push) Failing after 45s
Implements support diagnostics bundle, moves audit writes to action mountUsing to avoid side-effects during render, replaces custom slide-over with Filament-native schema, updates tests and adds spec docs. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #278
162 lines
4.7 KiB
PHP
162 lines
4.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Filament\Pages;
|
|
|
|
use App\Filament\Widgets\Tenant\TenantTriageArrivalContinuity;
|
|
use App\Filament\Widgets\Dashboard\BaselineCompareNow;
|
|
use App\Filament\Widgets\Dashboard\DashboardKpis;
|
|
use App\Filament\Widgets\Dashboard\NeedsAttention;
|
|
use App\Filament\Widgets\Dashboard\RecentDriftFindings;
|
|
use App\Filament\Widgets\Dashboard\RecentOperations;
|
|
use App\Filament\Widgets\Dashboard\RecoveryReadiness;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use App\Services\Audit\WorkspaceAuditLogger;
|
|
use App\Services\Auth\CapabilityResolver;
|
|
use App\Support\Auth\Capabilities;
|
|
use App\Support\Rbac\UiEnforcement;
|
|
use App\Support\SupportDiagnostics\SupportDiagnosticBundleBuilder;
|
|
use Filament\Actions\Action;
|
|
use Filament\Facades\Filament;
|
|
use Filament\Pages\Dashboard;
|
|
use Filament\Widgets\Widget;
|
|
use Filament\Widgets\WidgetConfiguration;
|
|
use Illuminate\Contracts\View\View;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
class TenantDashboard extends Dashboard
|
|
{
|
|
/**
|
|
* @var list<string>
|
|
*/
|
|
public array $supportDiagnosticsAuditKeys = [];
|
|
|
|
/**
|
|
* @param array<mixed> $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<class-string<Widget> | 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<Action>
|
|
*/
|
|
protected function getHeaderActions(): array
|
|
{
|
|
return [
|
|
$this->openSupportDiagnosticsAction(),
|
|
];
|
|
}
|
|
|
|
private function openSupportDiagnosticsAction(): Action
|
|
{
|
|
$action = Action::make('openSupportDiagnostics')
|
|
->label('Open support diagnostics')
|
|
->icon('heroicon-o-lifebuoy')
|
|
->color('gray')
|
|
->modal()
|
|
->slideOver()
|
|
->stickyModalHeader()
|
|
->modalHeading('Support diagnostics')
|
|
->modalDescription('Redacted tenant context from existing records.')
|
|
->modalSubmitAction(false)
|
|
->modalCancelAction(fn (Action $action): Action => $action->label('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<string, mixed>
|
|
*/
|
|
public function tenantSupportDiagnosticBundle(): array
|
|
{
|
|
$user = auth()->user();
|
|
$tenant = Filament::getTenant();
|
|
|
|
if (! $user instanceof User || ! $tenant instanceof Tenant) {
|
|
abort(404);
|
|
}
|
|
|
|
$resolver = app(CapabilityResolver::class);
|
|
|
|
if (! $resolver->isMember($user, $tenant)) {
|
|
abort(404);
|
|
}
|
|
|
|
if (! $resolver->can($user, $tenant, Capabilities::SUPPORT_DIAGNOSTICS_VIEW)) {
|
|
abort(403);
|
|
}
|
|
|
|
return app(SupportDiagnosticBundleBuilder::class)->forTenant($tenant, $user);
|
|
}
|
|
|
|
private function auditTenantSupportDiagnosticsOpen(): void
|
|
{
|
|
$user = auth()->user();
|
|
$tenant = Filament::getTenant();
|
|
|
|
if (! $user instanceof User || ! $tenant instanceof Tenant) {
|
|
abort(404);
|
|
}
|
|
|
|
$this->recordSupportDiagnosticsOpened(
|
|
tenant: $tenant,
|
|
bundle: $this->tenantSupportDiagnosticBundle(),
|
|
user: $user,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $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,
|
|
);
|
|
|
|
$this->supportDiagnosticsAuditKeys[] = $auditKey;
|
|
}
|
|
}
|