123 lines
4.4 KiB
TypeScript
123 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import { usePoll } from "@/lib/use-poll";
|
|
import { StatCard } from "@/components/stat-card";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { StatusBadge } from "@/components/status-badge";
|
|
import { DataTable, Column } from "@/components/data-table";
|
|
|
|
interface AdGuardStats {
|
|
total_queries?: number;
|
|
num_dns_queries?: number;
|
|
blocked?: number;
|
|
num_blocked_filtering?: number;
|
|
avg_time?: number;
|
|
avg_processing_time?: number;
|
|
}
|
|
|
|
interface HeadscaleNode {
|
|
id: string;
|
|
name: string;
|
|
ip_addresses?: string[];
|
|
ip?: string;
|
|
online: boolean;
|
|
last_seen?: string;
|
|
}
|
|
|
|
interface DnsRewrite {
|
|
domain: string;
|
|
answer: string;
|
|
}
|
|
|
|
export default function NetworkPage() {
|
|
const { data: adguard } = usePoll<AdGuardStats>("/api/network/adguard", 60000);
|
|
const { data: nodesRaw } = usePoll<HeadscaleNode[] | { nodes: HeadscaleNode[] }>("/api/network/headscale", 30000);
|
|
const { data: rewritesRaw } = usePoll<DnsRewrite[] | { rewrites: DnsRewrite[] }>("/api/network/adguard/rewrites", 120000);
|
|
|
|
const nodes = Array.isArray(nodesRaw) ? nodesRaw : (nodesRaw?.nodes ?? []);
|
|
const rewrites = Array.isArray(rewritesRaw) ? rewritesRaw : (rewritesRaw?.rewrites ?? []);
|
|
|
|
const rewriteColumns: Column<DnsRewrite>[] = [
|
|
{
|
|
key: "domain",
|
|
label: "Domain",
|
|
render: (row) => <span className="font-medium text-foreground">{row.domain}</span>,
|
|
},
|
|
{ key: "answer", label: "Answer" },
|
|
];
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
<h1 className="text-lg font-semibold">Network</h1>
|
|
|
|
{/* Top row: AdGuard stats */}
|
|
<div className="grid grid-cols-1 sm:grid-cols-3 lg:grid-cols-3 gap-5">
|
|
<StatCard
|
|
label="Total Queries"
|
|
value={(() => { const v = adguard?.total_queries ?? adguard?.num_dns_queries; return v != null ? v.toLocaleString() : "\u2014"; })()}
|
|
sub="DNS queries"
|
|
/>
|
|
<StatCard
|
|
label="Blocked"
|
|
value={(() => { const v = adguard?.blocked ?? adguard?.num_blocked_filtering; return v != null ? v.toLocaleString() : "\u2014"; })()}
|
|
sub="blocked by filters"
|
|
/>
|
|
<StatCard
|
|
label="Avg Response"
|
|
value={(() => { const v = adguard?.avg_time ?? adguard?.avg_processing_time; return v != null ? `${(v * 1000).toFixed(1)}ms` : "\u2014"; })()}
|
|
sub="processing time"
|
|
/>
|
|
</div>
|
|
|
|
{/* Middle: Headscale nodes grid */}
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium">Headscale Nodes</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{nodes.length === 0 ? (
|
|
<p className="text-xs text-muted-foreground">Loading...</p>
|
|
) : (
|
|
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
|
|
{nodes.map((node) => (
|
|
<Card key={node.id} className="overflow-hidden">
|
|
<CardContent className="pt-3 pb-3 px-4">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<span className={`w-2 h-2 rounded-full shrink-0 ${node.online ? "bg-green-500 glow-green" : "bg-red-500 glow-red"}`} />
|
|
<span className="text-sm font-medium text-foreground truncate">{node.name}</span>
|
|
</div>
|
|
<p className="text-[10px] text-muted-foreground/70 font-mono">
|
|
{node.ip_addresses?.[0] ?? node.ip ?? "\u2014"}
|
|
</p>
|
|
{node.last_seen && (
|
|
<p className="text-[10px] text-muted-foreground/60 mt-0.5">
|
|
{new Date(node.last_seen).toLocaleString("en-US", {
|
|
month: "short", day: "numeric", hour: "2-digit", minute: "2-digit",
|
|
})}
|
|
</p>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Bottom: DNS rewrites table */}
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium">DNS Rewrites</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<DataTable<DnsRewrite>
|
|
data={rewrites}
|
|
columns={rewriteColumns}
|
|
searchKey="domain"
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|