feat: Initial commit for OnEver Drive centralized backup system with Windows PyQt6 agent and Proxmox LXC deployment
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Sidebar } from './components/Sidebar';
|
||||
import { Topbar } from './components/Topbar';
|
||||
import { DashboardView } from './pages/DashboardView';
|
||||
import { ClientsView } from './pages/ClientsView';
|
||||
import { JobsView } from './pages/JobsView';
|
||||
import { RestoreView } from './pages/RestoreView';
|
||||
import { EventsView } from './pages/EventsView';
|
||||
import { LoginView } from './pages/LoginView';
|
||||
import { ActiveUpload } from './components/LiveTransferMeter';
|
||||
import {
|
||||
api,
|
||||
DashboardStats,
|
||||
ClientItem,
|
||||
BackupJobItem,
|
||||
EventLogItem,
|
||||
getAuthToken,
|
||||
getCurrentUser,
|
||||
setAuthToken
|
||||
} from './services/api';
|
||||
import { wsClient } from './services/websocket';
|
||||
|
||||
export const App: React.FC = () => {
|
||||
const [currentUser, setCurrentUser] = useState<any | null>(getCurrentUser());
|
||||
const [authToken, setTokenState] = useState<string | null>(getAuthToken());
|
||||
|
||||
const [currentTab, setCurrentTab] = useState<string>('dashboard');
|
||||
const [isWsConnected, setIsWsConnected] = useState<boolean>(false);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const [stats, setStats] = useState<DashboardStats | null>(null);
|
||||
const [clients, setClients] = useState<ClientItem[]>([]);
|
||||
const [jobs, setJobs] = useState<BackupJobItem[]>([]);
|
||||
const [events, setEvents] = useState<EventLogItem[]>([]);
|
||||
const [activeUploads, setActiveUploads] = useState<ActiveUpload[]>([]);
|
||||
|
||||
const handleLoginSuccess = (user: any) => {
|
||||
setCurrentUser(user);
|
||||
setTokenState(getAuthToken());
|
||||
loadData();
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
setAuthToken(null);
|
||||
setCurrentUser(null);
|
||||
setTokenState(null);
|
||||
setStats(null);
|
||||
setClients([]);
|
||||
setJobs([]);
|
||||
setEvents([]);
|
||||
};
|
||||
|
||||
const loadData = async () => {
|
||||
if (!getAuthToken()) return;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const [statsRes, clientsRes, jobsRes, eventsRes] = await Promise.all([
|
||||
api.getStats().catch(() => null),
|
||||
api.getClients().catch(() => []),
|
||||
api.getJobs().catch(() => []),
|
||||
api.getEvents(50).catch(() => []),
|
||||
]);
|
||||
|
||||
if (statsRes) setStats(statsRes);
|
||||
setClients(clientsRes);
|
||||
setJobs(jobsRes);
|
||||
setEvents(eventsRes);
|
||||
} catch (err) {
|
||||
console.error('Error loading dashboard data:', err);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleUnauthorized = () => {
|
||||
handleLogout();
|
||||
};
|
||||
|
||||
window.addEventListener('oed_unauthorized', handleUnauthorized);
|
||||
|
||||
if (authToken) {
|
||||
loadData();
|
||||
wsClient.connect();
|
||||
|
||||
const unsubscribe = wsClient.subscribe((evt) => {
|
||||
if (evt.type === 'WS_CONNECTED') {
|
||||
setIsWsConnected(true);
|
||||
} else if (evt.type === 'WS_DISCONNECTED') {
|
||||
setIsWsConnected(false);
|
||||
} else if (evt.type === 'UPLOAD_PROGRESS') {
|
||||
const upload = evt.data as ActiveUpload;
|
||||
setActiveUploads((prev) => {
|
||||
const index = prev.findIndex((u) => u.session_code === upload.session_code);
|
||||
if (index >= 0) {
|
||||
const next = [...prev];
|
||||
next[index] = { ...next[index], ...upload };
|
||||
return next;
|
||||
}
|
||||
return [upload, ...prev];
|
||||
});
|
||||
} else if (evt.type === 'UPLOAD_COMPLETED') {
|
||||
const sessionCode = evt.data.session_code;
|
||||
setTimeout(() => {
|
||||
setActiveUploads((prev) => prev.filter((u) => u.session_code !== sessionCode));
|
||||
loadData();
|
||||
}, 2000);
|
||||
} else if (evt.type === 'EVENT_LOG') {
|
||||
setEvents((prev) => [evt.data, ...prev.slice(0, 49)]);
|
||||
} else if (evt.type === 'CLIENT_REGISTERED' || evt.type === 'CLIENT_HEARTBEAT') {
|
||||
api.getClients().then(setClients).catch(() => {});
|
||||
}
|
||||
});
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (getAuthToken()) {
|
||||
api.getStats().then((s) => s && setStats(s)).catch(() => {});
|
||||
}
|
||||
}, 15000);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
clearInterval(interval);
|
||||
window.removeEventListener('oed_unauthorized', handleUnauthorized);
|
||||
};
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('oed_unauthorized', handleUnauthorized);
|
||||
};
|
||||
}, [authToken]);
|
||||
|
||||
// If not authenticated, render Login Screen
|
||||
if (!authToken || !currentUser) {
|
||||
return <LoginView onLoginSuccess={handleLoginSuccess} />;
|
||||
}
|
||||
|
||||
const getTabTitle = () => {
|
||||
switch (currentTab) {
|
||||
case 'dashboard':
|
||||
return 'Panel Principal & Telemetría';
|
||||
case 'clients':
|
||||
return 'Clientes Windows';
|
||||
case 'jobs':
|
||||
return 'Trabajos de Backup';
|
||||
case 'restore':
|
||||
return 'Explorador & Restore';
|
||||
case 'events':
|
||||
return 'Auditoría & Logs';
|
||||
default:
|
||||
return 'OnEver Drive';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="app-layout">
|
||||
<Sidebar
|
||||
currentTab={currentTab}
|
||||
setCurrentTab={setCurrentTab}
|
||||
isWsConnected={isWsConnected}
|
||||
/>
|
||||
|
||||
<div className="main-wrapper">
|
||||
<Topbar
|
||||
title={getTabTitle()}
|
||||
user={currentUser}
|
||||
onRefresh={loadData}
|
||||
onLogout={handleLogout}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
|
||||
<main className="content-scrollable">
|
||||
{currentTab === 'dashboard' && (
|
||||
<DashboardView
|
||||
stats={stats}
|
||||
events={events}
|
||||
activeUploads={activeUploads}
|
||||
onNavigateToClients={() => setCurrentTab('clients')}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentTab === 'clients' && (
|
||||
<ClientsView
|
||||
clients={clients}
|
||||
onRefresh={loadData}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentTab === 'jobs' && (
|
||||
<JobsView
|
||||
jobs={jobs}
|
||||
clients={clients}
|
||||
onRefresh={loadData}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentTab === 'restore' && (
|
||||
<RestoreView
|
||||
clients={clients}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentTab === 'events' && (
|
||||
<EventsView
|
||||
events={events}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user