++ fix grafico
This commit is contained in:
@@ -124,6 +124,37 @@ body {
|
|||||||
padding: 0.55rem 0.7rem;
|
padding: 0.55rem 0.7rem;
|
||||||
min-height: 42px;
|
min-height: 42px;
|
||||||
word-break: break-word;
|
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 {
|
.status-chip {
|
||||||
@@ -182,12 +213,57 @@ body {
|
|||||||
|
|
||||||
.table thead {
|
.table thead {
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
background: #0b1e2d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table tbody tr {
|
.table tbody tr {
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
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) {
|
@media (max-width: 768px) {
|
||||||
.hero-panel,
|
.hero-panel,
|
||||||
.log-panel {
|
.log-panel {
|
||||||
|
|||||||
@@ -35,16 +35,22 @@
|
|||||||
|
|
||||||
<section class="log-panel mt-4 p-3 p-md-4">
|
<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 justify-content-between mb-3">
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
<h2 class="h4 mb-0">Attivita Recenti</h2>
|
<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>
|
<span class="pill">UTC</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-responsive">
|
</div>
|
||||||
|
<div class="log-scroll">
|
||||||
<table class="table table-dark table-borderless align-middle mb-0" id="logsTable">
|
<table class="table table-dark table-borderless align-middle mb-0" id="logsTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Ora</th>
|
<th style="width:160px">Ora</th>
|
||||||
<th>Livello</th>
|
<th style="width:90px">Livello</th>
|
||||||
<th>Job</th>
|
<th style="width:130px">Job</th>
|
||||||
<th>Messaggio</th>
|
<th>Messaggio</th>
|
||||||
<th>Dettagli</th>
|
<th>Dettagli</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -69,15 +75,20 @@
|
|||||||
return new Date(value).toLocaleString();
|
return new Date(value).toLocaleString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function fmtSummary(summary) {
|
function fmtSummary(summary, running) {
|
||||||
if (!summary || Object.keys(summary).length === 0) {
|
if (running) {
|
||||||
return 'Nessuna esecuzione';
|
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 = [];
|
const parts = [];
|
||||||
for (const [key, val] of Object.entries(summary)) {
|
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) {
|
function statusClass(status, running) {
|
||||||
@@ -112,7 +123,7 @@
|
|||||||
<div>Ultima fine: ${fmtDate(job.last_end)}</div>
|
<div>Ultima fine: ${fmtDate(job.last_end)}</div>
|
||||||
</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">
|
<div class="mt-3 d-flex gap-2">
|
||||||
<button class="btn btn-outline-light btn-sm" data-job="${job.name}">Avvia ora</button>
|
<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) {
|
function renderLogs(logs) {
|
||||||
const tbody = document.querySelector('#logsTable tbody');
|
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');
|
const tr = document.createElement('tr');
|
||||||
|
if (hasNew && i < 3) tr.classList.add('row-new');
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td class="mono small">${fmtDate(entry.time)}</td>
|
<td class="mono small">${fmtDate(entry.time)}</td>
|
||||||
<td><span class="pill level-${entry.level.toLowerCase()}">${entry.level}</span></td>
|
<td><span class="pill level-${entry.level.toLowerCase()}">${entry.level}</span></td>
|
||||||
<td class="mono small">${entry.job || '-'}</td>
|
<td class="mono small">${entry.job || '-'}</td>
|
||||||
<td>${entry.message}</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);
|
tbody.appendChild(tr);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('logCount').textContent = entries.length + ' eventi';
|
||||||
|
|
||||||
|
if (hasNew) {
|
||||||
|
const scroll = document.querySelector('.log-scroll');
|
||||||
|
scroll.scrollTop = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reload() {
|
async function reload() {
|
||||||
@@ -158,6 +185,13 @@
|
|||||||
document.getElementById('serverTime').textContent = `Server UTC: ${fmtDate(data.server_time)}`;
|
document.getElementById('serverTime').textContent = `Server UTC: ${fmtDate(data.server_time)}`;
|
||||||
renderJobs(data.jobs || []);
|
renderJobs(data.jobs || []);
|
||||||
renderLogs(data.logs || []);
|
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) {
|
} catch (err) {
|
||||||
document.getElementById('serverTime').textContent = `Errore: ${err.message}`;
|
document.getElementById('serverTime').textContent = `Errore: ${err.message}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user