305 lines
20 KiB
PHP
305 lines
20 KiB
PHP
<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. 6 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)
|
||
<div wire:poll.2s="pollSetup"></div>
|
||
|
||
@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">
|
||
{{-- Kein wire:click — plain Link damit kein Livewire-POST nötig ist.
|
||
nginx leitet /login nach SSL-Switch automatisch auf HTTPS weiter. --}}
|
||
<a href="/login" class="mbx-btn-primary" style="font-size:12.5px;width:fit-content;text-decoration:none;display:inline-flex;align-items:center;gap:6px">
|
||
<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' }}
|
||
</a>
|
||
</div>
|
||
@endif
|
||
|
||
</div>
|