feat(admin): provisioning liveness — per-run progress bar, last-activity, stale warning
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>feat/portal-design
parent
b50f97568f
commit
be99f413f7
|
|
@ -60,6 +60,14 @@ class Provisioning extends Component
|
|||
$this->dispatch('notify', message: __('admin.run_retried'));
|
||||
}
|
||||
|
||||
/** A run that claims to be in progress but hasn't advanced in a while looks stuck. */
|
||||
private function isStale(ProvisioningRun $run): bool
|
||||
{
|
||||
return in_array($run->status, ['running', 'waiting'], true)
|
||||
&& $run->updated_at !== null
|
||||
&& $run->updated_at->lt(now()->subMinutes(2));
|
||||
}
|
||||
|
||||
private function subjectLabel(ProvisioningRun $run): string
|
||||
{
|
||||
$subject = $run->subject;
|
||||
|
|
@ -84,16 +92,24 @@ class Provisioning extends Component
|
|||
|
||||
return view('livewire.admin.provisioning', [
|
||||
'hasActive' => $active !== null && in_array($active->status, ['pending', 'running', 'waiting'], true),
|
||||
'rows' => $runs->map(fn (ProvisioningRun $r) => [
|
||||
'rows' => $runs->map(function (ProvisioningRun $r) {
|
||||
$total = max(count(config('provisioning.pipelines.'.$r->pipeline, [])), 1);
|
||||
$done = $r->status === ProvisioningRun::STATUS_COMPLETED ? $total : $r->current_step;
|
||||
|
||||
return [
|
||||
'uuid' => $r->uuid,
|
||||
'customer' => $this->subjectLabel($r),
|
||||
'pipeline' => $r->pipeline,
|
||||
'step' => $r->status === 'completed' ? '—' : $this->currentStepLabel($r),
|
||||
'n' => ($r->current_step + 1).'/'.count(config('provisioning.pipelines.'.$r->pipeline, [])),
|
||||
'n' => ($r->current_step + 1).'/'.$total,
|
||||
'percent' => (int) round($done / $total * 100),
|
||||
'attempt' => $r->attempt,
|
||||
'state' => $this->runState($r),
|
||||
'failed' => $r->status === ProvisioningRun::STATUS_FAILED,
|
||||
])->all(),
|
||||
'activity' => $r->updated_at?->diffForHumans() ?? '—',
|
||||
'stale' => $this->isStale($r),
|
||||
];
|
||||
})->all(),
|
||||
'panel' => $active ? $this->panelFor($active) : null,
|
||||
]);
|
||||
}
|
||||
|
|
@ -120,6 +136,9 @@ class Provisioning extends Component
|
|||
'done' => $done,
|
||||
'total' => $total,
|
||||
'percent' => (int) round($done / $total * 100),
|
||||
'activity' => $run->updated_at?->diffForHumans() ?? '—',
|
||||
'started' => $run->started_at?->diffForHumans() ?? null,
|
||||
'stale' => $this->isStale($run),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,10 @@ return [
|
|||
|
||||
'retry' => 'Erneut versuchen',
|
||||
'run_retried' => 'Lauf wird erneut gestartet.',
|
||||
'run_started' => 'Gestartet',
|
||||
'run_activity' => 'Letzte Aktivität',
|
||||
'run_stale_hint' => 'Seit über 2 Minuten kein Fortschritt — der Lauf hängt möglicherweise (in dieser Umgebung fehlt echte Infrastruktur).',
|
||||
'state_stale' => 'Keine Aktivität',
|
||||
'state_running' => 'Läuft',
|
||||
'state_done' => 'Fertig',
|
||||
'state_failed' => 'Fehlgeschlagen',
|
||||
|
|
|
|||
|
|
@ -95,6 +95,10 @@ return [
|
|||
|
||||
'retry' => 'Retry',
|
||||
'run_retried' => 'Run is being retried.',
|
||||
'run_started' => 'Started',
|
||||
'run_activity' => 'Last activity',
|
||||
'run_stale_hint' => 'No progress for over 2 minutes — the run may be stuck (this environment has no real infrastructure).',
|
||||
'state_stale' => 'No activity',
|
||||
'state_running' => 'Running',
|
||||
'state_done' => 'Done',
|
||||
'state_failed' => 'Failed',
|
||||
|
|
|
|||
|
|
@ -26,10 +26,22 @@
|
|||
<tr wire:key="run-{{ $r['uuid'] }}" class="border-b border-line last:border-0 hover:bg-surface-hover">
|
||||
<td class="px-4 py-3 font-medium text-ink">{{ $r['customer'] }}
|
||||
<span class="ml-1 font-mono text-xs text-faint">{{ $r['pipeline'] }}</span></td>
|
||||
<td class="px-4 py-3"><span class="text-xs text-body">{{ $r['step'] }}</span>
|
||||
<span class="font-mono text-xs text-faint">{{ $r['n'] }}</span></td>
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex items-baseline gap-1.5">
|
||||
<span class="text-xs text-body">{{ $r['step'] }}</span>
|
||||
<span class="font-mono text-xs text-faint">{{ $r['n'] }}</span>
|
||||
</div>
|
||||
<div class="mt-1.5 flex items-center gap-2">
|
||||
<div class="h-1 w-24 overflow-hidden rounded-pill bg-surface-2">
|
||||
<div class="h-full rounded-pill {{ $r['failed'] ? 'bg-danger' : ($r['stale'] ? 'bg-warning' : 'bg-accent') }} transition-[width] duration-500" style="width: {{ $r['percent'] }}%"></div>
|
||||
</div>
|
||||
<span class="font-mono text-[0.65rem] {{ $r['stale'] ? 'text-warning' : 'text-faint' }}">{{ $r['activity'] }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-3 font-mono text-xs text-muted">{{ $r['attempt'] }}</td>
|
||||
<td class="px-4 py-3"><x-ui.badge :status="$tone">{{ __('admin.state_'.$r['state']) }}</x-ui.badge></td>
|
||||
<td class="px-4 py-3">
|
||||
<x-ui.badge :status="$r['stale'] ? 'warning' : $tone">{{ $r['stale'] ? __('admin.state_stale') : __('admin.state_'.$r['state']) }}</x-ui.badge>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right">
|
||||
@if ($r['failed'])
|
||||
<button type="button" wire:click="retry('{{ $r['uuid'] }}')" wire:loading.attr="disabled" wire:target="retry('{{ $r['uuid'] }}')"
|
||||
|
|
@ -69,6 +81,20 @@
|
|||
</div>
|
||||
<p class="mt-1.5 font-mono text-xs text-muted">{{ __('admin.run_done', ['done' => $panel['done'], 'total' => $panel['total']]) }}</p>
|
||||
|
||||
<dl class="mt-3 space-y-1 text-xs">
|
||||
@if ($panel['started'])
|
||||
<div class="flex justify-between"><dt class="text-faint">{{ __('admin.run_started') }}</dt><dd class="font-mono text-muted">{{ $panel['started'] }}</dd></div>
|
||||
@endif
|
||||
<div class="flex justify-between"><dt class="text-faint">{{ __('admin.run_activity') }}</dt><dd class="font-mono {{ $panel['stale'] ? 'text-warning' : 'text-muted' }}">{{ $panel['activity'] }}</dd></div>
|
||||
</dl>
|
||||
|
||||
@if ($panel['stale'] && ! $panel['failed'])
|
||||
<div class="mt-3 flex items-start gap-2 rounded-lg border border-warning-border bg-warning-bg p-2.5">
|
||||
<x-ui.icon name="alert-triangle" class="mt-0.5 size-4 shrink-0 text-warning" />
|
||||
<p class="text-xs text-body">{{ __('admin.run_stale_hint') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($panel['failed'])
|
||||
<div class="mt-4 rounded-lg border border-danger-border bg-danger-bg p-3">
|
||||
<p class="text-xs font-semibold text-danger">{{ __('admin.state_failed') }}</p>
|
||||
|
|
|
|||
Loading…
Reference in New Issue