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

nordisk/holger-ui

src/pages/admin.rs

//! Admin — operational detail about the machine, for an ADMIN's session only.
//!
//! The panes read ONE answer, `GET /api/admin/snapshot` (holger's
//! `AdminService.AdminSnapshot`, relayed by this console's server). The server
//! decides who may read it: a session below admin is refused there with 403 and
//! holger is never asked (see `crate::admin` for why the nav link is only a
//! courtesy). This page renders what came back and says the refusal when one
//! did.
//!
//! Every size is shown in human units with its raw bytes in a detail row.

use leptos::prelude::*;

use crate::admin::{
    days_left_words, disk_headline, disk_raw, fill_tone, human, lag_words, offset_words, percent, PANES,
};
use crate::components::copy_button::CopyButton;
use crate::components::feedback::{Badge, ErrorText, Loading, Tone};
use crate::pages::admin_invites::InvitesPane;
use crate::pages::admin_queue::{AdmissionPane, QueuePane};
use crate::pages::front_certs::CertsPane;
use crate::grpc::pb;

fn tone(word: &str) -> Tone {
    match word {
        "bad" => Tone::Bad,
        "warn" => Tone::Warn,
        _ => Tone::Good,
    }
}

fn yes(b: bool) -> &'static str {
    if b {
        "yes"
    } else {
        "no"
    }
}

fn when_ms(ms: i64) -> String {
    if ms <= 0 {
        "never".into()
    } else {
        format!("{ms} (Unix ms)")
    }
}

#[component]
pub fn Admin() -> impl IntoView {
    crate::webio::set_title("Admin");
    let generation = RwSignal::new(0u32);
    let pane = RwSignal::new(PANES[0].0);
    // What holger answered the sync board's last Revoke. Kept here, not in the pane: the
    // refresh that follows the revocation draws the pane anew.
    let revoked = RwSignal::new(None::<Result<String, String>>);
    let snap = LocalResource::new(move || {
        let _ = generation.get();
        async move { crate::grpc::answer(crate::grpc::admin().admin_snapshot(pb::AdminSnapshotRequest { log_lines: 0 }).await) }
    });

    view! {
        <section>
            <h2>"Admin"</h2>
            <p class="muted">
                "Operational detail about this machine, measured on every load. Shown to an \
                 administrator's session only — holger refuses everyone else."
            </p>
            <p>
                <button on:click=move |_| generation.update(|g| *g += 1)>"Refresh"</button>
                {move || crate::webio::now_hms()}
            </p>
            <OrderOwnHolger/>
            <PublicSiteSwitch/>
            // The estate's picture is the Flow page's alone (HOL-UI-FIX); Admin links to it.
            <p class="count" data-board-link="estate">"The picture of the estate (sources, downstreams, counts, lag, consumers) is on the "<a href="/flow">"Flow"</a>" page; its state at a glance is the "<a href="/overview">"Overview"</a>"."</p>
            <nav class="tabs" aria-label="Admin panes">
                {PANES.iter().map(|(slug, label)| {
                    let slug: &'static str = slug;
                    view! {
                        <button
                            class=move || if pane.get() == slug { "tab active" } else { "tab" }
                            aria-pressed=move || (pane.get() == slug).to_string()
                            on:click=move |_| pane.set(slug)
                        >{*label}</button>
                    }
                }).collect_view()}
            </nav>
            {move || match pane.get() {
                // The two admit-queue panes read their own admin-only routes,
                // not the snapshot.
                "queue" => view! { <QueuePane generation=generation/> }.into_any(),
                "admission" => view! { <AdmissionPane generation=generation/> }.into_any(),
                "invites" => view! { <InvitesPane generation=generation/> }.into_any(),
                // The front's own certificates, read from the front (not the snapshot).
                "certs" => view! { <CertsPane generation=generation/> }.into_any(),
                _ => (move || match snap.get() {
                    None => view! { <Loading what="the admin view"/> }.into_any(),
                    Some(Err(e)) => view! { <ErrorText context="admin view" message=e/> }.into_any(),
                    Some(Ok(s)) => {
                        let header = format!("{} · {} · {} · up {} · taken {}", s.host, s.role, s.version, crate::util::uptime_words(s.uptime_seconds), s.taken_unix_ms);
                        let body = match pane.get_untracked() {
                            "twin" => twin_pane(s.twin.unwrap_or_default()),
                            "sync" => view! { {sync_pane(&s)} <SyncBoardPane generation=generation revoked=revoked/> <FirstImportForm/> }.into_any(),
                            "growth" => growth_pane(s.growth.unwrap_or_default()),
                            "clock" => clock_pane(s.clock.unwrap_or_default()),
                            "plugins" => plugins_pane(s.plugins.unwrap_or_default(), s.payments.unwrap_or_default()),
                            "logs" => logs_pane(s.logs.unwrap_or_default(), s.audit.unwrap_or_default()),
                            _ => disks_pane(s.disks.unwrap_or_default()),
                        };
                        view! { <p class="count mono">{header}</p> {body} }.into_any()
                    }
                })
                .into_any(),
            }}
        </section>
    }
}

