feat(traffic-flow): enhance flow analytics and IP handling
Docker images / prepare-release (push) Successful in 8s
Docker images / backend-test (push) Successful in 2m29s
Docker images / frontend-image (push) Successful in 3m20s
Docker images / updater-image (push) Successful in 51s
Docker images / backend-image (push) Successful in 2m40s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 13s

- Introduced `canonicalIp` function to standardize IP address formats across the application, improving consistency in flow processing.
- Updated traffic flow analytics to utilize new endpoint resolution logic, enhancing accuracy in traffic classification.
- Enhanced tests for traffic flow analytics and IP handling, ensuring comprehensive coverage for new functionalities.
- Improved traffic flow destination resolution with additional test cases for various internet services, including Google and Fastly.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-09-12 01:51:23 +07:00
co-authored by Cursor
parent 4ce6169d14
commit ee9804f1bb
17 changed files with 440 additions and 103 deletions
@@ -3,6 +3,7 @@
import { useMemo } from "react"
import { type ColumnDef, getCoreRowModel, useReactTable } from "@tanstack/react-table"
import type { FlowTalkerDto } from "@mmapp/contracts/traffic-flow"
import { ArrowDownIcon, ArrowUpIcon, MinusIcon } from "lucide-react"
import { DataGridShell } from "@/components/data-grids/shared/data-grid-shell"
import {
DATA_GRID_CELL_PAD,
@@ -19,6 +20,40 @@ function formatBytes(n: number): string {
return `${n} Б`
}
function formatEndpoint(ip: string | undefined, port: number | undefined): string {
const host = String(ip ?? "").trim()
if (!host) return "—"
return port ? `${host}:${port}` : host
}
function packetTuple(row: FlowTalkerDto): string {
const src = formatEndpoint(row.src, row.srcPort)
const dst = formatEndpoint(row.dst, row.dstPort)
return `${src}${dst}`
}
function FlowDirectionMark({ direction }: { direction: FlowTalkerDto["direction"] }) {
if (direction === "to_client") {
return (
<span className="inline-flex text-muted-foreground" title="Download: интернет → клиент">
<ArrowDownIcon className="size-3" />
</span>
)
}
if (direction === "from_client") {
return (
<span className="inline-flex text-muted-foreground" title="Upload: клиент → интернет">
<ArrowUpIcon className="size-3" />
</span>
)
}
return (
<span className="inline-flex text-muted-foreground" title="Транзит">
<MinusIcon className="size-3" />
</span>
)
}
function TrafficFlowsDataGrid({
rows,
emptyHint,
@@ -30,9 +65,16 @@ function TrafficFlowsDataGrid({
() => [
{
id: "client",
accessorFn: (r) => r.clientName ?? "",
accessorFn: (r) => r.clientName || r.clientIp || "",
header: () => <span className="text-xs font-medium text-muted-foreground">Клиент</span>,
cell: ({ row }) => <span className="text-xs">{row.original.clientName || "—"}</span>,
cell: ({ row }) => (
<span className="flex min-w-0 flex-col gap-0.5">
<span className="text-xs truncate">{row.original.clientName || "—"}</span>
{row.original.clientIp ? (
<span className="font-mono text-[10px] text-muted-foreground truncate">{row.original.clientIp}</span>
) : null}
</span>
),
meta: { headerClassName: DATA_GRID_CELL_PAD_FIRST, cellClassName: DATA_GRID_CELL_PAD_FIRST },
},
{
@@ -43,27 +85,26 @@ function TrafficFlowsDataGrid({
meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD },
},
{
id: "src",
accessorKey: "src",
header: () => <span className="text-xs font-medium text-muted-foreground">Src</span>,
cell: ({ row }) => (
<span className="font-mono text-xs">
{row.original.src}
{row.original.srcPort ? `:${row.original.srcPort}` : ""}
</span>
),
id: "direction",
accessorFn: (r) => r.direction ?? "",
header: () => <span className="text-xs font-medium text-muted-foreground">Напр.</span>,
cell: ({ row }) => <FlowDirectionMark direction={row.original.direction} />,
meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD },
},
{
id: "dst",
accessorKey: "dst",
header: () => <span className="text-xs font-medium text-muted-foreground">Dst</span>,
cell: ({ row }) => (
<span className="font-mono text-xs">
{row.original.dst}
{row.original.dstPort ? `:${row.original.dstPort}` : ""}
</span>
),
id: "internet",
accessorFn: (r) => r.internetPeer ?? r.dst,
header: () => <span className="text-xs font-medium text-muted-foreground">Интернет</span>,
cell: ({ row }) => {
const r = row.original
const label = formatEndpoint(r.internetPeer, r.internetPeerPort)
const tuple = packetTuple(r)
return (
<span className="font-mono text-xs truncate max-w-[220px]" title={tuple}>
{label}
</span>
)
},
meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD },
},
{