386 lines
17 KiB
TypeScript
386 lines
17 KiB
TypeScript
"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>
|
||
);
|
||
}
|