diff --git a/docs/plans/state.json b/docs/plans/state.json index f7839a1..beecb3e 100644 --- a/docs/plans/state.json +++ b/docs/plans/state.json @@ -50,6 +50,17 @@ ], "test_status": "pnpm test:run src/automation/presets.test.ts src/automation/cron.test.ts src/automation/heartbeat.test.ts src/backup/scheduler.test.ts src/backup/status.test.ts src/tools/builtin/minio-share.test.ts src/tools/policy.test.ts src/config/schema.test.ts src/daemon/channels.test.ts src/gateway/handlers/services.test.ts src/session/store.test.ts src/tools/executor.test.ts src/gateway/handlers/handlers.test.ts + pnpm typecheck passing" }, + "dashboard-session-analytics-panel": { + "status": "completed", + "date": "2026-02-16", + "updated": "2026-02-16", + "summary": "Extended the web UI dashboard with a Session Analytics panel backed by `system.sessionAnalytics`, including window summary cards, top tools/topics, top sessions, and daily trend tables refreshed alongside health/services.", + "files_modified": [ + "src/gateway/ui/pages/dashboard.js", + "docs/plans/state.json" + ], + "test_status": "pnpm eslint src/gateway/ui/pages/dashboard.js + pnpm typecheck passing; full pnpm lint currently fails due pre-existing unrelated repo lint errors" + }, "backup-session-summary-audit-trail": { "status": "completed", "date": "2026-02-16", diff --git a/src/gateway/ui/pages/dashboard.js b/src/gateway/ui/pages/dashboard.js index 4004f77..9d827d9 100644 --- a/src/gateway/ui/pages/dashboard.js +++ b/src/gateway/ui/pages/dashboard.js @@ -33,6 +33,21 @@ function formatTime(timestamp) { return d.toLocaleTimeString('en-GB', { hour12: false }); } +function formatDay(day) { + const parsed = new Date(`${day}T00:00:00`); + if (Number.isNaN(parsed.getTime())) {return day;} + return parsed.toLocaleDateString('en-GB', { day: '2-digit', month: 'short' }); +} + +function formatNumber(value) { + return (value ?? 0).toLocaleString(); +} + +function formatSessionDurationFromMessages(avgMessagesPerSession) { + if (!avgMessagesPerSession || avgMessagesPerSession <= 0) {return '—';} + return `${avgMessagesPerSession.toFixed(1)} msgs/session`; +} + function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; @@ -55,6 +70,11 @@ function renderSkeleton(el) {
Loading...
+

Session Analytics

+
+
Loading...
+
+

Event Stream

Loading events...
@@ -217,6 +237,115 @@ function updateActiveRequests(requestsData) { `; } +function updateSessionAnalytics(analyticsData) { + const el = document.getElementById('ops-session-analytics'); + if (!el) {return;} + + const daily = analyticsData?.daily ?? []; + const topSessions = analyticsData?.topSessions ?? []; + const topTools = analyticsData?.topTools ?? []; + const topTopics = analyticsData?.topTopics ?? []; + + const totalSessions = analyticsData?.totalSessions ?? 0; + const totalMessages = analyticsData?.totalMessages ?? 0; + const avgMessagesPerSession = analyticsData?.averageMessagesPerSession ?? 0; + + const summaryHtml = ` +
+
+
Sessions (Window)
+
${formatNumber(totalSessions)}
+
+
+
Messages (Window)
+
${formatNumber(totalMessages)}
+
+
+
Avg Session
+
${formatSessionDurationFromMessages(avgMessagesPerSession)}
+
+
+ `; + + const topToolsHtml = topTools.length > 0 + ? `` + : '
No tool usage captured in this window
'; + + const topTopicsHtml = topTopics.length > 0 + ? `` + : '
No indexed topics captured in this window
'; + + const topSessionsHtml = topSessions.length > 0 + ? ` + + + + + + + + + ${topSessions.map((session) => ` + + + + + + `).join('')} + +
SessionMessagesLast Active
${escapeHtml(session.sessionId)}${formatNumber(session.messages)}${timeAgo(session.lastActivity * 1000)}
` + : '
No session activity in this window
'; + + const dailyHtml = daily.length > 0 + ? ` + + + + + + + + + ${daily.slice(0, 7).map((row) => ` + + + + + + `).join('')} + +
DaySessionsMessages
${formatDay(row.day)}${formatNumber(row.sessions)}${formatNumber(row.messages)}
` + : '
No daily activity in this window
'; + + el.innerHTML = ` + ${summaryHtml} +
+
+ Top Tools + ${topToolsHtml} +
+
+ Top Topics + ${topTopicsHtml} +
+
+
+
+ Top Sessions + ${topSessionsHtml} +
+
+ Daily Trend (Last 7 Rows) + ${dailyHtml} +
+
+ `; +} + function _updateChannels(channelsData) { const el = document.getElementById('ops-channels'); if (!el) {return;} @@ -285,11 +414,12 @@ async function fetchFast(client) { async function fetchSlow(client) { try { - const [health, services] = await Promise.all([ + const [health, services, sessionAnalytics] = await Promise.all([ client.call('system.health'), client.call('system.services'), + client.call('system.sessionAnalytics', { days: 14, topLimit: 5 }), ]); - return { health, services }; + return { health, services, sessionAnalytics }; } catch { return null; } @@ -320,6 +450,7 @@ async function loadDashboard(el, client) { } if (slow) { updateServices(slow.services); + updateSessionAnalytics(slow.sessionAnalytics); } // Fast refresh: 3 seconds for metrics, events, requests @@ -341,6 +472,7 @@ async function loadDashboard(el, client) { _lastHealth = data.health; updateCounters(_lastMetrics, data.health); updateServices(data.services); + updateSessionAnalytics(data.sessionAnalytics); } }, 10000); }