"use client";
import { useState } from "react";
import { Activity } from "lucide-react";
import { fmtBytes, formatAppLabel } from "@/lib/utils";
import { Pagination } from "./Pagination";
import { UniversalFilters, useUniversalFilterState, FilterConfig } from "./UniversalFilters";
interface Props {
flows: any[];
ip: string;
}
interface ColDef {
header: string;
width: string;
align?: "center" | "right";
}
function TableWrap({ children, cols }: { children: React.ReactNode; cols: ColDef[] }) {
return (
{cols.map((c) => (
|
{c.header}
|
))}
{children}
);
}
function Cell({ value, className = "", center = true, right = false }: { value: string | number; className?: string; center?: boolean; right?: boolean }) {
const str = String(value ?? "—");
const align = center ? "text-center" : right ? "text-right" : "text-center";
return (
{str || "—"}
);
}
export default function DeviceFlowsTab({ flows, ip }: Props) {
const [page, setPage] = useState(1);
const pageSize = 50;
const {
filters,
handleFilterChange,
handleReset, activeCount
} = useUniversalFilterState();
const opts = (key: string) => {
const raw = Array.from(new Set(flows.map((r: any) => String(r[key] || "")).filter(Boolean)));
return raw.sort();
};
const filterConfigs: FilterConfig[] = [
{ id: "app_name", label: "App", type: "select", value: filters.app_name, options: opts("app_name"), formatter: formatAppLabel },
{ id: "protocol_name", label: "Protocol", type: "select", value: filters.protocol_name, options: opts("protocol_name"), formatter: formatAppLabel },
];
const filteredData = flows.filter((row: any) => {
if (filters.app_name && filters.app_name !== "All" && row.app_name !== filters.app_name) return false;
if (filters.protocol_name && filters.protocol_name !== "All" && row.protocol_name !== filters.protocol_name) return false;
return true;
});
const totalPages = Math.ceil(filteredData.length / pageSize);
const startIndex = (page - 1) * pageSize;
const paginated = filteredData.slice(startIndex, startIndex + pageSize);
if (flows.length === 0) {
return (
No Flow Data Yet
Network flows for this IP haven't been captured or are currently being ingested. If you just started the system, please wait a few moments as data is actively being synchronized.
);
}
const COLS: ColDef[] = [
{ header: "#", width: "w-[5%]", align: "center" },
{ header: "Time", width: "w-[15%]", align: "center" },
{ header: "Source", width: "w-[15%]", align: "center" },
{ header: "Destination", width: "w-[15%]", align: "center" },
{ header: "Port", width: "w-[10%]", align: "center" },
{ header: "App / Protocol", width: "w-[20%]", align: "center" },
{ header: "Download", width: "w-[10%]", align: "center" },
{ header: "Upload", width: "w-[10%]", align: "center" },
];
return (
{ handleFilterChange(id, val); setPage(1); }}
showDateRange={false}
activeFilterCount={activeCount}
onReset={() => { handleReset(); setPage(1); }}
/>
{paginated.map((f: any, i: number) => {
const isOutbound = f.src_ip === ip;
const timeStr = f.last_seen ? new Date(f.last_seen).toLocaleString() : '-';
return (
| |
| |
|
|
|
|
|
|
{f.app_label || f.protocol || '-'}
|
| |
| |
);
})}
{totalPages > 1 && (
)}
);
}