Builded new structure.

Now with remote setup and management Server
more than one Node can be connected
added user auth
This commit is contained in:
Philipp committed 2026-05-21 09:51:07 +02:00
1 parent 4c98d14352
commit aae54167a1
25 files changed
+1942 -52

No files matched your search

+68 -7
View File
@@ -1,7 +1,9 @@
import { useEffect, useMemo, useState } from 'react';
import { AlertTriangle, CalendarClock, DatabaseBackup, RefreshCw, Settings as SettingsIcon } from 'lucide-react';
import { AlertTriangle, CalendarClock, DatabaseBackup, LogOut, Network, RefreshCw, Settings as SettingsIcon } from 'lucide-react';
import { api, errorMessage } from './api.js';
import { Dashboard } from './components/Dashboard.jsx';
import { Login } from './components/Login.jsx';
import { Nodes } from './components/Nodes.jsx';
import { Scheduler } from './components/Scheduler.jsx';
import { Settings } from './components/Settings.jsx';
import { VMDetail } from './components/VMDetail.jsx';
@@ -11,7 +13,10 @@ export default function App() {
const [vms, setVms] = useState([]);
const [jobs, setJobs] = useState([]);
const [schedules, setSchedules] = useState([]);
const [nodes, setNodes] = useState([]);
const [selectedVm, setSelectedVm] = useState(null);
const [session, setSession] = useState(null);
const [sessionLoading, setSessionLoading] = useState(true);
const [view, setView] = useState('dashboard');
const [error, setError] = useState('');
const [loading, setLoading] = useState(true);
@@ -19,11 +24,12 @@ export default function App() {
async function refresh() {
setError('');
try {
const [healthResult, vmsResult, jobsResult, schedulesResult] = await Promise.allSettled([
const [healthResult, vmsResult, jobsResult, schedulesResult, nodesResult] = await Promise.allSettled([
api.get('/health'),
api.get('/vms'),
api.get('/jobs'),
api.get('/schedules'),
api.get('/nodes'),
]);
if (healthResult.status === 'fulfilled') {
@@ -35,8 +41,9 @@ export default function App() {
if (vmsResult.status === 'fulfilled') setVms(vmsResult.value.data);
if (jobsResult.status === 'fulfilled') setJobs(jobsResult.value.data);
if (schedulesResult.status === 'fulfilled') setSchedules(schedulesResult.value.data);
if (nodesResult.status === 'fulfilled') setNodes(nodesResult.value.data);
const firstFailure = [healthResult, vmsResult, jobsResult, schedulesResult].find((result) => result.status === 'rejected');
const firstFailure = [healthResult, vmsResult, jobsResult, schedulesResult, nodesResult].find((result) => result.status === 'rejected');
if (firstFailure) setError(errorMessage(firstFailure.reason));
} catch (requestError) {
setError(errorMessage(requestError));
@@ -46,16 +53,49 @@ export default function App() {
}
useEffect(() => {
loadSession();
}, []);
useEffect(() => {
if (!session?.authenticated) return undefined;
refresh();
const timer = window.setInterval(refresh, 5000);
return () => window.clearInterval(timer);
}, []);
}, [session?.authenticated]);
const currentVm = useMemo(
() => vms.find((vm) => vm.name === selectedVm) || null,
() => vms.find((vm) => vm.id === selectedVm) || null,
[selectedVm, vms],
);
async function loadSession() {
try {
const result = await api.get('/auth/session');
setSession(result.data);
} catch {
setSession({ authenticated: false, user: null });
} finally {
setSessionLoading(false);
}
}
async function logout() {
await api.post('/auth/logout');
setSession({ authenticated: false, user: null });
setVms([]);
setJobs([]);
setSchedules([]);
setNodes([]);
}
if (sessionLoading) {
return <main className="min-h-screen bg-zinc-950 text-zinc-100" />;
}
if (!session?.authenticated) {
return <Login onLogin={loadSession} />;
}
return (
<main className="min-h-screen bg-zinc-950 text-zinc-100">
<header className="border-b border-zinc-800 bg-zinc-950/95">
@@ -77,6 +117,17 @@ export default function App() {
</span>
</button>
<div className="flex items-center gap-2">
<button
className="inline-flex h-9 items-center gap-2 rounded-md border border-zinc-800 bg-zinc-900 px-3 text-sm text-zinc-200 hover:border-zinc-700"
onClick={() => {
setSelectedVm(null);
setView('nodes');
}}
type="button"
>
<Network className="h-4 w-4" />
Nodes
</button>
<button
className="inline-flex h-9 items-center gap-2 rounded-md border border-zinc-800 bg-zinc-900 px-3 text-sm text-zinc-200 hover:border-zinc-700"
onClick={() => {
@@ -107,6 +158,14 @@ export default function App() {
<RefreshCw className="h-4 w-4" />
Refresh
</button>
<button
className="inline-flex h-9 items-center gap-2 rounded-md border border-zinc-800 bg-zinc-900 px-3 text-sm text-zinc-200 hover:border-zinc-700"
onClick={logout}
type="button"
>
<LogOut className="h-4 w-4" />
Logout
</button>
</div>
</div>
</header>
@@ -119,8 +178,10 @@ export default function App() {
) : null}
<div className="mx-auto max-w-7xl px-4 py-6 sm:px-6">
{view === 'settings' ? (
<Settings onChanged={refresh} />
{view === 'nodes' ? (
<Nodes nodes={nodes} onChanged={refresh} />
) : view === 'settings' ? (
<Settings nodes={nodes} onChanged={refresh} />
) : view === 'scheduler' ? (
<Scheduler onChanged={refresh} schedules={schedules} vms={vms} />
) : currentVm ? (