Files
aggios.app/front-end-dash.aggios.app/app/superadmin/backup/page.tsx
2025-12-17 13:36:23 -03:00

386 lines
17 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState, useEffect } from "react";
import {
ArrowDownTrayIcon,
ArrowUpTrayIcon,
ClockIcon,
ServerIcon,
CheckCircleIcon,
ExclamationTriangleIcon,
BoltIcon,
} from '@heroicons/react/24/outline';
interface Backup {
filename: string;
size: string;
date: string;
timestamp: string;
}
export default function BackupPage() {
const [loading, setLoading] = useState(false);
const [backups, setBackups] = useState<Backup[]>([]);
const [selectedBackup, setSelectedBackup] = useState<string>("");
const [message, setMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null);
const [autoBackupEnabled, setAutoBackupEnabled] = useState(false);
const [autoBackupInterval, setAutoBackupInterval] = useState<number>(6);
useEffect(() => {
loadBackups();
loadAutoBackupSettings();
}, []);
const loadAutoBackupSettings = () => {
const enabled = localStorage.getItem('autoBackupEnabled') === 'true';
const interval = parseInt(localStorage.getItem('autoBackupInterval') || '6');
setAutoBackupEnabled(enabled);
setAutoBackupInterval(interval);
};
const toggleAutoBackup = () => {
const newValue = !autoBackupEnabled;
setAutoBackupEnabled(newValue);
localStorage.setItem('autoBackupEnabled', newValue.toString());
if (newValue) {
startAutoBackup();
setMessage({ type: 'success', text: `Backup automático ativado (a cada ${autoBackupInterval}h)` });
} else {
stopAutoBackup();
setMessage({ type: 'success', text: 'Backup automático desativado' });
}
};
const startAutoBackup = () => {
const intervalMs = autoBackupInterval * 60 * 60 * 1000; // horas para ms
const intervalId = setInterval(() => {
createBackup();
}, intervalMs);
localStorage.setItem('autoBackupIntervalId', intervalId.toString());
};
const stopAutoBackup = () => {
const intervalId = localStorage.getItem('autoBackupIntervalId');
if (intervalId) {
clearInterval(parseInt(intervalId));
localStorage.removeItem('autoBackupIntervalId');
}
};
const changeInterval = (hours: number) => {
setAutoBackupInterval(hours);
localStorage.setItem('autoBackupInterval', hours.toString());
if (autoBackupEnabled) {
stopAutoBackup();
startAutoBackup();
setMessage({ type: 'success', text: `Intervalo alterado para ${hours}h` });
}
};
const loadBackups = async () => {
try {
const token = localStorage.getItem('token');
const response = await fetch('http://localhost:8085/api/superadmin/backups', {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const data = await response.json();
setBackups(data.backups || []);
}
} catch (error) {
console.error('Erro ao carregar backups:', error);
}
};
const createBackup = async () => {
setLoading(true);
setMessage(null);
try {
const token = localStorage.getItem('token');
const response = await fetch('http://localhost:8085/api/superadmin/backup/create', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
}
});
const data = await response.json();
if (response.ok) {
setMessage({ type: 'success', text: `Backup criado: ${data.filename} (${data.size})` });
await loadBackups();
} else {
setMessage({ type: 'error', text: data.error || 'Erro ao criar backup' });
}
} catch (error) {
setMessage({ type: 'error', text: 'Erro ao criar backup' });
} finally {
setLoading(false);
}
};
const restoreBackup = async () => {
if (!selectedBackup) {
setMessage({ type: 'error', text: 'Selecione um backup para restaurar' });
return;
}
if (!confirm(`⚠️ ATENÇÃO: Isso irá SOBRESCREVER todos os dados atuais!\n\nDeseja restaurar o backup:\n${selectedBackup}?`)) {
return;
}
setLoading(true);
setMessage(null);
try {
const token = localStorage.getItem('token');
const response = await fetch('http://localhost:8085/api/superadmin/backup/restore', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ filename: selectedBackup })
});
const data = await response.json();
if (response.ok) {
setMessage({ type: 'success', text: 'Backup restaurado com sucesso! Recarregando...' });
setTimeout(() => {
window.location.reload();
}, 2000);
} else {
setMessage({ type: 'error', text: data.error || 'Erro ao restaurar backup' });
}
} catch (error) {
setMessage({ type: 'error', text: 'Erro ao restaurar backup' });
} finally {
setLoading(false);
}
};
const downloadBackup = async (filename: string) => {
try {
const token = localStorage.getItem('token');
const response = await fetch(`http://localhost:8085/api/superadmin/backup/download/${filename}`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} else {
setMessage({ type: 'error', text: 'Erro ao baixar backup' });
}
} catch (error) {
setMessage({ type: 'error', text: 'Erro ao baixar backup' });
}
};
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-8">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
Backup & Restore
</h1>
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
Gerencie backups do banco de dados PostgreSQL
</p>
</div>
{message && (
<div className={`mb-6 p-4 rounded-lg flex items-center gap-3 ${message.type === 'success'
? 'bg-green-50 dark:bg-green-900/20 text-green-800 dark:text-green-200'
: 'bg-red-50 dark:bg-red-900/20 text-red-800 dark:text-red-200'
}`}>
{message.type === 'success' ? (
<CheckCircleIcon className="h-5 w-5" />
) : (
<ExclamationTriangleIcon className="h-5 w-5" />
)}
<span>{message.text}</span>
</div>
)}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div className="flex items-center gap-3 mb-4">
<div className="p-3 bg-blue-100 dark:bg-blue-900/30 rounded-lg">
<ArrowDownTrayIcon className="h-6 w-6 text-blue-600 dark:text-blue-400" />
</div>
<div>
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
Criar Backup
</h2>
<p className="text-sm text-gray-600 dark:text-gray-400">
Exportar dados atuais
</p>
</div>
</div>
<button
onClick={createBackup}
disabled={loading}
className="w-full px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
>
{loading ? 'Criando...' : 'Criar Novo Backup'}
</button>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div className="flex items-center gap-3 mb-4">
<div className="p-3 bg-green-100 dark:bg-green-900/30 rounded-lg">
<BoltIcon className="h-6 w-6 text-green-600 dark:text-green-400" />
</div>
<div>
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
Backup Automático
</h2>
<p className="text-sm text-gray-600 dark:text-gray-400">
{autoBackupEnabled ? `Ativo (${autoBackupInterval}h)` : 'Desativado'}
</p>
</div>
</div>
<div className="space-y-3">
<select
value={autoBackupInterval}
onChange={(e) => changeInterval(parseInt(e.target.value))}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
>
<option value={1}>A cada 1 hora</option>
<option value={3}>A cada 3 horas</option>
<option value={6}>A cada 6 horas</option>
<option value={12}>A cada 12 horas</option>
<option value={24}>A cada 24 horas</option>
</select>
<button
onClick={toggleAutoBackup}
className={`w-full px-4 py-2 rounded-lg transition-colors ${
autoBackupEnabled
? 'bg-red-600 hover:bg-red-700 text-white'
: 'bg-green-600 hover:bg-green-700 text-white'
}`}
>
{autoBackupEnabled ? 'Desativar' : 'Ativar'}
</button>
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div className="flex items-center gap-3 mb-4">
<div className="p-3 bg-amber-100 dark:bg-amber-900/30 rounded-lg">
<ArrowUpTrayIcon className="h-6 w-6 text-amber-600 dark:text-amber-400" />
</div>
<div>
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
Restaurar Backup
</h2>
<p className="text-sm text-gray-600 dark:text-gray-400">
Sobrescreve dados atuais
</p>
</div>
</div>
<select
value={selectedBackup}
onChange={(e) => setSelectedBackup(e.target.value)}
className="w-full mb-3 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
>
<option value="">Selecione um backup...</option>
{backups.map((backup) => (
<option key={backup.filename} value={backup.filename}>
{backup.filename} - {backup.size}
</option>
))}
</select>
<button
onClick={restoreBackup}
disabled={loading || !selectedBackup}
className="w-full px-4 py-2 bg-amber-600 text-white rounded-lg hover:bg-amber-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
>
{loading ? 'Restaurando...' : 'Restaurar Backup'}
</button>
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow">
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center gap-3">
<ServerIcon className="h-6 w-6 text-gray-600 dark:text-gray-400" />
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
Backups Disponíveis
</h2>
<span className="ml-auto text-sm text-gray-500 dark:text-gray-400">
{backups.length} arquivo(s)
</span>
</div>
</div>
<div className="p-6">
{backups.length === 0 ? (
<div className="text-center py-12">
<ServerIcon className="mx-auto h-12 w-12 text-gray-400" />
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
Nenhum backup encontrado
</p>
</div>
) : (
<div className="space-y-3">
{backups.map((backup) => (
<div
key={backup.filename}
className={`p-4 rounded-lg border transition-colors cursor-pointer ${selectedBackup === backup.filename
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
: 'border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700/50'
}`}
onClick={() => setSelectedBackup(backup.filename)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<ClockIcon className="h-5 w-5 text-gray-400" />
<div>
<p className="font-medium text-gray-900 dark:text-white">
{backup.filename}
</p>
<p className="text-sm text-gray-600 dark:text-gray-400">
{backup.date} {backup.size}
</p>
</div>
</div>
<button
onClick={(e) => {
e.stopPropagation();
downloadBackup(backup.filename);
}}
className="px-3 py-1 text-sm text-blue-600 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900/30 rounded"
>
Download
</button>
</div>
</div>
))}
</div>
)}
</div>
</div>
</div>
</div>
);
}