import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import test from 'node:test'; import { readStyleSource } from './style-source.js'; const root = path.resolve(import.meta.dirname, '../..'); const overview = fs.readFileSync(path.join(root, 'src/web/components/ClientOverviewPage.tsx'), 'utf8'); const feature = fs.readFileSync(path.join(root, 'src/web/features/instructions/InstructionsFeature.tsx'), 'utf8'); const instructions = fs.readFileSync(path.join(root, 'src/web/features/instructions/instructionBlocks.ts'), 'utf8'); const prometheus = fs.readFileSync(path.join(root, 'src/web/features/instructions/prometheus.ts'), 'utf8'); const styles = readStyleSource(root); const dashboard = JSON.parse(fs.readFileSync(path.join(root, 'monitoring/grafana/harbor-gateway.json'), 'utf8')); test('Gateway info drawer contains copyable Prometheus and Grafana instructions', () => { assert.match(instructions, /\.\.\.\(isGateway \? \[\{/); assert.match(instructions, /id: 'prometheus'/); assert.match(instructions, /title: 'Prometheus и Grafana'/); assert.match(instructions, /prometheusScrapeConfig\(controlHost\)/); assert.match(instructions, /label: 'prometheus\.yml'/); assert.match(instructions, /label: 'Grafana dashboard'/); assert.match(prometheus, /monitoring\/grafana\/harbor-gateway\.json/); assert.match(prometheus, /scrape_interval: 30s[\s\S]*metrics_path: \/metrics/); assert.match(overview, /const controlHost = window\.location\.host/); assert.match(feature, /<\/button>/); assert.match(styles, /\.client-instruction-copy-button \{[\s\S]*width: 104px;[\s\S]*min-width: 104px/); assert.match(styles, /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.client-copy-feedback/); }); test('Grafana dashboard uses one all-or-one device scope and shows active device speed', () => { const expressions = dashboard.panels.flatMap((panel) => panel.targets || []).map(({ expr }) => expr).filter(Boolean); const titles = dashboard.panels.map(({ title }) => title); const deviceVariable = dashboard.templating.list[0]; const freshness = dashboard.panels.find(({ id }) => id === 2); const globalSpeed = dashboard.panels.find(({ id }) => id === 3); const topDevices = dashboard.panels.find(({ id }) => id === 5); const sourceSpeed = dashboard.panels.find(({ id }) => id === 6); const deviceSpeed = dashboard.panels.find(({ id }) => id === 14); const sourceTraffic = dashboard.panels.find(({ id }) => id === 7); const services = dashboard.panels.find(({ id }) => id === 8); const domains = dashboard.panels.find(({ id }) => id === 9); const totalTraffic = dashboard.panels.find(({ id }) => id === 1); const trafficBySource = dashboard.panels.find(({ id }) => id === 4); const speedPanels = [globalSpeed, deviceSpeed, sourceSpeed]; assert.equal(dashboard.title, 'Harbor Gateway: трафик'); assert.equal(dashboard.refresh, '30s'); assert.equal(dashboard.version, 7); assert.deepEqual(titles.filter((title) => ['Обзор', 'Устройства', 'Сервисы и домены', 'Техническая детализация'].includes(title)), [ 'Обзор', 'Устройства', 'Сервисы и домены', 'Техническая детализация', ]); assert.ok(titles.includes('Скорость за выбранный период')); assert.ok(titles.includes('Скорость по устройствам')); assert.ok(titles.includes('Скорость по источникам и направлениям')); assert.ok(titles.includes('Топ-10 устройств за выбранный период')); assert.ok(titles.includes('Трафик по источникам и направлениям за выбранный период')); assert.ok(titles.includes('Сервисы за выбранный период')); assert.ok(titles.includes('Домены за выбранный период')); assert.ok(titles.includes('Весь Harbor за выбранный период')); assert.ok(titles.includes('Источники за выбранный период')); assert.ok(expressions.some((expression) => expression.includes('harbor_traffic_bytes_total'))); assert.ok(expressions.some((expression) => expression.includes('harbor_device_traffic_bytes_total'))); assert.ok(expressions.some((expression) => expression.includes('harbor_device_domain_traffic_bytes_total{device_id=~"$device_id"}[$__range]'))); assert.ok(expressions.some((expression) => expression.includes('harbor_domain_traffic_last_observed_timestamp_seconds'))); assert.match(topDevices.targets[0].expr, /^topk\(10,[\s\S]*increase\(harbor_device_traffic_bytes_total\[\$__range\]\)[\s\S]*> 0[\s\S]*group_left \(name, ip\)/); assert.equal(topDevices.options.sortBy[0].desc, true); assert.match(topDevices.fieldConfig.overrides[1].properties[0].value[0].url, /var-device_id=\$\{__data\.fields/); assert.doesNotMatch(topDevices.fieldConfig.overrides[1].properties[0].value[0].url, /domain_device_id/); assert.deepEqual(freshness.fieldConfig.defaults.thresholds.steps.map(({ value }) => value), [null, 60, 120]); assert.equal(freshness.fieldConfig.defaults.noValue, 'Нет данных'); assert.equal(freshness.title, 'Свежесть данных в конце выбранного периода'); assert.equal(deviceVariable.name, 'device_id'); assert.equal(deviceVariable.multi, false); assert.equal(deviceVariable.includeAll, true); assert.equal(deviceVariable.allValue, '.*'); assert.equal(deviceVariable.current.value, '$__all'); assert.equal(deviceVariable.refresh, 2); assert.equal(dashboard.templating.list.length, 1); assert.match(deviceVariable.query.query, /query_result\(label_join\(/); assert.match(deviceVariable.regex, //); assert.match(deviceVariable.regex, //); for (const panel of speedPanels) { assert.equal(panel.fieldConfig.defaults.custom.axisCenteredZero, true); assert.deepEqual(panel.fieldConfig.defaults.custom.scaleDistribution, { linearThreshold: 1048576, log: 2, type: 'symlog', }); assert.deepEqual(panel.targets.map(({ refId }) => refId), ['A', 'B']); assert.ok(panel.targets.every(({ expr }) => expr.includes('[$__rate_interval]') && !expr.includes('[5m]'))); assert.ok(panel.fieldConfig.overrides.some((override) => ( override.matcher.id === 'byFrameRefID' && override.matcher.options === 'B' && override.properties.some(({ id, value }) => id === 'custom.transform' && value === 'negative-Y') ))); } assert.ok(globalSpeed.targets.every(({ expr }) => expr.includes('device_id=~"$device_id"') && expr.endsWith('> 0'))); assert.ok(deviceSpeed.targets.every(({ expr }) => /sum by \(device_id\)[\s\S]*device_id=~"\$device_id"[\s\S]*> 0[\s\S]*group_left \(name, ip\)/.test(expr))); assert.ok(deviceSpeed.targets.every(({ legendFormat }) => /\{\{name\}\} · \{\{ip\}\}/.test(legendFormat))); assert.ok(sourceSpeed.targets.every(({ expr }) => /device_id=~"\$device_id"[\s\S]*> 0$/.test(expr))); assert.equal(totalTraffic.targets[0].expr, 'sum(increase(harbor_traffic_bytes_total[$__range]))'); assert.equal(trafficBySource.targets[0].expr, 'sum by (source) (increase(harbor_traffic_bytes_total[$__range])) > 0'); assert.match(sourceTraffic.targets[0].expr, /increase\(harbor_device_traffic_bytes_total\{device_id=~"\$device_id"\}\[\$__range\]\)/); assert.ok(dashboard.panels.every(({ title = '', description = '' }) => !/Накоплено|за всё время/.test(`${title} ${description}`))); assert.match(services.targets[0].expr, /device_id=~"\$device_id"[\s\S]*> 0\)$/); assert.deepEqual(domains.transformations[0].options.indexByName, { service: 0, domain: 1, Value: 2 }); assert.match(domains.targets[0].expr, /^topk\(15,[\s\S]*device_id=~"\$device_id"[\s\S]*> 0\)$/); assert.ok(dashboard.panels .filter(({ type }) => ['bargauge', 'table'].includes(type)) .flatMap(({ targets }) => targets || []) .every(({ expr }) => expr.includes('> 0'))); });