fix(ui): показывать неопределённый исход speed и сверять историю

This commit is contained in:
Denozordec committed 2026-10-08 08:57:54 +07:00
1 parent 96fa910804
commit 8782054078
8 files changed
+145 -11

No files matched your search

+20 -7
View File
@@ -707,7 +707,7 @@ interface SpeedTestRun {
durationSec: number
txAvgMbps: number | null
rxAvgMbps: number | null
status: "running" | "done" | "error"
status: "running" | "done" | "error" | "unknown"
command: string
lines: string[]
afterBtPing?: { rttMs: number | null; lossPct: number | null; error: string | null } | null
@@ -1077,7 +1077,14 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
const [probeRttChartOpen, setProbeRttChartOpen] = useState<Record<string, boolean>>({})
const [speedBusy, setSpeedBusy] = useState(false)
const [speedError, setSpeedError] = useState<string | null>(null)
const [speedUnknownRunId, setSpeedUnknownRunId] = useState<string | null>(null)
const [speedRuns, setSpeedRuns] = useState<SpeedTestRun[]>([])
useEffect(() => {
if (speedUnknownRunId && speedRuns.some(run => run.id === speedUnknownRunId && (run.status === "done" || run.status === "error"))) {
setSpeedUnknownRunId(null)
setSpeedError(null)
}
}, [speedRuns, speedUnknownRunId])
const [speedProbes, setSpeedProbes] = useState<SpeedProbeRow[]>([])
const speedReadRevision = useRef(0)
const [speedReads, setSpeedReads] = useState<Record<"probes" | "history", { receivedAt: number | null; error: string | null }>>({ probes: { receivedAt: null, error: null }, history: { receivedAt: null, error: null } })
@@ -1182,7 +1189,10 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
srcInterfaceAddress: r.srcInterfaceAddress ?? null,
dstInterfaceAddress: r.dstInterfaceAddress ?? null,
}))
if (current()) setSpeedRuns(rows)
if (current()) setSpeedRuns(previous => [
...previous.filter(run => (run.status === "running" || run.status === "unknown") && !rows.some(row => row.id === run.id)),
...rows,
])
}),
])
}, [apiFetch, liveApi])
@@ -1687,6 +1697,7 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
overviewReqRef.current++
const context = ++saveContext.current
setSpeedError(null)
setSpeedUnknownRunId(null)
setSpeedBusy(true)
const durationSec = Math.max(3, Number.parseInt(probe.durationSec, 10) || 10)
const runId = `speed-${Date.now()}`
@@ -1806,13 +1817,15 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
} catch (e) {
if (context !== saveContext.current) return
const message = serverOperationError(e)
const persistenceUnknown = isSpeedPersistenceUnknown(e, runId)
setSpeedUnknownRunId(persistenceUnknown ? runId : null)
setSpeedRuns((prev) => prev.map((r) => r.id === runId ? ({
...r,
status: "error",
lines: [`status: error`, message],
status: persistenceUnknown ? "unknown" : "error",
lines: [`status: ${persistenceUnknown ? "unknown" : "error"}`, message],
}) : r))
setSpeedError(message)
if (!isSpeedPersistenceUnknown(e, runId)) setSpeedProbes((prev) => prev.map((p) => p.id === probe.id ? ({
if (!persistenceUnknown) setSpeedProbes((prev) => prev.map((p) => p.id === probe.id ? ({
...p,
lastRunAt: new Date().toISOString(),
lastStatus: "error",
@@ -2107,7 +2120,7 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
{/* ── error ── */}
{speedError && (
<div className="mx-6 mt-4">
<Alert variant="destructive" className="py-2 text-xs">
<Alert variant={speedUnknownRunId ? "default" : "destructive"} className="py-2 text-xs">
<AlertCircleIcon />
<AlertDescription>{speedError}</AlertDescription>
</Alert>
@@ -2160,7 +2173,7 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
const run = speedRuns.find((r) =>
sameSpeedMeasurement(r, probe),
)
const useRun = run && (run.status === "running" || (
const useRun = run && run.status !== "unknown" && (run.status === "running" || (
probe.lastStatus != null && probe.lastRunAt != null && run.startedAt >= Date.parse(probe.lastRunAt)
))
const viewStatus = useRun ? run.status : (probe.lastStatus === "error" || probe.lastStatus === "done" ? probe.lastStatus : null)
@@ -31,7 +31,7 @@ export interface SpeedTestRunRow {
durationSec: number
txAvgMbps: number | null
rxAvgMbps: number | null
status: "running" | "done" | "error"
status: "running" | "done" | "error" | "unknown"
afterBtPing?: { rttMs: number | null; lossPct: number | null; error: string | null } | null
srcInterfaceAddress?: string | null
dstInterfaceAddress?: string | null
@@ -130,6 +130,14 @@ function UptimeSpeedHistoryDataGrid({ runs, servers }: UptimeSpeedHistoryDataGri
if (status === "error") {
return <span className="text-[var(--status-offline-fg)] text-xs">error</span>
}
if (status === "unknown") {
return (
<div className="max-w-64 text-xs text-[var(--status-degraded-fg)]">
<span>Сохранение не подтверждено</span>
<div className="mt-1 font-mono wrap-anywhere">runId: {row.original.id}</div>
</div>
)
}
return <span className="text-[var(--status-online-fg)] text-xs">done</span>
},
meta: { headerTitle: "Статус", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD },
+8
View File
@@ -117,3 +117,11 @@ BGP-prefix evaluator, отдельное live GRE расписание и recove
## Локальный snapshot guard — 2026-10-08
12.5.20 сохраняетlastconfirmedspeed snapshot дляunknown502/совпадающегоrunId.2unit tests/frontendtypecheck passed;browserunknownUX ещёнепроверен,patchнеразвёрнут. Backend4c12c55/frontend673e5ba прежние. Следующий12.5.21 — build/browserunknown/deploymentобоихобразов.
## Локальный unknown UX — 2026-10-08
12.5.21: отдельная строка unknown/runId, сохранение snapshot и сверка по идентификатору при чтении истории. 13 браузерных тестов с HTTP-моками/typecheck/ESLint прошли; это не приёмка real browser/PG. Основной backend4c12c55/frontend673e5ba прежний. Следующий 12.5.22 — сборка и проверка обновлённых образов. Полный 12.5 открыт.
## Локальный unknown UX — 2026-10-08
12.5.21: отдельная строка unknown/runId, сохранение snapshot и сверка по идентификатору при чтении истории. 13 браузерных тестов с HTTP-моками/typecheck/ESLint прошли; это не приёмка real browser/PG. Основной backend4c12c55/frontend673e5ba прежний. Следующий 12.5.22 — сборка и проверка обновлённых образов. Полный 12.5 открыт.
+1 -1
View File
@@ -1,6 +1,6 @@
# План реализации UX/UI по пятичасовым окнам
Актуальное продолжение 2026-10-08:12.5.20 завершёнлокально — unknown502/currentrunId не переписываетподтверждённыйsnapshot пробы.2unit tests/frontendtypecheck passed;временнаястроказапросаещёerror,полныйbrowserunknownUX открыт. Runtimebackend4c12c55/frontend673e5ba прежние. Следующий12.5.21 — build/browserunknownspeed/deploymentобоихобразов.
Актуальное продолжение 2026-10-08: 12.5.21 завершён локально — speed unknown отображается отдельно от error, сохраняется при чтении пустой истории и сверяется по runId. Последний подтверждённый snapshot пробы сохранён. 13 браузерных тестов с HTTP-моками, frontend typecheck и ESLint прошли. Основной runtime backend4c12c55/frontend673e5ba прежний. Следующий 12.5.22 — сборка обновлённых образов и приёмка на стенде перед deployment.
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-07. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
+17 -1
View File
@@ -1,6 +1,6 @@
# UX/UI: журнал реализации
Актуальное продолжение 2026-10-08:12.5.20 завершёнлокально — unknown502/currentrunId не переписываетподтверждённыйsnapshot пробы.2unit tests/frontendtypecheck passed;временнаястроказапросаещёerror,полныйbrowserunknownUX открыт. Runtimebackend4c12c55/frontend673e5ba прежние. Следующий12.5.21 — build/browserunknownspeed/deploymentобоихобразов.
Актуальное продолжение 2026-10-08: 12.5.21 завершён локально — speed unknown отображается отдельно от error, сохраняется при чтении пустой истории и сверяется по runId. Последний подтверждённый snapshot пробы сохранён. 13 браузерных тестов с HTTP-моками, frontend typecheck и ESLint прошли. Основной runtime backend4c12c55/frontend673e5ba прежний. Следующий 12.5.22 — сборка обновлённых образов и приёмка на стенде перед deployment.
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
@@ -34,6 +34,22 @@
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 |
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в описанных границах:12.3.83,main1186/1186,context221/221. Этап остаётся в работе: стенд12.5.1–3 развёрнут,bootstrap/smoke проверены;остальные сценарии12.5 не выполнены. |
## 12.5.21 — неопределённый исход speed-запуска и сверка с историей (2026-10-08)
В локальной строке запуска введён status unknown: «Сохранение не подтверждено», с видимым runId; это не новый статус серверного DTO (история API по-прежнему принимает только done/error). При обновлении истории незавершённая или неподтверждённая локальная строка сохраняется, если её идентификатора нет в ответе. Подтверждённая строка с тем же runId заменяет локальную, предупреждение снимается. Unknown не подменяет последний подтверждённый snapshot карточки пробы; предупреждение показано без destructive-оформления. Автоматического повторного сетевого теста нет. Ручной новый запуск остаётся доступен; предупреждение просит проверить историю перед повтором. Состояние локальной строки не сохраняется после перезагрузки страницы.
Проверки: 13/13 Playwright (uptime-speed-data и uptime-speed-unknown) за 27.7s, включая unknown → пустая история → done/error, сохранение TX42, один POST и адаптацию 320px. Это live-ветка UI с HTTP-моками, не real PG/browser proof. Scoped frontend typecheck (tmp/tsconfig-speed-check.json, исключён старый ignored tmp build context) и ESLint изменённых файлов прошли. Существующие null/zero, malformed DTO, свежесть, независимые источники и Ping-состояния сохранены.
Основной backend4c12c55/frontend673e5ba не изменён. Новые backend/frontend исправления ещё не развёрнуты. Полный 12.5 открыт. Следующий 12.5.22 — сборка обновлённых образов и приёмка на стенде перед deployment. Локальный коммит без push.
## 12.5.21 — неопределённый исход speed-запуска и сверка с историей (2026-10-08)
В локальной строке запуска введён status unknown: «Сохранение не подтверждено», с видимым runId; это не новый статус серверного DTO (история API по-прежнему принимает только done/error). При обновлении истории незавершённая или неподтверждённая локальная строка сохраняется, если её идентификатора нет в ответе. Подтверждённая строка с тем же runId заменяет локальную, предупреждение снимается. Unknown не подменяет последний подтверждённый snapshot карточки пробы; предупреждение показано без destructive-оформления. Автоматического повторного сетевого теста нет. Ручной новый запуск остаётся доступен; предупреждение просит проверить историю перед повтором. Состояние локальной строки не сохраняется после перезагрузки страницы.
Проверки: 13/13 Playwright (uptime-speed-data и uptime-speed-unknown) за 27.7s, включая unknown → пустая история → done/error, сохранение TX42, один POST и адаптацию 320px. Это live-ветка UI с HTTP-моками, не real PG/browser proof. Scoped frontend typecheck (tmp/tsconfig-speed-check.json, исключён старый ignored tmp build context) и ESLint изменённых файлов прошли. Существующие null/zero, malformed DTO, свежесть, независимые источники и Ping-состояния сохранены.
Основной backend4c12c55/frontend673e5ba не изменён. Новые backend/frontend исправления ещё не развёрнуты. Полный 12.5 открыт. Следующий 12.5.22 — сборка обновлённых образов и приёмка на стенде перед deployment. Локальный коммит без push.
## 12.5.20 — сохранение подтверждённого speed snapshot при unknown receipt (2026-10-08)
Приостатке5%окнавыполненнебольшойлокальныйблокпередdeployment. Frontendcatch раньше ставилlastStatuserror/новыйlastRunAt любойошибкеPOSTspeed-test,включаяunknowncommit. Теперьподтверждённыйsnapshot пробысохраняется,еслиApiClientError502 содержитoutcomeunknown иrunId текущегозапроса. ЧужойrunId/неправильныйpayload/403/обычнаяошибка нераспознаютсякакэтотисход. Существующееservererrorсообщениепоказывается;новыекомпоненты/композицияReUIне менялись. Временнаястрокасамогозапросапо-прежнемуимеетstatuserror; полныйunknown-state/browserUX ещёнужнопроверить.
+17 -1
View File
@@ -1,6 +1,6 @@
# Этап 12: итоговая приёмка UX/UI
Актуальное продолжение 2026-10-08:12.5.20 завершёнлокально — unknown502/currentrunId не переписываетподтверждённыйsnapshot пробы.2unit tests/frontendtypecheck passed;временнаястроказапросаещёerror,полныйbrowserunknownUX открыт. Runtimebackend4c12c55/frontend673e5ba прежние. Следующий12.5.21 — build/browserunknownspeed/deploymentобоихобразов.
Актуальное продолжение 2026-10-08: 12.5.21 завершён локально — speed unknown отображается отдельно от error, сохраняется при чтении пустой истории и сверяется по runId. Последний подтверждённый snapshot пробы сохранён. 13 браузерных тестов с HTTP-моками, frontend typecheck и ESLint прошли. Основной runtime backend4c12c55/frontend673e5ba прежний. Следующий 12.5.22 — сборка обновлённых образов и приёмка на стенде перед deployment.
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
@@ -1154,3 +1154,19 @@ SchedulerSnapshotError раньшеигнорировалruns[].okfalse уuptime
Добавленыlib/speed-persistence-outcome.ts и2meaningfultests;подключенguardвuptime/page.tsx. Обаunit tests passed. ПервыйroottscподхватилкопиистарогоCI contextвignoredtmp ивернулисходныеошибкиэтихкопий. Повторныйполныйfrontendtypecheckсвременнымconfig,которыйсохраняетrootoptions/исключения иисключаетtmp,passed; основнойtsconfigне менялся. Diff-check passed. ReUI skillпрочитан: адаптациясуществующегоэкрана,безновойregistryinstallation/редизайна. Browserunknownspeed/productionbuild/deploymentвэтомблокенезапускались.
Основнойruntimebackend4c12c55/frontend673e5ba сохраняется;backendpatchf408afb иfrontendguardещёнеразвёрнуты. Следующий12.5.21 — собратьобаобновлённыхобраза,проверитьbrowserunknownspeed/историю иразвернутьпослепроверок. Журналобновлён;локальныйкоммитбезpush.
## 12.5.21 — неопределённый исход speed-запуска и сверка с историей (2026-10-08)
В локальной строке запуска введён status unknown: «Сохранение не подтверждено», с видимым runId; это не новый статус серверного DTO (история API по-прежнему принимает только done/error). При обновлении истории незавершённая или неподтверждённая локальная строка сохраняется, если её идентификатора нет в ответе. Подтверждённая строка с тем же runId заменяет локальную, предупреждение снимается. Unknown не подменяет последний подтверждённый snapshot карточки пробы; предупреждение показано без destructive-оформления. Автоматического повторного сетевого теста нет. Ручной новый запуск остаётся доступен; предупреждение просит проверить историю перед повтором. Состояние локальной строки не сохраняется после перезагрузки страницы.
Проверки: 13/13 Playwright (uptime-speed-data и uptime-speed-unknown) за 27.7s, включая unknown → пустая история → done/error, сохранение TX42, один POST и адаптацию 320px. Это live-ветка UI с HTTP-моками, не real PG/browser proof. Scoped frontend typecheck (tmp/tsconfig-speed-check.json, исключён старый ignored tmp build context) и ESLint изменённых файлов прошли. Существующие null/zero, malformed DTO, свежесть, независимые источники и Ping-состояния сохранены.
Основной backend4c12c55/frontend673e5ba не изменён. Новые backend/frontend исправления ещё не развёрнуты. Полный 12.5 открыт. Следующий 12.5.22 — сборка обновлённых образов и приёмка на стенде перед deployment. Локальный коммит без push.
## 12.5.21 — неопределённый исход speed-запуска и сверка с историей (2026-10-08)
В локальной строке запуска введён status unknown: «Сохранение не подтверждено», с видимым runId; это не новый статус серверного DTO (история API по-прежнему принимает только done/error). При обновлении истории незавершённая или неподтверждённая локальная строка сохраняется, если её идентификатора нет в ответе. Подтверждённая строка с тем же runId заменяет локальную, предупреждение снимается. Unknown не подменяет последний подтверждённый snapshot карточки пробы; предупреждение показано без destructive-оформления. Автоматического повторного сетевого теста нет. Ручной новый запуск остаётся доступен; предупреждение просит проверить историю перед повтором. Состояние локальной строки не сохраняется после перезагрузки страницы.
Проверки: 13/13 Playwright (uptime-speed-data и uptime-speed-unknown) за 27.7s, включая unknown → пустая история → done/error, сохранение TX42, один POST и адаптацию 320px. Это live-ветка UI с HTTP-моками, не real PG/browser proof. Scoped frontend typecheck (tmp/tsconfig-speed-check.json, исключён старый ignored tmp build context) и ESLint изменённых файлов прошли. Существующие null/zero, malformed DTO, свежесть, независимые источники и Ping-состояния сохранены.
Основной backend4c12c55/frontend673e5ba не изменён. Новые backend/frontend исправления ещё не развёрнуты. Полный 12.5 открыт. Следующий 12.5.22 — сборка обновлённых образов и приёмка на стенде перед deployment. Локальный коммит без push.
+8
View File
@@ -125,3 +125,11 @@ ssh -N -L 3000:127.0.0.1:3000 -L 8000:127.0.0.1:8000 root@161.104.46.222
## Локальный snapshot guard — 2026-10-08
12.5.20 сохраняетlastconfirmedspeed snapshot дляunknown502/совпадающегоrunId.2unit tests/frontendtypecheck passed;browserunknownUX ещёнепроверен,patchнеразвёрнут. Backend4c12c55/frontend673e5ba прежние. Следующий12.5.21 — build/browserunknown/deploymentобоихобразов.
## Локальный unknown UX — 2026-10-08
12.5.21: отдельная строка unknown/runId, сохранение snapshot и сверка по идентификатору при чтении истории. 13 браузерных тестов с HTTP-моками/typecheck/ESLint прошли; это не приёмка real browser/PG. Основной backend4c12c55/frontend673e5ba прежний. Следующий 12.5.22 — сборка и проверка обновлённых образов. Полный 12.5 открыт.
## Локальный unknown UX — 2026-10-08
12.5.21: отдельная строка unknown/runId, сохранение snapshot и сверка по идентификатору при чтении истории. 13 браузерных тестов с HTTP-моками/typecheck/ESLint прошли; это не приёмка real browser/PG. Основной backend4c12c55/frontend673e5ba прежний. Следующий 12.5.22 — сборка и проверка обновлённых образов. Полный 12.5 открыт.
+65
View File
@@ -0,0 +1,65 @@
import { test, expect } from "@playwright/test"
import { server } from "./fixtures/users"
for (const confirmedStatus of ["done", "error"] as const) {
test(`Speed: unknown receipt сохраняет snapshot и сверяется по runId/${confirmedStatus}`, async ({ page }) => {
let runId = ""
let confirmed = false
let writes = 0
const probe = {
id: "probe-unknown", srcServerId: "1", dstServerId: "2",
srcInterface: "ether1", dstInterface: "ether1", protocol: "tcp", direction: "transmit",
durationSec: "3", enabled: true, lastStatus: "done", lastRunAt: "2026-10-03T00:00:00Z",
lastTxAvgMbps: 42, lastRxAvgMbps: null, lastError: null,
}
await page.route("**/health", route => route.fulfill({ json: { ok: true } }))
await page.route("**/api/**", route => {
const path = new URL(route.request().url()).pathname
if (path === "/api/uptime/speed-test" && route.request().method() === "POST") {
writes++
runId = route.request().postDataJSON().runId
return route.fulfill({ status: 502, json: {
outcome: "unknown", runId,
error: `Сетевой тест завершён, сохранение результата не подтверждено. Проверьте историю по runId ${runId} перед повтором`,
} })
}
return route.fulfill({ json:
path === "/api/auth/config" ? { required: false } :
path === "/api/servers" ? [server, { ...server, id: 2, name: "Приёмник", host: "192.0.2.2" }] :
path === "/api/uptime/overview" ? { probes: [], resources: [] } :
path === "/api/uptime/speed-probes" ? { probes: [probe] } :
path === "/api/uptime/speed-test/runs" ? { runs: confirmed ? [{
id: runId, srcServerId: "1", dstServerId: "2", srcInterface: "ether1", dstInterface: "ether1",
protocol: "tcp", direction: "transmit", durationSec: 3, status: confirmedStatus,
txAvgMbps: confirmedStatus === "done" ? 84 : null, rxAvgMbps: null,
error: confirmedStatus === "error" ? "Подтверждённая ошибка" : null,
createdAt: new Date().toISOString(),
}] : [] } : {},
})
})
await page.goto("/uptime?tab=speed")
await page.getByRole("button", { name: "Пауза", exact: true }).click()
const launch = page.getByRole("button", { name: "Запустить", exact: true })
const card = launch.locator("xpath=../../..")
await expect(card).toContainText("42 Мбит/с")
await launch.click()
await expect(page.getByText("Сохранение не подтверждено", { exact: true })).toBeVisible()
const row = page.locator(`tr[data-row-id="${runId}"]`)
await expect(row).toContainText(`runId: ${runId}`)
await expect(row).not.toContainText("error")
await expect(card).toContainText("42 Мбит/с")
await page.setViewportSize({ width: 320, height: 1000 })
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
await page.getByRole("button", { name: "Обновить", exact: true }).click()
await expect(row).toContainText("Сохранение не подтверждено")
await expect(card).toContainText("42 Мбит/с")
expect(writes).toBe(1)
confirmed = true
await page.getByRole("button", { name: "Обновить", exact: true }).click()
await expect(row).toContainText(confirmedStatus)
await expect(page.getByText("Сохранение не подтверждено", { exact: true })).toHaveCount(0)
await expect(page.getByText(/Проверьте историю по runId/)).toHaveCount(0)
if (confirmedStatus === "done") await expect(row).toContainText("84 Мбит/с")
expect(writes).toBe(1)
})
}