fix(ui): отображать одиночные измерения на графике трафика

This commit is contained in:
Denozordec committed 2026-09-28 16:47:17 +07:00
1 parent ed491c313f
commit 88a8b5f02f
5 files changed
+47 -5

No files matched your search

+7 -2
View File
@@ -85,6 +85,11 @@ export function TrafficRxTxChart({
const hasRx = data.some(point => point.rx !== null)
const hasTx = data.some(point => point.tx !== null)
const hasData = hasRx || hasTx
const isolatedDot = (direction: "rx" | "tx") => function IsolatedDot({ cx, cy, index }: { cx?: number; cy?: number; index?: number }) {
if (index == null || cx == null || cy == null || data[index]?.[direction] == null
|| data[index - 1]?.[direction] != null || data[index + 1]?.[direction] != null) return <g />
return <circle cx={cx} cy={cy} r={3} fill={`var(--chart-${direction})`} data-isolated-direction={direction} />
}
const chart = !hasData ? (
<p role="status" className="flex min-h-[220px] items-center justify-center text-sm text-muted-foreground">Нет исторических измерений за выбранный период</p>
) : (
@@ -119,14 +124,14 @@ export function TrafficRxTxChart({
type="monotone"
stroke="var(--chart-rx)"
strokeWidth={2}
dot={false}
dot={isolatedDot("rx")}
/>}
{hasTx && <Line
dataKey="tx"
type="monotone"
stroke="var(--chart-tx)"
strokeWidth={2}
dot={false}
dot={isolatedDot("tx")}
/>}
</LineChart>
</ChartContainer>
+2 -2
View File
@@ -88,8 +88,8 @@ flowchart LR
Ближайший оставшийся объём:
1. Молчащий SSE и CRLF/heartbeat проверены в блоках 8.6–8.7. Далее — одиночная точка основного графика и ожидание первого sample.
2. Итоговая приёмка этапа 8: решение по архивным серверам/месяцам, проверка единичной точки графика, ReUI/accessibility checklist. Промежуточная матрица проверок сохранена в [этапе 8](ux-ui-stage-8.md).
1. Молчащий SSE и CRLF/heartbeat проверены в блоках 8.6–8.7. Одиночные точки графика исправлены в 8.8. Далее — ожидание первого sample.
2. Итоговая приёмка этапа 8: решение по архивным серверам/месяцам, ReUI/accessibility checklist. Промежуточная матрица проверок сохранена в [этапе 8](ux-ui-stage-8.md).
3. После фиксации результата этапа 8 — этап 9.
## Порядок продолжения и журналирования
+8 -1
View File
@@ -24,13 +24,20 @@
| 5 | Состояния данных, свежесть, частичные сбои, гонки polling | Завершён 2026-09-20 для дашборда; границы проверок ниже |
| 6 | Навигация и дашборд | Завершён 2026-09-20 в границах локальной приёмки; ограничения ниже |
| 7 | Серверы и мониторинг: детали, формы, URL-контекст, права | Завершён в границах локальной приёмки 2026-09-23; внешние ограничения ниже |
| 8 | Трафик и статистика | В работе: выполнены блоки 8.1–8.7; CRLF/heartbeat проверены, далее оставшийся аудит |
| 8 | Трафик и статистика | В работе: выполнены блоки 8.1–8.8; одиночные точки графика исправлены, далее оставшийся аудит |
| 9 | VPN, Firewall и Filters | Не начат |
| 10 | Маршрутизация, диагностика, карта и терминал | Не начат |
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Не начат |
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
## Блок 8.8 — одиночные точки основного графика (2026-09-28)
- Общий TrafficRxTxChart показывает маркер только для валидной точки без валидных соседей в том же направлении. Одиночный ноль и точки между пропусками видны; непрерывные серии сохраняют прежний вид без маркеров. RX/TX используют одинаковое правило и существующие цвета темы.
- До финального подключения TX прошли 38 E2E деталей/flow/дашборда. Новый тест выявил пропущенное подключение TX; после исправления все 10 тестов деталей прошли, включая нулевые RX/TX, три изолированные точки и порядок координат. Снимок изолированных RX осмотрен. Итоговые ESLint, diff-check и production build/TypeScript прошли (34 страницы).
- Граница: это отображение существующих точек, без изменения временной шкалы API или правил агрегации. Полная регрессия проекта не выполнялась; реальные RouterOS/IPFIX не проверены.
- Далее: ожидание первого SSE sample, решение по месячным ограничениям и ReUI/accessibility checklist. Этап 8 ещё открыт. Пятичасовой лимит обновился; на контрольной точке использовано 18%, недельного — 18%. Локальный коммит без push.
## Блок 8.7 — CRLF и heartbeat SSE (2026-09-28)
- В обоих hook разделители событий и строк принимают LF/CRLF, включая разделитель между двумя chunk. Комментарий перед event/data больше не отбрасывает весь блок: строки комментариев пропускаются парсером, sample сохраняется.
+2
View File
@@ -46,3 +46,5 @@
Обновление 8.6 (2026-09-28): молчащий SSE обработан в обоих hook — 15 секунд без валидного sample снимают live, сохраняя данные; следующий sample восстанавливает live. 24 прежних и 2 новых сценария прошли, как и lint/build. Следующий шаг — оставшийся аудит CRLF/heartbeat, первого sample, единичной точки графика и месячных ограничений; этап пока открыт.
Обновление 8.7 (2026-09-28): LF/CRLF и комментарий перед event поддержаны в обоих hook. Два потоковых сценария проверяют разделитель между chunk и heartbeat, не продлевающий свежесть. Все 26 профильных E2E прошли; далее одиночная точка графика и остальные ограничения, перечисленные выше.
Обновление 8.8 (2026-09-28): одиночные точки RX/TX, включая нули между пропусками, получили маркеры. Итоговые 10 тестов деталей и build/lint прошли; перед финальным подключением TX проверены 38 сценариев потребителей. Далее — первый SSE sample и оставшаяся приёмка.
+28
View File
@@ -147,3 +147,31 @@ test('Итоги трафика: Sparkline сохраняет разрывы и
await expect(page.getByLabel('Нет истории измерений')).toHaveCount(2)
await expect(page.getByRole('img', { name: 'История измерений', exact: true })).toHaveCount(0)
})
test('История RX/TX: одиночные значения и ноль видны между пропусками', async ({ page }) => {
const control = await setup(page)
const history = page.getByRole('region', { name: 'История RX/TX' })
control.series = [0]
control.txSeries = []
await page.getByRole('button', { name: '24ч', exact: true }).click()
const dots = history.locator('circle[data-isolated-direction]')
await expect(dots).toHaveCount(1)
await expect(dots.first()).toBeVisible()
await expect(dots.first()).toHaveAttribute('data-isolated-direction', 'rx')
expect(await dots.first().getAttribute('cx')).not.toMatch(/NaN|Infinity/)
expect(await dots.first().getAttribute('cy')).not.toMatch(/NaN|Infinity/)
control.series = [2, null, 0, null, 3]
control.txSeries = [1, 2, 3, 4, 5]
await page.getByRole('button', { name: '15м', exact: true }).click()
await expect(history.locator('circle[data-isolated-direction="rx"]')).toHaveCount(3)
await expect(history.locator('circle[data-isolated-direction="tx"]')).toHaveCount(0)
const positions = await dots.evaluateAll(nodes => nodes.map(node => Number(node.getAttribute('cx'))))
expect(positions[0]).toBeLessThan(positions[1])
expect(positions[1]).toBeLessThan(positions[2])
await history.screenshot({ path: "test-results/traffic-isolated-points.png" })
control.series = [1, 2, 3]
control.txSeries = [0]
await page.getByRole('button', { name: '4ч', exact: true }).click()
await expect(history.locator('circle[data-isolated-direction="rx"]')).toHaveCount(0)
await expect(history.locator('circle[data-isolated-direction="tx"]')).toHaveCount(1)
})