diff --git a/services/arbiter-3.0/src/routes/admin/servers.js b/services/arbiter-3.0/src/routes/admin/servers.js index fb8cb7f..d28a2c6 100644 --- a/services/arbiter-3.0/src/routes/admin/servers.js +++ b/services/arbiter-3.0/src/routes/admin/servers.js @@ -4,11 +4,94 @@ const db = require('../../database'); const { getMinecraftServers } = require('../../panel/discovery'); const { readServerProperties, writeWhitelistFile } = require('../../panel/files'); const { reloadWhitelistCommand } = require('../../panel/commands'); +const { ChannelType } = require('discord.js'); // In-memory cache for RV low-bandwidth operations let serverCache = { data: null, lastFetch: 0 }; const CACHE_TTL = 60000; // 60 seconds +// Cache for Discord channels (refresh less frequently) +let discordChannelCache = { channels: null, lastFetch: 0 }; +const DISCORD_CACHE_TTL = 300000; // 5 minutes + +/** + * Get Discord channels from cache or fetch fresh + */ +async function getDiscordChannels(client) { + const now = Date.now(); + if (discordChannelCache.channels && (now - discordChannelCache.lastFetch < DISCORD_CACHE_TTL)) { + return discordChannelCache.channels; + } + + const guild = client.guilds.cache.get(process.env.GUILD_ID); + if (!guild) return []; + + const channels = guild.channels.cache.map(ch => ({ + id: ch.id, + name: ch.name, + type: ch.type, + parentId: ch.parentId + })); + + discordChannelCache = { channels, lastFetch: now }; + return channels; +} + +/** + * Check which Discord channels exist for a server + * Returns object with missing channels array + */ +function checkServerChannels(serverName, allChannels) { + // Normalize server name to expected channel format + const baseName = serverName + .toLowerCase() + .replace(/[^a-z0-9\s-]/g, '') // Remove special chars except spaces/hyphens + .replace(/\s+/g, '-') // Spaces to hyphens + .replace(/-+/g, '-') // Multiple hyphens to single + .trim(); + + const expectedChannels = [ + { name: `${baseName}-chat`, type: 'text', label: 'Chat' }, + { name: `${baseName}-in-game`, type: 'text', label: 'In-Game' }, + { name: `${baseName}-forum`, type: 'forum', label: 'Forum' }, + { name: serverName, type: 'voice', label: 'Voice' } // Voice uses display name + ]; + + const missing = []; + const found = []; + + for (const expected of expectedChannels) { + let exists = false; + + if (expected.type === 'voice') { + // Voice channels match by exact name (case-insensitive) + exists = allChannels.some(ch => + ch.type === ChannelType.GuildVoice && + ch.name.toLowerCase() === expected.name.toLowerCase() + ); + } else if (expected.type === 'forum') { + exists = allChannels.some(ch => + ch.type === ChannelType.GuildForum && + ch.name === expected.name + ); + } else { + // Text channels + exists = allChannels.some(ch => + ch.type === ChannelType.GuildText && + ch.name === expected.name + ); + } + + if (exists) { + found.push(expected.label); + } else { + missing.push(expected.label); + } + } + + return { missing, found, complete: missing.length === 0 }; +} + router.get('/', (req, res) => { res.render('admin/servers/index', { title: 'Server Matrix' }); }); @@ -38,10 +121,18 @@ router.get('/matrix', async (req, res) => { return acc; }, {}); - const enrichedServers = serversData.map(srv => ({ - ...srv, - log: logMap[srv.identifier] || { is_online: false, last_error: 'Never synced' } - })); + // Get Discord channels + const client = req.app.locals.client; + const discordChannels = await getDiscordChannels(client); + + const enrichedServers = serversData.map(srv => { + const channelStatus = checkServerChannels(srv.name, discordChannels); + return { + ...srv, + log: logMap[srv.identifier] || { is_online: false, last_error: 'Never synced' }, + discord: channelStatus + }; + }); // Group by Node Location const txServers = enrichedServers.filter(s => s.node === 'TX1'); diff --git a/services/arbiter-3.0/src/views/admin/servers/_server_card.ejs b/services/arbiter-3.0/src/views/admin/servers/_server_card.ejs index 09639c4..54403ed 100644 --- a/services/arbiter-3.0/src/views/admin/servers/_server_card.ejs +++ b/services/arbiter-3.0/src/views/admin/servers/_server_card.ejs @@ -1,6 +1,7 @@ <% const isOnline = server.log.is_online; const hasError = !!server.log.last_error; + const discordComplete = server.discord?.complete; let borderClass = 'border-gray-200 dark:border-gray-700'; // Default / Offline if (isOnline && !hasError) borderClass = 'border-green-500 shadow-[0_0_10px_rgba(34,197,94,0.2)]'; @@ -37,6 +38,20 @@ + +