diff --git a/src/services/dashboard/app.js b/src/services/dashboard/app.js
index 4de3645..c7ce535 100644
--- a/src/services/dashboard/app.js
+++ b/src/services/dashboard/app.js
@@ -1,128 +1,136 @@
let allLogs = [];
- let showingAll = false;
- let lastDashboardState = {};
-
- let lastIpcLogTimestamp = 0;
- function updateIpcLogs() {
- fetch(`/api/ipc-logs?since=${lastIpcLogTimestamp}`)
- .then(r => r.json())
- .then(logs => {
- if (!logs || !logs.length) return;
- lastIpcLogTimestamp = Math.max(...logs.map(l => l.message.timestamp));
- const tbody = document.getElementById("ipc-table-body");
- if (!tbody) return;
-
- logs.forEach(log => {
- const tr = document.createElement("tr");
- const ts = new Date(log.message.timestamp);
- const tsStr = ts.getHours().toString().padStart(2, '0') + ':' + ts.getMinutes().toString().padStart(2, '0') + ':' + ts.getSeconds().toString().padStart(2, '0') + '.' + ts.getMilliseconds().toString().padStart(3, '0');
-
- let color = "var(--text)";
- if (log.message.type && log.message.type.includes("error")) color = "var(--error)";
- else if (log.message.type && log.message.type.includes("heartbeat")) color = "var(--text-muted)";
- else if (log.message.type && log.message.type.includes("event")) color = "var(--warning)";
- else if (log.message.type && log.message.type.includes("response")) color = "var(--success)";
-
- let routeHtml = '';
- if (log.direction === '←') {
- routeHtml = `${log.fromProcess} → ${log.toProcess}`;
- } else {
- routeHtml = `${log.fromProcess} → ${log.toProcess}`;
- }
+let showingAll = false;
+let lastDashboardState = {};
- tr.innerHTML = `
+function showTab(tabId) {
+ document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
+ document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
+
+ document.getElementById(tabId + '-tab').classList.add('active');
+ event.currentTarget.classList.add('active');
+}
+
+let lastIpcLogTimestamp = 0;
+function updateIpcLogs() {
+ fetch(`/api/ipc-logs?since=${lastIpcLogTimestamp}`)
+ .then(r => r.json())
+ .then(logs => {
+ if (!logs || !logs.length) return;
+ lastIpcLogTimestamp = Math.max(...logs.map(l => l.message.timestamp));
+ const tbody = document.getElementById("ipc-table-body");
+ if (!tbody) return;
+
+ logs.forEach(log => {
+ const tr = document.createElement("tr");
+ const ts = new Date(log.message.timestamp);
+ const tsStr = ts.getHours().toString().padStart(2, '0') + ':' + ts.getMinutes().toString().padStart(2, '0') + ':' + ts.getSeconds().toString().padStart(2, '0') + '.' + ts.getMilliseconds().toString().padStart(3, '0');
+
+ let color = "var(--text)";
+ if (log.message.type && log.message.type.includes("error")) color = "var(--error)";
+ else if (log.message.type && log.message.type.includes("heartbeat")) color = "var(--text-muted)";
+ else if (log.message.type && log.message.type.includes("event")) color = "var(--warning)";
+ else if (log.message.type && log.message.type.includes("response")) color = "var(--success)";
+
+ let routeHtml = '';
+ if (log.direction === '←') {
+ routeHtml = `${log.fromProcess} → ${log.toProcess}`;
+ } else {
+ routeHtml = `${log.fromProcess} → ${log.toProcess}`;
+ }
+
+ tr.innerHTML = `
+ if (statsChanged) {
+ const total = Object.values(d.tasks).reduce((a, b) => a + b, 0);
+ document.getElementById("task-total").textContent = total;
+ document.getElementById("rag-count").textContent = d.rag;
+ document.getElementById("cron-count").textContent = d.cron;
+ document.getElementById("max-nodes").textContent = d.maxNodes || 0;
+ document.getElementById("time-first").textContent = d.timing.first;
+ document.getElementById("time-last").textContent = d.timing.last;
+ document.getElementById("time-avg").textContent = d.timing.avg;
+
+ lastDashboardState.rag = d.rag;
+ lastDashboardState.cron = d.cron;
+ lastDashboardState.maxNodes = d.maxNodes;
+ lastDashboardState.tasks = d.tasks;
+ lastDashboardState.timing = d.timing;
+ }
+
+ // 1.5 Update Processes
+ const procsHash = JSON.stringify(d.processes);
+ if (lastDashboardState.procsHash !== procsHash) {
+ const pg = document.getElementById("processes-grid");
+ if (pg && d.processes) {
+ const html = Object.keys(d.processes).sort().map(pName => {
+ const p = d.processes[pName];
+ const isDown = (Date.now() - (p.lastHeartbeat || 0)) > 30000;
+ const status = isDown ? "OFFLINE" : (p.status || "UNKNOWN").toUpperCase();
+ const tc = status === "OFFLINE" ? "error" : (status === "READY" ? "success" : "warning");
+ const indicator = status === "READY" ? '
' : '';
+ const restartLabel = p.restartCount ? `
Restarts: ${p.restartCount}
` : '';
+ const mem = p.memory && p.memory.rss ? `
Mem: ${Math.round(p.memory.rss / 1024 / 1024)}MB
` : '';
+ const up = p.uptime ? `
Uptime: ${p.uptime}s
` : '';
+ return `
${pName}
${indicator}${status}
@@ -195,43 +203,43 @@ let allLogs = [];
${restartLabel}
`;
- }).join("");
- pg.innerHTML = html;
- }
- lastDashboardState.procsHash = procsHash;
- }
+ }).join("");
+ pg.innerHTML = html;
+ }
+ lastDashboardState.procsHash = procsHash;
+ }
- // 2. Update Models
- const modelsHash = JSON.stringify(d.models);
- if (lastDashboardState.modelsHash !== modelsHash) {
- const modelsSection = document.getElementById("model-list");
- modelsSection.innerHTML = Object.entries(d.models)
- .filter(([k]) => ['small', 'medium', 'large'].includes(k))
- .map(([k, v]) => `
${k.toUpperCase()}:${v}
`)
- .join("");
- lastDashboardState.modelsHash = modelsHash;
- }
-
- // 3. Update Charts
- const chartsHash = JSON.stringify(d.charts);
- if (lastDashboardState.chartsHash !== chartsHash) {
- document.getElementById("c1").innerHTML = d.charts.taskDonut;
- document.getElementById("c2").innerHTML = d.charts.compBar;
- lastDashboardState.chartsHash = chartsHash;
- }
+ // 2. Update Models
+ const modelsHash = JSON.stringify(d.models);
+ if (lastDashboardState.modelsHash !== modelsHash) {
+ const modelsSection = document.getElementById("model-list");
+ modelsSection.innerHTML = Object.entries(d.models)
+ .filter(([k]) => ['small', 'medium', 'large'].includes(k))
+ .map(([k, v]) => `
${k.toUpperCase()}:${v}
`)
+ .join("");
+ lastDashboardState.modelsHash = modelsHash;
+ }
- // 4. Update Active Queue
- const queueHash = JSON.stringify(d.pendingTasks);
- if (lastDashboardState.queueHash !== queueHash) {
- const qt = document.getElementById("qt");
- const qs = document.getElementById("active-queue-section");
- if (d.pendingTasks && d.pendingTasks.length > 0) {
- qs.style.display = "block";
- qt.innerHTML = d.pendingTasks.map(t => {
- const isRunning = t.status === 'running';
- const tc = isRunning ? "running" : "warning";
- const indicator = isRunning ? '
' : '';
- return `
+ // 3. Update Charts
+ const chartsHash = JSON.stringify(d.charts);
+ if (lastDashboardState.chartsHash !== chartsHash) {
+ document.getElementById("c1").innerHTML = d.charts.taskDonut;
+ document.getElementById("c2").innerHTML = d.charts.compBar;
+ lastDashboardState.chartsHash = chartsHash;
+ }
+
+ // 4. Update Active Queue
+ const queueHash = JSON.stringify(d.pendingTasks);
+ if (lastDashboardState.queueHash !== queueHash) {
+ const qt = document.getElementById("qt");
+ const qs = document.getElementById("active-queue-section");
+ if (d.pendingTasks && d.pendingTasks.length > 0) {
+ qs.style.display = "block";
+ qt.innerHTML = d.pendingTasks.map(t => {
+ const isRunning = t.status === 'running';
+ const tc = isRunning ? "running" : "warning";
+ const indicator = isRunning ? '' : '';
+ return `
| ${fmtDate(t.updated_at)} |
${indicator}${t.status.toUpperCase()} |
${(t.complexity || 'unknown').toUpperCase()} |
@@ -246,54 +254,54 @@ let allLogs = [];
${(() => {
- const levels = getGraphLayout(t.nodes || [], t.edges || []);
- return levels.map(level => `
+ const levels = getGraphLayout(t.nodes || [], t.edges || []);
+ return levels.map(level => `
${level.map(n => {
- const activeIndicator = n.status === 'running' ? ' ' : '';
- const typeLabel = n.type.split('.').pop().toUpperCase();
- let chipAttr = '';
- if (n.type === 'skill' && n.input) {
- try {
- const input = typeof n.input === 'string' ? JSON.parse(n.input) : n.input;
- const skillName = input.skillName || input.skill;
- if (skillName) chipAttr = ` data-title="Skill: ${skillName}"`;
- } catch(e) {}
- }
- return ` ${activeIndicator}${typeLabel} `;
- }).join('')}
+ const activeIndicator = n.status === 'running' ? ' ' : '';
+ const typeLabel = n.type.split('.').pop().toUpperCase();
+ let chipAttr = '';
+ if (n.type === 'skill' && n.input) {
+ try {
+ const input = typeof n.input === 'string' ? JSON.parse(n.input) : n.input;
+ const skillName = input.skillName || input.skill;
+ if (skillName) chipAttr = ` data-title="Skill: ${skillName}"`;
+ } catch (e) { }
+ }
+ return ` ${activeIndicator}${typeLabel} `;
+ }).join('')}
`).join(' ➔');
- })()}
+ })()}
|
`;
- }).join("");
- } else {
- qs.style.display = "none";
- }
- lastDashboardState.queueHash = queueHash;
- }
-
- // 5. Update Logs
- allLogs = d.logs;
- renderLogs();
- });
+ }).join("");
+ } else {
+ qs.style.display = "none";
+ }
+ lastDashboardState.queueHash = queueHash;
+ }
+
+ // 5. Update Logs
+ allLogs = d.logs;
+ renderLogs();
+ });
+}
+
+// Initial load
+fetch("/api/log-files")
+ .then(r => r.json())
+ .then(files => {
+ const dateSelect = document.getElementById("log-date-select");
+ const today = new Date().toISOString().split('T')[0];
+
+ if (!files.includes(today)) {
+ files.unshift(today);
}
- // Initial load
- fetch("/api/log-files")
- .then(r => r.json())
- .then(files => {
- const dateSelect = document.getElementById("log-date-select");
- const today = new Date().toISOString().split('T')[0];
-
- if (!files.includes(today)) {
- files.unshift(today);
- }
-
- dateSelect.innerHTML = files.map(f => `
`).join("");
- updateDashboard();
-
- setInterval(updateDashboard, 5000);
- });
\ No newline at end of file
+ dateSelect.innerHTML = files.map(f => `
`).join("");
+ updateDashboard();
+
+ setInterval(updateDashboard, 5000);
+ });
\ No newline at end of file
diff --git a/src/services/dashboard/index.html b/src/services/dashboard/index.html
index a2e4c00..6f67d76 100644
--- a/src/services/dashboard/index.html
+++ b/src/services/dashboard/index.html
@@ -19,123 +19,136 @@
-