/// The open bundle's twin: no switch at all.
#[cfg(not(feature = "metered"))]
#[component]
fn PublicSiteSwitch() -> impl IntoView {}

/// **The public site's switch** (owner 2026-10-26, `crate::site_switch`): drawn only where this
/// console's build carries it (a `public-site` build); holger keeps the position and decides
/// who may flip it.
#[cfg(feature = "metered")]
#[component]
fn PublicSiteSwitch() -> impl IntoView {
    use crate::site_switch::{button, flip_body, said, shown, ROUTE};
    let generation = RwSignal::new(0u32);
    let words = RwSignal::new(None::<String>);
    let position = LocalResource::new(move || {
        let _ = generation.get();
        async move {
            let answer = gloo_net::http::Request::get(ROUTE).send().await.ok()?;
            let status = answer.status();
            shown(status, &answer.text().await.unwrap_or_default())
        }
    });
    let press = move |on: bool| {
        leptos::task::spawn_local(async move {
            let req = gloo_net::http::Request::put(ROUTE).header("content-type", "application/json");
            let answer = match req.body(flip_body(on)) {
                Ok(r) => r.send().await,
                Err(e) => {
                    words.set(Some(e.to_string()));
                    return;
                }
            };
            match answer {
                Ok(a) => {
                    let status = a.status();
                    words.set(said(status, &a.text().await.unwrap_or_default()));
                }
                Err(e) => words.set(Some(format!("the console did not answer: {e}"))),
            }
            generation.update(|g| *g += 1);
        });
    };
    move || {
        position.get().flatten().map(|on| {
            view! {
                <section class="card">
                    <h3>"Public site"</h3>
                    <p class="muted">"The public home, robots.txt, the sitemap and llms.txt, for visitors and search engines."</p>
                    <p>{if on { "It is on." } else { "It is off." }}</p>
                    <button on:click=move |_| press(!on)>{button(on)}</button>
                    {move || words.get().map(|w| view! { <p class="bad" role="alert">{w}</p> })}
                </section>
            }
        })
    }
}

/// **Order your own Holger** (lane P1): holger mints the signed-in person's `estate` ticket at the click
/// (`RoomService.PrivateEstate`: ten minutes, one use) and answers this site's own sign-up page
/// (`/signup?plan=private`, A12), which the console opens; any other link is refused (A28 M10). The console prices and orders nothing; a holger that sells nothing
/// says so in its own words.
#[cfg(feature = "metered")]
#[component]
fn OrderOwnHolger() -> impl IntoView {
    let said = RwSignal::new(String::new());
    let busy = RwSignal::new(false);
    let order = move |_| {
        busy.set(true);
        leptos::task::spawn_local(async move {
            match crate::grpc::answer_or_none(crate::grpc::room().private_estate(crate::grpc::pb_room::PrivateEstateRequest {}).await) {
                Ok(Some(r)) => match crate::admin::private_estate_link(&r.shop_url, &site_origin()) {
                    Ok(Some(url)) => {
                        said.set(if r.words.is_empty() { "Opening the order page …".into() } else { r.words.clone() });
                        if let Some(w) = web_sys::window() {
                            let _ = w.location().assign(url);
                        }
                    }
                    Ok(None) => said.set(if r.words.is_empty() { "this Holger sells no private Holger".into() } else { r.words }),
                    Err(why) => said.set(why),
                },
                Ok(None) => said.set(crate::util::not_in_build("Ordering a private Holger")),
                Err(e) => said.set(e),
            }
            busy.set(false);
        });
    };
    view! {
        <p>
            <button class="btn primary" disabled=move || busy.get() on:click=order>"Order your own Holger"</button>
            " "<span class="count" role="status">{move || said.get()}</span>
        </p>
    }
}

/// The console's own origin (`location.origin`): the only site the order button follows.
#[cfg(feature = "metered")]
fn site_origin() -> String {
    web_sys::window().and_then(|w| w.location().origin().ok()).unwrap_or_default()
}

/// An open console orders nothing: no button, no words (A28 M13). A `#[cfg]` on a component
/// node inside `view!` is not honoured, so the twin is a component that renders nothing.
#[cfg(not(feature = "metered"))]
#[component]
fn OrderOwnHolger() -> impl IntoView {}

