//! Offline-only web UI. Everything the browser needs (HTML, CSS, JS, SVG //! logo) is embedded in the compiled binary via `include_str!` / //! `include_bytes!`. No CDN, no external fonts, no remote images — //! OpenPXE renders identically on an air-gapped network. //! //! Layout follows the Netbox Labs pattern: dark left sidebar with primary //! nav, top bar with secondary tabs, card-dense content panels. #![forbid(unsafe_code)] /// Render the top-level page. /// /// * `base_url` is interpolated into the footer so operators can see at /// a glance what URL clients are PXE-booting from. /// * `asset_version` is appended as `?v=…` to every asset URL so each /// release ships with brand-new asset URLs — browsers (and any /// intermediary proxy) can't keep serving last release's `app.js` /// when we know the new one is incompatible. Combined with /// `Cache-Control: no-cache, must-revalidate` on the asset handlers, /// the worst-case caching window is one version. /// * `logo_rev` is appended to the brand-mark and favicon URLs as an /// extra `&r=…` token. Unlike `asset_version` it changes every time /// the operator swaps the custom logo, so the top-left mark updates /// immediately on the next page load instead of being pinned to the /// release version (which only changes on upgrade). `index.html` /// itself is served `no-cache`, so the fresh token lands as soon as /// the operator reloads after an upload. /// * `has_custom_logo` switches the sidebar brand block between the /// bundled mark + "OpenPXE" wordmark (false) and a FleetDM-style /// full-width custom logo with the wordmark hidden (true). Rendered /// server-side so there's no flash of the default mark before JS runs. #[must_use] pub fn index_html( base_url: &str, asset_version: &str, logo_rev: u64, has_custom_logo: bool, ) -> String { let brand_class = if has_custom_logo { "brand has-custom-logo" } else { "brand" }; INDEX_HTML .replace("{{BASE_URL}}", base_url) .replace("{{ASSET_VERSION}}", asset_version) .replace("{{LOGO_REV}}", &logo_rev.to_string()) .replace("{{BRAND_CLASS}}", brand_class) } #[must_use] pub fn app_js() -> &'static str { APP_JS } #[must_use] pub fn app_css() -> &'static str { APP_CSS } #[must_use] pub fn logo_svg() -> &'static str { LOGO_SVG } /// Larger, faster-cycling rainbow disc — used for the page-load /// transition and the imaging-progress widget on Dashboard / Queue. /// Pure SVG + SMIL, no JS, no GIF. #[must_use] pub fn loader_svg() -> &'static str { LOADER_SVG } const INDEX_HTML: &str = include_str!("index.html"); const APP_CSS: &str = include_str!("app.css"); const APP_JS: &str = include_str!("app.js"); const LOGO_SVG: &str = include_str!("logo.svg"); const LOADER_SVG: &str = include_str!("loader.svg");