Files
OnEverDrive/frontend/src/App.tsx
T

227 lines
6.5 KiB
TypeScript

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 { SettingsView } from './pages/SettingsView';
import { ActiveUpload } from './components/LiveTransferMeter';
import {
api,
DashboardStats,
ClientItem,
BackupJobItem,
EventLogItem,
getAuthToken,
getCurrentUser,
setAuthToken,
SystemSettingsResponse
} 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 [settings, setSettings] = useState<SystemSettingsResponse | null>(null);
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, settingsRes] = await Promise.all([
api.getStats().catch(() => null),
api.getClients().catch(() => []),
api.getJobs().catch(() => []),
api.getEvents(50).catch(() => []),
api.getSettings().catch(() => null),
]);
if (statsRes) setStats(statsRes);
setClients(clientsRes);
setJobs(jobsRes);
setEvents(eventsRes);
if (settingsRes) setSettings(settingsRes);
} 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';
case 'settings':
return 'Configuración Global';
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}
settings={settings}
/>
)}
{currentTab === 'restore' && (
<RestoreView
clients={clients}
/>
)}
{currentTab === 'events' && (
<EventsView
events={events}
/>
)}
{currentTab === 'settings' && (
<SettingsView
onRefresh={loadData}
/>
)}
</main>
</div>
</div>
);
};