From cd6f84fd4d38dfd528d2069771014c6cbf944d8e Mon Sep 17 00:00:00 2001
From: sirpiglr <49359077-sirpiglr@users.noreply.replit.com>
Date: Mon, 8 Dec 2025 06:40:51 +0000
Subject: [PATCH] Add detailed analytics and charts for command usage trends
Integrate new '/command-analytics' endpoint to display daily trends, hourly activity, and top users in the dashboard, alongside updating the existing '/analytics' data fetching to support concurrent requests.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 0275c4be-7268-43b8-a8d0-8f97a0077139
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/4ZEVdt6
Replit-Helium-Checkpoint-Created: true
---
aethex-bot/public/dashboard.html | 467 ++++++++++++++++++++++++++++++-
1 file changed, 454 insertions(+), 13 deletions(-)
diff --git a/aethex-bot/public/dashboard.html b/aethex-bot/public/dashboard.html
index eb4c1af..e14273a 100644
--- a/aethex-bot/public/dashboard.html
+++ b/aethex-bot/public/dashboard.html
@@ -1502,6 +1502,59 @@
+
+
+
-
/${cmd.name}
-
-
+ const commands = cmdAnalytics.commands && cmdAnalytics.commands.length > 0
+ ? cmdAnalytics.commands
+ : (data.commandUsage || []);
+
+ if (commands.length > 0) {
+ const maxCmd = Math.max(...commands.map(c => parseInt(c.count) || 0), 1);
+ commandBars.innerHTML = commands.slice(0, 8).map(cmd => {
+ const name = cmd.command_name || cmd.name || 'unknown';
+ const count = parseInt(cmd.count) || 0;
+ const successRate = cmd.success_count ? Math.round((parseInt(cmd.success_count) / count) * 100) : 100;
+ const avgTime = cmd.avg_time ? Math.round(parseFloat(cmd.avg_time)) : 0;
+ return `
+
-
${cmd.count}
-
- `).join('');
+ `;
+ }).join('');
} else {
commandBars.innerHTML = '
';
}
+ // Render daily line chart from database
+ const dailyChartEl = document.getElementById('dailyLineChart');
+ if (dailyChartEl && cmdAnalytics.daily && cmdAnalytics.daily.length > 0) {
+ renderDailyLineChart(dailyChartEl, cmdAnalytics.daily);
+ }
+
+ // Render hourly chart from database
+ const hourlyChartEl = document.getElementById('hourlyBarChart');
+ if (hourlyChartEl && cmdAnalytics.hourly && cmdAnalytics.hourly.length > 0) {
+ renderHourlyChart(hourlyChartEl, cmdAnalytics.hourly);
+ }
+
+ // Render top users
+ const topUsersEl = document.getElementById('topUsersChart');
+ if (topUsersEl && cmdAnalytics.topUsers && cmdAnalytics.topUsers.length > 0) {
+ renderTopUsersChart(topUsersEl, cmdAnalytics.topUsers);
+ }
+
+ // Fallback heatmap from in-memory data
const heatmapGrid = document.getElementById('heatmapGrid');
if (data.hourlyActivity && data.hourlyActivity.length === 24) {
const maxHour = Math.max(...data.hourlyActivity, 1);
@@ -2601,6 +2688,112 @@
console.error('Failed to fetch analytics:', error);
}
}
+
+ // Render daily line chart (SVG-based)
+ function renderDailyLineChart(container, data) {
+ const width = container.clientWidth || 400;
+ const height = 120;
+ const padding = 30;
+
+ if (data.length === 0) {
+ container.innerHTML = '
No data available
';
+ return;
+ }
+
+ const counts = data.map(d => parseInt(d.count) || 0);
+ const maxCount = Math.max(...counts, 1);
+ const minCount = 0;
+
+ const xStep = (width - padding * 2) / Math.max(data.length - 1, 1);
+ const yScale = (height - padding * 2) / (maxCount - minCount || 1);
+
+ const points = data.map((d, i) => ({
+ x: padding + i * xStep,
+ y: height - padding - (parseInt(d.count) || 0) * yScale,
+ date: d.date,
+ count: parseInt(d.count) || 0
+ }));
+
+ const linePath = points.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x} ${p.y}`).join(' ');
+ const areaPath = `${linePath} L ${points[points.length - 1].x} ${height - padding} L ${points[0].x} ${height - padding} Z`;
+
+ container.innerHTML = `
+
+ `;
+ }
+
+ // Render hourly bar chart
+ function renderHourlyChart(container, data) {
+ const hours = Array(24).fill(0);
+ data.forEach(d => {
+ const h = parseInt(d.hour);
+ if (h >= 0 && h < 24) hours[h] = parseInt(d.count) || 0;
+ });
+
+ const maxCount = Math.max(...hours, 1);
+
+ container.innerHTML = `
+
+ ${hours.map((count, h) => {
+ const heightPct = 10 + (count / maxCount) * 90;
+ return `
+
+ `;
+ }).join('')}
+
+
+ 0:00
+ 6:00
+ 12:00
+ 18:00
+ 23:00
+
+ `;
+ }
+
+ // Render top users chart
+ function renderTopUsersChart(container, data) {
+ if (data.length === 0) {
+ container.innerHTML = '
No user data available
';
+ return;
+ }
+
+ const maxCount = Math.max(...data.map(d => parseInt(d.command_count) || 0), 1);
+
+ container.innerHTML = data.slice(0, 5).map((user, i) => {
+ const count = parseInt(user.command_count) || 0;
+ const pct = (count / maxCount) * 100;
+ const rankClass = i === 0 ? 'gold' : i === 1 ? 'silver' : i === 2 ? 'bronze' : 'default';
+ const tag = user.user_tag || `User ${user.user_id?.slice(-4) || '????'}`;
+
+ return `
+
+
${i + 1}
+
${tag}
+
+
${count}
+
+ `;
+ }).join('');
+ }
// Fetch leaderboard
async function fetchLeaderboard() {
@@ -3504,6 +3697,251 @@
}, 3000);
}
+ // =============================================================================
+ // WEBSOCKET CONNECTION FOR REAL-TIME UPDATES
+ // =============================================================================
+
+ let ws = null;
+ let wsReconnectAttempts = 0;
+ const MAX_RECONNECT_ATTEMPTS = 5;
+ const WS_RECONNECT_DELAY = 3000;
+ let wsConnected = false;
+
+ function initWebSocket() {
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
+ const wsUrl = `${protocol}//${window.location.host}`;
+
+ try {
+ ws = new WebSocket(wsUrl);
+
+ ws.onopen = () => {
+ console.log('[WebSocket] Connected');
+ wsConnected = true;
+ wsReconnectAttempts = 0;
+ updateConnectionStatus(true);
+
+ // Reduce polling interval since we have real-time updates
+ if (refreshInterval) {
+ clearInterval(refreshInterval);
+ refreshInterval = setInterval(refreshData, 60000); // Slower polling as backup
+ }
+ };
+
+ ws.onmessage = (event) => {
+ try {
+ const message = JSON.parse(event.data);
+ handleWebSocketMessage(message);
+ } catch (e) {
+ console.error('[WebSocket] Parse error:', e);
+ }
+ };
+
+ ws.onclose = () => {
+ console.log('[WebSocket] Disconnected');
+ wsConnected = false;
+ updateConnectionStatus(false);
+
+ // Immediately restore polling while disconnected
+ if (!refreshInterval) {
+ console.log('[WebSocket] Restoring polling while disconnected');
+ refreshInterval = setInterval(refreshData, 30000);
+ }
+
+ // Attempt reconnection with exponential backoff
+ if (wsReconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
+ wsReconnectAttempts++;
+ const backoffDelay = WS_RECONNECT_DELAY * Math.pow(1.5, wsReconnectAttempts - 1);
+ console.log(`[WebSocket] Reconnecting in ${Math.round(backoffDelay/1000)}s (attempt ${wsReconnectAttempts})...`);
+ setTimeout(initWebSocket, backoffDelay);
+ } else {
+ console.log('[WebSocket] Max reconnect attempts reached, continuing with polling');
+ }
+ };
+
+ ws.onerror = (error) => {
+ console.error('[WebSocket] Error:', error);
+ };
+ } catch (e) {
+ console.error('[WebSocket] Failed to connect:', e);
+ }
+ }
+
+ function handleWebSocketMessage(message) {
+ switch (message.type) {
+ case 'stats':
+ updateStatsFromWebSocket(message.data);
+ break;
+ case 'activity':
+ addActivityFromWebSocket(message.data);
+ break;
+ case 'command':
+ updateCommandFromWebSocket(message.data);
+ break;
+ case 'threat':
+ addThreatFromWebSocket(message.data);
+ break;
+ case 'ticket':
+ updateTicketFromWebSocket(message.data);
+ break;
+ default:
+ console.log('[WebSocket] Unknown message type:', message.type);
+ }
+ }
+
+ function updateStatsFromWebSocket(data) {
+ if (data.guilds !== undefined) {
+ document.getElementById('serverCount').textContent = data.guilds;
+ }
+ if (data.totalMembers !== undefined) {
+ document.getElementById('memberCount').textContent = formatNumber(data.totalMembers);
+ }
+ if (data.uptime !== undefined) {
+ document.getElementById('uptime').textContent = formatUptime(data.uptime);
+ document.getElementById('systemUptime').textContent = formatUptime(data.uptime);
+ }
+ if (data.heatMapSize !== undefined) {
+ document.getElementById('heatMapSize').textContent = data.heatMapSize;
+ document.getElementById('sentinelHeatMap').textContent = data.heatMapSize;
+ }
+ if (data.activeTickets !== undefined) {
+ document.getElementById('activeTickets').textContent = data.activeTickets;
+ document.getElementById('ticketBadge').textContent = data.activeTickets;
+ }
+ if (data.federationLinks !== undefined) {
+ document.getElementById('federationCount').textContent = data.federationLinks;
+ document.getElementById('sentinelFederation').textContent = data.federationLinks;
+ }
+ if (data.commandsExecuted !== undefined) {
+ document.getElementById('commandCount').textContent = data.commandsExecuted;
+ }
+
+ // Update last update time
+ document.getElementById('lastUpdate').textContent = new Date().toLocaleTimeString();
+ }
+
+ function addActivityFromWebSocket(data) {
+ const activityFeed = document.getElementById('activityFeed');
+ if (!activityFeed) return;
+
+ const iconClass = data.type === 'command' ? 'purple' :
+ data.type === 'moderation' ? 'red' :
+ data.type === 'member' ? 'green' : 'blue';
+ const iconSvg = data.type === 'command' ? '
' :
+ data.type === 'moderation' ? '
' :
+ data.type === 'member' ? '
' :
+ '
';
+
+ const newActivity = document.createElement('div');
+ newActivity.className = 'activity-item';
+ newActivity.innerHTML = `
+
+
+
+
+
${data.message || 'Activity'}
+
Just now
+
+ `;
+
+ // Add animation
+ newActivity.style.animation = 'slideIn 0.3s ease';
+
+ // Insert at top
+ if (activityFeed.firstChild) {
+ activityFeed.insertBefore(newActivity, activityFeed.firstChild);
+ } else {
+ activityFeed.appendChild(newActivity);
+ }
+
+ // Limit to 20 items
+ while (activityFeed.children.length > 20) {
+ activityFeed.removeChild(activityFeed.lastChild);
+ }
+ }
+
+ function updateCommandFromWebSocket(data) {
+ // Update command analytics if on that section
+ if (data.commandName && data.success !== undefined) {
+ // Could update command usage chart here
+ console.log(`[WebSocket] Command executed: ${data.commandName}`);
+ }
+ }
+
+ function addThreatFromWebSocket(data) {
+ const threatFeed = document.getElementById('threatsFeed');
+ if (!threatFeed) return;
+
+ const levelClass = data.level === 'critical' ? 'danger' :
+ data.level === 'high' ? 'warning' : 'info';
+
+ const newThreat = document.createElement('div');
+ newThreat.className = 'activity-item';
+ newThreat.innerHTML = `
+
+
+
${data.message || 'Security alert'}
+
Just now - ${data.level || 'unknown'}
+
+ `;
+
+ newThreat.style.animation = 'slideIn 0.3s ease';
+
+ if (threatFeed.firstChild) {
+ threatFeed.insertBefore(newThreat, threatFeed.firstChild);
+ } else {
+ threatFeed.appendChild(newThreat);
+ }
+
+ // Update threat badge
+ const badge = document.getElementById('threatBadge');
+ if (badge) {
+ badge.textContent = parseInt(badge.textContent || 0) + 1;
+ }
+ }
+
+ function updateTicketFromWebSocket(data) {
+ if (data.action === 'create') {
+ const badge = document.getElementById('ticketBadge');
+ if (badge) {
+ badge.textContent = parseInt(badge.textContent || 0) + 1;
+ }
+ document.getElementById('activeTickets').textContent =
+ parseInt(document.getElementById('activeTickets').textContent || 0) + 1;
+ } else if (data.action === 'close') {
+ const badge = document.getElementById('ticketBadge');
+ if (badge) {
+ const current = parseInt(badge.textContent || 0);
+ badge.textContent = Math.max(0, current - 1);
+ }
+ const activeEl = document.getElementById('activeTickets');
+ activeEl.textContent = Math.max(0, parseInt(activeEl.textContent || 0) - 1);
+ }
+ }
+
+ function updateConnectionStatus(connected) {
+ const statusDot = document.getElementById('statusDot');
+ const statusText = document.getElementById('statusText');
+
+ if (connected) {
+ // Add WebSocket indicator
+ if (!document.getElementById('wsIndicator')) {
+ const indicator = document.createElement('span');
+ indicator.id = 'wsIndicator';
+ indicator.style.cssText = 'margin-left: 8px; font-size: 0.7rem; color: var(--success);';
+ indicator.textContent = '(Live)';
+ statusText.parentNode.appendChild(indicator);
+ }
+ } else {
+ const indicator = document.getElementById('wsIndicator');
+ if (indicator) indicator.remove();
+ }
+ }
+
// Initialize
document.addEventListener('DOMContentLoaded', () => {
// Load theme
@@ -3511,6 +3949,9 @@
document.documentElement.setAttribute('data-theme', savedTheme);
updateThemeIcon(savedTheme);
+ // Initialize WebSocket connection
+ initWebSocket();
+
// Initial fetch
refreshData();
fetchExtendedStatus();
@@ -3518,7 +3959,7 @@
fetchFoundationStats();
fetchIntegrationStatus();
- // Auto-refresh every 30s
+ // Auto-refresh every 30s (will be reduced when WebSocket connects)
refreshInterval = setInterval(refreshData, 30000);
});