This PR introduces the Operation Run Actionability System. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #439
418 lines
15 KiB
PHP
418 lines
15 KiB
PHP
<?php
|
|
|
|
namespace App\Support;
|
|
|
|
use App\Filament\Pages\InventoryCoverage as InventoryCoveragePage;
|
|
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\RestoreRun;
|
|
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 viewFromOperationsIndex(OperationRun|int $run, string $backUrl): string
|
|
{
|
|
return self::tenantlessView(
|
|
$run,
|
|
self::operationsIndexNavigationContext($run, $backUrl),
|
|
);
|
|
}
|
|
|
|
public static function operationsIndexNavigationContext(OperationRun|int $run, string $backUrl): CanonicalNavigationContext
|
|
{
|
|
return new CanonicalNavigationContext(
|
|
sourceSurface: 'operations.index',
|
|
canonicalRouteName: 'admin.operations.index',
|
|
tenantId: $run instanceof OperationRun && is_numeric($run->managed_environment_id)
|
|
? (int) $run->managed_environment_id
|
|
: null,
|
|
backLinkLabel: 'Back to Operations',
|
|
backLinkUrl: $backUrl,
|
|
);
|
|
}
|
|
|
|
public static function withNavigationContext(string $url, ?CanonicalNavigationContext $context): string
|
|
{
|
|
if (! $context instanceof CanonicalNavigationContext) {
|
|
return $url;
|
|
}
|
|
|
|
if (! self::canCarryNavigationContext($url) || self::hasNavigationContext($url)) {
|
|
return $url;
|
|
}
|
|
|
|
return url()->query($url, $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 ($run->inventoryCoverage() instanceof \App\Support\Inventory\InventoryCoverage) {
|
|
$links['Inventory Coverage'] = InventoryCoveragePage::getUrl(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 = $run->reconciledRelatedBaselineSnapshotId()
|
|
?? (is_numeric(data_get($context, 'baseline_snapshot_id'))
|
|
? (int) data_get($context, 'baseline_snapshot_id')
|
|
: (is_numeric(data_get($context, 'result.snapshot_id'))
|
|
? (int) data_get($context, 'result.snapshot_id')
|
|
: null));
|
|
|
|
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);
|
|
|
|
$backupSetId = $run->reconciledRelatedBackupSetId()
|
|
?? (is_numeric($context['backup_set_id'] ?? null) ? (int) $context['backup_set_id'] : null);
|
|
|
|
if ($backupSetId !== null) {
|
|
$links['Backup Sets'] = BackupSetResource::getUrl('index', tenant: $tenant);
|
|
$links['Backup Set'] = BackupSetResource::getUrl('view', ['record' => $backupSetId], tenant: $tenant);
|
|
}
|
|
}
|
|
|
|
if ($canonicalType === 'restore.execute') {
|
|
$links['Restore Runs'] = RestoreRunResource::getUrl('index', tenant: $tenant);
|
|
|
|
$restoreRunId = $context['restore_run_id'] ?? null;
|
|
if (is_numeric($restoreRunId)) {
|
|
$restoreRun = RestoreRun::query()
|
|
->whereKey((int) $restoreRunId)
|
|
->where('workspace_id', (int) $run->workspace_id)
|
|
->where('managed_environment_id', (int) $run->managed_environment_id)
|
|
->first();
|
|
|
|
if (
|
|
$restoreRun instanceof RestoreRun
|
|
&& (
|
|
! is_numeric($restoreRun->operation_run_id)
|
|
|| (int) $restoreRun->operation_run_id === (int) $run->getKey()
|
|
)
|
|
) {
|
|
$links['Restore Run'] = RestoreRunResource::getUrl('view', ['record' => $restoreRun], tenant: $tenant);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($canonicalType === 'tenant.evidence.snapshot.generate') {
|
|
$snapshot = null;
|
|
$relatedSnapshotId = $run->reconciledRelatedEvidenceSnapshotId();
|
|
|
|
if ($relatedSnapshotId !== null) {
|
|
$snapshot = EvidenceSnapshot::query()
|
|
->whereKey($relatedSnapshotId)
|
|
->where('workspace_id', (int) $run->workspace_id)
|
|
->where('managed_environment_id', (int) $run->managed_environment_id)
|
|
->first();
|
|
}
|
|
|
|
if (! $snapshot instanceof EvidenceSnapshot) {
|
|
$snapshot = EvidenceSnapshot::query()
|
|
->where('workspace_id', (int) $run->workspace_id)
|
|
->where('managed_environment_id', (int) $run->managed_environment_id)
|
|
->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 = null;
|
|
$relatedReviewId = $run->reconciledRelatedReviewId();
|
|
|
|
if ($relatedReviewId !== null) {
|
|
$review = EnvironmentReview::query()
|
|
->whereKey($relatedReviewId)
|
|
->where('workspace_id', (int) $run->workspace_id)
|
|
->where('managed_environment_id', (int) $run->managed_environment_id)
|
|
->first();
|
|
}
|
|
|
|
if (! $review instanceof EnvironmentReview) {
|
|
$review = EnvironmentReview::query()
|
|
->where('workspace_id', (int) $run->workspace_id)
|
|
->where('managed_environment_id', (int) $run->managed_environment_id)
|
|
->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 = null;
|
|
$relatedReviewPackId = $run->reconciledRelatedReviewPackId();
|
|
|
|
if ($relatedReviewPackId !== null) {
|
|
$pack = ReviewPack::query()
|
|
->whereKey($relatedReviewPackId)
|
|
->where('workspace_id', (int) $run->workspace_id)
|
|
->where('managed_environment_id', (int) $run->managed_environment_id)
|
|
->first();
|
|
}
|
|
|
|
if (! $pack instanceof ReviewPack) {
|
|
$pack = ReviewPack::query()
|
|
->where('workspace_id', (int) $run->workspace_id)
|
|
->where('managed_environment_id', (int) $run->managed_environment_id)
|
|
->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());
|
|
}
|
|
|
|
private static function hasNavigationContext(string $url): bool
|
|
{
|
|
$query = parse_url($url, PHP_URL_QUERY);
|
|
|
|
return is_string($query)
|
|
&& (str_contains($query, 'nav%5B') || str_contains($query, 'nav['));
|
|
}
|
|
|
|
private static function canCarryNavigationContext(string $url): bool
|
|
{
|
|
$path = parse_url($url, PHP_URL_PATH);
|
|
|
|
if (! is_string($path) || ! str_starts_with($path, '/admin')) {
|
|
return false;
|
|
}
|
|
|
|
$host = parse_url($url, PHP_URL_HOST);
|
|
|
|
if ($host === null || $host === false) {
|
|
return true;
|
|
}
|
|
|
|
return $host === parse_url(url('/'), PHP_URL_HOST);
|
|
}
|
|
}
|