diff --git a/app/Livewire/Admin/Provisioning.php b/app/Livewire/Admin/Provisioning.php index 7b31884..0071092 100644 --- a/app/Livewire/Admin/Provisioning.php +++ b/app/Livewire/Admin/Provisioning.php @@ -4,8 +4,10 @@ namespace App\Livewire\Admin; use App\Livewire\Concerns\BuildsRunSteps; use App\Models\Host; +use App\Models\Instance; use App\Models\Order; use App\Models\ProvisioningRun; +use App\Provisioning\Jobs\AdvanceRunJob; use Livewire\Attributes\Layout; use Livewire\Attributes\On; use Livewire\Component; @@ -21,6 +23,35 @@ class Provisioning extends Component // A round-trip re-renders with fresh run state. } + /** Retry a failed run from its current step (mirrors the host-detail retry). */ + public function retry(string $uuid): void + { + $run = ProvisioningRun::query()->where('uuid', $uuid)->first(); + if ($run === null || $run->status !== ProvisioningRun::STATUS_FAILED) { + return; + } + + $run->update([ + 'status' => ProvisioningRun::STATUS_RUNNING, + 'attempt' => 0, + 'next_attempt_at' => now(), + 'started_at' => now(), // reset the step timer so it doesn't re-time-out instantly + 'error' => null, + ]); + + // Move the subject out of its error state so the console reflects the retry. + $subject = $run->subject; + if ($subject instanceof Host) { + $subject->update(['status' => 'onboarding']); + } elseif ($subject instanceof Order) { + $subject->update(['status' => 'provisioning']); + Instance::query()->where('order_id', $subject->id)->where('status', 'failed')->update(['status' => 'provisioning']); + } + + AdvanceRunJob::dispatch($run->uuid); + $this->dispatch('notify', message: __('admin.run_retried')); + } + private function subjectLabel(ProvisioningRun $run): string { $subject = $run->subject; @@ -46,12 +77,14 @@ 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) => [ + '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, [])), 'attempt' => $r->attempt, 'state' => $this->runState($r), + 'failed' => $r->status === ProvisioningRun::STATUS_FAILED, ])->all(), 'panel' => $active ? $this->panelFor($active) : null, ]); @@ -67,6 +100,7 @@ class Provisioning extends Component $done = $completed ? $total : $current; return [ + 'uuid' => $run->uuid, 'subject' => $this->subjectLabel($run), 'pipeline' => $run->pipeline, 'attempt' => $run->attempt, diff --git a/lang/de/admin.php b/lang/de/admin.php index 101a76f..c39b7b0 100644 --- a/lang/de/admin.php +++ b/lang/de/admin.php @@ -91,6 +91,8 @@ return [ 'warning' => 'Warnung', ], + 'retry' => 'Erneut versuchen', + 'run_retried' => 'Lauf wird erneut gestartet.', 'state_running' => 'Läuft', 'state_done' => 'Fertig', 'state_failed' => 'Fehlgeschlagen', diff --git a/lang/en/admin.php b/lang/en/admin.php index 5f5b427..616c6de 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -91,6 +91,8 @@ return [ 'warning' => 'Warning', ], + 'retry' => 'Retry', + 'run_retried' => 'Run is being retried.', 'state_running' => 'Running', 'state_done' => 'Done', 'state_failed' => 'Failed', diff --git a/resources/css/safelist-tailwindcss.txt b/resources/css/safelist-tailwindcss.txt new file mode 100644 index 0000000..c99c89b --- /dev/null +++ b/resources/css/safelist-tailwindcss.txt @@ -0,0 +1,5 @@ +sm:max-w-md +md:max-w-xl +lg:max-w-3xl +xl:max-w-5xl +2xl:max-w-7xl diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php index 09aad0e..df9a1c0 100644 --- a/resources/views/layouts/admin.blade.php +++ b/resources/views/layouts/admin.blade.php @@ -84,10 +84,16 @@
diff --git a/resources/views/layouts/portal-app.blade.php b/resources/views/layouts/portal-app.blade.php index 51bc39a..e2faa49 100644 --- a/resources/views/layouts/portal-app.blade.php +++ b/resources/views/layouts/portal-app.blade.php @@ -92,13 +92,18 @@ {{-- Global toast — any element can trigger it with $dispatch('notify', { message }) --}}
diff --git a/resources/views/livewire/admin/datacenters.blade.php b/resources/views/livewire/admin/datacenters.blade.php index b95aa24..fb608ec 100644 --- a/resources/views/livewire/admin/datacenters.blade.php +++ b/resources/views/livewire/admin/datacenters.blade.php @@ -25,7 +25,7 @@ {{ $dc->code }} @if ($editingUuid === $dc->uuid) - +
diff --git a/resources/views/livewire/admin/provisioning.blade.php b/resources/views/livewire/admin/provisioning.blade.php index 2ca94ec..135a6c8 100644 --- a/resources/views/livewire/admin/provisioning.blade.php +++ b/resources/views/livewire/admin/provisioning.blade.php @@ -17,18 +17,29 @@ {{ __('admin.col.step') }} {{ __('admin.col.attempt') }} {{ __('admin.col.status') }} + {{ __('admin.col.actions') }} @foreach ($rows as $r) @php $tone = ['running' => 'provisioning', 'done' => 'active', 'failed' => 'failed'][$r['state']] ?? 'info'; @endphp - + {{ $r['customer'] }} {{ $r['pipeline'] }} {{ $r['step'] }} {{ $r['n'] }} {{ $r['attempt'] }} {{ __('admin.state_'.$r['state']) }} + + @if ($r['failed']) + + @else + + @endif + @endforeach @@ -62,6 +73,10 @@

