diff --git a/Overlord-Server/public/assets/dashboard-stats.js b/Overlord-Server/public/assets/dashboard-stats.js index f3ced54..a3ce1b8 100644 --- a/Overlord-Server/public/assets/dashboard-stats.js +++ b/Overlord-Server/public/assets/dashboard-stats.js @@ -97,6 +97,19 @@ function formatWeekLabel(timestamp) { }); } +function compactRepeatedTickLabel(value, index, ticks) { + const labelFor = (tickValue) => { + const labels = this?.chart?.data?.labels || []; + return labels[Number(tickValue)] ?? this?.getLabelForValue?.(tickValue) ?? ""; + }; + const labels = (ticks || []).map((tick) => labelFor(tick.value)); + const current = labels[index] || labelFor(value); + if (!current) return ""; + const isRunStart = index === 0 || labels[index - 1] !== current; + const isRunEnd = index === labels.length - 1 || labels[index + 1] !== current; + return isRunStart || isRunEnd ? current : ""; +} + function aggregateOnlineHistory(history, snapshot) { const now = Date.now(); const weekAgo = now - 7 * 24 * 60 * 60 * 1000; @@ -227,7 +240,12 @@ function makeCharts() { scales: { x: { grid: { display: false }, - ticks: { color: palette.muted, maxRotation: 0, maxTicksLimit: 5 }, + ticks: { + color: palette.muted, + maxRotation: 0, + maxTicksLimit: 5, + callback: compactRepeatedTickLabel, + }, }, y: { beginAtZero: true, diff --git a/Overlord-Server/public/assets/metrics.js b/Overlord-Server/public/assets/metrics.js index 7bb3e6e..bad738f 100644 --- a/Overlord-Server/public/assets/metrics.js +++ b/Overlord-Server/public/assets/metrics.js @@ -16,6 +16,9 @@ let latestByCountry = {}; const GEOJSON_URL = "/vendor/geo-countries/countries.geojson"; const MAX_CHART_POINTS = 240; const METRICS_POLL_INTERVAL_MS = 5000; +const SESSION_LABELS = ["Console", "Remote Desktop", "Files", "Processes"]; +const SESSION_COLORS = ["#34d399", "#c084fc", "#60a5fa", "#fb923c"]; +const SESSION_EMPTY_COLOR = "rgba(100, 116, 139, 0.35)"; if (typeof Chart !== "undefined") { Chart.defaults.color = "#cbd5e1"; @@ -76,6 +79,34 @@ function makeLineChart(canvas, datasets, options = {}) { }); } +function getSessionValues(sessions = {}) { + return [ + Number(sessions.console) || 0, + Number(sessions.remoteDesktop) || 0, + Number(sessions.fileBrowser) || 0, + Number(sessions.process) || 0, + ]; +} + +function updateSessionsChart(sessions = {}) { + if (!sessionsChart) return; + const values = getSessionValues(sessions); + const total = values.reduce((sum, value) => sum + value, 0); + const dataset = sessionsChart.data.datasets[0]; + if (total > 0) { + sessionsChart.data.labels = SESSION_LABELS; + dataset.data = values; + dataset.backgroundColor = SESSION_COLORS; + sessionsChart.options.plugins.legend.display = true; + } else { + sessionsChart.data.labels = ["No active sessions"]; + dataset.data = [1]; + dataset.backgroundColor = [SESSION_EMPTY_COLOR]; + sessionsChart.options.plugins.legend.display = false; + } + sessionsChart.update("none"); +} + function initCharts() { const clientsCtx = document.getElementById("clients-chart"); const commandsCtx = document.getElementById("commands-chart"); @@ -403,10 +434,10 @@ function initCharts() { sessionsChart = sessionsCtx ? new Chart(sessionsCtx, { type: "doughnut", data: { - labels: ["Console", "Remote Desktop", "Files", "Processes"], + labels: ["No active sessions"], datasets: [{ - data: [0, 0, 0, 0], - backgroundColor: ["#34d399", "#c084fc", "#60a5fa", "#fb923c"], + data: [1], + backgroundColor: [SESSION_EMPTY_COLOR], borderColor: "#0f172a", borderWidth: 2, }], @@ -417,8 +448,17 @@ function initCharts() { animation: false, cutout: "64%", plugins: { - legend: { position: "right", labels: { boxWidth: 10, boxHeight: 10 } }, - tooltip: chartTooltip(), + legend: { display: false, position: "right", labels: { boxWidth: 10, boxHeight: 10 } }, + tooltip: { + ...chartTooltip(), + callbacks: { + label: (ctx) => { + if (ctx.chart.data.labels?.[ctx.dataIndex] === "No active sessions") return "No active sessions"; + const value = Number(ctx.parsed) || 0; + return `${ctx.label}: ${value.toLocaleString()} session${value === 1 ? "" : "s"}`; + }, + }, + }, }, }, }) : null; @@ -533,11 +573,7 @@ function updateMetrics(data, debug) { ); animateCounter(document.getElementById("clients-total"), data.clients.total); - const totalSessions = - data.sessions.console + - data.sessions.remoteDesktop + - data.sessions.fileBrowser + - data.sessions.process; + const totalSessions = getSessionValues(data.sessions).reduce((sum, value) => sum + value, 0); animateCounter(document.getElementById("active-sessions"), totalSessions); animateCounter( @@ -657,15 +693,7 @@ function updateMetrics(data, debug) { osList.innerHTML = '
No clients
'; } - if (sessionsChart) { - sessionsChart.data.datasets[0].data = [ - data.sessions.console || 0, - data.sessions.remoteDesktop || 0, - data.sessions.fileBrowser || 0, - data.sessions.process || 0, - ]; - sessionsChart.update("none"); - } + updateSessionsChart(data.sessions); if (osChart) { const osEntries = Object.entries(data.clients.byOS || {})