🐛 fix: dashboard bugs — graceful degradation, dbPath config, auth expiry detection

- 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
This commit is contained in:
TARS 2026-04-25 19:44:43 -07:00
parent 350483c26c
commit 2f60e99efc
3 changed files with 40 additions and 23 deletions

View file

@ -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": "📦"
}
]
}

View file

@ -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 — <a href="/dashboard.html" style="color:#3b82f6">reload page</a>';
}
console.error(e);
} finally {
btn.disabled = false;

View file

@ -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) {