From 0572e7ad61f2566cfc461b955927a2983926decd Mon Sep 17 00:00:00 2001 From: sirpiglr <49359077-sirpiglr@users.noreply.replit.com> Date: Mon, 8 Dec 2025 04:37:02 +0000 Subject: [PATCH] Add real-time activity feed and threat alert system Introduces new API endpoints for activity, tickets, and threats, and updates the dashboard to display real-time data. Replit-Commit-Author: Agent Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: a6871c20-3d5a-4c6e-bec7-be8a96afb6dc Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/SQxsvtx Replit-Helium-Checkpoint-Created: true --- aethex-bot/bot.js | 153 +++++++++++++++++++++++++ aethex-bot/public/dashboard.html | 191 ++++++++++++++++++++++++++++++- 2 files changed, 341 insertions(+), 3 deletions(-) diff --git a/aethex-bot/bot.js b/aethex-bot/bot.js index f02f448..b18933f 100644 --- a/aethex-bot/bot.js +++ b/aethex-bot/bot.js @@ -281,6 +281,50 @@ async function sendAlert(message, embed = null) { } client.sendAlert = sendAlert; +// ============================================================================= +// ACTIVITY FEED SYSTEM (New - Dashboard Real-time) +// ============================================================================= + +const activityFeed = []; +const MAX_ACTIVITY_EVENTS = 100; +const threatAlerts = []; +const MAX_THREAT_ALERTS = 50; + +function addActivity(type, data) { + const event = { + id: Date.now() + Math.random().toString(36).substr(2, 9), + type, + data, + timestamp: new Date().toISOString(), + }; + activityFeed.unshift(event); + if (activityFeed.length > MAX_ACTIVITY_EVENTS) { + activityFeed.pop(); + } + return event; +} + +function addThreatAlert(level, message, details = {}) { + const alert = { + id: Date.now() + Math.random().toString(36).substr(2, 9), + level, + message, + details, + timestamp: new Date().toISOString(), + resolved: false, + }; + threatAlerts.unshift(alert); + if (threatAlerts.length > MAX_THREAT_ALERTS) { + threatAlerts.pop(); + } + return alert; +} + +client.addActivity = addActivity; +client.activityFeed = activityFeed; +client.addThreatAlert = addThreatAlert; +client.threatAlerts = threatAlerts; + // ============================================================================= // COMMAND LOADING // ============================================================================= @@ -411,6 +455,14 @@ client.on("interactionCreate", async (interaction) => { console.log(`[Command] Executing: ${interaction.commandName}`); await command.execute(interaction, supabase, client); console.log(`[Command] Completed: ${interaction.commandName}`); + + addActivity('command', { + command: interaction.commandName, + user: interaction.user.tag, + userId: interaction.user.id, + guild: interaction.guild?.name || 'DM', + guildId: interaction.guildId, + }); } catch (error) { console.error(`Error executing ${interaction.commandName}:`, error); @@ -610,6 +662,107 @@ http uptime: Math.floor(process.uptime()), activeTickets: activeTickets.size, heatEvents: heatMap.size, + federationLinks: federationMappings.size, + })); + return; + } + + if (req.url === "/activity" || req.url.startsWith("/activity?")) { + const url = new URL(req.url, `http://localhost:${healthPort}`); + const limit = parseInt(url.searchParams.get('limit') || '50'); + const since = url.searchParams.get('since'); + + let events = activityFeed.slice(0, Math.min(limit, 100)); + if (since) { + events = events.filter(e => new Date(e.timestamp) > new Date(since)); + } + + res.writeHead(200); + res.end(JSON.stringify({ + events, + total: activityFeed.length, + timestamp: new Date().toISOString(), + })); + return; + } + + if (req.url === "/tickets") { + const ticketList = Array.from(activeTickets.entries()).map(([channelId, data]) => ({ + channelId, + userId: data.userId, + guildId: data.guildId, + reason: data.reason, + createdAt: new Date(data.createdAt).toISOString(), + age: Math.floor((Date.now() - data.createdAt) / 60000), + })); + + res.writeHead(200); + res.end(JSON.stringify({ + tickets: ticketList, + count: ticketList.length, + timestamp: new Date().toISOString(), + })); + return; + } + + if (req.url === "/threats") { + const unresolvedThreats = threatAlerts.filter(t => !t.resolved); + const threatLevel = unresolvedThreats.length > 5 ? 'Critical' : + unresolvedThreats.length > 2 ? 'High' : + unresolvedThreats.length > 0 ? 'Medium' : 'Low'; + + res.writeHead(200); + res.end(JSON.stringify({ + alerts: threatAlerts.slice(0, 50), + unresolvedCount: unresolvedThreats.length, + threatLevel, + heatMapSize: heatMap.size, + timestamp: new Date().toISOString(), + })); + return; + } + + if (req.url === "/server-health") { + const guilds = client.guilds.cache.map(g => { + const botMember = g.members.cache.get(client.user.id); + return { + id: g.id, + name: g.name, + memberCount: g.memberCount, + online: true, + permissions: botMember?.permissions.has('Administrator') ? 'Admin' : 'Limited', + joinedAt: g.joinedAt?.toISOString(), + }; + }); + + res.writeHead(200); + res.end(JSON.stringify({ + guilds, + botStatus: client.isReady() ? 'online' : 'offline', + ping: client.ws.ping, + timestamp: new Date().toISOString(), + })); + return; + } + + if (req.url === "/system-info") { + const memUsage = process.memoryUsage(); + res.writeHead(200); + res.end(JSON.stringify({ + uptime: Math.floor(process.uptime()), + memory: { + heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024), + heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024), + rss: Math.round(memUsage.rss / 1024 / 1024), + }, + nodeVersion: process.version, + platform: process.platform, + ping: client.ws.ping, + guilds: client.guilds.cache.size, + commands: client.commands.size, + activityEvents: activityFeed.length, + whitelistedUsers: whitelistedUsers.length, + timestamp: new Date().toISOString(), })); return; } diff --git a/aethex-bot/public/dashboard.html b/aethex-bot/public/dashboard.html index 22d4108..a9dbe59 100644 --- a/aethex-bot/public/dashboard.html +++ b/aethex-bot/public/dashboard.html @@ -1334,6 +1334,184 @@ return num.toString(); } + // Format relative time + function formatTimeAgo(timestamp) { + const seconds = Math.floor((Date.now() - new Date(timestamp)) / 1000); + if (seconds < 60) return 'Just now'; + if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`; + if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`; + return `${Math.floor(seconds / 86400)}d ago`; + } + + // Activity type icons and colors + const activityConfig = { + command: { icon: '', color: 'purple' }, + member_join: { icon: '', color: 'green' }, + member_leave: { icon: '', color: 'orange' }, + mod_action: { icon: '', color: 'red' }, + ticket: { icon: '', color: 'blue' }, + xp: { icon: '', color: 'orange' }, + alert: { icon: '', color: 'red' }, + }; + + // Render activity event + function renderActivityEvent(event) { + const config = activityConfig[event.type] || activityConfig.command; + let text = ''; + + switch (event.type) { + case 'command': + text = `${event.data.user} used /${event.data.command} in ${event.data.guild}`; + break; + case 'member_join': + text = `${event.data.user} joined ${event.data.guild}`; + break; + case 'member_leave': + text = `${event.data.user} left ${event.data.guild}`; + break; + case 'mod_action': + text = `${event.data.moderator} ${event.data.action} ${event.data.target}`; + break; + case 'ticket': + text = `Ticket ${event.data.action}: ${event.data.reason || 'Support request'}`; + break; + case 'xp': + text = `${event.data.user} earned ${event.data.amount} XP`; + break; + case 'alert': + text = `${event.data.message}`; + break; + default: + text = JSON.stringify(event.data); + } + + return ` +
+
+ ${config.icon} +
+
+
${text}
+
${formatTimeAgo(event.timestamp)}
+
+
+ `; + } + + // Fetch activity feed + async function fetchActivityFeed() { + try { + const response = await fetch('/activity?limit=20'); + const data = await response.json(); + + const feedContainer = document.getElementById('activityFeed'); + if (data.events && data.events.length > 0) { + feedContainer.innerHTML = data.events.map(renderActivityEvent).join(''); + const badge = document.getElementById('activityBadge'); + badge.textContent = data.events.length; + badge.style.display = 'inline'; + } else { + feedContainer.innerHTML = ` +
+ +

