changed UI layout
new Navigation Dashboard
This commit is contained in:
1 parent
bef27416bf
commit
7eb9fdef18
5 files changed
+707
-179
No files matched your search
+127
-100
@@ -1,10 +1,23 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { AlertTriangle, CalendarClock, ClipboardList, DatabaseBackup, LogOut, Network, RefreshCw, Settings as SettingsIcon } from 'lucide-react';
|
||||
import {
|
||||
AlertTriangle,
|
||||
CalendarClock,
|
||||
ClipboardList,
|
||||
Database,
|
||||
DatabaseBackup,
|
||||
LogOut,
|
||||
Moon,
|
||||
Network,
|
||||
RefreshCw,
|
||||
Settings as SettingsIcon,
|
||||
Shield,
|
||||
} 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 { Operations } from './components/Operations.jsx';
|
||||
import { Repositories } from './components/Repositories.jsx';
|
||||
import { Scheduler } from './components/Scheduler.jsx';
|
||||
import { Settings } from './components/Settings.jsx';
|
||||
import { VMDetail } from './components/VMDetail.jsx';
|
||||
@@ -97,122 +110,136 @@ export default function App() {
|
||||
return <Login onLogin={loadSession} />;
|
||||
}
|
||||
|
||||
const pageTitle = currentVm
|
||||
? currentVm.name
|
||||
: {
|
||||
dashboard: 'Backups',
|
||||
nodes: 'Nodes',
|
||||
repositories: 'Repositories',
|
||||
operations: 'Operations',
|
||||
scheduler: 'Scheduler',
|
||||
settings: 'Settings',
|
||||
}[view] || 'Backups';
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-zinc-950 text-zinc-100">
|
||||
<header className="border-b border-zinc-800 bg-zinc-950/95">
|
||||
<div className="mx-auto flex max-w-7xl items-center justify-between gap-4 px-4 py-4 sm:px-6">
|
||||
<div className="grid min-h-screen lg:grid-cols-[260px_1fr]">
|
||||
<aside className="flex flex-col border-r border-zinc-800 bg-zinc-900/80">
|
||||
<button
|
||||
className="flex items-center gap-3 text-left"
|
||||
className="flex h-20 w-full items-center gap-3 border-b border-zinc-800 px-6 text-left"
|
||||
onClick={() => {
|
||||
setSelectedVm(null);
|
||||
setView('dashboard');
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<span className="flex h-9 w-9 items-center justify-center rounded-md border border-zinc-800 bg-zinc-900">
|
||||
<DatabaseBackup className="h-5 w-5 text-cyan-300" />
|
||||
<span className="flex h-9 w-9 items-center justify-center rounded-md text-red-400">
|
||||
<DatabaseBackup className="h-6 w-6" />
|
||||
</span>
|
||||
<span>
|
||||
<span className="block text-sm font-semibold tracking-wide text-zinc-100">Incus Backup Control</span>
|
||||
<span className="block text-xs text-zinc-500">ZFS block-level Restic operations</span>
|
||||
<span className="block text-base font-semibold tracking-wide text-zinc-100">Incus Backup</span>
|
||||
<span className="block text-xs text-zinc-500">Management UI</span>
|
||||
</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={() => {
|
||||
setSelectedVm(null);
|
||||
setView('scheduler');
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<CalendarClock className="h-4 w-4" />
|
||||
Scheduler
|
||||
</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={() => {
|
||||
setSelectedVm(null);
|
||||
setView('operations');
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<ClipboardList className="h-4 w-4" />
|
||||
Operations
|
||||
</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={() => {
|
||||
setSelectedVm(null);
|
||||
setView('settings');
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<SettingsIcon className="h-4 w-4" />
|
||||
Settings
|
||||
</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={refresh}
|
||||
type="button"
|
||||
>
|
||||
<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>
|
||||
|
||||
<nav className="space-y-1 px-4 py-6">
|
||||
<NavButton active={view === 'nodes' && !currentVm} icon={Network} label="Nodes" onClick={() => switchView('nodes')} />
|
||||
<NavButton active={view === 'repositories' && !currentVm} icon={Database} label="Repositories" onClick={() => switchView('repositories')} />
|
||||
<NavButton active={view === 'dashboard' || Boolean(currentVm)} icon={CalendarClock} label="Backups" onClick={() => switchView('dashboard')} />
|
||||
<NavButton active={view === 'operations' && !currentVm} icon={ClipboardList} label="Operations" onClick={() => switchView('operations')} />
|
||||
<NavButton active={view === 'scheduler' && !currentVm} icon={Shield} label="Scheduler" onClick={() => switchView('scheduler')} />
|
||||
<NavButton active={view === 'settings' && !currentVm} icon={SettingsIcon} label="Settings" onClick={() => switchView('settings')} />
|
||||
</nav>
|
||||
|
||||
<div className="mt-auto hidden border-t border-zinc-800 px-6 py-4 text-xs text-zinc-500 lg:block">
|
||||
<div>v0.1.0</div>
|
||||
<div className="mt-2 text-red-400">Management</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</aside>
|
||||
|
||||
{error ? (
|
||||
<div className="mx-auto mt-4 flex max-w-7xl items-center gap-2 px-4 text-sm text-amber-300 sm:px-6">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
{error}
|
||||
</div>
|
||||
) : null}
|
||||
<section className="min-w-0">
|
||||
<header className="flex h-20 items-center justify-between border-b border-zinc-800 bg-zinc-900/80 px-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<button className="hidden h-9 w-9 items-center justify-center rounded-md border border-zinc-800 text-zinc-400 lg:inline-flex" type="button">
|
||||
<DatabaseBackup className="h-4 w-4" />
|
||||
</button>
|
||||
<h1 className="text-lg font-semibold tracking-wide text-zinc-100">{pageTitle}</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 rounded-full border border-zinc-800 bg-zinc-950 px-3 py-2">
|
||||
<span className="px-2 text-sm font-medium text-zinc-200">{session.user?.username || 'admin'}</span>
|
||||
<button className="flex h-7 w-7 items-center justify-center rounded-full text-zinc-400 hover:text-zinc-100" onClick={refresh} title="Refresh" type="button">
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
</button>
|
||||
<button className="flex h-7 w-7 items-center justify-center rounded-full text-zinc-400 hover:text-zinc-100" title="Theme" type="button">
|
||||
<Moon className="h-4 w-4" />
|
||||
</button>
|
||||
<button className="flex h-7 w-7 items-center justify-center rounded-full text-zinc-400 hover:text-zinc-100" onClick={logout} title="Logout" type="button">
|
||||
<LogOut className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="mx-auto max-w-7xl px-4 py-6 sm:px-6">
|
||||
{view === 'nodes' ? (
|
||||
<Nodes nodes={nodes} onChanged={refresh} />
|
||||
) : view === 'operations' ? (
|
||||
<Operations />
|
||||
) : view === 'settings' ? (
|
||||
<Settings nodes={nodes} onChanged={refresh} />
|
||||
) : view === 'scheduler' ? (
|
||||
<Scheduler onChanged={refresh} schedules={schedules} vms={vms} />
|
||||
) : currentVm ? (
|
||||
<VMDetail vm={currentVm} jobs={jobs} onBack={() => setSelectedVm(null)} onChanged={refresh} />
|
||||
) : (
|
||||
<Dashboard
|
||||
health={health}
|
||||
jobs={jobs}
|
||||
loading={loading}
|
||||
onManage={(vmName) => {
|
||||
setSelectedVm(vmName);
|
||||
setView('dashboard');
|
||||
}}
|
||||
vms={vms}
|
||||
/>
|
||||
)}
|
||||
<div className="app-grid-bg min-h-[calc(100vh-5rem)] px-5 py-7 sm:px-8">
|
||||
{error ? (
|
||||
<div className="mb-5 flex items-center gap-2 text-sm text-amber-300">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
{error}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="mx-auto max-w-[1500px]">
|
||||
{view === 'nodes' ? (
|
||||
<Nodes nodes={nodes} onChanged={refresh} />
|
||||
) : view === 'repositories' ? (
|
||||
<Repositories nodes={nodes} onChanged={refresh} />
|
||||
) : view === 'operations' ? (
|
||||
<Operations />
|
||||
) : view === 'settings' ? (
|
||||
<Settings nodes={nodes} onChanged={refresh} />
|
||||
) : view === 'scheduler' ? (
|
||||
<Scheduler onChanged={refresh} schedules={schedules} vms={vms} />
|
||||
) : currentVm ? (
|
||||
<VMDetail vm={currentVm} jobs={jobs} onBack={() => setSelectedVm(null)} onChanged={refresh} />
|
||||
) : (
|
||||
<Dashboard
|
||||
health={health}
|
||||
jobs={jobs}
|
||||
loading={loading}
|
||||
onCreateSchedule={() => switchView('scheduler')}
|
||||
onManage={(vmName) => {
|
||||
setSelectedVm(vmName);
|
||||
setView('dashboard');
|
||||
}}
|
||||
schedules={schedules}
|
||||
vms={vms}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
function switchView(nextView) {
|
||||
setSelectedVm(null);
|
||||
setView(nextView);
|
||||
}
|
||||
}
|
||||
|
||||
function NavButton({ active, icon: Icon, label, onClick }) {
|
||||
return (
|
||||
<button
|
||||
className={`flex h-11 w-full items-center gap-3 rounded-md px-3 text-left text-sm font-semibold transition ${
|
||||
active
|
||||
? 'border-l-4 border-red-500 bg-red-950/40 text-zinc-100'
|
||||
: 'text-zinc-400 hover:bg-zinc-800/70 hover:text-zinc-100'
|
||||
}`}
|
||||
onClick={onClick}
|
||||
type="button"
|
||||
>
|
||||
<Icon className={`h-4 w-4 ${active ? 'text-red-400' : 'text-zinc-500'}`} />
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user