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/pages/settings.rs

//! **SETTINGS** — one estate's settings, as its own monetize answers them.
//!
//! The one thing that tells two estates of the same product apart lives here,
//! as the SETTING it is (owner 2026-10-02): which payment keys the estate
//! holds, live or test — read off the estate's own vendor block, never off its
//! name. Beside it: the names it is reached by, the clouds it may spend at,
//! and the key this console signs every call to it with.
//!
//! ★ UW-18: and the console's own doors an operator needs with no shell on the box —
//! the passkeys that sign in here and "sign every session out" (`/auth/passkey/credentials`,
//! `/auth/sessions/revoke-all`), and a ledger backup now (`/api/backup`, `Ledger.Export`
//! into monetize's own backup directory; restored by the box's restore verb).

use leptos::prelude::*;

use serde_json::{json, Value};

use crate::estate::{stripe_word, summary_path, SummaryJson};
use crate::pages::money::MoneyAction;
use crate::pages::shell::use_estate;
use crate::ui::{resolved, Badge, PageHeader, Panel, Tone};

#[component]
pub fn SettingsPage() -> impl IntoView {
    let estate = use_estate();
    let summary = LocalResource::new(move || {
        let e = estate.get();
        async move { crate::api::get::<SummaryJson>(&summary_path(&e)).await }
    });
    view! {
        <PageHeader title=move || format!("{} · Settings", estate.get()) subtitle="Current settings. They change with an install or upgrade."/>
        {move || resolved(summary.get(), None, move |s: SummaryJson| {
            let stripe = stripe_word(&s.vendors, &s.vendor_names, &s.site_vendor);
            let stripe_tone = match stripe { "live" => Tone::Live, "test" => Tone::Plain, _ => Tone::Warn };
            let host = s.control.as_deref().map(|c| c.trim_start_matches("https://").trim_end_matches('/').to_owned());
            view! {
                <div class="stack">
                    <Panel title="Payment keys">
                        <dl class="facts panel-body">
                            <dt>"Stripe"</dt>
                            <dd><Badge tone=stripe_tone label=crate::estate::stripe_label(stripe)/></dd>
                            <dt>"Every vendor"</dt>
                            <dd class="mono">{{ let lines = crate::estate::vendor_lines(&s.vendors, &s.vendor_names); if lines.is_empty() { "none".to_owned() } else { lines.join(" · ") } }}</dd>
                        </dl>
                        <p class="panel-body hint">"Which keys the estate holds is the one setting that tells two estates of the same product apart. It is read off the estate's own [vendors.stripe] block, which its install writes from the key it feeds. Money is counted live or test by each payment's own word, not by this setting."</p>
                    </Panel>
                    <Panel title="Names">
                        <dl class="facts panel-body">
                            <dt>"Estate"</dt>
                            <dd class="mono">{s.estate.clone()}</dd>
                            <dt>"Reached at"</dt>
                            <dd class="mono">{host.unwrap_or_else(|| "this console's own loopback".into())}</dd>
                            <dt>"Serves"</dt>
                            <dd class="mono">{if s.products.is_empty() { "—".to_owned() } else { s.products.join(" · ") }}</dd>
                            <dt>"Clouds"</dt>
                            <dd class="mono">{if s.clouds.is_empty() { "none".to_owned() } else { s.clouds.iter().map(|c| if *c == s.default_cloud { format!("{c} (purchases go here)") } else { c.clone() }).collect::<Vec<_>>().join(" · ") }}</dd>
                        </dl>
                    </Panel>
                    <Panel title="Keys">
                        <dl class="facts panel-body">
                            <dt>"This console signs with"</dt>
                            <dd class="mono">{s.signed_with.clone().unwrap_or_else(|| "no key: a loopback bearer".into())}</dd>
                            <dt>"Answering"</dt>
                            <dd>{if s.reached { view! { <Badge tone=Tone::Pass label="this key is accepted"/> }.into_any() } else if s.unfed { view! { <Badge tone=Tone::Bad label=crate::estate::UNFED/> }.into_any() } else { view! { <Badge tone=Tone::Bad label=crate::estate::NOT_REACHED/> }.into_any() }}</dd>
                        </dl>
                        <p class="panel-body hint">"The public fingerprint of the SSH key every call to this estate is signed with — the monetize-ui principal its install fed it. The estate's certificate is pinned by this console."</p>
                    </Panel>
                    <Passkeys/>
                    <GuestCalls/>
                    <Panel title="Backup">
                        <p class="panel-body hint">"The ledger and the usage readings, exported now into monetize's own backup directory (no secret is in it). The box's restore takes it back."</p>
                        <div class="panel-body">
                            <MoneyAction label="Back up now…" confirm_label="Confirm: export the ledger" path="/api/backup".to_owned() base=json!({}) on_done=Callback::new(|()| {})/>
                        </div>
                    </Panel>
                </div>
            }.into_any()
        })}
    }
}

/// One passkey as its line says it: its device name, whose it is, when it was made and
/// last used, and DISABLED when it is.
pub fn passkey_line(c: &Value) -> String {
    let when = |k: &str| {
        c[k].as_i64()
            .filter(|s| *s > 0)
            .map(|s| crate::fmt::date(s as u64 * 1000))
    };
    let mut out = vec![
        c["label"]
            .as_str()
            .filter(|l| !l.is_empty())
            .unwrap_or("unnamed key")
            .to_owned(),
        c["operator"].as_str().unwrap_or("operator").to_owned(),
    ];
    if let Some(d) = when("created") {
        out.push(format!("since {d}"));
    }
    out.push(
        when("last_used")
            .map(|d| format!("last used {d}"))
            .unwrap_or_else(|| "never used".into()),
    );
    if c["disabled"].as_bool() == Some(true) {
        out.push("DISABLED".into());
    }
    out.join(" · ")
}

