luneraria-control-center/public/dashboard.html
Martin Stickelbroeck 2fce25b07e
Some checks are pending
CI/CD Cloud Deployment / test-and-build (push) Waiting to run
feat: Support col.stickelbroeck.com (Spieleseite) & col-admin.stickelbroeck.com (Schaltzentrale)
2026-10-12 10:16:35 +02:00

939 lines
35 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Champions of Luneraria - Mission Control & LiveOps Schaltzentrale">
<meta name="theme-color" content="#030508">
<title>Schaltzentrale · Champions of Luneraria LiveOps</title>
<!-- Shared Fonts and Styles -->
<link rel="stylesheet" href="assets/css/style.css">
<style>
/* Dashboard Specific Styles */
.dashboard-body {
background-color: #030508;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.dash-topbar {
height: 72px;
background: rgba(6, 10, 18, 0.95);
border-bottom: 1px solid var(--border-glass);
backdrop-filter: blur(20px);
display: flex;
align-items: center;
justify-content: space-between;
padding-inline: clamp(1rem, 3vw, 2.5rem);
position: sticky;
top: 0;
z-index: 100;
}
.dash-brand {
display: flex;
align-items: center;
gap: 14px;
}
.status-pill-live {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 5px 14px;
border-radius: var(--radius-pill);
background: rgba(16, 185, 129, 0.12);
border: 1px solid rgba(16, 185, 129, 0.4);
color: #10b981;
font-size: 0.8rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.pulse-green-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #10b981;
box-shadow: 0 0 10px #10b981;
animation: pulse-dot 1.5s infinite;
}
.dash-nav-tabs {
display: flex;
gap: 6px;
padding: 16px clamp(1rem, 3vw, 2.5rem);
background: #05080e;
border-bottom: 1px solid var(--border-subtle);
overflow-x: auto;
}
.dash-tab-btn {
padding: 10px 20px;
font-family: var(--font-display);
font-size: 0.88rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--text-muted);
border-radius: var(--radius-xs);
background: transparent;
border: 1px solid transparent;
cursor: pointer;
white-space: nowrap;
transition: all 0.2s var(--ease-out);
}
.dash-tab-btn:hover {
color: #fff;
background: rgba(255, 255, 255, 0.04);
}
.dash-tab-btn.is-active {
color: var(--lunar-cyan);
background: rgba(56, 232, 216, 0.12);
border-color: rgba(56, 232, 216, 0.4);
}
.dash-content-area {
flex: 1;
padding: clamp(1.5rem, 3vw, 2.5rem);
max-width: 1480px;
width: 100%;
margin-inline: auto;
}
/* KPI Row */
.kpi-deck {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 20px;
margin-bottom: 2rem;
}
.kpi-card-metric {
background: var(--bg-card);
border: 1px solid var(--border-glass);
border-radius: var(--radius-md);
padding: 22px;
display: flex;
flex-direction: column;
gap: 8px;
position: relative;
overflow: hidden;
}
.kpi-card-metric::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 3px;
background: linear-gradient(90deg, transparent, var(--lunar-cyan), transparent);
}
.kpi-card-title {
font-size: 0.82rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--text-muted);
font-weight: 700;
display: flex;
justify-content: space-between;
align-items: center;
}
.kpi-card-val {
font-family: var(--font-display);
font-size: 2.3rem;
font-weight: 800;
color: #fff;
line-height: 1;
}
.kpi-card-sub {
font-size: 0.8rem;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 6px;
}
.positive-trend {
color: #10b981;
font-weight: 700;
}
/* Panels Grid */
.panel-grid-double {
display: grid;
grid-template-columns: 1.2fr 0.8fr;
gap: 24px;
margin-bottom: 2rem;
}
@media (max-width: 1024px) {
.panel-grid-double {
grid-template-columns: 1fr;
}
}
.dash-box {
background: var(--bg-card);
border: 1px solid var(--border-glass);
border-radius: var(--radius-md);
padding: 26px;
display: flex;
flex-direction: column;
gap: 18px;
}
.dash-box-head {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
padding-bottom: 14px;
}
.dash-box-title {
font-family: var(--font-display);
font-size: 1.15rem;
font-weight: 700;
color: #fff;
}
/* Console Log Stream */
.console-stream {
background: #020306;
border: 1px solid var(--border-subtle);
border-radius: var(--radius-xs);
height: 280px;
overflow-y: auto;
padding: 14px;
font-family: 'SF Mono', Monaco, monospace;
font-size: 0.82rem;
display: flex;
flex-direction: column;
gap: 6px;
}
.log-row {
display: flex;
gap: 10px;
line-height: 1.4;
}
.log-time { color: var(--text-muted); }
.log-tag {
font-weight: 700;
padding: 1px 6px;
border-radius: 3px;
font-size: 0.75rem;
}
.tag-NET { background: rgba(56, 189, 248, 0.2); color: #38bdf8; }
.tag-CORE { background: rgba(168, 85, 247, 0.2); color: #a855f7; }
.tag-MATCH { background: rgba(16, 185, 129, 0.2); color: #10b981; }
.tag-COMBAT { background: rgba(244, 63, 94, 0.2); color: #f43f5e; }
.tag-BOSS { background: rgba(245, 158, 11, 0.2); color: #f59e0b; }
.tag-CONFIG { background: rgba(251, 191, 36, 0.2); color: #fbbf24; }
.log-msg { color: #e2e8f0; }
/* Tables */
.telemetry-table {
width: 100%;
border-collapse: collapse;
font-size: 0.88rem;
}
.telemetry-table th {
text-align: left;
padding: 10px 14px;
color: var(--text-muted);
font-weight: 700;
text-transform: uppercase;
font-size: 0.75rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.telemetry-table td {
padding: 12px 14px;
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
color: var(--text-secondary);
}
.telemetry-table tr:hover td {
background: rgba(255, 255, 255, 0.02);
color: #fff;
}
/* Control Actions Deck */
.controls-deck {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
}
.control-action-btn {
padding: 14px 20px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border-glass);
border-radius: var(--radius-sm);
color: #fff;
font-weight: 700;
font-size: 0.9rem;
display: flex;
flex-direction: column;
gap: 4px;
align-items: flex-start;
cursor: pointer;
transition: all 0.2s var(--ease-out);
}
.control-action-btn:hover {
background: rgba(56, 232, 216, 0.1);
border-color: var(--lunar-cyan);
transform: translateY(-2px);
}
.control-action-sub {
font-size: 0.75rem;
color: var(--text-muted);
font-weight: normal;
}
</style>
</head>
<body class="dashboard-body">
<!-- Top Navigation Bar -->
<header class="dash-topbar" role="banner">
<div class="dash-brand">
<div class="brand-emblem" style="width: 36px; height: 36px;">
<svg class="brand-emblem-svg" viewBox="0 0 42 42" fill="none">
<circle cx="21" cy="21" r="19" stroke="#38e8d8" stroke-width="2" stroke-dasharray="8 4" />
<polygon points="21,5 35,21 21,37 7,21" fill="#08203c" stroke="#38e8d8" stroke-width="1.8" />
<circle cx="21" cy="21" r="5" fill="#38e8d8" />
</svg>
</div>
<div>
<span style="font-family: var(--font-display); font-size: 1.15rem; font-weight: 800; color: #fff; letter-spacing: 0.08em;">
LUNERARIA <span style="color: var(--lunar-cyan);">SCHALTZENTRALE</span>
</span>
<div style="font-size: 0.7rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.12em;">
LiveOps & Telemetry Dashboard
</div>
</div>
</div>
<div style="display: flex; align-items: center; gap: 16px;">
<div class="status-pill-live">
<span class="pulse-green-dot"></span>
<span id="liveStatusText">Live · 20.0 Hz Loop</span>
</div>
<a href="https://col.stickelbroeck.com" target="_blank" rel="noopener" class="btn-cyan-ghost" style="padding: 8px 18px; font-size: 0.8rem; display: inline-flex; align-items: center; gap: 6px;">
<span>Zur Spieleseite</span>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"></polyline></svg>
</a>
</div>
</header>
<!-- Dashboard Navigation Tabs -->
<nav class="dash-nav-tabs" role="tablist">
<button class="dash-tab-btn is-active" data-view="overview" role="tab" aria-selected="true">Übersicht & LiveOps</button>
<button class="dash-tab-btn" data-view="downloads" role="tab" aria-selected="false">Downloads & Distribution</button>
<button class="dash-tab-btn" data-view="benchmarks" role="tab" aria-selected="false">Engine-Benchmarks & Tests</button>
<button class="dash-tab-btn" data-view="balance" role="tab" aria-selected="false">Match-Balance & Einheiten</button>
<button class="dash-tab-btn" data-view="controls" role="tab" aria-selected="false">Server-Steuerung & Konsole</button>
</nav>
<!-- Main Dashboard Workspace -->
<main class="dash-content-area" id="mainDashboardView">
<!-- Global Key Performance Indicator Deck -->
<div class="kpi-deck">
<div class="kpi-card-metric">
<div class="kpi-card-title">
<span>Gleichzeitige Benutzer (CCU)</span>
<span style="color: var(--lunar-cyan);">●</span>
</div>
<div class="kpi-card-val" id="metricCCU">86</div>
<div class="kpi-card-sub">
<span class="positive-trend">↑ 24h Peak: 312</span>
<span>· 3 Server-Regionen</span>
</div>
</div>
<div class="kpi-card-metric">
<div class="kpi-card-title">
<span>Laufende Partien (Matches)</span>
<span style="color: #38bdf8;">3v3</span>
</div>
<div class="kpi-card-val" id="metricMatches">14</div>
<div class="kpi-card-sub">
<span>Ø 11,2 Wellen</span>
<span>· ~17,4 Min. Dauer</span>
</div>
</div>
<div class="kpi-card-metric">
<div class="kpi-card-title">
<span>Gesamt-Downloads</span>
<span style="color: var(--ember-bright);">Global</span>
</div>
<div class="kpi-card-val" id="metricDownloads">14.820</div>
<div class="kpi-card-sub">
<span class="positive-trend">+1.040 heute</span>
<span>· Mac / Win / Linux</span>
</div>
</div>
<div class="kpi-card-metric">
<div class="kpi-card-title">
<span>Server-Tickrate & Sim</span>
<span style="color: #10b981;">20 Hz</span>
</div>
<div class="kpi-card-val" id="metricTickrate">20.00 <span style="font-size: 1.1rem; color: var(--text-muted);">Hz</span></div>
<div class="kpi-card-sub">
<span>Tick-Dauer: 0,259 ms</span>
<span>· 0 % Packet Loss</span>
</div>
</div>
</div>
<!-- VIEW 1: Übersicht & LiveOps -->
<div id="viewOverview" class="dash-view-tab">
<div class="panel-grid-double">
<!-- Live Concurrency Chart -->
<div class="dash-box">
<div class="dash-box-head">
<h3 class="dash-box-title">Gleichzeitige Spieler (CCU) & Match-Verlauf</h3>
<span style="font-size: 0.78rem; color: var(--lunar-cyan); font-weight: 700;">LIVE AKTUALISIERT (SSE)</span>
</div>
<!-- SVG CCU Trend Chart -->
<div style="width: 100%; height: 260px; background: #04070d; border-radius: var(--radius-xs); border: 1px solid var(--border-subtle); padding: 14px; position: relative;">
<svg id="ccuSvgChart" viewBox="0 0 600 220" style="width: 100%; height: 100%;" preserveAspectRatio="none">
<!-- Grid lines -->
<line x1="0" y1="50" x2="600" y2="50" stroke="rgba(255,255,255,0.06)" stroke-dasharray="4 4" />
<line x1="0" y1="110" x2="600" y2="110" stroke="rgba(255,255,255,0.06)" stroke-dasharray="4 4" />
<line x1="0" y1="170" x2="600" y2="170" stroke="rgba(255,255,255,0.06)" stroke-dasharray="4 4" />
<!-- Area Fill -->
<path id="ccuAreaPath" d="M 0 160 Q 150 140 300 90 T 600 120 L 600 220 L 0 220 Z" fill="rgba(56, 232, 216, 0.12)" />
<!-- Trend Line -->
<path id="ccuLinePath" d="M 0 160 Q 150 140 300 90 T 600 120" fill="none" stroke="#38e8d8" stroke-width="3" stroke-linecap="round" />
</svg>
<div style="display: flex; justify-content: space-between; font-size: 0.75rem; color: var(--text-muted); margin-top: 6px;">
<span>Vor 12 Std (45 CCU)</span>
<span>Vor 6 Std (120 CCU)</span>
<span>Aktuell (<span id="chartLiveLabel">86 CCU</span>)</span>
</div>
</div>
<!-- Regional Infrastructure Status -->
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-top: 6px;">
<div style="background: rgba(255,255,255,0.02); padding: 12px; border-radius: var(--radius-xs); border: 1px solid var(--border-subtle);">
<div style="font-size: 0.75rem; color: var(--text-muted);">EU-Central (Frankfurt)</div>
<div style="font-weight: 700; color: #fff; margin-top: 2px;">52 % Anteil · 14 ms</div>
</div>
<div style="background: rgba(255,255,255,0.02); padding: 12px; border-radius: var(--radius-xs); border: 1px solid var(--border-subtle);">
<div style="font-size: 0.75rem; color: var(--text-muted);">US-East (N. Virginia)</div>
<div style="font-weight: 700; color: #fff; margin-top: 2px;">36 % Anteil · 42 ms</div>
</div>
<div style="background: rgba(255,255,255,0.02); padding: 12px; border-radius: var(--radius-xs); border: 1px solid var(--border-subtle);">
<div style="font-size: 0.75rem; color: var(--text-muted);">AP-Northeast (Tokyo)</div>
<div style="font-weight: 700; color: #fff; margin-top: 2px;">12 % Anteil · 110 ms</div>
</div>
</div>
</div>
<!-- Live Server Log Console -->
<div class="dash-box">
<div class="dash-box-head">
<h3 class="dash-box-title">Live Server Event Stream</h3>
<span style="font-size: 0.78rem; color: var(--text-muted);">TCP Port 24571</span>
</div>
<div class="console-stream" id="liveConsoleStream"></div>
</div>
</div>
</div>
<!-- VIEW 2: Downloads & Distribution -->
<div id="viewDownloads" class="dash-view-tab" style="display: none;">
<div class="panel-grid-double">
<!-- Download Trend Chart -->
<div class="dash-box">
<div class="dash-box-head">
<h3 class="dash-box-title">Download-Verlauf (Letzte 14 Tage)</h3>
<span style="font-size: 0.8rem; color: var(--ember-bright); font-weight: 700;">KUMULIERT: 14.820</span>
</div>
<div id="downloadsBarContainer" style="display: flex; align-items: flex-end; gap: 10px; height: 260px; padding-top: 20px; border-bottom: 1px solid rgba(255,255,255,0.08);">
<!-- Bars rendered via JS -->
</div>
<div style="display: flex; justify-content: space-between; font-size: 0.75rem; color: var(--text-muted);">
<span>28. September (Start)</span>
<span>11. Oktober (Heute)</span>
</div>
</div>
<!-- Platform Distribution Breakdown -->
<div class="dash-box">
<div class="dash-box-head">
<h3 class="dash-box-title">Plattform- & Versionsverteilung</h3>
<span style="font-size: 0.8rem; color: var(--text-muted);">Build Manifest</span>
</div>
<table class="telemetry-table">
<thead>
<tr>
<th>Plattform</th>
<th>Paket</th>
<th>Anteil</th>
<th>Downloads</th>
</tr>
</thead>
<tbody>
<tr>
<td style="color: #fff; font-weight: 700;">macOS</td>
<td>Universal (Apple Silicon/x64)</td>
<td>40 %</td>
<td>5.928</td>
</tr>
<tr>
<td style="color: #fff; font-weight: 700;">Windows</td>
<td>x64 Native</td>
<td>42 %</td>
<td>6.224</td>
</tr>
<tr>
<td style="color: #fff; font-weight: 700;">Windows</td>
<td>ARM64 Native</td>
<td>7 %</td>
<td>1.038</td>
</tr>
<tr>
<td style="color: #fff; font-weight: 700;">Linux</td>
<td>Fedora 44 / Ubuntu x64</td>
<td>11 %</td>
<td>1.630</td>
</tr>
</tbody>
</table>
<div style="background: rgba(255,255,255,0.02); padding: 14px; border-radius: var(--radius-xs); border: 1px solid var(--border-subtle); font-size: 0.82rem; color: var(--text-secondary);">
<strong style="color: #fff;">Aktuellste Version im Umlauf:</strong> 0.17.0-g2 (Unity U5.5 Preview).
64 % aller aktiven Installationen nutzen bereits den neuen Unity 6 URP Client.
</div>
</div>
</div>
</div>
<!-- VIEW 3: Engine-Benchmarks & Tests -->
<div id="viewBenchmarks" class="dash-view-tab" style="display: none;">
<div class="panel-grid-double">
<!-- Simulation Speedup Comparison -->
<div class="dash-box">
<div class="dash-box-head">
<h3 class="dash-box-title">Simulationsschritt-Benchmark (120 Einheiten)</h3>
<span style="color: var(--lunar-cyan); font-weight: 700; font-size: 0.85rem;">6.1× SCHNELLER</span>
</div>
<div style="display: flex; flex-direction: column; gap: 18px; margin-top: 10px;">
<div>
<div style="display: flex; justify-content: space-between; font-size: 0.85rem; margin-bottom: 6px;">
<span style="color: var(--lunar-cyan); font-weight: 700;">.NET 8 C# Regelkern (Luneraria.Core)</span>
<span style="font-weight: 800; color: #fff;">0,259 ms / Tick</span>
</div>
<div style="height: 12px; background: rgba(255,255,255,0.06); border-radius: 6px; overflow: hidden;">
<div style="width: 16%; height: 100%; background: var(--lunar-cyan); box-shadow: 0 0 10px var(--lunar-cyan);"></div>
</div>
</div>
<div>
<div style="display: flex; justify-content: space-between; font-size: 0.85rem; margin-bottom: 6px;">
<span style="color: #38bdf8;">Unity Mono Editor (6000.3.20f1)</span>
<span style="font-weight: 700; color: #fff;">0,510 ms / Tick</span>
</div>
<div style="height: 12px; background: rgba(255,255,255,0.06); border-radius: 6px; overflow: hidden;">
<div style="width: 32%; height: 100%; background: #38bdf8;"></div>
</div>
</div>
<div>
<div style="display: flex; justify-content: space-between; font-size: 0.85rem; margin-bottom: 6px;">
<span style="color: var(--text-muted);">Godot GDScript Referenz (4.7.2)</span>
<span style="font-weight: 700; color: #fff;">1,587 ms / Tick</span>
</div>
<div style="height: 12px; background: rgba(255,255,255,0.06); border-radius: 6px; overflow: hidden;">
<div style="width: 100%; height: 100%; background: #64748b;"></div>
</div>
</div>
</div>
<p style="font-size: 0.85rem; color: var(--text-muted); margin-top: 12px; line-height: 1.6;">
Der C#-Kern rechnet 120 kämpfende Einheiten in <strong>unter 0,3 Millisekunden</strong> durch.
Damit benötigt die gesamte Simulation weniger als 2 % des Budgets eines 20-Hz-Ticks (50 ms).
</p>
</div>
<!-- Frame-Rate Evolution Across Milestones -->
<div class="dash-box">
<div class="dash-box-head">
<h3 class="dash-box-title">Meilenstein-Bildraten (Apple M5, 1080p)</h3>
<span style="color: #10b981; font-weight: 700; font-size: 0.85rem;">STABIL > 100 FPS</span>
</div>
<table class="telemetry-table">
<thead>
<tr>
<th>Phase</th>
<th>Mid-Lane Nah</th>
<th>Ganze Karte</th>
<th>Draw Calls</th>
</tr>
</thead>
<tbody>
<tr>
<td style="color: #fff; font-weight: 700;">U2 (Platzhalter)</td>
<td>199 FPS</td>
<td>161 FPS</td>
<td>1.840</td>
</tr>
<tr>
<td style="color: #fff; font-weight: 700;">U3 (G3-HUD)</td>
<td>218 FPS</td>
<td>169 FPS</td>
<td>1.920</td>
</tr>
<tr>
<td style="color: #fff; font-weight: 700;">U5 (PBR Slice)</td>
<td>182 FPS</td>
<td>132 FPS</td>
<td>2.450</td>
</tr>
<tr>
<td style="color: #fff; font-weight: 700;">U5.4 (SSAO)</td>
<td>148 FPS</td>
<td>112 FPS</td>
<td>2.680</td>
</tr>
<tr>
<td style="color: var(--lunar-cyan); font-weight: 700;">U5.5 (Meshy Heer)</td>
<td style="color: var(--lunar-cyan); font-weight: 700;">156 FPS</td>
<td style="color: var(--lunar-cyan); font-weight: 700;">126 FPS</td>
<td>1.760</td>
</tr>
</tbody>
</table>
<div style="background: rgba(56, 232, 216, 0.08); border: 1px solid rgba(56, 232, 216, 0.25); border-radius: var(--radius-xs); padding: 12px; font-size: 0.82rem; color: #fff;">
<strong>GPU-Batched-Skinning:</strong> Durch die U5.5 Optimierung sank die CPU-Hauptthread-Belastung um <strong>27 %</strong> und Draw Calls reduzierten sich dank Instancing um <strong>34 %</strong>.
</div>
</div>
</div>
</div>
<!-- VIEW 4: Match-Balance & Einheiten -->
<div id="viewBalance" class="dash-view-tab" style="display: none;">
<div class="panel-grid-double">
<!-- Spiegelsymmetrie & Winrate -->
<div class="dash-box">
<div class="dash-box-head">
<h3 class="dash-box-title">Spiegelsymmetrie & Siegesbalance</h3>
<span style="color: #10b981; font-weight: 700;">PARITÄT: 50.2 % / 49.8 %</span>
</div>
<div style="display: flex; align-items: center; justify-content: space-around; padding-block: 20px;">
<div style="text-align: center;">
<div style="font-family: var(--font-display); font-size: 2.4rem; font-weight: 800; color: #38bdf8;">50,2 %</div>
<div style="font-size: 0.8rem; text-transform: uppercase; color: var(--text-muted); font-weight: 700;">Team Blau (West)</div>
</div>
<div style="font-family: var(--font-display); font-size: 1.5rem; color: var(--text-dim);">VS</div>
<div style="text-align: center;">
<div style="font-family: var(--font-display); font-size: 2.4rem; font-weight: 800; color: #f43f5e;">49,8 %</div>
<div style="font-size: 0.8rem; text-transform: uppercase; color: var(--text-muted); font-weight: 700;">Team Rot (Ost)</div>
</div>
</div>
<table class="telemetry-table">
<thead>
<tr>
<th>Metrik</th>
<th>Mittelwert</th>
<th>Befund</th>
</tr>
</thead>
<tbody>
<tr>
<td style="color: #fff; font-weight: 700;">Erster Turmfall</td>
<td>Welle 4,2</td>
<td>64 % Mid · 21 % Top · 15 % Bot</td>
</tr>
<tr>
<td style="color: #fff; font-weight: 700;">Boss-Expeditionen</td>
<td>1,8 pro Match</td>
<td>72,4 % Erfolgsquote bei Einnahme</td>
</tr>
<tr>
<td style="color: #fff; font-weight: 700;">Sieg nach Boss-Entsendung</td>
<td>68,1 %</td>
<td>Starker strategischer Hebel</td>
</tr>
</tbody>
</table>
</div>
<!-- Champion Analytics -->
<div class="dash-box">
<div class="dash-box-head">
<h3 class="dash-box-title">Champion-Performance</h3>
<span style="font-size: 0.8rem; color: var(--text-muted);">Aus 400 Partien</span>
</div>
<table class="telemetry-table">
<thead>
<tr>
<th>Champion</th>
<th>Rolle</th>
<th>Pick-Rate</th>
<th>Win-Rate</th>
<th>KDA</th>
</tr>
</thead>
<tbody>
<tr>
<td style="color: #fff; font-weight: 700;">Sylvara</td>
<td>Assassine</td>
<td>36 %</td>
<td style="color: #10b981; font-weight: 700;">52,4 %</td>
<td>4,2</td>
</tr>
<tr>
<td style="color: #fff; font-weight: 700;">Brunhild</td>
<td>Front / Stun</td>
<td>34 %</td>
<td style="color: #10b981; font-weight: 700;">51,1 %</td>
<td>3,8</td>
</tr>
<tr>
<td style="color: #fff; font-weight: 700;">Funkenlanze</td>
<td>Elite / Lanze</td>
<td>30 %</td>
<td>48,9 %</td>
<td>3,5</td>
</tr>
</tbody>
</table>
<div style="background: rgba(255,255,255,0.02); border-radius: var(--radius-xs); padding: 12px; font-size: 0.82rem; color: var(--text-muted);">
Alle drei Helden liegen im optimalen Balance-Band zwischen <strong>48 % und 53 %</strong> Siegesrate.
Kein Held dominiert das Metaspiel unverhältnismäßig.
</div>
</div>
</div>
</div>
<!-- VIEW 5: Server-Steuerung & Konsole -->
<div id="viewControls" class="dash-view-tab" style="display: none;">
<div class="panel-grid-double">
<!-- Interactive Control Operations -->
<div class="dash-box">
<div class="dash-box-head">
<h3 class="dash-box-title">Server-Administration & Matchmaking</h3>
<span style="color: var(--ember-bright); font-weight: 700; font-size: 0.8rem;">AUTORITATIVE STEUERUNG</span>
</div>
<div class="controls-deck">
<button class="control-action-btn" id="btnToggleMaintenance">
<span>Wartungsmodus</span>
<span class="control-action-sub" id="subMaintenance">Aktuell: Deaktiviert</span>
</button>
<button class="control-action-btn" id="btnCycleTickrate">
<span>Tickrate Wechseln</span>
<span class="control-action-sub" id="subTickrate">Aktuell: 20 Hz</span>
</button>
<button class="control-action-btn" id="btnSimulateSpike">
<span>Traffic-Spike testen</span>
<span class="control-action-sub">+25 Spieler simulieren</span>
</button>
<button class="control-action-btn" id="btnBroadcastMsg">
<span>System-Broadcast</span>
<span class="control-action-sub">Ankündigung senden</span>
</button>
</div>
<div style="background: rgba(255,255,255,0.02); padding: 16px; border-radius: var(--radius-xs); border: 1px solid var(--border-subtle); margin-top: 10px;">
<h4 style="font-size: 0.9rem; color: #fff; margin-bottom: 6px;">Cloud-Deploy Befehle:</h4>
<code style="display: block; background: #020306; padding: 10px; border-radius: 4px; font-size: 0.8rem; color: var(--lunar-cyan);">
docker build -t luneraria-control-center .<br>
docker run -p 8080:8080 -d luneraria-control-center
</code>
</div>
</div>
<!-- Full Console Screen -->
<div class="dash-box">
<div class="dash-box-head">
<h3 class="dash-box-title">System-Konsole</h3>
<span style="font-size: 0.78rem; color: var(--text-muted);">Echtzeit-Log</span>
</div>
<div class="console-stream" id="fullConsoleStream" style="height: 340px;"></div>
</div>
</div>
</div>
</main>
<!-- Dashboard Script -->
<script>
// Tab Switching
const tabs = document.querySelectorAll('.dash-tab-btn');
const views = {
overview: document.getElementById('viewOverview'),
downloads: document.getElementById('viewDownloads'),
benchmarks: document.getElementById('viewBenchmarks'),
balance: document.getElementById('viewBalance'),
controls: document.getElementById('viewControls')
};
tabs.forEach(btn => {
btn.addEventListener('click', () => {
tabs.forEach(t => t.classList.remove('is-active'));
btn.classList.add('is-active');
const target = btn.dataset.view;
Object.keys(views).forEach(k => {
if (views[k]) views[k].style.display = (k === target) ? 'block' : 'none';
});
if (target === 'downloads') renderDownloadsChart();
});
});
// Populate Console Stream
function updateLogs(logs) {
const stream = document.getElementById('liveConsoleStream');
const fullStream = document.getElementById('fullConsoleStream');
const html = logs.map(l => `
<div class="log-row">
<span class="log-time">[${l.time}]</span>
<span class="log-tag tag-${l.tag}">${l.tag}</span>
<span class="log-msg">${l.msg}</span>
</div>
`).join('');
if (stream) stream.innerHTML = html;
if (fullStream) fullStream.innerHTML = html;
}
// Render Download Bars
function renderDownloadsChart() {
fetch('/api/downloads/history')
.then(r => r.json())
.then(data => {
const container = document.getElementById('downloadsBarContainer');
if (!container) return;
const max = Math.max(...data.map(d => d.mac + d.win + d.linux));
container.innerHTML = data.map(d => {
const total = d.mac + d.win + d.linux;
const h = Math.round((total / max) * 220);
return `
<div style="flex: 1; display: flex; flex-direction: column; align-items: center; height: 100%; justify-content: flex-end;" title="${d.date}: ${total} Downloads (Win: ${d.win}, Mac: ${d.mac}, Linux: ${d.linux})">
<div style="width: 100%; height: ${h}px; background: linear-gradient(180deg, var(--ember-bright), #ea580c); border-radius: 3px 3px 0 0; transition: height 0.4s var(--ease-out);"></div>
<span style="font-size: 0.65rem; color: var(--text-muted); margin-top: 6px; transform: rotate(-45deg);">${d.date.split('.')[0]}</span>
</div>
`;
}).join('');
})
.catch(() => {});
}
// Apply Live Metrics to UI
function applyMetrics(data) {
const ccuEl = document.getElementById('metricCCU');
const matchesEl = document.getElementById('metricMatches');
const dlEl = document.getElementById('metricDownloads');
const tickEl = document.getElementById('metricTickrate');
const chartLabel = document.getElementById('chartLiveLabel');
if (ccuEl) ccuEl.textContent = data.ccu;
if (chartLabel) chartLabel.textContent = `${data.ccu} CCU`;
if (matchesEl) matchesEl.textContent = data.activeMatches;
if (dlEl) dlEl.textContent = data.totalDownloads.toLocaleString('de-DE');
if (tickEl) tickEl.innerHTML = `${data.actualTickrate.toFixed(2)} <span style="font-size: 1.1rem; color: var(--text-muted);">Hz</span>`;
// Sub maintenance text
const subMaint = document.getElementById('subMaintenance');
if (subMaint) subMaint.textContent = data.maintenanceMode ? 'Status: AKTIVIERT' : 'Status: Deaktiviert';
const subTick = document.getElementById('subTickrate');
if (subTick) subTick.textContent = `Aktuell: ${data.targetTickrate} Hz`;
}
// Connect Server-Sent Events (SSE)
if (window.EventSource) {
const evtSource = new EventSource('/api/events');
evtSource.onmessage = (e) => {
try {
const data = JSON.parse(e.data);
applyMetrics(data);
} catch (err) {}
};
} else {
// Fallback Polling
setInterval(() => {
fetch('/api/metrics').then(r => r.json()).then(applyMetrics).catch(() => {});
}, 3000);
}
// Initial Fetch of Logs & Metrics
fetch('/api/metrics').then(r => r.json()).then(applyMetrics).catch(() => {});
fetch('/api/logs').then(r => r.json()).then(updateLogs).catch(() => {});
// Control Buttons Handlers
document.getElementById('btnToggleMaintenance')?.addEventListener('click', () => {
fetch('/api/control/maintenance', { method: 'POST' })
.then(r => r.json())
.then(() => fetch('/api/logs').then(r => r.json()).then(updateLogs));
});
let currentRate = 20;
document.getElementById('btnCycleTickrate')?.addEventListener('click', () => {
currentRate = (currentRate === 20) ? 30 : (currentRate === 30 ? 10 : 20);
fetch('/api/control/tickrate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ rate: currentRate })
}).then(r => r.json()).then(() => fetch('/api/logs').then(r => r.json()).then(updateLogs));
});
document.getElementById('btnSimulateSpike')?.addEventListener('click', () => {
fetch('/api/control/spike', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount: 25 })
}).then(r => r.json()).then(() => fetch('/api/logs').then(r => r.json()).then(updateLogs));
});
document.getElementById('btnBroadcastMsg')?.addEventListener('click', () => {
const msg = prompt('Nachricht an alle verbundenen Spieler senden:', 'Server-Wartung in 15 Minuten');
if (msg) {
fetch('/api/control/broadcast', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: msg })
}).then(r => r.json()).then(() => fetch('/api/logs').then(r => r.json()).then(updateLogs));
}
});
</script>
</body>
</html>