host = $host; } /** Live refresh whenever any run advances (admins-only channel). */ #[On('echo-private:admin.runs,StepAdvanced')] public function onStepAdvanced(): void { $this->host->refresh(); } public function retry(): void { $run = $this->currentRun(); if ($run !== null && $run->status === ProvisioningRun::STATUS_FAILED) { $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, ]); $this->host->update(['status' => 'onboarding']); AdvanceRunJob::dispatch($run->uuid); } } private function currentRun(): ?ProvisioningRun { return $this->host->runs()->latest('id')->first(); } /** @return array */ private function buildSteps(?ProvisioningRun $run): array { $pipeline = config('provisioning.pipelines.host', []); $current = $run?->current_step ?? 0; $status = $run?->status; $steps = []; foreach ($pipeline as $index => $class) { if ($status === ProvisioningRun::STATUS_COMPLETED || $index < $current) { $state = 'done'; } elseif ($index === $current) { $state = $status === ProvisioningRun::STATUS_FAILED ? 'failed' : 'running'; } else { $state = 'pending'; } $steps[] = ['label' => __(app($class)->label()), 'state' => $state]; } return $steps; } public function render() { $run = $this->currentRun(); /** @var Collection $events */ $events = $run ? $run->events()->latest('id')->limit(30)->get() : collect(); return view('livewire.admin.host-detail', [ 'run' => $run, 'steps' => $this->buildSteps($run), 'events' => $events, ]); } }