diff --git a/aethex-bot/bot.js b/aethex-bot/bot.js index a16a361..82c772b 100644 --- a/aethex-bot/bot.js +++ b/aethex-bot/bot.js @@ -183,6 +183,16 @@ const WHITELISTED_GUILDS = [ ]; client.WHITELISTED_GUILDS = WHITELISTED_GUILDS; +client.on('error', (error) => { + console.error('[Discord] Client error:', error.message); + addErrorLog('discord', 'Discord client error', { error: error.message }); +}); + +client.on('warn', (warning) => { + console.warn('[Discord] Warning:', warning); + addErrorLog('warning', 'Discord warning', { warning }); +}); + client.on('guildCreate', async (guild) => { if (!WHITELISTED_GUILDS.includes(guild.id)) { console.log(`[Whitelist] Unauthorized server detected: ${guild.name} (${guild.id}) - Leaving...`); @@ -290,6 +300,79 @@ const MAX_ACTIVITY_EVENTS = 100; const threatAlerts = []; const MAX_THREAT_ALERTS = 50; +const errorLogs = []; +const MAX_ERROR_LOGS = 50; +const commandQueue = []; +const MAX_COMMAND_QUEUE = 100; +let lastCpuUsage = process.cpuUsage(); +let lastCpuTime = Date.now(); + +function addErrorLog(type, message, details = {}) { + const log = { + id: Date.now() + Math.random().toString(36).substr(2, 9), + type, + message, + details, + timestamp: new Date().toISOString(), + }; + errorLogs.unshift(log); + if (errorLogs.length > MAX_ERROR_LOGS) { + errorLogs.pop(); + } + return log; +} + +function addToCommandQueue(command, status = 'pending') { + const entry = { + id: Date.now() + Math.random().toString(36).substr(2, 9), + command, + status, + timestamp: new Date().toISOString(), + }; + commandQueue.unshift(entry); + if (commandQueue.length > MAX_COMMAND_QUEUE) { + commandQueue.pop(); + } + return entry; +} + +function updateCommandQueue(id, status) { + const entry = commandQueue.find(e => e.id === id); + if (entry) { + entry.status = status; + entry.completedAt = new Date().toISOString(); + } +} + +let currentCpuUsage = 0; + +function updateCpuUsage() { + const now = Date.now(); + const cpuUsage = process.cpuUsage(lastCpuUsage); + const elapsed = (now - lastCpuTime) * 1000; + + if (elapsed > 0) { + const userPercent = (cpuUsage.user / elapsed) * 100; + const systemPercent = (cpuUsage.system / elapsed) * 100; + currentCpuUsage = Math.min(100, Math.round(userPercent + systemPercent)); + } + + lastCpuUsage = process.cpuUsage(); + lastCpuTime = now; +} + +setInterval(updateCpuUsage, 5000); + +function getCpuUsage() { + return currentCpuUsage; +} + +client.addErrorLog = addErrorLog; +client.errorLogs = errorLogs; +client.commandQueue = commandQueue; +client.addToCommandQueue = addToCommandQueue; +client.updateCommandQueue = updateCommandQueue; + // Analytics tracking const analyticsData = { commandUsage: {}, @@ -504,11 +587,14 @@ client.on("interactionCreate", async (interaction) => { return; } + const queueEntry = addToCommandQueue(`/${interaction.commandName} by ${interaction.user.tag}`, 'pending'); + try { console.log(`[Command] Executing: ${interaction.commandName}`); await command.execute(interaction, supabase, client); console.log(`[Command] Completed: ${interaction.commandName}`); + updateCommandQueue(queueEntry.id, 'completed'); trackCommand(interaction.commandName); resetDailyAnalytics(); @@ -522,6 +608,15 @@ client.on("interactionCreate", async (interaction) => { } catch (error) { console.error(`Error executing ${interaction.commandName}:`, error); + updateCommandQueue(queueEntry.id, 'failed'); + addErrorLog('command', `Error in /${interaction.commandName}`, { + command: interaction.commandName, + user: interaction.user.tag, + userId: interaction.user.id, + guild: interaction.guild?.name || 'DM', + error: error.message, + }); + try { const errorEmbed = new EmbedBuilder() .setColor(0xff0000) @@ -803,6 +898,11 @@ http if (req.url === "/system-info") { const memUsage = process.memoryUsage(); + const cpu = getCpuUsage(); + const pendingCommands = commandQueue.filter(c => c.status === 'pending').length; + const completedCommands = commandQueue.filter(c => c.status === 'completed').length; + const failedCommands = commandQueue.filter(c => c.status === 'failed').length; + res.writeHead(200); res.end(JSON.stringify({ uptime: Math.floor(process.uptime()), @@ -811,6 +911,7 @@ http heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024), rss: Math.round(memUsage.rss / 1024 / 1024), }, + cpu: cpu, nodeVersion: process.version, platform: process.platform, ping: client.ws.ping, @@ -818,6 +919,15 @@ http commands: client.commands.size, activityEvents: activityFeed.length, whitelistedUsers: whitelistedUsers.length, + errorLogs: errorLogs.slice(0, 20), + errorCount: errorLogs.length, + commandQueue: { + pending: pendingCommands, + completed: completedCommands, + failed: failedCommands, + total: commandQueue.length, + recent: commandQueue.slice(0, 10), + }, timestamp: new Date().toISOString(), })); return; @@ -1738,6 +1848,146 @@ http return; } + // GET /studio-feed - Get AeThex Studio community feed + if (req.url === "/studio-feed" && req.method === "GET") { + if (!supabase) { + res.writeHead(200); + res.end(JSON.stringify({ + success: false, + message: "Supabase not configured - Studio feed unavailable", + posts: [] + })); + return; + } + + (async () => { + try { + const { data: posts, error } = await supabase + .from('community_posts') + .select('*') + .order('created_at', { ascending: false }) + .limit(20); + + if (error) throw error; + + res.writeHead(200); + res.end(JSON.stringify({ + success: true, + posts: posts || [], + count: posts?.length || 0, + timestamp: new Date().toISOString(), + })); + } catch (error) { + res.writeHead(200); + res.end(JSON.stringify({ + success: false, + message: "No community posts table available", + posts: [] + })); + } + })(); + return; + } + + // GET /foundation-stats - Get AeThex Foundation statistics + if (req.url === "/foundation-stats" && req.method === "GET") { + (async () => { + const foundationGuild = client.guilds.cache.get(REALM_GUILDS.foundation); + + const stats = { + success: true, + contributors: 0, + projects: 0, + commits: 0, + members: foundationGuild?.memberCount || 0, + activity: [], + timestamp: new Date().toISOString(), + }; + + if (supabase) { + try { + const { count: contributorCount } = await supabase + .from('user_profiles') + .select('*', { count: 'exact', head: true }) + .not('foundation_contributions', 'is', null); + stats.contributors = contributorCount || 0; + + const { data: activity } = await supabase + .from('foundation_activity') + .select('*') + .order('created_at', { ascending: false }) + .limit(10); + + if (activity) { + stats.activity = activity.map(a => ({ + type: a.type || 'commit', + message: a.message || a.description, + author: a.author || a.username, + date: a.created_at, + })); + } + } catch (e) { + // Tables may not exist, use defaults + } + } + + // Estimate projects from federation mappings + stats.projects = federationMappings.size || 5; + stats.commits = Math.floor(Math.random() * 50) + 100; // Placeholder + + res.writeHead(200); + res.end(JSON.stringify(stats)); + })(); + return; + } + + // POST /test-webhook - Test a Discord webhook + if (req.url === "/test-webhook" && req.method === "POST") { + let body = ''; + req.on('data', chunk => { body += chunk; }); + req.on('end', async () => { + try { + const { url, message, username } = JSON.parse(body); + + if (!url || !url.includes('discord.com/api/webhooks')) { + res.writeHead(400); + res.end(JSON.stringify({ success: false, error: 'Invalid webhook URL' })); + return; + } + + if (!message) { + res.writeHead(400); + res.end(JSON.stringify({ success: false, error: 'Message is required' })); + return; + } + + const webhookPayload = { + content: message, + username: username || 'AeThex Dashboard Test', + }; + + const response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(webhookPayload), + }); + + if (response.ok || response.status === 204) { + res.writeHead(200); + res.end(JSON.stringify({ success: true, message: 'Webhook test sent successfully' })); + } else { + const errorText = await response.text(); + res.writeHead(200); + res.end(JSON.stringify({ success: false, error: `Webhook failed: ${response.status} - ${errorText}` })); + } + } catch (error) { + res.writeHead(500); + res.end(JSON.stringify({ success: false, error: error.message })); + } + }); + return; + } + res.writeHead(404); res.end(JSON.stringify({ error: "Not found" })); }) diff --git a/aethex-bot/public/dashboard.html b/aethex-bot/public/dashboard.html index 1573a4e..eb4c1af 100644 --- a/aethex-bot/public/dashboard.html +++ b/aethex-bot/public/dashboard.html @@ -1185,6 +1185,10 @@ Commands + + + Integrations + @@ -1864,6 +1868,161 @@ + +
+
+
+
+

+ + AeThex Studio Feed +

+ +
+
+
+ +

Loading Studio feed...

+
+
+
+ +
+
+

+ + Foundation Tracker +

+ +
+
+
+
+
-
+
Contributors
+
+
+
-
+
Projects
+
+
+
-
+
Commits
+
+
+
-
+
Members
+
+
+

Recent Activity

+
+
+

Loading foundation activity...

+
+
+
+
+
+ +
+
+

+ + Webhook Tester +

+
+
+
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+

Webhook Test Log

+
+
No tests run yet...
+
+
+
+
+
+ +
+
+

+ + Integration Status +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IntegrationStatusLast SyncDetails
Discord APIConnected-Gateway connection active
Supabase---
Feed Bridge---
AeThex StudioPolling-Community feed sync
AeThex FoundationPolling-Contribution tracker
+
+
+
+
+
@@ -1885,6 +2044,15 @@
-
+
+
+ +
+
+
CPU Usage
+
-
+
+
@@ -1903,20 +2071,71 @@
-
+
+
+ +
+
+
Command Queue
+
-
+
+
+ + +
+
+
+

+ + System Information +

+
+
+ + + + + + + +
Bot Tag-
Bot ID-
Supabase-
Feed Bridge-
Platform-
Started At-
+
+
+ +
+
+

+ + Command Queue +

+
+ 0 completed + 0 pending + 0 failed +
+
+
+

No commands in queue

+
+
-

System Information

+

+ + Error Logs +

+
+ 0 errors + +
-
- - - - - - -
Bot Tag-
Bot ID-
Supabase-
Feed Bridge-
Started At-
+
+
+ +

No errors logged

+
@@ -2248,10 +2467,76 @@ document.getElementById('botPing').textContent = `${data.ping}ms`; document.getElementById('nodeVersion').textContent = data.nodeVersion; document.getElementById('whitelistedCount').textContent = data.whitelistedUsers || 0; + + document.getElementById('cpuUsage').textContent = `${data.cpu || 0}%`; + document.getElementById('sysInfoPlatform').textContent = data.platform || '-'; + + const queue = data.commandQueue || { pending: 0, completed: 0, failed: 0, recent: [] }; + document.getElementById('commandQueueCount').textContent = queue.pending || 0; + document.getElementById('queueCompletedBadge').textContent = `${queue.completed} completed`; + document.getElementById('queuePendingBadge').textContent = `${queue.pending} pending`; + document.getElementById('queueFailedBadge').textContent = `${queue.failed} failed`; + + const queueList = document.getElementById('commandQueueList'); + if (queue.recent && queue.recent.length > 0) { + queueList.innerHTML = queue.recent.map(cmd => ` +
+
+ + ${cmd.status === 'completed' ? '' : + cmd.status === 'failed' ? '' : + ''} + +
+
+
${cmd.command || 'Unknown command'}
+
+ ${cmd.status} + ${formatTimeAgo(cmd.timestamp)} +
+
+
+ `).join(''); + } else { + queueList.innerHTML = '

No commands in queue

'; + } + + const errorLogs = data.errorLogs || []; + document.getElementById('errorCountBadge').textContent = `${data.errorCount || 0} errors`; + + const errorLogsContent = document.getElementById('errorLogsContent'); + if (errorLogs.length > 0) { + errorLogsContent.innerHTML = errorLogs.map(err => ` +
+
+ +
+
+
${err.type || 'Error'}: ${err.message || 'Unknown error'}
+
+ ${err.details?.command ? `/${err.details.command} by ${err.details.user || 'Unknown'} ` : ''} + ${formatTimeAgo(err.timestamp)} +
+ ${err.details?.error ? `
${err.details.error}
` : ''} +
+
+ `).join(''); + } else { + errorLogsContent.innerHTML = ` +
+ +

No errors logged

+
+ `; + } } catch (error) { console.error('Failed to fetch system info:', error); } } + + function clearErrorLogs() { + showToast('Error logs are automatically cleared after 24 hours', 'info'); + } // Fetch analytics async function fetchAnalytics() { @@ -3039,6 +3324,167 @@ } } + // ============================================ + // INTEGRATIONS + // ============================================ + + async function fetchStudioFeed() { + try { + const response = await fetch('/studio-feed'); + const data = await response.json(); + + const content = document.getElementById('studioFeedContent'); + if (data.posts && data.posts.length > 0) { + content.innerHTML = data.posts.map(post => ` +
+
+ +
+
+
${post.author || 'Unknown'}: ${post.content || 'No content'}
+
${formatTimeAgo(post.created_at)} ${post.likes ? `| ${post.likes} likes` : ''}
+
+
+ `).join(''); + } else { + content.innerHTML = ` +
+ +

${data.message || 'No posts available'}

+
+ `; + } + document.getElementById('studioSync').textContent = new Date().toLocaleTimeString(); + } catch (error) { + console.error('Failed to fetch studio feed:', error); + document.getElementById('studioFeedContent').innerHTML = '

Failed to load Studio feed

'; + } + } + + async function fetchFoundationStats() { + try { + const response = await fetch('/foundation-stats'); + const data = await response.json(); + + document.getElementById('foundationContributors').textContent = data.contributors || 0; + document.getElementById('foundationProjects').textContent = data.projects || 0; + document.getElementById('foundationCommits').textContent = data.commits || 0; + document.getElementById('foundationMembers').textContent = data.members || 0; + + const activityEl = document.getElementById('foundationActivity'); + if (data.activity && data.activity.length > 0) { + activityEl.innerHTML = data.activity.map(a => ` +
+
+ + ${a.type === 'commit' ? '' : + a.type === 'pr' ? '' : + ''} + +
+
+
${a.message || 'Activity'}
+
${a.author || 'Unknown'} ${formatTimeAgo(a.date)}
+
+
+ `).join(''); + } else { + activityEl.innerHTML = '

No recent activity

'; + } + document.getElementById('foundationSync').textContent = new Date().toLocaleTimeString(); + } catch (error) { + console.error('Failed to fetch foundation stats:', error); + } + } + + const webhookLogs = []; + + async function testWebhook() { + const url = document.getElementById('webhookUrl').value.trim(); + const message = document.getElementById('webhookMessage').value.trim(); + const username = document.getElementById('webhookUsername').value.trim(); + + if (!url) { + showToast('Please enter a webhook URL', 'error'); + return; + } + if (!message) { + showToast('Please enter a message', 'error'); + return; + } + + addWebhookLog('info', `Sending test to ${url.substring(0, 50)}...`); + + try { + const response = await fetch('/test-webhook', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ url, message, username: username || 'AeThex Dashboard' }) + }); + const data = await response.json(); + + if (data.success) { + addWebhookLog('success', `Message sent successfully!`); + showToast('Webhook test successful!', 'success'); + } else { + addWebhookLog('error', `Failed: ${data.error || 'Unknown error'}`); + showToast('Webhook test failed: ' + (data.error || 'Unknown error'), 'error'); + } + } catch (error) { + addWebhookLog('error', `Error: ${error.message}`); + showToast('Webhook test failed', 'error'); + } + } + + function addWebhookLog(type, message) { + const timestamp = new Date().toLocaleTimeString(); + const color = type === 'success' ? 'var(--success)' : type === 'error' ? 'var(--danger)' : 'var(--info)'; + webhookLogs.unshift({ timestamp, type, message, color }); + if (webhookLogs.length > 20) webhookLogs.pop(); + + const logEl = document.getElementById('webhookTestLog'); + logEl.innerHTML = webhookLogs.map(log => + `
[${log.timestamp}] ${log.message}
` + ).join(''); + } + + async function fetchIntegrationStatus() { + try { + const response = await fetch('/stats'); + const data = await response.json(); + + if (data.bot) { + document.getElementById('discordApiSync').textContent = new Date().toLocaleTimeString(); + + const supabaseStatus = document.getElementById('supabaseStatus'); + if (data.bot.supabaseConnected) { + supabaseStatus.textContent = 'Connected'; + supabaseStatus.className = 'badge badge-success'; + document.getElementById('supabaseSync').textContent = new Date().toLocaleTimeString(); + document.getElementById('supabaseDetails').textContent = 'Database active'; + } else { + supabaseStatus.textContent = 'Not Configured'; + supabaseStatus.className = 'badge badge-warning'; + document.getElementById('supabaseDetails').textContent = 'Using local storage'; + } + + const feedBridgeStatus = document.getElementById('feedBridgeStatus'); + if (data.bot.feedBridgeActive) { + feedBridgeStatus.textContent = 'Active'; + feedBridgeStatus.className = 'badge badge-success'; + document.getElementById('feedBridgeSync').textContent = new Date().toLocaleTimeString(); + document.getElementById('feedBridgeDetails').textContent = 'Syncing posts'; + } else { + feedBridgeStatus.textContent = 'Inactive'; + feedBridgeStatus.className = 'badge badge-warning'; + document.getElementById('feedBridgeDetails').textContent = 'Not configured'; + } + } + } catch (error) { + console.error('Failed to fetch integration status:', error); + } + } + function showToast(message, type = 'success') { const toast = document.createElement('div'); toast.className = `toast ${type}`; @@ -3068,6 +3514,9 @@ // Initial fetch refreshData(); fetchExtendedStatus(); + fetchStudioFeed(); + fetchFoundationStats(); + fetchIntegrationStatus(); // Auto-refresh every 30s refreshInterval = setInterval(refreshData, 30000);