fn disks_pane(d: pb::AdminDisks) -> AnyView {
    view! {
        <div>
            <p class="count">{format!("Floor: {}", d.floor_rule)}</p>
            {d.problems.into_iter().map(|p| view! { <p class="banner banner-warn" role="alert">{p}</p> }).collect_view()}
            // Paths holger could not place on a mount, split by holger's own flag (HOL-UI-FIX):
            // a fault is a warning; a repository's storage folder not made yet is a plain,
            // folded line in holger's words ("created at the first upload").
            {
                let u = crate::admin::unplaced(&d.unresolved);
                let (faults, later, why) = (u.faults, u.later, u.later_why);
                view! {
                    {(!faults.is_empty()).then(|| {
                        let n = faults.len();
                        view! {
                            <details class="foldbox" data-disks="faults" open>
                                <summary><Badge tone=Tone::Warn label="not placed"/>{format!("{n} path(s) sit on no mount")}</summary>
                                <ul class="pathlist">{faults.into_iter().map(|f| view! { <li>{f}</li> }).collect_view()}</ul>
                            </details>
                        }
                    })}
                    {(!later.is_empty()).then(|| {
                        let n = later.len();
                        view! {
                            <details class="foldbox" data-disks="later">
                                <summary><Badge tone=Tone::Neutral label="not made yet"/>{format!("{n} storage folder(s): {why}")}</summary>
                                <ul class="pathlist">{later.into_iter().map(|f| view! { <li>{f}</li> }).collect_view()}</ul>
                            </details>
                        }
                    })}
                }
            }
            <div class="cards">
                {d.mounts.into_iter().map(|m| {
                    let labels = if m.labels.is_empty() { String::new() } else { m.labels.join(", ") };
                    let title = format!("{} — {} ({}{})", m.mount_point, m.source, m.fstype, if m.read_only { ", read-only" } else { "" });
                    if !m.error.is_empty() {
                        return view! {
                            <div class="card"><h3>{title}</h3><Badge tone=Tone::Bad label="unmeasured"/><p class="count">{m.error}</p></div>
                        }.into_any();
                    }
                    let headline = disk_headline(m.total_bytes, m.used_bytes, m.available_bytes, m.floor_bytes, m.servable_bytes);
                    let raw = disk_raw(m.total_bytes, m.used_bytes, m.free_bytes, m.available_bytes, m.reserved_bytes, m.floor_bytes, m.servable_bytes, m.block_size);
                    let inodes = format!("inodes {} of {} free", m.inodes_free, m.inodes_total);
                    view! {
                        <div class="card">
                            <h3>{title}</h3>
                            <Badge tone=tone(fill_tone(m.used_permille)) label=percent(m.used_permille)/>
                            {(!labels.is_empty()).then(|| view! { <p class="chips"><span class="chip">{labels}</span></p> })}
                            <p class="cardline">{headline}</p>
                            <details><summary>"raw"</summary><p class="count mono">{raw}</p><p class="count mono">{inodes}</p></details>
                        </div>
                    }.into_any()
                }).collect_view()}
            </div>
            <details class="foldbox">
            <summary>{format!("Block devices ({})", d.block_devices.len())}</summary>
            <div class="tablewrap">
            <table>
                <thead><tr><th>"device"</th><th>"size"</th><th>"kind"</th><th>"partitions"</th><th>"held by"</th><th>"members"</th><th>"raw bytes"</th></tr></thead>
                <tbody>
                    {d.block_devices.into_iter().map(|b| {
                        let kind = format!("{}{}{}{}", if b.raid_level.is_empty() { String::new() } else { format!("{} ", b.raid_level) }, if b.rotational { "rotational" } else { "solid-state" }, if b.removable { ", removable" } else { "" }, if b.read_only { ", read-only" } else { "" });
                        let parts = b.partitions.iter().map(|p| format!("{} {}", p.name, human(p.size_bytes))).collect::<Vec<_>>().join(", ");
                        view! {
                            <tr>
                                <td class="mono">{b.name}</td><td>{human(b.size_bytes)}</td><td>{kind}</td><td>{parts}</td>
                                <td>{b.holders.join(", ")}</td><td>{b.members.join(", ")}</td><td class="mono">{b.size_bytes.to_string()}</td>
                            </tr>
                        }
                    }).collect_view()}
                </tbody>
            </table>
            </div>
            </details>
        </div>
    }
    .into_any()
}

