fix(ui): отличать пустую историю трафика от нулевой
This commit is contained in:
1 parent
7c6f937cec
commit
ec8e28d15d
4 files changed
+35
-12
No files matched your search
@@ -762,8 +762,8 @@ const toLiveServer = (s: LiveTrafficServer): ServerTraffic => {
|
||||
rxTotal: s.rxTotal,
|
||||
txTotal: s.txTotal,
|
||||
sessions: s.sessions,
|
||||
rxSeries: s.rxSeries.length ? s.rxSeries : Array(60).fill(0),
|
||||
txSeries: s.txSeries.length ? s.txSeries : Array(60).fill(0),
|
||||
rxSeries: s.rxSeries,
|
||||
txSeries: s.txSeries,
|
||||
boundIfaces: [],
|
||||
}
|
||||
}
|
||||
@@ -771,8 +771,8 @@ const toLiveServer = (s: LiveTrafficServer): ServerTraffic => {
|
||||
const toLiveBound = (c: BoundIfaceTraffic): BoundIfaceTraffic => ({
|
||||
...c,
|
||||
interfaceType: (["ether", "gre", "wg", "other"].includes(c.interfaceType) ? c.interfaceType : "other") as InterfaceType,
|
||||
rxSeries: c.rxSeries.length ? c.rxSeries : Array(60).fill(0),
|
||||
txSeries: c.txSeries.length ? c.txSeries : Array(60).fill(0),
|
||||
rxSeries: c.rxSeries,
|
||||
txSeries: c.txSeries,
|
||||
})
|
||||
|
||||
const toLiveUser = (u: UserTraffic & { interfaces?: BoundIfaceTraffic[] }): UserTraffic => {
|
||||
@@ -781,8 +781,8 @@ const toLiveUser = (u: UserTraffic & { interfaces?: BoundIfaceTraffic[] }): User
|
||||
...u,
|
||||
role: ROLE_LABEL[u.role as keyof typeof ROLE_LABEL] ?? u.role,
|
||||
interfaces: ifaces,
|
||||
rxSeries: u.rxSeries.length ? u.rxSeries : Array(60).fill(0),
|
||||
txSeries: u.txSeries.length ? u.txSeries : Array(60).fill(0),
|
||||
rxSeries: u.rxSeries,
|
||||
txSeries: u.txSeries,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,8 +60,8 @@ function toChartData(rx: number[], tx: number[], rangeMinutes: number) {
|
||||
const minutesAgo = Math.round((1 - i / denom) * rangeMinutes)
|
||||
return {
|
||||
time: formatRangeAgo(minutesAgo),
|
||||
rx: rx[i] ?? 0,
|
||||
tx: tx[i] ?? 0,
|
||||
rx: Number.isFinite(rx[i]) && rx[i] >= 0 ? rx[i] : null,
|
||||
tx: Number.isFinite(tx[i]) && tx[i] >= 0 ? tx[i] : null,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -82,8 +82,11 @@ export function TrafficRxTxChart({
|
||||
const data = toChartData(rx, tx, rangeMinutes)
|
||||
const tickEvery = Math.max(1, Math.ceil(data.length / 6))
|
||||
|
||||
const chart = (
|
||||
<div className="flex flex-col gap-4">
|
||||
const hasData = data.some(point => point.rx !== null || point.tx !== null)
|
||||
const chart = !hasData ? (
|
||||
<p role="status" className="flex min-h-[220px] items-center justify-center text-sm text-muted-foreground">Нет исторических измерений за выбранный период</p>
|
||||
) : (
|
||||
<div role="region" aria-label="История RX/TX" className="flex flex-col gap-4">
|
||||
<ChartContainer config={chartConfig} className="-ms-4 aspect-auto h-[220px] w-full">
|
||||
<LineChart data={data} margin={{ top: 5, right: 5, left: 5, bottom: 5 }}>
|
||||
<CartesianGrid
|
||||
|
||||
@@ -29,6 +29,13 @@
|
||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
|
||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
||||
|
||||
## Блок 8.4Д — пустая история RX/TX (2026-09-24)
|
||||
|
||||
- Live-преобразования серверов, клиентов и интерфейсов сохраняют пустые массивы истории вместо заполнения 60 нулями. Общий TrafficRxTxChart показывает «Нет исторических измерений за выбранный период», если обе серии не содержат валидных точек.
|
||||
- Пропущенные, отрицательные и нечисловые точки графика преобразуются в null, а не в ноль. Подтверждённые нулевые ряды остаются данными и отображаются графиком.
|
||||
- 22 E2E деталей/flow прошли, включая переход от пустой истории к [0, 0]. Lint графика/теста и diff-check чистые. Production build/TypeScript прошли (34 страницы). На странице остаётся ранее известная ошибка lint flowIface.
|
||||
- Далее: проверка частично отсутствующих рядов/tooltip, мини-графиков карточек и свежести данных; смена backend/сервера и поздний SSE, итоговая приёмка этапа 8. Изменён общий график: остальные его потребители требуют общей регрессии в приёмке; полную регрессию проекта здесь не выполняли. Реальные IPFIX/RouterOS не проверены. Использовано 93% пятичасового и 77% недельного лимита. Локальный коммит без push.
|
||||
|
||||
## Блок 8.4Г — неизвестные дополнительные flow-метрики (2026-09-24)
|
||||
|
||||
- Overlay/Wire GRE/mesh больше не подставляют ноль при отсутствии измерения. Отсутствующие, отрицательные и нечисловые значения показывают «Нет данных». Подтверждённый ноль сохраняется как 0 Кбит/с или 0 Б; нулевой объём overlay теперь виден в подписи.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { test, expect, type Page } from '@playwright/test'
|
||||
|
||||
async function setup(page: Page) {
|
||||
const control = { holdRange: '', failIface: false, release: () => {} }
|
||||
const control = { holdRange: '', failIface: false, series: undefined as number[] | undefined, release: () => {} }
|
||||
const gate = new Promise<void>(resolve => { control.release = resolve })
|
||||
const server = { id: '1', name: 'Первый сервер', site: 'TEST', country: 'RU', status: 'online', rxNow: 2, txNow: 3, rxPeak: 5, txPeak: 6, rxTotal: 100, txTotal: 200, sessions: 1, rxSeries: [1, 2], txSeries: [2, 3] }
|
||||
await page.route('**/health', route => route.fulfill({ json: { ok: true } }))
|
||||
@@ -12,7 +12,7 @@ async function setup(page: Page) {
|
||||
const range = url.searchParams.get('range'), iface = url.searchParams.get('iface')
|
||||
if (control.holdRange && range === control.holdRange) await gate
|
||||
if (control.failIface && iface === 'ether2') return route.fulfill({ status: 500, json: { error: 'Интерфейс временно недоступен' } })
|
||||
return route.fulfill({ json: { server: { ...server, name: `Детали ${range} ${iface}`, rxNow: range === '24h' ? 24 : 15 } } })
|
||||
return route.fulfill({ json: { server: { ...server, rxSeries: control.series ?? server.rxSeries, txSeries: control.series ?? server.txSeries, name: `Детали ${range} ${iface}`, rxNow: range === '24h' ? 24 : 15 } } })
|
||||
}
|
||||
const json = path === '/api/auth/config' ? { required: false }
|
||||
: path === '/api/traffic/servers' ? { servers: [server] }
|
||||
@@ -72,3 +72,16 @@ for (const width of [320, 390]) {
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth + 1)).toBe(true)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
test('Трафик: пустая история отличается от нулевой', async ({ page }) => {
|
||||
const control = await setup(page)
|
||||
control.series = []
|
||||
await page.getByRole('button', { name: '24ч', exact: true }).click()
|
||||
await expect(page.getByText('Нет исторических измерений за выбранный период', { exact: true })).toBeVisible()
|
||||
await expect(page.getByRole('region', { name: 'История RX/TX' })).toHaveCount(0)
|
||||
control.series = [0, 0]
|
||||
await page.getByRole('button', { name: '15м', exact: true }).click()
|
||||
await expect(page.getByRole('region', { name: 'История RX/TX' })).toBeVisible()
|
||||
await expect(page.getByText('Нет исторических измерений за выбранный период', { exact: true })).toHaveCount(0)
|
||||
})
|
||||
Reference in new issue
Block a user