/// **The passkeys that sign in to this console**, and one press that signs every session
/// out (each device signs in again with its passkey; this one too).
#[component]
fn Passkeys() -> impl IntoView {
    let data = LocalResource::new(|| async {
        crate::api::get::<Value>("/auth/passkey/credentials").await
    });
    view! {
        <Panel title="Passkeys">
            {move || {
                let read = data.get().and_then(|r| r.ok()).unwrap_or_default();
                let lines: Vec<String> = read["credentials"].as_array().map(|a| a.iter().map(passkey_line).collect()).unwrap_or_default();
                view! {
                    <ul class="panel-body mono">
                        {if lines.is_empty() { view! { <li class="hint">"none read"</li> }.into_any() } else { lines.into_iter().map(|l| view! { <li>{l}</li> }).collect_view().into_any() }}
                    </ul>
                }
            }}
            <div class="panel-body">
                <MoneyAction label="Sign every session out…" confirm_label="Confirm: sign out everywhere" path="/auth/sessions/revoke-all".to_owned() base=json!({}) on_done=Callback::new(|()| {})/>
            </div>
        </Panel>
    }
}

/// One guest counter row as its line says it: `vendor guest-stripe.wasm status: 10 calls, 1
/// failed, 25 ms on average, 90 ms at most`.
pub fn counter_line(c: &Value) -> String {
    let n = |k: &str| c[k].as_u64().unwrap_or_default();
    let s = |k: &str| c[k].as_str().unwrap_or("?").to_owned();
    let (calls, faults) = (n("calls"), n("faults"));
    let avg = n("total_ms").checked_div(calls).unwrap_or_default();
    let mut out = format!(
        "{} {} {}: {calls} call{}",
        s("kind"),
        s("guest"),
        s("op_name"),
        if calls == 1 { "" } else { "s" }
    );
    if faults > 0 {
        out.push_str(&format!(", {faults} failed"));
    }
    out.push_str(&format!(
        ", {avg} ms on average, {} ms at most",
        n("max_ms")
    ));
    out
}

/// **The calls this monetize made to its signed guests**, one line per guest op and vendor
/// call, as monetize counts them (`/api/counters`); since its last start.
#[component]
fn GuestCalls() -> impl IntoView {
    let data = LocalResource::new(|| async { crate::api::get::<Value>("/api/counters").await });
    view! {
        <Panel title="Guest calls">
            {move || {
                let read = data.get().and_then(|r| r.ok()).unwrap_or_default();
                let lines: Vec<String> = read["rows"].as_array().map(|a| a.iter().map(counter_line).collect()).unwrap_or_default();
                let since = read["since_ms"].as_u64().filter(|s| *s > 0).map(crate::fmt::date);
                view! {
                    <ul class="panel-body mono">
                        {if lines.is_empty() { view! { <li class="hint">"no guest called yet"</li> }.into_any() } else { lines.into_iter().map(|l| view! { <li>{l}</li> }).collect_view().into_any() }}
                    </ul>
                    <p class="panel-body hint">{format!("Every call to a signed guest (product, sign-up, payment vendor, cloud, DNS), counted by monetize since {}. A failed call is one the host ended (a bound, a trap): nothing came back.", since.unwrap_or_else(|| "its start".into()))}</p>
                }
            }}
        </Panel>
    }
}

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

    #[test]
    fn a_guest_counter_reads_as_its_calls_its_failures_and_its_times() {
        let c = json!({"kind": "vendor", "guest": "guest-stripe.wasm", "op": 3, "op_name": "status", "calls": 10, "faults": 1, "total_ms": 250, "max_ms": 90});
        assert_eq!(
            counter_line(&c),
            "vendor guest-stripe.wasm status: 10 calls, 1 failed, 25 ms on average, 90 ms at most"
        );
        let one = json!({"kind": "cloud", "guest": "guest-upcloud.wasm", "op_name": "lay", "calls": 1, "faults": 0, "total_ms": 4, "max_ms": 4});
        assert_eq!(
            counter_line(&one),
            "cloud guest-upcloud.wasm lay: 1 call, 4 ms on average, 4 ms at most"
        );
    }

    /// ★ UW-18: a passkey's line names the device, its operator, its days and a disabled key.
    #[test]
    fn a_passkey_reads_as_its_device_operator_and_days() {
        let k = json!({"id": "k1", "label": "laptop", "operator": "Rickard", "created": 1_760_000_000, "last_used": null, "disabled": false});
        assert_eq!(
            passkey_line(&k),
            format!(
                "laptop · Rickard · since {} · never used",
                crate::fmt::date(1_760_000_000_000)
            )
        );
        let off = json!({"label": "", "operator": "Henrik", "created": 0, "last_used": 1_760_000_000, "disabled": true});
        assert!(
            passkey_line(&off).starts_with("unnamed key · Henrik · last used ")
                && passkey_line(&off).ends_with(" · DISABLED"),
            "{}",
            passkey_line(&off)
        );
    }
}