fn twin_pane(t: pb::AdminTwin) -> AnyView {
    let alive = if t.role == "twin" {
        if t.complete { (Tone::Good, "whole") } else { (Tone::Warn, "not whole") }
    } else {
        (Tone::Neutral, "primary")
    };
    view! {
        <div>
            <div class="cards">
                <div class="card">
                    <h3>"Role"</h3>
                    <Badge tone=alive.0 label=alive.1/>
                    <p class="cardline">{format!("{}{}", t.role, if t.primary.is_empty() { String::new() } else { format!(" of {}", t.primary) })}</p>
                    <p class="count">{format!("serving: {} · accepts writes: {}", yes(t.serving), yes(t.accepts_writes))}</p>
                    {(!t.awaiting_recharge_from.is_empty()).then(|| view! { <p class="banner banner-warn">{format!("awaiting its recharge from {}", t.awaiting_recharge_from)}</p> })}
                </div>
                <div class="card">
                    <h3>"Follow"</h3>
                    <p class="cardline">{format!("lag: {}", lag_words(&t.role, t.lag_seconds))}</p>
                    <p class="count">{format!("last whole: {} · how: {}", t.last_whole_unix, if t.follow.is_empty() { "—".to_string() } else { t.follow.clone() })}</p>
                    {(!t.why.is_empty()).then(|| view! { <p class="count">{t.why.clone()}</p> })}
                    {(!t.store_warning.is_empty()).then(|| view! { <p class="banner banner-warn">{t.store_warning.clone()}</p> })}
                </div>
                <div class="card">
                    <h3>"Replication door"</h3>
                    <p class="cardline">{format!("refused {} · admitted {}", t.door_refused, t.door_admitted.iter().map(|(k, v)| format!("{k}×{v}")).collect::<Vec<_>>().join(", "))}</p>
                    {(!t.door_last_refusal.is_empty()).then(|| view! { <p class="count mono">{t.door_last_refusal.clone()}</p> })}
                </div>
            </div>
            <h3>"mTLS credentials"</h3>
            <table>
                <thead><tr><th>"purpose"</th><th>"state"</th><th>"principal"</th><th>"issuer"</th><th>"doors"</th><th>"not after (Unix s)"</th></tr></thead>
                <tbody>
                    {t.credentials.into_iter().map(|c| view! {
                        <tr>
                            <td>{c.purpose}</td>
                            <td>{if c.error.is_empty() { days_left_words(c.installed, c.days_left) } else { c.error }}</td>
                            <td class="mono">{c.principal}</td><td class="mono">{c.issuer}</td>
                            <td class="mono">{c.doors.join(", ")}</td><td class="mono">{c.not_after_unix.to_string()}</td>
                        </tr>
                    }).collect_view()}
                </tbody>
            </table>
            <h3>"Twins enrolled here"</h3>
            {(!t.twins_error.is_empty()).then(|| view! { <p class="count">{t.twins_error.clone()}</p> })}
            <table>
                <thead><tr><th>"twin"</th><th>"state"</th><th>"lag (entries)"</th><th>"last seen (Unix ms)"</th><th>"disk"</th><th>"raw bytes"</th></tr></thead>
                <tbody>
                    {t.twins.into_iter().map(|r| view! {
                        <tr>
                            <td class="mono">{format!("{} {}", r.principal, r.address)}</td><td>{r.state}</td><td>{r.lag_entries.to_string()}</td>
                            <td class="mono">{r.last_seen_unix_ms.to_string()}</td>
                            <td>{format!("{} of {} ({})", human(r.disk_used_bytes), human(r.disk_total_bytes), percent(r.fill_permille))}</td>
                            <td class="mono">{format!("{} / {}", r.disk_used_bytes, r.disk_total_bytes)}</td>
                        </tr>
                    }).collect_view()}
                </tbody>
            </table>
        </div>
    }
    .into_any()
}

/// Twins and paired downstreams, one line per repository: the server's state
/// word and numbers, said by `crate::sync`.
fn sync_pane(s: &pb::AdminSnapshotReply) -> AnyView {
    let rows = crate::sync::rows(s);
    if rows.is_empty() {
        return view! { <p class="count">"No twin is enrolled here and no downstream is paired."</p> }.into_any();
    }
    view! {
        <table>
            <thead><tr><th>"kind"</th><th>"name"</th><th>"repository"</th><th>"lag"</th><th>"state"</th><th>"last synced"</th><th>"pulls today"</th></tr></thead>
            <tbody>
                {rows.into_iter().map(|r| view! {
                    <tr>
                        <td>{r.kind}</td><td class="mono">{r.name}</td><td class="mono">{r.repository}</td><td>{r.lag}</td>
                        <td><Badge tone=r.tone label=r.state/></td><td>{r.last_synced}</td><td>{r.pulls_today}</td>
                    </tr>
                }).collect_view()}
            </tbody>
        </table>
    }
    .into_any()
}

