feat: role-aware presence bar, WebSocket logging fixes

- findAdminByKey returns role from admins.json (defaults to 'admin')
- JWT includes config-defined role instead of hardcoded 'admin'
- PresenceBar split into "who's here?" (page admins) and "connected"
  (bot/utility services with icon+color badges)
- Bot/utility roles appear in presence on all pages when connected
- usePresence hook uses refs to avoid WS reconnect on navigation
- WS auth log prints admin name instead of generic 'admin'
- WS connection log reads X-Forwarded-For for real client IP
- AuthContext stores adminRole from login response
- Uncomment admins.json Docker volume mount, add SELinux :z flags

Made-with: Cursor
This commit is contained in:
cottongin
2026-04-05 04:27:07 -04:00
parent 52e9a7af42
commit b2bb2989e9
9 changed files with 207 additions and 53 deletions

View File

@@ -26,13 +26,17 @@ class WebSocketManager {
* Handle new WebSocket connection
*/
handleConnection(ws, req) {
console.log('[WebSocket] New connection from', req.socket.remoteAddress);
const clientIp = req.headers['x-forwarded-for']?.split(',')[0]?.trim()
|| req.headers['x-real-ip']
|| req.socket.remoteAddress;
console.log('[WebSocket] New connection from', clientIp);
// Initialize client info
const clientInfo = {
authenticated: false,
userId: null,
adminName: null,
role: null,
currentPage: null,
subscribedSessions: new Set(),
lastPing: Date.now()
@@ -130,6 +134,7 @@ class WebSocketManager {
clientInfo.authenticated = true;
clientInfo.userId = decoded.role;
clientInfo.adminName = decoded.name || null;
clientInfo.role = decoded.role || 'admin';
if (!decoded.name) {
this.sendError(ws, 'Token missing admin identity, please re-login', 'auth_error');
@@ -141,7 +146,8 @@ class WebSocketManager {
message: 'Authenticated successfully'
});
console.log('[WebSocket] Client authenticated:', clientInfo.userId);
console.log('[WebSocket] Client authenticated:', clientInfo.adminName);
this.broadcastPresence();
}
} catch (err) {
console.error('[WebSocket] Authentication failed:', err.message);
@@ -299,7 +305,7 @@ class WebSocketManager {
});
this.clients.delete(ws);
console.log('[WebSocket] Client disconnected and cleaned up');
console.log('[WebSocket] Client disconnected:', clientInfo.adminName || 'unauthenticated');
this.broadcastPresence();
}
}
@@ -307,8 +313,12 @@ class WebSocketManager {
broadcastPresence() {
const admins = [];
this.clients.forEach((info) => {
if (info.authenticated && info.adminName && info.currentPage) {
admins.push({ name: info.adminName, page: info.currentPage });
if (!info.authenticated || !info.adminName) return;
const role = info.role || 'admin';
if (role === 'bot' || role === 'utility') {
admins.push({ name: info.adminName, role, page: null });
} else if (info.currentPage) {
admins.push({ name: info.adminName, role, page: info.currentPage });
}
});