fix(ui): показывать подтверждённые данные месячного отчёта
This commit is contained in:
1 parent
24313340d5
commit
8ac40ba78a
4 files changed
+71
-52
No files matched your search
+14
-47
@@ -79,42 +79,6 @@ function flowEmptyHint(stats: FlowStatsDto | null, collectorAlive?: boolean): st
|
||||
return "IPFIX ещё не доходит до коллектора. На jump-host у target Src должен быть 0.0.0.0 (авто). На хосте MM проверьте bind 10.255.254.1:4739 после wg-flow."
|
||||
}
|
||||
|
||||
function monthlyToAnalytics(m: FlowMonthlyDto): FlowAnalyticsDto {
|
||||
const emptySeries = Array(60).fill(0) as number[]
|
||||
return {
|
||||
bpsNow: 0,
|
||||
bytes: m.bytes,
|
||||
packets: 0,
|
||||
conversations: 0,
|
||||
conversationsRaw: 0,
|
||||
uniqueSrc: 0,
|
||||
uniqueDst: 0,
|
||||
topProto: "—",
|
||||
topCategory: "—",
|
||||
rxSeries: emptySeries,
|
||||
txSeries: emptySeries,
|
||||
applications: [],
|
||||
protocols: [],
|
||||
sources: [],
|
||||
destinations: [],
|
||||
interfaces: [],
|
||||
asns: m.asns,
|
||||
countries: m.countries,
|
||||
categories: [],
|
||||
services: m.services,
|
||||
mapEdges: [],
|
||||
conversationsList: [],
|
||||
paths: [],
|
||||
ifaces: [],
|
||||
live: false,
|
||||
degraded: false,
|
||||
bytesPayload: m.bytes,
|
||||
bytesOverlay: 0,
|
||||
bytesMesh: 0,
|
||||
bytesWire: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// ─── data model ───────────────────────────────────────────────────────────────
|
||||
|
||||
interface BoundIfaceTraffic {
|
||||
@@ -858,7 +822,7 @@ export default function TrafficPage() {
|
||||
const flowExporters = useMemo(() => currentFlows?.exporters ?? [], [currentFlows])
|
||||
const flowClients = useMemo(() => currentFlows?.clients ?? [], [currentFlows])
|
||||
const [analyticsRetry, setAnalyticsRetry] = useState(0)
|
||||
const [analyticsResult, setAnalyticsResult] = useState<{ key: string; data?: FlowAnalyticsDto; error?: string } | null>(null)
|
||||
const [analyticsResult, setAnalyticsResult] = useState<{ key: string; data?: FlowAnalyticsDto; monthly?: FlowMonthlyDto; error?: string } | null>(null)
|
||||
const [flowIface, setFlowIface] = useState("__all__")
|
||||
const [flowDedup, setFlowDedup] = useState(true)
|
||||
const [flowExcludeMesh, setFlowExcludeMesh] = useState(true)
|
||||
@@ -928,9 +892,9 @@ export default function TrafficPage() {
|
||||
if (!isLive || effectiveMode !== "flows") return
|
||||
let cancelled = false
|
||||
void Promise.all([
|
||||
getTrafficFlows(backendUrl, range),
|
||||
getFlowExporters(backendUrl, range),
|
||||
getFlowClients(backendUrl, range),
|
||||
getTrafficFlows(backendUrl, range === "30d" ? "24h" : range),
|
||||
getFlowExporters(backendUrl, range === "30d" ? "24h" : range),
|
||||
getFlowClients(backendUrl, range === "30d" ? "24h" : range),
|
||||
]).then(([stats, exporters, clients]) => {
|
||||
if (cancelled) return
|
||||
setFlowResult({ key: flowKey, stats, exporters: exporters.exporters, clients: clients.clients })
|
||||
@@ -951,14 +915,14 @@ export default function TrafficPage() {
|
||||
if (!isLive || effectiveMode !== "flows" || !selectedId) return
|
||||
let cancelled = false
|
||||
const request = range === "30d"
|
||||
? getFlowMonthly(backendUrl, { month: new Date().toISOString().slice(0, 7), serverId: flowScope === "servers" ? selectedId : undefined }).then(monthlyToAnalytics)
|
||||
? getFlowMonthly(backendUrl, { month: new Date().toISOString().slice(0, 7), serverId: flowScope === "servers" ? selectedId : undefined }).then(monthly => ({ monthly }))
|
||||
: getFlowAnalytics(backendUrl, {
|
||||
range, serverId: flowScope === "servers" ? selectedId : undefined,
|
||||
userId: flowScope === "users" ? selectedId : undefined,
|
||||
iface: flowIface, dedup: flowDedup, excludeMesh: flowExcludeMesh, excludeOverlay: flowExcludeOverlay,
|
||||
})
|
||||
void request.then(data => {
|
||||
if (!cancelled) setAnalyticsResult({ key: analyticsKey, data })
|
||||
}).then(data => ({ data }))
|
||||
void request.then(result => {
|
||||
if (!cancelled) setAnalyticsResult({ key: analyticsKey, ...result })
|
||||
}).catch(e => {
|
||||
if (!cancelled) setAnalyticsResult({ key: analyticsKey, error: e instanceof Error ? e.message : "Не удалось загрузить аналитику" })
|
||||
})
|
||||
@@ -1231,7 +1195,7 @@ export default function TrafficPage() {
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-6">
|
||||
<div className="flex flex-col gap-5">
|
||||
{(!isLive || !(effectiveMode === "flows" ? liveError : trafficError)) && <KpiStatGrid
|
||||
{!(effectiveMode === "flows" && range === "30d") && (!isLive || !(effectiveMode === "flows" ? liveError : trafficError)) && <KpiStatGrid
|
||||
isLoading={isLive && (effectiveMode === "flows" ? liveBusy : trafficLoading)}
|
||||
aria-label="Сводка трафика"
|
||||
items={effectiveMode === "flows" ? flowKpiItems : counterKpiItems}
|
||||
@@ -1257,7 +1221,7 @@ export default function TrafficPage() {
|
||||
)}
|
||||
<div className="grid grid-cols-[300px_1fr] gap-5 items-start">
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex gap-1">
|
||||
<div role="group" aria-label="Источник потоков" className="flex gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { setFlowScope("servers"); setFlowIface("__all__") }}
|
||||
@@ -1272,7 +1236,7 @@ export default function TrafficPage() {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { setFlowScope("users"); setFlowIface("__all__") }}
|
||||
onClick={() => { setFlowScope("users"); setFlowIface("__all__"); if (range === "30d") setRange("1h") }}
|
||||
className={cn(
|
||||
"text-[10px] px-2 py-0.5 rounded border transition-colors",
|
||||
flowScope === "users"
|
||||
@@ -1311,6 +1275,7 @@ export default function TrafficPage() {
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{range === "30d" && <p className="text-xs text-muted-foreground">Выбор сервера: активность за последние 24 часа. Месячный отчёт — справа.</p>}
|
||||
<div className="flex flex-col gap-2">
|
||||
{sortedFlowCards.map((card) => (
|
||||
<FlowEntityCardView
|
||||
@@ -1334,6 +1299,8 @@ export default function TrafficPage() {
|
||||
<FlowAnalyticsDetail
|
||||
card={selFlowCard}
|
||||
analytics={displayedFlow}
|
||||
monthly={currentAnalytics?.monthly}
|
||||
monthlyAvailable={flowScope === "servers"}
|
||||
range={range}
|
||||
onRange={(r) => setRange(r as Range)}
|
||||
selectedIface={flowIface}
|
||||
|
||||
Reference in new issue
Block a user