Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-ui-web/src/narrate.rs

//! **"Logs" on a narrated site** (lane N2, 2026-10-20): the DEV-NARRATE boot logs the ONE box
//! keeps for a test site that says `narrate = true`, each machine's tail. The cells the page
//! shows, tested on the host; [`crate::pages::sites::SiteLogs`] only lays them out. Admin
//! only: the console's server refuses anyone else (`/api/narrate/…`), and the page offers
//! the button to an admin alone.

use serde::Deserialize;

/// The console's route head: `{NARRATE_API}{estate}` (JSON), `{NARRATE_API}{estate}/{vm}` (text).
pub const NARRATE_API: &str = "/api/narrate/";
/// How often an open Logs panel reads again.
pub const REREAD_MS: u32 = 5_000;

#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
#[serde(default)]
pub struct VmLogJson {
    pub vm: String,
    pub text: String,
    pub bytes: u64,
    pub updated_unix_ms: i64,
}

#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
#[serde(default)]
pub struct NarrateJson {
    pub estate: String,
    pub narrated: bool,
    pub words: String,
    pub vms: Vec<VmLogJson>,
}

/// `/api/narrate/<estate>`.
pub fn logs_path(estate: &str) -> String {
    format!("{NARRATE_API}{}", estate.trim())
}

/// `/api/narrate/<estate>/<vm>`: one machine's text, to open or save.
pub fn text_path(estate: &str, vm: &str) -> String {
    format!("{NARRATE_API}{}/{}", estate.trim(), vm.trim())
}

/// The button's words, and whether it is offered: an admin's alone.
pub fn logs_button(admin: bool, open: bool) -> Option<&'static str> {
    admin.then_some(if open { "Hide logs" } else { "Logs" })
}

/// One machine's heading: its role, what is kept, and an empty log said in words.
pub fn vm_head(v: &VmLogJson) -> String {
    if v.bytes == 0 {
        format!("{} — nothing heard yet", v.vm)
    } else {
        format!("{} — {} KiB kept", v.vm, v.bytes.div_ceil(1024))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn the_logs_cells_read_as_the_page_shows_them() {
        assert_eq!(
            logs_path(" test3-holger-rs "),
            "/api/narrate/test3-holger-rs"
        );
        assert_eq!(
            text_path("test3-holger-rs", "main"),
            "/api/narrate/test3-holger-rs/main"
        );
        assert_eq!(
            logs_button(false, false),
            None,
            "a viewer is offered no logs"
        );
        assert_eq!(
            (logs_button(true, false), logs_button(true, true)),
            (Some("Logs"), Some("Hide logs"))
        );
        assert_eq!(
            vm_head(&VmLogJson {
                vm: "front".into(),
                ..Default::default()
            }),
            "front — nothing heard yet"
        );
        assert_eq!(
            vm_head(&VmLogJson {
                vm: "main".into(),
                bytes: 2049,
                ..Default::default()
            }),
            "main — 3 KiB kept"
        );
    }
}