238 lines
15 KiB
PHP
238 lines
15 KiB
PHP
<x-slot:breadcrumbParent>System</x-slot:breadcrumbParent>
|
|
<x-slot:breadcrumb>Installer</x-slot:breadcrumb>
|
|
|
|
<div wire:init="checkComponentStatus"
|
|
x-on:installer:run.window="$wire.runInstaller($event.detail.component)">
|
|
|
|
{{-- ═══ Page Header ═══ --}}
|
|
<div class="mbx-page-header">
|
|
<div class="mbx-page-title">
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
<rect x="2" y="2" width="12" height="12" rx="2" stroke="currentColor" stroke-width="1.3"/>
|
|
<path d="M5 8h6M8 5v6" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
|
|
</svg>
|
|
Installer
|
|
</div>
|
|
<div class="mbx-page-actions">
|
|
<button wire:click="openConfirmModal('all')"
|
|
@if($state === 'running') disabled @endif
|
|
class="mbx-btn-primary">
|
|
<svg width="12" height="12" viewBox="0 0 14 14" fill="none"><rect x="1.5" y="1.5" width="11" height="11" rx="2" stroke="currentColor" stroke-width="1.2"/><path d="M5 7h4M7 5v4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>
|
|
Komplett-Installation
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Polling when running --}}
|
|
@if($state === 'running' || $running)
|
|
<div wire:poll.2s="pollStatus"></div>
|
|
@endif
|
|
|
|
<div class="mbx-sections">
|
|
|
|
{{-- ═══ Warning Banner ═══ --}}
|
|
<div style="display:flex;align-items:flex-start;gap:10px;padding:12px 16px;background:rgba(251,191,36,.07);border:1px solid rgba(251,191,36,.22);border-radius:8px">
|
|
<svg width="15" height="15" 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:12px;color:rgba(251,191,36,.9);line-height:1.6">
|
|
<strong>Achtung:</strong> Diese Seite führt Systemänderungen durch.
|
|
Installationen und Neukonfigurationen werden mit Root-Rechten ausgeführt und können laufende Dienste kurzzeitig unterbrechen.
|
|
</span>
|
|
</div>
|
|
|
|
{{-- ═══ Section 1: Komponentenstatus ═══ --}}
|
|
<div class="mbx-section">
|
|
<div class="mbx-domain-head">
|
|
<div class="mbx-domain-info">
|
|
<span class="mbx-badge-mute">Komponentenstatus</span>
|
|
</div>
|
|
<button wire:click="checkComponentStatus"
|
|
wire:loading.attr="disabled"
|
|
wire:target="checkComponentStatus"
|
|
class="mbx-btn-mute"
|
|
style="font-size:11px;padding:3px 10px">
|
|
<svg width="11" height="11" viewBox="0 0 14 14" fill="none"><path d="M7 1.5A5.5 5.5 0 1 1 1.5 7" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><path d="M1.5 2v3.5H5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
Aktualisieren
|
|
</button>
|
|
</div>
|
|
<div style="padding:16px 18px">
|
|
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:10px">
|
|
|
|
@php
|
|
$componentIcons = [
|
|
'nginx' => '<path d="M7 2L2 5v4l5 3 5-3V5L7 2Z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/><path d="M7 2v11M2 5l10 6M12 5L2 11" stroke="currentColor" stroke-width="1.2"/>',
|
|
'postfix' => '<rect x=".5" y="2.5" width="13" height="9" rx="1.5" stroke="currentColor" stroke-width="1.2"/><path d=".5 5l6.5 4.5L13.5 5" stroke="currentColor" stroke-width="1.2"/>',
|
|
'dovecot' => '<path d="M7 1.5C7 1.5 2 4 2 8.5a5 5 0 0 0 10 0C12 4 7 1.5 7 1.5Z" stroke="currentColor" stroke-width="1.2"/>',
|
|
'rspamd' => '<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"/>',
|
|
'fail2ban' => '<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"/>',
|
|
'certbot' => '<circle cx="7" cy="7" r="5.5" stroke="currentColor" stroke-width="1.2"/><path d="M4.5 7l2 2 3.5-3.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
];
|
|
@endphp
|
|
|
|
@foreach($componentStatus as $key => $info)
|
|
<div style="display:flex;align-items:center;justify-content:space-between;padding:12px 14px;background:var(--mw-bg3);border:1px solid var(--mw-b2);border-radius:8px;gap:10px">
|
|
<div style="display:flex;align-items:center;gap:10px;min-width:0">
|
|
<div style="width:32px;height:32px;border-radius:7px;background:var(--mw-bg4);border:1px solid var(--mw-b2);display:flex;align-items:center;justify-content:center;flex-shrink:0">
|
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" style="color:var(--mw-t3)">
|
|
{!! $componentIcons[$key] ?? '<circle cx="7" cy="7" r="5" stroke="currentColor" stroke-width="1.2"/>' !!}
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<div style="font-size:12.5px;font-weight:600;color:var(--mw-t1)">{{ $info['label'] }}</div>
|
|
<div style="margin-top:3px">
|
|
@if($info['installed'] && $info['active'])
|
|
<span class="mbx-badge-ok" style="font-size:10px;padding:2px 7px">Aktiv</span>
|
|
@elseif($info['installed'])
|
|
<span class="mbx-badge-warn" style="font-size:10px;padding:2px 7px">Inaktiv</span>
|
|
@else
|
|
<span style="font-size:10px;padding:2px 7px;border-radius:4px;background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.25);color:#f87171">Nicht installiert</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button wire:click="openConfirmModal('{{ $key }}')"
|
|
@if($state === 'running') disabled @endif
|
|
class="mbx-btn-mute"
|
|
style="font-size:11px;padding:4px 10px;white-space:nowrap;flex-shrink:0">
|
|
{{ $info['installed'] ? 'Neu konfigurieren' : 'Installieren' }}
|
|
</button>
|
|
</div>
|
|
@endforeach
|
|
|
|
@if(empty($componentStatus))
|
|
<div style="grid-column:1/-1;font-size:12px;color:var(--mw-t4);padding:8px 0">
|
|
Status wird geladen …
|
|
</div>
|
|
@endif
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ═══ Section 2: Status ═══ --}}
|
|
<div class="mbx-section">
|
|
<div class="mbx-domain-head">
|
|
<div class="mbx-domain-info">
|
|
<span class="mbx-badge-mute">Installations-Status</span>
|
|
</div>
|
|
</div>
|
|
<div style="padding:16px 18px;display:flex;flex-direction:column;gap:14px">
|
|
|
|
@if($state === 'running')
|
|
<div style="display:flex;align-items:center;gap:12px">
|
|
<div style="width:36px;height:36px;border-radius:50%;background:rgba(14,165,233,.1);border:1px solid rgba(14,165,233,.3);display:flex;align-items:center;justify-content:center;flex-shrink:0;animation:spin 1.5s linear infinite">
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="2" y="2" width="12" height="12" rx="2" stroke="#7dd3fc" stroke-width="1.4"/><path d="M5 8h6M8 5v6" stroke="#7dd3fc" stroke-width="1.4" stroke-linecap="round"/></svg>
|
|
</div>
|
|
<div>
|
|
<div style="font-size:13px;font-weight:600;color:var(--mw-t1)">
|
|
Installation läuft …
|
|
@if($component !== 'all')
|
|
<span style="color:var(--mw-t4)">({{ ucfirst($component) }})</span>
|
|
@endif
|
|
</div>
|
|
<div style="font-size:11.5px;color:var(--mw-t4);margin-top:2px">Bitte nicht unterbrechen. Die Seite aktualisiert sich automatisch.</div>
|
|
</div>
|
|
</div>
|
|
@elseif($rc !== null && $rc !== 0)
|
|
<div style="display:flex;align-items:flex-start;gap:10px;padding:12px 14px;background:rgba(239,68,68,.07);border:1px solid rgba(239,68,68,.25);border-radius:8px">
|
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" style="flex-shrink:0;margin-top:1px;color:#f87171"><path d="M7 1L13 12H1L7 1Z" stroke="currentColor" stroke-width="1.3" 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>
|
|
<div>
|
|
<div style="font-size:12.5px;font-weight:600;color:#f87171">Installation fehlgeschlagen (rc={{ $rc }})</div>
|
|
<div style="font-size:11.5px;color:var(--mw-t4);margin-top:3px">Bitte das Log unten prüfen.</div>
|
|
</div>
|
|
</div>
|
|
@elseif($rc === 0)
|
|
<div style="display:flex;align-items:center;gap:10px;padding:12px 14px;background:rgba(34,197,94,.06);border:1px solid rgba(34,197,94,.2);border-radius:8px">
|
|
<svg width="14" height="14" 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:12.5px;color:rgba(34,197,94,.9)">Installation erfolgreich abgeschlossen.</span>
|
|
</div>
|
|
@else
|
|
<div style="font-size:12.5px;color:var(--mw-t4)">Keine Installation aktiv. Wähle eine Komponente oder starte die Komplett-Installation.</div>
|
|
@endif
|
|
|
|
{{-- Progress Bar --}}
|
|
@if($state === 'running' || $progressPct > 0)
|
|
<div>
|
|
<div style="display:flex;justify-content:space-between;margin-bottom:5px">
|
|
<span style="font-size:11px;color:var(--mw-t4)">Fortschritt</span>
|
|
<span style="font-size:11px;color:var(--mw-t4);font-family:monospace">{{ $progressPct }}%</span>
|
|
</div>
|
|
<div style="height:6px;background:var(--mw-bg4);border-radius:4px;overflow:hidden;border:1px solid var(--mw-b2)">
|
|
<div style="height:100%;width:{{ $progressPct }}%;transition:width .6s ease;background:{{ $rc !== null && $rc !== 0 ? 'rgba(239,68,68,.7)' : ($progressPct >= 100 ? 'rgba(34,197,94,.8)' : 'rgba(14,165,233,.8)') }};border-radius:4px"></div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ═══ Section 3: Log Viewer ═══ --}}
|
|
<div class="mbx-section"
|
|
x-data="{
|
|
autoScroll: true,
|
|
init() {
|
|
this.$watch('$wire.logLines', () => {
|
|
if (this.autoScroll && this.$refs.logBox) {
|
|
this.$nextTick(() => {
|
|
this.$refs.logBox.scrollTop = this.$refs.logBox.scrollHeight;
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}">
|
|
<div class="mbx-domain-head">
|
|
<div class="mbx-domain-info">
|
|
<span class="mbx-badge-mute">Installer-Log</span>
|
|
</div>
|
|
<div style="display:flex;align-items:center;gap:8px">
|
|
<label style="display:flex;align-items:center;gap:5px;font-size:11.5px;color:var(--mw-t4);cursor:pointer">
|
|
<input type="checkbox" x-model="autoScroll" style="accent-color:var(--mw-v2)">
|
|
Auto-Scroll
|
|
</label>
|
|
<button wire:click="clearLog"
|
|
wire:loading.attr="disabled"
|
|
wire:target="clearLog"
|
|
class="mbx-btn-mute"
|
|
style="font-size:11px;padding:3px 10px">
|
|
<svg width="11" height="11" viewBox="0 0 14 14" fill="none"><path d="M2 4h10M5 4V2.5h4V4M11.5 4l-.5 8H3L2.5 4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
Log leeren
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div style="padding:12px 14px">
|
|
<div x-ref="logBox"
|
|
style="background:var(--mw-bg3);border:1px solid var(--mw-b2);border-radius:7px;padding:12px 14px;max-height:400px;overflow-y:auto;font-family:monospace;font-size:11.5px;color:var(--mw-t3);line-height:1.6;white-space:pre-wrap;word-break:break-all">
|
|
@if(count($logLines) === 0)
|
|
<span style="color:var(--mw-t5);font-style:italic">Keine Log-Einträge vorhanden.</span>
|
|
@else
|
|
@foreach($logLines as $line)
|
|
@php
|
|
$color = 'inherit';
|
|
if (str_contains($line, '[!]') || str_contains($line, 'error') || str_contains($line, 'Error') || str_contains($line, 'fehlgeschlagen')) {
|
|
$color = '#f87171';
|
|
} elseif (str_contains($line, '[✓]') || str_contains($line, 'beendet') || str_contains($line, 'abgeschlossen')) {
|
|
$color = 'rgba(34,197,94,.85)';
|
|
} elseif (str_contains($line, '[i]')) {
|
|
$color = 'var(--mw-t3)';
|
|
} elseif (str_contains($line, '=====')) {
|
|
$color = 'rgba(14,165,233,.8)';
|
|
}
|
|
@endphp
|
|
<span style="color:{{ $color }}">{{ $line }}</span>
|
|
<br>
|
|
@endforeach
|
|
@endif
|
|
</div>
|
|
<div style="margin-top:6px;font-size:11px;color:var(--mw-t5)">
|
|
{{ count($logLines) }} Zeilen · /var/log/mailwolt-install.log
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|