From 8fbee4fbf2a25ee0f79294572041f644c7374abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?m=C3=BCde?= Date: Fri, 15 May 2026 16:21:25 +0200 Subject: [PATCH] dashboard: async forms with spinner + rebuild button on every container --- hive-c0re/src/dashboard.rs | 56 +++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs index eb55d9e..77fb0e4 100644 --- a/hive-c0re/src/dashboard.rs +++ b/hive-c0re/src/dashboard.rs @@ -82,7 +82,7 @@ async fn index(headers: HeaderMap, State(state): State) -> Html\n\n\n\nhyperhive // h1ve-c0re\n{refresh}\n{STYLE}\n\n\n{BANNER}\n{containers}\n{approvals_html}\n{MSG_FLOW}\n{FOOTER}\n{MSG_FLOW_JS}\n\n\n", + "\n\n\n\nhyperhive // h1ve-c0re\n{refresh}\n{STYLE}\n\n\n{BANNER}\n{containers}\n{approvals_html}\n{MSG_FLOW}\n{FOOTER}\n{ASYNC_FORMS_JS}\n{MSG_FLOW_JS}\n\n\n", containers = render_containers(&containers, &transient, current_rev.as_deref(), &hostname), )) } @@ -181,7 +181,7 @@ fn render_containers( let mut out = String::from( "

◆ C0NTAINERS ◆

\n
══════════════════════════════════════════════════════════════
\n", ); - out.push_str("
\n \n \n
\n

spawn requests queue as approvals. operator approves below to actually create the container.

\n"); + out.push_str("
\n \n \n
\n

spawn requests queue as approvals. operator approves below to actually create the container.

\n"); // Render in-flight spawns first so the operator sees feedback immediately. if !transient.is_empty() { out.push_str("
    \n"); @@ -212,7 +212,7 @@ fn render_containers( let update_badge = update_badge_for(MANAGER_NAME, current_rev); let _ = writeln!( out, - "
  • ▓█▓▒░ {container} m1nd{update_badge} :{MANAGER_PORT}
  • ", + "
  • ▓█▓▒░ {container} m1nd{update_badge} :{MANAGER_PORT}\n
    \n
  • ", ); } else if let Some(name) = container.strip_prefix(AGENT_PREFIX) { let port = lifecycle::agent_web_port(name); @@ -227,7 +227,7 @@ fn render_containers( let update_badge = update_badge_for(name, current_rev); let _ = writeln!( out, - "
  • ▒░▒░░ {name} ag3nt{login_badge}{update_badge} {container} :{port}\n
    \n
  • ", + "
  • ▒░▒░░ {name} ag3nt{login_badge}{update_badge} {container} :{port}\n
    \n
    \n
  • ", ); } } @@ -252,7 +252,7 @@ async fn render_approvals(approvals: &[Approval]) -> String { let diff_html = render_diff_lines(&diff); let _ = writeln!( out, - "
  • \n
    #{id} {agent} apply {sha_short}\n
    \n
    \n
    \n
    diff vs applied
    {diff_html}
    \n
  • ", + "
  • \n
    #{id} {agent} apply {sha_short}\n
    \n
    \n
    \n
    diff vs applied
    {diff_html}
    \n
  • ", id = a.id, agent = a.agent, ); @@ -260,7 +260,7 @@ async fn render_approvals(approvals: &[Approval]) -> String { hive_sh4re::ApprovalKind::Spawn => { let _ = writeln!( out, - "
  • \n
    #{id} {agent} spawn new sub-agent — container will be created on approve\n
    \n
    \n
    \n
  • ", + "
  • \n
    #{id} {agent} spawn new sub-agent — container will be created on approve\n
    \n
    \n
    \n
  • ", id = a.id, agent = a.agent, ); @@ -331,7 +331,7 @@ fn update_badge_for(name: &str, current_rev: Option<&str>) -> String { return String::new(); } format!( - "
    ", + "
    ", ) } @@ -407,6 +407,47 @@ const BANNER: &str = r#""#; +/// Generic async submit + spinner for any `
    `. Replaces +/// the standard form-POST navigation: button shows a spinner during the +/// request, `data-confirm` runs first (skips the action if cancelled), +/// page reloads on success so the new state is reflected. +const ASYNC_FORMS_JS: &str = r#""#; + const MSG_FLOW: &str = r#"

    ◆ MESS4GE FL0W ◆

    ══════════════════════════════════════════════════════════════

    live tail — newest at the top. tap on every send / recv through the broker.

    @@ -551,6 +592,7 @@ const STYLE: &str = r#" .btn-approve { color: var(--green); border-color: var(--green); } .btn-deny { color: var(--red); border-color: var(--red); } .btn-destroy { color: var(--red); border-color: var(--red); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; } + .btn-rebuild { color: var(--amber); border-color: var(--amber); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; } .btn-talk { color: var(--cyan); border-color: var(--cyan); } .btn-spawn { color: var(--amber); border-color: var(--amber); } .spawnform { display: flex; gap: 0.6em; align-items: stretch; margin: 0.5em 0; }