Files
lr-openwrt-tool/src/app/routes.tsx
T
2026-05-11 15:59:28 +01:00

216 lines
4.7 KiB
TypeScript

import { useEffect, useMemo, useState } from 'react';
import {
Clock,
Database,
Server,
ShieldCheck,
} from 'lucide-react';
import { TopBar } from '@/components/layout/TopBar';
import { MetricCard } from '@/components/dashboard/MetricCard';
import { IpPoolChart } from '@/components/dashboard/IpPoolChart';
import { NetworkTrafficChart } from '@/components/dashboard/NetworkTrafficChart';
import { ProvisioningWizard } from '@/components/provisioning/ProvisioningWizard';
import { BackendSettings } from '@/components/settings/BackendSettings';
import { ActivityLogs } from '@/components/activity/ActivityLogs';
import { Card } from '@/components/ui/Card';
import { vpnApi } from '@/services/vpnApi';
import { vpsApi } from '@/services/vpsApi';
import type {
UsedIpsResponse,
VpsHealth,
} from '@/types/api';
export function DashboardRoute() {
const [health, setHealth] =
useState<VpsHealth | null>(null);
const [usedIps, setUsedIps] =
useState<UsedIpsResponse | null>(null);
const [error, setError] = useState('');
useEffect(() => {
async function loadDashboard() {
try {
const [
healthResponse,
usedIpsResponse,
] = await Promise.all([
vpsApi.health(),
vpnApi.usedIps(),
]);
setHealth(healthResponse);
setUsedIps(usedIpsResponse);
setError('');
} catch (err) {
setHealth(null);
setUsedIps(null);
setError(String(err));
}
}
loadDashboard();
const intervalId = window.setInterval(
loadDashboard,
15000,
);
return () => {
window.clearInterval(intervalId);
};
}, []);
const ipPoolTotal =
health?.ipPoolTotal ?? 65534;
const usedCount =
health?.ipPoolUsed ??
usedIps?.count ??
0;
const ipPoolPercent = useMemo(() => {
return (
(usedCount / ipPoolTotal) *
100
).toFixed(2);
}, [usedCount, ipPoolTotal]);
const backendHealthy =
health?.backend === true;
const vpnHealthy =
health?.wireGuardRunning === true;
const dashboardReady =
!error &&
Boolean(health) &&
backendHealthy &&
vpnHealthy;
return (
<div className="flex h-full flex-col overflow-hidden">
<TopBar healthy={dashboardReady} />
{error && (
<div className="mb-3 rounded-xl border border-red-500/20 bg-red-500/10 p-3 text-sm text-red-300">
Dashboard backend error:{' '}
{error}
</div>
)}
<div className="grid grid-cols-4 gap-4">
<MetricCard
title="VPN Status"
value={
vpnHealthy
? 'Connected'
: 'Offline'
}
subtitle={
health?.wireGuardInterface
? `${health.wireGuardInterface} interface`
: 'WireGuard wg0 status'
}
icon={<ShieldCheck />}
/>
<MetricCard
title="IP Pool Usage"
value={`${ipPoolPercent}%`}
subtitle={`${usedCount} / ${ipPoolTotal} IPs used`}
icon={<Database />}
/>
<MetricCard
title="VPS Uptime"
value={
health?.systemUptime ??
'Unknown'
}
subtitle="Reported by VPS health"
icon={<Clock />}
/>
<MetricCard
title="Backend Health"
value={
backendHealthy
? 'Healthy'
: 'Offline'
}
subtitle="API connectivity"
icon={<Server />}
/>
</div>
<div className="mt-4 grid min-h-0 flex-1 grid-cols-12 gap-4 overflow-hidden pb-4">
<div className="col-span-9 min-h-0">
<NetworkTrafficChart />
</div>
<div className="col-span-3 min-h-0">
<IpPoolChart
used={usedCount}
total={ipPoolTotal}
/>
</div>
</div>
</div>
);
}
export function PlaceholderRoute({
name,
}: {
name: string;
}) {
return (
<Card>
<h2 className="text-2xl font-bold">
{name}
</h2>
<p className="mt-2 text-slate-400">
Screen scaffold ready for production
implementation.
</p>
</Card>
);
}
export function RouteView({
active,
}: {
active: string;
}) {
if (active === 'Dashboard') {
return <DashboardRoute />;
}
if (active === 'Provisioning') {
return <ProvisioningWizard />;
}
if (active === 'UDP2RAW Config') {
return (
<PlaceholderRoute name="UDP2RAW Config" />
);
}
if (active === 'Activity Logs') {
return <ActivityLogs />;
}
if (active === 'Workstation') {
return <BackendSettings />;
}
return <PlaceholderRoute name={active} />;
}