feat(source2): lock dark mode, sans-serif typography, and all recent updates
This commit is contained in:
1 parent
dd4c8f6876
commit
9f24b56e97
259 files changed
+15596
-8966
No files matched your search
@@ -1,18 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { TimestampCell } from "@/components/ui/TimestampCell";
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useFlows, useFlowsOptions } from "@/lib/api-with-context";
|
||||
import { DataTable, Column } from "@/components/ui/DataTable";
|
||||
import { DataTable } from "@/components/ui/DataTable";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/Card";
|
||||
import { Loader2, Download, Search } from "lucide-react";
|
||||
import { fmtBytes, formatAppLabel } from "@/lib/utils";
|
||||
import { IpDetails } from "@/components/ui/IpDetails";
|
||||
import { formatAppLabel } from "@/lib/utils";
|
||||
import { DeviceDetailModal } from "@/components/ui/DeviceDetailModal";
|
||||
import { UniversalFilters, useUniversalFilterState, FilterConfig } from "@/components/ui/UniversalFilters";
|
||||
import { explainAppOrPort } from "./explainFlow";
|
||||
import { downloadCsv } from "@/components/ui/DataTableCsvHelper";
|
||||
import { HelpTrigger } from "@/components/help/HelpTrigger";
|
||||
import { getFlowColumns } from "./flowColumns";
|
||||
|
||||
export default function FlowsPage() {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
@@ -53,29 +51,33 @@ export default function FlowsPage() {
|
||||
document.title = "Flows | BackOne - Deep Package Inspection";
|
||||
}, []);
|
||||
|
||||
if (!mounted) return null;
|
||||
const columns = useMemo(() => getFlowColumns(setSelectedIp), []);
|
||||
|
||||
const opts = (key: string) => {
|
||||
if (!optionsData) return [];
|
||||
const mapping: Record<string, string[]> = {
|
||||
protocol: optionsData.protocols, src_ip: optionsData.srcIps, dst_ip: optionsData.dstIps,
|
||||
dst_port: optionsData.dstPorts, app_label: optionsData.apps, domain: optionsData.domains
|
||||
const filterConfigs: FilterConfig[] = useMemo(() => {
|
||||
const opts = (key: string) => {
|
||||
if (!optionsData) return [];
|
||||
const mapping: Record<string, string[]> = {
|
||||
protocol: optionsData.protocols, src_ip: optionsData.srcIps, dst_ip: optionsData.dstIps,
|
||||
dst_port: optionsData.dstPorts, app_label: optionsData.apps, domain: optionsData.domains
|
||||
};
|
||||
return mapping[key] || [];
|
||||
};
|
||||
return mapping[key] || [];
|
||||
};
|
||||
|
||||
const filterConfigs: FilterConfig[] = [
|
||||
{ id: "protocol", label: "Protocol", type: "select", value: filters.protocol, options: opts("protocol"), formatter: formatAppLabel },
|
||||
{ id: "src_ip", label: "Source IP", type: "select", value: filters.src_ip, options: opts("src_ip") },
|
||||
{ id: "dst_ip", label: "Dest IP", type: "select", value: filters.dst_ip, options: opts("dst_ip") },
|
||||
{ id: "dst_port", label: "Dest Port", type: "select", value: filters.dst_port, options: opts("dst_port") },
|
||||
{ id: "app", label: "App", type: "select", value: filters.app, options: opts("app_label"), formatter: formatAppLabel },
|
||||
{ id: "domain", label: "Domain", type: "select", value: filters.domain, options: opts("domain") },
|
||||
{ id: "sort_download", label: "Sort Download", type: "select", value: filters.sort_download, options: ["Descending", "Ascending"] },
|
||||
{ id: "sort_upload", label: "Sort Upload", type: "select", value: filters.sort_upload, options: ["Descending", "Ascending"] },
|
||||
];
|
||||
return [
|
||||
{ id: "protocol", label: "Protocol", type: "select", value: filters.protocol, options: opts("protocol"), formatter: formatAppLabel },
|
||||
{ id: "src_ip", label: "Source IP", type: "select", value: filters.src_ip, options: opts("src_ip") },
|
||||
{ id: "dst_ip", label: "Dest IP", type: "select", value: filters.dst_ip, options: opts("dst_ip") },
|
||||
{ id: "dst_port", label: "Dest Port", type: "select", value: filters.dst_port, options: opts("dst_port") },
|
||||
{ id: "app", label: "App", type: "select", value: filters.app, options: opts("app_label"), formatter: formatAppLabel },
|
||||
{ id: "domain", label: "Domain", type: "select", value: filters.domain, options: opts("domain") },
|
||||
{ id: "sort_download", label: "Sort Download", type: "select", value: filters.sort_download, options: ["Descending", "Ascending"] },
|
||||
{ id: "sort_upload", label: "Sort Upload", type: "select", value: filters.sort_upload, options: ["Descending", "Ascending"] },
|
||||
];
|
||||
}, [optionsData, filters]);
|
||||
|
||||
const minDate = new Date(Date.now() - 30 * 24 * 3600 * 1000).toISOString().slice(0, 10);
|
||||
const minDate = useMemo(() => {
|
||||
return new Date(Date.now() - 30 * 24 * 3600 * 1000).toISOString().slice(0, 10);
|
||||
}, []);
|
||||
|
||||
const handleExport = () => {
|
||||
const headers = ["Flow ID", "Src IP", "Dst IP", "Dst Port", "Protocol", "App / Domain", "Download (bytes)", "Upload (bytes)"];
|
||||
@@ -83,120 +85,31 @@ export default function FlowsPage() {
|
||||
downloadCsv(`flows-${new Date().toISOString().slice(0,10)}.csv`, rows);
|
||||
};
|
||||
|
||||
const columns: Column<any>[] = [
|
||||
{
|
||||
header: "Last Seen",
|
||||
accessor: (row) => (
|
||||
<TimestampCell
|
||||
timestamp={row.last_seen}
|
||||
firstSeen={row.first_seen}
|
||||
showActiveStatus={true}
|
||||
/>
|
||||
),
|
||||
className: "w-[14%] text-center",
|
||||
tooltip: (row) => new Date(row.last_seen).toLocaleString()
|
||||
},
|
||||
{
|
||||
header: "#",
|
||||
accessor: (row, i) => i + 1,
|
||||
className: "w-[4%] text-center"
|
||||
},
|
||||
{
|
||||
header: "Flow ID",
|
||||
accessor: (row) => row.flow_id,
|
||||
className: "w-[11%] text-center font-mono text-xs truncate"
|
||||
},
|
||||
{
|
||||
header: "Src IP",
|
||||
accessor: (row) => (
|
||||
<div className="flex flex-col items-center justify-center text-center">
|
||||
<button
|
||||
onClick={() => setSelectedIp(row.src_ip)}
|
||||
className="text-blue-400 hover:text-blue-300 hover:underline font-mono text-center focus:outline-none truncate w-full"
|
||||
>
|
||||
{row.src_ip}
|
||||
</button>
|
||||
{row.src_label && (
|
||||
<span className="text-[10px] text-cyan-400 font-semibold tracking-wide mt-0.5 truncate w-full block">
|
||||
{row.src_label}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
className: "w-[11%] text-center",
|
||||
tooltip: (row) => row.src_label ? `${row.src_ip} (${row.src_label})` : row.src_ip
|
||||
},
|
||||
{
|
||||
header: "Dst IP",
|
||||
accessor: (row) => (
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<button
|
||||
onClick={() => setSelectedIp(row.dst_ip)}
|
||||
className="text-blue-400 hover:text-blue-300 hover:underline font-mono text-center focus:outline-none truncate w-full"
|
||||
>
|
||||
{row.dst_ip}
|
||||
</button>
|
||||
<IpDetails ip={row.dst_ip} />
|
||||
</div>
|
||||
),
|
||||
className: "w-[13%] text-center",
|
||||
tooltip: (row) => row.dst_ip
|
||||
},
|
||||
{
|
||||
header: "Dst Port",
|
||||
accessor: (row) => row.dst_port,
|
||||
className: "w-[7%] text-center font-mono text-xs"
|
||||
},
|
||||
{
|
||||
header: "Protocol",
|
||||
accessor: (row) => row.protocol,
|
||||
className: "w-[7%] text-center text-xs"
|
||||
},
|
||||
{
|
||||
header: "App / Domain",
|
||||
accessor: (row) => {
|
||||
const exp = explainAppOrPort(row.app_label, row.domain, row.dst_port);
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<span className="font-semibold text-white truncate max-w-[200px] text-center" title={exp.main}>
|
||||
{exp.main}
|
||||
</span>
|
||||
{exp.sub && (
|
||||
<span className="text-[10px] text-slate-400 italic text-center truncate max-w-[200px]">
|
||||
{exp.sub}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
className: "w-[18%] text-center",
|
||||
tooltip: (row) => {
|
||||
const exp = explainAppOrPort(row.app_label, row.domain, row.dst_port);
|
||||
return exp.sub ? `${exp.main} (${exp.sub})` : exp.main;
|
||||
}
|
||||
},
|
||||
{
|
||||
header: "Download",
|
||||
accessor: (row) => <span className="text-blue-400">{fmtBytes(row.bytes_download)}</span>,
|
||||
className: "w-[8%] text-center",
|
||||
tooltip: (row) => `${fmtBytes(row.bytes_download)} (${(row.bytes_download || 0).toLocaleString()} bytes)`
|
||||
},
|
||||
{
|
||||
header: "Upload",
|
||||
accessor: (row) => <span className="text-green-500">{fmtBytes(row.bytes_upload)}</span>,
|
||||
className: "w-[7%] text-center",
|
||||
tooltip: (row) => `${fmtBytes(row.bytes_upload)} (${(row.bytes_upload || 0).toLocaleString()} bytes)`
|
||||
}
|
||||
];
|
||||
if (!mounted) return null;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-2xl font-bold tracking-tight text-white flex items-center gap-3">
|
||||
Flows
|
||||
{isLoading && <Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />}
|
||||
</h2>
|
||||
<HelpTrigger pageId="flows" />
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center justify-between gap-3 sm:justify-start sm:gap-4 w-full">
|
||||
<h2 className="text-xl sm:text-2xl md:text-3xl font-bold tracking-tight text-white flex items-center gap-2 sm:gap-3 truncate">
|
||||
<span>Flows</span>
|
||||
{isLoading && <Loader2 className="w-4 h-4 sm:w-5 sm:h-5 animate-spin text-primary flex-shrink-0" />}
|
||||
</h2>
|
||||
{/* Mobile Actions: Inline next to Title */}
|
||||
<div className="flex items-center gap-1.5 flex-shrink-0 sm:hidden">
|
||||
<HelpTrigger pageId="flows" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs sm:text-sm text-slate-400 mt-1 leading-normal sm:leading-relaxed">
|
||||
Real-time packet inspection and active network connection flows.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Laptop/Tablet Actions: Floats on the right */}
|
||||
<div className="hidden sm:flex items-center gap-2 sm:gap-3 flex-shrink-0">
|
||||
<HelpTrigger pageId="flows" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UniversalFilters
|
||||
|
||||
Reference in new issue
Block a user