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: '/${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 `
+
No recent activity
+No active tickets
+