feat: dashboard real-time con aggiornamento incrementale
- Buffer locale samples[] per aggiornamenti real-time - Poll /api/status ogni 10 secondi - Aggiunge nuovi campioni senza ricaricare history - Aggiunge AGENTS.md per istruzioni agenti OpenCode - Aggiunge team agenti in .opencode/agents/
This commit is contained in:
@@ -288,6 +288,11 @@ def _build_dashboard_html() -> str:
|
||||
const badge = document.getElementById('statusBadge');
|
||||
const meta = document.getElementById('meta');
|
||||
const ctx = document.getElementById('latencyChart').getContext('2d');
|
||||
|
||||
// Buffer locale per campioni - mantiene solo i dati nel grafico
|
||||
let samples = [];
|
||||
const MAX_VISIBLE_SAMPLES = 100; // Limite visualizzazione per performance
|
||||
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: { labels: [], datasets: [
|
||||
@@ -303,37 +308,74 @@ def _build_dashboard_html() -> str:
|
||||
}
|
||||
});
|
||||
|
||||
async function load() {
|
||||
const [statusRes, historyRes] = await Promise.all([
|
||||
fetch('/api/status'),
|
||||
fetch('/api/history?hours=48')
|
||||
]);
|
||||
const status = await statusRes.json();
|
||||
async function loadHistory() {
|
||||
// Carica history iniziale una sola volta
|
||||
const historyRes = await fetch('/api/history?hours=48');
|
||||
const history = await historyRes.json();
|
||||
|
||||
if (status.success === true) {
|
||||
badge.textContent = 'UP';
|
||||
badge.className = 'badge ok';
|
||||
} else if (status.success === false) {
|
||||
badge.textContent = 'DOWN';
|
||||
badge.className = 'badge ko';
|
||||
} else {
|
||||
badge.textContent = 'N/A';
|
||||
badge.className = 'badge';
|
||||
}
|
||||
|
||||
meta.textContent = `Campioni 48h: ${history.count} | Ultimo campione: ${status.timestamp ? new Date(status.timestamp * 1000).toLocaleString() : 'n/a'}`;
|
||||
|
||||
chart.data.labels = history.samples.map(s => new Date(s.timestamp * 1000).toLocaleTimeString());
|
||||
chart.data.datasets[0].data = history.samples.map(s => s.latency_ms);
|
||||
chart.data.datasets[1].data = history.samples.map(s => s.success ? 0 : 1);
|
||||
chart.update();
|
||||
samples = history.samples || [];
|
||||
updateChart();
|
||||
}
|
||||
|
||||
load().catch((err) => {
|
||||
meta.textContent = `Errore caricamento dashboard: ${err}`;
|
||||
function updateChart() {
|
||||
// Limita i campioni visibili per performance
|
||||
const visibleSamples = samples.slice(-MAX_VISIBLE_SAMPLES);
|
||||
|
||||
chart.data.labels = visibleSamples.map(s => {
|
||||
const d = new Date(s.timestamp * 1000);
|
||||
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
});
|
||||
chart.data.datasets[0].data = visibleSamples.map(s => s.latency_ms);
|
||||
chart.data.datasets[1].data = visibleSamples.map(s => s.success ? 0 : 1);
|
||||
chart.update('none'); // update senza animazione per performance
|
||||
}
|
||||
|
||||
async function pollStatus() {
|
||||
try {
|
||||
const statusRes = await fetch('/api/status');
|
||||
const status = await statusRes.json();
|
||||
|
||||
// Aggiorna badge
|
||||
if (status.success === true) {
|
||||
badge.textContent = 'UP';
|
||||
badge.className = 'badge ok';
|
||||
} else if (status.success === false) {
|
||||
badge.textContent = 'DOWN';
|
||||
badge.className = 'badge ko';
|
||||
} else {
|
||||
badge.textContent = 'N/A';
|
||||
badge.className = 'badge';
|
||||
}
|
||||
|
||||
// Aggiorna meta con timestamp corrente
|
||||
meta.textContent = 'Ultimo aggiornamento: ' + new Date().toLocaleTimeString() + ' | Campioni: ' + samples.length;
|
||||
|
||||
// Aggiunge nuovo sample se non gia presente
|
||||
if (status.timestamp && !samples.find(s => s.timestamp === status.timestamp)) {
|
||||
samples.push({
|
||||
timestamp: status.timestamp,
|
||||
success: status.success,
|
||||
latency_ms: status.latency_ms,
|
||||
error_message: status.error_message
|
||||
});
|
||||
|
||||
// Rimuove campioni piu vecchi di 48h
|
||||
const cutoff = Math.floor(Date.now() / 1000) - (48 * 3600);
|
||||
samples = samples.filter(s => s.timestamp >= cutoff);
|
||||
|
||||
updateChart();
|
||||
}
|
||||
} catch (err) {
|
||||
meta.textContent = 'Errore: ' + err.message;
|
||||
}
|
||||
}
|
||||
|
||||
// Inizializzazione
|
||||
loadHistory().catch((err) => {
|
||||
meta.textContent = 'Errore caricamento history: ' + err;
|
||||
});
|
||||
setInterval(() => load().catch(() => {}), 30000);
|
||||
|
||||
// Poll ogni 10 secondi per aggiornamenti real-time
|
||||
setInterval(pollStatus, 10000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user