feat: add emergency backup restoration page for new installs
This commit is contained in:
91
frontend/src/app/admin/backup/emergency/page.tsx
Normal file
91
frontend/src/app/admin/backup/emergency/page.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
export default function EmergencyBackupPage() {
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [status, setStatus] = useState<'idle' | 'uploading' | 'success' | 'error'>('idle');
|
||||
const [message, setMessage] = useState('');
|
||||
const router = useRouter();
|
||||
|
||||
const handleUpload = async () => {
|
||||
if (!file) return;
|
||||
setStatus('uploading');
|
||||
setMessage('Restaurando sistema de emergência... Por favor, aguarde.');
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/admin/backup/full', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
setStatus('success');
|
||||
setMessage('Sistema restaurado com sucesso! Redirecionando para o login...');
|
||||
setTimeout(() => router.push('/admin/login'), 3000);
|
||||
} else {
|
||||
const errorData = await res.json();
|
||||
setStatus('error');
|
||||
setMessage(errorData.error || 'Erro ao restaurar backup.');
|
||||
}
|
||||
} catch (err) {
|
||||
setStatus('error');
|
||||
setMessage('Erro de conexão ao restaurar backup.');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-900 flex items-center justify-center p-4">
|
||||
<div className="max-w-md w-full bg-slate-800 rounded-2xl shadow-2xl p-8 border border-white/10">
|
||||
<h1 className="text-2xl font-bold text-white mb-2">🔑 Chave Mestra de Restauração</h1>
|
||||
<p className="text-slate-400 mb-8">Use esta página para restaurar seu backup ZIP em um banco de dados novo.</p>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="border-2 border-dashed border-slate-600 rounded-xl p-8 text-center hover:border-blue-500 transition-colors cursor-pointer relative">
|
||||
<input
|
||||
type="file"
|
||||
accept=".zip"
|
||||
className="absolute inset-0 opacity-0 cursor-pointer"
|
||||
onChange={(e) => setFile(e.target.files?.[0] || null)}
|
||||
/>
|
||||
<div className="text-slate-300">
|
||||
{file ? (
|
||||
<span className="text-blue-400 font-medium">{file.name}</span>
|
||||
) : (
|
||||
'Clique ou arraste o arquivo .zip de backup aqui'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleUpload}
|
||||
disabled={!file || status === 'uploading'}
|
||||
className="w-full bg-blue-600 hover:bg-blue-500 disabled:bg-slate-700 text-white font-bold py-4 rounded-xl transition-all shadow-lg flex items-center justify-center gap-3"
|
||||
>
|
||||
{status === 'uploading' ? (
|
||||
<>
|
||||
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin"></div>
|
||||
Restaurando...
|
||||
</>
|
||||
) : (
|
||||
'Restaurar Sistema Agora'
|
||||
)}
|
||||
</button>
|
||||
|
||||
{message && (
|
||||
<div className={`p-4 rounded-lg text-center text-sm font-medium ${status === 'success' ? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30' :
|
||||
status === 'error' ? 'bg-rose-500/20 text-rose-400 border border-rose-500/30' :
|
||||
'bg-blue-500/10 text-blue-400 border border-blue-500/20'
|
||||
}`}>
|
||||
{message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user