fix: Restore Brain Monitor HTML dashboard and API stats route
This commit is contained in:
1 parent
b2cda6c9cc
commit
5703e54b88
2 files changed
+103
No files matched your search
@@ -0,0 +1,73 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Brain Monitor</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; padding: 40px; background-color: #f0f2f5; color: #333; }
|
||||||
|
h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; }
|
||||||
|
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; max-width: 800px; margin: 0 auto; }
|
||||||
|
.stat-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.05); text-align: center; transition: transform 0.2s ease; }
|
||||||
|
.stat-card:hover { transform: translateY(-5px); }
|
||||||
|
.stat-value { font-size: 2.5em; font-weight: bold; color: #3498db; margin: 10px 0; }
|
||||||
|
.stat-label { font-size: 1.1em; color: #7f8c8d; text-transform: uppercase; letter-spacing: 1px; }
|
||||||
|
.status-dot { display: inline-block; width: 10px; height: 10px; background-color: #2ecc71; border-radius: 50%; margin-right: 8px; box-shadow: 0 0 5px #2ecc71; }
|
||||||
|
.header-bar { max-width: 800px; margin: 0 auto 20px; display: flex; justify-content: space-between; align-items: center; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header-bar">
|
||||||
|
<h1>🧠 Antigravity Brain Monitor</h1>
|
||||||
|
<div><span class="status-dot"></span>System Online</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-label">Entities</div>
|
||||||
|
<div class="stat-value" id="val-entities">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-label">Relations</div>
|
||||||
|
<div class="stat-value" id="val-relations">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-label">Active Tasks</div>
|
||||||
|
<div class="stat-value" id="val-tasks">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-label">Snippets</div>
|
||||||
|
<div class="stat-value" id="val-snippets">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-label">Tech Debt</div>
|
||||||
|
<div class="stat-value" id="val-tech_debts">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-label">ADRs</div>
|
||||||
|
<div class="stat-value" id="val-adrs">-</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function fetchStats() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/stats');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
document.getElementById('val-entities').innerText = data.entities;
|
||||||
|
document.getElementById('val-relations').innerText = data.relations;
|
||||||
|
document.getElementById('val-tasks').innerText = data.tasks;
|
||||||
|
document.getElementById('val-snippets').innerText = data.snippets;
|
||||||
|
document.getElementById('val-tech_debts').innerText = data.tech_debts;
|
||||||
|
document.getElementById('val-adrs').innerText = data.adrs;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to fetch stats:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial fetch
|
||||||
|
fetchStats();
|
||||||
|
// Poll every 2 seconds
|
||||||
|
setInterval(fetchStats, 2000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -139,6 +139,32 @@ fn run_server(state: Arc<MemoryState>) -> Result<(), Box<dyn std::error::Error>>
|
|||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/sse", get(sse_handler))
|
.route("/sse", get(sse_handler))
|
||||||
.route("/messages", post(message_handler))
|
.route("/messages", post(message_handler))
|
||||||
|
.route("/health", get(health_handler))
|
||||||
|
.route("/dashboard", get(|| async move {
|
||||||
|
axum::response::Html(include_str!("dashboard.html"))
|
||||||
|
}))
|
||||||
|
.route("/api/stats", get({
|
||||||
|
let state_clone = app_state.handler.state.clone();
|
||||||
|
move || async move {
|
||||||
|
let (entities, relations) = {
|
||||||
|
let graph = state_clone.get_full_graph();
|
||||||
|
(graph.entities.len(), graph.relations.len())
|
||||||
|
};
|
||||||
|
let tasks = state_clone.tasks.read().len();
|
||||||
|
let snippets = state_clone.snippets.read().len();
|
||||||
|
let tech_debts = state_clone.tech_debts.read().len();
|
||||||
|
let adrs = state_clone.adrs.read().len();
|
||||||
|
|
||||||
|
axum::Json(serde_json::json!({
|
||||||
|
"entities": entities,
|
||||||
|
"relations": relations,
|
||||||
|
"tasks": tasks,
|
||||||
|
"snippets": snippets,
|
||||||
|
"tech_debts": tech_debts,
|
||||||
|
"adrs": adrs
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}))
|
||||||
.with_state(app_state);
|
.with_state(app_state);
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||||
@@ -162,6 +188,10 @@ async fn sse_handler(
|
|||||||
Sse::new(stream).keep_alive(axum::response::sse::KeepAlive::new())
|
Sse::new(stream).keep_alive(axum::response::sse::KeepAlive::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn health_handler() -> &'static str {
|
||||||
|
"OK"
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
struct SessionQuery {
|
struct SessionQuery {
|
||||||
#[serde(rename = "sessionId")]
|
#[serde(rename = "sessionId")]
|
||||||
|
|||||||
Reference in new issue
Block a user