/// **The first import** (lanes HOL-CDS, HOL-NCSC): with no anti-rollback floor, holger refuses
/// every bundle until an admin names the one that sets it. The form asks holger's door
/// (`crate::first_import`) and says its answer; holger checks, audits and keeps the grant.
#[component]
fn FirstImportForm() -> impl IntoView {
    let said: RwSignal<Option<Result<String, String>>> = RwSignal::new(None);
    let (vendor, product, sequence) = (RwSignal::new(String::new()), RwSignal::new(String::new()), RwSignal::new(String::new()));
    let ask = move |ev: leptos::ev::SubmitEvent| {
        ev.prevent_default();
        let path = match crate::first_import::path(&vendor.get_untracked(), &product.get_untracked(), &sequence.get_untracked()) {
            Ok(p) => p,
            Err(e) => {
                said.set(Some(Err(e)));
                return;
            }
        };
        leptos::task::spawn_local(async move {
            let answer = match crate::session::door(&path, Some(&serde_json::json!({}))).await {
                Ok((status, body)) => crate::first_import::granted(status, &body),
                Err(e) => Err(e),
            };
            said.set(Some(answer));
        });
    };
    view! {
        <h3>"First import"</h3>
        <p class="muted">"With no anti-rollback floor yet, this Holger refuses every bundle until an admin names the one that sets it (used once, audited)."</p>
        <form class="toolbar first-import" on:submit=ask>
            <input type="text" placeholder="vendor (the bundle's supplier)" aria-label="The bundle's vendor" required
                prop:value=move || vendor.get() on:input=move |ev| vendor.set(event_target_value(&ev))/>
            <input type="text" placeholder="product" aria-label="The bundle's product" required
                prop:value=move || product.get() on:input=move |ev| product.set(event_target_value(&ev))/>
            <input type="text" inputmode="numeric" placeholder="sequence" aria-label="The sequence that sets the floor" required
                prop:value=move || sequence.get() on:input=move |ev| sequence.set(event_target_value(&ev))/>
            <button type="submit">"Authorize the first import"</button>
        </form>
        {move || said.get().map(|s| match s {
            Ok(w) => view! { <p class="count first-import-said" role="status">{w}</p> }.into_any(),
            Err(w) => view! { <p class="err first-import-said" role="alert">{w}</p> }.into_any(),
        })}
    }
}

