v0.4.0: upload telemetry, host log, jet-black UI

- Upload reliability + diagnostics:
  - api_upload_iso now distinguishes clean EOF from mid-stream errors;
    a truncated multipart body (proxy buffer cap, network drop) returns
    400 with the cause and a "try the LAN IP" hint instead of silently
    finalising a partial file.
  - Per-stage tracing (begin/MB-watermark/finish/abort) so a stuck
    upload is debuggable from the Terminal tab.
  - Web upload UI surfaces bytes/total, percent, throughput, ETA, and
    maps 413/502/504/network-drop to actionable hints.
- New BootLog feature under Hosts:
  - openpxe-core::BootLog — bounded in-memory ring (500) + append-only
    JSONL on disk, recording (timestamp, mac, ip, target_id,
    target_title) every time a boot entry script is served.
  - iPXE per-entry chain URLs grow ?mac=${mac}; password prompt
    submission carries it through; host-binding short-circuit uses the
    bound MAC. ConnectInfo<SocketAddr> wired for peer IP capture (with
    optional fallback so tower::oneshot in tests still works).
  - GET /api/boot-log endpoint + Host log table under the Hosts tab.
- UI changes:
  - Queue card header "Forge" → "Status".
  - Removed Tinkerbell attribution sentence from Hosts tab.
  - Topbar readiness chip moved into the sidebar footer as
    "Service status: Ready / Advertised to clients / <url>", grouping
    advertised PXE URL with operator-relevant status.
  - Jet-black dark palette (#000 / #0a0a0a / #141414 / #1c1c1c)
    replacing the blue-tinted ramp; terminal toolbar/input recoloured
    to match.
- 89 tests passing (was 85 in v0.3.2); cargo clippy --workspace
  --all-targets clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
Miles Ward
2026-05-24 13:10:40 -04:00
co-authored by Claude Opus 4.7
parent 115ba779da
commit ec171ede47
11 changed files with 767 additions and 78 deletions
+46 -23
View File
@@ -8,23 +8,26 @@
* CSS lands). */
:root {
/* Dark palette (default). */
--bg: #0b1018;
--bg-panel: #121826;
--bg-panel-2: #1a2334;
--bg-elev: #223047;
--fg: #e4e8ef;
--fg-dim: #8a94a7;
--fg-dimmer: #5a6379;
--accent: #00d4b4; /* Netbox-ish teal */
/* Jet-black dark palette (default). Modelled on Netbox Labs's
near-black product chrome — surfaces step from #000 → #0d → #16 → #1c
rather than the previous blue-tinted ramp, so the UI reads as a
genuine "dark" rather than "dim navy". */
--bg: #000000;
--bg-panel: #0a0a0a;
--bg-panel-2: #141414;
--bg-elev: #1c1c1c;
--fg: #e8eaed;
--fg-dim: #9aa0a6;
--fg-dimmer: #6b7077;
--accent: #00d4b4; /* Netbox-ish teal — kept for brand */
--accent-dim: #07a38c;
--warn: #ffb347;
--err: #ef6e6e;
--ok: #4ade80;
--border: #223047;
--border-soft: #172033;
--terminal-bg: #06090e;
--shadow-card: 0 1px 0 rgba(255,255,255,0.02), 0 8px 24px rgba(0,0,0,0.25);
--border: #1f1f1f;
--border-soft: #141414;
--terminal-bg: #000000;
--shadow-card: 0 1px 0 rgba(255,255,255,0.02), 0 8px 24px rgba(0,0,0,0.55);
--radius: 6px;
--radius-lg: 10px;
--sidebar-w: 240px;
@@ -113,10 +116,30 @@ code, kbd { font-family: var(--mono); font-size: 12.5px;
}
.sidebar nav a.active .count { background: var(--accent); color: #002923; }
.sidebar .footer {
padding: 10px 18px; border-top: 1px solid var(--border);
padding: 12px 18px; border-top: 1px solid var(--border);
color: var(--fg-dimmer); font-size: 11px;
display: flex; flex-direction: column; gap: 4px;
}
.sidebar .footer code { background: transparent; color: var(--fg-dim); padding: 0; }
.sidebar .footer code { background: transparent; color: var(--fg-dim); padding: 0;
font-size: 11px; word-break: break-all; }
.sidebar .footer .status-row {
display: flex; align-items: center; gap: 8px;
margin-bottom: 4px;
}
.sidebar .footer .status-row .dot {
width: 8px; height: 8px; border-radius: 50%; display: inline-block;
background: var(--fg-dimmer); flex: none;
}
.sidebar .footer .status-row .dot.ok { background: var(--ok);
box-shadow: 0 0 6px color-mix(in srgb, var(--ok) 60%, transparent); }
.sidebar .footer .status-row .dot.err { background: var(--err); }
.sidebar .footer .status-row .dot.warn { background: var(--warn); }
.sidebar .footer .status-label { color: var(--fg-dim); }
.sidebar .footer .status-value { color: var(--fg); font-weight: 600; }
.sidebar .footer .status-value.ok { color: var(--ok); }
.sidebar .footer .status-value.err { color: var(--err); }
.sidebar .footer .status-value.warn { color: var(--warn); }
.sidebar .footer .footer-sub { color: var(--fg-dimmer); margin-top: 2px; }
/* ── Top bar ───────────────────────────────────────────────────────── */
@@ -431,29 +454,29 @@ tr.unbootable td:first-child { border-left: 3px solid var(--warn); }
.terminal .input-row {
display: flex; align-items: center; gap: 8px;
padding: 8px 14px;
background: #0a0e15;
border-top: 1px solid #1d2330;
background: #050505;
border-top: 1px solid #181818;
}
.terminal .input-row .prompt { color: var(--accent); font-family: var(--mono); }
.terminal .input-row input {
flex: 1; background: transparent; border: 0; color: #e4e8ef;
flex: 1; background: transparent; border: 0; color: var(--fg);
font: inherit; font-family: var(--mono); font-size: 13px;
outline: none; padding: 4px 0;
}
.terminal .toolbar {
display: flex; gap: 8px; align-items: center;
padding: 8px 14px;
background: #0a0e15;
border-bottom: 1px solid #1d2330;
font-size: 12px; color: #8a94a7;
background: #050505;
border-bottom: 1px solid #181818;
font-size: 12px; color: var(--fg-dim);
}
.terminal .toolbar .right { margin-left: auto; display: flex; gap: 6px; }
.terminal .toolbar button {
padding: 3px 9px; font-size: 11px;
background: transparent; color: #8a94a7; border: 1px solid #1d2330;
background: transparent; color: var(--fg-dim); border: 1px solid #181818;
font-weight: 500;
}
.terminal .toolbar button:hover { color: #e4e8ef; background: #1d2330; }
.terminal .toolbar button:hover { color: var(--fg); background: #181818; }
/* ── About card ─────────────────────────────────────────────────── */
.about-hero { padding: 20px 24px; }