fix(traffic): сбрасывать src-address Traffic Flow в авто
Docker images / prepare-release (push) Successful in 11s
Docker images / backend-image (push) Successful in 2m2s
Docker images / frontend-image (push) Successful in 3m11s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 47s
Docker images / publish-release (push) Successful in 12s

Target с адресом wg-flow помечался invalid, IPFIX не уходил на коллектор.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-09-07 00:37:12 +07:00
co-authored by Cursor
parent 5e512407e5
commit cf68b59b3f
8 changed files with 72 additions and 24 deletions
+37 -9
View File
@@ -50,6 +50,31 @@ function addSeries(a: number[], b: number[]): number[] {
return a.map((v, i) => v + (b[i] ?? 0))
}
function flowIngestLine(stats: FlowStatsDto | null): string | null {
if (!stats) return null
const listener = stats.listenerBound
? (stats.listenerAddress ?? "слушает")
: "не слушает"
const last = stats.lastDatagramAt
? new Date(stats.lastDatagramAt).toLocaleString("ru-RU")
: "—"
const exporter = stats.lastExporterIp ? ` · ${stats.lastExporterIp}` : ""
const err = stats.lastError ? ` · ${stats.lastError}` : ""
return `Коллектор: ${listener} · пакеты ${stats.packetsReceived ?? 0} · последний ${last}${exporter}${err}`
}
function flowEmptyHint(stats: FlowStatsDto | null): string | undefined {
if (!stats) return undefined
if (stats.lastError) return stats.lastError
if (stats.packetsReceived) {
return `IPFIX приходит (${stats.lastExporterIp ?? "экспортёр"}), но разговоры ещё не записаны.`
}
if (stats.listenerBound === false) {
return "Коллектор UDP не слушает. Подключите JH ещё раз — ingest включится автоматически."
}
return "IPFIX ещё не доходит до коллектора. На jump-host у target Src должен быть 0.0.0.0 (авто). На хосте MM проверьте bind 10.255.254.1:4739 после wg-flow."
}
// ─── data model ───────────────────────────────────────────────────────────────
interface BoundIfaceTraffic {
@@ -969,6 +994,7 @@ export default function TrafficPage() {
const peakTx = kpiSource.reduce((a, s) => Math.max(a, s.txPeak), 0)
const visibleSortFields = SORT_FIELDS.filter(s => !s.modesOnly || s.modesOnly.includes(effectiveMode))
const ingestLine = flowIngestLine(flowStats)
const flowKpiItems = [
{
@@ -1081,9 +1107,16 @@ export default function TrafficPage() {
{effectiveMode === "flows" ? (
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between gap-2 flex-wrap">
<p className="text-sm text-muted-foreground">
IPFIX top-разговоры. Счётчики интерфейсов в режимах Серверы / Клиенты / Интерфейсы.
</p>
<div className="flex flex-col gap-1 min-w-0">
<p className="text-sm text-muted-foreground">
IPFIX top-разговоры. Счётчики интерфейсов в режимах Серверы / Клиенты / Интерфейсы.
</p>
{ingestLine ? (
<p className="text-xs text-muted-foreground font-mono truncate">
{ingestLine}
</p>
) : null}
</div>
<div className="flex items-center gap-2 flex-wrap">
<div className="flex gap-1">
{TRAFFIC_RANGE_KEYS.map((key) => (
@@ -1116,12 +1149,7 @@ export default function TrafficPage() {
<DataPageCard>
<TrafficFlowsDataGrid
rows={flowStats?.talkers ?? []}
emptyHint={
flowStats?.packetsReceived
? (flowStats.lastError
|| `IPFIX приходит (${flowStats.lastExporterIp ?? "экспортёр"}), но разговоры ещё не записаны.`)
: undefined
}
emptyHint={flowEmptyHint(flowStats)}
/>
</DataPageCard>
<FlowOverlaySheet