/// **The sync board** (Holger v2.0 H6): the upstream's `GET /-/sync/board`, per downstream
/// the last sync applied, lag, invalidation reach and the ALARM, in the server's words
/// (`crate::sync_board`). A Holger without a `sync:` block says so.
#[component]
fn SyncBoardPane(generation: RwSignal<u32>, revoked: RwSignal<Option<Result<String, String>>>) -> impl IntoView {
    let board = LocalResource::new(move || {
        let _ = generation.get();
        async move {
            let (status, body) = crate::session::door(crate::sync_board::BOARD_PATH, None).await?;
            crate::sync_board::read(status, &body).map(|b| crate::sync_board::view(&b))
        }
    });
    // Holger lane OX-WGDS2: the console enrols and invites through holger's doors, which check,
    // audit and sign; it only says the answer.
    let said: RwSignal<Option<Result<String, String>>> = RwSignal::new(None);
    let invite: RwSignal<Option<crate::sync_board::Invited>> = RwSignal::new(None);
    let (name, key, via, wg) = (RwSignal::new(String::new()), RwSignal::new(String::new()), RwSignal::new("front".to_string()), RwSignal::new(String::new()));
    let enrol = move |ev: leptos::ev::SubmitEvent| {
        ev.prevent_default();
        let path = crate::sync_board::enrol_path(&name.get_untracked(), &key.get_untracked(), &via.get_untracked(), &wg.get_untracked());
        leptos::task::spawn_local(async move {
            let answer = match crate::session::door(&path, Some(&serde_json::json!({}))).await {
                Ok((status, body)) => crate::sync_board::enrolled(status, &body),
                Err(e) => Err(e),
            };
            if answer.is_ok() {
                name.set(String::new());
                key.set(String::new());
                wg.set(String::new());
            }
            said.set(Some(answer));
            generation.update(|g| *g += 1);
        });
    };
    view! {
        <h3>"Sync board"</h3>
        {move || revoked.get().map(|s| match s {
            Ok(w) => view! { <p class="count sync-revoke-said" role="status">{w}</p> }.into_any(),
            Err(w) => view! { <p class="err sync-revoke-said" role="alert">{w}</p> }.into_any(),
        })}
        {move || match board.get() {
            None => view! { <Loading what="the sync board"/> }.into_any(),
            Some(Err(e)) => view! { <p class="count sync-board-none">{e}</p> }.into_any(),
            Some(Ok(v)) => view! {
                <div class="sync-board">
                    {v.alarm.map(|a| view! { <p class="err sync-alarm" role="alert">{a}</p> })}
                    <p class="count mono">{v.header}</p>
                    <table>
                        <thead><tr><th>"downstream"</th><th>"via"</th><th>"state"</th><th>"last sync"</th><th>"last sync applied"</th><th>"lag"</th><th>"last ack"</th><th>"invalidations"</th><th>"not acknowledged"</th><th></th></tr></thead>
                        <tbody>
                            {v.rows.into_iter().map(|r| {
                                let (name, class) = (r.name.clone(), if r.alarm { "alarm" } else { "" });
                                let revoke = crate::sync_board::revoke_path(&r.name);
                                let (invitable, invite_path, who) = (r.invitable, crate::sync_board::invite_path(&r.name), r.name.clone());
                                let gone = r.name.clone();
                                view! {
                                <tr class=class data-downstream=name>
                                    <td class="mono">{r.name}</td>
                                    <td>{r.via}</td>
                                    <td><Badge tone=r.tone label=r.state/>" "{r.headline}</td>
                                    <td>{r.last_sync}</td><td>{r.last_applied}</td><td>{r.lag}</td><td>{r.last_ack}</td><td>{r.invalidations}</td>
                                    <td>{r.pending.join("; ")}</td>
                                    <td>
                                        {invitable.then(|| view! { <button on:click=move |_| {
                                            let (path, who) = (invite_path.clone(), who.clone());
                                            leptos::task::spawn_local(async move {
                                                match crate::session::door(&path, Some(&serde_json::json!({}))).await.and_then(|(status, body)| crate::sync_board::invited(&who, status, &body)) {
                                                    Ok(i) => {
                                                        said.set(None);
                                                        invite.set(Some(i));
                                                    }
                                                    Err(e) => said.set(Some(Err(e))),
                                                }
                                            });
                                        }>"Invite"</button>" " })}
                                        <button class="danger" on:click=move |_| {
                                            let (path, gone) = (revoke.clone(), gone.clone());
                                            leptos::task::spawn_local(async move {
                                                let answer = crate::session::door(&path, Some(&serde_json::json!({}))).await;
                                                revoked.set(Some(answer.and_then(|(status, body)| crate::sync_board::revoke_said(&gone, status, &body))));
                                                generation.update(|g| *g += 1);
                                            });
                                        }>"Revoke"</button>
                                    </td>
                                </tr>
                            }}).collect_view()}
                        </tbody>
                    </table>
                    <table>
                        <thead><tr><th>"invalidated"</th><th>"why"</th><th>"reached"</th><th>"waiting"</th></tr></thead>
                        <tbody>
                            {v.invalidations.into_iter().map(|i| view! {
                                <tr><td class="mono">{i.coordinate}</td><td>{i.why}</td><td>{i.reached}</td><td>{i.waiting}</td></tr>
                            }).collect_view()}
                        </tbody>
                    </table>
                    <form class="toolbar sync-enrol" on:submit=enrol>
                        <strong>"Enrol a downstream "</strong>
                        <input type="text" placeholder="name" aria-label="The downstream's name" required
                            prop:value=move || name.get() on:input=move |ev| name.set(event_target_value(&ev))/>
                        <input type="text" placeholder="ack key (holger sync key on it)" aria-label="The downstream's ack key" required
                            prop:value=move || key.get() on:input=move |ev| key.set(event_target_value(&ev))/>
                        <select aria-label="How it reaches this Holger" prop:value=move || via.get() on:change=move |ev| via.set(event_target_value(&ev))>
                            <option value="front">"via the front"</option>
                            <option value="wireguard">"via WireGuard"</option>
                        </select>
                        {move || (via.get() == "wireguard").then(|| view! {
                            <input type="text" placeholder="WireGuard key (holger sync wg-key on it)" aria-label="The downstream's WireGuard key" required
                                prop:value=move || wg.get() on:input=move |ev| wg.set(event_target_value(&ev))/>
                        })}
                        <button type="submit">"Enrol"</button>
                    </form>
                    {move || said.get().map(|r| match r {
                        Ok(words) => view! { <p class="count sync-said"><Badge tone=Tone::Good label="enrolled"/>" "{words}</p> }.into_any(),
                        Err(words) => view! { <p class="err sync-said" role="alert">{words}</p> }.into_any(),
                    })}
                    {move || invite.get().map(|i| {
                        let text = i.invite.clone();
                        view! {
                            <div class="minted sync-invite">
                                <p><Badge tone=Tone::Good label="invite signed"/>{i.said()}</p>
                                <pre class="mono">{i.invite.clone()}</pre>
                                {(!i.upstream.is_empty()).then(|| view! { <p class="count mono">"its RON: sync.upstreams: [ "{i.upstream_line()}" ]"</p> })}
                                <CopyButton label="invite" value=Signal::derive(move || text.clone())/>
                            </div>
                        }
                    })}
                </div>
            }.into_any(),
        }}
    }
}

