227 lines
6.5 KiB
TypeScript
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>
|
|
);
|
|
};
|