🚀 Auto-deploy: BrainKP atualizado em 24/07/2026 23:45:56
This commit is contained in:
@@ -221,6 +221,79 @@ def status():
|
||||
stats = get_backup_stats()
|
||||
return jsonify({"log": log, "stats": stats})
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# CLEANUP
|
||||
# ──────────────────────────────────────────────
|
||||
|
||||
@app.route("/api/cleanup/<backup_type>", methods=["POST"])
|
||||
def cleanup_backups(backup_type):
|
||||
remotes = {
|
||||
"apps": "gdrive:Apps",
|
||||
"bd": "gdrive:BD",
|
||||
"git": "gdrive:Git",
|
||||
"repos": "gdrive:Git/repos"
|
||||
}
|
||||
remote = remotes.get(backup_type)
|
||||
if not remote:
|
||||
return jsonify({"error": "Unknown type"}), 400
|
||||
|
||||
if backup_type == "repos":
|
||||
items = list_rclone_contents("gdrive", "Git/repos", depth=2)
|
||||
elif backup_type == "git":
|
||||
items = list_rclone_contents("gdrive", "Git")
|
||||
items = [i for i in items if "gitea-data" in i.get("Name", "")]
|
||||
else:
|
||||
items = list_rclone_contents("gdrive", backup_type.capitalize())
|
||||
|
||||
import re
|
||||
def get_group_key(item):
|
||||
path = item.get("Path", item.get("Name", ""))
|
||||
# For repos, path might be owner/repo-datetime.tar.gz
|
||||
# Extract base name before the date string (e.g. -20260724_021744)
|
||||
match = re.search(r'(-\d{8}_\d{6})', path)
|
||||
if match:
|
||||
return path[:match.start()]
|
||||
return path.split('-')[0]
|
||||
|
||||
# Filter out directories
|
||||
items = [i for i in items if not i.get("IsDir")]
|
||||
|
||||
# Group by key
|
||||
grouped = {}
|
||||
for item in items:
|
||||
key = get_group_key(item)
|
||||
if key not in grouped:
|
||||
grouped[key] = []
|
||||
grouped[key].append(item)
|
||||
|
||||
deleted_count = 0
|
||||
errors = []
|
||||
|
||||
for key, group_items in grouped.items():
|
||||
# Sort by ModTime descending (newest first)
|
||||
group_items.sort(key=lambda x: x.get("ModTime", ""), reverse=True)
|
||||
# Keep first 2
|
||||
to_delete = group_items[2:]
|
||||
for item in to_delete:
|
||||
path = item.get("Path", item.get("Name"))
|
||||
full_remote_path = f"{remote}/{path}"
|
||||
cmd = ["rclone", "deletefile", full_remote_path, "--drive-root-folder-id", "1ey-5aABgHDtirxeJarWy0ZmeqQPVTYtm"]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
||||
if result.returncode == 0:
|
||||
deleted_count += 1
|
||||
else:
|
||||
errors.append(f"Failed to delete {path}: {result.stderr}")
|
||||
|
||||
# invalidate cache
|
||||
global _stats_cache
|
||||
_stats_cache["timestamp"] = 0
|
||||
|
||||
return jsonify({
|
||||
"ok": True,
|
||||
"deleted_count": deleted_count,
|
||||
"errors": errors
|
||||
})
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# LIST BACKUPS
|
||||
# ──────────────────────────────────────────────
|
||||
|
||||
+29
-1
@@ -319,7 +319,8 @@ async function loadStatus() {
|
||||
</div>
|
||||
${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>`
|
||||
: `<button class="run-btn" onclick="event.stopPropagation();runBackup('${c.id}')">▶ Executar Backup</button>
|
||||
<button class="run-btn" style="margin-top:5px; background:var(--danger)" onclick="event.stopPropagation();cleanupBackups('${c.id}')">🗑️ Limpar Antigos</button>`
|
||||
}
|
||||
</div>
|
||||
`).join('');
|
||||
@@ -448,6 +449,33 @@ async function runBackup(type) {
|
||||
btn.innerHTML = '▶ Executar Backup';
|
||||
}
|
||||
|
||||
async function cleanupBackups(type) {
|
||||
if (!confirm(`Tem certeza que deseja apagar backups antigos de ${getTypeLabel(type)} (mantendo apenas os 2 mais recentes de cada)?`)) return;
|
||||
const btn = event.target;
|
||||
const originalText = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="loading"></span> Limpando...';
|
||||
try {
|
||||
const res = await fetch(`${API}/cleanup/${type}`, { method: 'POST' });
|
||||
const d = await res.json();
|
||||
if (d.ok) {
|
||||
if (d.errors && d.errors.length > 0) {
|
||||
showToast(`Concluído com alertas: ${d.deleted_count} apagados. ${d.errors.length} erros.`, 'warning');
|
||||
} else {
|
||||
showToast(`Limpeza concluída! ${d.deleted_count} backups antigos apagados.`, 'ok');
|
||||
}
|
||||
loadStatus();
|
||||
loadBackups();
|
||||
} else {
|
||||
showToast(d.error || 'Erro na limpeza', 'error');
|
||||
}
|
||||
} catch(e) {
|
||||
showToast('Erro ao realizar limpeza', 'error');
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = originalText;
|
||||
}
|
||||
|
||||
let restoreTarget = null;
|
||||
|
||||
function openRestoreModal(type, filename, size) {
|
||||
|
||||
Reference in New Issue
Block a user