Files
MikrotikManager/packages/contracts/src/statistics.ts
T
DenozordecandCursor 5bb9066be8
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
feat(statistics): добавить BI-разрез и сводную матрицу
Сопоставить клиентов с ifIndex как на карте трафика, чтобы KPI пользователей не обнулялся. На экране — разрез остальных измерений и сводная матрица.

Co-authored-by: Cursor <[email protected]>
2026-09-10 16:07:25 +07:00

100 lines
3.1 KiB
TypeScript

import { z } from "zod"
export const STATISTICS_UNBOUND_USER_ID = "__unbound__"
export const statisticsPivotDimSchema = z.enum([
"country",
"service",
"asn",
"server",
"user",
"iface",
])
export const statisticsBreakdownRowSchema = z.object({
id: z.string(),
label: z.string(),
bytes: z.number().nonnegative(),
packets: z.number().nonnegative(),
bps: z.number().nonnegative(),
percent: z.number().nonnegative(),
})
export const statisticsSeriesPointSchema = z.object({
t: z.string(),
bytes: z.number().nonnegative(),
})
export const statisticsKpisSchema = z.object({
bytes: z.number().nonnegative(),
packets: z.number().nonnegative(),
avgBps: z.number().nonnegative(),
users: z.number().int().nonnegative(),
servers: z.number().int().nonnegative(),
ifaces: z.number().int().nonnegative(),
topCountry: z.string(),
topService: z.string(),
})
export const statisticsQuerySchema = z.object({
from: z.string().min(1),
to: z.string().min(1),
serverId: z.coerce.number().int().positive().optional(),
userId: z.string().min(1).optional(),
iface: z.string().min(1).optional(),
country: z.string().min(2).max(2).optional(),
service: z.string().min(1).optional(),
asn: z.coerce.number().int().optional(),
})
export const statisticsDtoSchema = z.object({
from: z.string(),
to: z.string(),
grain: z.enum(["hour", "day"]),
kpis: statisticsKpisSchema,
series: z.array(statisticsSeriesPointSchema),
users: z.array(statisticsBreakdownRowSchema),
servers: z.array(statisticsBreakdownRowSchema),
interfaces: z.array(statisticsBreakdownRowSchema),
countries: z.array(statisticsBreakdownRowSchema),
services: z.array(statisticsBreakdownRowSchema),
asns: z.array(statisticsBreakdownRowSchema),
})
export const statisticsPivotQuerySchema = statisticsQuerySchema.extend({
row: statisticsPivotDimSchema,
col: statisticsPivotDimSchema,
metric: z.enum(["bytes", "packets"]).default("bytes"),
})
export const statisticsPivotColumnSchema = z.object({
id: z.string(),
label: z.string(),
total: z.number().nonnegative(),
})
export const statisticsPivotRowSchema = z.object({
id: z.string(),
label: z.string(),
total: z.number().nonnegative(),
cells: z.record(z.string(), z.number().nonnegative()),
})
export const statisticsPivotDtoSchema = z.object({
rowDim: statisticsPivotDimSchema,
colDim: statisticsPivotDimSchema,
metric: z.enum(["bytes", "packets"]),
columns: z.array(statisticsPivotColumnSchema),
rows: z.array(statisticsPivotRowSchema),
otherBytes: z.number().nonnegative(),
})
export type StatisticsBreakdownRow = z.infer<typeof statisticsBreakdownRowSchema>
export type StatisticsSeriesPoint = z.infer<typeof statisticsSeriesPointSchema>
export type StatisticsKpis = z.infer<typeof statisticsKpisSchema>
export type StatisticsQuery = z.infer<typeof statisticsQuerySchema>
export type StatisticsDto = z.infer<typeof statisticsDtoSchema>
export type StatisticsPivotDim = z.infer<typeof statisticsPivotDimSchema>
export type StatisticsPivotQuery = z.infer<typeof statisticsPivotQuerySchema>
export type StatisticsPivotDto = z.infer<typeof statisticsPivotDtoSchema>