Files
MikrotikManager/backend/src/services/traffic-flow-settings.ts
T
DenozordecandCursor 90c8c393e5
Docker images / prepare-release (push) Successful in 8s
Docker images / backend-image (push) Failing after 1m31s
Docker images / frontend-image (push) Successful in 2m36s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 42s
Docker images / publish-release (push) Skipped
feat(traffic): разделить плоскости IPFIX и показать путь JH→EN
Считать payload отдельно от overlay GRE/ESP и mesh; вкладка Пути и KPI Wire из счётчиков iface.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 11:39:14 +07:00

144 lines
4.7 KiB
TypeScript

import { eq } from "drizzle-orm"
import { db } from "../db/index.js"
import { trafficFlowSettings } from "../db/schema.js"
import type { FlowHostPeer, TrafficFlowSettingsDto, TrafficFlowSettingsPatch } from "@mmapp/contracts/traffic-flow"
import { generateWireGuardKeyPair } from "./wg-keys.js"
function nowIso() {
return new Date().toISOString()
}
function parsePeers(raw: string): FlowHostPeer[] {
try {
const parsed = JSON.parse(raw) as unknown
if (!Array.isArray(parsed)) return []
return parsed.filter((p): p is FlowHostPeer =>
p != null && typeof p === "object" && typeof (p as FlowHostPeer).publicKey === "string",
)
} catch {
return []
}
}
export function getTrafficFlowSettingsRow() {
const row = db.select().from(trafficFlowSettings).where(eq(trafficFlowSettings.id, 1)).limit(1).all()[0]
if (row) return row
const now = nowIso()
db.insert(trafficFlowSettings).values({
id: 1,
enabled: false,
collectorIp: "10.255.254.1",
flowListenPort: 4739,
wgListenPort: 51821,
prefix: "10.255.254.0/24",
createdAt: now,
updatedAt: now,
}).run()
return db.select().from(trafficFlowSettings).where(eq(trafficFlowSettings.id, 1)).limit(1).all()[0]
}
export function toTrafficFlowSettingsDto(
listener: { bound: boolean; address: string | null },
): TrafficFlowSettingsDto {
const row = getTrafficFlowSettingsRow()
return {
enabled: row.enabled,
collectorIp: row.collectorIp,
flowListenPort: row.flowListenPort,
wgListenPort: row.wgListenPort,
prefix: row.prefix,
publicEndpoint: row.publicEndpoint,
hostPublicKey: row.hostPublicKey,
hasHostPrivateKey: Boolean(row.hostPrivateKey),
hubServerId: row.hubServerId ?? null,
retentionHours: row.retentionHours,
topN: row.topN,
lastDatagramAt: row.lastDatagramAt ?? null,
lastExporterIp: row.lastExporterIp ?? null,
lastError: row.lastError || null,
packetsReceived: row.packetsReceived,
listenerBound: listener.bound,
listenerAddress: listener.address,
peers: parsePeers(row.peersJson),
}
}
export function updateTrafficFlowSettings(patch: TrafficFlowSettingsPatch) {
const row = getTrafficFlowSettingsRow()
db.update(trafficFlowSettings).set({
enabled: patch.enabled ?? row.enabled,
collectorIp: patch.collectorIp ?? row.collectorIp,
flowListenPort: patch.flowListenPort ?? row.flowListenPort,
wgListenPort: patch.wgListenPort ?? row.wgListenPort,
prefix: patch.prefix ?? row.prefix,
publicEndpoint: patch.publicEndpoint ?? row.publicEndpoint,
hubServerId: patch.hubServerId === undefined ? row.hubServerId : patch.hubServerId,
retentionHours: patch.retentionHours ?? row.retentionHours,
topN: patch.topN ?? row.topN,
updatedAt: nowIso(),
}).where(eq(trafficFlowSettings.id, 1)).run()
return getTrafficFlowSettingsRow()
}
export function ensureHostKeys(): { publicKey: string; created: boolean } {
const row = getTrafficFlowSettingsRow()
if (row.hostPublicKey && row.hostPrivateKey) {
return { publicKey: row.hostPublicKey, created: false }
}
const keys = generateWireGuardKeyPair()
db.update(trafficFlowSettings).set({
hostPublicKey: keys.publicKey,
hostPrivateKey: keys.privateKey,
updatedAt: nowIso(),
}).where(eq(trafficFlowSettings.id, 1)).run()
return { publicKey: keys.publicKey, created: true }
}
export function upsertHostPeer(peer: FlowHostPeer) {
const row = getTrafficFlowSettingsRow()
const peers = parsePeers(row.peersJson).filter((p) => p.serverId !== peer.serverId)
peers.push(peer)
db.update(trafficFlowSettings).set({
peersJson: JSON.stringify(peers),
updatedAt: nowIso(),
}).where(eq(trafficFlowSettings.id, 1)).run()
}
export function recordFlowPacket(exporterIp: string) {
const row = getTrafficFlowSettingsRow()
db.update(trafficFlowSettings).set({
lastDatagramAt: nowIso(),
lastExporterIp: exporterIp,
packetsReceived: row.packetsReceived + 1,
updatedAt: nowIso(),
}).where(eq(trafficFlowSettings.id, 1)).run()
}
export function recordFlowListenerError(message: string) {
db.update(trafficFlowSettings).set({
lastError: message,
updatedAt: nowIso(),
}).where(eq(trafficFlowSettings.id, 1)).run()
}
export function enableTrafficFlowIngest() {
db.update(trafficFlowSettings).set({
enabled: true,
updatedAt: nowIso(),
}).where(eq(trafficFlowSettings.id, 1)).run()
}
export function listHostPeers(): FlowHostPeer[] {
return parsePeers(getTrafficFlowSettingsRow().peersJson)
}
export function resetFlowIngestCounters(): void {
db.update(trafficFlowSettings).set({
packetsReceived: 0,
lastDatagramAt: null,
lastExporterIp: null,
lastError: "",
updatedAt: nowIso(),
}).where(eq(trafficFlowSettings.id, 1)).run()
}