Docker images / prepare-release (push) Successful in 8s
Docker images / backend-test (push) Successful in 2m11s
Docker images / frontend-image (push) Successful in 3m14s
Docker images / updater-image (push) Successful in 43s
Docker images / backend-image (push) Successful in 2m44s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 10s
- Контракт: countryServiceGroups (группа сервисов страны) в flowMapHopsDto, тип FlowMapCountryServiceGroup. - Бэкенд: вложенная агрегация brand×ISO в том же проходе hops; рёбра страна→сервис, пути с реальным EN; cap 8 / min 4, доля от байтов страны. - Хелпер mapCountryServiceNodeId: id вида cc:us|svc:google, сервисы разных стран не смешиваются. - Карта сети: клик по стране сдвигает колонку стран и раскрывает столбец её сервисов; остальные страны приглушены; панели страны и вложенного сервиса. - Тесты: группы US/US+NL, рёбра от страны, cap/min вложенного слоя; mock countryServiceGroups для демо-режима.
370 lines
13 KiB
TypeScript
370 lines
13 KiB
TypeScript
import { z } from "zod"
|
|
|
|
export const flowHostPeerSchema = z.object({
|
|
serverId: z.number().int().positive(),
|
|
name: z.string(),
|
|
publicKey: z.string().min(1),
|
|
allowedIps: z.array(z.string().min(1)).min(1),
|
|
address: z.string().min(1),
|
|
endpoint: z.string().optional(),
|
|
})
|
|
|
|
export const trafficFlowSettingsDtoSchema = z.object({
|
|
enabled: z.boolean(),
|
|
collectorIp: z.string().min(1),
|
|
flowListenPort: z.number().int().positive(),
|
|
wgListenPort: z.number().int().positive(),
|
|
prefix: z.string().min(1),
|
|
publicEndpoint: z.string(),
|
|
hostPublicKey: z.string(),
|
|
hasHostPrivateKey: z.boolean(),
|
|
hubServerId: z.number().int().positive().nullable(),
|
|
retentionHours: z.number().int().positive(),
|
|
topN: z.number().int().positive(),
|
|
mapServiceMinSharePct: z.number().min(0).max(100),
|
|
lastDatagramAt: z.string().nullable(),
|
|
lastExporterIp: z.string().nullable(),
|
|
lastError: z.string().nullable(),
|
|
packetsReceived: z.number().int().nonnegative(),
|
|
listenerBound: z.boolean(),
|
|
listenerAddress: z.string().nullable(),
|
|
peers: z.array(flowHostPeerSchema),
|
|
})
|
|
|
|
export const trafficFlowSettingsPatchSchema = z.object({
|
|
enabled: z.boolean().optional(),
|
|
collectorIp: z.string().min(1).optional(),
|
|
flowListenPort: z.number().int().positive().optional(),
|
|
wgListenPort: z.number().int().positive().optional(),
|
|
prefix: z.string().min(1).optional(),
|
|
publicEndpoint: z.string().optional(),
|
|
hubServerId: z.number().int().positive().nullable().optional(),
|
|
retentionHours: z.number().int().positive().optional(),
|
|
topN: z.number().int().positive().max(1000).optional(),
|
|
mapServiceMinSharePct: z.number().min(0).max(100).optional(),
|
|
})
|
|
|
|
export const trafficFlowOverlayRequestSchema = z.object({
|
|
serverId: z.union([z.string(), z.number()]),
|
|
publicEndpoint: z.string().optional(),
|
|
/** GRE allow-fast-path=no: inner IPFIX через CPU. По умолчанию выкл (нагрузка на CPU). */
|
|
disableGreFastPath: z.boolean().optional(),
|
|
})
|
|
|
|
export const trafficFlowHostFileSchema = z.object({
|
|
id: z.string(),
|
|
label: z.string(),
|
|
filename: z.string(),
|
|
code: z.string(),
|
|
})
|
|
|
|
export const trafficFlowOverlayResultSchema = z.object({
|
|
ok: z.boolean(),
|
|
serverId: z.number().int(),
|
|
interfaceName: z.string(),
|
|
address: z.string(),
|
|
publicKey: z.string(),
|
|
linuxPeerBlock: z.string(),
|
|
trafficFlow: z.boolean(),
|
|
steps: z.array(z.string()),
|
|
hostFiles: z.array(trafficFlowHostFileSchema),
|
|
})
|
|
|
|
export const flowTalkerDtoSchema = z.object({
|
|
serverId: z.string(),
|
|
serverName: z.string(),
|
|
src: z.string(),
|
|
dst: z.string(),
|
|
proto: z.number().int(),
|
|
protoName: z.string(),
|
|
srcPort: z.number().int(),
|
|
dstPort: z.number().int(),
|
|
bytes: z.number().nonnegative(),
|
|
packets: z.number().nonnegative(),
|
|
bps: z.number().nonnegative(),
|
|
inIface: z.string(),
|
|
inIfaceIndex: z.string().optional(),
|
|
outIface: z.string().optional(),
|
|
nextHop: z.string().optional(),
|
|
application: z.string().optional(),
|
|
category: z.string().optional(),
|
|
service: z.string().optional(),
|
|
dstCountry: z.string().optional(),
|
|
dstAsn: z.number().int().optional(),
|
|
clientId: z.string().optional(),
|
|
clientName: z.string().optional(),
|
|
clientIp: z.string().optional(),
|
|
internetPeer: z.string().optional(),
|
|
internetPeerPort: z.number().int().optional(),
|
|
direction: z.enum(["to_client", "from_client", "transit"]).optional(),
|
|
enId: z.string().optional(),
|
|
enName: z.string().optional(),
|
|
plane: z.string().optional(),
|
|
})
|
|
|
|
export const flowStatsDtoSchema = z.object({
|
|
exportersOnline: z.number().int().nonnegative(),
|
|
bytesPerMin: z.number().nonnegative(),
|
|
uniqueSrc: z.number().int().nonnegative(),
|
|
uniqueDst: z.number().int().nonnegative(),
|
|
topProto: z.string(),
|
|
talkers: z.array(flowTalkerDtoSchema),
|
|
lastExporterIp: z.string().nullable().optional(),
|
|
lastError: z.string().nullable().optional(),
|
|
packetsReceived: z.number().int().nonnegative().optional(),
|
|
lastDatagramAt: z.string().nullable().optional(),
|
|
listenerBound: z.boolean().optional(),
|
|
listenerAddress: z.string().nullable().optional(),
|
|
})
|
|
|
|
export type FlowHostPeer = z.infer<typeof flowHostPeerSchema>
|
|
export type TrafficFlowSettingsDto = z.infer<typeof trafficFlowSettingsDtoSchema>
|
|
export type TrafficFlowSettingsPatch = z.infer<typeof trafficFlowSettingsPatchSchema>
|
|
export type TrafficFlowOverlayResult = z.infer<typeof trafficFlowOverlayResultSchema>
|
|
export type TrafficFlowHostFile = z.infer<typeof trafficFlowHostFileSchema>
|
|
export const flowBreakdownRowSchema = 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 flowIfaceChipSchema = z.object({
|
|
name: z.string(),
|
|
index: z.string(),
|
|
bps: z.number().nonnegative(),
|
|
})
|
|
|
|
export const flowEntityCardSchema = z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
subtitle: z.string(),
|
|
site: z.string(),
|
|
country: z.string(),
|
|
status: z.enum(["online", "offline", "degraded"]),
|
|
rxNow: z.number(),
|
|
txNow: z.number(),
|
|
sessions: z.number().int().nonnegative(),
|
|
rxSeries: z.array(z.number()),
|
|
txSeries: z.array(z.number()),
|
|
bytes: z.number().nonnegative(),
|
|
})
|
|
|
|
export const flowMapEdgeSchema = z.object({
|
|
fromId: z.string(),
|
|
fromLabel: z.string(),
|
|
fromCountry: z.string(),
|
|
toCountry: z.string(),
|
|
toAsn: z.number().int().nonnegative(),
|
|
category: z.string(),
|
|
bytes: z.number().nonnegative(),
|
|
bps: z.number().nonnegative(),
|
|
})
|
|
|
|
export const flowPathRowSchema = z.object({
|
|
id: z.string(),
|
|
clientId: z.string(),
|
|
clientName: z.string(),
|
|
ifaces: z.string(),
|
|
serverId: z.string(),
|
|
serverName: z.string(),
|
|
inIface: z.string(),
|
|
outIface: z.string(),
|
|
enId: z.string(),
|
|
enName: z.string(),
|
|
dst: z.string(),
|
|
service: z.string(),
|
|
category: z.string(),
|
|
plane: z.string(),
|
|
bytes: z.number().nonnegative(),
|
|
packets: z.number().nonnegative(),
|
|
bps: z.number().nonnegative(),
|
|
})
|
|
|
|
export const flowAnalyticsDtoSchema = z.object({
|
|
bpsNow: z.number().nonnegative(),
|
|
bytes: z.number().nonnegative(),
|
|
packets: z.number().nonnegative(),
|
|
conversations: z.number().int().nonnegative(),
|
|
conversationsRaw: z.number().int().nonnegative().optional(),
|
|
uniqueSrc: z.number().int().nonnegative(),
|
|
uniqueDst: z.number().int().nonnegative(),
|
|
topProto: z.string(),
|
|
topCategory: z.string().optional(),
|
|
rxSeries: z.array(z.number()),
|
|
txSeries: z.array(z.number()),
|
|
applications: z.array(flowBreakdownRowSchema),
|
|
protocols: z.array(flowBreakdownRowSchema),
|
|
sources: z.array(flowBreakdownRowSchema),
|
|
destinations: z.array(flowBreakdownRowSchema),
|
|
interfaces: z.array(flowBreakdownRowSchema),
|
|
asns: z.array(flowBreakdownRowSchema).optional(),
|
|
countries: z.array(flowBreakdownRowSchema).optional(),
|
|
categories: z.array(flowBreakdownRowSchema).optional(),
|
|
services: z.array(flowBreakdownRowSchema).optional(),
|
|
mapEdges: z.array(flowMapEdgeSchema).optional(),
|
|
conversationsList: z.array(flowTalkerDtoSchema),
|
|
paths: z.array(flowPathRowSchema).optional(),
|
|
ifaces: z.array(flowIfaceChipSchema),
|
|
live: z.boolean(),
|
|
dedupApplied: z.boolean().optional(),
|
|
degraded: z.boolean().optional(),
|
|
bytesPayload: z.number().nonnegative().optional(),
|
|
bytesOverlay: z.number().nonnegative().optional(),
|
|
bytesMesh: z.number().nonnegative().optional(),
|
|
bytesWire: z.number().nonnegative().optional(),
|
|
bpsOverlay: z.number().nonnegative().optional(),
|
|
bpsWire: z.number().nonnegative().optional(),
|
|
excludeMeshApplied: z.boolean().optional(),
|
|
excludeOverlayApplied: z.boolean().optional(),
|
|
})
|
|
|
|
export const flowExportersDtoSchema = z.object({
|
|
exporters: z.array(flowEntityCardSchema),
|
|
lastExporterIp: z.string().nullable().optional(),
|
|
lastError: z.string().nullable().optional(),
|
|
packetsReceived: z.number().int().nonnegative().optional(),
|
|
lastDatagramAt: z.string().nullable().optional(),
|
|
listenerBound: z.boolean().optional(),
|
|
listenerAddress: z.string().nullable().optional(),
|
|
})
|
|
|
|
export const flowClientsDtoSchema = z.object({
|
|
clients: z.array(flowEntityCardSchema),
|
|
})
|
|
|
|
export const flowMonthlyDtoSchema = z.object({
|
|
month: z.string(),
|
|
bytes: z.number().nonnegative(),
|
|
countries: z.array(flowBreakdownRowSchema),
|
|
services: z.array(flowBreakdownRowSchema),
|
|
asns: z.array(flowBreakdownRowSchema),
|
|
})
|
|
|
|
export const flowPurgeDtoSchema = z.object({
|
|
ok: z.literal(true),
|
|
deleted: z.object({
|
|
buckets: z.number().int().nonnegative(),
|
|
minuteStats: z.number().int().nonnegative(),
|
|
minuteDims: z.number().int().nonnegative(),
|
|
dailyDims: z.number().int().nonnegative(),
|
|
hourFacts: z.number().int().nonnegative().optional(),
|
|
dailyFacts: z.number().int().nonnegative().optional(),
|
|
}),
|
|
fileBytesBefore: z.number().int().nonnegative(),
|
|
fileBytesAfter: z.number().int().nonnegative(),
|
|
vacuumed: z.boolean(),
|
|
})
|
|
|
|
export const flowFactsRebuildDtoSchema = z.object({
|
|
ok: z.literal(true),
|
|
buckets: z.number().int().nonnegative(),
|
|
facts: z.number().int().nonnegative(),
|
|
days: z.array(z.string()),
|
|
})
|
|
|
|
export const flowMapHopKindSchema = z.enum(["gre", "wan", "iface"])
|
|
|
|
export const flowMapHopDtoSchema = z.object({
|
|
fromId: z.string(),
|
|
fromLabel: z.string(),
|
|
toId: z.string(),
|
|
toLabel: z.string(),
|
|
kind: flowMapHopKindSchema,
|
|
iface: z.string().optional(),
|
|
bytes: z.number().nonnegative(),
|
|
bps: z.number().nonnegative(),
|
|
bpsFwd: z.number().nonnegative(),
|
|
bpsRev: z.number().nonnegative(),
|
|
})
|
|
|
|
export const flowMapServiceDtoSchema = z.object({
|
|
id: z.string(),
|
|
label: z.string(),
|
|
category: z.string(),
|
|
bytes: z.number().nonnegative(),
|
|
bps: z.number().nonnegative(),
|
|
share: z.number().min(0).max(1),
|
|
})
|
|
|
|
export const flowMapServiceEdgeDtoSchema = z.object({
|
|
fromId: z.string(),
|
|
toId: z.string(),
|
|
bytes: z.number().nonnegative(),
|
|
bps: z.number().nonnegative(),
|
|
bpsFwd: z.number().nonnegative(),
|
|
bpsRev: z.number().nonnegative(),
|
|
clientId: z.string().optional(),
|
|
clientName: z.string().optional(),
|
|
clients: z.array(z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
})).optional(),
|
|
})
|
|
|
|
export const flowMapServicePathDtoSchema = z.object({
|
|
clientId: z.string(),
|
|
clientName: z.string(),
|
|
viaId: z.string(),
|
|
viaName: z.string(),
|
|
enId: z.string(),
|
|
enName: z.string(),
|
|
serviceId: z.string(),
|
|
bytes: z.number().nonnegative(),
|
|
bps: z.number().nonnegative(),
|
|
})
|
|
|
|
/** Сервисы внутри одной страны: id вида `cc:us|svc:google`, рёбра от `countryId`. */
|
|
export const flowMapCountryServiceGroupDtoSchema = z.object({
|
|
countryId: z.string(),
|
|
services: z.array(flowMapServiceDtoSchema),
|
|
edges: z.array(flowMapServiceEdgeDtoSchema),
|
|
paths: z.array(flowMapServicePathDtoSchema),
|
|
})
|
|
|
|
export const flowMapHopsDtoSchema = z.object({
|
|
hops: z.array(flowMapHopDtoSchema),
|
|
live: z.boolean(),
|
|
rangeMinutes: z.number().int().positive(),
|
|
windowSec: z.number().positive(),
|
|
totalBytes: z.number().nonnegative().optional(),
|
|
namedBytes: z.number().nonnegative().optional(),
|
|
unclassifiedBytes: z.number().nonnegative().optional(),
|
|
asnLoaded: z.boolean().optional(),
|
|
countryLoaded: z.boolean().optional(),
|
|
services: z.array(flowMapServiceDtoSchema).optional(),
|
|
serviceEdges: z.array(flowMapServiceEdgeDtoSchema).optional(),
|
|
servicePaths: z.array(flowMapServicePathDtoSchema).optional(),
|
|
countries: z.array(flowMapServiceDtoSchema).optional(),
|
|
countryEdges: z.array(flowMapServiceEdgeDtoSchema).optional(),
|
|
countryPaths: z.array(flowMapServicePathDtoSchema).optional(),
|
|
countryServiceGroups: z.array(flowMapCountryServiceGroupDtoSchema).optional(),
|
|
mapServiceMinSharePct: z.number().min(0).max(100).optional(),
|
|
dedupApplied: z.boolean(),
|
|
excludeMeshApplied: z.boolean(),
|
|
excludeOverlayApplied: z.boolean(),
|
|
})
|
|
|
|
export type FlowTalkerDto = z.infer<typeof flowTalkerDtoSchema>
|
|
export type FlowStatsDto = z.infer<typeof flowStatsDtoSchema>
|
|
export type FlowBreakdownRow = z.infer<typeof flowBreakdownRowSchema>
|
|
export type FlowIfaceChip = z.infer<typeof flowIfaceChipSchema>
|
|
export type FlowEntityCard = z.infer<typeof flowEntityCardSchema>
|
|
export type FlowMapEdge = z.infer<typeof flowMapEdgeSchema>
|
|
export type FlowPathRow = z.infer<typeof flowPathRowSchema>
|
|
export type FlowAnalyticsDto = z.infer<typeof flowAnalyticsDtoSchema>
|
|
export type FlowExportersDto = z.infer<typeof flowExportersDtoSchema>
|
|
export type FlowClientsDto = z.infer<typeof flowClientsDtoSchema>
|
|
export type FlowMonthlyDto = z.infer<typeof flowMonthlyDtoSchema>
|
|
export type FlowPurgeDto = z.infer<typeof flowPurgeDtoSchema>
|
|
export type FlowFactsRebuildDto = z.infer<typeof flowFactsRebuildDtoSchema>
|
|
export type FlowMapHopKind = z.infer<typeof flowMapHopKindSchema>
|
|
export type FlowMapHop = z.infer<typeof flowMapHopDtoSchema>
|
|
export type FlowMapService = z.infer<typeof flowMapServiceDtoSchema>
|
|
export type FlowMapServiceEdge = z.infer<typeof flowMapServiceEdgeDtoSchema>
|
|
export type FlowMapServicePath = z.infer<typeof flowMapServicePathDtoSchema>
|
|
export type FlowMapCountryServiceGroup = z.infer<typeof flowMapCountryServiceGroupDtoSchema>
|
|
export type FlowMapHopsDto = z.infer<typeof flowMapHopsDtoSchema>
|