feat(statistics): добавить BI-разрез и сводную матрицу
Docker images / prepare-release (push) Successful in 10s
Docker images / backend-test (push) Successful in 2m23s
Docker images / frontend-image (push) Successful in 3m30s
Docker images / updater-image (push) Successful in 52s
Docker images / backend-image (push) Successful in 3m8s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 14s

Сопоставить клиентов с ifIndex как на карте трафика, чтобы KPI пользователей не обнулялся. На экране — разрез остальных измерений и сводная матрица.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-09-10 16:07:25 +07:00
co-authored by Cursor
parent b1fd259f10
commit 5bb9066be8
16 changed files with 1119 additions and 199 deletions
@@ -5,7 +5,7 @@ import { Flag } from "@/components/flag"
import { Badge } from "@/components/reui/badge"
import { fmtBps, formatBytes } from "@/lib/fmt-rate"
import { cn } from "@/lib/utils"
import type { StatisticsBreakdownRow } from "@mmapp/contracts/statistics"
import { STATISTICS_UNBOUND_USER_ID, type StatisticsBreakdownRow } from "@mmapp/contracts/statistics"
export type StatisticsSliceKind = "users" | "servers" | "interfaces" | "countries" | "services" | "asns"
@@ -15,13 +15,16 @@ export function StatisticsBreakdownDataGrid({
selectedId,
onRowClick,
isLoading,
density = "full",
}: {
rows: StatisticsBreakdownRow[]
kind: StatisticsSliceKind
selectedId?: string
onRowClick?: (row: StatisticsBreakdownRow) => void
isLoading?: boolean
density?: "full" | "mini"
}) {
const mini = density === "mini"
const columns: CompactDataGridColumn<StatisticsBreakdownRow>[] = [
{
id: "label",
@@ -29,7 +32,9 @@ export function StatisticsBreakdownDataGrid({
accessorKey: "label",
cell: (row) => (
<span className={cn("flex items-center gap-2", selectedId === row.id && "font-medium")}>
{kind === "countries" && row.id !== "XX" ? <Flag code={row.id} size={16} /> : null}
{kind === "countries" && row.id !== "XX" && row.id !== STATISTICS_UNBOUND_USER_ID ? (
<Flag code={row.id} size={16} />
) : null}
<span className="truncate">{row.label || row.id}</span>
{selectedId === row.id ? (
<Badge variant="outline" size="sm">
@@ -45,18 +50,24 @@ export function StatisticsBreakdownDataGrid({
accessorKey: "bytes",
cell: (row) => <span className="tabular-nums">{formatBytes(row.bytes)}</span>,
},
{
id: "packets",
header: "Пакеты",
accessorKey: "packets",
cell: (row) => <span className="tabular-nums">{row.packets.toLocaleString("ru-RU")}</span>,
},
{
id: "bps",
header: "Средний bitrate",
accessorKey: "bps",
cell: (row) => <span className="tabular-nums">{fmtBps(row.bps)}</span>,
},
...(!mini
? [
{
id: "packets",
header: "Пакеты",
accessorKey: "packets" as const,
cell: (row: StatisticsBreakdownRow) => (
<span className="tabular-nums">{row.packets.toLocaleString("ru-RU")}</span>
),
},
{
id: "bps",
header: "Средний bitrate",
accessorKey: "bps" as const,
cell: (row: StatisticsBreakdownRow) => <span className="tabular-nums">{fmtBps(row.bps)}</span>,
},
]
: []),
{
id: "percent",
header: "Доля",