++ update project
This commit is contained in:
181
app/templates/index.html
Normal file
181
app/templates/index.html
Normal file
@@ -0,0 +1,181 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Controllo Mirror S3</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/static/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="aurora aurora-a"></div>
|
||||
<div class="aurora aurora-b"></div>
|
||||
|
||||
<main class="container py-4 position-relative">
|
||||
<section class="hero-panel p-4 p-md-5 mb-4">
|
||||
<div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between gap-3">
|
||||
<div>
|
||||
<p class="eyebrow">Mirror S3 su Locale</p>
|
||||
<h1 class="mb-2">Dashboard Sync</h1>
|
||||
<p class="lead mb-0">Controlla tutti i job, avvia sync manuali e consulta i log in tempo reale.</p>
|
||||
</div>
|
||||
<div class="text-md-end">
|
||||
<button id="runAllBtn" class="btn btn-accent btn-lg">Avvia Tutto Ora</button>
|
||||
<p class="small mt-2 mb-0" id="serverTime">loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div id="jobsGrid" class="row g-3"></div>
|
||||
</section>
|
||||
|
||||
<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>
|
||||
<div class="table-responsive">
|
||||
<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>Messaggio</th>
|
||||
<th>Dettagli</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
async function fetchStatus() {
|
||||
const response = await fetch('/api/status');
|
||||
if (!response.ok) {
|
||||
throw new Error('Impossibile caricare lo stato');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
function fmtDate(value) {
|
||||
if (!value) return '-';
|
||||
return new Date(value).toLocaleString();
|
||||
}
|
||||
|
||||
function fmtSummary(summary) {
|
||||
if (!summary || Object.keys(summary).length === 0) {
|
||||
return 'Nessuna esecuzione';
|
||||
}
|
||||
const parts = [];
|
||||
for (const [key, val] of Object.entries(summary)) {
|
||||
parts.push(`${key}: ${val}`);
|
||||
}
|
||||
return parts.join(' | ');
|
||||
}
|
||||
|
||||
function statusClass(status, running) {
|
||||
if (running) return 'status-running';
|
||||
if (status === 'success') return 'status-ok';
|
||||
if (status === 'failed') return 'status-fail';
|
||||
return 'status-idle';
|
||||
}
|
||||
|
||||
function renderJobs(jobs) {
|
||||
const grid = document.getElementById('jobsGrid');
|
||||
grid.innerHTML = '';
|
||||
|
||||
jobs.forEach((job) => {
|
||||
const col = document.createElement('div');
|
||||
col.className = 'col-12 col-lg-6';
|
||||
|
||||
const statusText = job.running ? 'in esecuzione' : job.last_status;
|
||||
col.innerHTML = `
|
||||
<article class="job-card p-4">
|
||||
<div class="d-flex justify-content-between align-items-start gap-3">
|
||||
<div>
|
||||
<h3 class="h5 mb-1">${job.name}</h3>
|
||||
<p class="mono small mb-1">bucket: ${job.bucket}</p>
|
||||
<p class="mono small mb-0">locale: ${job.local_dir}</p>
|
||||
</div>
|
||||
<span class="status-chip ${statusClass(job.last_status, job.running)}">${statusText}</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 small text-secondary-emphasis">
|
||||
<div>Ultimo avvio: ${fmtDate(job.last_start)}</div>
|
||||
<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 d-flex gap-2">
|
||||
<button class="btn btn-outline-light btn-sm" data-job="${job.name}">Avvia ora</button>
|
||||
</div>
|
||||
</article>
|
||||
`;
|
||||
|
||||
const runBtn = col.querySelector('button[data-job]');
|
||||
runBtn.addEventListener('click', async () => {
|
||||
runBtn.disabled = true;
|
||||
try {
|
||||
await fetch(`/api/run/${encodeURIComponent(job.name)}`, { method: 'POST' });
|
||||
await reload();
|
||||
} finally {
|
||||
runBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
grid.appendChild(col);
|
||||
});
|
||||
}
|
||||
|
||||
function renderLogs(logs) {
|
||||
const tbody = document.querySelector('#logsTable tbody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
logs.slice(0, 120).forEach((entry) => {
|
||||
const tr = document.createElement('tr');
|
||||
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>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
async function reload() {
|
||||
try {
|
||||
const data = await fetchStatus();
|
||||
document.getElementById('serverTime').textContent = `Server UTC: ${fmtDate(data.server_time)}`;
|
||||
renderJobs(data.jobs || []);
|
||||
renderLogs(data.logs || []);
|
||||
} catch (err) {
|
||||
document.getElementById('serverTime').textContent = `Errore: ${err.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('runAllBtn').addEventListener('click', async () => {
|
||||
const btn = document.getElementById('runAllBtn');
|
||||
btn.disabled = true;
|
||||
try {
|
||||
await fetch('/api/run-all', { method: 'POST' });
|
||||
await reload();
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
reload();
|
||||
setInterval(reload, 5000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user