344 lines
22 KiB
PHP
344 lines
22 KiB
PHP
<x-slot:breadcrumbParent>System</x-slot:breadcrumbParent>
|
||
<x-slot:breadcrumb>Einstellungen</x-slot:breadcrumb>
|
||
|
||
<div>
|
||
|
||
<div class="mbx-page-header">
|
||
<div class="mbx-page-title">
|
||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||
<circle cx="8" cy="8" r="2.2" stroke="currentColor" stroke-width="1.3"/>
|
||
<path d="M8 1.5v1.3M8 13.2v1.3M1.5 8h1.3M13.2 8h1.3M3.2 3.2l.9.9M11.9 11.9l.9.9M3.2 12.8l.9-.9M11.9 4.1l.9-.9" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
|
||
</svg>
|
||
Einstellungen
|
||
</div>
|
||
<div class="mbx-page-actions">
|
||
<button wire:click="save" class="mbx-btn-primary">
|
||
<svg width="12" height="12" viewBox="0 0 14 14" fill="none"><path d="M2 2h8l2 2v8a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1Z" stroke="currentColor" stroke-width="1.2"/><path d="M5 2v3h4V2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>
|
||
<span wire:loading.remove wire:target="save">Speichern</span>
|
||
<span wire:loading wire:target="save">Speichert…</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sec-layout">
|
||
|
||
{{-- ═══ Main ═══ --}}
|
||
<div style="min-width:0">
|
||
<div class="mbx-sections">
|
||
|
||
{{-- Allgemein --}}
|
||
<div class="mbx-section">
|
||
<div class="mbx-domain-head">
|
||
<div class="mbx-domain-info">
|
||
<span class="mbx-badge-mute">Allgemein</span>
|
||
</div>
|
||
</div>
|
||
<div style="padding:16px 18px">
|
||
<div class="mw-modal-grid2">
|
||
<div>
|
||
<label class="mw-modal-label">Instanzname</label>
|
||
<input type="text" value="{{ $instance_name }}" class="mw-modal-input" readonly
|
||
style="opacity:.55;cursor:default">
|
||
<div class="mw-modal-hint">Wird aus config/app.php gelesen (APP_NAME)</div>
|
||
</div>
|
||
<div>
|
||
<label class="mw-modal-label">Sprache</label>
|
||
<select wire:model.defer="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.defer="timezone" class="mw-modal-input">
|
||
@foreach($timezones as $tz)
|
||
<option value="{{ $tz }}" @selected($timezone === $tz)>{{ $tz }}</option>
|
||
@endforeach
|
||
</select>
|
||
<div class="mw-modal-hint">Aktuelle Serverzeit: {{ now()->format('H:i:s') }}</div>
|
||
</div>
|
||
<div>
|
||
<label class="mw-modal-label">Session-Timeout (Min.)</label>
|
||
<input type="number" wire:model.defer="session_timeout" class="mw-modal-input" min="5" max="1440">
|
||
<div class="mw-modal-hint">5–1440 Minuten (24 h max.)</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- 2FA --}}
|
||
<livewire:ui.system.two-fa-status />
|
||
|
||
{{-- Backup --}}
|
||
<div class="mbx-section" id="backup">
|
||
<div class="mbx-domain-head">
|
||
<div class="mbx-domain-info">
|
||
<span class="mbx-badge-mute">Backup-Zeitplan</span>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:8px">
|
||
@if($backup_enabled)
|
||
<span class="mbx-badge-ok">Aktiv</span>
|
||
@else
|
||
<span class="mbx-badge-warn">Deaktiviert</span>
|
||
@endif
|
||
<a href="{{ route('ui.system.backups') }}" class="mbx-btn-mute" style="font-size:11.5px;padding:4px 12px;text-decoration:none">Verlauf</a>
|
||
</div>
|
||
</div>
|
||
<div style="padding:16px 18px;display:flex;flex-direction:column;gap:14px">
|
||
|
||
{{-- Enable toggle --}}
|
||
<label class="sec-check">
|
||
<input type="checkbox" wire:model.live="backup_enabled">
|
||
<span style="font-size:12.5px;color:var(--mw-t2)">Automatisches Backup aktivieren</span>
|
||
</label>
|
||
|
||
{{-- Was sichern --}}
|
||
<div>
|
||
<label class="mw-modal-label" style="margin-bottom:8px;display:block">Was sichern</label>
|
||
<div class="bk-src-grid">
|
||
<label class="bk-src-card">
|
||
<input type="checkbox" wire:model.live="backup_include_db">
|
||
<span class="bk-src-icon">
|
||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><ellipse cx="7" cy="3.5" rx="5" ry="2" stroke="currentColor" stroke-width="1.2"/><path d="M2 3.5v7c0 1.1 2.24 2 5 2s5-.9 5-2v-7" stroke="currentColor" stroke-width="1.2"/><path d="M2 7c0 1.1 2.24 2 5 2s5-.9 5-2" stroke="currentColor" stroke-width="1.2"/></svg>
|
||
</span>
|
||
<span class="bk-src-name">Datenbank</span>
|
||
<span class="bk-src-hint">MySQL-Dump</span>
|
||
</label>
|
||
<label class="bk-src-card">
|
||
<input type="checkbox" wire:model.live="backup_include_maildirs">
|
||
<span class="bk-src-icon">
|
||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x=".5" y="2.5" width="13" height="9" rx="1.5" stroke="currentColor" stroke-width="1.2"/><path d="M.5 5l6.5 4.5L13.5 5" stroke="currentColor" stroke-width="1.2"/></svg>
|
||
</span>
|
||
<span class="bk-src-name">E-Mails</span>
|
||
<span class="bk-src-hint">Maildirs</span>
|
||
</label>
|
||
<label class="bk-src-card">
|
||
<input type="checkbox" wire:model.live="backup_include_configs">
|
||
<span class="bk-src-icon">
|
||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 1.5L12.5 4v4.8c0 2.8-2.3 4.8-5.5 5.2C3.8 13.6 1.5 11.6 1.5 8.8V4L7 1.5Z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
||
</span>
|
||
<span class="bk-src-name">Konfiguration</span>
|
||
<span class="bk-src-hint">postfix, dovecot, SSL</span>
|
||
</label>
|
||
</div>
|
||
@if($backup_include_maildirs)
|
||
<div style="margin-top:8px">
|
||
<input type="text" wire:model.defer="backup_mail_dir"
|
||
class="mw-modal-input" placeholder="/var/mail/vhosts">
|
||
<div class="mw-modal-hint">Maildir-Pfad – leer lassen für automatische Erkennung</div>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
@if($backup_enabled)
|
||
|
||
{{-- Preset buttons --}}
|
||
<div>
|
||
<label class="mw-modal-label">Häufigkeit</label>
|
||
<div style="display:flex;gap:6px;flex-wrap:wrap">
|
||
@foreach(['hourly' => 'Stündlich', 'daily' => 'Täglich', 'weekly' => 'Wöchentlich', 'monthly' => 'Monatlich', 'custom' => 'Benutzerdefiniert'] as $val => $label)
|
||
<button type="button" wire:click="$set('backup_preset','{{ $val }}')"
|
||
style="padding:5px 14px;border-radius:6px;font-size:12px;cursor:pointer;transition:all .1s;
|
||
{{ $backup_preset === $val
|
||
? 'background:var(--mw-vbg);border:1px solid var(--mw-vbd);color:var(--mw-v2);font-weight:600'
|
||
: 'background:transparent;border:1px solid var(--mw-b2);color:var(--mw-t4)' }}">
|
||
{{ $label }}
|
||
</button>
|
||
@endforeach
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Time / day pickers --}}
|
||
@if($backup_preset !== 'hourly' && $backup_preset !== 'custom')
|
||
<div class="mw-modal-grid2">
|
||
<div>
|
||
<label class="mw-modal-label">Uhrzeit</label>
|
||
<input type="time" wire:model.live="backup_time" class="mw-modal-input">
|
||
</div>
|
||
|
||
@if($backup_preset === 'weekly')
|
||
<div>
|
||
<label class="mw-modal-label">Wochentag</label>
|
||
<select wire:model.live="backup_weekday" class="mw-modal-input">
|
||
@foreach(['0'=>'Sonntag','1'=>'Montag','2'=>'Dienstag','3'=>'Mittwoch','4'=>'Donnerstag','5'=>'Freitag','6'=>'Samstag'] as $v => $d)
|
||
<option value="{{ $v }}">{{ $d }}</option>
|
||
@endforeach
|
||
</select>
|
||
</div>
|
||
@elseif($backup_preset === 'monthly')
|
||
<div>
|
||
<label class="mw-modal-label">Tag des Monats</label>
|
||
<select wire:model.live="backup_monthday" class="mw-modal-input">
|
||
@for($d = 1; $d <= 28; $d++)
|
||
<option value="{{ $d }}">{{ $d }}.</option>
|
||
@endfor
|
||
</select>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
@endif
|
||
|
||
@if($backup_preset === 'custom')
|
||
<div>
|
||
<label class="mw-modal-label">Cron-Ausdruck</label>
|
||
<input type="text" wire:model.live="backup_cron" class="mw-modal-input"
|
||
placeholder="0 3 * * *" style="font-family:monospace">
|
||
<div class="mw-modal-hint">Standard Cron-Syntax: Minute Stunde Tag Monat Wochentag</div>
|
||
</div>
|
||
@endif
|
||
|
||
{{-- Cron-Vorschau --}}
|
||
<div style="display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--mw-bg4);border:1px solid var(--mw-b2);border-radius:7px">
|
||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><circle cx="6" cy="6" r="5" stroke="var(--mw-t4)" stroke-width="1.2"/><path d="M6 3.5v3l1.5 1.5" stroke="var(--mw-t4)" stroke-width="1.2" stroke-linecap="round"/></svg>
|
||
<span style="font-size:11px;color:var(--mw-t4)">Cron:</span>
|
||
<code style="font-family:monospace;font-size:11px;color:var(--mw-v2)">{{ $backup_cron }}</code>
|
||
</div>
|
||
|
||
{{-- Aufbewahrung --}}
|
||
<div>
|
||
<label class="mw-modal-label">Aufbewahrung (Anzahl Backups)</label>
|
||
<input type="number" wire:model.defer="backup_retention" class="mw-modal-input"
|
||
min="1" max="365" style="max-width:120px">
|
||
<div class="mw-modal-hint">Ältere Backups werden automatisch gelöscht. Empfehlung: 7–14.</div>
|
||
</div>
|
||
|
||
@endif
|
||
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Sicherheit --}}
|
||
<div class="mbx-section">
|
||
<div class="mbx-domain-head">
|
||
<div class="mbx-domain-info">
|
||
<span class="mbx-badge-mute">Sicherheit</span>
|
||
</div>
|
||
</div>
|
||
<div style="padding:16px 18px;display:flex;flex-direction:column;gap:14px">
|
||
<div class="mw-modal-grid2">
|
||
<div>
|
||
<label class="mw-modal-label">Login Rate Limit</label>
|
||
<input type="number" wire:model.defer="rate_limit" class="mw-modal-input" min="1" max="100">
|
||
<div class="mw-modal-hint">Max. Versuche pro Minute</div>
|
||
</div>
|
||
<div>
|
||
<label class="mw-modal-label">Passwort Mindestlänge</label>
|
||
<input type="number" wire:model.defer="password_min" class="mw-modal-input" min="6" max="128">
|
||
<div class="mw-modal-hint">Zeichen (min. 6)</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
{{-- ═══ Sidebar ═══ --}}
|
||
<div>
|
||
<div class="mbx-sections">
|
||
|
||
{{-- Domains --}}
|
||
<div class="mbx-section">
|
||
<div class="mbx-domain-head">
|
||
<div class="mbx-domain-info">
|
||
<span class="mbx-badge-mute">Domains</span>
|
||
</div>
|
||
</div>
|
||
<div style="padding:14px 16px;display:flex;flex-direction:column;gap:12px">
|
||
|
||
<div style="display:flex;align-items:flex-start;gap:8px;padding:10px 12px;border-radius:8px;background:rgba(251,191,36,.07);border:1px solid rgba(251,191,36,.25)">
|
||
<svg width="14" height="14" 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,.9);line-height:1.5">
|
||
Änderungen hier wirken sich auf das gesamte Routing aus — UI, Webmail und Mailserver sind betroffen. Nur ändern wenn DNS bereits korrekt konfiguriert ist.
|
||
</span>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="mw-modal-label">UI Domain</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</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>
|
||
<button wire:click="saveDomains"
|
||
wire:loading.attr="disabled"
|
||
wire:target="saveDomains"
|
||
class="mbx-btn-primary" style="width:100%;justify-content:center;font-size:12px">
|
||
<span wire:loading.remove wire:target="saveDomains" style="display:inline-flex;align-items:center;gap:5px">
|
||
<svg width="11" height="11" viewBox="0 0 14 14" fill="none"><path d="M2 2h8l2 2v8a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1Z" stroke="currentColor" stroke-width="1.2"/><path d="M5 2v3h4V2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>Domains speichern
|
||
</span>
|
||
<span wire:loading wire:target="saveDomains">Speichert…</span>
|
||
</button>
|
||
@if(app()->isProduction())
|
||
<div style="display:flex;align-items:center;gap:6px;padding:8px 10px;background:rgba(34,197,94,.06);border:1px solid rgba(34,197,94,.2);border-radius:7px">
|
||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" style="color:#22c55e;flex-shrink:0"><path d="M2 6.5l2.5 2.5 5.5-5.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||
<span style="font-size:11.5px;color:rgba(34,197,94,.85)">Let's Encrypt SSL wird automatisch eingerichtet</span>
|
||
</div>
|
||
@else
|
||
<div style="display:flex;align-items:flex-start;gap:8px;padding:8px 10px;background:rgba(99,102,241,.06);border:1px solid rgba(99,102,241,.2);border-radius:7px">
|
||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" style="color:#818cf8;flex-shrink:0;margin-top:1px"><circle cx="6" cy="6" r="5" stroke="currentColor" stroke-width="1.2"/><path d="M6 5.5v3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><circle cx="6" cy="4" r=".6" fill="currentColor"/></svg>
|
||
<span style="font-size:11.5px;color:rgba(129,140,248,.9);line-height:1.5">Local-Modus — SSL wird beim Speichern übersprungen (NPM übernimmt)</span>
|
||
</div>
|
||
<button wire:click="openSslModal"
|
||
class="mbx-btn-mute" style="width:100%;justify-content:center;font-size:11.5px">
|
||
<svg width="11" height="11" viewBox="0 0 14 14" fill="none"><rect x="1.5" y="6" width="11" height="7.5" rx="1.5" stroke="currentColor" stroke-width="1.2"/><path d="M4.5 6V4.5a2.5 2.5 0 0 1 5 0V6" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>
|
||
SSL manuell erzwingen (certbot)
|
||
</button>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Info --}}
|
||
<div class="mbx-section">
|
||
<div class="mbx-domain-head">
|
||
<div class="mbx-domain-info">
|
||
<span class="mbx-badge-mute">System-Info</span>
|
||
</div>
|
||
</div>
|
||
<div style="padding:14px 16px;font-size:11.5px;color:var(--mw-t3);display:flex;flex-direction:column;gap:8px">
|
||
<div style="display:flex;justify-content:space-between">
|
||
<span style="color:var(--mw-t4)">PHP</span>
|
||
<span>{{ PHP_VERSION }}</span>
|
||
</div>
|
||
<div style="display:flex;justify-content:space-between">
|
||
<span style="color:var(--mw-t4)">Laravel</span>
|
||
<span>{{ app()->version() }}</span>
|
||
</div>
|
||
<div style="display:flex;justify-content:space-between">
|
||
<span style="color:var(--mw-t4)">Umgebung</span>
|
||
<span>{{ app()->environment() }}</span>
|
||
</div>
|
||
<div style="display:flex;justify-content:space-between">
|
||
<span style="color:var(--mw-t4)">Hostname</span>
|
||
<span style="font-family:monospace;font-size:10.5px">{{ gethostname() }}</span>
|
||
</div>
|
||
<div style="display:flex;justify-content:space-between">
|
||
<span style="color:var(--mw-t4)">Certbot</span>
|
||
<span>{{ trim(@shell_exec('certbot --version 2>&1') ?? '—') ?: '—' }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|