🚀 Auto-deploy: BrainKP atualizado em 22/06/2026 18:58:20

This commit is contained in:
2026-06-22 18:58:20 +00:00
parent 9c1d8fc002
commit ac1c573df9
+66 -33
View File
@@ -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 `${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 `${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 `${hrs}h`; } else {
const days = Math.floor(hrs / 24); d = new Date(dateStr);
if (days < 7) return `${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; }
} }