production: docker + nginx config for rf-flux.com
Deploy to VPS / deploy (push) Has been cancelled

This commit is contained in:
2026-03-20 13:46:05 -05:00
parent b275b19f08
commit fc24313f15
187 changed files with 20977 additions and 767 deletions
@@ -0,0 +1,219 @@
"use client";
import { useState, useEffect, useRef } from "react";
import Link from "next/link";
import {
ArrowLeft, Server, Activity, Database, HardDrive,
DownloadCloud, ShieldAlert, UploadCloud, Loader2, CheckCircle2
} from "lucide-react";
import { getSystemMetrics, exportDatabase, restoreDatabase } from "./actions";
export default function SystemHealth() {
const [metrics, setMetrics] = useState<any>(null);
const [isExporting, setIsExporting] = useState(false);
// Restore State
const [isRestoring, setIsRestoring] = useState(false);
const [restoreError, setRestoreError] = useState("");
const [restoreSuccess, setRestoreSuccess] = useState(false);
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const fetchMetrics = async () => {
const res = await getSystemMetrics();
if (res.success) setMetrics(res);
};
useEffect(() => {
fetchMetrics();
// Actualizar métricas cada 10 segundos
const interval = setInterval(fetchMetrics, 10000);
return () => clearInterval(interval);
}, []);
const formatUptime = (seconds: number) => {
const d = Math.floor(seconds / (3600 * 24));
const h = Math.floor(seconds % (3600 * 24) / 3600);
const m = Math.floor(seconds % 3600 / 60);
return `${d}d ${h}h ${m}m`;
};
const handleExport = async () => {
setIsExporting(true);
const res = await exportDatabase();
if (res.success && res.data) {
// Crear un Blob y forzar la descarga del JSON
const blob = new Blob([res.data], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `flux-db-snapshot-${new Date().toISOString().split('T')[0]}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
} else {
alert(res.error || "Export failed.");
}
setIsExporting(false);
};
const handleRestore = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!selectedFile) return setRestoreError("Please select a JSON backup file.");
setIsRestoring(true);
setRestoreError("");
try {
const reader = new FileReader();
reader.onload = async (event) => {
const jsonString = event.target?.result as string;
const formData = new FormData(e.currentTarget as HTMLFormElement);
formData.append("jsonString", jsonString);
const res = await restoreDatabase(formData);
if (res.error) {
setRestoreError(res.error);
} else {
setRestoreSuccess(true);
// Recargar para forzar la re-lectura de la BD
setTimeout(() => window.location.href = "/hq-command/dashboard", 3000);
}
setIsRestoring(false);
};
reader.readAsText(selectedFile);
} catch (error) {
setRestoreError("Failed to read the file.");
setIsRestoring(false);
}
};
return (
<div className="min-h-screen p-6 md:p-12 max-w-5xl mx-auto">
{/* HEADER */}
<div className="mb-10">
<Link href="/hq-command/dashboard" className="inline-flex items-center gap-2 text-sm text-[#86868B] hover:text-[#00F0FF] transition-colors mb-6 group">
<ArrowLeft size={16} className="group-hover:-translate-x-1 transition-transform" /> Back to Command Center
</Link>
<div>
<h1 className="text-3xl font-light text-white flex items-center gap-3">
<Server className="text-blue-400" /> System Health & Vault
</h1>
<p className="text-[#86868B] mt-2">Server telemetry, data snapshots, and disaster recovery protocols.</p>
</div>
</div>
{/* TELEMETRÍA (MÉTRICAS) */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-10">
<div className="bg-[#111] border border-white/5 p-6 rounded-3xl relative overflow-hidden">
<div className="absolute -right-4 -bottom-4 opacity-5"><Activity size={100} /></div>
<span className="text-[10px] uppercase tracking-widest text-[#86868B] block mb-2">Node.js Uptime</span>
<p className="text-2xl font-mono text-white">
{metrics ? formatUptime(metrics.uptime) : "Loading..."}
</p>
</div>
<div className="bg-[#111] border border-white/5 p-6 rounded-3xl relative overflow-hidden">
<div className="absolute -right-4 -bottom-4 opacity-5"><HardDrive size={100} /></div>
<span className="text-[10px] uppercase tracking-widest text-[#86868B] block mb-2">Heap Memory Usage</span>
<p className="text-2xl font-mono text-white">
{metrics ? `${metrics.memory.used} MB ` : "0 MB "}
<span className="text-sm text-[#86868B]">/ {metrics ? metrics.memory.total : 0} MB</span>
</p>
</div>
<div className="bg-[#111] border border-white/5 p-6 rounded-3xl relative overflow-hidden">
<div className="absolute -right-4 -bottom-4 opacity-5"><Database size={100} /></div>
<span className="text-[10px] uppercase tracking-widest text-[#86868B] block mb-2">Postgres Connection</span>
<div className="flex items-center gap-2">
<span className="relative flex h-3 w-3">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-3 w-3 bg-emerald-500"></span>
</span>
<p className="text-lg font-medium text-emerald-400">
{metrics ? metrics.dbStatus : "Connecting..."}
</p>
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{/* ZONA VERDE: EXPORTAR (BACKUP) */}
<div className="bg-[#111] border border-white/5 rounded-3xl p-8 flex flex-col">
<div className="flex items-center gap-3 text-emerald-400 mb-4">
<DownloadCloud size={24} />
<h3 className="text-xl font-medium">Data Snapshot</h3>
</div>
<p className="text-sm text-[#86868B] leading-relaxed mb-8 flex-1">
Download a complete JSON snapshot of your PostgreSQL database. This file contains all configurations, users, and content needed to perfectly clone or restore your environment via Docker.
</p>
<button
onClick={handleExport}
disabled={isExporting}
className="w-full bg-white text-black py-4 rounded-xl text-sm font-bold flex items-center justify-center gap-2 hover:bg-gray-200 transition-colors disabled:opacity-50"
>
{isExporting ? <><Loader2 size={18} className="animate-spin" /> Compiling Data...</> : "Download Secure Backup"}
</button>
</div>
{/* ZONA ROJA: RESTAURAR (DANGER ZONE) */}
<div className="bg-rose-500/5 border border-rose-500/20 rounded-3xl p-8 relative overflow-hidden">
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-transparent via-rose-500 to-transparent"></div>
{restoreSuccess ? (
<div className="flex flex-col items-center justify-center h-full text-center animate-in fade-in zoom-in duration-500">
<CheckCircle2 size={48} className="text-emerald-400 mb-4" />
<h3 className="text-2xl font-light text-white mb-2">Restoration Complete</h3>
<p className="text-[#86868B]">The database has been successfully overwritten. Rebooting session...</p>
</div>
) : (
<>
<div className="flex items-center gap-3 text-rose-500 mb-4">
<ShieldAlert size={24} />
<h3 className="text-xl font-medium">Disaster Recovery</h3>
</div>
<p className="text-xs text-rose-400/80 leading-relaxed mb-6">
<strong>WARNING:</strong> Uploading a snapshot will instantaneously erase all current database records and overwrite them. This process is irreversible.
</p>
<form onSubmit={handleRestore} className="space-y-4">
<div className="bg-black/40 border border-white/5 p-3 rounded-xl flex items-center justify-between">
<span className="text-xs text-[#86868B] truncate max-w-[200px]">
{selectedFile ? selectedFile.name : "No backup selected"}
</span>
<button type="button" onClick={() => fileInputRef.current?.click()} className="text-xs bg-white/10 hover:bg-white/20 text-white px-3 py-1.5 rounded-lg transition-colors">
<UploadCloud size={14} className="inline mr-1" /> Select JSON
</button>
<input type="file" accept=".json" ref={fileInputRef} onChange={e => setSelectedFile(e.target.files?.[0] || null)} className="hidden" />
</div>
<div className="grid grid-cols-2 gap-3">
<input required name="username" placeholder="Admin Username" className="w-full bg-black/60 border border-white/10 rounded-xl px-4 py-3 text-white text-sm outline-none focus:border-rose-500" />
<input required name="password" type="password" placeholder="Admin Password" className="w-full bg-black/60 border border-white/10 rounded-xl px-4 py-3 text-white text-sm outline-none focus:border-rose-500" />
</div>
<input required name="confirm" placeholder="Type CONFIRM-RESTORE" className="w-full bg-black/60 border border-rose-500/30 rounded-xl px-4 py-3 text-rose-400 font-mono text-sm text-center outline-none focus:border-rose-500" />
{restoreError && <div className="p-3 bg-rose-500/10 border border-rose-500/20 rounded-lg text-rose-400 text-xs text-center">{restoreError}</div>}
<button
type="submit"
disabled={isRestoring || !selectedFile}
className="w-full bg-rose-600 text-white py-4 rounded-xl text-sm font-bold flex items-center justify-center gap-2 hover:bg-rose-500 transition-colors disabled:opacity-50"
>
{isRestoring ? <><Loader2 size={18} className="animate-spin" /> Overwriting Database...</> : "Execute Destructive Restore"}
</button>
</form>
</>
)}
</div>
</div>
</div>
);
}