## Summary - enforce the canonical workspace/environment scope contract for workspace hubs and environment-owned surfaces - replace first-party Operations deep links that leaked Filament `tableFilters[...]` internals with stable product-level query behavior - add the sidebar scope indicator and split environment-page navigation into explicit `Workspace-wide` and `Workspace admin` groups - remove redundant tenantless `All environments` scope badges from workspace-wide pages while preserving explicit environment filter affordances - include the Spec 338 artifacts, guard tests, and browser smoke coverage for the new contract ## Validation - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Navigation/Spec338EnvironmentSidebarSeparationTest.php tests/Feature/Navigation/Spec338OperationRunLinksQueryContractTest.php tests/Feature/Navigation/Spec338SidebarScopeIndicatorTest.php tests/Feature/Filament/PanelNavigationSegregationTest.php` - `cd apps/platform && ./vendor/bin/sail php vendor/bin/pest tests/Browser/Spec338ScopeContractSmokeTest.php --compact` ## Notes - Livewire v4 compliance unchanged - Filament provider registration remains in `bootstrap/providers.php` - no destructive action behavior changed - no migrations, env var changes, or new Filament asset registration Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #409
281 lines
9.5 KiB
PHP
281 lines
9.5 KiB
PHP
<?php
|
|
|
|
namespace App\Support;
|
|
|
|
use App\Filament\Resources\BackupScheduleResource;
|
|
use App\Filament\Resources\BackupSetResource;
|
|
use App\Filament\Resources\BaselineSnapshotResource;
|
|
use App\Filament\Resources\EntraGroupResource;
|
|
use App\Filament\Resources\EnvironmentReviewResource;
|
|
use App\Filament\Resources\EvidenceSnapshotResource;
|
|
use App\Filament\Resources\InventoryItemResource;
|
|
use App\Filament\Resources\PolicyResource;
|
|
use App\Filament\Resources\RestoreRunResource;
|
|
use App\Filament\Resources\ReviewPackResource;
|
|
use App\Models\EnvironmentReview;
|
|
use App\Models\EvidenceSnapshot;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\OperationRun;
|
|
use App\Models\ReviewPack;
|
|
use App\Models\Workspace;
|
|
use App\Support\Navigation\CanonicalNavigationContext;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
|
|
final class OperationRunLinks
|
|
{
|
|
public static function collectionLabel(): string
|
|
{
|
|
return 'Operations';
|
|
}
|
|
|
|
public static function openCollectionLabel(): string
|
|
{
|
|
return 'Open operations';
|
|
}
|
|
|
|
public static function collectionScopeDescription(): string
|
|
{
|
|
return 'Broader admin view across recent and historical operations.';
|
|
}
|
|
|
|
public static function viewInCollectionLabel(): string
|
|
{
|
|
return 'View in Operations';
|
|
}
|
|
|
|
public static function singularLabel(): string
|
|
{
|
|
return 'Operation';
|
|
}
|
|
|
|
public static function openLabel(): string
|
|
{
|
|
return 'Open operation';
|
|
}
|
|
|
|
public static function advancedMonitoringLabel(): string
|
|
{
|
|
return 'Open operation in Monitoring (advanced)';
|
|
}
|
|
|
|
public static function advancedMonitoringDescription(): string
|
|
{
|
|
return 'Diagnostics-only link to the canonical admin operation viewer.';
|
|
}
|
|
|
|
public static function identifierLabel(): string
|
|
{
|
|
return 'Operation ID';
|
|
}
|
|
|
|
public static function identifier(OperationRun|int $run): string
|
|
{
|
|
$runId = $run instanceof OperationRun ? (int) $run->getKey() : (int) $run;
|
|
|
|
return 'Operation #'.$runId;
|
|
}
|
|
|
|
public static function index(
|
|
?ManagedEnvironment $tenant = null,
|
|
?CanonicalNavigationContext $context = null,
|
|
?string $activeTab = null,
|
|
bool $allTenants = false,
|
|
?string $problemClass = null,
|
|
?string $operationType = null,
|
|
?Workspace $workspace = null,
|
|
): string {
|
|
$workspace = $tenant instanceof ManagedEnvironment
|
|
? self::resolveWorkspace($tenant)
|
|
: ($workspace ?? self::resolveWorkspace());
|
|
|
|
if (! $workspace instanceof Workspace) {
|
|
return url('/admin');
|
|
}
|
|
|
|
$parameters = $context?->toQuery() ?? [];
|
|
|
|
$parameters['workspace'] = $workspace;
|
|
|
|
if ($tenant instanceof ManagedEnvironment) {
|
|
$parameters['environment_id'] = (int) $tenant->getKey();
|
|
}
|
|
|
|
if (is_string($activeTab) && $activeTab !== '') {
|
|
$parameters['activeTab'] = $activeTab;
|
|
}
|
|
|
|
if (
|
|
is_string($problemClass)
|
|
&& in_array($problemClass, OperationRun::problemClasses(), true)
|
|
&& $problemClass !== OperationRun::PROBLEM_CLASS_NONE
|
|
) {
|
|
$parameters['problemClass'] = $problemClass;
|
|
|
|
if (! is_string($activeTab) || $activeTab === '') {
|
|
$parameters['activeTab'] = $problemClass;
|
|
}
|
|
}
|
|
|
|
if (is_string($operationType) && trim($operationType) !== '') {
|
|
$parameters['operation_type'] = OperationCatalog::canonicalCode($operationType);
|
|
}
|
|
|
|
return route('admin.operations.index', $parameters);
|
|
}
|
|
|
|
public static function tenantlessView(OperationRun|int $run, ?CanonicalNavigationContext $context = null): string
|
|
{
|
|
$runId = $run instanceof OperationRun ? (int) $run->getKey() : (int) $run;
|
|
|
|
$workspace = self::resolveWorkspace($run);
|
|
|
|
if (! $workspace instanceof Workspace) {
|
|
return url('/admin');
|
|
}
|
|
|
|
return route('admin.operations.view', array_merge(
|
|
['workspace' => $workspace, 'run' => $runId],
|
|
$context?->toQuery() ?? [],
|
|
));
|
|
}
|
|
|
|
public static function view(OperationRun|int $run, ManagedEnvironment $tenant, ?CanonicalNavigationContext $context = null): string
|
|
{
|
|
return self::tenantlessView($run, $context);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public static function related(OperationRun $run, ?ManagedEnvironment $tenant): array
|
|
{
|
|
$context = is_array($run->context) ? $run->context : [];
|
|
|
|
$links = [];
|
|
|
|
$links[self::collectionLabel()] = self::index($tenant);
|
|
|
|
if (! $tenant instanceof ManagedEnvironment) {
|
|
return $links;
|
|
}
|
|
|
|
$providerConnectionId = $context['provider_connection_id'] ?? null;
|
|
$canonicalType = $run->canonicalOperationType();
|
|
|
|
if (is_numeric($providerConnectionId)) {
|
|
$links['Provider Connections'] = ManagedEnvironmentLinks::providerConnectionsUrl($tenant);
|
|
$links['Provider Connection'] = ManagedEnvironmentLinks::providerConnectionUrl((int) $providerConnectionId, 'edit', $tenant);
|
|
}
|
|
|
|
if ($canonicalType === 'inventory.sync') {
|
|
$links['Inventory'] = InventoryItemResource::getUrl('index', tenant: $tenant);
|
|
}
|
|
|
|
if ($canonicalType === 'policy.sync') {
|
|
$links['Policies'] = PolicyResource::getUrl('index', tenant: $tenant);
|
|
|
|
$policyId = $context['policy_id'] ?? null;
|
|
if (is_numeric($policyId)) {
|
|
$links['Policy'] = PolicyResource::getUrl('view', ['record' => (int) $policyId], tenant: $tenant);
|
|
}
|
|
}
|
|
|
|
if ($canonicalType === 'directory.groups.sync') {
|
|
$links['Directory Groups'] = EntraGroupResource::scopedUrl('index', tenant: $tenant);
|
|
}
|
|
|
|
if ($canonicalType === 'baseline.compare') {
|
|
$links['Drift'] = ManagedEnvironmentLinks::baselineCompareUrl($tenant);
|
|
}
|
|
|
|
if ($canonicalType === 'baseline.capture') {
|
|
$snapshotId = data_get($context, 'result.snapshot_id');
|
|
|
|
if (is_numeric($snapshotId)) {
|
|
$links['Baseline Snapshot'] = BaselineSnapshotResource::getUrl('view', ['record' => (int) $snapshotId], panel: 'admin');
|
|
}
|
|
}
|
|
|
|
if ($canonicalType === 'backup_set.update') {
|
|
$links['Backup Sets'] = BackupSetResource::getUrl('index', tenant: $tenant);
|
|
|
|
$backupSetId = $context['backup_set_id'] ?? null;
|
|
if (is_numeric($backupSetId)) {
|
|
$links['Backup Set'] = BackupSetResource::getUrl('view', ['record' => (int) $backupSetId], tenant: $tenant);
|
|
}
|
|
}
|
|
|
|
if (in_array($canonicalType, ['backup.schedule.execute', 'backup.schedule.retention', 'backup.schedule.purge'], true)) {
|
|
$links['Backup Schedules'] = BackupScheduleResource::getUrl('index', tenant: $tenant);
|
|
}
|
|
|
|
if ($canonicalType === 'restore.execute') {
|
|
$links['Restore Runs'] = RestoreRunResource::getUrl('index', tenant: $tenant);
|
|
|
|
$restoreRunId = $context['restore_run_id'] ?? null;
|
|
if (is_numeric($restoreRunId)) {
|
|
$links['Restore Run'] = RestoreRunResource::getUrl('view', ['record' => (int) $restoreRunId], tenant: $tenant);
|
|
}
|
|
}
|
|
|
|
if ($canonicalType === 'tenant.evidence.snapshot.generate') {
|
|
$snapshot = EvidenceSnapshot::query()
|
|
->where('operation_run_id', (int) $run->getKey())
|
|
->latest('id')
|
|
->first();
|
|
|
|
if ($snapshot instanceof EvidenceSnapshot) {
|
|
$links['Evidence Snapshot'] = EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant);
|
|
}
|
|
}
|
|
|
|
if ($canonicalType === 'environment.review.compose') {
|
|
$review = EnvironmentReview::query()
|
|
->where('operation_run_id', (int) $run->getKey())
|
|
->latest('id')
|
|
->first();
|
|
|
|
if ($review instanceof EnvironmentReview) {
|
|
$links['ManagedEnvironment Review'] = EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review], $tenant);
|
|
}
|
|
}
|
|
|
|
if ($canonicalType === 'environment.review_pack.generate') {
|
|
$pack = ReviewPack::query()
|
|
->where('operation_run_id', (int) $run->getKey())
|
|
->latest('id')
|
|
->first();
|
|
|
|
if ($pack instanceof ReviewPack) {
|
|
$links['Review Pack'] = ReviewPackResource::getUrl('view', ['record' => $pack], tenant: $tenant);
|
|
}
|
|
}
|
|
|
|
return array_filter($links, static fn (?string $url): bool => is_string($url) && $url !== '');
|
|
}
|
|
|
|
private static function resolveWorkspace(ManagedEnvironment|OperationRun|int|null $subject = null): ?Workspace
|
|
{
|
|
if ($subject instanceof ManagedEnvironment) {
|
|
return $subject->workspace()->first();
|
|
}
|
|
|
|
if ($subject instanceof OperationRun) {
|
|
return Workspace::query()->whereKey((int) $subject->workspace_id)->first();
|
|
}
|
|
|
|
if (is_int($subject) && $subject > 0) {
|
|
$run = OperationRun::query()
|
|
->select(['id', 'workspace_id'])
|
|
->whereKey($subject)
|
|
->first();
|
|
|
|
if ($run instanceof OperationRun) {
|
|
return Workspace::query()->whereKey((int) $run->workspace_id)->first();
|
|
}
|
|
}
|
|
|
|
return app(WorkspaceContext::class)->currentWorkspace(request());
|
|
}
|
|
}
|