{{ __('admin.state_failed') }}

@if ($panel['error'])

{{ $panel['error'] }}

@endif +
@elseif ($panel['current'])
diff --git a/resources/views/vendor/wire-elements-modal/modal.blade.php b/resources/views/vendor/wire-elements-modal/modal.blade.php index 6916e8c..cdab72a 100644 --- a/resources/views/vendor/wire-elements-modal/modal.blade.php +++ b/resources/views/vendor/wire-elements-modal/modal.blade.php @@ -11,7 +11,7 @@ x-on:close.stop="setShowPropertyTo(false)" x-on:keydown.escape.window="show && closeModalOnEscape()" x-show="show" - class="fixed inset-0 z-10 overflow-y-auto" + class="fixed inset-0 z-[70] overflow-y-auto" style="display: none;" >
diff --git a/tests/Feature/Admin/ProvisioningActionsTest.php b/tests/Feature/Admin/ProvisioningActionsTest.php new file mode 100644 index 0000000..8a05474 --- /dev/null +++ b/tests/Feature/Admin/ProvisioningActionsTest.php @@ -0,0 +1,42 @@ +create(['status' => 'error']); + $run = ProvisioningRun::factory()->create([ + 'subject_type' => Host::class, 'subject_id' => $host->id, 'pipeline' => 'host', + 'status' => ProvisioningRun::STATUS_FAILED, 'attempt' => 3, 'current_step' => 4, 'error' => 'boom', + ]); + + Livewire::actingAs(admin())->test(Provisioning::class)->call('retry', $run->uuid); + + $run->refresh(); + expect($run->status)->toBe(ProvisioningRun::STATUS_RUNNING) + ->and($run->attempt)->toBe(0) + ->and($run->error)->toBeNull() + ->and($host->fresh()->status)->toBe('onboarding'); + Queue::assertPushed(AdvanceRunJob::class); +}); + +it('ignores retry on a run that is not failed', function () { + Queue::fake(); + $host = Host::factory()->active()->create(); + $run = ProvisioningRun::factory()->create([ + 'subject_type' => Host::class, 'subject_id' => $host->id, 'pipeline' => 'host', + 'status' => ProvisioningRun::STATUS_RUNNING, + ]); + + Livewire::actingAs(admin())->test(Provisioning::class)->call('retry', $run->uuid); + + expect($run->fresh()->status)->toBe(ProvisioningRun::STATUS_RUNNING); + Queue::assertNotPushed(AdvanceRunJob::class); +});