Add Discord channel status check to server cards
- Checks for 4 channels per server: chat, in-game, forum, voice - Shows 'All 4 channels configured' or lists missing channels - Caches Discord channel data for 5 minutes to reduce API calls
This commit is contained in:
@@ -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');
|
||||
|
||||
@@ -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 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Discord Channel Status -->
|
||||
<div class="mb-4">
|
||||
<span class="text-gray-500 dark:text-gray-400 block text-xs mb-1">Discord Channels</span>
|
||||
<% if (discordComplete) { %>
|
||||
<span class="text-green-600 dark:text-green-400 text-sm font-medium">✅ All 4 channels configured</span>
|
||||
<% } else if (server.discord?.missing?.length > 0) { %>
|
||||
<div class="bg-yellow-50 dark:bg-yellow-900/20 text-yellow-700 dark:text-yellow-400 p-2 rounded text-xs">
|
||||
<strong>Missing:</strong> <%= server.discord.missing.join(', ') %>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<span class="text-gray-500 dark:text-gray-400 text-sm">Unable to check</span>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<% if (hasError) { %>
|
||||
<div class="bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 p-2 rounded text-xs mb-4 break-words">
|
||||
<strong>Error:</strong> <%= server.log.last_error %>
|
||||
|
||||
Reference in New Issue
Block a user