Some checks are pending
CI/CD Cloud Deployment / test-and-build (push) Waiting to run
- Triple-A Werbeseite mit Gameplay-Videos, 3-Lane Radar und Einheiten-Showcase - LiveOps-Schaltzentrale mit CCU-Tracking, Download-Statistiken und Match-Balance - Reale Engine-Benchmarks (C# vs Godot, U2-U5.5 FPS, GPU-Skinning Entlastung) - Zero-Dependency Node.js Backend mit REST-APIs und Server-Sent Events (SSE) - Cloud-Ready mit Dockerfile, docker-compose.yml und GitHub Actions CI/CD
939 lines
35 KiB
HTML
939 lines
35 KiB
HTML
<!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="/" class="btn-cyan-ghost" style="padding: 8px 18px; font-size: 0.8rem;">
|
||
<span>Zur Website</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>
|