++ fix grafico
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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">
|
||||
<h2 class="h4 mb-0">Attivita Recenti</h2>
|
||||
<span class="pill">UTC</span>
|
||||
<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>
|
||||
<div class="table-responsive">
|
||||
<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">▶ Sincronizzazione in corso…</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,"'").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}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user