mailwolt/resources/views/livewire/setup/wizard.blade.php

305 lines
20 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.

<div style="width:100%;max-width:520px;padding:24px 16px">
{{-- ═══ Logo ═══ --}}
<div style="display:flex;align-items:center;gap:10px;margin-bottom:32px;justify-content:center">
<div style="width:32px;height:32px;background:linear-gradient(135deg,#6366f1,#4f46e5);border-radius:8px;display:flex;align-items:center;justify-content:center;box-shadow:0 0 16px rgba(99,102,241,.4)">
<svg width="16" height="16" viewBox="0 0 18 18" fill="none">
<path d="M9 2L15.5 5.5v7L9 16 2.5 12.5v-7L9 2Z" stroke="white" stroke-width="1.5" stroke-linejoin="round"/>
<path d="M9 6L12 7.5v3L9 12 6 10.5v-3L9 6Z" fill="white" opacity=".9"/>
</svg>
</div>
<div>
<div style="font-size:14px;font-weight:700;letter-spacing:.3px;color:var(--mw-t1)">MAILWOLT</div>
<div style="font-size:11px;color:var(--mw-t4);margin-top:-1px">Ersteinrichtung</div>
</div>
</div>
{{-- ═══ Schritt-Indikator ═══ --}}
@if($step < 5)
<div style="display:flex;align-items:center;gap:0;margin-bottom:28px">
@foreach([1 => 'System', 2 => 'Domains', 3 => 'Admin', 4 => 'Fertig'] as $n => $label)
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:5px">
<div style="width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11.5px;font-weight:600;transition:all .2s;
{{ $step > $n
? 'background:rgba(99,102,241,.25);border:1.5px solid #6366f1;color:#a5b4fc'
: ($step === $n
? 'background:rgba(99,102,241,.15);border:1.5px solid #6366f1;color:#c7d2fe'
: 'background:transparent;border:1.5px solid var(--mw-b2);color:var(--mw-t5)') }}">
@if($step > $n)
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2 6.5l2.5 2.5 5.5-5.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
@else
{{ $n }}
@endif
</div>
<span style="font-size:10.5px;color:{{ $step === $n ? 'var(--mw-t3)' : 'var(--mw-t5)' }}">{{ $label }}</span>
</div>
@if($n < 4)
<div style="flex:1;height:1px;background:{{ $step > $n ? 'rgba(99,102,241,.4)' : 'var(--mw-b2)' }};margin-bottom:18px;max-width:40px"></div>
@endif
@endforeach
</div>
@endif
{{-- ═══ Karte ═══ --}}
<div style="background:var(--mw-bg2);border:1px solid var(--mw-b2);border-radius:14px;padding:28px 24px">
{{-- ── Schritt 1: System ── --}}
@if($step === 1)
<div style="margin-bottom:20px">
<div style="font-size:15px;font-weight:600;color:var(--mw-t1)">System-Einstellungen</div>
<div style="font-size:12px;color:var(--mw-t4);margin-top:3px">Grundkonfiguration für deine Mailwolt-Instanz</div>
</div>
<div style="display:flex;flex-direction:column;gap:16px">
<div>
<label class="mw-modal-label">Instanzname</label>
<input type="text" wire:model="instance_name" class="mw-modal-input" placeholder="Mailwolt">
@error('instance_name') <div class="mw-modal-error">{{ $message }}</div> @enderror
</div>
<div>
<label class="mw-modal-label">Sprache</label>
<select wire:model="locale" class="mw-modal-input">
<option value="de">Deutsch</option>
<option value="en">English</option>
<option value="fr">Français</option>
</select>
</div>
<div>
<label class="mw-modal-label">Zeitzone</label>
<select wire:model="timezone" class="mw-modal-input">
@foreach($timezones as $tz)
<option value="{{ $tz }}" @selected($timezone === $tz)>{{ $tz }}</option>
@endforeach
</select>
</div>
</div>
{{-- ── Schritt 2: Domains ── --}}
@elseif($step === 2)
<div style="margin-bottom:20px">
<div style="font-size:15px;font-weight:600;color:var(--mw-t1)">Domains</div>
<div style="font-size:12px;color:var(--mw-t4);margin-top:3px">Domains müssen bereits auf diesen Server zeigen</div>
</div>
<div style="display:flex;align-items:flex-start;gap:8px;padding:10px 12px;border-radius:8px;background:rgba(251,191,36,.06);border:1px solid rgba(251,191,36,.2);margin-bottom:16px">
<svg width="13" height="13" viewBox="0 0 14 14" fill="none" style="flex-shrink:0;margin-top:1px;color:#f59e0b"><path d="M7 1.5L12.5 11H1.5L7 1.5Z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/><path d="M7 5.5v3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><circle cx="7" cy="10" r=".7" fill="currentColor"/></svg>
<span style="font-size:11.5px;color:rgba(251,191,36,.85);line-height:1.5">DNS-Einträge zuerst setzen, dann hier eintragen. Kein <code style="font-size:10.5px">http://</code> am Anfang.</span>
</div>
<div style="display:flex;flex-direction:column;gap:16px">
<div>
<label class="mw-modal-label">UI Domain <span style="color:var(--mw-t4);font-weight:400">(Control Panel)</span></label>
<input type="text" wire:model="ui_domain" class="mw-modal-input" placeholder="mail.example.com">
@error('ui_domain') <div class="mw-modal-error">{{ $message }}</div> @enderror
</div>
<div>
<label class="mw-modal-label">Mailserver Domain <span style="color:var(--mw-t4);font-weight:400">(MX / SMTP / IMAP)</span></label>
<input type="text" wire:model="mail_domain" class="mw-modal-input" placeholder="mx.example.com">
@error('mail_domain') <div class="mw-modal-error">{{ $message }}</div> @enderror
</div>
<div>
<label class="mw-modal-label">Webmail Domain</label>
<input type="text" wire:model="webmail_domain" class="mw-modal-input" placeholder="webmail.example.com">
@error('webmail_domain') <div class="mw-modal-error">{{ $message }}</div> @enderror
</div>
</div>
{{-- ── Schritt 3: Admin ── --}}
@elseif($step === 3)
<div style="margin-bottom:20px">
<div style="font-size:15px;font-weight:600;color:var(--mw-t1)">Admin-Account</div>
<div style="font-size:12px;color:var(--mw-t4);margin-top:3px">Dieser Account hat vollen Zugriff auf das Control Panel</div>
</div>
<div style="display:flex;flex-direction:column;gap:16px">
<div>
<label class="mw-modal-label">Name</label>
<input type="text" wire:model="admin_name" class="mw-modal-input" placeholder="Max Mustermann">
@error('admin_name') <div class="mw-modal-error">{{ $message }}</div> @enderror
</div>
<div>
<label class="mw-modal-label">E-Mail</label>
<input type="email" wire:model="admin_email" class="mw-modal-input" placeholder="admin@example.com">
@error('admin_email') <div class="mw-modal-error">{{ $message }}</div> @enderror
</div>
<div>
<label class="mw-modal-label">Passwort <span style="color:var(--mw-t4);font-weight:400">(min. 10 Zeichen)</span></label>
<input type="password" wire:model="admin_password" class="mw-modal-input">
@error('admin_password') <div class="mw-modal-error">{{ $message }}</div> @enderror
</div>
<div>
<label class="mw-modal-label">Passwort bestätigen</label>
<input type="password" wire:model="admin_password_confirmation" class="mw-modal-input">
</div>
</div>
{{-- ── Schritt 4: Zusammenfassung ── --}}
@elseif($step === 4)
<div style="margin-bottom:20px">
<div style="font-size:15px;font-weight:600;color:var(--mw-t1)">Alles bereit</div>
<div style="font-size:12px;color:var(--mw-t4);margin-top:3px">Überprüfe die Einstellungen und schließe die Einrichtung ab</div>
</div>
<div style="display:flex;flex-direction:column;gap:10px;margin-bottom:4px">
@foreach([
['label' => 'Instanz', 'value' => $instance_name],
['label' => 'Zeitzone', 'value' => $timezone],
['label' => 'UI Domain', 'value' => $ui_domain],
['label' => 'Mail Domain', 'value' => $mail_domain],
['label' => 'Webmail Domain', 'value' => $webmail_domain],
['label' => 'Admin E-Mail', 'value' => $admin_email],
] as $row)
<div style="display:flex;justify-content:space-between;align-items:center;padding:8px 12px;background:var(--mw-bg3);border-radius:7px;border:1px solid var(--mw-b2)">
<span style="font-size:12px;color:var(--mw-t4)">{{ $row['label'] }}</span>
<span style="font-size:12px;color:var(--mw-t2);font-family:{{ in_array($row['label'], ['UI Domain','Mail Domain','Webmail Domain']) ? 'monospace' : 'inherit' }}">{{ $row['value'] ?: '—' }}</span>
</div>
@endforeach
</div>
<div style="margin-top:16px;padding:12px 14px;background:var(--mw-bg3);border-radius:8px;border:1px solid var(--mw-b2)">
<label class="mw-modal-check">
<input type="checkbox" wire:model="skipSsl">
<span class="mw-modal-check-label">SSL jetzt überspringen später in den Einstellungen einrichten</span>
</label>
@if($skipSsl)
<div style="margin-top:8px;font-size:11.5px;color:#fbbf24;padding-left:23px">Nginx wird ohne SSL konfiguriert. Im Dashboard erscheint ein Hinweis bis SSL eingerichtet ist.</div>
@endif
</div>
{{-- ── Schritt 5: Domain-Setup ── --}}
@elseif($step === 5)
@if(!$setupDone)
<div wire:poll.2s="pollSetup"></div>
@endif
@php
$anyFailed = collect($domainStatus)->contains(fn($s) => in_array($s, ['error','nodns','noipv6']));
$allDone = $setupDone;
@endphp
<div style="margin-bottom:20px">
<div style="font-size:15px;font-weight:600;color:var(--mw-t1)">Domains werden eingerichtet</div>
<div style="font-size:12px;color:var(--mw-t4);margin-top:3px">SSL-Zertifikate werden beantragt und Nginx wird konfiguriert</div>
</div>
@php
$labels = ['ui' => $ui_domain, 'mail' => $mail_domain, 'webmail' => $webmail_domain];
$statusConfig = [
'pending' => [
'icon' => '…', 'color' => 'var(--mw-t5)', 'bg' => 'var(--mw-bg3)',
'label' => 'Warte …', 'hint' => null,
],
'running' => [
'icon' => '↻', 'color' => '#7dd3fc', 'bg' => 'rgba(14,165,233,.08)',
'label' => 'Wird registriert …', 'spin' => true, 'hint' => null,
],
'done' => [
'icon' => '✓', 'color' => 'rgba(34,197,94,.9)', 'bg' => 'rgba(34,197,94,.07)',
'label' => 'SSL-Zertifikat ausgestellt', 'hint' => null,
],
'nodns' => [
'icon' => '!', 'color' => '#fbbf24', 'bg' => 'rgba(251,191,36,.07)',
'label' => 'Kein DNS-Eintrag gefunden',
'hint' => 'Der A-Record dieser Domain zeigt nicht auf diesen Server oder ist noch nicht propagiert. DNS-Einstellungen prüfen und danach Retry klicken.',
'hints_extra' => ['DNS A-Record auf Server-IP setzen', 'DNS-Propagierung abwarten (bis zu 24h)', 'Mit dig +short A domain.com prüfen'],
],
'noipv6' => [
'icon' => '!', 'color' => '#fbbf24', 'bg' => 'rgba(251,191,36,.07)',
'label' => 'IPv6 nicht konfiguriert',
'hint' => 'Die Domain hat einen AAAA-Record, aber dieser Server hat kein aktives IPv6. Let\'s Encrypt prüft alle DNS-Records.',
'hints_extra' => ['IPv6 am Server aktivieren', 'ODER: AAAA-Record aus dem DNS entfernen'],
],
'error' => [
'icon' => '✗', 'color' => '#f87171', 'bg' => 'rgba(239,68,68,.07)',
'label' => 'SSL-Zertifikat fehlgeschlagen',
'hint' => 'Let\'s Encrypt konnte die Domain nicht verifizieren. Self-signed Zertifikat wird verwendet.',
'hints_extra' => ['Port 80 muss von außen erreichbar sein', 'Firewall-Regeln prüfen (ufw allow 80)', 'AAAA-Record ohne IPv6 am Server entfernen', 'http://domain/.well-known/acme-challenge/ im Browser testen'],
],
'skip' => [
'icon' => '', 'color' => 'var(--mw-t4)', 'bg' => 'var(--mw-bg3)',
'label' => 'SSL übersprungen', 'hint' => null,
],
];
@endphp
<div style="display:flex;flex-direction:column;gap:8px">
@foreach(['ui' => 'UI Domain', 'mail' => 'Mail Domain', 'webmail' => 'Webmail Domain'] as $key => $typeLabel)
@php
$st = $domainStatus[$key] ?? 'pending';
$cfg = $statusConfig[$st] ?? $statusConfig['pending'];
@endphp
<div style="border-radius:8px;border:1px solid var(--mw-b2);overflow:hidden">
<div style="display:flex;align-items:center;gap:12px;padding:12px 14px;background:{{ $cfg['bg'] }}">
<div style="width:28px;height:28px;border-radius:50%;background:{{ $cfg['bg'] }};border:1px solid {{ $cfg['color'] }};display:flex;align-items:center;justify-content:center;font-size:13px;color:{{ $cfg['color'] }};flex-shrink:0;{{ isset($cfg['spin']) ? 'animation:spin .9s linear infinite' : '' }}">
{{ $cfg['icon'] }}
</div>
<div style="flex:1;min-width:0">
<div style="font-size:11px;color:var(--mw-t4)">{{ $typeLabel }}</div>
<div style="font-size:12.5px;color:var(--mw-t1);font-family:monospace;margin-top:1px">{{ $labels[$key] }}</div>
</div>
<span style="font-size:11.5px;color:{{ $cfg['color'] }};white-space:nowrap">{{ $cfg['label'] }}</span>
</div>
@if(!empty($cfg['hint']))
<div style="padding:10px 14px;background:rgba(0,0,0,.15);border-top:1px solid var(--mw-b2)">
<div style="font-size:11.5px;color:var(--mw-t3);line-height:1.5;margin-bottom:{{ !empty($cfg['hints_extra']) ? '8px' : '0' }}">
{{ $cfg['hint'] }}
</div>
@if(!empty($cfg['hints_extra']))
<ul style="margin:0;padding-left:14px;display:flex;flex-direction:column;gap:3px">
@foreach($cfg['hints_extra'] as $hint)
<li style="font-size:11px;color:var(--mw-t4);line-height:1.4">{{ $hint }}</li>
@endforeach
</ul>
@endif
</div>
@endif
</div>
@endforeach
</div>
@if($allDone && $anyFailed)
<div style="margin-top:16px;padding:12px 14px;background:rgba(239,68,68,.05);border:1px solid rgba(239,68,68,.2);border-radius:8px">
<div style="font-size:12px;color:var(--mw-t3);margin-bottom:10px">
Einige Domains konnten nicht vollständig eingerichtet werden. Du kannst es erneut versuchen oder mit Self-signed Zertifikat fortfahren.
</div>
<button wire:click="retryDomains" wire:loading.attr="disabled" style="display:inline-flex;align-items:center;gap:6px;padding:7px 14px;border-radius:7px;font-size:12px;font-weight:500;cursor:pointer;border:1px solid rgba(99,102,241,.4);background:rgba(99,102,241,.12);color:#a5b4fc">
<svg wire:loading.remove wire:target="retryDomains" width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M10 6A4 4 0 1 1 8.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/><path d="M8.5 1v2h2" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
<svg wire:loading wire:target="retryDomains" width="12" height="12" viewBox="0 0 12 12" fill="none" style="animation:spin .7s linear infinite"><path d="M10 6A4 4 0 1 1 6 2" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
Erneut versuchen
</button>
</div>
@endif
@endif
</div>
{{-- ═══ Navigation ═══ --}}
@if($step < 5)
<div style="display:flex;align-items:center;margin-top:20px;gap:10px">
@if($step > 1)
<button wire:click="back" class="mbx-btn-mute" style="font-size:12.5px">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M7.5 2L3.5 6l4 4" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
Zurück
</button>
@endif
@if($step < 4)
<button wire:click="next" wire:loading.attr="disabled" class="mbx-btn-primary" style="font-size:12.5px;width:fit-content;margin-left:auto">
<svg wire:loading.remove wire:target="next" width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M4.5 2L8.5 6l-4 4" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
<svg wire:loading wire:target="next" width="12" height="12" viewBox="0 0 12 12" fill="none" style="animation:spin .7s linear infinite"><path d="M10 6A4 4 0 1 1 6 2" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
Weiter
</button>
@else
<button wire:click="finish" wire:loading.attr="disabled" class="mbx-btn-primary" style="font-size:12.5px;width:fit-content;margin-left:auto;background:linear-gradient(135deg,rgba(99,102,241,.25),rgba(79,70,229,.2));border-color:rgba(99,102,241,.5)">
<svg wire:loading.remove wire:target="finish" width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2 6.5l2.5 2.5 5.5-5.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
<svg wire:loading wire:target="finish" width="12" height="12" viewBox="0 0 12 12" fill="none" style="animation:spin .7s linear infinite"><path d="M10 6A4 4 0 1 1 6 2" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
Einrichtung abschließen
</button>
@endif
</div>
@elseif($step === 5 && $setupDone)
<div style="display:flex;justify-content:flex-end;margin-top:20px">
<button wire:click="goToLogin" class="mbx-btn-primary" style="font-size:12.5px;width:fit-content">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2 6.5l2.5 2.5 5.5-5.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
{{ collect($domainStatus)->contains(fn($s) => in_array($s, ['error','nodns','noipv6'])) ? 'Trotzdem zum Login' : 'Zum Login' }}
</button>
</div>
@endif
</div>