Redesign Sentinel UI: index.html

This commit is contained in:
sascha 2026-07-29 15:38:34 +02:00
parent 7da281117a
commit 6bf266e870

View file

@ -1,295 +1,201 @@
<!DOCTYPE html>
<html class="dark" lang="de">
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>The Sentinel Backup Monitor</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Inter:wght@400;500;600&display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🛡️</text></svg>">
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"surface-dim": "#0b1326", "surface": "#0b1326", "surface-bright": "#31394d",
"surface-container-lowest": "#060e20", "surface-container-low": "#131b2e",
"surface-container": "#171f33", "surface-container-high": "#222a3d",
"surface-container-highest": "#2d3449", "surface-variant": "#2d3449",
"on-surface": "#dae2fd", "on-surface-variant": "#c1c6d6",
"primary": "#adc6ff", "primary-container": "#0069de", "on-primary": "#002e69",
"secondary": "#4edea3", "secondary-container": "#00a572", "on-secondary": "#003824",
"tertiary": "#ffb95f", "tertiary-container": "#9a6100",
"error": "#ffb4ab", "error-container": "#93000a", "on-error": "#690005",
"outline": "#8b909f", "outline-variant": "#414753",
},
fontFamily: { headline: ["Manrope"], body: ["Inter"], label: ["Inter"] },
borderRadius: { DEFAULT: "0.25rem", lg: "0.5rem", xl: "0.75rem", full: "9999px" },
},
},
}
</script>
<style>
.material-symbols-outlined { font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24; }
.pulse-ok { animation: pulse-g 2s infinite; }
@keyframes pulse-g { 0%{box-shadow:0 0 0 0 rgba(78,222,163,.4)} 70%{box-shadow:0 0 0 10px rgba(78,222,163,0)} 100%{box-shadow:0 0 0 0 rgba(78,222,163,0)} }
.pulse-err { animation: pulse-r 2s infinite; }
@keyframes pulse-r { 0%{box-shadow:0 0 0 0 rgba(255,180,171,.4)} 70%{box-shadow:0 0 0 10px rgba(255,180,171,0)} 100%{box-shadow:0 0 0 0 rgba(255,180,171,0)} }
.glass { background: rgba(45,52,73,.6); backdrop-filter: blur(20px); }
::-webkit-scrollbar { width: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: #2d3449; border-radius: 3px; }
</style>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark">
<title>The Sentinel · Backup-Monitor</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'><rect width='64' height='64' rx='10' fill='%23171917'/><path d='M18 20h28M18 32h28M18 44h18' stroke='%237abf8a' stroke-width='5' stroke-linecap='round'/></svg>">
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body class="bg-surface-dim font-body text-on-surface antialiased">
<!-- ── Top Nav ──────────────────────────────────────────── -->
<nav class="fixed top-0 w-full z-50 flex justify-between items-center px-6 h-16 bg-slate-900/60 backdrop-blur-xl shadow-2xl shadow-slate-950/40">
<div class="flex items-center gap-8">
<span class="text-xl font-bold tracking-tighter text-white font-headline">The Sentinel</span>
<div class="hidden md:flex gap-1 items-center">
<a href="#" onclick="showPage('dashboard')" id="nav-dashboard" class="text-sm font-bold tracking-tight text-blue-400 px-3 py-1 rounded-lg font-headline">Dashboard</a>
<a href="#" onclick="showPage('alerts')" id="nav-alerts" class="text-sm font-medium tracking-tight text-slate-400 hover:bg-slate-800/50 px-3 py-1 rounded-lg transition-colors font-headline">Alert Center</a>
<a href="#" onclick="showPage('hosts')" id="nav-hosts" class="text-sm font-medium tracking-tight text-slate-400 hover:bg-slate-800/50 px-3 py-1 rounded-lg transition-colors font-headline">Backup Hosts</a>
</div>
</div>
<div class="flex items-center gap-4">
<div id="sysStatus" class="flex items-center gap-2 px-3 py-1.5 rounded-full bg-slate-800/50">
<span class="material-symbols-outlined text-sm text-secondary" style="font-variation-settings:'FILL' 1">cloud_done</span>
<span class="font-headline text-xs font-medium text-slate-300">System OK</span>
</div>
<button onclick="loadAll()" class="material-symbols-outlined text-slate-400 hover:bg-slate-800/50 p-2 rounded-full transition-colors">refresh</button>
<button onclick="openAddHost()" class="material-symbols-outlined text-slate-400 hover:bg-slate-800/50 p-2 rounded-full transition-colors">add_circle</button>
</div>
</nav>
<!-- ── Sidebar ─────────────────────────────────────────── -->
<aside class="fixed left-0 top-0 h-full flex-col py-6 bg-slate-950 w-56 z-40 pt-20 hidden lg:flex">
<div class="px-5 mb-8">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-primary-container flex items-center justify-center">
<span class="material-symbols-outlined text-white text-lg" style="font-variation-settings:'FILL' 1">shield</span>
</div>
<div>
<div class="text-sm font-black text-white font-headline leading-tight">The Sentinel</div>
<div class="text-[10px] text-slate-500 uppercase tracking-widest font-semibold">Backup Monitor</div>
</div>
</div>
</div>
<div class="flex-1 px-3 space-y-1">
<a href="#" onclick="showPage('dashboard')" id="side-dashboard" class="flex items-center gap-3 px-4 py-3 rounded-xl bg-blue-600/10 text-blue-400 border-r-2 border-blue-500 font-headline text-sm font-semibold">
<span class="material-symbols-outlined">dashboard</span><span>Dashboard</span>
<body>
<header class="site-header">
<div class="topbar">
<a class="brand" href="#dashboard" onclick="showPage('dashboard'); return false;" aria-label="The Sentinel Startseite">
<span class="brand-mark" aria-hidden="true">S</span>
<span>
<strong>The Sentinel</strong>
<small>Backup-Monitor</small>
</span>
</a>
<a href="#" onclick="showPage('alerts')" id="side-alerts" class="flex items-center gap-3 px-4 py-3 rounded-xl text-slate-500 hover:text-slate-300 hover:bg-slate-900/80 transition-all font-headline text-sm font-semibold">
<span class="material-symbols-outlined">warning</span><span>Alert Center</span>
</a>
<a href="#" onclick="showPage('hosts')" id="side-hosts" class="flex items-center gap-3 px-4 py-3 rounded-xl text-slate-500 hover:text-slate-300 hover:bg-slate-900/80 transition-all font-headline text-sm font-semibold">
<span class="material-symbols-outlined">dns</span><span>Backup Hosts</span>
</a>
<a href="#" onclick="showPage('config')" id="side-config" class="flex items-center gap-3 px-4 py-3 rounded-xl text-slate-500 hover:text-slate-300 hover:bg-slate-900/80 transition-all font-headline text-sm font-semibold">
<span class="material-symbols-outlined">settings</span><span>Configuration</span>
</a>
</div>
<div class="px-3 mt-auto">
<button onclick="openAddHost()" class="w-full bg-primary text-on-primary font-bold py-3 rounded-xl flex items-center justify-center gap-2 shadow-lg shadow-primary/20 hover:scale-[1.02] active:scale-95 transition-all text-sm">
<span class="material-symbols-outlined text-sm">add</span> New Host
</button>
</div>
</aside>
<!-- ── Main Content ────────────────────────────────────── -->
<main class="lg:ml-56 pt-24 px-6 lg:px-8 pb-12 min-h-screen">
<nav class="main-nav" aria-label="Hauptnavigation">
<a href="#dashboard" onclick="showPage('dashboard'); return false;" id="nav-dashboard" class="is-active" aria-current="page">Übersicht</a>
<a href="#alerts" onclick="showPage('alerts'); return false;" id="nav-alerts">Meldungen</a>
<a href="#hosts" onclick="showPage('hosts'); return false;" id="nav-hosts">Hosts</a>
<a href="#config" onclick="showPage('config'); return false;" id="nav-config">Einstellungen</a>
</nav>
<!-- ════════ PAGE: DASHBOARD ════════ -->
<div id="page-dashboard">
<!-- Header -->
<header class="mb-10 flex flex-col md:flex-row justify-between md:items-end gap-4">
<div>
<h1 class="text-3xl font-extrabold font-headline tracking-tight text-white">Vault Overview</h1>
<p class="text-on-surface-variant mt-1">Operational command for Borgmatic backup infrastructure.</p>
<div class="topbar-actions">
<div id="sysStatus" class="system-state status--ok" aria-live="polite">
<span class="status-dot status--ok" aria-hidden="true"></span>
<span>Betriebsbereit</span>
</div>
<div class="flex gap-3">
<div class="flex items-center gap-2 bg-surface-container-low px-4 py-2 rounded-lg text-sm">
<span class="text-on-surface-variant">Last Scan:</span>
<span class="text-primary font-semibold" id="lastScan"></span>
<button type="button" class="icon-button" onclick="loadAll()" aria-label="Daten aktualisieren" title="Aktualisieren"></button>
<button type="button" class="icon-button" onclick="openAddHost()" aria-label="Host hinzufügen" title="Host hinzufügen"></button>
</div>
<button onclick="loadAll()" class="bg-surface-container-highest px-4 py-2 rounded-lg text-sm font-bold flex items-center gap-2 hover:bg-surface-bright transition-colors">
<span class="material-symbols-outlined text-sm">refresh</span> Refresh
</button>
</div>
</header>
<!-- Metric Cards -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 lg:gap-6 mb-8" id="metricCards">
<div class="bg-surface-container-low p-6 rounded-xl relative overflow-hidden">
<div class="absolute top-0 right-0 w-24 h-24 bg-secondary/5 rounded-full -mr-8 -mt-8 blur-2xl"></div>
<div class="flex justify-between items-start mb-4">
<div class="p-2 bg-secondary/10 rounded-lg"><span class="material-symbols-outlined text-secondary" style="font-variation-settings:'FILL' 1">check_circle</span></div>
</div>
<div class="text-4xl font-extrabold font-headline text-white mb-1" id="mOk"></div>
<div class="text-xs text-on-surface-variant font-medium uppercase tracking-wider">Hosts OK</div>
</div>
<div class="bg-surface-container-low p-6 rounded-xl relative overflow-hidden">
<div class="flex justify-between items-start mb-4">
<div class="p-2 bg-primary/10 rounded-lg"><span class="material-symbols-outlined text-primary" style="font-variation-settings:'FILL' 1">database</span></div>
</div>
<div class="text-4xl font-extrabold font-headline text-white mb-1" id="mSize"></div>
<div class="text-xs text-on-surface-variant font-medium uppercase tracking-wider">Today Backed Up</div>
</div>
<div class="bg-surface-container-low p-6 rounded-xl relative overflow-hidden">
<div class="flex justify-between items-start mb-4">
<div class="p-2 bg-secondary/10 rounded-lg pulse-ok"><span class="material-symbols-outlined text-secondary" style="font-variation-settings:'FILL' 1">bolt</span></div>
<div class="flex items-center gap-1.5"><div class="w-2 h-2 rounded-full bg-secondary"></div><span class="text-xs font-bold text-secondary">LIVE</span></div>
</div>
<div class="text-2xl font-bold font-headline text-white mb-1" id="mLatest"></div>
<div class="text-xs text-on-surface-variant font-medium uppercase tracking-wider">Latest Backup</div>
<div class="text-[10px] mt-2 font-mono text-slate-500" id="mLatestHost"></div>
</div>
<div class="bg-surface-container-low p-6 rounded-xl" id="mWarnCard">
<div class="flex justify-between items-start mb-4">
<div class="p-2 bg-error/10 rounded-lg"><span class="material-symbols-outlined text-error" style="font-variation-settings:'FILL' 1">report_problem</span></div>
</div>
<div class="text-4xl font-extrabold font-headline text-error mb-1" id="mWarn">0</div>
<div class="text-xs text-on-surface-variant font-medium uppercase tracking-wider">Issues</div>
<button onclick="showPage('alerts')" class="mt-3 text-xs font-bold text-error flex items-center gap-1 hover:underline">
View alerts <span class="material-symbols-outlined text-xs">arrow_forward</span>
</button>
</div>
</div>
<!-- Chart + Clusters -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
<!-- Volume Trend Chart -->
<div class="lg:col-span-2 bg-surface-container-low rounded-xl p-6 lg:p-8">
<div class="flex justify-between items-center mb-8">
<main class="app-shell">
<section id="page-dashboard" class="page" aria-labelledby="dashboard-title">
<header class="page-header">
<div>
<h3 class="text-lg font-bold font-headline text-white">Backup Volume Trends</h3>
<p class="text-sm text-on-surface-variant">Storage growth across all hosts (Last 30 Days)</p>
</div>
</div>
<div class="h-48 w-full relative" id="volumeChart">
<svg class="w-full h-full" preserveAspectRatio="none" id="chartSvg"></svg>
</div>
</div>
<!-- Host Clusters -->
<div class="bg-surface-container-low rounded-xl p-6 lg:p-8">
<h3 class="text-lg font-bold font-headline text-white mb-6">Host Status</h3>
<div class="space-y-3 max-h-[300px] overflow-y-auto" id="clusterList"></div>
</div>
</div>
<!-- Live Stream -->
<div class="bg-surface-container-low rounded-xl p-6 lg:p-8">
<div class="flex justify-between items-center mb-6">
<h3 class="text-lg font-bold font-headline text-white">Live Backup Stream</h3>
<span class="text-xs text-on-surface-variant font-mono">Auto-refresh: 30s</span>
</div>
<div class="space-y-0" id="liveStream"></div>
</div>
</div>
<!-- ════════ PAGE: ALERTS ════════ -->
<div id="page-alerts" class="hidden">
<header class="mb-10 flex flex-col md:flex-row md:items-end justify-between gap-6">
<div>
<h1 class="text-3xl font-extrabold tracking-tighter font-headline text-white">Alert Center</h1>
<p class="text-on-surface-variant max-w-lg">Real-time surveillance of the backup infrastructure.</p>
</div>
<div class="flex gap-3">
<div class="bg-surface-container-low px-6 py-4 rounded-xl flex flex-col gap-1 border-b-2 border-error">
<span class="text-error font-bold text-2xl" id="aCrit">0</span>
<span class="text-[10px] uppercase tracking-widest text-on-surface-variant font-bold">Errors</span>
</div>
<div class="bg-surface-container-low px-6 py-4 rounded-xl flex flex-col gap-1 border-b-2 border-tertiary">
<span class="text-tertiary font-bold text-2xl" id="aStale">0</span>
<span class="text-[10px] uppercase tracking-widest text-on-surface-variant font-bold">Stale</span>
<p class="eyebrow">Borgmatic-Infrastruktur</p>
<h1 id="dashboard-title">Backup-Übersicht</h1>
<p class="lede">Der aktuelle Stand aller gemeldeten Sicherungen.</p>
</div>
<div class="scan-state">
<span>Letzte Aktualisierung</span>
<time id="lastScan"></time>
</div>
</header>
<div class="space-y-4" id="alertList">
<div class="text-center py-12 text-on-surface-variant">No active alerts all systems operational ✓</div>
</div>
</div>
<!-- ════════ PAGE: HOSTS ════════ -->
<div id="page-hosts" class="hidden">
<header class="mb-10 flex flex-col md:flex-row md:items-end justify-between gap-6">
<section class="metric-strip" id="metricCards" aria-label="Zusammenfassung">
<article class="metric">
<span class="metric-label">Hosts in Ordnung</span>
<strong id="mOk"></strong>
</article>
<article class="metric">
<span class="metric-label">Heute gesichert</span>
<strong id="mSize"></strong>
</article>
<article class="metric">
<span class="metric-label">Letzte Sicherung</span>
<strong id="mLatest"></strong>
<small id="mLatestHost"></small>
</article>
<article class="metric metric--issue" id="mWarnCard">
<span class="metric-label">Auffälligkeiten</span>
<strong id="mWarn">0</strong>
<button type="button" class="text-button" onclick="showPage('alerts')">Meldungen öffnen</button>
</article>
</section>
<div class="dashboard-grid">
<section class="panel panel--chart" aria-labelledby="volume-title">
<header class="panel-header">
<div>
<h1 class="text-3xl font-extrabold tracking-tighter font-headline text-white">Backup Hosts</h1>
<p class="text-on-surface-variant">Manage and monitor all registered backup endpoints.</p>
<h2 id="volume-title">Gesichertes Datenvolumen</h2>
<p>Summe der letzten 30 Tage, über die zehn zuerst gemeldeten Hosts.</p>
</div>
<button onclick="openAddHost()" class="bg-primary text-on-primary font-bold py-3 px-6 rounded-xl flex items-center gap-2 shadow-lg shadow-primary/20 hover:scale-[1.02] active:scale-95 transition-all text-sm">
<span class="material-symbols-outlined text-sm">add</span> Add Host
</button>
</header>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4" id="hostGrid"></div>
</div>
<div class="chart" id="volumeChart">
<svg id="chartSvg" role="img" aria-label="Backup-Volumen der letzten 30 Tage" viewBox="0 0 100 100" preserveAspectRatio="none"></svg>
</div>
</section>
<!-- ════════ PAGE: CONFIG ════════ -->
<div id="page-config" class="hidden">
<header class="mb-10">
<h1 class="text-3xl font-extrabold tracking-tighter font-headline text-white">Configuration</h1>
<p class="text-on-surface-variant mt-1">API endpoint and integration settings.</p>
<section class="panel panel--hosts" aria-labelledby="host-status-title">
<header class="panel-header">
<h2 id="host-status-title">Host-Status</h2>
</header>
<div class="bg-surface-container-low rounded-xl p-8 max-w-2xl space-y-6">
<div id="clusterList" class="cluster-list"></div>
</section>
</div>
<section class="panel" aria-labelledby="stream-title">
<header class="panel-header panel-header--split">
<h2 id="stream-title">Letzte Meldungen</h2>
<span class="quiet-note">automatisch alle 30 Sekunden</span>
</header>
<div id="liveStream" class="event-list" aria-live="polite"></div>
</section>
</section>
<section id="page-alerts" class="page hidden" aria-labelledby="alerts-title">
<header class="page-header">
<div>
<h3 class="text-sm font-bold text-on-surface-variant uppercase tracking-wider mb-3">Push Endpoint</h3>
<code class="block bg-surface-dim px-4 py-3 rounded-lg text-primary text-sm font-mono">POST /api/push</code>
<p class="eyebrow">Prüfbedarf</p>
<h1 id="alerts-title">Meldungen</h1>
<p class="lede">Fehlgeschlagene oder überfällige Sicherungen.</p>
</div>
<dl class="inline-counts">
<div><dt>Fehler</dt><dd id="aCrit">0</dd></div>
<div><dt>Überfällig</dt><dd id="aStale">0</dd></div>
</dl>
</header>
<div id="alertList" class="alert-list">
<p class="empty-state">Keine offenen Meldungen.</p>
</div>
</section>
<section id="page-hosts" class="page hidden" aria-labelledby="hosts-title">
<header class="page-header">
<div>
<p class="eyebrow">Inventar</p>
<h1 id="hosts-title">Backup-Hosts</h1>
<p class="lede">Alle registrierten Endpunkte und ihre letzten Sicherungen.</p>
</div>
<button type="button" class="button button--primary" onclick="openAddHost()">Host hinzufügen</button>
</header>
<div id="hostGrid" class="host-grid"></div>
</section>
<section id="page-config" class="page hidden" aria-labelledby="config-title">
<header class="page-header">
<div>
<p class="eyebrow">Schnittstellen</p>
<h1 id="config-title">Einstellungen</h1>
<p class="lede">Die Endpunkte, über die Sentinel Daten annimmt und ausliefert.</p>
</div>
</header>
<dl class="settings-list">
<div>
<dt>Backup-Meldungen</dt>
<dd><code>POST /api/push</code></dd>
</div>
<div>
<h3 class="text-sm font-bold text-on-surface-variant uppercase tracking-wider mb-3">Prometheus Metrics</h3>
<code class="block bg-surface-dim px-4 py-3 rounded-lg text-primary text-sm font-mono">GET /metrics</code>
<dt>Prometheus</dt>
<dd><code>GET /metrics</code></dd>
</div>
<div>
<h3 class="text-sm font-bold text-on-surface-variant uppercase tracking-wider mb-3">API Key</h3>
<p class="text-sm text-on-surface-variant">{{ 'Enabled set via API_KEY env var' if api_key_required else 'Disabled all endpoints open' }}</p>
<dt>API-Schlüssel</dt>
<dd>{{ 'Aktiviert (Umgebungsvariable API_KEY)' if api_key_required else 'Deaktiviert' }}</dd>
</div>
</div>
</div>
</dl>
</section>
</main>
</main>
<!-- ── Host Detail Drawer ──────────────────────────────── -->
<div id="drawerBg" onclick="closeDrawer()" class="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 opacity-0 pointer-events-none transition-opacity duration-300"></div>
<aside id="drawer" class="fixed top-0 right-0 bottom-0 w-[500px] max-w-[90vw] bg-surface-container-lowest z-[51] shadow-2xl transform translate-x-full transition-transform duration-300 flex flex-col">
<div class="flex justify-between items-center px-6 py-5 border-b border-outline-variant/10">
<h2 class="text-lg font-bold font-headline text-white" id="drawerTitle">Host</h2>
<button onclick="closeDrawer()" class="material-symbols-outlined text-slate-400 hover:text-white p-1 rounded-lg hover:bg-surface-container-high transition-colors">close</button>
<div id="drawerBg" class="backdrop is-hidden" onclick="closeDrawer()"></div>
<aside id="drawer" class="drawer is-closed" aria-labelledby="drawerTitle" aria-hidden="true">
<header class="overlay-header">
<div>
<span class="eyebrow">Host-Details</span>
<h2 id="drawerTitle">Host</h2>
</div>
<div class="flex-1 overflow-y-auto px-6 py-6" id="drawerBody"></div>
</aside>
<button type="button" class="icon-button" onclick="closeDrawer()" aria-label="Details schließen">×</button>
</header>
<div id="drawerBody" class="drawer-body"></div>
</aside>
<!-- ── Add/Edit Modal ──────────────────────────────────── -->
<div id="modalBg" onclick="closeModal()" class="fixed inset-0 bg-black/60 backdrop-blur-sm z-[60] opacity-0 pointer-events-none transition-opacity duration-300"></div>
<div id="modal" class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[440px] max-w-[90vw] bg-surface-container-lowest rounded-xl z-[61] shadow-2xl scale-95 opacity-0 pointer-events-none transition-all duration-300">
<div class="flex justify-between items-center px-6 py-5 border-b border-outline-variant/10">
<h2 class="text-lg font-bold font-headline text-white" id="modalTitle">Add Host</h2>
<button onclick="closeModal()" class="material-symbols-outlined text-slate-400 hover:text-white">close</button>
</div>
<form id="hostForm" onsubmit="saveHost(event)" class="p-6 space-y-4">
<div id="modalBg" class="backdrop is-hidden" onclick="closeModal()"></div>
<section id="modal" class="modal is-closed" role="dialog" aria-modal="true" aria-labelledby="modalTitle" aria-hidden="true">
<header class="overlay-header">
<h2 id="modalTitle">Host hinzufügen</h2>
<button type="button" class="icon-button" onclick="closeModal()" aria-label="Dialog schließen">×</button>
</header>
<form id="hostForm" onsubmit="saveHost(event)" class="form-stack">
<input type="hidden" id="formMode" value="add">
<div>
<label class="block text-xs text-on-surface-variant uppercase tracking-wider font-bold mb-2">Hostname</label>
<input type="text" id="formName" required class="w-full bg-surface-container-highest border-none rounded-lg px-4 py-3 text-on-surface text-sm focus:ring-2 focus:ring-primary/30" placeholder="e.g. arrapps">
</div>
<div>
<label class="block text-xs text-on-surface-variant uppercase tracking-wider font-bold mb-2">Uptime Kuma Push URL</label>
<input type="url" id="formKumaUrl" class="w-full bg-surface-container-highest border-none rounded-lg px-4 py-3 text-on-surface text-sm focus:ring-2 focus:ring-primary/30" placeholder="https://status.example.com/api/push/...">
</div>
<label class="flex items-center gap-3 cursor-pointer">
<input type="checkbox" id="formEnabled" checked class="rounded bg-surface-container-highest border-none text-primary focus:ring-primary/30">
<span class="text-sm text-on-surface">Enabled</span>
<label>
<span>Hostname</span>
<input type="text" id="formName" required placeholder="zum Beispiel arrapps">
</label>
<div class="flex justify-end gap-3 pt-4">
<button type="button" onclick="closeModal()" class="px-5 py-2.5 rounded-lg text-sm font-bold text-on-surface-variant hover:bg-surface-container-high transition-colors">Cancel</button>
<button type="submit" class="bg-primary text-on-primary px-6 py-2.5 rounded-lg text-sm font-bold hover:brightness-110 active:scale-95 transition-all">Save</button>
<label>
<span>Uptime-Kuma Push-URL</span>
<input type="url" id="formKumaUrl" placeholder="https://status.example.com/api/push/…">
</label>
<label class="checkbox-row">
<input type="checkbox" id="formEnabled" checked>
<span>Host überwachen</span>
</label>
<div class="form-actions">
<button type="button" class="button" onclick="closeModal()">Abbrechen</button>
<button type="submit" class="button button--primary">Speichern</button>
</div>
</form>
</div>
</section>
<!-- ── Toast ───────────────────────────────────────────── -->
<div id="toasts" class="fixed bottom-6 right-6 z-[70] flex flex-col gap-2"></div>
<script src="/static/js/app.js"></script>
<div id="toasts" class="toast-stack" aria-live="polite"></div>
<script src="/static/js/app.js"></script>
</body>
</html>