🚀 Auto-deploy: BrainKP atualizado em 22/06/2026 18:58:20
This commit is contained in:
+66
-33
@@ -273,10 +273,21 @@ async function loadStatus() {
|
|||||||
// Last run badge
|
// Last run badge
|
||||||
const lr = d.log.last_run;
|
const lr = d.log.last_run;
|
||||||
const badge = document.getElementById('last-run');
|
const badge = document.getElementById('last-run');
|
||||||
|
let isAnyRunning = false;
|
||||||
|
let runningScripts = [];
|
||||||
|
|
||||||
if (lr) {
|
if (lr) {
|
||||||
const ok = !lr.has_error && lr.scripts.every(s => s.status === 'ok');
|
const isRunning = lr.scripts.some(s => s.status === 'running');
|
||||||
badge.textContent = ok ? '✓ OK' : '⚠ Erro';
|
if (isRunning) {
|
||||||
badge.className = `badge ${ok ? 'green' : 'red'}`;
|
isAnyRunning = true;
|
||||||
|
runningScripts = lr.scripts.filter(s => s.status === 'running').map(s => s.name);
|
||||||
|
badge.innerHTML = '<span class="loading" style="width:10px;height:10px;margin-right:4px;border-width:2px"></span> Em andamento';
|
||||||
|
badge.className = 'badge yellow';
|
||||||
|
} else {
|
||||||
|
const ok = !lr.has_error && lr.scripts.every(s => s.status === 'ok');
|
||||||
|
badge.textContent = ok ? '✓ Concluído' : '⚠ Erro';
|
||||||
|
badge.className = `badge ${ok ? 'green' : 'red'}`;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
badge.textContent = '—';
|
badge.textContent = '—';
|
||||||
badge.className = 'badge yellow';
|
badge.className = 'badge yellow';
|
||||||
@@ -284,24 +295,47 @@ async function loadStatus() {
|
|||||||
|
|
||||||
// Cards
|
// Cards
|
||||||
const s = d.stats;
|
const s = d.stats;
|
||||||
|
const isAppsRunning = runningScripts.includes('Backup Apps');
|
||||||
|
const isBdRunning = runningScripts.includes('Backup BD');
|
||||||
|
const isGitRunning = runningScripts.includes('Backup Git');
|
||||||
|
const isReposRunning = runningScripts.includes('Backup repos Git');
|
||||||
|
|
||||||
const cards = [
|
const cards = [
|
||||||
{id:'apps', icon:'📦', name:'Apps', desc:`${s.apps.count} backup${s.apps.count!==1?'s':''}`, meta: s.apps.latest ? `Último: ${timeAgo(s.apps.latest)}` : '', size: formatSize(s.apps.size)},
|
{id:'apps', icon:'📦', name:'Apps', count: s.apps.count, meta: s.apps.latest ? `Último: ${formatDateTime(s.apps.latest)}` : 'Nenhum backup', size: formatSize(s.apps.size), running: isAppsRunning},
|
||||||
{id:'bd', icon:'🗄️', name:'Bancos', desc:`${s.bd.count} backup${s.bd.count!==1?'s':''}`, meta: s.bd.latest ? `Último: ${timeAgo(s.bd.latest)}` : '', size: formatSize(s.bd.size)},
|
{id:'bd', icon:'🗄️', name:'Bancos', count: s.bd.count, meta: s.bd.latest ? `Último: ${formatDateTime(s.bd.latest)}` : 'Nenhum backup', size: formatSize(s.bd.size), running: isBdRunning},
|
||||||
{id:'git', icon:'🐙', name:'Git Full', desc:`${s.git.count} backup${s.git.count!==1?'s':''}`, meta: s.git.latest ? `Último: ${timeAgo(s.git.latest)}` : '', size: formatSize(s.git.size)},
|
{id:'git', icon:'🐙', name:'Git Full', count: s.git.count, meta: s.git.latest ? `Último: ${formatDateTime(s.git.latest)}` : 'Nenhum backup', size: formatSize(s.git.size), running: isGitRunning},
|
||||||
{id:'repos', icon:'📁', name:'Repos', desc:`${s.repos.count} backup${s.repos.count!==1?'s':''}`, meta: s.repos.latest ? `Último: ${timeAgo(s.repos.latest)}` : '', size: formatSize(s.repos.size)},
|
{id:'repos', icon:'📁', name:'Repos', count: s.repos.count, meta: s.repos.latest ? `Último: ${formatDateTime(s.repos.latest)}` : 'Nenhum backup', size: formatSize(s.repos.size), running: isReposRunning},
|
||||||
];
|
];
|
||||||
|
|
||||||
document.getElementById('cards').innerHTML = cards.map(c => `
|
document.getElementById('cards').innerHTML = cards.map(c => `
|
||||||
<div class="card" onclick="switchTab('${c.id}')">
|
<div class="card" onclick="switchTab('${c.id}')" style="${c.running ? 'border-color:var(--accent)' : ''}">
|
||||||
<div class="card-icon">${c.icon}</div>
|
<div class="card-icon">${c.icon}</div>
|
||||||
<div class="card-name">${c.name}</div>
|
<div class="card-name">${c.name}</div>
|
||||||
<div class="card-meta">
|
<div class="card-meta">
|
||||||
<span>${c.desc}</span>
|
<span style="font-weight:600;color:var(--text);font-size:12px;margin:4px 0">${c.count} arquivo${c.count!==1?'s':''} de backup</span>
|
||||||
<span>${c.meta}</span>
|
<span>${c.meta}</span>
|
||||||
<span>${c.size}</span>
|
<span>Tamanho: ${c.size}</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="run-btn" onclick="event.stopPropagation();runBackup('${c.id}')">▶ Executar Backup</button>
|
${c.running
|
||||||
|
? `<div style="margin-top:12px;text-align:center;font-size:11px;color:var(--accent);font-weight:bold"><span class="loading"></span> Sincronizando...</div>`
|
||||||
|
: `<button class="run-btn" onclick="event.stopPropagation();runBackup('${c.id}')">▶ Executar Backup</button>`
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|
||||||
|
// Poll faster if something is running
|
||||||
|
if (isAnyRunning) {
|
||||||
|
if (!window.pollIntervalFast) {
|
||||||
|
window.pollIntervalFast = setInterval(loadStatus, 2000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (window.pollIntervalFast) {
|
||||||
|
clearInterval(window.pollIntervalFast);
|
||||||
|
window.pollIntervalFast = null;
|
||||||
|
// Reload backups to show the newly finished one
|
||||||
|
loadBackups();
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch(e) { console.error(e); }
|
} catch(e) { console.error(e); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,7 +361,7 @@ function renderBackupList(items) {
|
|||||||
<div class="backup-item">
|
<div class="backup-item">
|
||||||
<div class="bi-left">
|
<div class="bi-left">
|
||||||
<span class="bi-name">${item.name}</span>
|
<span class="bi-name">${item.name}</span>
|
||||||
<span class="bi-date">${timeAgo(item.modified)}</span>
|
<span class="bi-date">${formatDateTime(item.modified)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="bi-right">
|
<div class="bi-right">
|
||||||
<span class="bi-size">${formatSize(item.size)}</span>
|
<span class="bi-size">${formatSize(item.size)}</span>
|
||||||
@@ -404,7 +438,7 @@ async function runBackup(type) {
|
|||||||
const d = await res.json();
|
const d = await res.json();
|
||||||
if (d.ok) {
|
if (d.ok) {
|
||||||
showToast(`${type} backup iniciado ✓`);
|
showToast(`${type} backup iniciado ✓`);
|
||||||
setTimeout(loadAll, 3000);
|
loadStatus();
|
||||||
} else {
|
} else {
|
||||||
showToast(d.error, 'error');
|
showToast(d.error, 'error');
|
||||||
}
|
}
|
||||||
@@ -560,28 +594,27 @@ function formatSize(bytes) {
|
|||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
function timeAgo(dateStr) {
|
function formatDateTime(dateStr) {
|
||||||
if (!dateStr) return '—';
|
if (!dateStr) return '—';
|
||||||
// dateStr can be filename (20260622) or ModTime
|
|
||||||
const m = dateStr.match(/(\d{4})(\d{2})(\d{2})/);
|
|
||||||
if (m) {
|
|
||||||
const d = new Date(m[1], m[2]-1, m[3]);
|
|
||||||
const diff = Date.now() - d.getTime();
|
|
||||||
const days = Math.floor(diff / 86400000);
|
|
||||||
if (days === 0) return 'Hoje';
|
|
||||||
if (days === 1) return 'Ontem';
|
|
||||||
return `há ${days} dia${days>1?'s':''}`;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const d = new Date(dateStr);
|
// try to match YYYYMMDD_HHMMSS if it's from filename
|
||||||
const diff = Date.now() - d.getTime();
|
const m = dateStr.match(/(\d{4})(\d{2})(\d{2})_?(\d{2})?(\d{2})?(\d{2})?/);
|
||||||
const mins = Math.floor(diff / 60000);
|
let d;
|
||||||
if (mins < 60) return `há ${mins}m`;
|
if (m && m[1] && m[2] && m[3] && dateStr.indexOf('-') === -1) {
|
||||||
const hrs = Math.floor(mins / 60);
|
d = new Date(m[1], m[2]-1, m[3], m[4]||0, m[5]||0, m[6]||0);
|
||||||
if (hrs < 24) return `há ${hrs}h`;
|
} else {
|
||||||
const days = Math.floor(hrs / 24);
|
d = new Date(dateStr);
|
||||||
if (days < 7) return `há ${days}d`;
|
}
|
||||||
return d.toLocaleDateString('pt-BR');
|
|
||||||
|
if (isNaN(d.getTime())) return dateStr;
|
||||||
|
|
||||||
|
const day = String(d.getDate()).padStart(2, '0');
|
||||||
|
const month = String(d.getMonth() + 1).padStart(2, '0');
|
||||||
|
const year = d.getFullYear();
|
||||||
|
const hr = String(d.getHours()).padStart(2, '0');
|
||||||
|
const min = String(d.getMinutes()).padStart(2, '0');
|
||||||
|
|
||||||
|
return `${day}/${month}/${year} às ${hr}:${min}`;
|
||||||
} catch { return dateStr; }
|
} catch { return dateStr; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user