From 2f60e99efc7d3599f596594f152d5e554333255e Mon Sep 17 00:00:00 2001 From: TARS Date: Sat, 25 Apr 2026 19:44:43 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20dashboard=20bugs=20?= =?UTF-8?q?=E2=80=94=20graceful=20degradation,=20dbPath=20config,=20auth?= =?UTF-8?q?=20expiry=20detection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fetchWithTimeout now rejects non-2xx HTTP status codes - normalizeStats takes system object, reads dbPath from config - Inventory special-cased (no longer syncs GitHub) - dashboard.html detects HTML/auth redirect and shows reload link - dashboard-systems.json: add dbPath for all UseCaseGen apps --- data/dashboard-systems.json | 19 +++++++++++-------- public/dashboard.html | 14 +++++++++++++- server.js | 30 ++++++++++++++++-------------- 3 files changed, 40 insertions(+), 23 deletions(-) diff --git a/data/dashboard-systems.json b/data/dashboard-systems.json index 06ee869..08aae01 100644 --- a/data/dashboard-systems.json +++ b/data/dashboard-systems.json @@ -8,7 +8,7 @@ "apiBase": "http://127.0.0.1:3030", "statsEndpoint": "/api/dashboard/internal-stats", "authType": "self", - "icon": "\ud83d\udd10" + "icon": "🔐" }, { "id": "model-service", @@ -18,7 +18,7 @@ "apiBase": "http://127.0.0.1:3093", "statsEndpoint": "/health", "authType": "oauth2", - "icon": "\ud83e\udd16" + "icon": "🤖" }, { "id": "inventory", @@ -28,7 +28,7 @@ "apiBase": "http://127.0.0.1:3025", "statsEndpoint": "/api/github", "authType": "oauth2", - "icon": "\ud83d\udccb" + "icon": "📋" }, { "id": "incorta-dashboard", @@ -38,7 +38,7 @@ "apiBase": "http://127.0.0.1:3013", "statsEndpoint": "/health", "authType": "oauth2", - "icon": "\ud83d\udcca" + "icon": "📊" }, { "id": "eco-usecasegen", @@ -48,7 +48,8 @@ "apiBase": "http://127.0.0.1:3090", "statsEndpoint": "/api/admin/usage", "authType": "standalone", - "icon": "\ud83c\udf3f" + "dbPath": "/var/www/eco-usecasegen/data/users.db", + "icon": "🌿" }, { "id": "ggl-usecasegen", @@ -58,7 +59,8 @@ "apiBase": "http://127.0.0.1:3095", "statsEndpoint": "/api/admin/usage", "authType": "standalone", - "icon": "\ud83d\udd0d" + "dbPath": "/var/www/ggl-usecasegen/data/users.db", + "icon": "🔍" }, { "id": "fdx-usecasegen", @@ -68,7 +70,8 @@ "apiBase": "http://127.0.0.1:3089", "statsEndpoint": "/health", "authType": "standalone", - "icon": "\ud83d\udce6" + "dbPath": "/var/www/fedex-cohort-app/data/users.db", + "icon": "📦" } ] -} \ No newline at end of file +} diff --git a/public/dashboard.html b/public/dashboard.html index 9bab429..089c1f9 100644 --- a/public/dashboard.html +++ b/public/dashboard.html @@ -103,6 +103,11 @@ render(cachedData); } else { const res = await fetch('/api/dashboard/stats'); + const contentType = res.headers.get('content-type') || ''; + // Detect auth redirect (HTML response from oauth2-proxy) + if (contentType.includes('text/html')) { + throw new Error('AUTH_EXPIRED'); + } if (!res.ok) throw new Error('Failed to load stats: ' + res.status); const data = await res.json(); cachedData = data; @@ -110,7 +115,14 @@ render(data); } } catch (e) { - document.getElementById('last-updated').textContent = 'Error: ' + e.message; + const msg = e.message === 'AUTH_EXPIRED' + ? 'Session expired — refresh page to re-authenticate' + : 'Error: ' + e.message; + document.getElementById('last-updated').textContent = msg; + if (e.message === 'AUTH_EXPIRED') { + document.getElementById('last-updated').innerHTML = + 'Session expired — reload page'; + } console.error(e); } finally { btn.disabled = false; diff --git a/server.js b/server.js index 6e00817..db0292c 100644 --- a/server.js +++ b/server.js @@ -777,6 +777,10 @@ function saveDashboardSystems(systems) { async function fetchWithTimeout(url, timeoutMs = 500) { return new Promise((resolve, reject) => { const req = http.get(url, { timeout: timeoutMs }, (res) => { + if (res.statusCode < 200 || res.statusCode >= 300) { + reject(new Error(`HTTP ${res.statusCode}`)); + return; + } let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { @@ -793,7 +797,8 @@ async function fetchWithTimeout(url, timeoutMs = 500) { }); } -function normalizeStats(systemId, raw) { +function normalizeStats(system, raw) { + const systemId = system.id; if (systemId === 'access-manager') { const users = loadUsers(); return { @@ -808,21 +813,18 @@ function normalizeStats(systemId, raw) { status: raw.status }; } + if (systemId === 'inventory') { + // Inventory no longer syncs GitHub repos; show online with no metrics + return {}; + } if (systemId.includes('usecasegen')) { // Query actual DB for user count (session counters reset on restart) let dbUsers = 0; try { - const Database = require('better-sqlite3'); - if (systemId === 'eco-usecasegen') { - const db = new Database('/var/www/eco-usecasegen/data/users.db', { readonly: true }); - dbUsers = db.prepare('SELECT COUNT(*) as count FROM users').get()?.count || 0; - db.close(); - } else if (systemId === 'ggl-usecasegen') { - const db = new Database('/var/www/ggl-usecasegen/data/users.db', { readonly: true }); - dbUsers = db.prepare('SELECT COUNT(*) as count FROM users').get()?.count || 0; - db.close(); - } else if (systemId === 'fdx-usecasegen') { - const db = new Database('/var/www/fedex-cohort-app/data/users.db', { readonly: true }); + const dbPath = system.dbPath; + if (dbPath) { + const Database = require('better-sqlite3'); + const db = new Database(dbPath, { readonly: true }); dbUsers = db.prepare('SELECT COUNT(*) as count FROM users').get()?.count || 0; db.close(); } @@ -846,11 +848,11 @@ app.get('/api/dashboard/stats', requireAdmin, async (req, res) => { let stats; if (sys.authType === 'self') { // Local query - stats = normalizeStats(sys.id, {}); + stats = normalizeStats(sys, {}); } else { const url = sys.apiBase + sys.statsEndpoint; const raw = await fetchWithTimeout(url, 1000); - stats = normalizeStats(sys.id, raw); + stats = normalizeStats(sys, raw); } return { ...sys, status: 'ok', stats, error: null }; } catch (e) {