CheddahBot/cheddahbot/templates/dashboard.html

175 lines
6.1 KiB
HTML

{% extends "base.html" %}
{% block title %}Dashboard - CheddahBot{% endblock %}
{% block nav_dash_active %}active{% endblock %}
{% block content %}
<div class="dashboard-layout">
<!-- Ops Panel -->
<section class="panel" id="ops-panel">
<h2 class="panel-title">Operations</h2>
<!-- Active Executions -->
<div class="panel-section">
<h3>Active Executions</h3>
<div id="active-executions" class="exec-list">
<span class="text-muted">Loading...</span>
</div>
</div>
<!-- Loop Health -->
<div class="panel-section">
<h3>Loop Health</h3>
<div id="loop-health" class="loop-grid">
<span class="text-muted">Loading...</span>
</div>
</div>
<!-- Actions -->
<div class="panel-section">
<h3>Actions</h3>
<div class="action-buttons">
<button class="btn btn-sm"
hx-post="/api/system/loops/force"
hx-swap="none"
hx-on::after-request="showFlash('Force pulse sent')">
Force Pulse
</button>
<button class="btn btn-sm"
hx-post="/api/system/briefing/force"
hx-swap="none"
hx-on::after-request="showFlash('Briefing triggered')">
Force Briefing
</button>
<button class="btn btn-sm"
hx-post="/api/cache/clear"
hx-swap="none"
hx-on::after-request="showFlash('Cache cleared')">
Clear Cache
</button>
</div>
</div>
<!-- Notification Feed -->
<div class="panel-section">
<h3>Notifications</h3>
<div id="notification-feed" class="notif-feed">
<span class="text-muted">Waiting for notifications...</span>
</div>
</div>
</section>
<!-- Pipeline Panel -->
<section class="panel" id="pipeline-panel">
<h2 class="panel-title">Pipeline</h2>
<div id="pipeline-content"
hx-get="/dashboard/pipeline"
hx-trigger="load, every 120s"
hx-swap="innerHTML">
<span class="text-muted">Loading pipeline data...</span>
</div>
</section>
</div>
{% endblock %}
{% block scripts %}
<script>
// Connect to SSE for live loop updates
const loopSource = new EventSource('/sse/loops');
loopSource.addEventListener('loops', function(e) {
const data = JSON.parse(e.data);
renderLoopHealth(data.loops);
renderActiveExecutions(data.executions);
});
// Connect to SSE for notifications
const notifSource = new EventSource('/sse/notifications');
notifSource.addEventListener('notification', function(e) {
const notif = JSON.parse(e.data);
addNotification(notif.message, notif.category);
});
function renderLoopHealth(loops) {
const container = document.getElementById('loop-health');
if (!loops || Object.keys(loops).length === 0) {
container.innerHTML = '<span class="text-muted">No loop data</span>';
return;
}
let html = '';
const now = new Date();
for (const [name, ts] of Object.entries(loops)) {
let statusClass = 'badge-muted';
let agoText = 'never';
if (ts) {
const dt = new Date(ts);
const secs = Math.floor((now - dt) / 1000);
if (secs < 120) {
statusClass = 'badge-ok';
agoText = secs + 's ago';
} else if (secs < 600) {
statusClass = 'badge-warn';
agoText = Math.floor(secs / 60) + 'm ago';
} else {
statusClass = 'badge-err';
agoText = Math.floor(secs / 60) + 'm ago';
}
}
html += '<div class="loop-badge ' + statusClass + '">' +
'<span class="loop-name">' + name + '</span>' +
'<span class="loop-ago">' + agoText + '</span>' +
'</div>';
}
container.innerHTML = html;
}
function renderActiveExecutions(execs) {
const container = document.getElementById('active-executions');
if (!execs || Object.keys(execs).length === 0) {
container.innerHTML = '<span class="text-muted">No active executions</span>';
return;
}
let html = '';
const now = new Date();
for (const [id, info] of Object.entries(execs)) {
const started = new Date(info.started_at);
const durSecs = Math.floor((now - started) / 1000);
let dur = durSecs + 's';
if (durSecs >= 60) dur = Math.floor(durSecs / 60) + 'm ' + (durSecs % 60) + 's';
html += '<div class="exec-item">' +
'<span class="exec-name">' + info.name + '</span>' +
'<span class="exec-tool">' + info.tool + '</span>' +
'<span class="exec-dur">' + dur + '</span>' +
'</div>';
}
container.innerHTML = html;
}
let notifCount = 0;
function addNotification(message, category) {
const container = document.getElementById('notification-feed');
if (notifCount === 0) container.innerHTML = '';
notifCount++;
const div = document.createElement('div');
div.className = 'notif-item notif-' + (category || 'info');
div.innerHTML = '<span class="notif-cat">' + (category || 'info') + '</span> ' + message;
container.insertBefore(div, container.firstChild);
// Keep max 30
while (container.children.length > 30) {
container.removeChild(container.lastChild);
}
}
function showFlash(msg) {
const el = document.createElement('div');
el.className = 'flash-msg';
el.textContent = msg;
document.body.appendChild(el);
setTimeout(() => el.remove(), 3000);
}
</script>
{% endblock %}