Files
harbor-net/test/web/prometheus-instructions.test.js
T
dokril ab14fc979e
Build and Deploy Gateway / build-and-push (push) Successful in 31s
Build and Deploy Gateway / deploy (push) Successful in 7s
Clarify VPN and Direct traffic metrics in Grafana dashboard
2026-09-01 07:01:26 +03:00

181 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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, /<CopyButton[\s\S]*label="Скопировать"/);
assert.doesNotMatch(`${overview}\n${feature}`, /prometheus-toggle|Prometheus<\/span><\/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 a compact user-first layout and one device scope', () => {
const flattenPanels = (items) => items.flatMap((item) => [item, ...flattenPanels(item.panels || [])]);
const panels = flattenPanels(dashboard.panels);
const byId = new Map(panels.map((panel) => [panel.id, panel]));
const expressions = panels.flatMap((panel) => panel.targets || []).map(({ expr }) => expr).filter(Boolean);
const deviceVariable = dashboard.templating.list[0];
const freshness = byId.get(2);
const speed = byId.get(3);
const devices = byId.get(5);
const currentDownload = byId.get(6);
const currentUpload = byId.get(7);
const services = byId.get(8);
const domains = byId.get(9);
const totalTraffic = byId.get(1);
const sourceOutbounds = byId.get(4);
const singboxOutbounds = byId.get(15);
const directPaths = byId.get(16);
const appliedPolicy = byId.get(17);
const technical = byId.get(13);
assert.equal(dashboard.title, 'Harbor Gateway: трафик');
assert.equal(dashboard.refresh, '30s');
assert.equal(dashboard.version, 10);
assert.deepEqual(dashboard.panels.map(({ id }) => id), [10, 6, 7, 1, 15, 2, 17, 3, 5, 12, 8, 9, 13]);
assert.deepEqual(dashboard.panels.filter(({ type }) => type === 'row').map(({ title }) => title), [
'Обзор',
'Куда уходит трафик',
'Техническая детализация',
]);
assert.equal(technical.collapsed, true);
assert.deepEqual(technical.panels.map(({ id }) => id), [4, 16]);
assert.equal(byId.has(11), false);
assert.equal(byId.has(14), false);
assert.deepEqual(appliedPolicy.gridPos, { h: 4, w: 16, x: 0, y: 5 });
assert.deepEqual(speed.gridPos, { h: 12, w: 16, x: 0, y: 9 });
assert.deepEqual(devices.gridPos, { h: 16, w: 8, x: 16, y: 5 });
assert.deepEqual(services.gridPos, { h: 15, w: 9, x: 0, y: 22 });
assert.deepEqual(domains.gridPos, { h: 15, w: 15, x: 9, y: 22 });
assert.deepEqual(technical.gridPos, { h: 1, w: 24, x: 0, y: 37 });
assert.equal(devices.options.footer.enablePagination, false);
assert.equal(domains.options.footer.enablePagination, false);
assert.equal(currentDownload.type, 'stat');
assert.equal(currentUpload.type, 'stat');
assert.equal(currentDownload.targets[0].instant, true);
assert.equal(currentUpload.targets[0].instant, true);
assert.match(currentDownload.title, /в конце/);
assert.match(currentUpload.title, /в конце/);
assert.match(currentDownload.description, /адаптивным окном усреднения/);
assert.match(currentUpload.description, /адаптивным окном усреднения/);
assert.match(currentDownload.targets[0].expr, /device_id=~"\$device_id"[\s\S]*direction="download"[\s\S]*\[\$__rate_interval\]/);
assert.match(currentUpload.targets[0].expr, /device_id=~"\$device_id"[\s\S]*direction="upload"[\s\S]*\[\$__rate_interval\]/);
assert.equal(totalTraffic.targets[0].expr, 'sum(increase(harbor_device_traffic_bytes_total{device_id=~"$device_id"}[$__range]))');
assert.equal(speed.fieldConfig.defaults.custom.axisCenteredZero, true);
assert.deepEqual(speed.fieldConfig.defaults.custom.scaleDistribution, {
linearThreshold: 1048576,
log: 2,
type: 'symlog',
});
assert.equal(speed.title, 'Фактический VPN / Direct');
assert.deepEqual(speed.targets.map(({ refId }) => refId), ['A', 'B', 'C', 'D', 'E', 'F']);
assert.ok(speed.targets.every(({ expr }) => expr.includes('device_id=~"$device_id"') && expr.includes('[$__rate_interval]') && !expr.includes('[5m]')));
assert.ok(speed.targets.filter(({ refId }) => ['A', 'B', 'E', 'F'].includes(refId))
.every(({ expr }) => expr.includes('harbor_device_singbox_tracked_bytes_total')));
assert.ok(speed.targets.every(({ expr }) => !expr.includes('source=')));
assert.ok(speed.targets.filter(({ refId }) => ['C', 'D'].includes(refId)).every(({ expr }) => (
expr.includes('harbor_device_singbox_tracked_bytes_total')
&& expr.includes('harbor_device_direct_ipv4_packet_bytes_total')
&& expr.includes('or vector(0)')
)));
assert.deepEqual(speed.fieldConfig.overrides.filter((override) => (
override.matcher.id === 'byFrameRefID'
&& override.properties.some(({ id, value }) => id === 'custom.transform' && value === 'negative-Y')
)).map(({ matcher }) => matcher.options), ['B', 'D', 'F']);
assert.equal(speed.options.legend.placement, 'right');
assert.deepEqual(speed.options.legend.calcs, ['lastNotNull', 'mean', 'max']);
assert.equal(appliedPolicy.type, 'state-timeline');
assert.equal(appliedPolicy.title, 'Применённый режим');
assert.match(appliedPolicy.description, /Direct[\s\S]*VPN через явно настроенный Harbor Proxy/);
assert.match(appliedPolicy.targets[0].expr, /harbor_device_applied_policy\{device_id=~"\$device_id"\}/);
assert.match(appliedPolicy.targets[0].expr, /group_left \(name, ip\)[\s\S]*harbor_device_info/);
assert.deepEqual(appliedPolicy.fieldConfig.defaults.mappings[0].options, {
0: { color: 'orange', index: 0, text: 'Direct' },
1: { color: 'green', index: 1, text: 'VPN' },
});
assert.equal(appliedPolicy.fieldConfig.defaults.noValue, 'Нет данных');
assert.deepEqual(singboxOutbounds.targets.map(({ legendFormat }) => legendFormat), ['VPN', 'Direct', 'Не определено']);
assert.ok(singboxOutbounds.targets.every(({ expr }) => (
expr.includes('harbor_device_singbox_tracked_bytes_total')
&& expr.includes('device_id=~"$device_id"')
&& expr.includes('[$__range]')
)));
assert.match(singboxOutbounds.targets[0].expr, /outbound="vpn"/);
assert.doesNotMatch(singboxOutbounds.targets[0].expr, /source=/);
assert.match(singboxOutbounds.targets[1].expr, /harbor_device_singbox_tracked_bytes_total[\s\S]*outbound="direct"/);
assert.match(singboxOutbounds.targets[1].expr, /harbor_device_direct_ipv4_packet_bytes_total/);
assert.match(singboxOutbounds.targets[1].expr, /or vector\(0\)/);
assert.match(singboxOutbounds.targets[2].expr, /outbound="unknown"/);
assert.equal(singboxOutbounds.fieldConfig.defaults.noValue, 'Нет данных');
assert.equal(sourceOutbounds.title, 'Вход × выход sing-box');
assert.match(sourceOutbounds.targets[0].expr, /sum by \(source, outbound\)[\s\S]*harbor_device_singbox_tracked_bytes_total/);
assert.equal(sourceOutbounds.targets[0].legendFormat, '{{source}} · {{outbound}}');
assert.match(sourceOutbounds.description, /proxy · vpn/);
assert.deepEqual(directPaths.targets.map(({ legendFormat }) => legendFormat), ['Через sing-box', 'Linux мимо sing-box']);
assert.match(directPaths.targets[0].expr, /harbor_device_singbox_tracked_bytes_total[\s\S]*outbound="direct"/);
assert.match(directPaths.targets[1].expr, /harbor_device_direct_ipv4_packet_bytes_total/);
assert.match(directPaths.description, /приблизительной оценкой/);
assert.match(devices.targets[0].expr, /^\(sum by \(device_id\)[\s\S]*increase\(harbor_device_traffic_bytes_total\[\$__range\]\)[\s\S]*> 0[\s\S]*group_left \(name, ip\)/);
assert.doesNotMatch(devices.targets[0].expr, /topk/);
assert.equal(devices.options.sortBy[0].desc, true);
const deviceNameOverride = devices.fieldConfig.overrides.find(({ matcher }) => matcher.options === 'name');
const deviceLink = deviceNameOverride.properties.find(({ id }) => id === 'links').value[0];
assert.equal(deviceLink.url, '?' + '$' + '{__url_time_range}&var-device_id=' + '$' + '{__data.fields["device_id"]}');
assert.ok(devices.fieldConfig.overrides.some(({ properties }) => properties.some(({ id }) => id === 'custom.width')));
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(freshness.targets.length, 1);
assert.match(freshness.targets[0].expr, /harbor_traffic_last_observed_timestamp_seconds/);
assert.match(freshness.targets[0].expr, /harbor_domain_traffic_last_observed_timestamp_seconds/);
assert.match(freshness.targets[0].expr, /harbor_direct_ipv4_packet_last_observed_timestamp_seconds/);
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, /<text>/);
assert.match(deviceVariable.regex, /<value>/);
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(panels.every(({ title = '', description = '' }) => !/Накоплено|за всё время/.test([title, description].join(' '))));
assert.ok(panels
.filter(({ type }) => ['bargauge', 'table'].includes(type))
.flatMap(({ targets }) => targets || [])
.every(({ expr }) => expr.includes('> 0')));
assert.ok(expressions.every((expression) => !expression.includes('harbor_singbox_tracked_bytes_total')));
});