Files
OpenPXE/crates/webui/src/index.html
T
Miles WardandClaude Opus 4.8 7adf5e2918 v0.5.2: FleetDM login split, 3-slot branding, unattended installs
Authentication / login:
- Separate the local username/password form from the SSO "Sign in with …"
  button (FleetDM-style divider + optional IdP logo); credential fields no
  longer double as the SSO trigger. Settings → SSO copy now says SAML is live.

Branding — three slots (light / dark / client) on one row:
- Light/Dark feed the top-left mark + sign-in page by active theme (with
  cross-theme fallback; theme toggle swaps the logo live). Client feeds the
  PXE boot-menu background. Favicon pinned to the bundled mark via a new
  /assets/favicon.svg endpoint. Legacy single logo migrates to dark + client.
- BrandingStore refactored to per-slot storage; /api/branding/logo/:slot.

Unattended installs (Storage → Advanced):
- New UnattendedStore (iso-store) + /api/unattended upload/list/delete and a
  public templated serve at /unattended/:id (+ NoCloud seed dir for
  autoinstall). Accepts .ks/.cfg/.seed/.yaml/.yml/.xml/user-data; classified
  on upload; stored in its own unattended/ dir, never the ISO listing/menu.
- {{HOSTNAME}}/{{IP}}/{{MAC}} substituted per host at serve time.

Host pins + Queue profiles:
- HostBinding + QueueEntry carry an optional DeployProfile (auto_hostname /
  auto_ip / unattended_file). Hosts pin form + a per-device Queue "Profile"
  button collect them. On boot, a matched MAC has the right kernel arg
  injected (inst.ks= / preseed url= / autoinstall ds=nocloud-net) and the
  hostname/IP templated into the served answer file. DHCP stays proxy-only.

Storage:
- Remote shares default protocol is now NFS; updated descriptive copy.

235 tests green, clippy clean. Still a single static musl binary, pure Rust.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-05-31 16:11:04 -04:00

131 lines
5.8 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="dark light" />
<title>OpenPXE</title>
<!-- v0.4.61: the `?v=…` query string is replaced by the server at
request time with the running OpenPXE version. That guarantees a
fresh URL on every upgrade so browsers (and intermediary proxies)
can't keep serving stale JS / CSS / branding from before the
deploy. Combined with `Cache-Control: no-cache, must-revalidate`
on the asset handlers, the practical caching window is one
version. -->
<link rel="stylesheet" href="/assets/app.css?v={{ASSET_VERSION}}" />
<!-- v0.5.2: favicon is pinned to the bundled OpenPXE mark (its own
endpoint, decoupled from operator branding) for tab-icon
continuity regardless of any uploaded light/dark/client logo. -->
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg?v={{ASSET_VERSION}}" />
<!-- Theme is read from localStorage *before* paint to avoid the
dark→light flash on every navigation. Falls back to the OS
preference and finally to dark. -->
<script>
(function() {
try {
var stored = localStorage.getItem('openpxe-theme');
var theme = stored || (matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark');
document.documentElement.setAttribute('data-theme', theme);
} catch (e) {
document.documentElement.setAttribute('data-theme', 'dark');
}
})();
</script>
</head>
<body>
<div class="shell">
<aside class="sidebar">
<div class="{{BRAND_CLASS}}">
<img src="/assets/logo.svg?v={{ASSET_VERSION}}&r={{LOGO_REV}}" alt="OpenPXE" />
<strong>OpenPXE</strong>
</div>
<nav>
<a data-view="dashboard" class="active">Dashboard</a>
<a data-view="network">Network</a>
<a data-view="queue">
Queue
<span class="count" data-bind="queue_count">0</span>
</a>
<a data-view="storage">
Storage
<span class="count" data-bind="iso_count">0</span>
</a>
<a data-view="hosts">
Hosts
<span class="count" data-bind="host_count">0</span>
</a>
<a data-view="terminal">Terminal</a>
<a data-view="settings">Settings</a>
<a data-view="about">About</a>
</nav>
<div class="footer">
<div class="status-row">
<span class="dot" data-bind="ready_dot" title="Server readiness"></span>
<span class="status-label">Service status:</span>
<span class="status-value" data-bind="ready_label">checking…</span>
</div>
<div class="footer-sub">Advertised to clients</div>
<code>{{BASE_URL}}</code>
<!-- The brand badge at the top can be overridden by operator-uploaded
logos; keep "OpenPXE v…" pinned in the footer so the backend
identity is always visible regardless of branding. -->
<div class="footer-version">OpenPXE&nbsp;v<span data-bind="version">0.4.63</span></div>
</div>
</aside>
<header class="topbar">
<h1 data-bind="view_title">Dashboard</h1>
<div class="spacer"></div>
<span class="chip"><strong data-bind="iso_count2">0</strong>&nbsp;images</span>
<span class="chip"><strong data-bind="client_count2">0</strong>&nbsp;clients</span>
<span class="chip"><strong data-bind="queue_count2">0</strong>&nbsp;in queue</span>
<button id="theme-toggle" class="theme-toggle" type="button"
aria-label="Toggle light/dark theme" title="Toggle theme (T)">
<!-- Two glyphs; CSS shows whichever matches the active theme. -->
<svg class="t-sun" viewBox="0 0 24 24" width="18" height="18" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="12" cy="12" r="4.2"/>
<line x1="12" y1="2.5" x2="12" y2="5.5"/>
<line x1="12" y1="18.5" x2="12" y2="21.5"/>
<line x1="2.5" y1="12" x2="5.5" y2="12"/>
<line x1="18.5" y1="12" x2="21.5" y2="12"/>
<line x1="5.2" y1="5.2" x2="7.3" y2="7.3"/>
<line x1="16.7" y1="16.7" x2="18.8" y2="18.8"/>
<line x1="5.2" y1="18.8" x2="7.3" y2="16.7"/>
<line x1="16.7" y1="7.3" x2="18.8" y2="5.2"/>
</svg>
<svg class="t-moon" viewBox="0 0 24 24" width="18" height="18" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20.5 14A8 8 0 0 1 10 3.5 a8 8 0 1 0 10.5 10.5z"/>
</svg>
</button>
<!-- v0.4.6: signed-in operator menu. Sits next to the theme toggle
in the top-right corner so the sidebar footer stays clean for
the "Service status / Advertised URL / Backend version" trio.
The whole block is hidden until /api/me confirms a session. -->
<div class="user-menu" data-bind="user_menu_wrap" style="display:none">
<button id="user-menu-btn" class="user-btn" type="button"
aria-label="Account menu" aria-haspopup="true" aria-expanded="false"
title="Account">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="8" r="3.6"/>
<path d="M4.5 20a7.5 7.5 0 0 1 15 0"/>
</svg>
</button>
<div id="user-menu-pop" class="user-pop" data-bind="user_menu_pop" hidden>
<div class="user-pop-name" data-bind="user_pop_name"></div>
<button type="button" class="user-pop-item" data-bind="user_pop_edit">Edit account</button>
<button type="button" class="user-pop-item user-pop-danger" data-bind="user_pop_logout">Sign out</button>
</div>
</div>
</header>
<main class="main" id="view-root"></main>
</div>
<script src="/assets/app.js?v={{ASSET_VERSION}}"></script>
</body>
</html>