++ fix grafico

This commit is contained in:
2026-03-18 17:22:33 +01:00
parent 405b8d24bd
commit 2a249e16d4
2 changed files with 125 additions and 15 deletions

View File

@@ -124,6 +124,37 @@ body {
padding: 0.55rem 0.7rem;
min-height: 42px;
word-break: break-word;
display: flex;
flex-wrap: wrap;
gap: 0.4rem 0.6rem;
align-items: center;
}
.summary-kv {
display: inline-flex;
align-items: center;
gap: 0.25rem;
background: rgba(255, 255, 255, 0.06);
border-radius: 6px;
padding: 0.15rem 0.45rem;
font-size: 0.78rem;
}
.summary-key {
color: #8ecae6;
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.4px;
}
.sync-in-progress {
color: #ffe2a3;
animation: pulse-text 1.4s ease-in-out infinite alternate;
}
@keyframes pulse-text {
from { opacity: 0.6; }
to { opacity: 1; }
}
.status-chip {
@@ -182,12 +213,57 @@ body {
.table thead {
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
z-index: 1;
background: #0b1e2d;
}
.table tbody tr {
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.log-scroll {
max-height: 420px;
overflow-y: auto;
overflow-x: auto;
border-radius: 10px;
scrollbar-width: thin;
scrollbar-color: rgba(255,255,255,0.2) transparent;
}
.log-scroll::-webkit-scrollbar {
width: 6px;
}
.log-scroll::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.2);
border-radius: 4px;
}
@keyframes flash-row {
0% { background: rgba(255, 183, 3, 0.25); }
100% { background: transparent; }
}
.row-new {
animation: flash-row 1.8s ease forwards;
}
.live-dot {
width: 9px;
height: 9px;
border-radius: 50%;
background: #2ec4b6;
flex-shrink: 0;
animation: dot-pulse 1.2s ease-in-out infinite;
}
@keyframes dot-pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.4; transform: scale(0.75); }
}
@media (max-width: 768px) {
.hero-panel,
.log-panel {

View File

@@ -35,16 +35,22 @@
<section class="log-panel mt-4 p-3 p-md-4">
<div class="d-flex align-items-center justify-content-between mb-3">
<div class="d-flex align-items-center gap-2">
<h2 class="h4 mb-0">Attivita Recenti</h2>
<span id="liveIndicator" class="live-dot d-none"></span>
</div>
<div class="d-flex align-items-center gap-2">
<span id="logCount" class="pill">0 eventi</span>
<span class="pill">UTC</span>
</div>
<div class="table-responsive">
</div>
<div class="log-scroll">
<table class="table table-dark table-borderless align-middle mb-0" id="logsTable">
<thead>
<tr>
<th>Ora</th>
<th>Livello</th>
<th>Job</th>
<th style="width:160px">Ora</th>
<th style="width:90px">Livello</th>
<th style="width:130px">Job</th>
<th>Messaggio</th>
<th>Dettagli</th>
</tr>
@@ -69,15 +75,20 @@
return new Date(value).toLocaleString();
}
function fmtSummary(summary) {
if (!summary || Object.keys(summary).length === 0) {
return 'Nessuna esecuzione';
function fmtSummary(summary, running) {
if (running) {
return '<span class="sync-in-progress">&#9654; Sincronizzazione in corso&hellip;</span>';
}
if (!summary || Object.keys(summary).length === 0) {
return '<span class="text-secondary">Nessuna esecuzione ancora</span>';
}
const labels = { downloaded: 'scaricati', kept: 'invariati', deleted: 'eliminati', errors: 'errori', trigger: 'avviato da', error: 'errore' };
const parts = [];
for (const [key, val] of Object.entries(summary)) {
parts.push(`${key}: ${val}`);
const label = labels[key] || key;
parts.push(`<span class="summary-kv"><span class="summary-key">${label}</span> ${val}</span>`);
}
return parts.join(' | ');
return parts.join('');
}
function statusClass(status, running) {
@@ -112,7 +123,7 @@
<div>Ultima fine: ${fmtDate(job.last_end)}</div>
</div>
<div class="mt-3 summary-box mono small">${fmtSummary(job.last_summary)}</div>
<div class="mt-3 summary-box mono small">${fmtSummary(job.last_summary, job.running)}</div>
<div class="mt-3 d-flex gap-2">
<button class="btn btn-outline-light btn-sm" data-job="${job.name}">Avvia ora</button>
@@ -135,21 +146,37 @@
});
}
let prevLogTime = null;
function renderLogs(logs) {
const tbody = document.querySelector('#logsTable tbody');
tbody.innerHTML = '';
const entries = logs.slice(0, 200);
const newTopTime = entries.length > 0 ? entries[0].time : null;
const hasNew = newTopTime && newTopTime !== prevLogTime;
prevLogTime = newTopTime;
logs.slice(0, 120).forEach((entry) => {
tbody.innerHTML = '';
entries.forEach((entry, i) => {
const tr = document.createElement('tr');
if (hasNew && i < 3) tr.classList.add('row-new');
tr.innerHTML = `
<td class="mono small">${fmtDate(entry.time)}</td>
<td><span class="pill level-${entry.level.toLowerCase()}">${entry.level}</span></td>
<td class="mono small">${entry.job || '-'}</td>
<td>${entry.message}</td>
<td class="mono small">${JSON.stringify(entry.details || {})}</td>
<td class="mono small text-truncate" style="max-width:260px" title="${encodeURIComponent(JSON.stringify(entry.details || {})).replace(/'/g,"&apos;").replace(/%/g,'%')}">` +
JSON.stringify(entry.details || {}) +
`</td>
`;
tbody.appendChild(tr);
});
document.getElementById('logCount').textContent = entries.length + ' eventi';
if (hasNew) {
const scroll = document.querySelector('.log-scroll');
scroll.scrollTop = 0;
}
}
async function reload() {
@@ -158,6 +185,13 @@
document.getElementById('serverTime').textContent = `Server UTC: ${fmtDate(data.server_time)}`;
renderJobs(data.jobs || []);
renderLogs(data.logs || []);
const anyRunning = (data.jobs || []).some(j => j.running);
const dot = document.getElementById('liveIndicator');
if (anyRunning) {
dot.classList.remove('d-none');
} else {
dot.classList.add('d-none');
}
} catch (err) {
document.getElementById('serverTime').textContent = `Errore: ${err.message}`;
}