276 lines
16 KiB
PHP
276 lines
16 KiB
PHP
<?php
|
|
|
|
use App\Livewire\Forms\LoginForm;
|
|
use Illuminate\Support\Facades\Session;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Volt\Component;
|
|
|
|
new #[Layout('layouts.login')] class extends Component {
|
|
public LoginForm $form;
|
|
|
|
public function login(): void
|
|
{
|
|
$this->validate();
|
|
$this->form->authenticate();
|
|
Session::regenerate();
|
|
$this->redirectIntended(default: route('dashboard', absolute: false), navigate: true);
|
|
}
|
|
}; ?>
|
|
|
|
<div class="clu-auth-grid">
|
|
|
|
{{-- ============ LEFT: FORM ============ --}}
|
|
<section class="clu-glass flex flex-col" style="padding:30px 44px;min-height:calc(100vh - 28px);">
|
|
|
|
{{-- Top row: brand + status chip --}}
|
|
<div class="flex items-center justify-between gap-4 flex-wrap">
|
|
<div class="flex items-center gap-[10px]">
|
|
<div class="clu-brand-mark" aria-hidden="true"></div>
|
|
<div class="font-semibold text-[15px] tracking-[-0.01em]">
|
|
CluPilot<span class="text-(--color-muted-2) font-medium ml-[5px] text-[11.5px]">v2.4</span>
|
|
</div>
|
|
</div>
|
|
<span class="clu-status-chip"><span class="clu-pulse-dot"></span> Alle Systeme aktiv</span>
|
|
</div>
|
|
|
|
{{-- Form wrap --}}
|
|
<div class="my-auto flex flex-col gap-6 w-full" style="max-width:380px;padding:44px 0;">
|
|
|
|
<header>
|
|
<h1 class="m-0 text-[25px] font-semibold tracking-[-0.02em] leading-[1.2]">Willkommen zurück</h1>
|
|
<p class="mt-[6px] text-[13.5px] text-(--color-muted)">
|
|
Bei CluPilot anmelden oder
|
|
@if (Route::has('register'))
|
|
<a href="{{ route('register') }}" wire:navigate class="font-semibold">neues Konto erstellen</a>
|
|
@endif
|
|
</p>
|
|
</header>
|
|
|
|
{{-- SSO --}}
|
|
<div class="grid grid-cols-2 gap-[10px]">
|
|
<button type="button" class="clu-sso-btn">
|
|
<svg width="15" height="15" viewBox="0 0 24 24" aria-hidden="true">
|
|
<path fill="#4285F4" d="M22.5 12.27c0-.79-.07-1.54-.2-2.27H12v4.51h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.32z"/>
|
|
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.99.66-2.25 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84A11 11 0 0 0 12 23z"/>
|
|
<path fill="#FBBC05" d="M5.84 14.10A6.6 6.6 0 0 1 5.5 12c0-.73.13-1.44.34-2.10V7.06H2.18A11 11 0 0 0 1 12c0 1.77.42 3.45 1.18 4.94l3.66-2.84z"/>
|
|
<path fill="#EA4335" d="M12 5.38c1.62 0 3.07.56 4.21 1.64l3.15-3.15C17.45 2.10 14.96 1 12 1A11 11 0 0 0 2.18 7.06l3.66 2.84C6.71 7.30 9.14 5.38 12 5.38z"/>
|
|
</svg>
|
|
Google
|
|
</button>
|
|
<button type="button" class="clu-sso-btn">
|
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
<path d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.11.79-.25.79-.55v-2.02c-3.2.7-3.87-1.36-3.87-1.36-.52-1.33-1.27-1.68-1.27-1.68-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.75 2.69 1.24 3.35.95.1-.74.4-1.24.73-1.53-2.55-.29-5.24-1.28-5.24-5.69 0-1.26.45-2.28 1.18-3.09-.12-.29-.51-1.46.11-3.05 0 0 .97-.31 3.18 1.18.92-.26 1.91-.39 2.89-.39.98 0 1.97.13 2.89.39 2.20-1.49 3.17-1.18 3.17-1.18.63 1.59.23 2.76.12 3.05.74.81 1.18 1.83 1.18 3.09 0 4.42-2.69 5.40-5.25 5.68.41.36.78 1.06.78 2.14v3.18c0 .31.21.67.79.55C20.21 21.39 23.5 17.08 23.5 12c0-6.35-5.15-11.5-11.5-11.5z"/>
|
|
</svg>
|
|
GitHub
|
|
</button>
|
|
</div>
|
|
|
|
<div class="clu-divider">oder mit E-Mail</div>
|
|
|
|
{{-- Session status (Laravel default partial) --}}
|
|
<x-auth-session-status :status="session('status')" />
|
|
|
|
{{-- Form --}}
|
|
<form wire:submit="login" class="flex flex-col gap-[14px]" autocomplete="on" x-data="{ showPwd: false }">
|
|
|
|
{{-- Email --}}
|
|
<div class="flex flex-col gap-[6px]">
|
|
<label for="email" class="text-[11.5px] text-(--color-muted) font-semibold">E-Mail-Adresse</label>
|
|
<div class="clu-input">
|
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
<rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/>
|
|
</svg>
|
|
<input wire:model="form.email" id="email" name="email" type="email"
|
|
placeholder="du@firma.de" autocomplete="email" required autofocus />
|
|
</div>
|
|
<x-input-error :messages="$errors->get('form.email')" class="mt-1" />
|
|
</div>
|
|
|
|
{{-- Password --}}
|
|
<div class="flex flex-col gap-[6px]">
|
|
<label for="password" class="flex items-center justify-between text-[11.5px] text-(--color-muted) font-semibold">
|
|
Passwort
|
|
@if (Route::has('password.request'))
|
|
<a href="{{ route('password.request') }}" wire:navigate
|
|
class="text-[11.5px] text-(--color-muted) font-medium hover:text-(--color-fg) no-underline">Vergessen?</a>
|
|
@endif
|
|
</label>
|
|
<div class="clu-input">
|
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
<rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/>
|
|
</svg>
|
|
<input wire:model="form.password" id="password" name="password"
|
|
:type="showPwd ? 'text' : 'password'" placeholder="••••••••••••"
|
|
autocomplete="current-password" required />
|
|
<button type="button" class="clu-eye" @click="showPwd = !showPwd"
|
|
:aria-label="showPwd ? 'Passwort verbergen' : 'Passwort anzeigen'">
|
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
<path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<x-input-error :messages="$errors->get('form.password')" class="mt-1" />
|
|
</div>
|
|
|
|
{{-- Remember --}}
|
|
<div class="flex items-center mt-[2px]">
|
|
<label class="clu-checkbox">
|
|
<input wire:model="form.remember" type="checkbox" />
|
|
<span class="box">
|
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M5 12l5 5L20 7"/>
|
|
</svg>
|
|
</span>
|
|
<span>30 Tage angemeldet bleiben</span>
|
|
</label>
|
|
</div>
|
|
|
|
{{-- Submit --}}
|
|
<button class="clu-btn-primary" type="submit" wire:loading.attr="disabled">
|
|
<svg wire:loading class="clu-spin" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true">
|
|
<path d="M21 12a9 9 0 1 1-6.2-8.55"/>
|
|
</svg>
|
|
<span wire:loading.remove>Anmelden</span>
|
|
<span wire:loading>Authentifiziere…</span>
|
|
<svg class="arrow" wire:loading.remove width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true">
|
|
<path d="M5 12h14M13 5l7 7-7 7"/>
|
|
</svg>
|
|
</button>
|
|
|
|
{{-- Alt options --}}
|
|
<div class="flex flex-col gap-[9px] mt-[4px]">
|
|
<button class="clu-alt-btn" type="button">
|
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-(--color-muted)" aria-hidden="true">
|
|
<path d="M22 2L11 13"/><path d="M22 2l-7 20-4-9-9-4 20-7z"/>
|
|
</svg>
|
|
Magic-Link per E-Mail senden
|
|
</button>
|
|
<button class="clu-alt-btn" type="button">
|
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-(--color-muted)" aria-hidden="true">
|
|
<path d="M21 12c0-4.97-4.03-9-9-9-2.5 0-4.76 1.02-6.39 2.67M3 12c0 4.97 4.03 9 9 9 2.5 0 4.76-1.02 6.39-2.67"/>
|
|
<path d="M9 12a3 3 0 1 0 6 0 3 3 0 1 0-6 0M12 1v4M12 19v4"/>
|
|
</svg>
|
|
Mit Passkey anmelden
|
|
<span class="ml-auto font-mono text-[10px] text-(--color-accent) bg-(--color-accent-soft) px-[7px] py-[2px] rounded-[5px] border border-(--color-accent)/25 font-semibold">empfohlen</span>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
{{-- Footer --}}
|
|
<footer class="flex items-center justify-between gap-3 text-(--color-muted) text-[11.5px] font-mono pt-[14px] border-t border-(--color-hairline) flex-wrap">
|
|
<div class="flex items-center gap-[6px]">
|
|
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" aria-hidden="true">
|
|
<rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/>
|
|
</svg>
|
|
TLS 1.3 · AES-256 · region eu-central-1
|
|
</div>
|
|
<div class="flex gap-[14px]">
|
|
<a href="#" class="text-(--color-muted) hover:text-(--color-fg) no-underline">Datenschutz</a>
|
|
<a href="#" class="text-(--color-muted) hover:text-(--color-fg) no-underline">AGB</a>
|
|
<a href="#" class="text-(--color-muted) hover:text-(--color-fg) no-underline">Status</a>
|
|
<a href="#" class="text-(--color-muted) hover:text-(--color-fg) no-underline">Hilfe</a>
|
|
</div>
|
|
</footer>
|
|
</section>
|
|
|
|
{{-- ============ RIGHT: LIVE PANEL ============ --}}
|
|
<aside class="clu-glass clu-auth-side-right relative overflow-hidden flex flex-col gap-[18px]"
|
|
style="padding:30px 34px;min-height:calc(100vh - 28px);" aria-hidden="true">
|
|
|
|
{{-- ambient overlay --}}
|
|
<div class="absolute inset-0 pointer-events-none"
|
|
style="border-radius:inherit;background:radial-gradient(80% 50% at 100% 0%, rgba(99,140,255,0.18), transparent 60%);"></div>
|
|
|
|
{{-- head --}}
|
|
<div class="relative flex items-center justify-between">
|
|
<div class="text-[11px] text-(--color-muted) font-mono uppercase tracking-[0.10em]">— live cluster</div>
|
|
<div class="text-[11px] text-(--color-muted) font-mono">eu-central-1 · build #4f8a32c</div>
|
|
</div>
|
|
|
|
{{-- hero --}}
|
|
<div class="relative">
|
|
<p class="m-0 text-[23px] font-semibold tracking-[-0.015em] leading-[1.3]" style="max-width:480px;">
|
|
Verwalte <em class="not-italic text-(--color-accent) font-semibold">87 WordPress-Sites</em> auf 6 Servern. Ein Login. Volle Kontrolle.
|
|
</p>
|
|
<p class="text-(--color-muted) text-[13px] mt-[7px]" style="max-width:460px;">
|
|
SSH-Tunnel, Backups, Updates und Security in einer Konsole — von Push-to-Deploy bis Brute-Force-Defense.
|
|
</p>
|
|
</div>
|
|
|
|
{{-- Stat strip --}}
|
|
<div class="relative grid grid-cols-3 gap-[12px] mt-[6px]">
|
|
<div class="clu-stat">
|
|
<div class="clu-stat-label">Sites Online</div>
|
|
<div class="clu-stat-value">87<span class="unit">/87</span></div>
|
|
<div class="clu-stat-trend">● 100% Uptime · 24h</div>
|
|
</div>
|
|
<div class="clu-stat">
|
|
<div class="clu-stat-label">Ø Response</div>
|
|
<div class="clu-stat-value">142<span class="unit">ms</span></div>
|
|
<div class="clu-stat-trend">↓ 8,4% vs. 7d</div>
|
|
</div>
|
|
<div class="clu-stat">
|
|
<div class="clu-stat-label">Blocked / 24h</div>
|
|
<div class="clu-stat-value">128</div>
|
|
<div class="clu-stat-trend warn">↑ 23% Brute-force</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Terminal --}}
|
|
<div class="relative clu-term-card flex-1">
|
|
<div class="clu-term-head">
|
|
<div class="clu-term-dots"><span></span><span></span><span></span></div>
|
|
<div class="clu-term-title">ssh deploy@eu-prod-01</div>
|
|
<div class="clu-term-meta">pts/0</div>
|
|
</div>
|
|
<div id="clu-term-body" class="clu-term-body" wire:ignore></div>
|
|
</div>
|
|
|
|
{{-- Live tags --}}
|
|
<div class="relative flex flex-wrap gap-[7px]">
|
|
<span class="clu-live-tag"><span class="dot"></span>eu-prod-01 · 23 sites</span>
|
|
<span class="clu-live-tag"><span class="dot"></span>eu-prod-02 · 19 sites</span>
|
|
<span class="clu-live-tag"><span class="dot"></span>edge-ams · 14 sites</span>
|
|
<span class="clu-live-tag warn"><span class="dot"></span>us-east-01 · CPU 78%</span>
|
|
<span class="clu-live-tag"><span class="dot"></span>staging · 6 sites</span>
|
|
</div>
|
|
</aside>
|
|
|
|
{{-- Terminal type-in script --}}
|
|
@push('scripts')
|
|
<script>
|
|
(function () {
|
|
const body = document.getElementById('clu-term-body');
|
|
if (!body) return;
|
|
const lines = [
|
|
['09:42:18', '<span class="clu-term-prompt"></span> <span class="dim">clupilot</span> status --cluster=acme'],
|
|
['09:42:18', '<span class="ok">✓</span> 87/87 sites <span class="dim">healthy</span> · <span class="acc">142ms</span> ø response'],
|
|
['09:42:19', '<span class="ok">✓</span> backup eu-prod-02 → <span class="pkg">s3://clupilot-backups</span> <span class="dim">248 MB</span>'],
|
|
['09:42:21', '<span class="acc">→</span> deploying <span class="pkg">Yoast-SEO@22.5</span> to <span class="dim">3 sites…</span>'],
|
|
['09:42:23', '<span class="ok">✓</span> agentur-helio.com <span class="dim">(312ms)</span>'],
|
|
['09:42:24', '<span class="ok">✓</span> blog.fintech-radar.io <span class="dim">(287ms)</span>'],
|
|
['09:42:24', '<span class="ok">✓</span> docs.opencrate.dev <span class="dim">(298ms)</span>'],
|
|
['09:42:26', '<span class="warn">!</span> classic.tonhoff-bau.de <span class="dim">PHP 7.4 EOL — migration queued</span>'],
|
|
['09:42:28', '<span class="err">✗</span> fw <span class="dim">blocked</span> 17 login attempts from <span class="acc">185.220.101.18</span> <span class="dim">(TOR)</span>'],
|
|
['09:42:30', '<span class="ok">✓</span> SSL renewed: <span class="pkg">*.kunde-mueller.de</span> <span class="dim">until 2026-08-25</span>'],
|
|
['09:42:32', '<span class="clu-term-prompt"></span> <span class="dim">tail -f /var/log/clupilot/audit.log</span><span class="clu-cursor"></span>'],
|
|
];
|
|
let i = 0;
|
|
function addLine() {
|
|
if (i >= lines.length) { setTimeout(function () { body.innerHTML = ''; i = 0; addLine(); }, 5000); return; }
|
|
const [t, html] = lines[i++];
|
|
const el = document.createElement('div');
|
|
el.className = 'clu-term-line';
|
|
el.innerHTML = '<div class="clu-term-time">' + t + '</div><div class="clu-term-text">' + html + '</div>';
|
|
body.appendChild(el);
|
|
body.scrollTop = body.scrollHeight;
|
|
while (body.children.length > 11) body.removeChild(body.firstChild);
|
|
setTimeout(addLine, 600 + Math.random() * 450);
|
|
}
|
|
addLine();
|
|
})();
|
|
</script>
|
|
@endpush
|
|
</div>
|