Refactor Harbor client connection flow
Build and Deploy Gateway / build-and-push (push) Successful in 22s
Build and Deploy Gateway / deploy (push) Successful in 7s

This commit is contained in:
2026-08-17 12:19:26 +03:00
parent 8eccdd4050
commit 0b39211fbd
4 changed files with 625 additions and 631 deletions
+92 -68
View File
@@ -29,60 +29,104 @@ test('Gateway info drawer contains copyable Prometheus and Grafana instructions'
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);
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 = 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 singboxOutbounds = dashboard.panels.find(({ id }) => id === 15);
const directPackets = dashboard.panels.find(({ id }) => id === 16);
const speedPanels = [globalSpeed, deviceSpeed, sourceSpeed];
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 trafficBySource = byId.get(4);
const singboxOutbounds = byId.get(15);
const directPackets = byId.get(16);
const technical = byId.get(13);
assert.equal(dashboard.title, 'Harbor Gateway: трафик');
assert.equal(dashboard.refresh, '30s');
assert.equal(dashboard.version, 8);
assert.deepEqual(titles.filter((title) => ['Обзор', 'Устройства', 'Сервисы и домены', 'Техническая детализация'].includes(title)), [
assert.equal(dashboard.version, 9);
assert.deepEqual(dashboard.panels.map(({ id }) => id), [10, 6, 7, 1, 15, 2, 3, 5, 12, 8, 9, 13]);
assert.deepEqual(dashboard.panels.filter(({ type }) => type === 'row').map(({ title }) => 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(titles.includes('sing-box: VPN / Direct за выбранный период'));
assert.ok(titles.includes('Gateway Direct: IPv4-пакеты за выбранный период'));
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.ok(expressions.some((expression) => expression.includes('harbor_direct_ipv4_packet_last_observed_timestamp_seconds')));
assert.match(singboxOutbounds.targets[0].expr, /sum by \(outbound, source\).*harbor_singbox_tracked_bytes_total\[\$__range\]/);
assert.match(directPackets.targets[0].expr, /sum by \(direction\).*harbor_direct_ipv4_packet_bytes_total\[\$__range\]/);
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(speed.gridPos, { h: 12, w: 16, x: 0, y: 5 });
assert.deepEqual(devices.gridPos, { h: 12, w: 8, x: 16, y: 5 });
assert.deepEqual(services.gridPos, { h: 15, w: 9, x: 0, y: 18 });
assert.deepEqual(domains.gridPos, { h: 15, w: 15, x: 9, y: 18 });
assert.deepEqual(technical.gridPos, { h: 1, w: 24, x: 0, y: 33 });
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.deepEqual(speed.targets.map(({ refId }) => refId), ['A', 'B']);
assert.ok(speed.targets.every(({ expr }) => expr.includes('device_id=~"$device_id"') && expr.includes('[$__rate_interval]') && !expr.includes('[5m]')));
assert.ok(speed.fieldConfig.overrides.some((override) => (
override.matcher.id === 'byFrameRefID'
&& override.matcher.options === 'B'
&& override.properties.some(({ id, value }) => id === 'custom.transform' && value === 'negative-Y')
)));
assert.equal(speed.options.legend.placement, 'right');
assert.deepEqual(speed.options.legend.calcs, ['lastNotNull', 'mean', 'max']);
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.match(singboxOutbounds.targets[1].expr, /outbound="direct"/);
assert.match(singboxOutbounds.targets[2].expr, /outbound="unknown"/);
assert.match(directPackets.targets[0].expr, /harbor_device_direct_ipv4_packet_bytes_total\{device_id=~"\$device_id"\}\[\$__range\]/);
assert.match(directPackets.description, /нельзя складывать с sing-box tracker bytes/);
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.match(trafficBySource.targets[0].expr, /harbor_device_traffic_bytes_total\{device_id=~"\$device_id"\}\[\$__range\]/);
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.find(({ refId }) => refId === 'C').legendFormat, 'Gateway Direct');
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);
@@ -93,34 +137,14 @@ test('Grafana dashboard uses one all-or-one device scope and shows active device
assert.match(deviceVariable.query.query, /query_result\(label_join\(/);
assert.match(deviceVariable.regex, /<text>/);
assert.match(deviceVariable.regex, /<value>/);
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
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')));
});