add Endpoint table in the details

This commit is contained in:
lda
2026-05-27 02:17:40 +07:00 Verified
parent ba95257309
commit 69b5313005
6 changed files with 119 additions and 9 deletions
+17
View File
@@ -103,6 +103,20 @@ export type FleetDeviceAgent = {
last_seen_unix: number;
};
export type FleetDeviceEndpoint = {
agent_id: string;
nickname?: string | null;
connected: boolean;
source: string;
mac: string | null;
ip: string | null;
hostname: string | null;
interface: string | null;
presence: string;
first_seen_unix: number | null;
last_seen_unix: number | null;
};
export type FleetWakeRoute = {
route_id: string;
agent_id: string;
@@ -111,7 +125,9 @@ export type FleetWakeRoute = {
mac: string | null;
ip: string | null;
hostname: string | null;
interface: string | null;
source: string;
presence: string;
last_seen_unix: number;
wakeable: boolean;
};
@@ -126,6 +142,7 @@ export type FleetDevice = {
hostnames: string[];
agents: FleetDeviceAgent[];
sources: string[];
endpoints: FleetDeviceEndpoint[];
first_seen_unix: number | null;
last_seen_unix: number | null;
presence: string;
@@ -1,5 +1,5 @@
import { useMemo, useState } from "react";
import { Minus, Plus, Zap } from "lucide-react";
import { Copy, Minus, Plus, Zap } from "lucide-react";
import { toast } from "sonner";
import {
@@ -8,6 +8,7 @@ import {
detachDeviceIdentifier,
mergeKnownDevice,
type FleetDevice,
type FleetDeviceEndpoint,
type KnownDevice,
} from "@/api";
import { Badge } from "@/components/ui/badge";
@@ -29,7 +30,12 @@ import {
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { DetailBlock, PresenceBadge } from "@/pages/fleet/FleetComponents";
import { agentLabel, identifiersFor, routeLabel } from "@/pages/fleet/utils";
import {
agentLabel,
formatSeen,
identifiersFor,
routeLabel,
} from "@/pages/fleet/utils";
type Props = {
device: FleetDevice | null;
@@ -225,6 +231,30 @@ export function FleetDeviceDetailsDialog({
<Separator />
<div className="grid gap-2">
<div className="flex items-center justify-between gap-2">
<h3 className="text-sm font-medium">Endpoints</h3>
<Badge variant="outline">{device.endpoints.length}</Badge>
</div>
{device.endpoints.length ? (
<div className="divide-y overflow-hidden rounded-md border">
{device.endpoints.map((endpoint, index) => (
<EndpointRow
key={`${endpoint.agent_id}:${endpoint.source}:${endpoint.mac ?? ""}:${endpoint.ip ?? ""}:${index}`}
endpoint={endpoint}
onCopy={onCopy}
/>
))}
</div>
) : (
<p className="rounded-md border bg-muted/30 p-3 text-sm text-muted-foreground">
No endpoint evidence reported.
</p>
)}
</div>
<Separator />
{/* ── Wake route ── */}
<div className="grid gap-2">
<div className="flex items-center justify-between gap-2">
@@ -513,3 +543,52 @@ export function FleetDeviceDetailsDialog({
</Dialog>
);
}
function EndpointRow({
endpoint,
onCopy,
}: {
endpoint: FleetDeviceEndpoint;
onCopy: (label: string, value: string) => void;
}) {
const target = [endpoint.mac, endpoint.ip].filter(Boolean).join(" / ");
const location = [endpoint.interface, endpoint.hostname]
.filter(Boolean)
.join(" / ");
return (
<div className="grid gap-2 p-3 text-sm sm:grid-cols-[minmax(0,1.35fr)_minmax(0,1fr)_auto] sm:items-center">
<div className="min-w-0">
<div className="flex min-w-0 flex-wrap items-center gap-1.5">
<PresenceBadge presence={endpoint.presence} />
<Badge variant="secondary">{endpoint.source.replace("_", " ")}</Badge>
</div>
<p className="mt-1 truncate text-xs text-muted-foreground">
{agentLabel(endpoint.agent_id, endpoint.nickname)}
{endpoint.connected ? " (connected)" : " (offline)"}
</p>
</div>
<div className="min-w-0">
<p className="truncate font-mono text-xs">{target || "-"}</p>
<p className="truncate text-xs text-muted-foreground">
{location || "No interface or hostname"}
</p>
</div>
<div className="flex items-center justify-between gap-2 sm:justify-end">
<span className="text-xs text-muted-foreground">
{formatSeen(endpoint.last_seen_unix)}
</span>
{target && (
<Button
variant="ghost"
size="icon-sm"
onClick={() => onCopy("Endpoint", target)}
aria-label={`Copy endpoint ${target}`}
>
<Copy className="size-3.5" />
</Button>
)}
</div>
</div>
);
}
+4 -4
View File
@@ -14,8 +14,8 @@ export const presenceFilters = [
"all",
"online",
"likely_online",
"unknown",
"offline",
"unknown",
] as const;
export const knownFilters: KnownFilter[] = ["all", "known", "unknown"];
@@ -40,8 +40,8 @@ export function formatSeen(value: number | null): string {
export function presenceRank(presence: string): number {
if (presence === "online") return 4;
if (presence === "likely_online") return 3;
if (presence === "unknown") return 2;
if (presence === "offline") return 1;
if (presence === "offline") return 2;
if (presence === "unknown") return 1;
return 0;
}
@@ -60,7 +60,7 @@ export function agentLabel(agentId: string, nickname?: string | null): string {
}
export function routeLabel(route: FleetWakeRoute): string {
const status = route.connected ? "connected" : "offline";
const status = route.connected ? "connected" : "agent offline";
const target = route.mac
? `${route.mac}${route.ip ? ` / ${route.ip}` : ""}`
: (route.ip ?? "-");