No recent activity

+
+ `; + } + } catch (error) { + console.error('Failed to fetch activity:', error); + } + } + + // Fetch tickets + async function fetchTickets() { + try { + const response = await fetch('/tickets'); + const data = await response.json(); + + const ticketList = document.getElementById('ticketList'); + const ticketBadge = document.getElementById('ticketBadge'); + const ticketCountBadge = document.getElementById('ticketCountBadge'); + + ticketBadge.textContent = data.count || 0; + ticketCountBadge.textContent = `${data.count || 0} open`; + + if (data.tickets && data.tickets.length > 0) { + ticketList.innerHTML = data.tickets.map(t => ` +
+
+ +
+
+
${t.reason || 'Support Request'}
+
Open for ${t.age} minutes
+
+ Open +
+ `).join(''); + } else { + ticketList.innerHTML = ` +
+ +

No active tickets

+
+ `; + } + } catch (error) { + console.error('Failed to fetch tickets:', error); + } + } + + // Fetch threat alerts + async function fetchThreats() { + try { + const response = await fetch('/threats'); + const data = await response.json(); + + document.getElementById('sentinelHeatMap').textContent = data.heatMapSize || 0; + document.getElementById('heatMapSize').textContent = data.heatMapSize || 0; + document.getElementById('threatLevel').textContent = data.threatLevel || 'Low'; + + const threatLevelEl = document.getElementById('threatLevel'); + threatLevelEl.style.color = data.threatLevel === 'Critical' ? 'var(--danger)' : + data.threatLevel === 'High' ? 'var(--warning)' : + data.threatLevel === 'Medium' ? 'var(--info)' : 'var(--success)'; + + const threatMonitor = document.querySelector('#section-sentinel .card-body:last-child'); + if (data.alerts && data.alerts.length > 0) { + threatMonitor.innerHTML = data.alerts.slice(0, 10).map(a => ` +
+
+ +
+
+
${a.message}
+
${formatTimeAgo(a.timestamp)}
+
+ ${a.resolved ? 'Resolved' : 'Active'} +
+ `).join(''); + } + } catch (error) { + console.error('Failed to fetch threats:', error); + } + } + + // Fetch system info + async function fetchSystemInfo() { + try { + const response = await fetch('/system-info'); + const data = await response.json(); + + document.getElementById('memoryUsage').textContent = `${data.memory.heapUsed}MB`; + document.getElementById('botPing').textContent = `${data.ping}ms`; + document.getElementById('nodeVersion').textContent = data.nodeVersion; + document.getElementById('whitelistedCount').textContent = data.whitelistedUsers || 0; + } catch (error) { + console.error('Failed to fetch system info:', error); + } + } + // Fetch and update data async function refreshData() { try { @@ -1356,8 +1534,9 @@ // Sentinel stats document.getElementById('heatMapSize').textContent = health.heatMapSize || 0; document.getElementById('activeTickets').textContent = stats.activeTickets || 0; - document.getElementById('federationCount').textContent = '0'; + document.getElementById('federationCount').textContent = stats.federationLinks || 0; document.getElementById('ticketBadge').textContent = stats.activeTickets || 0; + document.getElementById('sentinelFederation').textContent = stats.federationLinks || 0; // Server list const serverList = document.getElementById('serverList'); @@ -1406,11 +1585,17 @@ // Sentinel stats (in section) document.getElementById('sentinelHeatMap').textContent = health.heatMapSize || 0; - document.getElementById('threatLevel').textContent = health.heatMapSize > 10 ? 'Medium' : 'Low'; - document.getElementById('sentinelFederation').textContent = '0'; // Last update document.getElementById('lastUpdate').textContent = new Date().toLocaleTimeString(); + + // Fetch additional real-time data + await Promise.all([ + fetchActivityFeed(), + fetchTickets(), + fetchThreats(), + fetchSystemInfo() + ]); } catch (error) { console.error('Failed to fetch data:', error);