hyperhive/hive-ag3nt/src/web_ui.rs

75 lines
2.6 KiB
Rust

//! Per-container HTTP UI. Phase 6 minimum — a status page on a host port.
//! Containers share the host's network namespace (privateNetwork = false), so
//! each instance must bind a distinct port. `HIVE_PORT` is set per agent by
//! `hive-c0re`'s generated per-agent flake (deterministic from agent name).
use std::net::SocketAddr;
use anyhow::{Context, Result};
use axum::{Router, extract::State, response::Html, routing::get};
#[derive(Clone)]
struct AppState {
label: String,
}
pub async fn serve(label: String, port: u16) -> Result<()> {
let state = AppState { label };
let app = Router::new().route("/", get(index)).with_state(state);
let addr = SocketAddr::from(([0, 0, 0, 0], port));
let listener = tokio::net::TcpListener::bind(addr)
.await
.with_context(|| format!("bind web UI on port {port}"))?;
tracing::info!(%port, "web UI listening");
axum::serve(listener, app).await?;
Ok(())
}
async fn index(State(state): State<AppState>) -> Html<String> {
Html(format!(
"<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>{label} // hyperhive</title>\n{STYLE}\n</head>\n<body>\n<pre class=\"banner\">░▒▓█▓▒░ {label} ░▒▓█▓▒░ hyperhive ag3nt ░▒▓█▓▒░</pre>\n<h2>◆ {label} ◆</h2>\n<div class=\"divider\">══════════════════════════════════════════════════════════════</div>\n<p>▓█▓▒░ harness alive ▓█▓▒░</p>\n<p class=\"meta\">phase 6a placeholder — turn-loop status / inbox / xterm.js coming in 6b+</p>\n</body>\n</html>\n",
label = state.label,
))
}
const STYLE: &str = r#"
<style>
:root {
--bg: #0a0014;
--fg: #e0d4ff;
--muted: #6c5c8c;
--purple: #cc66ff;
--purple-dim: #4a1a6a;
}
body {
background: var(--bg);
color: var(--fg);
font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", "Source Code Pro", monospace;
max-width: 70em;
margin: 1.5em auto;
padding: 0 1.5em;
line-height: 1.6;
}
.banner {
color: var(--purple);
text-align: center;
margin: 0 0 1em 0;
font-size: 0.95em;
text-shadow: 0 0 6px rgba(204, 102, 255, 0.5);
overflow-x: auto;
}
h2 {
color: var(--purple);
text-transform: uppercase;
letter-spacing: 0.15em;
text-shadow: 0 0 8px rgba(204, 102, 255, 0.4);
}
.divider {
color: var(--purple-dim);
overflow: hidden;
white-space: nowrap;
margin-bottom: 0.5em;
}
.meta { color: var(--muted); font-size: 0.85em; }
</style>
"#;