fix(ui): выбирать неактивные серверы для месячного отчёта

This commit is contained in:
Denozordec committed 2026-09-28 16:59:40 +07:00
1 parent 7dd377d7c3
commit 460cf67313
6 files changed
+76 -33

No files matched your search

+28 -2
View File
@@ -2,13 +2,14 @@ import { installOpenLiveStream, resumeLiveStream, sendLiveHeartbeat } from "./li
import { test, expect, type Page } from '@playwright/test'
async function setup(page: Page) {
const control = { hold: '', holdIface: '', fail: false, failList: false, invalidSample: undefined as unknown, mergeSample: false, metrics: {} as Record<string, unknown>, release: () => {} }
const control = { emptyExporters: false, failCatalog: false, hold: '', holdIface: '', fail: false, failList: false, invalidSample: undefined as unknown, mergeSample: false, metrics: {} as Record<string, unknown>, release: () => {} }
const gate = new Promise<void>(resolve => { control.release = resolve })
const card = { id: '1', name: 'Экспортёр тест', subtitle: '', site: 'TEST', country: 'RU', status: 'online', rxNow: 2, txNow: 3, sessions: 1, rxSeries: [1, 2], txSeries: [2, 3], bytes: 200 }
const data = { bpsNow: 1000000, bytes: 200, packets: 2, conversations: 1, uniqueSrc: 1, uniqueDst: 1, topProto: 'TCP', rxSeries: [1], txSeries: [2], applications: [], protocols: [], sources: [], destinations: [], interfaces: [], conversationsList: [], ifaces: [], live: false }
await page.route('**/health', r => r.fulfill({ json: { ok: true } }))
await page.route('**/api/**', async route => {
const url = new URL(route.request().url()), path = url.pathname, range = url.searchParams.get('range')
if (path === '/api/servers') return route.fulfill(control.failCatalog ? { status: 403, json: { error: 'Каталог недоступен' } } : { json: [1, 2, 3].map(id => ({ id, name: id === 3 ? 'Сервер без потоков' : id === 2 ? 'Экспортёр второй' : 'Экспортёр тест', host: '127.0.0.1', port: 8728, useSsl: false, verifySsl: false, username: 'admin', password: '', type: 'jump-host', site: 'TEST', country: 'RU', asn: '', comment: '', enabled: true, lanSubnet: '', wanUplinks: [], createdAt: '', updatedAt: '', status: null, latency: null, os: null, model: null, uptime: null, cpuLoad: null, freeMemory: null, totalMemory: null, identityName: null, sessions: 0, polledAt: null })) })
if (path === '/api/traffic/flow/monthly' && control.hold === url.searchParams.get('month')) await gate
if (path === '/api/traffic/flow/monthly') return route.fulfill({ json: { month: url.searchParams.get('month'), bytes: 1500, countries: [{ id: 'RU', label: 'Россия', bytes: 1500, packets: 1, bps: 0, percent: 100 }], services: [], asns: [] } })
if (path === '/api/traffic/flow/analytics') {
@@ -19,7 +20,7 @@ async function setup(page: Page) {
if (path === '/api/traffic/flow/live') return route.fulfill({ contentType: 'text/event-stream', body: 'event: sample\ndata: ' + JSON.stringify({ ...data, live: true }) + '\n\n' + (control.invalidSample === undefined ? '' : 'event: sample\ndata: ' + JSON.stringify(control.mergeSample ? { ...data, ...(control.invalidSample as object) } : control.invalidSample) + '\n\n') })
if (path === '/api/traffic/flow/exporters' && control.failList) return route.fulfill({ status: 403, json: { error: 'Нет доступа к экспортёрам' } })
const json = path === '/api/auth/config' ? { required: false }
: path === '/api/traffic/flow/exporters' ? { exporters: [card, { ...card, id: '2', name: 'Экспортёр второй' }] }
: path === '/api/traffic/flow/exporters' ? { exporters: control.emptyExporters ? [] : [card, { ...card, id: '2', name: 'Экспортёр второй' }] }
: path === '/api/traffic/flow/clients' ? { clients: [{ ...card, id: 'client1', name: 'Клиент тест' }] }
: path === '/api/traffic/flows' ? { exportersOnline: 1, bytesPerMin: 1, uniqueSrc: 1, uniqueDst: 1, topProto: 'TCP', talkers: [], listenerBound: true }
: path === '/api/traffic/servers' ? { servers: [] }
@@ -401,3 +402,28 @@ test('Flow: выбор месяца игнорирует поздний отчё
await expect(field).toBeFocused()
await expect(page.getByRole('button', { name: 'Месяц', exact: true })).toHaveAttribute('aria-pressed', 'true')
})
test('Flow: месячный отчёт доступен серверу без активных потоков; ошибка каталога допускает повтор', async ({ page }) => {
const control = await setup(page)
control.emptyExporters = true
await page.getByRole('button', { name: 'Обновить', exact: true }).click()
await expect(page.getByRole('heading', { name: 'Выберите сервер или клиента', exact: true })).toBeVisible()
await page.getByRole('button', { name: 'Месяц', exact: true }).click()
const inactive = page.getByRole('button', { name: 'Сервер без потоков', exact: true })
const request = page.waitForRequest(r => r.url().includes('/flow/monthly?') && r.url().includes('serverId=3'))
await inactive.click()
await request
await expect(inactive).toHaveAttribute('aria-pressed', 'true')
await expect(page.getByRole('heading', { name: 'Сервер без потоков', exact: true })).toBeVisible()
await expect(page.getByRole('region', { name: 'Месячный отчёт' })).toBeVisible()
await expect(page.getByLabel('Мини-график истории RX/TX')).toHaveCount(0)
control.failCatalog = true
await page.getByRole('button', { name: 'Обновить', exact: true }).click()
await expect(page.getByRole('alert').filter({ hasText: 'Потоки недоступны' })).toContainText('Каталог недоступен')
await expect(page.getByRole('region', { name: 'Месячный отчёт' })).toHaveCount(0)
control.failCatalog = false
await page.getByRole('button', { name: 'Повторить загрузку потоков', exact: true }).click()
await expect(inactive).toHaveAttribute('aria-pressed', 'true')
await expect(page.getByRole('region', { name: 'Месячный отчёт' })).toBeVisible()
})