clupilot/app/resources/views/livewire/pages/clusters/create.blade.php

238 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
use App\Models\Cluster;
use Illuminate\Support\Str;
use Livewire\Attributes\Layout;
use Livewire\Volt\Component;
new #[Layout('layouts.app')] class extends Component {
public int $step = 1;
// Step 1 — basics
public string $name = '';
public string $env = 'prod';
public string $description = '';
// Step 2 — region + size
public string $region = 'eu-central-1';
public string $size = 'medium';
public int $nodes = 2;
// Step 3 — WP options
public string $phpVersion = '8.3';
public bool $autoUpdates = true;
public bool $stagingEnv = true;
public bool $cdnEnabled = true;
public bool $hourlyBackups = true;
public function nextStep(): void
{
if ($this->step === 1) {
$this->validate([
'name' => ['required', 'string', 'min:3', 'max:64'],
'env' => ['required', 'in:prod,staging,edge,dev'],
]);
}
$this->step = min(4, $this->step + 1);
}
public function prevStep(): void
{
$this->step = max(1, $this->step - 1);
}
public function getSlugProperty(): string
{
return Str::slug($this->name) ?: 'neuer-cluster';
}
public function getMonthlyPriceProperty(): int
{
$base = match ($this->size) {
'small' => 4900,
'medium' => 9900,
'large' => 24900,
default => 9900,
};
return ($base * $this->nodes) / 100;
}
public function create(): void
{
Cluster::create([
'name' => $this->name,
'slug' => $this->slug,
'region' => $this->region,
'env' => $this->env,
'monthly_price_cents' => $this->monthlyPrice * 100,
'node_count' => $this->nodes,
'backups_enabled' => $this->hourlyBackups,
]);
$this->redirect(route('servers.index'), navigate: true);
}
}; ?>
<div>
<div class="clu-topbar">
<div>
<h1 class="clu-page-title">Cluster anlegen</h1>
<div class="clu-page-sub">schritt {{ $step }} von 4</div>
</div>
<div class="spacer"></div>
<a href="{{ route('servers.index') }}" wire:navigate class="clu-ghost-btn">Abbrechen</a>
</div>
{{-- Stepper bar --}}
<div class="clu-stepper mb-[18px]" aria-label="Wizard schritte">
@foreach (['Basis', 'Region & Größe', 'WordPress', 'Review'] as $i => $label)
@php $stepNum = $i + 1; @endphp
<div class="clu-step @if($step === $stepNum) active @endif @if($step > $stepNum) done @endif">
<div class="clu-step-num">{{ $stepNum }}</div>
<div class="clu-step-label">{{ $label }}</div>
</div>
@if ($stepNum < 4)
<div class="clu-step-line"></div>
@endif
@endforeach
</div>
<div class="grid gap-[14px]" style="grid-template-columns:minmax(0,2fr) minmax(280px,1fr);">
<div class="clu-card">
@if ($step === 1)
<div class="clu-card-head"><h3>Basis</h3></div>
<div class="clu-card-body" style="display:flex;flex-direction:column;gap:14px;">
<div class="flex flex-col gap-[6px]">
<label class="text-[11.5px] text-(--color-muted) font-semibold">Cluster Name</label>
<div class="clu-input"><input wire:model.live="name" type="text" placeholder="z.B. Acme Cluster Prod" required /></div>
<x-input-error :messages="$errors->get('name')" class="mt-1" />
<div class="clu-slug-preview">URL-Slug: <b>{{ $this->slug }}</b></div>
</div>
<div class="flex flex-col gap-[6px]">
<label class="text-[11.5px] text-(--color-muted) font-semibold">Environment</label>
<div class="flex gap-[8px]">
@foreach (['prod' => 'Production', 'staging' => 'Staging', 'edge' => 'Edge', 'dev' => 'Development'] as $val => $lbl)
<button type="button" wire:click="$set('env', '{{ $val }}')"
class="clu-ghost-btn @if($env === $val) active @endif"
style="@if($env === $val) background:var(--color-accent-soft);border-color:var(--color-accent);color:var(--color-accent); @endif">
{{ $lbl }}
</button>
@endforeach
</div>
</div>
<div class="flex flex-col gap-[6px]">
<label class="text-[11.5px] text-(--color-muted) font-semibold">Beschreibung (optional)</label>
<div class="clu-input"><input wire:model="description" type="text" placeholder="kurz, was hier läuft" /></div>
</div>
</div>
@elseif ($step === 2)
<div class="clu-card-head"><h3>Region & Größe</h3></div>
<div class="clu-card-body" style="display:flex;flex-direction:column;gap:14px;">
<div class="flex flex-col gap-[6px]">
<label class="text-[11.5px] text-(--color-muted) font-semibold">Region</label>
<select wire:model="region" class="clu-input" style="appearance:auto;">
<option value="eu-central-1">eu-central-1 · 🇩🇪 Frankfurt</option>
<option value="eu-west-1">eu-west-1 · 🇮🇪 Ireland</option>
<option value="us-east-1">us-east-1 · 🇺🇸 Virginia</option>
<option value="ap-south-1">ap-south-1 · 🇮🇳 Mumbai</option>
</select>
</div>
<div class="flex flex-col gap-[6px]">
<label class="text-[11.5px] text-(--color-muted) font-semibold">Node-Größe</label>
<div class="grid grid-cols-3 gap-[8px]">
@foreach (['small' => ['4 vCPU · 8 GB', '€49/node'], 'medium' => ['8 vCPU · 32 GB', '€99/node'], 'large' => ['16 vCPU · 64 GB', '€249/node']] as $val => $spec)
<button type="button" wire:click="$set('size', '{{ $val }}')"
class="clu-ghost-btn @if($size === $val) active @endif"
style="flex-direction:column;align-items:flex-start;padding:14px;@if($size === $val) background:var(--color-accent-soft);border-color:var(--color-accent); @endif">
<span class="font-semibold text-[13px]">{{ ucfirst($val) }}</span>
<span class="text-[11px] text-(--color-muted) font-mono">{{ $spec[0] }}</span>
<span class="text-[11px] text-(--color-accent) font-mono mt-[4px]">{{ $spec[1] }}</span>
</button>
@endforeach
</div>
</div>
<div class="flex flex-col gap-[6px]">
<label class="text-[11.5px] text-(--color-muted) font-semibold">Anzahl Nodes: <b>{{ $nodes }}</b></label>
<input wire:model.live="nodes" type="range" min="1" max="10" class="w-full" />
<div class="text-[10.5px] text-(--color-muted) font-mono">1 10 nodes · später skalierbar</div>
</div>
</div>
@elseif ($step === 3)
<div class="clu-card-head"><h3>WordPress Defaults</h3></div>
<div class="clu-card-body" style="display:flex;flex-direction:column;gap:14px;">
<div class="flex flex-col gap-[6px]">
<label class="text-[11.5px] text-(--color-muted) font-semibold">PHP Version</label>
<select wire:model="phpVersion" class="clu-input" style="appearance:auto;">
<option value="8.1">PHP 8.1</option>
<option value="8.2">PHP 8.2</option>
<option value="8.3">PHP 8.3 (empfohlen)</option>
<option value="8.4">PHP 8.4 (beta)</option>
</select>
</div>
<label class="clu-checkbox" style="justify-content:space-between;width:100%;">
<span>Auto-Updates (Minor + Security)</span>
<input type="checkbox" wire:model.live="autoUpdates" />
<span class="box"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5"><path d="M5 12l5 5L20 7"/></svg></span>
</label>
<label class="clu-checkbox" style="justify-content:space-between;width:100%;">
<span>Staging Environment</span>
<input type="checkbox" wire:model.live="stagingEnv" />
<span class="box"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5"><path d="M5 12l5 5L20 7"/></svg></span>
</label>
<label class="clu-checkbox" style="justify-content:space-between;width:100%;">
<span>CDN (Cloudflare)</span>
<input type="checkbox" wire:model.live="cdnEnabled" />
<span class="box"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5"><path d="M5 12l5 5L20 7"/></svg></span>
</label>
<label class="clu-checkbox" style="justify-content:space-between;width:100%;">
<span>Stündliche Backups</span>
<input type="checkbox" wire:model.live="hourlyBackups" />
<span class="box"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5"><path d="M5 12l5 5L20 7"/></svg></span>
</label>
</div>
@elseif ($step === 4)
<div class="clu-card-head"><h3>Review</h3></div>
<div class="clu-card-body" style="display:flex;flex-direction:column;gap:10px;">
<div class="flex justify-between"><span class="text-(--color-muted)">Name</span><span class="font-semibold">{{ $name }}</span></div>
<div class="flex justify-between"><span class="text-(--color-muted)">Environment</span><span class="clu-pill accent">{{ strtoupper($env) }}</span></div>
<div class="flex justify-between"><span class="text-(--color-muted)">Region</span><span class="font-mono">{{ $region }}</span></div>
<div class="flex justify-between"><span class="text-(--color-muted)">Größe</span><span>{{ ucfirst($size) }} × {{ $nodes }}</span></div>
<div class="flex justify-between"><span class="text-(--color-muted)">PHP</span><span class="font-mono">{{ $phpVersion }}</span></div>
<div class="flex justify-between"><span class="text-(--color-muted)">Auto-Updates</span><span>{{ $autoUpdates ? 'an' : 'aus' }}</span></div>
<div class="flex justify-between"><span class="text-(--color-muted)">Staging</span><span>{{ $stagingEnv ? 'an' : 'aus' }}</span></div>
<div class="flex justify-between"><span class="text-(--color-muted)">CDN</span><span>{{ $cdnEnabled ? 'an' : 'aus' }}</span></div>
<div class="flex justify-between"><span class="text-(--color-muted)">Hourly Backups</span><span>{{ $hourlyBackups ? 'an' : 'aus' }}</span></div>
</div>
@endif
{{-- Wizard nav --}}
<div class="flex justify-between gap-[8px] p-[14px] border-t border-(--color-hairline)">
<button type="button" class="clu-ghost-btn" wire:click="prevStep" @if($step === 1) disabled @endif>← Zurück</button>
@if ($step < 4)
<button type="button" class="clu-btn-primary" wire:click="nextStep" style="height:36px;padding:0 16px;">Weiter →</button>
@else
<button type="button" class="clu-btn-primary" wire:click="create" style="height:36px;padding:0 16px;">Cluster erstellen ✓</button>
@endif
</div>
</div>
{{-- Sticky summary --}}
<div class="clu-card" style="height:fit-content;position:sticky;top:90px;">
<div class="clu-card-head"><h3>Zusammenfassung</h3></div>
<div class="clu-card-body" style="display:flex;flex-direction:column;gap:10px;">
<div class="flex justify-between text-[12.5px]"><span class="text-(--color-muted)">Name</span><span class="font-semibold">{{ $name ?: '—' }}</span></div>
<div class="flex justify-between text-[12.5px]"><span class="text-(--color-muted)">Env</span><span class="font-mono">{{ $env }}</span></div>
<div class="flex justify-between text-[12.5px]"><span class="text-(--color-muted)">Region</span><span class="font-mono">{{ $region }}</span></div>
<div class="flex justify-between text-[12.5px]"><span class="text-(--color-muted)">Size</span><span>{{ ucfirst($size) }} × {{ $nodes }}</span></div>
<div class="flex justify-between text-[12.5px]"><span class="text-(--color-muted)">Backups</span><span>{{ $hourlyBackups ? 'hourly' : 'daily' }}</span></div>
<div class="border-t border-(--color-hairline) pt-[10px] mt-[6px]">
<div class="flex justify-between items-center">
<span class="text-[12px] text-(--color-muted)">monatlich</span>
<span class="font-mono font-semibold text-[18px]">€{{ number_format($this->monthlyPrice, 0, ',', '.') }}</span>
</div>
</div>
</div>
</div>
</div>
</div>