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

nordisk/holger-ui

src/front_certs.rs

//! **Admin → Certificates** (owner 2026-10-25: certificates must fix themselves) — the
//! pane's pure decisions, host-tested; `pages/front_certs.rs` lays them out.
//!
//! Thin: the FRONT says everything (holger-estate-front `certs_page`, edda estate-front's
//! `web::status`): valid until, next attempt, last reason, whether "try now" is taken, the
//! one-hour alert. The pane shows those lines and, where the front allows it, the button.

#![cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]

use serde::{Deserialize, Serialize};

/// The front's routes (not under `/-/`, which is the primary's).
pub const CERTS_PATH: &str = "/front/certs";
pub const TRY_PATH: &str = "/front/certs/try";

/// One certificate as the front says it.
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct Cert {
    pub host: String,
    pub hosts: Vec<String>,
    pub lines: Vec<String>,
    pub try_now: bool,
    pub alert: Option<String>,
}

/// `GET /front/certs`.
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct Certs {
    pub certificates: Vec<Cert>,
}

/// Pure: **the pane's reading of the front's answer** — the certificates, or the words why
/// there are none to show (a console not served by an estate front has no such route).
pub fn read(status: u16, body: &str) -> Result<Certs, String> {
    match status {
        200 => serde_json::from_str(body).map_err(|e| format!("the front's answer is not the certificate report: {e}")),
        404 => Err("this console is not served by an estate front: it has no certificates of its own to show".into()),
        _ => Err(if body.trim().is_empty() { format!("the front answered HTTP {status}") } else { body.trim().to_string() }),
    }
}

/// Pure: **"try now"** — `(may be pressed, why not)`: only where the front allows it (the
/// front refuses while a failed validation's wait runs; the pane is already the admin's).
pub fn button(c: &Cert) -> (bool, String) {
    if c.try_now {
        (true, String::new())
    } else {
        (false, "the last attempt failed Let's Encrypt's validation; the front waits it out".into())
    }
}

/// Pure: the body of a try now.
pub fn try_body(c: &Cert) -> serde_json::Value {
    serde_json::json!({ "host": c.host })
}

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

    #[test]
    fn the_pane_shows_the_fronts_words_and_offers_try_now_only_where_the_front_allows_it() {
        let c = read(200, r#"{"certificates":[{"host":"holger.example","hosts":["holger.example"],"lines":["valid until:  no valid certificate","last reason:  dns: holger.example does not point at this box (it resolves to nothing)"],"try_now":true,"alert":null}]}"#).unwrap();
        let one = &c.certificates[0];
        assert_eq!(one.lines.len(), 2);
        assert_eq!(button(one), (true, String::new()));
        assert_eq!(try_body(one)["host"], "holger.example");
        let refused = Cert { try_now: false, ..one.clone() };
        assert!(!button(&refused).0 && button(&refused).1.contains("validation"));
        assert!(read(404, "").unwrap_err().contains("not served by an estate front"));
        assert!(read(403, "REFUSED [front-certs-admin] the certificates are an admin's (you are reader)").unwrap_err().contains("front-certs-admin"));
        assert!(read(200, "not json").is_err());
    }
}