fn growth_pane(g: pb::AdminGrowth) -> AnyView {
    view! {
        <div>
            <div class="cards">
                <div class="card">
                    <h3>"Now"</h3>
                    {if g.present { view! { <Badge tone=Tone::Neutral label=g.phase.clone()/> }.into_any() } else { view! { <Badge tone=Tone::Warn label="no growth plane"/> }.into_any() }}
                    <p class="cardline">{g.why.clone()}</p>
                    <p class="count">{format!("epoch {} · serving {} · in flight {} · since {}", g.epoch, yes(g.serving), g.in_flight, when_ms(g.since_unix_ms))}</p>
                    <p class="count">{format!("set {} · {} ({} B)", if g.set_uuid.is_empty() { "—" } else { &g.set_uuid }, human(g.total_bytes), g.total_bytes)}</p>
                    {(!g.detail.is_empty()).then(|| view! { <p class="count">{g.detail.clone()}</p> })}
                </div>
                <div class="card">
                    <h3>"Can it grow?"</h3>
                    <p class="cardline">{g.capacity_growth_reason.clone()}</p>
                    <p class="count">{format!("data set: {} · engine: {} · PCI hot-plug: {}", if g.data_set.is_empty() { "—" } else { &g.data_set }, yes(g.engine_present), yes(g.hotplug))}</p>
                </div>
            </div>
            <h3>"Members"</h3>
            <p class="count">{g.members_verdict.clone()}</p>
            <table>
                <thead><tr><th>"#"</th><th>"device"</th><th>"disk"</th><th>"in the set"</th><th>"offered"</th><th>"raw bytes (disk / set / offered)"</th></tr></thead>
                <tbody>
                    {g.members.into_iter().map(|m| view! {
                        <tr>
                            <td>{m.index.to_string()}</td><td class="mono">{format!("{} {}", m.device, m.partition)}</td>
                            <td>{human(m.disk_bytes)}</td><td>{human(m.set_bytes)}</td><td>{format!("{}{}", human(m.offered_bytes), if m.larger { " (larger)" } else { "" })}</td>
                            <td class="mono">{format!("{} / {} / {}", m.disk_bytes, m.set_bytes, m.offered_bytes)}</td>
                        </tr>
                    }).collect_view()}
                </tbody>
            </table>
            <h3>"History (the grow journal)"</h3>
            <p class="count mono">{format!("{} · {}", if g.journal_phase.is_empty() { "no journal" } else { &g.journal_phase }, g.journal_path)}</p>
            {(!g.journal_detail.is_empty()).then(|| view! { <p class="count">{g.journal_detail.clone()}</p> })}
            <ol>{g.journal_steps.into_iter().map(|s| view! { <li class="mono">{s}</li> }).collect_view()}</ol>
            {(!g.journal_partial_members.is_empty()).then(|| view! {
                <p class="banner banner-warn">{format!("a partial growth left members {:?} behind", g.journal_partial_members)}</p>
            })}
        </div>
    }
    .into_any()
}

fn sync_view(title: &'static str, has: bool, s: Option<pb::AdminClockSync>) -> AnyView {
    let Some(s) = s.filter(|_| has) else {
        return view! { <div class="card"><h3>{title}</h3><p class="count">"not synced yet"</p></div> }.into_any();
    };
    view! {
        <div class="card">
            <h3>{title}</h3>
            <Badge tone=if s.vouches { Tone::Good } else { Tone::Warn } label=s.outcome.clone()/>
            <p class="cardline">{format!("{} · offset {} · at {}", s.trigger, offset_words(s.offset_ms), s.at_unix_ms)}</p>
            <p class="count">{s.detail.clone()}</p>
            <ul>{s.sources.into_iter().map(|src| view! {
                <li class="mono">{if src.answered { format!("{} {}", src.source, offset_words(src.offset_ms)) } else { format!("{} — {}", src.source, src.error) }}</li>
            }).collect_view()}</ul>
        </div>
    }
    .into_any()
}

fn clock_pane(c: pb::AdminClock) -> AnyView {
    view! {
        <div class="cards">
            <div class="card">
                <h3>"Estate clock"</h3>
                <Badge tone=if c.trusted { Tone::Good } else { Tone::Warn } label=if c.trusted { "trusted" } else { "NOT trusted" }/>
                <p class="cardline">{c.config.clone()}</p>
                <p class="count">{format!("owed in-process: {} · estate {} ms · system {} ms", offset_words(c.owed_ms), c.estate_unix_ms, c.system_unix_ms)}</p>
            </div>
            {sync_view("Last sync", c.has_last, c.last)}
            {sync_view("Boot sync", c.has_boot, c.boot)}
        </div>
    }
    .into_any()
}

