feat: add async prerequisite installation with retry logic and visual feedback

This commit enhances the boarding flow to handle prerequisite installation asynchronously with proper retry logic and user feedback:

- Add retry mechanism with max 3 attempts for prerequisite installation
- Display live installation logs via ActivityMonitor during boarding
- Reset ActivityMonitor state when starting new activity to prevent stale event dispatching
- Support dynamic header updates in ActivityMonitor
- Add prerequisitesInstalled event handler to revalidate after installation completes
- Extract validation logic into continueValidation() method for cleaner flow
- Add unit tests for prerequisite installation logic

This improves UX by showing users real-time progress during prerequisite installation and handles installation failures gracefully with automatic retries.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai
2025-11-24 08:44:04 +01:00
parent 29135e00ba
commit 30d206e7b9
6 changed files with 253 additions and 10 deletions

View File

@@ -14,7 +14,10 @@ use Visus\Cuid2\Cuid2;
class Index extends Component
{
protected $listeners = ['refreshBoardingIndex' => 'validateServer'];
protected $listeners = [
'refreshBoardingIndex' => 'validateServer',
'prerequisitesInstalled' => 'handlePrerequisitesInstalled',
];
#[\Livewire\Attributes\Url(as: 'step', history: true)]
public string $currentState = 'welcome';
@@ -76,6 +79,10 @@ class Index extends Component
public ?string $minDockerVersion = null;
public int $prerequisiteInstallAttempts = 0;
public int $maxPrerequisiteInstallAttempts = 3;
public function mount()
{
if (auth()->user()?->isMember() && auth()->user()->currentTeam()->show_boarding === true) {
@@ -324,18 +331,58 @@ class Index extends Component
// Check prerequisites
$validationResult = $this->createdServer->validatePrerequisites();
if (! $validationResult['success']) {
$this->createdServer->installPrerequisites();
// Recheck after installation
$validationResult = $this->createdServer->validatePrerequisites();
if (! $validationResult['success']) {
// Check if we've exceeded max attempts
if ($this->prerequisiteInstallAttempts >= $this->maxPrerequisiteInstallAttempts) {
$missingCommands = implode(', ', $validationResult['missing']);
throw new \Exception("Prerequisites ({$missingCommands}) could not be installed. Please install them manually.");
throw new \Exception("Prerequisites ({$missingCommands}) could not be installed after {$this->maxPrerequisiteInstallAttempts} attempts. Please install them manually.");
}
// Start async installation and wait for completion via ActivityMonitor
$activity = $this->createdServer->installPrerequisites();
$this->prerequisiteInstallAttempts++;
$this->dispatch('activityMonitor', $activity->id, 'prerequisitesInstalled');
// Return early - handlePrerequisitesInstalled() will be called when installation completes
return;
}
// Prerequisites are already installed, continue with validation
$this->continueValidation();
} catch (\Throwable $e) {
return handleError(error: $e, livewire: $this);
}
}
public function handlePrerequisitesInstalled()
{
try {
// Revalidate prerequisites after installation completes
$validationResult = $this->createdServer->validatePrerequisites();
if (! $validationResult['success']) {
// Installation completed but prerequisites still missing - retry
$missingCommands = implode(', ', $validationResult['missing']);
if ($this->prerequisiteInstallAttempts >= $this->maxPrerequisiteInstallAttempts) {
throw new \Exception("Prerequisites ({$missingCommands}) could not be installed after {$this->maxPrerequisiteInstallAttempts} attempts. Please install them manually.");
}
// Try again
$activity = $this->createdServer->installPrerequisites();
$this->prerequisiteInstallAttempts++;
$this->dispatch('activityMonitor', $activity->id, 'prerequisitesInstalled');
return;
}
// Prerequisites validated successfully - continue with Docker validation
$this->continueValidation();
} catch (\Throwable $e) {
return handleError(error: $e, livewire: $this);
}
}
private function continueValidation()
{
try {
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this->createdServer, true);
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);