Automated PR created by Codex via Gitea API. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #460
385 lines
24 KiB
PHP
385 lines
24 KiB
PHP
<x-filament-panels::page>
|
|
@php
|
|
$workspace = $overview['workspace'] ?? ['name' => 'Workspace'];
|
|
$quickActions = $overview['quick_actions'] ?? [];
|
|
$myFindingsSignal = $overview['my_findings_signal'] ?? null;
|
|
$findingsHygieneSignal = $overview['findings_hygiene_signal'] ?? null;
|
|
$zeroTenantState = $overview['zero_tenant_state'] ?? null;
|
|
$attentionItems = $overview['attention_items'] ?? [];
|
|
$summaryMetrics = $overview['summary_metrics'] ?? [];
|
|
$hasVisibleEnvironments = (int) ($overview['accessible_tenant_count'] ?? 0) > 0;
|
|
$priorityAttention = null;
|
|
$priorityAttentionIndex = null;
|
|
|
|
foreach ($attentionItems as $index => $candidateAttention) {
|
|
if (! is_array($candidateAttention)) {
|
|
continue;
|
|
}
|
|
|
|
$candidateDestination = $candidateAttention['destination'] ?? null;
|
|
$candidateActionUrl = is_array($candidateDestination) && ($candidateDestination['disabled'] ?? false) === false
|
|
? ($candidateDestination['url'] ?? null)
|
|
: null;
|
|
|
|
if (is_string($candidateActionUrl) && $candidateActionUrl !== '') {
|
|
$priorityAttention = $candidateAttention;
|
|
$priorityAttentionIndex = $index;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
$primaryQuickActions = array_values(array_filter(
|
|
$quickActions,
|
|
static fn (array $action): bool => ($action['key'] ?? null) === 'choose_environment',
|
|
));
|
|
$operationalQuickActions = array_values(array_filter(
|
|
$quickActions,
|
|
static fn (array $action): bool => in_array($action['key'] ?? null, ['operations', 'alerts'], true),
|
|
));
|
|
$adminQuickActions = array_values(array_filter(
|
|
$quickActions,
|
|
static fn (array $action): bool => in_array($action['key'] ?? null, ['switch_workspace', 'manage_workspaces'], true),
|
|
));
|
|
$listedAttentionItems = $attentionItems;
|
|
|
|
if ($priorityAttentionIndex !== null) {
|
|
unset($listedAttentionItems[$priorityAttentionIndex]);
|
|
$listedAttentionItems = array_values($listedAttentionItems);
|
|
}
|
|
@endphp
|
|
|
|
<div class="space-y-6">
|
|
<x-filament::section>
|
|
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
|
<div class="min-w-0 space-y-2">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<span class="inline-flex items-center gap-1.5 rounded-full border border-primary-200 bg-primary-50 px-3 py-1 text-xs font-medium text-primary-700 dark:border-primary-700/60 dark:bg-primary-950/40 dark:text-primary-300">
|
|
<x-filament::icon icon="heroicon-o-home" class="h-3.5 w-3.5" />
|
|
Workspace overview
|
|
</span>
|
|
|
|
@if (filled($workspace['slug'] ?? null))
|
|
<span class="inline-flex items-center gap-1 rounded-full border border-gray-200 bg-gray-50 px-3 py-1 text-xs font-medium text-gray-600 dark:border-white/10 dark:bg-white/5 dark:text-gray-300">
|
|
{{ $workspace['slug'] }}
|
|
</span>
|
|
@endif
|
|
</div>
|
|
|
|
<h2 class="text-2xl font-semibold tracking-tight text-gray-950 dark:text-white">
|
|
{{ $workspace['name'] ?? 'Workspace' }}
|
|
</h2>
|
|
|
|
<p class="max-w-2xl text-sm leading-6 text-gray-600 dark:text-gray-300">
|
|
Workspace-scoped command center for visible environments. Attention signals are ranked before diagnostic activity.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-3 sm:flex sm:items-center">
|
|
<div class="rounded-lg border border-gray-200 bg-gray-50 px-3 py-2 dark:border-white/10 dark:bg-white/5">
|
|
<div class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
|
Visible environments
|
|
</div>
|
|
<div class="mt-1 text-lg font-semibold text-gray-950 dark:text-white">
|
|
{{ $overview['accessible_tenant_count'] ?? 0 }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="rounded-lg border border-gray-200 bg-gray-50 px-3 py-2 dark:border-white/10 dark:bg-white/5">
|
|
<div class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
|
Scope
|
|
</div>
|
|
<div class="mt-1 text-sm font-semibold text-gray-950 dark:text-white">
|
|
Workspace
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</x-filament::section>
|
|
|
|
@if (is_array($priorityAttention))
|
|
@php
|
|
$priorityDestination = $priorityAttention['destination'] ?? null;
|
|
$priorityActionUrl = is_array($priorityDestination) && ($priorityDestination['disabled'] ?? false) === false
|
|
? ($priorityDestination['url'] ?? null)
|
|
: null;
|
|
$priorityIsCritical = ($priorityAttention['badge_color'] ?? null) === 'danger' || ($priorityAttention['urgency'] ?? null) === 'critical';
|
|
@endphp
|
|
|
|
<section class="rounded-2xl border p-5 shadow-sm {{ $priorityIsCritical ? 'border-danger-200 bg-danger-50/70 dark:border-danger-700/50 dark:bg-danger-950/20' : 'border-warning-200 bg-warning-50/70 dark:border-warning-700/50 dark:bg-warning-950/20' }}">
|
|
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
|
<div class="min-w-0 space-y-3">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<span class="inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-medium {{ $priorityIsCritical ? 'border-danger-200 bg-white text-danger-700 dark:border-danger-700/50 dark:bg-danger-500/10 dark:text-danger-200' : 'border-warning-200 bg-white text-warning-800 dark:border-warning-700/50 dark:bg-warning-500/10 dark:text-warning-100' }}">
|
|
<x-filament::icon icon="heroicon-o-exclamation-triangle" class="h-3.5 w-3.5" />
|
|
Priority attention
|
|
</span>
|
|
<span class="inline-flex items-center rounded-full border border-gray-200 bg-white px-2.5 py-0.5 text-xs font-medium text-gray-700 dark:border-white/10 dark:bg-white/10 dark:text-gray-200">
|
|
{{ $priorityAttention['tenant_label'] ?? 'Environment' }}
|
|
</span>
|
|
<x-filament::badge :color="$priorityAttention['badge_color'] ?? 'warning'" size="sm">
|
|
{{ $priorityAttention['badge'] ?? 'Needs attention' }}
|
|
</x-filament::badge>
|
|
</div>
|
|
|
|
<div class="space-y-1">
|
|
<h2 class="text-base font-semibold text-gray-950 dark:text-white">
|
|
{{ $priorityAttention['title'] ?? 'Workspace attention needed' }}
|
|
</h2>
|
|
<p class="max-w-3xl text-sm leading-6 text-gray-700 dark:text-gray-200">
|
|
{{ $priorityAttention['body'] ?? 'Review the highest-priority environment before treating recent operations as health.' }}
|
|
</p>
|
|
|
|
@if (filled($priorityAttention['supporting_message'] ?? null))
|
|
<p class="text-xs leading-5 text-gray-600 dark:text-gray-300">
|
|
{{ $priorityAttention['supporting_message'] }}
|
|
</p>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
@if (is_string($priorityActionUrl) && $priorityActionUrl !== '')
|
|
<x-filament::button
|
|
tag="a"
|
|
:color="$priorityIsCritical ? 'danger' : 'warning'"
|
|
:href="$priorityActionUrl"
|
|
icon="heroicon-o-arrow-right"
|
|
class="w-full justify-center sm:w-auto"
|
|
>
|
|
{{ $priorityDestination['label'] ?? 'Review priority environment' }}
|
|
</x-filament::button>
|
|
@endif
|
|
</div>
|
|
</section>
|
|
@endif
|
|
|
|
@livewire(\App\Filament\Widgets\Workspace\WorkspaceSummaryStats::class, [
|
|
'metrics' => $summaryMetrics,
|
|
], key('workspace-overview-summary-' . ($workspace['id'] ?? 'none')))
|
|
|
|
@if ($quickActions !== [])
|
|
<section class="rounded-2xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-white/5">
|
|
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
|
<div class="space-y-1">
|
|
<h2 class="text-sm font-semibold text-gray-950 dark:text-white">
|
|
Workspace shortcuts
|
|
</h2>
|
|
<p class="hidden text-sm leading-6 text-gray-600 dark:text-gray-300 sm:block">
|
|
Operational paths stay available without competing with the priority queue.
|
|
</p>
|
|
</div>
|
|
|
|
@if ($primaryQuickActions !== [])
|
|
<x-filament::button
|
|
tag="a"
|
|
color="primary"
|
|
:href="$primaryQuickActions[0]['url']"
|
|
:icon="$primaryQuickActions[0]['icon']"
|
|
class="w-full justify-center sm:w-auto"
|
|
>
|
|
{{ $primaryQuickActions[0]['label'] }}
|
|
</x-filament::button>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="mt-4 grid grid-cols-1 gap-3 lg:grid-cols-[minmax(0,1fr)_minmax(14rem,18rem)]">
|
|
@if ($operationalQuickActions !== [])
|
|
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
|
@foreach ($operationalQuickActions as $action)
|
|
<a
|
|
href="{{ $action['url'] }}"
|
|
class="rounded-xl border border-gray-200 bg-gray-50 px-4 py-3 text-left transition hover:border-primary-300 hover:bg-white hover:shadow-sm dark:border-white/10 dark:bg-white/5 dark:hover:border-primary-500/40 dark:hover:bg-white/10"
|
|
>
|
|
<div class="flex items-start gap-3">
|
|
<div class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-white text-gray-700 ring-1 ring-gray-200 dark:bg-white/10 dark:text-gray-200 dark:ring-white/10">
|
|
<x-filament::icon :icon="$action['icon']" class="h-5 w-5" />
|
|
</div>
|
|
|
|
<div class="min-w-0">
|
|
<div class="text-sm font-semibold text-gray-950 dark:text-white">
|
|
{{ $action['label'] }}
|
|
</div>
|
|
<div class="mt-1 hidden text-xs leading-5 text-gray-600 dark:text-gray-300 sm:block">
|
|
{{ $action['description'] }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
|
|
@if ($adminQuickActions !== [])
|
|
<div class="rounded-xl border border-gray-200 bg-gray-50 p-3 dark:border-white/10 dark:bg-white/5">
|
|
<div class="text-xs font-medium uppercase text-gray-500 dark:text-gray-400">
|
|
Workspace admin
|
|
</div>
|
|
|
|
<div class="mt-2 space-y-2">
|
|
@foreach ($adminQuickActions as $action)
|
|
<a
|
|
href="{{ $action['url'] }}"
|
|
class="flex items-center gap-2 rounded-lg px-2 py-1.5 text-sm font-medium text-gray-700 transition hover:bg-white hover:text-primary-700 dark:text-gray-200 dark:hover:bg-white/10 dark:hover:text-primary-300"
|
|
>
|
|
<x-filament::icon :icon="$action['icon']" class="h-4 w-4 text-gray-500 dark:text-gray-400" />
|
|
<span>{{ $action['label'] }}</span>
|
|
</a>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</section>
|
|
@endif
|
|
|
|
@if ($hasVisibleEnvironments && is_array($myFindingsSignal))
|
|
<section class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm dark:border-white/10 dark:bg-white/5">
|
|
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
|
<div class="space-y-3">
|
|
<div class="inline-flex w-fit items-center gap-2 rounded-full border border-primary-200 bg-primary-50 px-3 py-1 text-xs font-medium text-primary-700 dark:border-primary-700/60 dark:bg-primary-950/40 dark:text-primary-300">
|
|
<x-filament::icon icon="heroicon-o-clipboard-document-check" class="h-3.5 w-3.5" />
|
|
Assigned to me
|
|
</div>
|
|
|
|
<div class="space-y-1">
|
|
<h2 class="text-base font-semibold text-gray-950 dark:text-white">
|
|
{{ $myFindingsSignal['headline'] }}
|
|
</h2>
|
|
<p class="max-w-2xl text-sm leading-6 text-gray-600 dark:text-gray-300">
|
|
{{ $myFindingsSignal['description'] }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex flex-wrap gap-2 text-xs">
|
|
<span class="inline-flex items-center rounded-full border border-gray-200 bg-gray-50 px-3 py-1 font-medium text-gray-700 dark:border-white/10 dark:bg-white/5 dark:text-gray-300">
|
|
Open assigned: {{ $myFindingsSignal['open_assigned_count'] }}
|
|
</span>
|
|
<span class="inline-flex items-center rounded-full border px-3 py-1 font-medium {{ ($myFindingsSignal['overdue_assigned_count'] ?? 0) > 0 ? 'border-danger-200 bg-danger-50 text-danger-700 dark:border-danger-700/50 dark:bg-danger-950/30 dark:text-danger-200' : 'border-success-200 bg-success-50 text-success-700 dark:border-success-700/50 dark:bg-success-950/30 dark:text-success-200' }}">
|
|
Overdue: {{ $myFindingsSignal['overdue_assigned_count'] }}
|
|
</span>
|
|
<span class="inline-flex items-center rounded-full border px-3 py-1 font-medium {{ ($myFindingsSignal['is_calm'] ?? false) ? 'border-success-200 bg-success-50 text-success-700 dark:border-success-700/50 dark:bg-success-950/30 dark:text-success-200' : 'border-warning-200 bg-warning-50 text-warning-700 dark:border-warning-700/50 dark:bg-warning-950/30 dark:text-warning-200' }}">
|
|
{{ ($myFindingsSignal['is_calm'] ?? false) ? 'Calm' : 'Needs follow-up' }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<x-filament::button
|
|
tag="a"
|
|
color="primary"
|
|
:href="$myFindingsSignal['cta_url']"
|
|
icon="heroicon-o-arrow-right"
|
|
>
|
|
{{ $myFindingsSignal['cta_label'] }}
|
|
</x-filament::button>
|
|
</div>
|
|
</section>
|
|
@endif
|
|
|
|
@if ($hasVisibleEnvironments && is_array($findingsHygieneSignal))
|
|
@php
|
|
$hygieneIsCalm = (bool) ($findingsHygieneSignal['is_calm'] ?? false);
|
|
$brokenAssignmentCount = (int) ($findingsHygieneSignal['broken_assignment_count'] ?? 0);
|
|
$staleInProgressCount = (int) ($findingsHygieneSignal['stale_in_progress_count'] ?? 0);
|
|
@endphp
|
|
|
|
<section class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm dark:border-white/10 dark:bg-white/5">
|
|
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
|
<div class="space-y-3">
|
|
<div class="inline-flex w-fit items-center gap-2 rounded-full border px-3 py-1 text-xs font-medium {{ $hygieneIsCalm ? 'border-gray-200 bg-gray-50 text-gray-700 dark:border-white/10 dark:bg-white/5 dark:text-gray-300' : 'border-danger-200 bg-danger-50 text-danger-700 dark:border-danger-700/60 dark:bg-danger-950/40 dark:text-danger-200' }}">
|
|
<x-filament::icon icon="heroicon-o-wrench-screwdriver" class="h-3.5 w-3.5" />
|
|
Findings hygiene
|
|
</div>
|
|
|
|
<div class="space-y-1">
|
|
<h2 class="text-base font-semibold text-gray-950 dark:text-white">
|
|
{{ $findingsHygieneSignal['headline'] }}
|
|
</h2>
|
|
<p class="max-w-2xl text-sm leading-6 text-gray-600 dark:text-gray-300">
|
|
{{ $findingsHygieneSignal['description'] }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex flex-wrap gap-2 text-xs">
|
|
<span class="inline-flex items-center rounded-full border border-gray-200 bg-gray-50 px-3 py-1 font-medium text-gray-700 dark:border-white/10 dark:bg-white/5 dark:text-gray-300">
|
|
Unique issues: {{ $findingsHygieneSignal['unique_issue_count'] }}
|
|
</span>
|
|
<span class="inline-flex items-center rounded-full border px-3 py-1 font-medium {{ $brokenAssignmentCount > 0 ? 'border-danger-200 bg-danger-50 text-danger-700 dark:border-danger-700/50 dark:bg-danger-950/30 dark:text-danger-200' : 'border-gray-200 bg-gray-50 text-gray-700 dark:border-white/10 dark:bg-white/5 dark:text-gray-300' }}">
|
|
Broken assignments: {{ $findingsHygieneSignal['broken_assignment_count'] }}
|
|
</span>
|
|
<span class="inline-flex items-center rounded-full border px-3 py-1 font-medium {{ $staleInProgressCount > 0 ? 'border-warning-200 bg-warning-50 text-warning-700 dark:border-warning-700/50 dark:bg-warning-950/30 dark:text-warning-200' : 'border-gray-200 bg-gray-50 text-gray-700 dark:border-white/10 dark:bg-white/5 dark:text-gray-300' }}">
|
|
Stale in progress: {{ $findingsHygieneSignal['stale_in_progress_count'] }}
|
|
</span>
|
|
<span class="inline-flex items-center rounded-full border px-3 py-1 font-medium {{ ($findingsHygieneSignal['is_calm'] ?? false) ? 'border-success-200 bg-success-50 text-success-700 dark:border-success-700/50 dark:bg-success-950/30 dark:text-success-200' : 'border-warning-200 bg-warning-50 text-warning-700 dark:border-warning-700/50 dark:bg-warning-950/30 dark:text-warning-200' }}">
|
|
{{ ($findingsHygieneSignal['is_calm'] ?? false) ? 'Calm' : 'Needs repair' }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<x-filament::button
|
|
tag="a"
|
|
:color="$hygieneIsCalm ? 'gray' : 'danger'"
|
|
:href="$findingsHygieneSignal['cta_url']"
|
|
icon="heroicon-o-arrow-right"
|
|
>
|
|
{{ $findingsHygieneSignal['cta_label'] }}
|
|
</x-filament::button>
|
|
</div>
|
|
</section>
|
|
@endif
|
|
|
|
@if (is_array($zeroTenantState))
|
|
<section class="rounded-2xl border border-warning-200 bg-warning-50 p-5 shadow-sm dark:border-warning-700/50 dark:bg-warning-950/30">
|
|
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
|
<div class="space-y-1">
|
|
<h2 class="text-sm font-semibold text-warning-900 dark:text-warning-100">
|
|
{{ $zeroTenantState['title'] }}
|
|
</h2>
|
|
<p class="text-sm text-warning-800 dark:text-warning-200">
|
|
{{ $zeroTenantState['body'] }}
|
|
</p>
|
|
</div>
|
|
|
|
<x-filament::button
|
|
tag="a"
|
|
color="warning"
|
|
:href="$zeroTenantState['action_url']"
|
|
icon="heroicon-o-arrow-right"
|
|
>
|
|
{{ $zeroTenantState['action_label'] }}
|
|
</x-filament::button>
|
|
</div>
|
|
</section>
|
|
@endif
|
|
|
|
<div class="space-y-6">
|
|
<div class="flex flex-wrap gap-2 text-xs text-gray-600 dark:text-gray-300">
|
|
<span class="inline-flex items-center rounded-full border border-danger-200 bg-danger-50 px-3 py-1 font-medium text-danger-700 dark:border-danger-700/50 dark:bg-danger-950/30 dark:text-danger-200">
|
|
Governance risk counts affected environments
|
|
</span>
|
|
<span class="inline-flex items-center rounded-full border border-warning-200 bg-warning-50 px-3 py-1 font-medium text-warning-700 dark:border-warning-700/50 dark:bg-warning-950/30 dark:text-warning-200">
|
|
Backup health stays separate from recovery evidence
|
|
</span>
|
|
<span class="inline-flex items-center rounded-full border border-gray-200 bg-white px-3 py-1 font-medium text-gray-600 dark:border-white/10 dark:bg-white/5 dark:text-gray-300">
|
|
Calm wording stays bounded to visible environments and checked domains
|
|
</span>
|
|
<span class="inline-flex items-center rounded-full border border-gray-200 bg-white px-3 py-1 font-medium text-gray-600 dark:border-white/10 dark:bg-white/5 dark:text-gray-300">
|
|
Recent operations stay diagnostic
|
|
</span>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 gap-6 xl:grid-cols-2">
|
|
@livewire(\App\Filament\Widgets\Workspace\WorkspaceNeedsAttention::class, [
|
|
'items' => $listedAttentionItems,
|
|
'emptyState' => $overview['attention_empty_state'] ?? [],
|
|
'triageReviewProgress' => $overview['triage_review_progress'] ?? [],
|
|
], key('workspace-overview-attention-' . ($workspace['id'] ?? 'none')))
|
|
|
|
@livewire(\App\Filament\Widgets\Workspace\WorkspaceRecentOperations::class, [
|
|
'operations' => $overview['recent_operations'] ?? [],
|
|
'emptyState' => $overview['recent_operations_empty_state'] ?? [],
|
|
], key('workspace-overview-operations-' . ($workspace['id'] ?? 'none')))
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</x-filament-panels::page>
|