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

nordisk/gunnar-ui

crates/gunnar-ui-web/src/pages/cli.rs

//! **Get the `gunnar` CLI** (E4, owner 2026-10-11): the signed binaries this
//! site's own release build carries, with their sha256 and signature and the
//! one line that checks both against the published root — and `cargo install`
//! as the second way. Everything shown comes from the front's
//! `/download/index.json`; every decision is in [`crate::cli`].

use leptos::prelude::*;

use crate::cli::{fetch_line, size, url, Binary, Manifest, Root, CARGO_INSTALL, MANIFEST};
use crate::pages::resolved;
use crate::ui::{Hint, PageHeader, Panel};

fn origin() -> String {
    web_sys::window().and_then(|w| w.location().origin().ok()).unwrap_or_default()
}

#[component]
fn Line(#[prop(into)] text: String) -> impl IntoView {
    view! { <pre class="mono cli-line"><code>{text}</code></pre> }
}

#[component]
fn Download(binary: Binary, root: Root) -> impl IntoView {
    let o = origin();
    let fetch = fetch_line(&o, &binary, &root);
    view! {
        <Panel title=binary.target.clone()>
            <div class="panel-body">
                <p>
                    <a class="btn" href=url(&o, &binary.file) download=binary.file.clone()>{format!("Download {}", binary.file)}</a>
                    " "{size(binary.bytes)}" · "
                    <a href=url(&o, &binary.signature)>"signature (.p7s)"</a>" · "
                    <a href=url(&o, &format!("{}.sha256", binary.file))>"sha256"</a>
                </p>
                <dl class="cli-sums">
                    <dt>"sha256"</dt><dd class="mono">{binary.sha256.clone()}</dd>
                    <dt>"blake3"</dt><dd class="mono">{binary.blake3.clone()}</dd>
                </dl>
                <Hint line="Fetch, then verify (sha256 and signature against the published root):"
                    more="The signature is a detached CMS over the file's BLAKE3 digest, made by the release build's \
                          signing certificate, which chains to the root below. The verify line needs openssl 3, b3sum and xxd."/>
                <Line text=fetch/>
                <Line text=binary.verify.clone()/>
                <Line text=format!("chmod +x {0} && mv {0} ~/.local/bin/gunnar", binary.file)/>
            </div>
        </Panel>
    }
}

/// `/cli`.
#[component]
pub fn Cli() -> impl IntoView {
    let manifest = LocalResource::new(|| async { crate::api::get::<Manifest>(MANIFEST).await });
    view! {
        <PageHeader title="The Gunnar CLI"/>
        <Hint line="Encrypted repositories: clone, unlock and seal them with it on your computer."/>
        {move || match manifest.get() {
            // No manifest here (a console with no front behind it): not a failure. Say so
            // and offer crates.io; draw no download that is not there (GUN-FIELD-TEST).
            Some(Err(_)) => view! {
                <Panel title="From crates.io">
                    <div class="panel-body">
                        <p>{crate::cli::NO_DOWNLOADS}</p>
                        <Line text=CARGO_INSTALL.to_owned()/>
                    </div>
                </Panel>
            }.into_any(),
            state => resolved(state, None, |m: Manifest| {
            let cargo = if m.cargo_install.is_empty() { CARGO_INSTALL.to_owned() } else { m.cargo_install.clone() };
            let root = m.root.clone();
            view! {
                <p class="meta">{format!("Release {}", m.version)}</p>
                {m.absent.clone().map(|why| view! { <p class="fail">{why}</p> })}
                {root.clone().map(|r| m.binaries.clone().into_iter().map(|b| view! { <Download binary=b root=r.clone()/> }).collect_view())}
                {root.map(|r| view! {
                    <Panel title="Trust root">
                        <div class="panel-body">
                            <p><a href=url(&origin(), &r.file)>{r.file.clone()}</a></p>
                            <dl class="cli-sums"><dt>"sha256"</dt><dd class="mono">{r.sha256.clone()}</dd></dl>
                        </div>
                    </Panel>
                })}
                <Panel title="Or from crates.io">
                    <div class="panel-body"><Line text=cargo/></div>
                </Panel>
            }
        }),
        }}
    }
}

/// **The hint beside a flow that needs the CLI**: its one line, and the link here.
#[component]
pub fn NeedsCli(needs: crate::cli::Needs) -> impl IntoView {
    view! {
        <p class="hint-line cli-hint">
            {needs.line()}" "
            <a href=crate::cli::PAGE>{crate::cli::LINK}</a>
        </p>
    }
}