Sanitized mirror from private repository - 2026-04-05 05:34:18 UTC
This commit is contained in:
122
dashboard/ui/app/network/page.tsx
Normal file
122
dashboard/ui/app/network/page.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
"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-6">
|
||||
<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-3 lg:gap-4">
|
||||
<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-3">
|
||||
{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 font-mono">
|
||||
{node.ip_addresses?.[0] ?? node.ip ?? "—"}
|
||||
</p>
|
||||
{node.last_seen && (
|
||||
<p className="text-[10px] text-muted-foreground 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user