clupilot/app/resources/views/dashboard.blade.php

705 lines
53 KiB
PHP

@extends('layouts.dash')
@section('content')
<div class="flex min-h-screen" style="display:grid;grid-template-columns:220px 1fr;">
{{-- ===== SIDEBAR ===== --}}
<aside class="sticky top-0 h-screen flex flex-col border-r border-white/6 bg-s0">
{{-- HEADER (fixed) --}}
<div class="flex-none" style="padding:16px 12px 0;">
{{-- Brand --}}
<div class="flex items-center gap-[9px] border-b border-white/6" style="padding:4px 6px 12px;">
<div class="clu-brand-mark-sm w-[26px] h-[26px] rounded-[7px] flex-none" aria-hidden="true"></div>
<div class="font-semibold text-[14px] tracking-[-0.01em]">
CluPilot<span class="text-t2 font-medium ml-1 text-[11px]">v2.4</span>
</div>
</div>
{{-- Workspace switch --}}
<button class="flex items-center gap-[9px] border border-white/6 rounded-md bg-surface transition-colors hover:bg-[#14141e] cursor-pointer w-full" style="padding:8px 9px;margin-top:12px;" aria-label="Workspace wechseln">
<div class="w-[22px] h-[22px] rounded-[5px] grid place-items-center text-bg font-bold text-[11px] flex-none" style="background:linear-gradient(135deg,#3ecf8e,#2a9d6f);">AC</div>
<div class="flex-1 min-w-0 text-left">
<div class="text-[12.5px] font-medium truncate">Acme Cluster</div>
<div class="text-[10.5px] text-t2 font-mono">team · 14 user</div>
</div>
<svg class="text-dim flex-none" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M8 9l4-4 4 4M8 15l4 4 4-4"/>
</svg>
</button>
</div>
{{-- Navigation (scrollable) --}}
<nav class="flex flex-col gap-[14px] flex-1 overflow-y-auto min-h-0" style="padding:12px 12px 0;">
<div class="flex flex-col gap-[1px]">
<div class="text-[10.5px] uppercase tracking-[.08em] text-dim font-semibold" style="padding:6px 9px 4px;">Übersicht</div>
<div class="clu-nav-active flex items-center gap-[9px] rounded-md text-t1 cursor-pointer text-[12.5px] transition-all bg-[#181822]" style="padding:6.5px 9px;">
<svg class="w-[15px] h-[15px] text-akzent flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/></svg>
<span>Dashboard</span>
</div>
<div class="flex items-center gap-[9px] rounded-md text-[#c8c8d8] cursor-pointer text-[12.5px] transition-all hover:bg-[#14141e] hover:text-t1" style="padding:6.5px 9px;">
<svg class="w-[15px] h-[15px] text-t2 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M3 12h4l3-7 4 14 3-7h4"/></svg>
<span>Metriken</span>
<span class="ml-auto text-[10.5px] font-mono text-t2 bg-[#14141e] px-[6px] py-[1px] rounded-[10px] border border-white/6">live</span>
</div>
<div class="flex items-center gap-[9px] rounded-md text-[#c8c8d8] cursor-pointer text-[12.5px] transition-all hover:bg-[#14141e] hover:text-t1" style="padding:6.5px 9px;">
<svg class="w-[15px] h-[15px] text-t2 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>
<span>Aktivität</span>
</div>
</div>
<div class="flex flex-col gap-[1px]">
<div class="text-[10.5px] uppercase tracking-[.08em] text-dim font-semibold" style="padding:6px 9px 4px;">Infrastruktur</div>
<div class="flex items-center gap-[9px] rounded-md text-[#c8c8d8] cursor-pointer text-[12.5px] transition-all hover:bg-[#14141e] hover:text-t1" style="padding:6.5px 9px;">
<svg class="w-[15px] h-[15px] text-t2 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/><circle cx="7" cy="7.5" r="0.7" fill="currentColor"/><circle cx="7" cy="16.5" r="0.7" fill="currentColor"/></svg>
<span>Server</span>
<span class="ml-auto text-[10.5px] font-mono text-t2 bg-[#14141e] px-[6px] py-[1px] rounded-[10px] border border-white/6">6</span>
</div>
<div class="flex items-center gap-[9px] rounded-md text-[#c8c8d8] cursor-pointer text-[12.5px] transition-all hover:bg-[#14141e] hover:text-t1" style="padding:6.5px 9px;">
<svg class="w-[15px] h-[15px] text-t2 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M5 12V7a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v5M3 12h18l-2 8H5l-2-8z"/></svg>
<span>Cluster</span>
</div>
<div class="flex items-center gap-[9px] rounded-md text-[#c8c8d8] cursor-pointer text-[12.5px] transition-all hover:bg-[#14141e] hover:text-t1" style="padding:6.5px 9px;">
<svg class="w-[15px] h-[15px] text-t2 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M9 17l-5 3v-7l5 3zM15 17l5 3v-7l-5 3zM12 4l5 3v6l-5 3-5-3V7l5-3z"/></svg>
<span>SSH Keys</span>
</div>
<div class="flex items-center gap-[9px] rounded-md text-[#c8c8d8] cursor-pointer text-[12.5px] transition-all hover:bg-[#14141e] hover:text-t1" style="padding:6.5px 9px;">
<svg class="w-[15px] h-[15px] text-t2 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>
<span>DNS &amp; Domains</span>
</div>
</div>
<div class="flex flex-col gap-[1px]">
<div class="text-[10.5px] uppercase tracking-[.08em] text-dim font-semibold" style="padding:6px 9px 4px;">WordPress</div>
<div class="flex items-center gap-[9px] rounded-md text-[#c8c8d8] cursor-pointer text-[12.5px] transition-all hover:bg-[#14141e] hover:text-t1" style="padding:6.5px 9px;">
<svg class="w-[15px] h-[15px] text-t2 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3v18"/></svg>
<span>Sites</span>
<span class="ml-auto text-[10.5px] font-mono text-t2 bg-[#14141e] px-[6px] py-[1px] rounded-[10px] border border-white/6">87</span>
</div>
<div class="flex items-center gap-[9px] rounded-md text-[#c8c8d8] cursor-pointer text-[12.5px] transition-all hover:bg-[#14141e] hover:text-t1" style="padding:6.5px 9px;">
<svg class="w-[15px] h-[15px] text-t2 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M12 4v16M4 12h16"/><circle cx="12" cy="12" r="9"/></svg>
<span>Plugins</span>
</div>
<div class="flex items-center gap-[9px] rounded-md text-[#c8c8d8] cursor-pointer text-[12.5px] transition-all hover:bg-[#14141e] hover:text-t1" style="padding:6.5px 9px;">
<svg class="w-[15px] h-[15px] text-t2 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M3 6h18M3 12h18M3 18h18"/></svg>
<span>Themes</span>
</div>
<div class="flex items-center gap-[9px] rounded-md text-[#c8c8d8] cursor-pointer text-[12.5px] transition-all hover:bg-[#14141e] hover:text-t1" style="padding:6.5px 9px;">
<svg class="w-[15px] h-[15px] text-t2 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M21 12c0 5-9 9-9 9s-9-4-9-9V5l9-3 9 3v7z"/><path d="M9 11l2 2 4-4"/></svg>
<span>Backups</span>
</div>
</div>
<div class="flex flex-col gap-[1px]">
<div class="text-[10.5px] uppercase tracking-[.08em] text-dim font-semibold" style="padding:6px 9px 4px;">Sicherheit</div>
<div class="flex items-center gap-[9px] rounded-md text-[#c8c8d8] cursor-pointer text-[12.5px] transition-all hover:bg-[#14141e] hover:text-t1" style="padding:6.5px 9px;">
<svg class="w-[15px] h-[15px] text-t2 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/></svg>
<span>Firewall</span>
<span class="ml-auto text-[10.5px] font-mono text-warning bg-warning/12 px-[6px] py-[1px] rounded-[10px] border border-warning/25">3</span>
</div>
<div class="flex items-center gap-[9px] rounded-md text-[#c8c8d8] cursor-pointer text-[12.5px] transition-all hover:bg-[#14141e] hover:text-t1" style="padding:6.5px 9px;">
<svg class="w-[15px] h-[15px] text-t2 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 8v4l3 2"/></svg>
<span>Audit-Log</span>
</div>
<div class="flex items-center gap-[9px] rounded-md text-[#c8c8d8] cursor-pointer text-[12.5px] transition-all hover:bg-[#14141e] hover:text-t1" style="padding:6.5px 9px;">
<svg class="w-[15px] h-[15px] text-t2 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><rect x="5" y="11" width="14" height="9" rx="1.5"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>
<span>Zertifikate</span>
</div>
</div>
<div class="flex flex-col gap-[1px]">
<div class="text-[10.5px] uppercase tracking-[.08em] text-dim font-semibold" style="padding:6px 9px 4px;">Einstellungen</div>
<div class="flex items-center gap-[9px] rounded-md text-[#c8c8d8] cursor-pointer text-[12.5px] transition-all hover:bg-[#14141e] hover:text-t1" style="padding:6.5px 9px;">
<svg class="w-[15px] h-[15px] text-t2 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg>
<span>Allgemein</span>
</div>
<div class="flex items-center gap-[9px] rounded-md text-[#c8c8d8] cursor-pointer text-[12.5px] transition-all hover:bg-[#14141e] hover:text-t1" style="padding:6.5px 9px;">
<svg class="w-[15px] h-[15px] text-t2 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8"/></svg>
<span>Team</span>
</div>
<div class="flex items-center gap-[9px] rounded-md text-[#c8c8d8] cursor-pointer text-[12.5px] transition-all hover:bg-[#14141e] hover:text-t1" style="padding:6.5px 9px;">
<svg class="w-[15px] h-[15px] text-t2 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><rect x="3" y="6" width="18" height="13" rx="2"/><path d="M3 10h18"/></svg>
<span>Abrechnung</span>
</div>
</div>
</nav>
{{-- FOOTER (fixed) --}}
<div class="flex-none border-t border-white/6" style="padding:12px;">
{{-- Plan box --}}
<div class="clu-plan-box border border-white/6 rounded-[10px] p-3" style="background:linear-gradient(180deg,#11111a 0%,#0e0e15 100%);">
<div class="relative flex items-center justify-between text-[11.5px]">
<span class="font-semibold">Team Plan</span>
<span class="font-mono text-[10px] text-akzent bg-akzent/12 px-[6px] py-[2px] rounded-[4px] border border-akzent/25">PRO</span>
</div>
<div class="relative mt-[9px] h-[5px] rounded-[3px] bg-[#191923] overflow-hidden">
<span class="block h-full w-[62%] rounded-[3px]" style="background:linear-gradient(90deg,#4f8ef7,#7c5cff);"></span>
</div>
<div class="relative flex justify-between mt-[6px] text-[10.5px] text-t2 font-mono">
<span>87 / 140 sites</span><span>62%</span>
</div>
<button class="relative mt-[10px] w-full border border-white/10 rounded-md text-[11.5px] font-medium text-t1 bg-s2 transition-colors hover:bg-s3" style="padding:7px;">
Upgrade auf Scale
</button>
</div>
{{-- Logout --}}
<form method="POST" action="{{ route('logout') }}" class="mt-[8px]">
@csrf
<button type="submit" class="flex items-center gap-[9px] w-full rounded-md text-t2 text-[12.5px] font-medium transition-all hover:bg-[#14141e] hover:text-t1" style="padding:7px 9px;">
<svg class="flex-none" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9"/>
</svg>
Abmelden
</button>
</form>
</div>
</aside>
{{-- ===== MAIN ===== --}}
<main class="flex flex-col min-w-0">
{{-- Topbar --}}
<div class="clu-topbar sticky top-0 z-[5] flex items-center gap-[14px] border-b border-white/6" style="padding:14px 22px;">
<div class="flex items-center gap-2 min-w-0">
<span class="text-t2 text-[12.5px]">Acme Cluster</span>
<span class="text-dim text-[12.5px]">/</span>
<span class="text-[14.5px] font-semibold tracking-[-0.01em]">Dashboard</span>
</div>
<span class="inline-flex items-center gap-[6px] px-2 py-[3px] border border-success/25 bg-success/12 text-success rounded-full text-[11px] font-medium font-mono">
<span class="clu-pulse-dot w-[6px] h-[6px] rounded-full bg-current"></span>
All systems operational
</span>
<div class="flex-1"></div>
<div class="clu-search">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
<input placeholder="Sites, Server, Domains suchen…" />
<kbd class="font-mono text-[10.5px] text-t2 px-[5px] py-[1px] border border-white/6 rounded-[4px] bg-s0">⌘K</kbd>
</div>
<button class="relative w-8 h-8 border border-white/6 rounded-[7px] grid place-items-center text-t2 bg-surface transition-all hover:text-t1 hover:border-white/10 hover:bg-[#15151f]" aria-label="Dokumentation">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M9.1 9a3 3 0 0 1 5.8 1c0 2-3 3-3 3M12 17h.01"/></svg>
</button>
<button class="relative w-8 h-8 border border-white/6 rounded-[7px] grid place-items-center text-t2 bg-surface transition-all hover:text-t1 hover:border-white/10 hover:bg-[#15151f]" aria-label="Benachrichtigungen">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10 21a2 2 0 0 0 4 0"/></svg>
<span class="absolute -top-[3px] -right-[3px] bg-danger text-white text-[9.5px] font-semibold min-w-[16px] h-4 rounded-full grid place-items-center px-1 border-2 border-bg font-mono">3</span>
</button>
<button class="clu-btn-dash">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>
Server
</button>
</div>
{{-- Content --}}
<div class="flex flex-col gap-[22px] w-full mx-auto" style="padding:22px;max-width:1600px;">
{{-- METRICS --}}
<div style="display:grid;grid-template-columns:repeat(6,1fr);gap:12px;">
<div class="clu-metric clu-metric-success bg-s2 border border-white/6 rounded-[10px] transition-colors hover:border-white/10" style="padding:14px 14px 13px;">
<div class="flex items-center gap-[7px] text-t2 text-[11.5px] font-medium">
<svg class="w-[13px] h-[13px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M9 12l2 2 4-4"/></svg>
Sites Online
</div>
<div class="font-mono text-[22px] font-semibold tracking-[-0.02em] leading-[1.1] mt-2">
87<span class="text-[13px] text-t2 font-medium ml-[2px]">/87</span>
</div>
<div class="flex items-center gap-[6px] text-t2 text-[11px] mt-[6px]">
<span class="font-mono font-medium text-success">100%</span> Verfügbarkeit · 24h
</div>
<svg class="absolute right-[10px] bottom-[10px] opacity-70" width="60" height="22" viewBox="0 0 60 22" aria-hidden="true"><polyline fill="none" stroke="#3ecf8e" stroke-width="1.5" points="0,16 8,14 16,15 24,12 32,13 40,10 48,11 60,8"/></svg>
</div>
<div class="clu-metric bg-s2 border border-white/6 rounded-[10px] transition-colors hover:border-white/10" style="padding:14px 14px 13px;">
<div class="flex items-center gap-[7px] text-t2 text-[11.5px] font-medium">
<svg class="w-[13px] h-[13px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>
Ø Response
</div>
<div class="font-mono text-[22px] font-semibold tracking-[-0.02em] leading-[1.1] mt-2">
142<span class="text-[13px] text-t2 font-medium ml-[2px]">ms</span>
</div>
<div class="flex items-center gap-[6px] text-t2 text-[11px] mt-[6px]">
<span class="font-mono font-medium text-success"> 8.4%</span> vs. letzte Woche
</div>
<svg class="absolute right-[10px] bottom-[10px] opacity-70" width="60" height="22" viewBox="0 0 60 22" aria-hidden="true"><polyline fill="none" stroke="#4f8ef7" stroke-width="1.5" points="0,8 8,12 16,9 24,14 32,11 40,10 48,7 60,9"/></svg>
</div>
<div class="clu-metric clu-metric-warning bg-s2 border border-white/6 rounded-[10px] transition-colors hover:border-white/10" style="padding:14px 14px 13px;">
<div class="flex items-center gap-[7px] text-t2 text-[11.5px] font-medium">
<svg class="w-[13px] h-[13px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21 12a9 9 0 1 1-3-6.7M21 4v5h-5"/></svg>
Updates Pending
</div>
<div class="font-mono text-[22px] font-semibold tracking-[-0.02em] leading-[1.1] mt-2">24</div>
<div class="flex items-center gap-[6px] text-t2 text-[11px] mt-[6px]">
<span class="font-mono font-medium text-warning">7 kritisch</span> · 17 minor
</div>
<svg class="absolute right-[10px] bottom-[10px] opacity-70" width="60" height="22" viewBox="0 0 60 22" aria-hidden="true"><polyline fill="none" stroke="#f5a623" stroke-width="1.5" points="0,12 8,14 16,11 24,9 32,10 40,8 48,6 60,5"/></svg>
</div>
<div class="clu-metric clu-metric-success bg-s2 border border-white/6 rounded-[10px] transition-colors hover:border-white/10" style="padding:14px 14px 13px;">
<div class="flex items-center gap-[7px] text-t2 text-[11.5px] font-medium">
<svg class="w-[13px] h-[13px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21 12c0 5-9 9-9 9s-9-4-9-9V5l9-3 9 3v7z"/><path d="M9 11l2 2 4-4"/></svg>
Backups 24h
</div>
<div class="font-mono text-[22px] font-semibold tracking-[-0.02em] leading-[1.1] mt-2">
86<span class="text-[13px] text-t2 font-medium ml-[2px]">/87</span>
</div>
<div class="flex items-center gap-[6px] text-t2 text-[11px] mt-[6px]">
<span class="font-mono font-medium text-warning">1 fehlgeschlagen</span> · 02:14 UTC
</div>
<svg class="absolute right-[10px] bottom-[10px] opacity-70" width="60" height="22" viewBox="0 0 60 22" aria-hidden="true"><polyline fill="none" stroke="#3ecf8e" stroke-width="1.5" points="0,6 8,5 16,6 24,5 32,6 40,5 48,6 60,5"/></svg>
</div>
<div class="clu-metric clu-metric-danger bg-s2 border border-white/6 rounded-[10px] transition-colors hover:border-white/10" style="padding:14px 14px 13px;">
<div class="flex items-center gap-[7px] text-t2 text-[11.5px] font-medium">
<svg class="w-[13px] h-[13px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/><path d="M12 8v5M12 16h.01"/></svg>
Security Events
</div>
<div class="font-mono text-[22px] font-semibold tracking-[-0.02em] leading-[1.1] mt-2">128</div>
<div class="flex items-center gap-[6px] text-t2 text-[11px] mt-[6px]">
<span class="font-mono font-medium text-danger"> 23%</span> Brute-force blockiert
</div>
<svg class="absolute right-[10px] bottom-[10px] opacity-70" width="60" height="22" viewBox="0 0 60 22" aria-hidden="true"><polyline fill="none" stroke="#f05252" stroke-width="1.5" points="0,16 8,12 16,14 24,9 32,11 40,6 48,8 60,4"/></svg>
</div>
<div class="clu-metric bg-s2 border border-white/6 rounded-[10px] transition-colors hover:border-white/10" style="padding:14px 14px 13px;">
<div class="flex items-center gap-[7px] text-t2 text-[11.5px] font-medium">
<svg class="w-[13px] h-[13px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><ellipse cx="12" cy="6" rx="9" ry="3"/><path d="M3 6v12c0 1.7 4 3 9 3s9-1.3 9-3V6M3 12c0 1.7 4 3 9 3s9-1.3 9-3"/></svg>
Storage
</div>
<div class="font-mono text-[22px] font-semibold tracking-[-0.02em] leading-[1.1] mt-2">
1.84<span class="text-[13px] text-t2 font-medium ml-[2px]">TB</span>
</div>
<div class="flex items-center gap-[6px] text-t2 text-[11px] mt-[6px]">
<span class="font-mono font-medium text-warning">73%</span> von 2.5 TB belegt
</div>
<svg class="absolute right-[10px] bottom-[10px] opacity-70" width="60" height="22" viewBox="0 0 60 22" aria-hidden="true"><polyline fill="none" stroke="#8888a0" stroke-width="1.5" points="0,14 8,13 16,12 24,11 32,10 40,9 48,8 60,7"/></svg>
</div>
</div>
{{-- SERVER TABLE --}}
<section class="bg-s2 border border-white/6 rounded-[10px] overflow-hidden">
<div class="flex items-center justify-between border-b border-white/6" style="padding:13px 16px 12px;">
<h3 class="m-0 text-[13px] font-semibold tracking-[-0.005em] flex items-center gap-2">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/><circle cx="7" cy="7.5" r="0.7" fill="currentColor"/><circle cx="7" cy="16.5" r="0.7" fill="currentColor"/></svg>
Server-Flotte
<span class="font-mono text-[10.5px] text-t2 border border-white/6 bg-hover px-[7px] py-[2px] rounded-[4px] ml-[6px]">6 nodes</span>
</h3>
<div class="flex gap-[2px]">
<button class="px-[9px] py-1 text-[11.5px] text-t1 rounded-[5px] bg-hover transition-colors">Alle</button>
<button class="px-[9px] py-1 text-[11.5px] text-t2 rounded-[5px] transition-colors hover:text-t1">Produktion</button>
<button class="px-[9px] py-1 text-[11.5px] text-t2 rounded-[5px] transition-colors hover:text-t1">Staging</button>
<button class="px-[9px] py-1 text-[11.5px] text-t2 rounded-[5px] transition-colors hover:text-t1">Edge</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full border-collapse text-[12.5px]">
<thead>
<tr>
<th class="text-left font-medium text-t2 text-[11px] tracking-[.04em] uppercase border-b border-white/6 bg-surface sticky top-0" style="padding:9px 14px;">Server</th>
<th class="text-left font-medium text-t2 text-[11px] tracking-[.04em] uppercase border-b border-white/6 bg-surface sticky top-0" style="padding:9px 14px;">Sites</th>
<th class="text-left font-medium text-t2 text-[11px] tracking-[.04em] uppercase border-b border-white/6 bg-surface sticky top-0" style="padding:9px 14px;">CPU · RAM · Disk</th>
<th class="text-left font-medium text-t2 text-[11px] tracking-[.04em] uppercase border-b border-white/6 bg-surface sticky top-0" style="padding:9px 14px;">Standort</th>
<th class="text-right font-medium text-t2 text-[11px] tracking-[.04em] uppercase border-b border-white/6 bg-surface sticky top-0" style="padding:9px 14px;">Uptime</th>
<th class="border-b border-white/6 bg-surface sticky top-0 w-[120px]" style="padding:9px 14px;"></th>
</tr>
</thead>
<tbody id="server-body"></tbody>
</table>
</div>
</section>
{{-- SITES TABLE --}}
<section class="bg-s2 border border-white/6 rounded-[10px] overflow-hidden">
<div class="flex items-center justify-between border-b border-white/6" style="padding:13px 16px 12px;">
<h3 class="m-0 text-[13px] font-semibold tracking-[-0.005em] flex items-center gap-2">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>
WordPress-Sites
<span class="font-mono text-[10.5px] text-t2 border border-white/6 bg-hover px-[7px] py-[2px] rounded-[4px] ml-[6px]">8 sichtbar · 87 gesamt</span>
</h3>
<div class="flex gap-2 items-center">
<button class="px-[9px] py-[5px] text-[11.5px] text-t2 border border-white/6 rounded-md bg-surface transition-all hover:text-t1 hover:border-white/10">Filter</button>
<button class="px-[9px] py-[5px] text-[11.5px] text-t2 border border-white/6 rounded-md bg-surface transition-all hover:text-t1 hover:border-white/10">Sortieren</button>
<button class="px-[9px] py-[5px] text-[11.5px] text-t1 border border-white/10 rounded-md bg-hover transition-all hover:border-white/15">Alle anzeigen </button>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full border-collapse text-[12.5px]">
<thead>
<tr>
<th class="text-left font-medium text-t2 text-[11px] tracking-[.04em] uppercase border-b border-white/6 bg-surface sticky top-0" style="padding:9px 14px;">Site</th>
<th class="text-left font-medium text-t2 text-[11px] tracking-[.04em] uppercase border-b border-white/6 bg-surface sticky top-0" style="padding:9px 14px;">Server</th>
<th class="text-left font-medium text-t2 text-[11px] tracking-[.04em] uppercase border-b border-white/6 bg-surface sticky top-0" style="padding:9px 14px;">WP</th>
<th class="text-left font-medium text-t2 text-[11px] tracking-[.04em] uppercase border-b border-white/6 bg-surface sticky top-0" style="padding:9px 14px;">PHP</th>
<th class="text-right font-medium text-t2 text-[11px] tracking-[.04em] uppercase border-b border-white/6 bg-surface sticky top-0" style="padding:9px 14px;">Visitors / 24h</th>
<th class="text-left font-medium text-t2 text-[11px] tracking-[.04em] uppercase border-b border-white/6 bg-surface sticky top-0" style="padding:9px 14px;">Updates</th>
<th class="text-left font-medium text-t2 text-[11px] tracking-[.04em] uppercase border-b border-white/6 bg-surface sticky top-0" style="padding:9px 14px;">Backup</th>
<th class="text-left font-medium text-t2 text-[11px] tracking-[.04em] uppercase border-b border-white/6 bg-surface sticky top-0" style="padding:9px 14px;">Security</th>
<th class="border-b border-white/6 bg-surface sticky top-0" style="padding:9px 14px;"></th>
</tr>
</thead>
<tbody id="sites-body"></tbody>
</table>
</div>
</section>
{{-- BOTTOM ROW --}}
<div style="display:grid;grid-template-columns:1.4fr 1fr 1fr;gap:14px;">
{{-- Traffic chart --}}
<section class="bg-s2 border border-white/6 rounded-[10px] overflow-hidden">
<div class="flex items-center justify-between border-b border-white/6" style="padding:13px 16px 12px;">
<h3 class="m-0 text-[13px] font-semibold tracking-[-0.005em] flex items-center gap-2">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M3 17l5-5 4 4 7-8M14 8h6v6"/></svg>
Traffic
<span class="inline-flex items-center gap-[5px] font-mono text-[10.5px] text-success font-medium ml-[2px]">
<span class="clu-pulse-dot w-[6px] h-[6px] rounded-full bg-current"></span>live
</span>
</h3>
<div class="flex gap-[2px]">
<button class="px-[9px] py-1 text-[11.5px] text-t2 rounded-[5px] transition-colors hover:text-t1">15m</button>
<button class="px-[9px] py-1 text-[11.5px] text-t1 rounded-[5px] bg-hover transition-colors">60m</button>
<button class="px-[9px] py-1 text-[11.5px] text-t2 rounded-[5px] transition-colors hover:text-t1">24h</button>
<button class="px-[9px] py-1 text-[11.5px] text-t2 rounded-[5px] transition-colors hover:text-t1">7d</button>
</div>
</div>
<div style="padding:14px 16px 8px;">
<div class="flex items-end justify-between mb-[6px]">
<div>
<div class="font-mono text-[22px] font-semibold tracking-[-0.02em]">14.2k<span class="text-[13px] text-t2 ml-1 font-medium">req/min</span></div>
<div class="text-[11px] text-t2 mt-[2px]">Peak vor 12 min · 16.8k req/min</div>
</div>
<div class="flex gap-[14px] text-[11px] text-t2 font-mono">
<span><i class="inline-block w-2 h-2 rounded-[2px] mr-[5px] align-middle" style="background:#4f8ef7;"></i>Requests</span>
<span><i class="inline-block w-2 h-2 rounded-[2px] mr-[5px] align-middle" style="background:#7c5cff;"></i>Cache hit</span>
</div>
</div>
<svg id="traffic-chart" class="w-full block" height="160" viewBox="0 0 600 160" preserveAspectRatio="none">
<defs>
<linearGradient id="grad-req" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stop-color="#4f8ef7" stop-opacity="0.35"/>
<stop offset="100%" stop-color="#4f8ef7" stop-opacity="0"/>
</linearGradient>
<linearGradient id="grad-cache" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stop-color="#7c5cff" stop-opacity="0.20"/>
<stop offset="100%" stop-color="#7c5cff" stop-opacity="0"/>
</linearGradient>
</defs>
<g stroke="rgba(255,255,255,0.04)" stroke-width="1">
<line x1="0" y1="30" x2="600" y2="30"/>
<line x1="0" y1="65" x2="600" y2="65"/>
<line x1="0" y1="100" x2="600" y2="100"/>
<line x1="0" y1="135" x2="600" y2="135"/>
</g>
<text class="clu-chart-axis" x="4" y="34">20k</text>
<text class="clu-chart-axis" x="4" y="69">15k</text>
<text class="clu-chart-axis" x="4" y="104">10k</text>
<text class="clu-chart-axis" x="4" y="139">5k</text>
<path id="cache-area" fill="url(#grad-cache)"/>
<path id="cache-line" stroke="#7c5cff" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<path id="req-area" fill="url(#grad-req)"/>
<path id="req-line" stroke="#4f8ef7" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<circle id="live-dot" cx="0" cy="0" r="3.5" fill="#4f8ef7" stroke="#0a0a0f" stroke-width="2"/>
<text class="clu-chart-axis" x="40" y="156">-60m</text>
<text class="clu-chart-axis" x="180" y="156">-45m</text>
<text class="clu-chart-axis" x="320" y="156">-30m</text>
<text class="clu-chart-axis" x="460" y="156">-15m</text>
<text class="clu-chart-axis" x="568" y="156">jetzt</text>
</svg>
</div>
</section>
{{-- Update pipeline --}}
<section class="bg-s2 border border-white/6 rounded-[10px] overflow-hidden">
<div class="flex items-center justify-between border-b border-white/6" style="padding:13px 16px 12px;">
<h3 class="m-0 text-[13px] font-semibold tracking-[-0.005em] flex items-center gap-2">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21 12a9 9 0 1 1-3-6.7M21 4v5h-5"/></svg>
Update-Pipeline
</h3>
<span class="text-[11.5px] text-t2 font-mono">5 aktiv · 19 wartend</span>
</div>
<div id="pipe-body" class="flex flex-col"></div>
</section>
{{-- Activity feed --}}
<section class="bg-s2 border border-white/6 rounded-[10px] overflow-hidden">
<div class="flex items-center justify-between border-b border-white/6" style="padding:13px 16px 12px;">
<h3 class="m-0 text-[13px] font-semibold tracking-[-0.005em] flex items-center gap-2">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
Live Activity
<span class="inline-flex items-center gap-[5px] font-mono text-[10.5px] text-success font-medium ml-[2px]">
<span class="clu-pulse-dot w-[6px] h-[6px] rounded-full bg-current"></span>live
</span>
</h3>
<span class="text-[11.5px] text-t2 font-mono">letzte 30 min</span>
</div>
<div id="feed-body" class="py-[6px]"></div>
</section>
</div>
<div class="text-center text-dim text-[11px] font-mono tracking-[.04em] pb-5">
CluPilot · region: eu-central-1 · build #4f8a32c · uptime 99.984%
</div>
</div>
</main>
</div>
<script>
/* ── Icon helpers ── */
const IC = {
terminal: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 17l5-5-5-5M13 19h7"/></svg>',
restart: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-3-6.7M21 4v5h-5"/></svg>',
more: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="5" cy="12" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/></svg>',
ext: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 3h7v7M21 3l-9 9M19 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h5"/></svg>',
};
/* ── pill helper ── */
function pill(cls, label) {
const map = {
success: 'color:#3ecf8e;background:rgba(62,207,142,0.12);border-color:rgba(62,207,142,0.22);',
warning: 'color:#f5a623;background:rgba(245,166,35,0.12);border-color:rgba(245,166,35,0.22);',
danger: 'color:#f05252;background:rgba(240,82,82,0.12);border-color:rgba(240,82,82,0.22);',
accent: 'color:#4f8ef7;background:rgba(79,142,247,0.12);border-color:rgba(79,142,247,0.22);',
muted: 'color:#8888a0;background:#1a1a24;border-color:rgba(255,255,255,0.06);',
default: 'color:#f0f0f5;background:#1a1a24;border-color:rgba(255,255,255,0.06);',
};
return `<span style="display:inline-flex;align-items:center;gap:5px;padding:2px 7px;font-size:10.5px;font-weight:500;border-radius:4px;font-family:var(--font-mono);border:1px solid;line-height:1.4;${map[cls]||map.default}">${label}</span>`;
}
/* ── res-bar helper ── */
function resBar(label, pct) {
const color = pct < 60 ? '#3ecf8e' : pct < 80 ? '#f5a623' : '#f05252';
return `<div style="display:flex;flex-direction:column;gap:3px;min-width:90px;">
<div style="display:flex;justify-content:space-between;font-size:10.5px;color:#8888a0;font-family:var(--font-mono);">
<span>${label}</span><span style="color:#f0f0f5;">${pct}%</span>
</div>
<div style="height:4px;background:#1d1d28;border-radius:2px;overflow:hidden;">
<div style="height:100%;width:${pct}%;background:${color};border-radius:2px;transition:width .3s;"></div>
</div>
</div>`;
}
/* ── SERVERS ── */
const servers = [
{name:'eu-prod-01', ip:'185.243.117.42', sites:23, cpu:42, ram:58, disk:71, loc:'Frankfurt 🇩🇪', flag:'FRA', uptime:'127d 14h', ok:true, host:'Hetzner · CX42'},
{name:'eu-prod-02', ip:'185.243.117.43', sites:19, cpu:67, ram:74, disk:55, loc:'Frankfurt 🇩🇪', flag:'FRA', uptime:'127d 14h', ok:true, host:'Hetzner · CX42'},
{name:'eu-edge-amsterdam', ip:'78.46.201.18', sites:14, cpu:31, ram:44, disk:38, loc:'Amsterdam 🇳🇱', flag:'AMS', uptime:'89d 02h', ok:true, host:'Hetzner · CX32'},
{name:'us-east-prod-01', ip:'5.161.42.198', sites:21, cpu:78, ram:81, disk:62, loc:'Ashburn 🇺🇸', flag:'IAD', uptime:'214d 06h', ok:false, host:'Hetzner · CCX23'},
{name:'eu-staging', ip:'168.119.84.221', sites:6, cpu:18, ram:29, disk:22, loc:'Nürnberg 🇩🇪', flag:'NBG', uptime:'43d 11h', ok:true, host:'Hetzner · CX22'},
{name:'apac-sydney-01', ip:'209.97.165.88', sites:4, cpu:54, ram:51, disk:84, loc:'Sydney 🇦🇺', flag:'SYD', uptime:'62d 19h', ok:false, host:'DigitalOcean'},
];
const serverBody = document.getElementById('server-body');
servers.forEach(s => {
const stateIco = s.ok
? '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12l5 5L20 7"/></svg>'
: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 9v4M12 17h.01"/><circle cx="12" cy="12" r="9"/></svg>';
const iconStyle = s.ok
? 'color:#3ecf8e;background:rgba(62,207,142,0.06);border-color:rgba(62,207,142,0.18);'
: 'color:#f5a623;background:rgba(245,166,35,0.06);border-color:rgba(245,166,35,0.18);';
const tr = document.createElement('tr');
tr.style.cssText = 'transition:background .12s;';
tr.onmouseenter = () => tr.style.background = '#1a1a24';
tr.onmouseleave = () => tr.style.background = '';
tr.innerHTML = `
<td style="padding:11px 14px;border-bottom:1px solid rgba(255,255,255,.06);vertical-align:middle;">
<div style="display:flex;align-items:center;gap:10px;">
<div style="width:30px;height:30px;border-radius:7px;border:1px solid;display:grid;place-items:center;flex:none;${iconStyle}">${stateIco}</div>
<div>
<div style="font-weight:500;color:#f0f0f5;font-size:12.5px;">${s.name}</div>
<div style="font-family:var(--font-mono);font-size:10.5px;color:#8888a0;">${s.ip} · ${s.host}</div>
</div>
</div>
</td>
<td style="padding:11px 14px;border-bottom:1px solid rgba(255,255,255,.06);vertical-align:middle;">${pill('default', s.sites + ' sites')}</td>
<td style="padding:11px 14px;border-bottom:1px solid rgba(255,255,255,.06);vertical-align:middle;">
<div style="display:grid;grid-template-columns:repeat(3,minmax(80px,1fr));gap:10px;min-width:300px;">
${resBar('CPU', s.cpu)}${resBar('RAM', s.ram)}${resBar('Disk', s.disk)}
</div>
</td>
<td style="padding:11px 14px;border-bottom:1px solid rgba(255,255,255,.06);vertical-align:middle;">
<span style="color:#f0f0f5;">${s.loc.replace(/(.*) (.*)/, '$1')}</span>
${pill('muted', s.flag)}
</td>
<td style="padding:11px 14px;border-bottom:1px solid rgba(255,255,255,.06);vertical-align:middle;text-align:right;font-family:var(--font-mono);">
<span style="color:#f0f0f5;">${s.uptime}</span>
</td>
<td style="padding:11px 14px;border-bottom:1px solid rgba(255,255,255,.06);vertical-align:middle;text-align:right;">
<div class="clu-row-actions">
${['SSH-Terminal','Neustart','Öffnen','Mehr'].map((t,i)=>`<button title="${t}" style="width:26px;height:26px;border-radius:5px;color:#8888a0;display:grid;place-items:center;transition:all .15s;cursor:pointer;" onmouseenter="this.style.color='#f0f0f5';this.style.background='#22222e'" onmouseleave="this.style.color='#8888a0';this.style.background=''">${[IC.terminal,IC.restart,IC.ext,IC.more][i]}</button>`).join('')}
</div>
</td>`;
serverBody.appendChild(tr);
});
/* ── SITES ── */
const palette = ['#4f8ef7','#3ecf8e','#f5a623','#7c5cff','#f05252','#22c1d6','#e879f9','#facc15'];
const sites = [
{url:'kunde-mueller.de', srv:'eu-prod-01', wp:'6.5.2', wpOk:true, php:'8.2', vis:'12.4k', upd:0, bk:'ok', grade:'A'},
{url:'shop.bergmann-coffee.com', srv:'eu-prod-01', wp:'6.4.3', wpOk:false, php:'8.1', vis:'48.7k', upd:3, bk:'ok', grade:'A'},
{url:'blog.fintech-radar.io', srv:'eu-prod-02', wp:'6.5.2', wpOk:true, php:'8.3', vis:'9.1k', upd:1, bk:'ok', grade:'B'},
{url:'praxis-dr-schwarz.de', srv:'eu-prod-02', wp:'6.2.1', wpOk:false, php:'7.4', vis:'1.2k', upd:7, bk:'warn', grade:'D'},
{url:'agentur-helio.com', srv:'eu-edge-amsterdam', wp:'6.5.2', wpOk:true, php:'8.2', vis:'18.2k', upd:0, bk:'ok', grade:'A'},
{url:'docs.opencrate.dev', srv:'us-east-prod-01', wp:'6.5.1', wpOk:true, php:'8.3', vis:'7.8k', upd:2, bk:'ok', grade:'B'},
{url:'staging.opencrate.dev', srv:'eu-staging', wp:'6.6-RC1',wpOk:true, php:'8.3', vis:'82', upd:0, bk:'ok', grade:'B'},
{url:'classic.tonhoff-bau.de', srv:'us-east-prod-01', wp:'5.9.7', wpOk:false, php:'7.4', vis:'3.4k', upd:11, bk:'err', grade:'F'},
];
const sitesBody = document.getElementById('sites-body');
const gradeStyle = {
A:'color:#3ecf8e;background:rgba(62,207,142,0.15);box-shadow:0 0 0 1px rgba(62,207,142,0.25) inset;',
B:'color:#4f8ef7;background:rgba(79,142,247,0.15);box-shadow:0 0 0 1px rgba(79,142,247,0.25) inset;',
C:'color:#f5a623;background:rgba(245,166,35,0.15);box-shadow:0 0 0 1px rgba(245,166,35,0.25) inset;',
D:'color:#f5a623;background:rgba(245,166,35,0.18);box-shadow:0 0 0 1px rgba(245,166,35,0.32) inset;',
F:'color:#f05252;background:rgba(240,82,82,0.15);box-shadow:0 0 0 1px rgba(240,82,82,0.28) inset;',
};
const bkMap = {
ok: {cls:'success', label:'vor 2 h'},
warn: {cls:'warning', label:'vor 18 h'},
err: {cls:'danger', label:'fehlgeschl.'},
};
sites.forEach((s, i) => {
const color = palette[i % palette.length];
const updCls = s.upd === 0 ? 'success' : s.upd >= 5 ? 'danger' : 'warning';
const updLbl = s.upd === 0 ? 'aktuell' : s.upd + ' verfügbar';
const bk = bkMap[s.bk];
const tr = document.createElement('tr');
tr.style.cssText = 'transition:background .12s;';
tr.onmouseenter = () => tr.style.background = '#1a1a24';
tr.onmouseleave = () => tr.style.background = '';
tr.innerHTML = `
<td style="padding:11px 14px;border-bottom:1px solid rgba(255,255,255,.06);vertical-align:middle;">
<div style="display:flex;align-items:center;gap:10px;min-width:0;">
<div style="width:24px;height:24px;border-radius:5px;display:grid;place-items:center;font-weight:600;font-size:11px;color:#0a0a0f;flex:none;font-family:var(--font-mono);background:${color};">${s.url[0].toUpperCase()}</div>
<div style="min-width:0;">
<div style="font-weight:500;color:#f0f0f5;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:200px;">${s.url}</div>
<div style="font-family:var(--font-mono);font-size:10.5px;color:#8888a0;">id_${(7300+i).toString(16)} · ssl ✓</div>
</div>
</div>
</td>
<td style="padding:11px 14px;border-bottom:1px solid rgba(255,255,255,.06);vertical-align:middle;">${pill('muted', s.srv)}</td>
<td style="padding:11px 14px;border-bottom:1px solid rgba(255,255,255,.06);vertical-align:middle;">${pill(s.wpOk?'success':'warning', 'WP '+s.wp)}</td>
<td style="padding:11px 14px;border-bottom:1px solid rgba(255,255,255,.06);vertical-align:middle;">${pill(parseFloat(s.php)<8?'warning':'muted', 'PHP '+s.php)}</td>
<td style="padding:11px 14px;border-bottom:1px solid rgba(255,255,255,.06);vertical-align:middle;text-align:right;font-family:var(--font-mono);color:#f0f0f5;">${s.vis}</td>
<td style="padding:11px 14px;border-bottom:1px solid rgba(255,255,255,.06);vertical-align:middle;">${pill(updCls, updLbl)}</td>
<td style="padding:11px 14px;border-bottom:1px solid rgba(255,255,255,.06);vertical-align:middle;">${pill(bk.cls, bk.label)}</td>
<td style="padding:11px 14px;border-bottom:1px solid rgba(255,255,255,.06);vertical-align:middle;">
<div style="display:inline-grid;place-items:center;width:24px;height:24px;border-radius:6px;font-family:var(--font-mono);font-weight:600;font-size:12px;line-height:1;${gradeStyle[s.grade]}">${s.grade}</div>
</td>
<td style="padding:11px 14px;border-bottom:1px solid rgba(255,255,255,.06);vertical-align:middle;text-align:right;">
<div class="clu-row-actions">
${['Site öffnen','WP-Admin','Mehr'].map((t,j)=>`<button title="${t}" style="width:26px;height:26px;border-radius:5px;color:#8888a0;display:grid;place-items:center;transition:all .15s;cursor:pointer;" onmouseenter="this.style.color='#f0f0f5';this.style.background='#22222e'" onmouseleave="this.style.color='#8888a0';this.style.background=''">${[IC.ext,IC.terminal,IC.more][j]}</button>`).join('')}
</div>
</td>`;
sitesBody.appendChild(tr);
});
/* ── PIPELINE ── */
const pipeItems = [
{type:'WP', cls:'wp', title:'WordPress 6.5.2 → 6.5.3', sub:'agentur-helio.com', pct:78, status:'läuft', sCls:'accent'},
{type:'PL', cls:'pl', title:'WooCommerce 8.7 → 8.8.1', sub:'shop.bergmann-coffee.com', pct:42, status:'läuft', sCls:'accent'},
{type:'PL', cls:'pl', title:'Yoast SEO 22.4 → 22.5', sub:'blog.fintech-radar.io', pct:100, status:'erledigt', sCls:'success'},
{type:'PHP',cls:'php', title:'PHP 7.4 → 8.2 Migration', sub:'praxis-dr-schwarz.de', pct:12, status:'review', sCls:'warning'},
{type:'WP', cls:'wp', title:'WordPress 5.9 → 6.5.2', sub:'classic.tonhoff-bau.de', pct:0, status:'fehler', sCls:'danger'},
{type:'PL', cls:'pl', title:'Wordfence 7.11 → 7.12.0', sub:'+ 14 sites batch', pct:0, status:'wartet', sCls:'muted'},
];
const icoClsMap = {
wp: 'color:#21759b;border-color:rgba(33,117,155,0.3);background:rgba(33,117,155,0.08);',
pl: 'color:#4f8ef7;border-color:rgba(79,142,247,0.25);background:rgba(79,142,247,0.12);',
php: 'color:#7c5cff;border-color:rgba(124,92,255,0.28);background:rgba(124,92,255,0.10);',
};
const barColor = {accent:'#4f8ef7', success:'#3ecf8e', warning:'#f5a623', danger:'#f05252'};
const pipeBody = document.getElementById('pipe-body');
pipeItems.forEach(p => {
const div = document.createElement('div');
div.style.cssText = 'display:flex;align-items:center;gap:11px;padding:10px 16px;border-bottom:1px solid rgba(255,255,255,.06);transition:background .12s;cursor:default;';
div.onmouseenter = () => div.style.background = '#1a1a24';
div.onmouseleave = () => div.style.background = '';
div.innerHTML = `
<div style="width:26px;height:26px;border-radius:6px;display:grid;place-items:center;flex:none;border:1px solid;font-size:10.5px;font-family:var(--font-mono);font-weight:600;${icoClsMap[p.cls]}">${p.type}</div>
<div style="flex:1;min-width:0;">
<div style="font-size:12.5px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${p.title}</div>
<div style="font-size:11px;color:#8888a0;font-family:var(--font-mono);">${p.sub}</div>
</div>
<div style="display:flex;flex-direction:column;align-items:flex-end;gap:4px;">
${pill(p.sCls, (p.pct>0&&p.pct<100?p.pct+'% ':'')+p.status)}
${p.pct>0?`<div style="width:60px;height:4px;background:#1d1d28;border-radius:2px;overflow:hidden;"><div style="height:100%;width:${p.pct}%;background:${barColor[p.sCls]||'#4f8ef7'};border-radius:2px;"></div></div>`:''}
</div>`;
pipeBody.appendChild(div);
});
/* ── ACTIVITY FEED ── */
const feedItems = [
{ic:'success', title:'Backup erstellt für', target:'agentur-helio.com', meta:'vor 12 sek · 248 MB · eu-prod-02',
icon:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M5 12l5 5L20 7"/></svg>'},
{ic:'danger', title:'17 Login-Versuche blockiert auf', target:'classic.tonhoff-bau.de', meta:'vor 1 min · IP 185.220.101.18 · TOR exit',
icon:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/></svg>'},
{ic:'accent', title:'Plugin-Update Yoast SEO 22.5 auf 3 Sites', target:null, meta:'vor 4 min · ausgeführt von m.weber',
icon:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M21 12a9 9 0 1 1-3-6.7M21 4v5h-5"/></svg>'},
{ic:'warning', title:'CPU > 80% auf', target:'us-east-prod-01', meta:'vor 7 min · sustained 4m 12s',
icon:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M12 9v4M12 17h.01"/><circle cx="12" cy="12" r="9"/></svg>'},
{ic:'success', title:'SSL-Zertifikat erneuert ·', target:'*.kunde-mueller.de', meta:'vor 14 min · gültig bis 25.08.2026',
icon:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><rect x="5" y="11" width="14" height="9" rx="1.5"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>'},
{ic:'accent', title:'SSH-Session geöffnet auf', target:'eu-prod-01', meta:'vor 19 min · user j.koch · key id_ed25519_jk',
icon:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M4 17l5-5-5-5M13 19h7"/></svg>'},
{ic:'success', title:'Health-Check OK · 87/87 Sites', target:null, meta:'vor 23 min · run #4421',
icon:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M5 12l5 5L20 7"/></svg>'},
];
const icoStyle = {
success: 'color:#3ecf8e;background:rgba(62,207,142,0.12);border-color:rgba(62,207,142,0.20);',
warning: 'color:#f5a623;background:rgba(245,166,35,0.12);border-color:rgba(245,166,35,0.20);',
danger: 'color:#f05252;background:rgba(240,82,82,0.12);border-color:rgba(240,82,82,0.20);',
accent: 'color:#4f8ef7;background:rgba(79,142,247,0.12);border-color:rgba(79,142,247,0.22);',
};
const feedBody = document.getElementById('feed-body');
feedItems.forEach(f => {
const targetHtml = f.target
? ` <span style="font-family:var(--font-mono);color:#f0f0f5;background:#1d1d28;padding:1px 5px;border-radius:3px;font-size:11px;border:1px solid rgba(255,255,255,0.06);">${f.target}</span>`
: '';
const div = document.createElement('div');
div.style.cssText = 'display:flex;gap:11px;padding:10px 16px;align-items:flex-start;transition:background .12s;cursor:default;';
div.onmouseenter = () => div.style.background = '#1a1a24';
div.onmouseleave = () => div.style.background = '';
div.innerHTML = `
<div style="width:24px;height:24px;border-radius:6px;display:grid;place-items:center;flex:none;border:1px solid;${icoStyle[f.ic]}">${f.icon}</div>
<div style="flex:1;min-width:0;font-size:12px;">
<div style="color:#8888a0;">${f.title}${targetHtml}</div>
<div style="color:#8888a0;font-size:10.5px;margin-top:2px;font-family:var(--font-mono);">${f.meta}</div>
</div>`;
feedBody.appendChild(div);
});
/* ── TRAFFIC CHART ── */
(function(){
const W=600,H=160,padL=30,padR=8,padT=10,padB=28,N=60;
const req=[],cache=[];
let v=11;
for(let i=0;i<N;i++){
v+=(Math.random()-.45)*1.6;
if(i>40&&i<52) v+=Math.sin((i-40)/12*Math.PI)*3.5;
v=Math.max(5,Math.min(19,v));
req.push(v);
cache.push(v*(.55+Math.random()*.15));
}
const xc=i=>padL+(W-padL-padR)*(i/(N-1));
const yc=v=>{const t=v/20;return H-padB-t*(H-padT-padB);};
function pathFor(arr){
let d=`M ${xc(0)} ${yc(arr[0])}`;
for(let i=1;i<arr.length;i++){
const cx=(xc(i-1)+xc(i))/2;
d+=` Q ${xc(i-1)} ${yc(arr[i-1])} ${cx} ${(yc(arr[i-1])+yc(arr[i]))/2}`;
}
return d+` T ${xc(arr.length-1)} ${yc(arr[arr.length-1])}`;
}
function areaFor(arr){return pathFor(arr)+` L ${xc(arr.length-1)} ${H-padB} L ${xc(0)} ${H-padB} Z`;}
document.getElementById('req-line').setAttribute('d',pathFor(req));
document.getElementById('req-area').setAttribute('d',areaFor(req));
document.getElementById('cache-line').setAttribute('d',pathFor(cache));
document.getElementById('cache-area').setAttribute('d',areaFor(cache));
const dot=document.getElementById('live-dot');
dot.setAttribute('cx',xc(N-1));dot.setAttribute('cy',yc(req[N-1]));
let t0=0;
(function tick(){t0+=.04;dot.setAttribute('r',3.5+Math.sin(t0)*.8);requestAnimationFrame(tick);})();
})();
</script>
@endsection