5dcadd1c20
Warm, friendly redesign replacing the generic cold-shadcn look. Built as a swappable token bundle so other presets can be added later; dark mode and the user-tunable accent hue are retained. Foundation - app.css: warm cream (light) + "dusk" (dark) token system; terracotta accent (default hue 16); pastel --room-* palette; vivid --status-* (dots/bars) plus AA-legible --status-*-ink (text); soft warm shadows; --radius 1rem; font tokens - Fonts: Fraunces (display) + Figtree (body), self-hosted in static/fonts (no Google CDN) so offline/LAN installs work; system-ui fallbacks kept - h1/h2/h3 render in Fraunces via base layer Chrome and surfaces - Sidebar, Header, home, AppCard/BoardCard, BoardHeader, sections, favorites - 29 widgets + integration renderers: cozy card shells, room-palette charts - Default background is a static warm "cozy" glow (mesh demoted, rAF gated on prefers-reduced-motion) System-wide - Status colors tokenized (no raw bg/text-*-500 or status hex); success/warning to status tokens, categorical to room palette, errors to destructive - Inputs rounded-xl; buttons rounded-xl; cards/dialogs rounded-[1.4rem]; soft-shadow vocabulary only; focus-visible:ring-primary/30 - Forms, admin tables (now cozy cards), dialogs, popovers, auth screens a11y: reduced-motion guards; darker status "ink" text for AA on cream. Known tradeoff: terracotta primary + white button text ~2.96:1 (signature color, user-tunable). Verified: svelte-check 0/0, build ok, 274 tests pass, eslint 0 errors. Design refs + system sheet in design-mockups/.
790 lines
20 KiB
HTML
790 lines
20 KiB
HTML
<!doctype html>
|
|
<html lang="en" class="dark">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>Web App Launcher — Command Deck</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
<link
|
|
href="https://fonts.googleapis.com/css2?family=Saira:wght@400;500;600;700&family=Saira+Condensed:wght@500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap"
|
|
rel="stylesheet"
|
|
/>
|
|
<style>
|
|
:root {
|
|
--bg: #070a0d;
|
|
--panel: #0d1217;
|
|
--panel-2: #10171e;
|
|
--line: #1d2730;
|
|
--line-bright: #2b3946;
|
|
--ink: #e7eef3;
|
|
--ink-dim: #7c8b97;
|
|
--ink-faint: #4a5763;
|
|
--accent: #36e0a4; /* tactical green */
|
|
--accent-2: #ffb020; /* amber */
|
|
--danger: #ff4d5e;
|
|
--warn: #ffb020;
|
|
--grid: rgba(54, 224, 164, 0.04);
|
|
--radius: 4px;
|
|
}
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
html,
|
|
body {
|
|
height: 100%;
|
|
}
|
|
body {
|
|
background: var(--bg);
|
|
color: var(--ink);
|
|
font-family: 'Saira', system-ui, sans-serif;
|
|
-webkit-font-smoothing: antialiased;
|
|
overflow-x: hidden;
|
|
}
|
|
/* subtle scanline grid backdrop */
|
|
body::before {
|
|
content: '';
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 0;
|
|
pointer-events: none;
|
|
background-image:
|
|
linear-gradient(var(--grid) 1px, transparent 1px),
|
|
linear-gradient(90deg, var(--grid) 1px, transparent 1px);
|
|
background-size: 40px 40px;
|
|
mask-image: radial-gradient(ellipse 80% 60% at 70% 0%, #000 30%, transparent 90%);
|
|
}
|
|
.app {
|
|
position: relative;
|
|
z-index: 1;
|
|
display: grid;
|
|
grid-template-columns: 74px 1fr;
|
|
min-height: 100vh;
|
|
}
|
|
.mono {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
}
|
|
.cond {
|
|
font-family: 'Saira Condensed', sans-serif;
|
|
}
|
|
|
|
/* ===== Rail ===== */
|
|
.rail {
|
|
border-right: 1px solid var(--line);
|
|
background: linear-gradient(180deg, var(--panel), #080c10);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 16px 0;
|
|
}
|
|
.logo {
|
|
width: 40px;
|
|
height: 40px;
|
|
border: 1px solid var(--accent);
|
|
border-radius: var(--radius);
|
|
display: grid;
|
|
place-items: center;
|
|
color: var(--accent);
|
|
margin-bottom: 18px;
|
|
box-shadow:
|
|
0 0 0 1px #0a1f18,
|
|
0 0 18px -4px var(--accent);
|
|
position: relative;
|
|
}
|
|
.logo svg {
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
.rail-btn {
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: var(--radius);
|
|
display: grid;
|
|
place-items: center;
|
|
color: var(--ink-dim);
|
|
position: relative;
|
|
cursor: pointer;
|
|
transition: 0.15s;
|
|
}
|
|
.rail-btn:hover {
|
|
color: var(--ink);
|
|
background: var(--panel-2);
|
|
}
|
|
.rail-btn.active {
|
|
color: var(--accent);
|
|
}
|
|
.rail-btn.active::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: -16px;
|
|
top: 10px;
|
|
bottom: 10px;
|
|
width: 2px;
|
|
background: var(--accent);
|
|
box-shadow: 0 0 8px var(--accent);
|
|
}
|
|
.rail-btn svg {
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
.rail-spacer {
|
|
flex: 1;
|
|
}
|
|
.rail-avatar {
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: var(--radius);
|
|
background: linear-gradient(135deg, #1a2a22, #0f1a14);
|
|
border: 1px solid var(--line-bright);
|
|
display: grid;
|
|
place-items: center;
|
|
font-weight: 700;
|
|
color: var(--accent);
|
|
font-size: 13px;
|
|
}
|
|
|
|
/* ===== Main ===== */
|
|
.main {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
}
|
|
.topbar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
height: 60px;
|
|
padding: 0 26px;
|
|
border-bottom: 1px solid var(--line);
|
|
background: rgba(8, 12, 16, 0.6);
|
|
backdrop-filter: blur(8px);
|
|
}
|
|
.crumbs {
|
|
font-family: 'Saira Condensed';
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.14em;
|
|
font-size: 12px;
|
|
color: var(--ink-faint);
|
|
}
|
|
.crumbs b {
|
|
color: var(--ink-dim);
|
|
font-weight: 600;
|
|
}
|
|
.search {
|
|
margin-left: auto;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
width: 340px;
|
|
max-width: 38vw;
|
|
background: var(--panel);
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
padding: 9px 12px;
|
|
color: var(--ink-dim);
|
|
font-size: 13px;
|
|
cursor: text;
|
|
transition: 0.15s;
|
|
}
|
|
.search:hover {
|
|
border-color: var(--line-bright);
|
|
}
|
|
.search svg {
|
|
width: 15px;
|
|
height: 15px;
|
|
}
|
|
.search .kbd {
|
|
margin-left: auto;
|
|
font-family: 'JetBrains Mono';
|
|
font-size: 10px;
|
|
color: var(--ink-faint);
|
|
border: 1px solid var(--line);
|
|
border-radius: 3px;
|
|
padding: 2px 6px;
|
|
}
|
|
.ico-btn {
|
|
width: 38px;
|
|
height: 38px;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
display: grid;
|
|
place-items: center;
|
|
color: var(--ink-dim);
|
|
cursor: pointer;
|
|
transition: 0.15s;
|
|
background: var(--panel);
|
|
}
|
|
.ico-btn:hover {
|
|
color: var(--ink);
|
|
border-color: var(--line-bright);
|
|
}
|
|
.ico-btn svg {
|
|
width: 17px;
|
|
height: 17px;
|
|
}
|
|
|
|
.content {
|
|
padding: 26px;
|
|
max-width: 1320px;
|
|
width: 100%;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
/* status bar */
|
|
.statline {
|
|
display: flex;
|
|
align-items: stretch;
|
|
gap: 1px;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
overflow: hidden;
|
|
margin-bottom: 24px;
|
|
background: var(--line);
|
|
}
|
|
.stat {
|
|
flex: 1;
|
|
background: var(--panel);
|
|
padding: 16px 18px;
|
|
position: relative;
|
|
}
|
|
.stat .lbl {
|
|
font-family: 'Saira Condensed';
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.16em;
|
|
font-size: 11px;
|
|
color: var(--ink-faint);
|
|
}
|
|
.stat .val {
|
|
font-family: 'JetBrains Mono';
|
|
font-size: 26px;
|
|
font-weight: 700;
|
|
margin-top: 6px;
|
|
letter-spacing: -0.02em;
|
|
}
|
|
.stat .val.ok {
|
|
color: var(--accent);
|
|
}
|
|
.stat .val.warn {
|
|
color: var(--warn);
|
|
}
|
|
.stat .val.bad {
|
|
color: var(--danger);
|
|
}
|
|
.stat .sub {
|
|
font-size: 12px;
|
|
color: var(--ink-dim);
|
|
margin-top: 2px;
|
|
}
|
|
.stat::after {
|
|
content: '';
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
height: 2px;
|
|
width: 100%;
|
|
background: linear-gradient(90deg, var(--accent), transparent);
|
|
}
|
|
.stat.s2::after {
|
|
background: linear-gradient(90deg, var(--accent-2), transparent);
|
|
}
|
|
.stat.s3::after {
|
|
background: linear-gradient(90deg, #3aa0ff, transparent);
|
|
}
|
|
.stat.s4::after {
|
|
background: linear-gradient(90deg, var(--danger), transparent);
|
|
}
|
|
|
|
.sec-head {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
margin: 30px 0 16px;
|
|
}
|
|
.sec-head h2 {
|
|
font-family: 'Saira Condensed';
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.12em;
|
|
font-size: 15px;
|
|
color: var(--ink);
|
|
}
|
|
.sec-head .rule {
|
|
flex: 1;
|
|
height: 1px;
|
|
background: linear-gradient(90deg, var(--line-bright), transparent);
|
|
}
|
|
.sec-head .count {
|
|
font-family: 'JetBrains Mono';
|
|
font-size: 11px;
|
|
color: var(--ink-faint);
|
|
}
|
|
|
|
/* app grid */
|
|
.grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(248px, 1fr));
|
|
gap: 14px;
|
|
}
|
|
.node {
|
|
background: linear-gradient(180deg, var(--panel), var(--panel-2));
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
padding: 16px;
|
|
cursor: pointer;
|
|
position: relative;
|
|
transition:
|
|
transform 0.15s,
|
|
border-color 0.15s,
|
|
box-shadow 0.15s;
|
|
overflow: hidden;
|
|
}
|
|
.node::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background: radial-gradient(circle at 100% 0%, rgba(54, 224, 164, 0.08), transparent 60%);
|
|
opacity: 0;
|
|
transition: 0.2s;
|
|
}
|
|
.node:hover {
|
|
transform: translateY(-2px);
|
|
border-color: var(--line-bright);
|
|
box-shadow: 0 10px 30px -12px #000;
|
|
}
|
|
.node:hover::before {
|
|
opacity: 1;
|
|
}
|
|
.node-top {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
.node-ico {
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: var(--radius);
|
|
background: #0a0f13;
|
|
border: 1px solid var(--line);
|
|
display: grid;
|
|
place-items: center;
|
|
font-size: 18px;
|
|
flex-shrink: 0;
|
|
}
|
|
.node-name {
|
|
font-weight: 600;
|
|
font-size: 15px;
|
|
line-height: 1.1;
|
|
}
|
|
.node-cat {
|
|
font-family: 'Saira Condensed';
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.1em;
|
|
font-size: 10px;
|
|
color: var(--ink-faint);
|
|
margin-top: 3px;
|
|
}
|
|
.led {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
margin-left: auto;
|
|
flex-shrink: 0;
|
|
position: relative;
|
|
}
|
|
.led.ok {
|
|
background: var(--accent);
|
|
box-shadow: 0 0 10px var(--accent);
|
|
}
|
|
.led.ok::after {
|
|
content: '';
|
|
position: absolute;
|
|
inset: -4px;
|
|
border-radius: 50%;
|
|
border: 1px solid var(--accent);
|
|
opacity: 0.4;
|
|
animation: ping 2s ease-out infinite;
|
|
}
|
|
.led.warn {
|
|
background: var(--warn);
|
|
box-shadow: 0 0 10px var(--warn);
|
|
}
|
|
.led.bad {
|
|
background: var(--danger);
|
|
box-shadow: 0 0 10px var(--danger);
|
|
}
|
|
@keyframes ping {
|
|
0% {
|
|
transform: scale(0.8);
|
|
opacity: 0.5;
|
|
}
|
|
100% {
|
|
transform: scale(2.2);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
.node-foot {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-top: 16px;
|
|
padding-top: 12px;
|
|
border-top: 1px solid var(--line);
|
|
}
|
|
.node-foot .up {
|
|
font-family: 'JetBrains Mono';
|
|
font-size: 11px;
|
|
color: var(--ink-dim);
|
|
}
|
|
.node-foot .up b {
|
|
color: var(--accent);
|
|
font-weight: 500;
|
|
}
|
|
.node-foot .up.bad b {
|
|
color: var(--danger);
|
|
}
|
|
.spark {
|
|
height: 22px;
|
|
width: 96px;
|
|
}
|
|
|
|
.footer-note {
|
|
margin-top: 40px;
|
|
text-align: center;
|
|
font-family: 'JetBrains Mono';
|
|
font-size: 11px;
|
|
color: var(--ink-faint);
|
|
}
|
|
|
|
/* entrance */
|
|
@keyframes rise {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: none;
|
|
}
|
|
}
|
|
.node,
|
|
.stat {
|
|
animation: rise 0.5s both;
|
|
}
|
|
.stat:nth-child(2) {
|
|
animation-delay: 0.05s;
|
|
}
|
|
.stat:nth-child(3) {
|
|
animation-delay: 0.1s;
|
|
}
|
|
.stat:nth-child(4) {
|
|
animation-delay: 0.15s;
|
|
}
|
|
.grid .node:nth-child(1) {
|
|
animation-delay: 0.1s;
|
|
}
|
|
.grid .node:nth-child(2) {
|
|
animation-delay: 0.16s;
|
|
}
|
|
.grid .node:nth-child(3) {
|
|
animation-delay: 0.22s;
|
|
}
|
|
.grid .node:nth-child(4) {
|
|
animation-delay: 0.28s;
|
|
}
|
|
.grid .node:nth-child(5) {
|
|
animation-delay: 0.34s;
|
|
}
|
|
.grid .node:nth-child(6) {
|
|
animation-delay: 0.4s;
|
|
}
|
|
.grid .node:nth-child(7) {
|
|
animation-delay: 0.46s;
|
|
}
|
|
.grid .node:nth-child(8) {
|
|
animation-delay: 0.52s;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="app">
|
|
<!-- Rail -->
|
|
<nav class="rail">
|
|
<div class="logo" title="Launcher">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<rect x="3" y="3" width="7" height="7" />
|
|
<rect x="14" y="3" width="7" height="7" />
|
|
<rect x="14" y="14" width="7" height="7" />
|
|
<rect x="3" y="14" width="7" height="7" />
|
|
</svg>
|
|
</div>
|
|
<a class="rail-btn active" title="Overview"
|
|
><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
<rect x="3" y="3" width="18" height="18" rx="2" />
|
|
<line x1="3" y1="9" x2="21" y2="9" />
|
|
<line x1="9" y1="21" x2="9" y2="9" /></svg
|
|
></a>
|
|
<a class="rail-btn" title="Apps"
|
|
><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
<circle cx="12" cy="12" r="9" />
|
|
<line x1="3" y1="12" x2="21" y2="12" />
|
|
<path d="M12 3a14 14 0 0 1 0 18 14 14 0 0 1 0-18" /></svg
|
|
></a>
|
|
<a class="rail-btn" title="Status"
|
|
><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
<path d="M22 12h-4l-3 9L9 3l-3 9H2" /></svg
|
|
></a>
|
|
<a class="rail-btn" title="Admin"
|
|
><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
<circle cx="12" cy="12" r="3" />
|
|
<path
|
|
d="M19 12a7 7 0 0 0-.1-1l2-1.6-2-3.4-2.4 1a7 7 0 0 0-1.7-1l-.4-2.6h-4l-.4 2.6a7 7 0 0 0-1.7 1l-2.4-1-2 3.4 2 1.6a7 7 0 0 0 0 2l-2 1.6 2 3.4 2.4-1a7 7 0 0 0 1.7 1l.4 2.6h4l.4-2.6a7 7 0 0 0 1.7-1l2.4 1 2-3.4-2-1.6a7 7 0 0 0 .1-1z"
|
|
/></svg
|
|
></a>
|
|
<div class="rail-spacer"></div>
|
|
<div class="rail-avatar">AD</div>
|
|
</nav>
|
|
|
|
<!-- Main -->
|
|
<div class="main">
|
|
<header class="topbar">
|
|
<div class="crumbs">SYSTEMS / <b>OVERVIEW</b></div>
|
|
<div class="search">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<circle cx="11" cy="11" r="7" />
|
|
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
|
</svg>
|
|
Search apps, boards, commands…
|
|
<span class="kbd">⌘K</span>
|
|
</div>
|
|
<div class="ico-btn" title="Notifications">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
<path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9" />
|
|
<path d="M13.7 21a2 2 0 0 1-3.4 0" />
|
|
</svg>
|
|
</div>
|
|
<div class="ico-btn" title="Theme">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
|
<circle cx="12" cy="12" r="4" />
|
|
<path
|
|
d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="content">
|
|
<!-- Status line -->
|
|
<div class="statline">
|
|
<div class="stat">
|
|
<div class="lbl">Services Online</div>
|
|
<div class="val ok">08 / 10</div>
|
|
<div class="sub">2 require attention</div>
|
|
</div>
|
|
<div class="stat s2">
|
|
<div class="lbl">Avg Response</div>
|
|
<div class="val warn">
|
|
142<span style="font-size: 14px; color: var(--ink-faint)"> ms</span>
|
|
</div>
|
|
<div class="sub">p95 over 24h</div>
|
|
</div>
|
|
<div class="stat s3">
|
|
<div class="lbl">Fleet Uptime</div>
|
|
<div class="val" style="color: #3aa0ff">99.4%</div>
|
|
<div class="sub">rolling 30 days</div>
|
|
</div>
|
|
<div class="stat s4">
|
|
<div class="lbl">UPS Load</div>
|
|
<div class="val bad">61%</div>
|
|
<div class="sub">est. 38 min on battery</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Favorites / pinned -->
|
|
<div class="sec-head">
|
|
<h2>Pinned Services</h2>
|
|
<span class="rule"></span><span class="count">8 ACTIVE</span>
|
|
</div>
|
|
<div class="grid">
|
|
<!-- 1 Jellyfin -->
|
|
<div class="node">
|
|
<div class="node-top">
|
|
<div class="node-ico">🎬</div>
|
|
<div>
|
|
<div class="node-name">Jellyfin</div>
|
|
<div class="node-cat">Media</div>
|
|
</div>
|
|
<div class="led ok"></div>
|
|
</div>
|
|
<div class="node-foot">
|
|
<svg class="spark" viewBox="0 0 96 22" preserveAspectRatio="none">
|
|
<polyline
|
|
fill="none"
|
|
stroke="#36e0a4"
|
|
stroke-width="1.5"
|
|
points="0,16 12,14 24,17 36,9 48,12 60,7 72,10 84,5 96,8"
|
|
/></svg
|
|
><span class="up"><b>99.9%</b> 24h</span>
|
|
</div>
|
|
</div>
|
|
<!-- 2 Immich -->
|
|
<div class="node">
|
|
<div class="node-top">
|
|
<div class="node-ico">📷</div>
|
|
<div>
|
|
<div class="node-name">Immich</div>
|
|
<div class="node-cat">Photos</div>
|
|
</div>
|
|
<div class="led ok"></div>
|
|
</div>
|
|
<div class="node-foot">
|
|
<svg class="spark" viewBox="0 0 96 22" preserveAspectRatio="none">
|
|
<polyline
|
|
fill="none"
|
|
stroke="#36e0a4"
|
|
stroke-width="1.5"
|
|
points="0,12 12,13 24,11 36,12 48,10 60,11 72,9 84,11 96,10"
|
|
/></svg
|
|
><span class="up"><b>100%</b> 24h</span>
|
|
</div>
|
|
</div>
|
|
<!-- 3 Gitea -->
|
|
<div class="node">
|
|
<div class="node-top">
|
|
<div class="node-ico">🌿</div>
|
|
<div>
|
|
<div class="node-name">Gitea</div>
|
|
<div class="node-cat">Git</div>
|
|
</div>
|
|
<div class="led ok"></div>
|
|
</div>
|
|
<div class="node-foot">
|
|
<svg class="spark" viewBox="0 0 96 22" preserveAspectRatio="none">
|
|
<polyline
|
|
fill="none"
|
|
stroke="#36e0a4"
|
|
stroke-width="1.5"
|
|
points="0,14 12,10 24,12 36,8 48,11 60,9 72,13 84,8 96,9"
|
|
/></svg
|
|
><span class="up"><b>99.8%</b> 24h</span>
|
|
</div>
|
|
</div>
|
|
<!-- 4 Portainer -->
|
|
<div class="node">
|
|
<div class="node-top">
|
|
<div class="node-ico">🐳</div>
|
|
<div>
|
|
<div class="node-name">Portainer</div>
|
|
<div class="node-cat">Containers</div>
|
|
</div>
|
|
<div class="led warn"></div>
|
|
</div>
|
|
<div class="node-foot">
|
|
<svg class="spark" viewBox="0 0 96 22" preserveAspectRatio="none">
|
|
<polyline
|
|
fill="none"
|
|
stroke="#ffb020"
|
|
stroke-width="1.5"
|
|
points="0,10 12,12 24,9 36,15 48,11 60,18 72,12 84,16 96,13"
|
|
/></svg
|
|
><span class="up"><b style="color: var(--warn)">98.1%</b> 24h</span>
|
|
</div>
|
|
</div>
|
|
<!-- 5 Pi-hole -->
|
|
<div class="node">
|
|
<div class="node-top">
|
|
<div class="node-ico">🛡️</div>
|
|
<div>
|
|
<div class="node-name">Pi-hole</div>
|
|
<div class="node-cat">DNS</div>
|
|
</div>
|
|
<div class="led ok"></div>
|
|
</div>
|
|
<div class="node-foot">
|
|
<svg class="spark" viewBox="0 0 96 22" preserveAspectRatio="none">
|
|
<polyline
|
|
fill="none"
|
|
stroke="#36e0a4"
|
|
stroke-width="1.5"
|
|
points="0,13 12,11 24,12 36,10 48,11 60,9 72,10 84,8 96,9"
|
|
/></svg
|
|
><span class="up"><b>100%</b> 24h</span>
|
|
</div>
|
|
</div>
|
|
<!-- 6 Planka -->
|
|
<div class="node">
|
|
<div class="node-top">
|
|
<div class="node-ico">📋</div>
|
|
<div>
|
|
<div class="node-name">Planka</div>
|
|
<div class="node-cat">Kanban</div>
|
|
</div>
|
|
<div class="led ok"></div>
|
|
</div>
|
|
<div class="node-foot">
|
|
<svg class="spark" viewBox="0 0 96 22" preserveAspectRatio="none">
|
|
<polyline
|
|
fill="none"
|
|
stroke="#36e0a4"
|
|
stroke-width="1.5"
|
|
points="0,15 12,12 24,14 36,11 48,12 60,10 72,12 84,9 96,11"
|
|
/></svg
|
|
><span class="up"><b>99.5%</b> 24h</span>
|
|
</div>
|
|
</div>
|
|
<!-- 7 Deluge -->
|
|
<div class="node">
|
|
<div class="node-top">
|
|
<div class="node-ico">⬇️</div>
|
|
<div>
|
|
<div class="node-name">Deluge</div>
|
|
<div class="node-cat">Downloads</div>
|
|
</div>
|
|
<div class="led bad"></div>
|
|
</div>
|
|
<div class="node-foot">
|
|
<svg class="spark" viewBox="0 0 96 22" preserveAspectRatio="none">
|
|
<polyline
|
|
fill="none"
|
|
stroke="#ff4d5e"
|
|
stroke-width="1.5"
|
|
points="0,9 12,11 24,14 36,12 48,18 60,16 72,20 84,19 96,21"
|
|
/></svg
|
|
><span class="up bad"><b>OFFLINE</b></span>
|
|
</div>
|
|
</div>
|
|
<!-- 8 Pi-hole / NPM -->
|
|
<div class="node">
|
|
<div class="node-top">
|
|
<div class="node-ico">🔀</div>
|
|
<div>
|
|
<div class="node-name">Nginx Proxy Mgr</div>
|
|
<div class="node-cat">Network</div>
|
|
</div>
|
|
<div class="led ok"></div>
|
|
</div>
|
|
<div class="node-foot">
|
|
<svg class="spark" viewBox="0 0 96 22" preserveAspectRatio="none">
|
|
<polyline
|
|
fill="none"
|
|
stroke="#36e0a4"
|
|
stroke-width="1.5"
|
|
points="0,12 12,11 24,12 36,10 48,11 60,11 72,9 84,10 96,9"
|
|
/></svg
|
|
><span class="up"><b>99.9%</b> 24h</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="footer-note">
|
|
// COMMAND DECK — Saira + JetBrains Mono · tactical dark · LED telemetry · monospace
|
|
data
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|