fix(dashboard): make assistant-health saves resilient to partial refresh failures

This commit is contained in:
William Valentin
2026-02-18 18:14:22 -08:00
parent 3647198295
commit 45261a090a
2 changed files with 62 additions and 17 deletions
+51 -17
View File
@@ -491,13 +491,18 @@ function updateContextHealth(contextData) {
}
async function applyAssistantPatch(patches, statusEl) {
if (!_dashboardClient) {return;}
if (!_dashboardClient) {
console.warn('[Flynn] applyAssistantPatch: no client');
return;
}
console.log('[Flynn] applyAssistantPatch:', JSON.stringify(patches));
if (statusEl) {
statusEl.textContent = 'Saving...';
statusEl.className = 'text-sm text-zinc-500';
}
try {
const result = await _dashboardClient.call('config.patch', { patches });
console.log('[Flynn] config.patch result:', JSON.stringify(result));
const rejected = result?.rejected ?? [];
const persistError = result?.persistError;
const applied = result?.applied ?? [];
@@ -512,13 +517,14 @@ async function applyAssistantPatch(patches, statusEl) {
statusEl.className = 'text-sm text-red-500';
} else if (!persisted) {
statusEl.textContent = `Runtime saved (${applied.length} updated)`;
statusEl.className = 'text-sm text-zinc-500';
statusEl.className = 'text-sm text-amber-500';
} else {
statusEl.textContent = `Saved (${applied.length} updated)`;
statusEl.textContent = `Saved & persisted (${applied.length} updated)`;
statusEl.className = 'text-sm text-green-500';
}
}
} catch (error) {
console.error('[Flynn] config.patch error:', error);
if (statusEl) {
statusEl.textContent = `Save error: ${error instanceof Error ? error.message : String(error)}`;
statusEl.className = 'text-sm text-red-500';
@@ -758,7 +764,16 @@ function updateAssistantHealth(configData) {
updateServices(refreshed.services);
updateSessionAnalytics(refreshed.sessionAnalytics);
updateContextHealth(refreshed.contextUsage);
// Capture status message before re-render destroys the DOM element
const savedText = statusEl?.textContent ?? '';
const savedClass = statusEl?.className ?? '';
updateAssistantHealth(refreshed.config);
// Restore status message in the new DOM
const newStatusEl = document.getElementById('ops-assistant-status');
if (newStatusEl && savedText) {
newStatusEl.textContent = savedText;
newStatusEl.className = savedClass;
}
}
});
});
@@ -839,18 +854,23 @@ async function fetchFast(client) {
}
async function fetchSlow(client) {
try {
const [health, services, sessionAnalytics, contextUsage, config] = await Promise.all([
client.call('system.health'),
client.call('system.services'),
client.call('system.sessionAnalytics', { days: 14, topLimit: 5 }),
client.call('system.contextUsage'),
client.call('config.get'),
]);
return { health, services, sessionAnalytics, contextUsage, config };
} catch {
return null;
}
const [health, services, sessionAnalytics, contextUsage, config] = await Promise.allSettled([
client.call('system.health'),
client.call('system.services'),
client.call('system.sessionAnalytics', { days: 14, topLimit: 5 }),
client.call('system.contextUsage'),
client.call('config.get'),
]);
const unwrap = (result) => (result.status === 'fulfilled' ? result.value : null);
return {
health: unwrap(health),
services: unwrap(services),
sessionAnalytics: unwrap(sessionAnalytics),
contextUsage: unwrap(contextUsage),
config: unwrap(config),
};
}
// ── Main load function ──────────────────────────────────────────
@@ -877,10 +897,16 @@ async function loadDashboard(el, client) {
updateEvents(fast.eventsData);
updateActiveRequests(fast.requestsData);
}
if (slow) {
if (slow?.services) {
updateServices(slow.services);
}
if (slow?.sessionAnalytics) {
updateSessionAnalytics(slow.sessionAnalytics);
}
if (slow?.contextUsage) {
updateContextHealth(slow.contextUsage);
}
if (slow?.config) {
updateAssistantHealth(slow.config);
}
@@ -899,12 +925,20 @@ async function loadDashboard(el, client) {
// Slow refresh: 10 seconds for health, services
_slowTimer = setInterval(async () => {
const data = await fetchSlow(client);
if (data) {
if (data.health) {
_lastHealth = data.health;
updateCounters(_lastMetrics, data.health);
}
if (data.services) {
updateServices(data.services);
}
if (data.sessionAnalytics) {
updateSessionAnalytics(data.sessionAnalytics);
}
if (data.contextUsage) {
updateContextHealth(data.contextUsage);
}
if (data.config) {
updateAssistantHealth(data.config);
}
}, 10000);