/// **The payments card** of the plugins pane: when the vendor's webhook last delivered, and
/// holger's note. A metered console's only (holger's own admin snapshot fills it only there).
#[cfg(feature = "metered")]
fn payments_card(pay: pb::AdminPayments) -> AnyView {
    view! {
        <div class="card">
            <h3>"Stripe webhook"</h3>
            <p class="cardline">{if pay.stripe_last_delivery_unix_ms > 0 { format!("last delivery {}", pay.stripe_last_delivery_unix_ms) } else { "last delivery: not measured here".to_string() }}</p>
            <p class="count">{pay.note.clone()}</p>
        </div>
    }
    .into_any()
}

/// An open console draws no payments card.
#[cfg(not(feature = "metered"))]
fn payments_card(_pay: pb::AdminPayments) -> AnyView {
    ().into_any()
}

fn plugins_pane(p: pb::AdminPlugins, pay: pb::AdminPayments) -> AnyView {
    let reg = p.self_registration.clone();
    view! {
        <div>
            <div class="cards">
                <div class="card">
                    <h3>"Monetize"</h3>
                    <p class="cardline">{format!("metered build: {} · entitlement store: {} · meters: {}", yes(p.metered_build), if p.entitlements_configured { "configured" } else { "none" }, p.meters)}</p>
                    {match reg {
                        Some(r) if r.active => view! {
                            <Badge tone=if r.registered { Tone::Good } else if r.warning { Tone::Bad } else { Tone::Warn } label=if r.registered { "plugin registered" } else { "plugin not registered" }/>
                            <p class="count">{format!("{} (at {})", r.line, r.at_unix_ms)}</p>
                        }.into_any(),
                        _ => view! { <p class="count">"self-registration: not running in this process"</p> }.into_any(),
                    }}
                </div>
                {payments_card(pay)}
            </div>
            <h3>"Entitlements"</h3>
            <table>
                <thead><tr><th>"repository"</th><th>"state"</th><th>"caps"</th><th>"valid until (Unix ms)"</th><th>"source"</th></tr></thead>
                <tbody>
                    {p.entitlements.into_iter().map(|e| view! {
                        <tr><td class="mono">{e.repository}</td><td>{e.state}</td><td class="mono">{e.caps.join(", ")}</td><td class="mono">{e.paid_until_unix_ms.to_string()}</td><td>{e.source}</td></tr>
                    }).collect_view()}
                </tbody>
            </table>
            <h3>"Loaded modules"</h3>
            <table>
                <thead><tr><th>"name"</th><th>"kind"</th><th>"origin"</th><th>"digest"</th><th>"granted"</th></tr></thead>
                <tbody>
                    {p.modules.into_iter().map(|m| view! {
                        <tr><td>{m.name}</td><td>{m.kind}</td><td class="mono">{m.origin}</td><td class="mono">{m.digest}</td><td class="mono">{m.granted.join(", ")}</td></tr>
                    }).collect_view()}
                </tbody>
            </table>
            <p class="count">{format!("granted capabilities: {}", p.granted_capabilities.join(", "))}</p>
        </div>
    }
    .into_any()
}

fn logs_pane(l: pb::AdminLogs, a: pb::AdminAudit) -> AnyView {
    view! {
        <div>
            <div class="cards">
                <div class="card">
                    <h3>"Installer log door"</h3>
                    {if l.present { view! { <Badge tone=Tone::Neutral label=l.door.clone()/> }.into_any() } else { view! { <Badge tone=Tone::Neutral label="none"/> }.into_any() }}
                    <p class="count">{if l.present { format!("{} lines logged, {} dropped from the ring", l.lines_total, l.lines_dropped) } else { l.door.clone() }}</p>
                </div>
                <div class="card">
                    <h3>"Audit log"</h3>
                    <Badge tone=if a.error.is_empty() { Tone::Neutral } else { Tone::Bad } label=a.mode.clone()/>
                    <p class="cardline">{format!("{} rows · {} segments · {}", a.total_rows, a.segments, human(a.total_bytes))}</p>
                    <p class="count mono">{format!("{} · {} B · first {} · last {} (ns)", a.source, a.total_bytes, a.first_ts_nanos, a.last_ts_nanos)}</p>
                    {(!a.error.is_empty()).then(|| view! { <p class="err">{a.error.clone()}</p> })}
                </div>
            </div>
            <h3>"Newest log lines"</h3>
            <pre class="mono logtail">{l.tail.join("\n")}</pre>
        </div>
    }
    .into_any()
}