"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 { Badge } from "@/components/ui/badge"; import { DataTable, Column } from "@/components/data-table"; import type { CloudflareStats, AuthentikStats, GiteaActivity } from "@/lib/types"; 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; } const nodeColors: Record = { atlantis: "text-blue-400", calypso: "text-violet-400", olares: "text-emerald-400", nuc: "text-amber-400", rpi5: "text-cyan-400", "homelab-vm": "text-green-400", "matrix-ubuntu": "text-pink-400", guava: "text-orange-400", seattle: "text-teal-400", jellyfish: "text-indigo-400", }; function getNodeColor(name: string): string { const lower = name.toLowerCase(); for (const [key, cls] of Object.entries(nodeColors)) { if (lower.includes(key)) return cls; } return "text-foreground"; } function formatTime(ts: string): string { try { return new Date(ts).toLocaleString("en-US", { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); } catch { return ts; } } const dnsTypeColors: Record = { A: "text-blue-400", AAAA: "text-violet-400", CNAME: "text-cyan-400", MX: "text-amber-400", TXT: "text-green-400", SRV: "text-pink-400", NS: "text-teal-400", }; export default function NetworkPage() { const { data: adguard } = usePoll("/api/network/adguard", 60000); const { data: nodesRaw } = usePoll("/api/network/headscale", 30000); const { data: rewritesRaw } = usePoll("/api/network/adguard/rewrites", 120000); const { data: cloudflare } = usePoll("/api/network/cloudflare", 120000); const { data: authentik } = usePoll("/api/network/authentik", 60000); const { data: gitea } = usePoll("/api/network/gitea", 60000); const nodes = Array.isArray(nodesRaw) ? nodesRaw : (nodesRaw?.nodes ?? []); const rewrites = Array.isArray(rewritesRaw) ? rewritesRaw : (rewritesRaw?.rewrites ?? []); const rewriteColumns: Column[] = [ { key: "domain", label: "Domain", render: (row) => {row.domain}, }, { key: "answer", label: "Answer", render: (row) => {row.answer}, }, ]; return (

Network

{/* Top row: AdGuard stats */}
{ const v = adguard?.total_queries ?? adguard?.num_dns_queries; return v != null ? v.toLocaleString() : "\u2014"; })()} sub="DNS queries" /> { const v = adguard?.blocked ?? adguard?.num_blocked_filtering; return v != null ? v.toLocaleString() : "\u2014"; })()} sub="blocked by filters" /> { const v = adguard?.avg_time ?? adguard?.avg_processing_time; return v != null ? `${(v * 1000).toFixed(1)}ms` : "\u2014"; })()} sub="processing time" />
{/* Middle: Headscale nodes grid */} Headscale Nodes {nodes.length === 0 ? (

Loading...

) : (
{nodes.map((node) => (
{node.name}

{node.ip_addresses?.[0] ?? node.ip ?? "\u2014"}

{node.last_seen && (

{new Date(node.last_seen).toLocaleString("en-US", { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", })}

)}
))}
)}
{/* Cloudflare, Authentik, Gitea -- 3 columns */}
{/* Cloudflare DNS */} Cloudflare DNS {cloudflare && ( {cloudflare.total} records )} {!cloudflare ? (

Loading...

) : (

{cloudflare.proxied}

Proxied

{cloudflare.dns_only}

DNS Only

Record Types

{Object.entries(cloudflare.types).map(([type, count]) => ( {type}: {count} ))}
)}
{/* Authentik SSO */} Authentik SSO {authentik && ( {authentik.active_sessions} sessions )} {!authentik ? (

Loading...

) : (
{authentik.recent_events.length > 0 && (

Recent Events

{authentik.recent_events.slice(0, 6).map((evt, i) => (
{evt.user && ( {evt.user} )}
{formatTime(evt.timestamp)}
))}
)}
)}
{/* Gitea Activity */} Gitea {gitea && gitea.open_prs.length > 0 && ( {gitea.open_prs.length} open PR{gitea.open_prs.length !== 1 ? "s" : ""} )} {!gitea ? (

Loading...

) : (
{gitea.commits.length > 0 && (

Recent Commits

{gitea.commits.slice(0, 6).map((c, i) => (
{c.sha.slice(0, 7)} {c.message.split("\n")[0]}
{c.author} {formatTime(c.date)}
))}
)} {gitea.open_prs.length > 0 && (

Open PRs

{gitea.open_prs.map((pr, i) => (
#{pr.number} {pr.title}
{pr.author}
))}
)}
)}
{/* Bottom: DNS rewrites table */} DNS Rewrites data={rewrites} columns={rewriteColumns} searchKey="domain" />
); }