fix(api): сохранить неизвестные значения истории скорости

This commit is contained in:
Denozordec committed 2026-10-04 05:19:34 +07:00
1 parent e5ff69e243
commit a558412a27
13 files changed
+119 -70

No files matched your search

+20 -35
View File
@@ -1,5 +1,7 @@
"use client"
import { speedHistoryResponseSchema } from "@mmapp/contracts"
import probeStyles from "./probe-rows.module.css"
import { Suspense, useState, useMemo, useEffect, useRef, useCallback } from "react"
@@ -698,8 +700,8 @@ interface SpeedTestRun {
protocol: "tcp" | "udp"
direction: "transmit" | "receive" | "both"
durationSec: number
txAvgMbps: number
rxAvgMbps: number
txAvgMbps: number | null
rxAvgMbps: number | null
status: "running" | "done" | "error"
command: string
lines: string[]
@@ -1164,26 +1166,6 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
const reloadSpeedData = useCallback(async (writeContext?: number) => {
if (!liveApi || (savingRef.current && writeContext !== saveContext.current)) return
const context = saveContext.current
type RunRow = {
id: string
srcServerId: string
dstServerId: string
srcInterface: string
dstInterface: string
protocol: "tcp" | "udp"
direction: "transmit" | "receive" | "both"
durationSec: number
txAvgMbps: number
rxAvgMbps: number
status: "done" | "error"
error?: string | null
srcAddress?: string | null
dstAddress?: string | null
srcInterfaceAddress?: string | null
dstInterfaceAddress?: string | null
afterBtPing?: { rttMs: number | null; lossPct: number | null; error: string | null } | null
createdAt: string
}
const revision = ++speedReadRevision.current
const current = () => revision === speedReadRevision.current && context === saveContext.current && (!savingRef.current || writeContext === context)
const read = async (resource: "probes" | "history", task: () => Promise<void>) => {
@@ -1201,8 +1183,9 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
if (current()) setSpeedProbes(sp.probes)
}),
read("history", async () => {
const runsRes = await apiFetch<{ runs: RunRow[] }>("/api/uptime/speed-test/runs")
if (!Array.isArray(runsRes.runs)) throw new Error("Некорректная история тестов")
const result = speedHistoryResponseSchema.safeParse(await apiFetch<unknown>("/api/uptime/speed-test/runs"))
if (!result.success) throw new Error("Некорректная история тестов скорости")
const runsRes = result.data
const rows = (runsRes.runs ?? []).map((r) => ({
id: r.id,
startedAt: Date.parse(r.createdAt),
@@ -1213,9 +1196,9 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
protocol: r.protocol,
direction: r.direction,
durationSec: r.durationSec,
txAvgMbps: Math.round(r.txAvgMbps ?? 0),
rxAvgMbps: Math.round(r.rxAvgMbps ?? 0),
status: (r.status === "error" ? "error" : "done") as "error" | "done",
txAvgMbps: r.txAvgMbps === null ? null : Math.round(r.txAvgMbps),
rxAvgMbps: r.rxAvgMbps === null ? null : Math.round(r.rxAvgMbps),
status: r.status,
command: "",
lines: r.error ? [`status: error`, r.error] : [],
afterBtPing: r.afterBtPing ?? null,
@@ -2121,8 +2104,10 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
{(() => {
const doneRuns = speedRuns.filter(r => r.status === "done")
const runningCnt = speedRuns.filter(r => r.status === "running").length
const maxTx = doneRuns.length ? Math.max(...doneRuns.map(r => r.txAvgMbps)) : null
const maxRx = doneRuns.length ? Math.max(...doneRuns.map(r => r.rxAvgMbps)) : null
const txValues = doneRuns.flatMap(r => r.txAvgMbps === null ? [] : [r.txAvgMbps])
const maxTx = txValues.length ? Math.max(...txValues) : null
const rxValues = doneRuns.flatMap(r => r.rxAvgMbps === null ? [] : [r.rxAvgMbps])
const maxRx = rxValues.length ? Math.max(...rxValues) : null
return (
<div className="px-6 py-4 border-b bg-muted/10 shrink-0">
<KpiStatGrid
@@ -2223,10 +2208,10 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
(r.srcInterface ?? "") === probe.srcInterface &&
(r.dstInterface ?? "") === probe.dstInterface,
)
const viewStatus = run?.status ?? (probe.lastStatus === "error" ? "error" : probe.lastRunAt ? "done" : null)
const viewTx = run?.txAvgMbps ?? Math.round(probe.lastTxAvgMbps ?? 0)
const viewRx = run?.rxAvgMbps ?? Math.round(probe.lastRxAvgMbps ?? 0)
const maxVal = Math.max(viewTx, viewRx, 1)
const viewStatus = run?.status ?? (probe.lastStatus === "error" || probe.lastStatus === "done" ? probe.lastStatus : null)
const viewTx = run ? run.txAvgMbps : probe.lastTxAvgMbps == null ? null : Math.round(probe.lastTxAvgMbps)
const viewRx = run ? run.rxAvgMbps : probe.lastRxAvgMbps == null ? null : Math.round(probe.lastRxAvgMbps)
const maxVal = Math.max(viewTx ?? 0, viewRx ?? 0, 1)
const canRun = probe.enabled && !!probe.srcServerId && !!probe.dstServerId && probe.srcServerId !== probe.dstServerId
const viewAfterBtPing =
run?.status === "done" && run.afterBtPing
@@ -2314,10 +2299,10 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
<span className="w-8 font-semibold text-muted-foreground">{r.label}</span>
<div className="flex-1 h-1.5 rounded-full bg-muted overflow-hidden">
<div className={cn("h-full rounded-full", r.color)}
style={{ width: `${Math.max((r.val / maxVal) * 100, r.val > 0 ? 3 : 0)}%` }} />
style={{ width: `${r.val === null ? 0 : Math.max((r.val / maxVal) * 100, r.val > 0 ? 3 : 0)}%` }} />
</div>
<span className={cn("w-24 text-right font-mono tabular-nums font-semibold", r.textColor)}>
{r.val} Мбит/с
{r.val === null ? "Неизвестно" : `${r.val} Мбит/с`}
</span>
</div>
))}