From b96ab1fb248dd91aee2c9624e72944546b308d54 Mon Sep 17 00:00:00 2001 From: Claude Chronicler-70 Date: Wed, 8 Apr 2026 15:30:22 +0000 Subject: [PATCH] feat(arbiter): Add Discord dashboard to Trinity Console - New sidebar entry for Discord - Full server structure visualization - Channel tree with expandable categories - Role hierarchy with color badges - Health checks (orphan channels, empty roles, bot roles) - Search/filter across channels and roles - Click channel to see permission overwrites - Click role to see explicit channel access - Responsive design with modal details view Chronicler: #70 --- .../src/routes/admin/discord-audit.js | 118 +++++ .../src/views/admin/discord/index.ejs | 467 ++++++++++++++++++ services/arbiter-3.0/src/views/layout.ejs | 3 + 3 files changed, 588 insertions(+) create mode 100644 services/arbiter-3.0/src/views/admin/discord/index.ejs diff --git a/services/arbiter-3.0/src/routes/admin/discord-audit.js b/services/arbiter-3.0/src/routes/admin/discord-audit.js index f767313..45a5760 100644 --- a/services/arbiter-3.0/src/routes/admin/discord-audit.js +++ b/services/arbiter-3.0/src/routes/admin/discord-audit.js @@ -9,6 +9,124 @@ const express = require('express'); const router = express.Router(); +/** + * GET /admin/discord + * Main Discord audit dashboard + */ +router.get('/', async (req, res) => { + try { + const client = req.app.locals.client; + const guildId = process.env.GUILD_ID; + + if (!client || !client.isReady()) { + return res.render('admin/discord/index', { + title: 'Discord', + error: 'Discord client not ready', + data: null + }); + } + + const guild = client.guilds.cache.get(guildId); + if (!guild) { + return res.render('admin/discord/index', { + title: 'Discord', + error: 'Guild not found', + data: null + }); + } + + // Fetch fresh data + await guild.channels.fetch(); + await guild.roles.fetch(); + + // Build channel structure + const channels = guild.channels.cache.map(ch => ({ + id: ch.id, + name: ch.name, + type: ch.type, + typeName: getChannelTypeName(ch.type), + parentId: ch.parentId, + position: ch.position, + nsfw: ch.nsfw || false, + topic: ch.topic || null, + permissionOverwrites: ch.permissionOverwrites?.cache.map(p => ({ + id: p.id, + type: p.type, + allow: p.allow.bitfield.toString(), + deny: p.deny.bitfield.toString() + })) || [] + })).sort((a, b) => a.position - b.position); + + // Build role structure with permission overwrites lookup + const roles = guild.roles.cache.map(r => ({ + id: r.id, + name: r.name, + color: r.hexColor, + position: r.position, + permissions: r.permissions.bitfield.toString(), + mentionable: r.mentionable, + managed: r.managed, + memberCount: r.members.size + })).sort((a, b) => b.position - a.position); + + // Categories with their children + const categories = channels + .filter(ch => ch.type === 4) + .map(cat => ({ + ...cat, + children: channels.filter(ch => ch.parentId === cat.id) + })); + + // Orphan channels + const orphanChannels = channels.filter(ch => !ch.parentId && ch.type !== 4); + + // Server info + const serverInfo = { + id: guild.id, + name: guild.name, + memberCount: guild.memberCount, + ownerId: guild.ownerId, + createdAt: guild.createdAt, + icon: guild.iconURL(), + features: guild.features + }; + + // Health checks + const healthChecks = { + orphanChannels: orphanChannels.length, + emptyRoles: roles.filter(r => r.memberCount === 0 && !r.managed && r.name !== '@everyone').length, + botRoles: roles.filter(r => r.managed).length + }; + + res.render('admin/discord/index', { + title: 'Discord', + error: null, + data: { + server: serverInfo, + categories, + orphanChannels, + allChannels: channels, + roles, + healthChecks, + summary: { + totalChannels: channels.length, + totalRoles: roles.length, + categoryCount: categories.length, + orphanCount: orphanChannels.length + } + } + }); + + } catch (error) { + console.error('Discord dashboard error:', error); + res.render('admin/discord/index', { + title: 'Discord', + error: error.message, + data: null + }); + } +}); + /** * GET /admin/discord/audit * Full Discord server audit - channels, roles, members diff --git a/services/arbiter-3.0/src/views/admin/discord/index.ejs b/services/arbiter-3.0/src/views/admin/discord/index.ejs new file mode 100644 index 0000000..6da77ab --- /dev/null +++ b/services/arbiter-3.0/src/views/admin/discord/index.ejs @@ -0,0 +1,467 @@ +<% if (error) { %> +
+
⚠️
+
<%= error %>
+
+<% } else if (data) { %> + + +
+
+
+
+ +
+
+ + + +
+
+
+
+ + +
+
+
<%= data.summary.totalChannels %>
+
Channels
+
+
+
<%= data.summary.totalRoles %>
+
Roles
+
+
+
<%= data.summary.categoryCount %>
+
Categories
+
+
+
<%= data.server.memberCount %>
+
Members
+
+
+ <% if (data.healthChecks.orphanChannels === 0) { %> +
+
Healthy
+ <% } else { %> +
<%= data.healthChecks.orphanChannels %>
+
Orphans
+ <% } %> +
+
+ + +
+ + +
+
+
+ +

<%= data.server.name %>

+
+
+ <% data.categories.forEach((cat, catIndex) => { %> +
+
+ + 📁 + <%= cat.name %> + <%= cat.children.length %> +
+ +
+ <% }); %> + + <% if (data.orphanChannels.length > 0) { %> +
+
⚠️ Orphan Channels (no category)
+ <% data.orphanChannels.forEach(ch => { %> +
+ # + <%= ch.name %> +
+ <% }); %> +
+ <% } %> +
+
+
+ + + + + + +
+ + + + + + +<% } %> diff --git a/services/arbiter-3.0/src/views/layout.ejs b/services/arbiter-3.0/src/views/layout.ejs index 4d57322..97843e4 100644 --- a/services/arbiter-3.0/src/views/layout.ejs +++ b/services/arbiter-3.0/src/views/layout.ejs @@ -95,6 +95,9 @@ ⏰ Restart Scheduler + + 💬 Discord +