fix(ui): обозначить выбранные состояния и поиск трафика
This commit is contained in:
1 parent
460cf67313
commit
48b7a061c1
6 files changed
+44
-10
No files matched your search
@@ -338,7 +338,7 @@ function StatChip({ icon, label, value }: { icon: ReactNode; label: string; valu
|
||||
|
||||
function ServerCard({ s, selected, onClick }: { s: ServerTraffic; selected: boolean; onClick: () => void }) {
|
||||
return (
|
||||
<button onClick={onClick} className={cn(
|
||||
<button type="button" aria-pressed={selected} onClick={onClick} className={cn(
|
||||
"text-left w-full rounded-lg border p-3 transition-colors hover:bg-muted/50",
|
||||
selected ? "border-primary bg-primary/5" : "border-border bg-card",
|
||||
s.status === "offline" && "opacity-60",
|
||||
@@ -375,7 +375,7 @@ function ServerCard({ s, selected, onClick }: { s: ServerTraffic; selected: bool
|
||||
function UserCard({ u, selected, onClick }: { u: UserTraffic; selected: boolean; onClick: () => void }) {
|
||||
const hasClients = u.interfaces.length > 0
|
||||
return (
|
||||
<button onClick={onClick} className={cn(
|
||||
<button type="button" aria-pressed={selected} onClick={onClick} className={cn(
|
||||
"text-left w-full rounded-lg border p-3 transition-colors hover:bg-muted/50",
|
||||
selected ? "border-primary bg-primary/5" : "border-border bg-card",
|
||||
!hasClients && "opacity-60",
|
||||
@@ -417,7 +417,7 @@ function UserCard({ u, selected, onClick }: { u: UserTraffic; selected: boolean;
|
||||
|
||||
function IfaceCard({ c, selected, onClick }: { c: BoundIfaceTraffic; selected: boolean; onClick: () => void }) {
|
||||
return (
|
||||
<button onClick={onClick} className={cn(
|
||||
<button type="button" aria-pressed={selected} onClick={onClick} className={cn(
|
||||
"text-left w-full rounded-lg border p-3 transition-colors hover:bg-muted/50",
|
||||
selected ? "border-primary bg-primary/5" : "border-border bg-card",
|
||||
c.status === "offline" && "opacity-60",
|
||||
@@ -516,7 +516,7 @@ function DetailHeader({ range, setRange, children }: {
|
||||
<div className="flex items-center gap-2 flex-wrap min-w-0">{children}</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{TRAFFIC_RANGE_KEYS.map((r) => (
|
||||
<Button key={r} size="sm" variant={r === range ? "default" : "ghost"}
|
||||
<Button key={r} type="button" aria-pressed={range === r} size="sm" variant={r === range ? "default" : "ghost"}
|
||||
className="h-7 px-2 text-xs" onClick={() => setRange(r)}>
|
||||
{TRAFFIC_RANGE_LABELS[r]}
|
||||
</Button>
|
||||
@@ -1178,7 +1178,7 @@ export default function TrafficPage() {
|
||||
<div className="border-b bg-background shrink-0">
|
||||
<div role="group" aria-label="Разделы трафика" className="flex items-center overflow-x-auto px-3 sm:px-6">
|
||||
{GROUP_MODES.map(({ mode, icon, label }) => (
|
||||
<button key={mode} onClick={() => handleModeChange(mode)}
|
||||
<button type="button" aria-pressed={effectiveMode === mode} key={mode} onClick={() => handleModeChange(mode)}
|
||||
className={cn(
|
||||
"flex items-center gap-2 px-4 py-3 text-sm font-medium border-b-2 transition-colors -mb-px",
|
||||
effectiveMode === mode
|
||||
@@ -1224,6 +1224,7 @@ export default function TrafficPage() {
|
||||
<div role="group" aria-label="Источник потоков" className="flex gap-1">
|
||||
<button
|
||||
type="button"
|
||||
aria-pressed={flowScope === "servers"}
|
||||
onClick={() => { setFlowScope("servers"); setFlowIface("__all__") }}
|
||||
className={cn(
|
||||
"text-[10px] px-2 py-0.5 rounded border transition-colors",
|
||||
@@ -1236,6 +1237,7 @@ export default function TrafficPage() {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-pressed={flowScope === "users"}
|
||||
onClick={() => { setFlowScope("users"); setFlowIface("__all__"); if (range === "30d") setRange("1h") }}
|
||||
className={cn(
|
||||
"text-[10px] px-2 py-0.5 rounded border transition-colors",
|
||||
@@ -1253,7 +1255,7 @@ export default function TrafficPage() {
|
||||
type="text"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder="Поиск…"
|
||||
aria-label="Поиск потоков" placeholder="Поиск…"
|
||||
className="w-full pl-8 pr-3 h-8 text-xs bg-muted/50 border border-border rounded-md outline-none focus:border-primary transition-colors"
|
||||
/>
|
||||
</div>
|
||||
@@ -1341,7 +1343,7 @@ export default function TrafficPage() {
|
||||
type="text"
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
placeholder="Поиск…"
|
||||
aria-label="Поиск трафика" placeholder="Поиск…"
|
||||
className="w-full pl-8 pr-3 h-8 text-xs bg-muted/50 border border-border rounded-md outline-none focus:border-primary transition-colors"
|
||||
/>
|
||||
</div>
|
||||
@@ -1350,7 +1352,7 @@ export default function TrafficPage() {
|
||||
<div className="flex gap-1 flex-wrap">
|
||||
<span className="text-[10px] text-muted-foreground self-center mr-0.5">Сортировка:</span>
|
||||
{visibleSortFields.map(({ field, label }) => (
|
||||
<button key={field} onClick={() => toggleSort(field)}
|
||||
<button type="button" aria-pressed={sortField === field} key={field} onClick={() => toggleSort(field)}
|
||||
className={cn(
|
||||
"text-[10px] px-2 py-0.5 rounded border transition-colors",
|
||||
sortField === field
|
||||
@@ -1391,6 +1393,7 @@ export default function TrafficPage() {
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-[11px] text-muted-foreground mr-1">Интерфейс:</span>
|
||||
<button
|
||||
type="button" aria-pressed={hideDisabledIfaces}
|
||||
onClick={() => setHideDisabledIfaces((v) => !v)}
|
||||
className={cn(
|
||||
"inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium transition-all",
|
||||
@@ -1402,6 +1405,7 @@ export default function TrafficPage() {
|
||||
{hideDisabledIfaces ? "Показать отключенные" : "Скрыть отключенные"}
|
||||
</button>
|
||||
<button
|
||||
type="button" aria-pressed={selectedIface === "__all__"}
|
||||
onClick={() => setSelectedIface("__all__")}
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1 rounded-full border px-2.5 py-0.5 text-[10px] font-medium transition-all",
|
||||
@@ -1417,6 +1421,7 @@ export default function TrafficPage() {
|
||||
return (
|
||||
<button
|
||||
key={`${iface.name}:${index}`}
|
||||
type="button" aria-pressed={active}
|
||||
onClick={() => setSelectedIface(iface.name)}
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-[10px] font-medium transition-all",
|
||||
|
||||
@@ -61,6 +61,7 @@ export function FlowEntityCardView({
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
aria-pressed={selected}
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"text-left w-full rounded-lg border p-3 transition-colors hover:bg-muted/50",
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@ flowchart LR
|
||||
|
||||
Ближайший оставшийся объём:
|
||||
|
||||
1. Молчащий SSE и CRLF/heartbeat проверены в блоках 8.6–8.7. Одиночные точки графика исправлены в 8.8; ожидание первого sample проверено в 8.9. Выбор месяца реализован в 8.10, каталог неактивных серверов — в 8.11. Следующий шаг — доступность и итоговая приёмка.
|
||||
1. Молчащий SSE и CRLF/heartbeat проверены в блоках 8.6–8.7. Одиночные точки графика исправлены в 8.8; ожидание первого sample проверено в 8.9. Выбор месяца реализован в 8.10, каталог неактивных серверов — в 8.11. Подписи и выбранные состояния исправлены в 8.12; следующий шаг — оставшийся аудит доступности и итоговая регрессия.
|
||||
2. Итоговая приёмка этапа 8: ReUI/accessibility checklist и регрессия; удалённые серверы остаются ограничением. Промежуточная матрица проверок сохранена в [этапе 8](ux-ui-stage-8.md).
|
||||
3. После фиксации результата этапа 8 — этап 9.
|
||||
|
||||
|
||||
@@ -24,13 +24,20 @@
|
||||
| 5 | Состояния данных, свежесть, частичные сбои, гонки polling | Завершён 2026-09-20 для дашборда; границы проверок ниже |
|
||||
| 6 | Навигация и дашборд | Завершён 2026-09-20 в границах локальной приёмки; ограничения ниже |
|
||||
| 7 | Серверы и мониторинг: детали, формы, URL-контекст, права | Завершён в границах локальной приёмки 2026-09-23; внешние ограничения ниже |
|
||||
| 8 | Трафик и статистика | В работе: выполнены блоки 8.1–8.11; каталог месячного отчёта готов, далее доступность и приёмка |
|
||||
| 8 | Трафик и статистика | В работе: выполнены блоки 8.1–8.12; подписи/состояния исправлены, далее итоговая регрессия |
|
||||
| 9 | VPN, Firewall и Filters | Не начат |
|
||||
| 10 | Маршрутизация, диагностика, карта и терминал | Не начат |
|
||||
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Не начат |
|
||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
|
||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
||||
|
||||
## Блок 8.12 — доступные имена и выбранные состояния (2026-09-28)
|
||||
|
||||
- Получен актуальный ReUI get_audit_checklist. Поисковые поля трафика/потоков получили доступные имена. Карточки, разделы, источник flow, периоды и интерфейсы обозначают выбранное состояние через aria-pressed; затронутые native-кнопки получили type=button.
|
||||
- Новый E2E проверяет выбор flow-сервера через Enter, сохранение его заголовка при поиске другой карточки, открытие месяца через Space и aria-pressed. Сценарий, lint, diff-check и production build/TypeScript прошли (34 страницы).
|
||||
- Это профильный блок доступности, не полный аудит: ещё проверить общую Tab-навигацию/видимый фокус, слои/Escape, статусы загрузки и статистику, затем выполнить итоговый набор трафика/статистики и backend-switch. Этап 8 не закрыт. Реальные RouterOS/IPFIX/JWT не проверены; удалённые серверы и экспорт остаются ограничениями.
|
||||
- Использовано 97% пятичасового и 31% недельного лимита. Следующий запуск начать с лимита и итоговой регрессии после оставшегося аудита. Локальный коммит без push.
|
||||
|
||||
## Блок 8.11 — каталог серверов месячного отчёта (2026-09-28)
|
||||
|
||||
- Для месячного режима список загружается через существующий listServers, независимо от активности IPFIX за 24 часа. Выбор доступен даже при полном отсутствии активных экспортёров. В списке только имена; скорости, графики и статус без подтверждения не подставляются. Сортировка по live-метрикам скрыта, поиск работает по имени.
|
||||
|
||||
@@ -54,3 +54,5 @@
|
||||
Обновление 8.10 (2026-09-28): добавлен выбор прошлого/текущего месяца UTC; валидация не отправляет пустые/будущие значения, ключ запроса защищает от позднего отчёта. 23 прежних и 1 новый flow-E2E прошли, lint/build чистые. Ограничение списка серверов по активности 24 часа остаётся следующим шагом; полный аудит доступности ещё не завершён.
|
||||
|
||||
Обновление 8.11 (2026-09-28): месячный режим выбирает сервер из каталога независимо от активности за 24 часа. 25 flow-E2E и дополнительная проверка пустого списка экспортёров прошли, как и lint/build. Удалённые из каталога серверы не перечисляются; далее доступность и итоговая регрессия этапа 8.
|
||||
|
||||
Обновление 8.12: получен checklist ReUI; имена поиска и aria-pressed исправлены. Клавиатурный E2E Enter/Space, lint/build прошли. Полный аудит Tab/фокуса/слоёв/статистики и итоговая регрессия ещё нужны; этап не закрыт.
|
||||
@@ -427,3 +427,22 @@ test('Flow: месячный отчёт доступен серверу без
|
||||
await expect(inactive).toHaveAttribute('aria-pressed', 'true')
|
||||
await expect(page.getByRole('region', { name: 'Месячный отчёт' })).toBeVisible()
|
||||
})
|
||||
|
||||
|
||||
test('Flow: клавиатурный выбор сервера и месяца имеет доступное состояние', async ({ page }) => {
|
||||
await setup(page)
|
||||
await expect(page.getByRole('group', { name: 'Разделы трафика' }).getByRole('button', { name: 'Потоки', exact: true })).toHaveAttribute('aria-pressed', 'true')
|
||||
const second = page.getByRole('button').filter({ hasText: 'Экспортёр второй' })
|
||||
await second.focus()
|
||||
await page.keyboard.press('Enter')
|
||||
await expect(second).toHaveAttribute('aria-pressed', 'true')
|
||||
await expect(page.getByRole('heading', { name: 'Экспортёр второй', exact: true })).toBeVisible()
|
||||
await page.getByRole('textbox', { name: 'Поиск потоков', exact: true }).fill('Экспортёр тест')
|
||||
await expect(page.getByRole('heading', { name: 'Экспортёр второй', exact: true })).toBeVisible()
|
||||
await page.getByRole('textbox', { name: 'Поиск потоков', exact: true }).fill('')
|
||||
const month = page.getByRole('button', { name: 'Месяц', exact: true })
|
||||
await month.focus()
|
||||
await page.keyboard.press('Space')
|
||||
await expect(month).toHaveAttribute('aria-pressed', 'true')
|
||||
await expect(page.getByLabel('Месяц отчёта (UTC)', { exact: true })).toBeVisible()
|
||||
})
|
||||
Reference in new issue
Block a user