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/code_red.rs

//! **CODE RED on every page** (owner 2026-10-18): one red blinking banner under the top bar —
//! site · box · what is down · the error · the next action — for every thing an automatic path
//! left down and no operator has acknowledged yet. Monetize keeps them and mails them; this
//! only shows them and sends the operator's Ack.

use serde::{Deserialize, Serialize};

pub const CODE_RED_API: &str = "/api/code-red";
/// How often every page reads CODE RED again.
pub const REREAD_MS: u32 = 10_000;

#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct CodeRedJson {
    pub key: String,
    pub site: String,
    pub r#box: String,
    pub what: String,
    pub error: String,
    pub next: String,
    pub raised_at_ms: i64,
    pub acked_by: String,
    pub acked_at_ms: i64,
}

#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct CodeRedsJson {
    pub items: Vec<CodeRedJson>,
}

/// Pure: **the banner's lines** — `(key, the line, the next action)` for every one not yet
/// acknowledged: `CODE RED · <site> · <box> — <what>: <error>`.
pub fn banner_lines(r: &CodeRedsJson) -> Vec<(String, String, String)> {
    r.items
        .iter()
        .filter(|c| c.acked_at_ms == 0)
        .map(|c| {
            let mut head = vec!["CODE RED".to_string()];
            for part in [&c.site, &c.r#box] {
                if !part.trim().is_empty() {
                    head.push(part.trim().to_string());
                }
            }
            let what = if c.error.trim().is_empty() {
                c.what.trim().to_string()
            } else {
                format!("{}: {}", c.what.trim(), c.error.trim())
            };
            (
                c.key.clone(),
                format!("{} — {what}", head.join(" · ")),
                c.next.trim().to_string(),
            )
        })
        .collect()
}

/// The Ack's path for one.
pub fn ack_path(key: &str) -> String {
    format!("{CODE_RED_API}/{}/ack", crate::fmt::percent_encode(key))
}

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

    #[test]
    fn the_banner_says_site_box_what_error_and_next_and_hides_the_seen() {
        let r = CodeRedsJson {
            items: vec![
                CodeRedJson {
                    key: "update:gunnarrs:twin".into(),
                    site: "gunnar.rs".into(),
                    r#box: "twin".into(),
                    what: "the update stopped at the twin".into(),
                    error: "HTTP 404 STORAGE_NOT_FOUND".into(),
                    next: "start the twin, then press Update again".into(),
                    ..Default::default()
                },
                CodeRedJson {
                    key: "seen".into(),
                    site: "test2.gunnar.rs".into(),
                    acked_at_ms: 5,
                    ..Default::default()
                },
            ],
        };
        let l = banner_lines(&r);
        assert_eq!(l.len(), 1, "the acknowledged one is off the banner");
        assert_eq!(l[0].1, "CODE RED · gunnar.rs · twin — the update stopped at the twin: HTTP 404 STORAGE_NOT_FOUND");
        assert_eq!(l[0].2, "start the twin, then press Update again");
        assert_eq!(
            ack_path("update:gunnarrs:twin"),
            "/api/code-red/update%3Agunnarrs%3Atwin/ack"
        );
        assert!(banner_lines(&CodeRedsJson::default()).is_empty());
    }
}