Files
mischlabs/admin.html
Kroonk 205cb1d948
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 12s
feat: implement automatic SSO login redirect on admin panel, with logout protection
2026-05-22 00:26:27 +02:00

247 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MischLabs Admin</title>
<meta name="description" content="MischLabs dashboard administration.">
<meta name="theme-color" content="#0f172a">
<link rel="manifest" href="/manifest.webmanifest?v=4">
<link rel="shortcut icon" href="/favicon.ico?v=4">
<link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32-v4.png">
<link rel="icon" type="image/png" sizes="48x48" href="/icons/favicon-48-v4.png">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/favicon-192-v4.png">
<link rel="apple-touch-icon" href="/icons/apple-touch-icon-v4.png">
<link rel="stylesheet" href="/style.css?v=30">
</head>
<body>
<header class="shell hero admin-hero">
<div class="brand">
<img class="brand-mark" src="/icons/app-icon-source.png" alt="" width="72" height="72">
<div>
<p class="eyebrow">Admin</p>
<h1>MischLabs</h1>
<p class="subtitle">Dienste pruefen, Kategorien vorbereiten und Dashboard-Konfiguration verwalten.</p>
</div>
</div>
<div class="admin-actions">
<a class="ghost-button" id="headerDashboardBtn" href="/">Dashboard</a>
<button class="ghost-button" id="loginButton" type="button">Mit Keycloak anmelden</button>
<button class="ghost-button" id="logoutButton" type="button" hidden>Abmelden</button>
<button class="hamburger-menu-btn" id="hamburgerMenuBtn" aria-label="Menü öffnen" type="button" style="display: none;">
<span class="hamburger-bar"></span>
<span class="hamburger-bar"></span>
<span class="hamburger-bar"></span>
</button>
</div>
</header>
<main class="shell admin-shell">
<section class="admin-panel" id="loginPanel">
<p class="section-kicker">SSO</p>
<h2>Anmeldung erforderlich</h2>
<p class="admin-copy">Diese Seite nutzt Keycloak per Authorization Code + PKCE. Der Client muss in Keycloak als Public Client mit Redirect URI `https://mischlabs.de/admin.html` angelegt sein.</p>
<div class="notice" id="loginMessage">Noch nicht angemeldet.</div>
</section>
<section class="admin-panel" id="adminPanel" hidden>
<div class="admin-panel-head">
<div>
<p class="section-kicker">Konfiguration</p>
<h2>System-Administration</h2>
</div>
</div>
<div id="adminUser" style="display: none;"></div>
<!-- Dashboard Overview Tab -->
<div class="admin-tab-content" id="card-overview">
<div class="overview-grid">
<div class="overview-mini-card" data-target-tab="services">
<div class="mini-card-head">
<span class="mini-card-icon">🌐</span>
<h4>Dienste-Status</h4>
</div>
<div class="mini-card-body" id="mini-services-status">Lade...</div>
</div>
<div class="overview-mini-card" data-target-tab="disks">
<div class="mini-card-head">
<span class="mini-card-icon">💾</span>
<h4>NAS Speicher</h4>
</div>
<div class="mini-card-body" id="mini-disks-status">Lade...</div>
</div>
<div class="overview-mini-card" data-target-tab="watchtower">
<div class="mini-card-head">
<span class="mini-card-icon">🐳</span>
<h4>Watchtower</h4>
</div>
<div class="mini-card-body" id="mini-watchtower-status">Lade...</div>
</div>
<div class="overview-mini-card" data-target-tab="containers">
<div class="mini-card-head">
<span class="mini-card-icon">📦</span>
<h4>Container</h4>
</div>
<div class="mini-card-body" id="mini-containers-status">Lade...</div>
</div>
</div>
</div>
<div class="ops-grid" id="opsGrid">
<section class="ops-card ops-card-wide admin-tab-content" id="card-services">
<div class="ops-card-head">
<div style="display: flex; align-items: center; gap: 8px;">
<span>Dienste-Status (Live-Proxy)</span>
<span id="servicesStatusSummary" class="section-count" style="display: inline; margin-left: 8px; font-weight: 500;"></span>
</div>
<button class="ghost-button mini" id="refreshServicesButton" type="button">Aktualisieren</button>
</div>
<div id="servicesStatusBoard" class="services-status-board">Lade Status...</div>
</section>
<section class="ops-card admin-tab-content" id="card-disks">
<div class="ops-card-head">
<span>NAS Speicher</span>
<button class="ghost-button mini" id="refreshOpsButton" type="button">Aktualisieren</button>
</div>
<div id="diskStatus" class="ops-list">Noch nicht geladen.</div>
<div class="disk-scanner-tool" style="margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--border);">
<h4 style="margin-bottom: 8px; color: #fff; font-size: 1.05rem; font-weight: 700;">Verzeichnis-Größen-Scanner</h4>
<p style="font-size: 0.85rem; color: var(--dim); margin-bottom: 14px; line-height: 1.4;">
Scanne Ordnergrößen auf den Host-Volumes (z.B. <code>/host/volume2</code> oder <code>/host/volume1/docker</code>), um Speicherfresser zu finden.
</p>
<div class="scanner-input-group" style="display: flex; gap: 8px; margin-bottom: 16px;">
<input type="text" id="scannerPathInput" value="/host/volume2" placeholder="/host/volume2" style="flex-grow: 1; padding: 10px 14px; background: rgba(0, 0, 0, 0.2); border: 1px solid var(--border); border-radius: var(--radius); color: #fff; font-family: monospace; font-size: 0.9rem;">
<button class="ghost-button mini" id="scannerStartBtn" type="button" style="padding-inline: 16px;">Scan starten</button>
</div>
<div id="scannerResults" class="ops-list" style="max-height: 350px; overflow-y: auto;" hidden></div>
</div>
</section>
<section class="ops-card admin-tab-content" id="card-watchtower">
<div class="ops-card-head">
<span>Watchtower</span>
<div style="display: flex; gap: 8px;">
<button class="ghost-button mini" id="runWatchtowerButton" type="button">Jetzt pruefen</button>
<button class="ghost-button mini" id="viewWatchtowerLogsBtn" type="button">Logs</button>
</div>
</div>
<div id="watchtowerStatus" class="ops-list">Noch nicht geladen.</div>
</section>
<section class="ops-card ops-card-wide admin-tab-content" id="card-containers">
<div class="ops-card-head">
<span>Container</span>
<button class="ghost-button mini danger-button" id="dangerModeButton" type="button">Danger entsperren</button>
</div>
<div class="danger-state" id="dangerState" hidden></div>
<div id="containerStatus" class="ops-list">Noch nicht geladen.</div>
</section>
</div>
<section class="ops-card ops-card-wide admin-tab-content" id="editorCard" style="margin-top: 14px;">
<div class="ops-card-head">
<span>Dienste &amp; Kategorien (Konfiguration)</span>
</div>
<div class="admin-actions-bar">
<button class="ghost-button mini" id="checkAllButton" type="button">Alle Status pruefen</button>
<button class="ghost-button mini" id="saveLocalButton" type="button">Lokal speichern</button>
<button class="ghost-button mini" id="resetLocalButton" type="button">Aenderungen verwerfen</button>
<button class="ghost-button mini" id="downloadConfigButton" type="button">JSON exportieren</button>
</div>
<div class="admin-table" id="serviceEditor"></div>
</section>
</section>
</main>
<!-- Hamburger Navigation Drawer -->
<div class="admin-nav-backdrop" id="adminNavBackdrop"></div>
<aside class="admin-nav-drawer" id="adminNavDrawer">
<div class="drawer-header">
<h3>MischLabs Admin</h3>
<button class="drawer-close-btn" id="drawerCloseBtn" aria-label="Menü schließen" type="button">&times;</button>
</div>
<nav class="drawer-nav">
<button class="drawer-nav-item" data-tab="overview" type="button">
<span class="drawer-nav-icon">📊</span>
<span class="drawer-nav-text">Übersicht</span>
</button>
<button class="drawer-nav-item" data-tab="services" type="button">
<span class="drawer-nav-icon">🌐</span>
<span class="drawer-nav-text">Dienste-Status</span>
</button>
<button class="drawer-nav-item" data-tab="disks" type="button">
<span class="drawer-nav-icon">💾</span>
<span class="drawer-nav-text">NAS Speicher</span>
</button>
<button class="drawer-nav-item" data-tab="watchtower" type="button">
<span class="drawer-nav-icon">🐳</span>
<span class="drawer-nav-text">Watchtower</span>
</button>
<button class="drawer-nav-item" data-tab="containers" type="button">
<span class="drawer-nav-icon">📦</span>
<span class="drawer-nav-text">Container</span>
</button>
<button class="drawer-nav-item" data-tab="config" type="button">
<span class="drawer-nav-icon">⚙️</span>
<span class="drawer-nav-text">Dienste &amp; Kategorien</span>
</button>
<div class="drawer-divider"></div>
<a class="drawer-nav-item" href="/">
<span class="drawer-nav-icon">🏠</span>
<span class="drawer-nav-text">Dashboard</span>
</a>
<button class="drawer-nav-item drawer-logout-btn" id="drawerLogoutBtn" type="button">
<span class="drawer-nav-icon">🚪</span>
<span class="drawer-nav-text">Abmelden</span>
</button>
</nav>
</aside>
<!-- Container Logs Modal Overlay -->
<div class="modal-overlay" id="logsModalOverlay" hidden>
<div class="modal-card">
<div class="modal-header">
<h3 id="logsModalTitle">Container Logs</h3>
<button class="modal-close-btn" id="logsModalCloseBtn" type="button">&times;</button>
</div>
<div class="modal-body">
<div class="terminal-logs-controls" style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; font-size: 0.85rem;">
<div>
<label for="logsTailSelect" style="color: var(--dim); margin-right: 6px;">Zeilen:</label>
<select id="logsTailSelect" style="background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius); color: #fff; padding: 4px 8px; cursor: pointer;">
<option value="50">50</option>
<option value="150" selected>150</option>
<option value="300">300</option>
<option value="500">500</option>
</select>
</div>
<button class="ghost-button mini" id="logsModalRefreshBtn" type="button">Aktualisieren</button>
</div>
<pre class="terminal-logs" id="logsModalConsole">Lade Logs...</pre>
</div>
</div>
</div>
<!-- Service Details Modal Overlay -->
<div class="modal-overlay" id="serviceModalOverlay" hidden>
<div class="modal-card">
<div class="modal-header">
<h3 id="serviceModalTitle">Dienst-Inspektion</h3>
<button class="modal-close-btn" id="serviceModalCloseBtn" type="button">&times;</button>
</div>
<div class="modal-body" id="serviceModalContent">
<!-- Dynamically populated details -->
</div>
</div>
</div>
<script src="/admin.js?v=29" defer></script>
</body>
</html>