This commit is contained in:
Mateusz Gruszczyński committed 2026-10-15 23:29:15 +02:00
1 parent 162e986d37
commit f462ca7194
26 files changed
+980 -710

No files matched your search

@@ -1,124 +0,0 @@
#!/usr/bin/env python3
"""0.33.5 CPU plot autoscaling regression with production HTML/CSS/JS.
Uses Chromium with simulated authenticated DOM and status samples, not a live
RustProxy server, SSE stream or real CPU telemetry.
"""
from pathlib import Path
import re
from playwright.sync_api import sync_playwright
ROOT = Path(__file__).resolve().parents[2]
HTML = (ROOT / 'src/admin/web/index.html').read_text(encoding='utf-8')
CSS = (ROOT / 'src/admin/web/style.css').read_text(encoding='utf-8')
WIDE_JS = (ROOT / 'src/admin/web/app/overview-wide-metrics.js').read_text(encoding='utf-8')
def inspect(page):
return page.evaluate('''() => {
const get = id => document.querySelector(`#dashboard-overview-grid [data-dashboard-widget="${id}"]`);
const cpu = get('wide_cpu'), cpuSvg = cpu.querySelector('svg');
const protocolSvg = get('wide_protocol_rates').querySelector('svg');
const backendSvg = get('backend_traffic').querySelector('svg');
const legend = cpu.querySelector('.overview-wide-legend').getBoundingClientRect();
const bounds = cpu.getBoundingClientRect();
const matrix = cpuSvg.getScreenCTM();
return {
cpuHeight: cpuSvg.getBoundingClientRect().height,
protocolHeight: protocolSvg.getBoundingClientRect().height,
backendHeight: backendSvg.getBoundingClientRect().height,
svgWidth: cpuSvg.getBoundingClientRect().width,
drawnWidth: Math.hypot(matrix.a, matrix.b) * cpuSvg.viewBox.baseVal.width,
ratio: cpuSvg.getAttribute('preserveAspectRatio'),
cpuSeries: cpuSvg.querySelectorAll('.overview-wide-series').length,
lineEffect: getComputedStyle(cpuSvg.querySelector('.overview-wide-series')).vectorEffect,
gridEffect: getComputedStyle(cpuSvg.querySelector('.chart-grid')).vectorEffect,
legendInCard: legend.top >= bounds.top && legend.bottom <= bounds.bottom + 1,
overflow: document.documentElement.scrollWidth - innerWidth,
};
}''')
def render_cpu(page, revision, system, process):
return page.evaluate('''({revision,system,process}) => {
window.rustProxyWideOverview.render({revision,live_system:{
cpu_percent:system, process_cpu_percent:process
}});
const read = name => {
const prefix = `overview-wide-wide_cpu-${name}`;
return {
path: document.getElementById(`${prefix}-line`).getAttribute('d'),
label: document.getElementById(`${prefix}-value`).textContent,
};
};
return {system:read('system'),process:read('process')};
}''', {'revision': revision, 'system': system, 'process': process})
def ys(path):
return [int(v) for v in re.findall(r'[ML]\d+\s+(\d+)', path)]
with sync_playwright() as p:
browser = p.chromium.launch(headless=True, executable_path='/usr/bin/chromium', args=['--no-sandbox'])
for viewport in (320, 375, 768, 1440, 1920):
page = browser.new_page(viewport={'width': viewport, 'height': 950})
errors = []
page.on('pageerror', lambda error: errors.append(str(error)))
page.set_content(HTML.replace('/assets/app___RUSTPROXY_ASSET_ID__.js', '')
.replace('/assets/style___RUSTPROXY_ASSET_ID__.css', ''))
page.add_style_tag(content=CSS)
page.evaluate('''() => {
document.documentElement.classList.remove('app-loading', 'app-startup-failed');
document.getElementById('workspace').hidden = false;
document.getElementById('panel-overview').hidden = false;
const grid = document.getElementById('dashboard-overview-grid');
grid.classList.add('is-aligned');
for (const card of grid.querySelectorAll(':scope > [data-dashboard-widget]')) {
card.hidden = !['wide_cpu', 'wide_protocol_rates', 'backend_traffic'].includes(card.dataset.dashboardWidget);
}
window.currentPanel = 'overview';
window.$ = id => document.getElementById(id);
window.fmt = (value, digits = 0) => Number(value).toFixed(digits);
window.fmtBytes = value => `${value} B`;
window.rustProxyOverviewSamples = {getLimit: () => 60, onChange: () => {}};
}''')
page.add_script_tag(content=WIDE_JS)
for density in ('compact', 'normal', 'ultra'):
page.evaluate('(density) => document.documentElement.dataset.dashboardDensity = density', density)
result = inspect(page)
assert result['protocolHeight'] >= 124, (viewport, density, result)
assert abs(result['cpuHeight'] - result['protocolHeight']) <= 1, (
'CPU must use standard chart height', viewport, density, result)
assert result['backendHeight'] > 0, (viewport, density, result)
assert result['ratio'] == 'none' and result['cpuSeries'] == 2, result
assert result['lineEffect'] == result['gridEffect'] == 'non-scaling-stroke', result
assert result['svgWidth'] > 100 and abs(result['svgWidth'] - result['drawnWidth']) < 2, result
assert result['legendInCard'] and result['overflow'] <= 2, (viewport, density, result)
# Measured 0.5-0.8% must be visible, not flattened by a 100% ceiling.
first = render_cpu(page, 1, 0.5, 0.2)
second = render_cpu(page, 1, 0.8, 0.6)
assert first['system']['label'] == '0.5%' and second['process']['label'] == '0.6%'
assert ys(second['system']['path']) == [77, 41], (viewport, second)
assert ys(second['process']['path']) == [113, 65], (viewport, second)
# The peak follows observed history and also works above 100% (multi-core).
third = render_cpu(page, 1, 3.4, 2.2)
assert ys(third['system']['path'])[-1] == 17, (viewport, third)
assert ys(third['process']['path'])[-1] == 59, (viewport, third)
high = render_cpu(page, 1, 250, 123)
assert high['system']['label'] == '250.0%'
assert ys(high['system']['path'])[-1] == 17, (viewport, high)
assert 17 <= ys(high['process']['path'])[-1] < 137, (viewport, high)
# Revision changes reset the history. Zero stays at the plot baseline.
idle = render_cpu(page, 2, 0, 0)
assert ys(idle['system']['path']) == [137] and ys(idle['process']['path']) == [137]
missing = render_cpu(page, 3, None, None)
assert missing['system']['path'] == '' and missing['process']['label'] == '-'
assert not errors, (viewport, errors)
print(f'PASS CPU auto Y-scale and standard plot height at {viewport}px (all densities)')
page.close()
browser.close()
@@ -166,13 +166,25 @@ with sync_playwright() as p:
window.rustProxyStreamDashboard.render(status);
}''')
assert_visible(page, ['backend_health'], ['backend_traffic'])
# Restore defaults in the modal after a custom layout was saved.
# 0.33.7: the former layout modal is a preset manager and must not mutate
# the active custom layout merely by opening/closing it.
page.locator('#overview-layout-open').evaluate('(node) => node.hidden = false')
page.locator('#overview-layout-open').click()
page.wait_for_function("document.getElementById('overview-layout-dialog').open")
page.locator('#overview-layout-default').click()
page.locator('#overview-layout-save').click()
assert page.locator('#overview-preset-name').is_visible()
assert page.locator('#overview-layout-default').count() == 0
assert_visible(page, ['backend_health'], ['backend_traffic'])
page.locator('#overview-layout-cancel').click()
page.wait_for_function("!document.getElementById('overview-layout-dialog').open")
assert_visible(page, ['backend_health'], ['backend_traffic'])
# Restore factory defaults through the same account preference API used
# by Settings/inline editing rather than the preset manager.
page.evaluate('''preset => window.rustProxyPreferences.update({
dashboard_density: preset.dashboard_density,
dashboard_widgets: [...preset.dashboard_widgets],
dashboard_hidden_widgets: [...preset.dashboard_hidden_widgets],
dashboard_widget_sizes: null,
})''', PRESET)
assert_visible(page, ['backend_traffic'], ['backend_health', 'stream_sessions', 'www_status'])
assert page.evaluate('window.__savedDashboard.dashboard_widgets') == PRESET['dashboard_widgets']
assert page.evaluate('window.__savedDashboard.dashboard_hidden_widgets') == PRESET['dashboard_hidden_widgets']
@@ -1,8 +1,8 @@
#!/usr/bin/env python3
"""Regression: Overview visual layout editor fits without horizontal overflow.
"""Responsive regression for the Overview toolbar modal.
Runs the real panel DOM/CSS/bundled JavaScript with mocked preferences.
This is browser layout testing, not Rust server acceptance.
0.33.7 repurposed the former visual layout editor as Dashboard presets. Keep
this older viewport regression focused on modal fit/no horizontal overflow.
"""
import json
import re
@@ -10,17 +10,17 @@ from pathlib import Path
from playwright.sync_api import sync_playwright
ROOT = Path(__file__).resolve().parents[2]
HTML = (ROOT / 'src/admin/web/index.html').read_text()
CSS = (ROOT / 'src/admin/web/style.css').read_text()
ROUTER = (ROOT / 'src/admin/router.rs').read_text()
HTML = (ROOT / "src/admin/web/index.html").read_text(encoding="utf-8")
CSS = (ROOT / "src/admin/web/style.css").read_text(encoding="utf-8")
ROUTER = (ROOT / "src/admin/router.rs").read_text(encoding="utf-8")
PACKS = {
name: {'id': name, **json.loads((ROOT / 'langs' / (name + '.json')).read_text())}
for name in ('en_US', 'pl_pl')
name: {"id": name, **json.loads((ROOT / "langs" / f"{name}.json").read_text(encoding="utf-8"))}
for name in ("en_US", "pl_pl")
}
def app_bundle():
match = re.search(r'fn admin_app_javascript\([^)]*\)[^{]*\{', ROUTER)
match = re.search(r"fn admin_app_javascript\([^)]*\)[^{]*\{", ROUTER)
assert match
body = ROUTER[match.end():]
body = body[:re.search(r'\]\s*\.join\("\\n"\)', body).start()]
@@ -29,141 +29,64 @@ def app_bundle():
line = line.strip()
include = re.search(r'include_str!\("([^"]+)"\)', line)
if include:
result.append((ROOT / 'src/admin' / include.group(1)).read_text())
elif line == 'admin_vhosts_javascript(),':
result.append('(() => {\n' + '\n'.join((ROOT / 'src/admin/web/vhosts' / (name + '.js')).read_text()
for name in ('core', 'protocol', 'health', 'rates', 'editor', 'render')) + '\n})();')
result.append((ROOT / "src/admin" / include.group(1)).read_text(encoding="utf-8"))
elif line == "admin_vhosts_javascript(),":
result.append("(() => {\n" + "\n".join(
(ROOT / "src/admin/web/vhosts" / f"{name}.js").read_text(encoding="utf-8")
for name in ("core", "protocol", "health", "rates", "editor", "render")) + "\n})();")
elif line.startswith('"') and line.endswith('",'):
result.append(json.loads(line[:-1]))
return '\n'.join(result)
return "\n".join(result)
BUNDLE = app_bundle()
def load(page):
page.set_content(HTML.replace('/assets/app___RUSTPROXY_ASSET_ID__.js', '')
.replace('/assets/style___RUSTPROXY_ASSET_ID__.css', ''))
page.add_style_tag(content=CSS)
page.add_script_tag(content=BUNDLE)
page.wait_for_function('window.rustProxyInlineLayout && window.rustProxyPreferences && window.rustProxyDashboardLayout')
page.evaluate('''packs => {
languagePacks = new Map(Object.entries(packs));
defaultLanguage = 'en_US';
language = 'pl_pl';
document.documentElement.lang = 'pl-PL';
document.querySelectorAll('#overview-layout-dialog [data-i18n]').forEach(node => node.textContent = t(node.dataset.i18n));
}''', PACKS)
page.evaluate('''() => {
document.documentElement.classList.remove('app-loading', 'app-startup-failed');
document.getElementById('workspace').hidden = false;
document.getElementById('panel-overview').hidden = false;
document.getElementById('overview-layout-unlock').hidden = false;
document.getElementById('overview-layout-open').hidden = false;
currentPanel = 'overview';
window.__savedDashboard = structuredClone(ADMIN_APPEARANCE_DEFAULTS);
window.rustProxyPreferences.load = async () => window.__savedDashboard;
window.rustProxyPreferences.current = () => window.__savedDashboard;
window.rustProxyPreferences.update = async patch => {
window.__savedDashboard = {...window.__savedDashboard, ...patch};
applyAdminPreferences(window.__savedDashboard);
return window.__savedDashboard;
};
applyAdminPreferences(window.__savedDashboard);
language = 'pl_pl';
document.documentElement.lang = 'pl-PL';
document.querySelectorAll('#overview-layout-dialog [data-i18n]').forEach(node => node.textContent = t(node.dataset.i18n));
}''')
def geometry(page):
return page.evaluate('''() => {
const d = document.getElementById('overview-layout-dialog');
const grid = document.getElementById('overview-layout-visible');
const rect = node => node.getBoundingClientRect();
const tiles = Array.from(grid.querySelectorAll('.overview-layout-tile'));
const overflows = [];
for (const tile of tiles) {
const r = rect(tile);
const toolbar = tile.querySelector('.overview-layout-tile-toolbar');
if (tile.scrollWidth > tile.clientWidth + 2)
overflows.push([tile.dataset.previewWidget, 'tile-scroll', tile.clientWidth, tile.scrollWidth]);
for (const button of toolbar.querySelectorAll('button')) {
const b = rect(button);
if (b.left < r.left - 2 || b.right > r.right + 2)
overflows.push([tile.dataset.previewWidget, 'button-outside', button.className, b.left, b.right, r.left, r.right]);
}
const buttons = Array.from(toolbar.querySelectorAll('button'));
for (let i = 0; i < buttons.length; i++) {
const a = rect(buttons[i]);
for (let j = i + 1; j < buttons.length; j++) {
const b = rect(buttons[j]);
if (a.right - b.left > 1 && b.right - a.left > 1 && a.bottom - b.top > 1 && b.bottom - a.top > 1)
overflows.push([tile.dataset.previewWidget, 'overlapping-buttons', buttons[i].className, buttons[j].className]);
}
}
}
const hiddenList = document.getElementById('overview-layout-hidden');
for (const row of hiddenList.querySelectorAll('.overview-layout-hidden-item')) {
const r = rect(row), button = row.querySelector('button');
const b = rect(button);
if (b.right > r.right + 2 || b.left < r.left - 2)
overflows.push(['hidden card', 'button-outside', b.left, b.right, r.left, r.right]);
}
const panelPreview = grid.querySelector('[data-preview-widget="network_diagnostics"]');
const dialogRect = rect(d);
return {
cols: getComputedStyle(grid).gridTemplateColumns.split(' ').length,
panelWidth: panelPreview ? rect(panelPreview).width : null,
dialogWidth: d.clientWidth, dialogScrollWidth: d.scrollWidth,
gridWidth: grid.clientWidth, gridScrollWidth: grid.scrollWidth,
dialogLeft:dialogRect.left, dialogRight:dialogRect.right,
visibleCount:tiles.length, overflows,
firstTileWidth:rect(tiles[0]).width,
};
}''')
with sync_playwright() as p:
browser = p.chromium.launch(headless=True, executable_path='/usr/bin/chromium', args=['--no-sandbox'])
browser = p.chromium.launch(headless=True, executable_path="/usr/bin/chromium", args=["--no-sandbox"])
for width in (320, 375, 550, 620, 800, 900, 960, 1158, 1440, 1920):
context = browser.new_context(viewport={'width': width, 'height': 823})
page = context.new_page()
page = browser.new_page(viewport={"width": width, "height": 823})
errors = []
page.on('pageerror', lambda err: errors.append(str(err)))
load(page)
page.locator('#overview-layout-open').click()
page.on("pageerror", lambda error: errors.append(str(error)))
page.set_content(HTML.replace("/assets/app___RUSTPROXY_ASSET_ID__.js", "")
.replace("/assets/style___RUSTPROXY_ASSET_ID__.css", ""))
page.add_style_tag(content=CSS)
page.add_script_tag(content=BUNDLE)
page.wait_for_function("window.rustProxyPreferences && window.rustProxyDashboardLayout")
page.evaluate("""packs => {
languagePacks = new Map(Object.entries(packs)); defaultLanguage = 'en_US';
language = 'pl_pl'; languagePreference = 'pl_pl';
document.documentElement.classList.remove('app-loading', 'app-startup-failed');
document.getElementById('workspace').hidden = false;
document.getElementById('panel-overview').hidden = false;
document.getElementById('overview-layout-open').hidden = false;
currentPanel = 'overview';
window.__savedDashboard = {...structuredClone(ADMIN_APPEARANCE_DEFAULTS), language:'pl_pl'};
window.rustProxyPreferences.load = async () => window.__savedDashboard;
window.rustProxyPreferences.current = () => window.__savedDashboard;
window.rustProxyPreferences.update = async patch => {
window.__savedDashboard = {...window.__savedDashboard, ...patch};
applyAdminPreferences(window.__savedDashboard); return window.__savedDashboard;
};
applyAdminPreferences(window.__savedDashboard); language = 'pl_pl'; languagePreference = 'pl_pl';
for (const node of document.querySelectorAll('#overview-layout-dialog [data-i18n]'))
node.textContent = t(node.dataset.i18n);
}""", PACKS)
page.locator("#overview-layout-open").click()
page.wait_for_function("document.getElementById('overview-layout-dialog').open")
check = geometry(page)
expected = 1 if width <= 620 else 2 if width <= 900 else 3
assert check['cols'] == expected, (width, check)
assert check['visibleCount'] > 20
assert check['panelWidth'] is not None
assert abs(check['firstTileWidth'] - check['panelWidth']) < 3, (width, check)
assert check['dialogLeft'] >= -1 and check['dialogRight'] <= width + 1, (width, check)
assert check['dialogScrollWidth'] <= check['dialogWidth'] + 2, (width, check)
assert check['gridScrollWidth'] <= check['gridWidth'] + 2, (width, check)
assert not check['overflows'], (width, check['overflows'][:20])
tile = '#overview-layout-visible [data-preview-widget="http_protocol"]'
before = page.locator(tile).evaluate('(el) => el.getBoundingClientRect().width')
page.locator(tile + ' .overview-layout-widen').click()
assert page.locator(tile).get_attribute('data-dashboard-span') == '3'
after = page.locator(tile).evaluate('(el) => el.getBoundingClientRect().width')
assert abs(before - after) < 2, (width, before, after)
assert not geometry(page)['overflows']
page.locator(tile + ' .overview-layout-narrow').click()
assert page.locator(tile).get_attribute('data-dashboard-span') == '2'
if width == 1158:
page.screenshot(path=str(ROOT.parent / 'modal_preview_1158.png'))
if width == 375:
page.screenshot(path=str(ROOT.parent / 'modal_preview_375.png'))
page.locator(tile + ' .overview-layout-hide').click()
assert page.locator(tile).count() == 0
assert not geometry(page)['overflows']
page.locator('#overview-layout-cancel').click()
page.wait_for_function("!document.getElementById('overview-layout-dialog').open")
check = page.locator("#overview-layout-dialog").evaluate("""d => {
const r = d.getBoundingClientRect();
const rows = [...d.querySelectorAll('.dashboard-preset-save,.dashboard-preset-row')];
return {
left:r.left, right:r.right, client:d.clientWidth, scroll:d.scrollWidth,
overflow: rows.some(row => row.scrollWidth > row.clientWidth + 2),
};
}""")
assert check["left"] >= -1 and check["right"] <= width + 1, (width, check)
assert check["scroll"] <= check["client"] + 2 and not check["overflow"], (width, check)
assert page.locator("#overview-preset-name").is_visible()
assert page.locator("#overview-preset-save").is_visible()
assert not errors, (width, errors)
context.close()
print('PASS visual modal grid, no overlap/scroll, width/hide/cancel:', width)
page.close()
print(f"PASS responsive Dashboard preset modal at {width}px")
browser.close()
print('PASS 0.33.3 modal regression (simulated API)')
print("PASS modal responsive regression after 0.33.7 preset conversion")
+189
View File
@@ -0,0 +1,189 @@
#!/usr/bin/env python3
"""0.33.7 Dashboard preset manager and chart-title font regression.
Runs the bundled administration DOM/CSS/JavaScript with mocked account preferences.
This is simulated-browser UI coverage, not a live RustProxy server test.
"""
import json
import re
from pathlib import Path
from playwright.sync_api import sync_playwright
ROOT = Path(__file__).resolve().parents[2]
HTML = (ROOT / "src/admin/web/index.html").read_text(encoding="utf-8")
CSS = (ROOT / "src/admin/web/style.css").read_text(encoding="utf-8")
ROUTER = (ROOT / "src/admin/router.rs").read_text(encoding="utf-8")
PACKS = {
name: {"id": name, **json.loads((ROOT / "langs" / f"{name}.json").read_text(encoding="utf-8"))}
for name in ("en_US", "pl_pl")
}
def app_bundle():
match = re.search(r"fn admin_app_javascript\([^)]*\)[^{]*\{", ROUTER)
assert match
body = ROUTER[match.end():]
body = body[:re.search(r'\]\s*\.join\("\\n"\)', body).start()]
result = []
for line in body.splitlines():
line = line.strip()
include = re.search(r'include_str!\("([^"]+)"\)', line)
if include:
result.append((ROOT / "src/admin" / include.group(1)).read_text(encoding="utf-8"))
elif line == "admin_vhosts_javascript(),":
result.append("(() => {\n" + "\n".join(
(ROOT / "src/admin/web/vhosts" / f"{name}.js").read_text(encoding="utf-8")
for name in ("core", "protocol", "health", "rates", "editor", "render")) + "\n})();")
elif line.startswith('"') and line.endswith('",'):
result.append(json.loads(line[:-1]))
return "\n".join(result)
BUNDLE = app_bundle()
def load(page):
page.set_content(HTML.replace("/assets/app___RUSTPROXY_ASSET_ID__.js", "")
.replace("/assets/style___RUSTPROXY_ASSET_ID__.css", ""))
page.add_style_tag(content=CSS)
page.add_script_tag(content=BUNDLE)
page.wait_for_function("window.rustProxyPreferences && window.rustProxyDashboardLayout")
page.evaluate("""packs => {
languagePacks = new Map(Object.entries(packs));
defaultLanguage = 'en_US';
language = 'pl_pl';
document.documentElement.lang = 'pl-PL';
document.documentElement.classList.remove('app-loading', 'app-startup-failed');
document.getElementById('workspace').hidden = false;
document.getElementById('panel-overview').hidden = false;
document.getElementById('overview-layout-open').hidden = false;
currentPanel = 'overview';
const visible = ['connection_chart', 'traffic_chart'];
const hidden = ADMIN_DASHBOARD_WIDGETS.filter(id => !visible.includes(id));
window.__savedDashboard = {
...structuredClone(ADMIN_APPEARANCE_DEFAULTS),
language: 'pl_pl',
dashboard_density: 'ultra',
dashboard_widgets: [...visible, ...hidden],
dashboard_hidden_widgets: hidden,
dashboard_widget_sizes: {connection_chart: 6, traffic_chart: 3},
dashboard_presets: [],
};
window.rustProxyPreferences.load = async () => window.__savedDashboard;
window.rustProxyPreferences.current = () => window.__savedDashboard;
window.rustProxyPreferences.update = async patch => {
window.__savedDashboard = {...window.__savedDashboard, ...structuredClone(patch)};
applyAdminPreferences(window.__savedDashboard);
return window.__savedDashboard;
};
applyAdminPreferences(window.__savedDashboard);
language = 'pl_pl'; languagePreference = 'pl_pl';
for (const node of document.querySelectorAll('#overview-layout-dialog [data-i18n]'))
node.textContent = t(node.dataset.i18n);
for (const node of document.querySelectorAll('#overview-layout-dialog [data-i18n-placeholder]'))
node.placeholder = t(node.dataset.i18nPlaceholder);
}""", PACKS)
with sync_playwright() as p:
browser = p.chromium.launch(headless=True, executable_path="/usr/bin/chromium", args=["--no-sandbox"])
for width in (320, 375, 768, 1440):
page = browser.new_page(viewport={"width": width, "height": 900})
errors = []
page.on("pageerror", lambda error: errors.append(str(error)))
load(page)
page.locator("#overview-layout-open").click()
page.wait_for_function("document.getElementById('overview-layout-dialog').open")
assert page.locator("#overview-layout-dialog-title").inner_text() == "Presety Dashboardu"
assert page.locator("#overview-layout-visible").count() == 0
assert page.locator("#overview-layout-hidden").count() == 0
geom = page.locator("#overview-layout-dialog").evaluate("""node => ({
left: node.getBoundingClientRect().left,
right: node.getBoundingClientRect().right,
client: node.clientWidth,
scroll: node.scrollWidth,
})""")
assert geom["left"] >= -1 and geom["right"] <= width + 1, (width, geom)
assert geom["scroll"] <= geom["client"] + 2, (width, geom)
page.locator("#overview-preset-name").fill("NOC")
page.locator("#overview-preset-save").click()
page.wait_for_function("window.__savedDashboard.dashboard_presets.length === 1")
saved = page.evaluate("window.__savedDashboard.dashboard_presets[0]")
assert saved["name"] == "NOC"
assert saved["dashboard_density"] == "ultra"
assert saved["dashboard_widgets"][:2] == ["connection_chart", "traffic_chart"]
assert len(saved["dashboard_hidden_widgets"]) == len(saved["dashboard_widgets"]) - 2
assert saved["dashboard_widget_sizes"] == {"connection_chart": 6, "traffic_chart": 3}
assert page.locator("#overview-preset-list .dashboard-preset-row").count() == 1
assert "2 widocznych kart" in page.locator(".dashboard-preset-info small").inner_text()
page.locator("#overview-preset-name").fill("NOC")
page.locator("#overview-preset-save").click()
page.wait_for_function("document.getElementById('overview-layout-message').textContent.includes('Zaktualizowano')")
assert page.evaluate("window.__savedDashboard.dashboard_presets.length") == 1
page.locator("#overview-layout-cancel").click()
page.evaluate("""async () => {
await window.rustProxyPreferences.update({
dashboard_density: 'normal',
dashboard_widgets: [...ADMIN_DASHBOARD_WIDGETS],
dashboard_hidden_widgets: [],
dashboard_widget_sizes: null,
});
}""")
page.locator("#overview-layout-open").click()
page.locator("#overview-preset-list .outline").click()
page.wait_for_function("!document.getElementById('overview-layout-dialog').open")
restored = page.evaluate("""() => ({
density: window.__savedDashboard.dashboard_density,
hidden: window.__savedDashboard.dashboard_hidden_widgets.length,
sizes: window.__savedDashboard.dashboard_widget_sizes,
})""")
assert restored == {
"density": "ultra",
"hidden": len(saved["dashboard_hidden_widgets"]),
"sizes": {"connection_chart": 6, "traffic_chart": 3},
}, (width, restored)
fonts = page.evaluate("""() => {
const family = selector => getComputedStyle(document.querySelector(selector)).fontFamily;
return {
connection: family('[data-dashboard-widget="connection_chart"] .small-label'),
traffic: family('[data-dashboard-widget="traffic_chart"] .card-heading h2'),
wide: family('[data-dashboard-widget="wide_cpu"] .card-heading h2'),
compact: family('[data-dashboard-widget="http_protocol"] > span:first-child'),
};
}""")
assert len(set(fonts.values())) == 1, (width, fonts)
page.locator("#overview-layout-open").click()
page.once("dialog", lambda prompt: prompt.accept())
page.locator("#overview-preset-list .quiet").click()
page.wait_for_function("window.__savedDashboard.dashboard_presets.length === 0")
assert page.locator("#overview-preset-list .dashboard-preset-row").count() == 0
assert page.locator("#overview-preset-empty").is_visible()
# The browser enforces the same 20-preset limit as the preferences API.
page.evaluate("""() => {
const prefs = window.__savedDashboard;
prefs.dashboard_presets = Array.from({length: 20}, (_, index) => ({
name: `Preset ${index + 1}`,
dashboard_density: prefs.dashboard_density,
dashboard_widgets: [...prefs.dashboard_widgets],
dashboard_hidden_widgets: [...prefs.dashboard_hidden_widgets],
dashboard_widget_sizes: prefs.dashboard_widget_sizes === null ? null : {...prefs.dashboard_widget_sizes},
}));
applyAdminPreferences(prefs);
window.rustProxyDashboardLayout.refresh();
}""")
assert page.locator("#overview-preset-list .dashboard-preset-row").count() == 20
page.locator("#overview-preset-name").fill("Preset 21")
page.locator("#overview-preset-save").click()
assert page.evaluate("window.__savedDashboard.dashboard_presets.length") == 20
assert "Osiągnięto limit presetów" in page.locator("#overview-layout-message").inner_text()
assert not errors, (width, errors)
page.close()
print(f"PASS Dashboard presets + chart title font at {width}px")
browser.close()
print("PASS 0.33.7 Dashboard preset manager regression (simulated API)")
@@ -142,25 +142,23 @@ with sync_playwright() as p:
assert page.locator(selector).get_attribute('data-dashboard-span') == '2'
assert page.locator('[data-dashboard-widget="wide_transform_activity"]').get_attribute('data-dashboard-span') == '6'
assert page.evaluate('window.__savedDashboard.dashboard_widget_sizes') is None
# In a fresh draft, the modal editor also provides width controls.
page.locator('#overview-layout-open').click()
page.wait_for_function("document.getElementById('overview-layout-dialog').open")
tile = '#overview-layout-visible [data-preview-widget="wide_transform_activity"]'
page.locator(tile + ' .overview-layout-narrow').click()
assert page.locator(tile).get_attribute('data-dashboard-span') == '4'
page.locator(tile + ' .overview-layout-narrow').click()
assert page.locator(tile).get_attribute('data-dashboard-span') == '3'
# Modal drag-and-drop also works across former small/wide size groups.
# In 0.33.7 resizing/reordering remains in the inline layout editor; the
# former modal is now dedicated to named presets.
page.locator('#overview-layout-unlock').click()
page.wait_for_function('window.rustProxyInlineLayout.isEditing()')
for _ in range(2):
page.locator('[data-dashboard-widget="wide_transform_activity"] .overview-inline-narrow').click()
assert page.locator('[data-dashboard-widget="wide_transform_activity"]').get_attribute('data-dashboard-span') == '3'
page.evaluate('''() => {
const from = document.querySelector('[data-preview-widget="wide_transform_activity"]');
const to = document.querySelector('[data-preview-widget="requests"]');
const from = document.querySelector('[data-dashboard-widget="wide_transform_activity"] .overview-inline-grip');
const to = document.querySelector('[data-dashboard-widget="requests"] .overview-inline-grip');
const transfer = new DataTransfer();
from.dispatchEvent(new DragEvent('dragstart', {bubbles:true,cancelable:true,dataTransfer:transfer}));
to.dispatchEvent(new DragEvent('drop', {bubbles:true,cancelable:true,dataTransfer:transfer}));
to.dispatchEvent(new DragEvent('drop', {bubbles:true,cancelable:true,dataTransfer:transfer,clientX:0}));
from.dispatchEvent(new DragEvent('dragend', {bubbles:true}));
}''')
page.locator('#overview-layout-save').click()
page.wait_for_function("!document.getElementById('overview-layout-dialog').open")
page.locator('#overview-inline-save').click()
page.wait_for_function('!window.rustProxyInlineLayout.isEditing()')
assert page.evaluate('window.__savedDashboard.dashboard_widget_sizes.wide_transform_activity') == 3
assert page.evaluate('window.__savedDashboard.dashboard_widgets[0]') == 'wide_transform_activity'
# Reload account settings: new card width persists.
@@ -184,11 +182,12 @@ with sync_playwright() as p:
assert len(set(b['y'] for b in data['boxes'])) == 1, (width, data)
assert all(b['w'] > 150 for b in data['boxes']), (width,data)
assert page.evaluate('document.documentElement.scrollWidth - window.innerWidth') <= 2, (width, data)
page.locator('#overview-layout-open').click()
page.wait_for_function("document.getElementById('overview-layout-dialog').open")
page.locator('#overview-layout-default').click()
page.locator('#overview-layout-save').click()
page.wait_for_function("!document.getElementById('overview-layout-dialog').open")
page.evaluate('''preset => window.rustProxyPreferences.update({
dashboard_density: preset.dashboard_density,
dashboard_widgets: [...preset.dashboard_widgets],
dashboard_hidden_widgets: [...preset.dashboard_hidden_widgets],
dashboard_widget_sizes: null,
})''', PRESET)
assert page.evaluate('window.__savedDashboard.dashboard_widget_sizes') is None
assert page.evaluate('window.__savedDashboard.dashboard_widgets') == PRESET['dashboard_widgets']
assert page.evaluate('document.getElementById("dashboard-overview-grid").dataset.overviewLayoutMode') == 'legacy'
View File
File mode changed.