Phase 3: Svelte 5 UI skeleton
Svelte 5 + Vite + TypeScript dashboard that consumes kamado-api over REST for the first paint and then subscribes to /api/ws for live updates. Zero runtime deps beyond svelte itself; plain CSS, no component library. Layout: - Header with brand, WebSocket status badge, chain + height - PoolOverview: hashrate (1m/5m/1h/24h), miner count, network hashrate + diff, pool share (ppb), expected time to block, uptime - BlocksTable: recent solves from the in-memory ring (will be SQLite-backed in Phase 2b.5) - BestShares: top-10 workers by bestever, falling back to bestdiff when the ckpool patch isn't present - MinersTable: joined view of stratum clients and workers with user-agent-based hardware detection (Bitaxe, NerdQAxe, Antminer, ...), hashrate, best-round, best-ever, last share State is a single $state() snapshot store in svelte-runes form; components read from it via $derived. The store does one initial REST snapshot fetch, then owns the WebSocket with exponential backoff reconnects. Vite dev server on :5173 proxies /api and /api/ws to localhost:8080 so you can run `make ui-dev` alongside `make up` in development. Production serving (bundled into the Go binary via embed, behind / on :8080) lands in Phase 4.
This commit is contained in:
+151
@@ -0,0 +1,151 @@
|
||||
:root {
|
||||
color-scheme: dark light;
|
||||
--bg: #0b0e14;
|
||||
--bg-alt: #11151d;
|
||||
--bg-card: #151a24;
|
||||
--bg-hover: #1c2230;
|
||||
--border: #232a3a;
|
||||
--fg: #e6e9ef;
|
||||
--fg-dim: #8a92a6;
|
||||
--accent: #ff7a3a;
|
||||
--accent-dim: #b8501f;
|
||||
--good: #5ce0a8;
|
||||
--bad: #ff6b6b;
|
||||
--warn: #f5c447;
|
||||
font-family: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
body {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
button {
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
padding: 0.5em 1em;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
code,
|
||||
.mono {
|
||||
font-family: "JetBrains Mono", "Fira Code", ui-monospace, SFMono-Regular,
|
||||
Menlo, monospace;
|
||||
font-size: 0.92em;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 1.25rem 1.5rem;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.grid-4 {
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
}
|
||||
.grid-2 {
|
||||
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: var(--fg-dim);
|
||||
font-size: 0.85em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
margin-bottom: 0.35em;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 1.7rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.stat-sub {
|
||||
color: var(--fg-dim);
|
||||
font-size: 0.85em;
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
th,
|
||||
td {
|
||||
text-align: left;
|
||||
padding: 0.55rem 0.85rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
th {
|
||||
font-weight: 500;
|
||||
color: var(--fg-dim);
|
||||
font-size: 0.82em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
tbody tr:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
td.num,
|
||||
th.num {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.15em 0.55em;
|
||||
border-radius: 999px;
|
||||
font-size: 0.75em;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--bg-alt);
|
||||
}
|
||||
.badge.good {
|
||||
color: var(--good);
|
||||
border-color: #2f5c48;
|
||||
background: #0f2419;
|
||||
}
|
||||
.badge.bad {
|
||||
color: var(--bad);
|
||||
border-color: #5c2f2f;
|
||||
background: #241010;
|
||||
}
|
||||
.badge.warn {
|
||||
color: var(--warn);
|
||||
border-color: #5c502f;
|
||||
background: #241f10;
|
||||
}
|
||||
Reference in New Issue
Block a user