0.32.1
This commit is contained in:
1 parent
81ab39e70a
commit
101df02a5f
45 files changed
+1148
-2277
No files matched your search
@@ -7,7 +7,7 @@ import tomllib
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
read = lambda path: (root / path).read_text(encoding='utf-8')
|
||||
version = tomllib.loads(read('Cargo.toml'))['package']['version']
|
||||
assert version == '0.32.0', version
|
||||
assert version == '0.32.1', version
|
||||
spec = json.loads(read('src/admin/openapi.json'))
|
||||
assert spec['info']['version'] == version
|
||||
paths = spec['paths']
|
||||
@@ -64,5 +64,5 @@ for language in ('en_US', 'pl_pl'):
|
||||
for key in ('certificateApiView', 'certificateApiEdit', 'certificateApiSave', 'certificateApiRegenerate',
|
||||
'certificateApiRegenerateConfirm', 'certificateApiTokenHidden', 'certificateApiCreatedAt'):
|
||||
assert strings.get(key), (language, key)
|
||||
assert '0.32.0' in read('docs/TEST_REPORT.md')
|
||||
assert '0.32.1' in read('docs/TEST_REPORT.md')
|
||||
print('PASS: 0.31.9 per-credential API routes, separated metadata editing/token regeneration, OpenAPI security, UI and locales (source-only).')
|
||||
@@ -24,7 +24,7 @@ def read_docker_agent_source() -> str:
|
||||
|
||||
preflight(root)
|
||||
manifest = tomllib.loads((root / "Cargo.toml").read_text())
|
||||
assert manifest["package"]["version"] == "0.32.0"
|
||||
assert manifest["package"]["version"] == "0.32.1"
|
||||
assert manifest["dependencies"]["quinn"]["version"] == "0.11.12"
|
||||
h3dep = manifest["dependencies"]["h3"]
|
||||
assert h3dep["package"] == "hpx-h3" and h3dep["version"] == "=2.5.20" and "quinn" in h3dep["features"]
|
||||
@@ -32,14 +32,14 @@ assert "h3-quinn" not in manifest["dependencies"]
|
||||
lock_path = root / "Cargo.lock"
|
||||
if lock_path.exists():
|
||||
lock = lock_path.read_text()
|
||||
assert 'name = "rust-proxy"\nversion = "0.32.0"' in lock
|
||||
assert 'name = "rust-proxy"\nversion = "0.32.1"' in lock
|
||||
assert 'name = "systemstat"' in lock and 'name = "metrics-process"' in lock and 'name = "sysinfo"' in lock, "Cargo.lock must be resolver-generated after adding cross-platform statistics libraries"
|
||||
for release_doc, marker in {
|
||||
"README.md": "# RustProxy 0.32.0",
|
||||
"CHANGELOG.md": "## 0.32.0",
|
||||
"ROADMAP.md": "## 0.32.0",
|
||||
"AGENTS.md": "Current source version: 0.32.0.",
|
||||
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.32.0",
|
||||
"README.md": "# RustProxy 0.32.1",
|
||||
"CHANGELOG.md": "## 0.32.1",
|
||||
"ROADMAP.md": "## 0.32.1",
|
||||
"AGENTS.md": "Current source version: 0.32.1.",
|
||||
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.32.1",
|
||||
}.items():
|
||||
assert marker in (root / release_doc).read_text(), f"stale release identity in {release_doc}"
|
||||
assert tomllib.loads((root / "rust-toolchain.toml").read_text())["toolchain"]["channel"] == "stable"
|
||||
@@ -285,7 +285,7 @@ assert 'Opt-in health decisions based on ordinary user traffic. Defaults to disa
|
||||
assert '"force_ssl"' in openapi and '"forward_client_ip"' in openapi
|
||||
assert '"backend_header"' in openapi and '"backend_timing_header"' in openapi
|
||||
assert '"StreamMetrics"' in openapi and '"streams"' in openapi
|
||||
for marker in ['"version": "0.32.0"', '"/api/traffic"', '"capture_user_agent"', '"capture_country"', '"hot_auto_trim_enabled"', '"hot_auto_trim_after_seconds"', '"hot_auto_trim_target_percent"', '"/api/config/{section}"', '"ConfigSectionUpdate"', '"cold_on_idle"', '"cold_on_age"', '"cold_max_hot_seconds"', '"cold_on_lru"', '"cold_on_shutdown"', '"cold_gc_interval_seconds"', '"cold_gc_max_delete"', '"collapse_enabled"', '"collapse_wait_timeout_ms"', '"collapse_timeout_fallback"', '"stale_if_error_timeout"', '"stale_if_error_connection"', '"stale_if_error_dns"', '"stale_if_error_tls"', '"stale_if_error_statuses"', '"proactive_refresh_enabled"', '"proactive_refresh_before_expiry_seconds"', '"proactive_refresh_percent"', '"proactive_refresh_max_inflight"', '"TransformSettings"', '"TransformOverrides"', '"/api/cache/cold/cleanup"', '"/api/cache/invalidate"', '"/api/cache/bans"', '"/api/cache/bans/{id}"', '"CacheInvalidationRequest"', '"CacheInvalidationResponse"', '"ColdCleanupJob"', '"match_type"', '"regex_insensitive"', '"rewrite"', '"response"', '"covered_hosts"', '"default": true']:
|
||||
for marker in ['"version": "0.32.1"', '"/api/traffic"', '"capture_user_agent"', '"capture_country"', '"hot_auto_trim_enabled"', '"hot_auto_trim_after_seconds"', '"hot_auto_trim_target_percent"', '"/api/config/{section}"', '"ConfigSectionUpdate"', '"cold_on_idle"', '"cold_on_age"', '"cold_max_hot_seconds"', '"cold_on_lru"', '"cold_on_shutdown"', '"cold_gc_interval_seconds"', '"cold_gc_max_delete"', '"collapse_enabled"', '"collapse_wait_timeout_ms"', '"collapse_timeout_fallback"', '"stale_if_error_timeout"', '"stale_if_error_connection"', '"stale_if_error_dns"', '"stale_if_error_tls"', '"stale_if_error_statuses"', '"proactive_refresh_enabled"', '"proactive_refresh_before_expiry_seconds"', '"proactive_refresh_percent"', '"proactive_refresh_max_inflight"', '"TransformSettings"', '"TransformOverrides"', '"/api/cache/cold/cleanup"', '"/api/cache/invalidate"', '"/api/cache/bans"', '"/api/cache/bans/{id}"', '"CacheInvalidationRequest"', '"CacheInvalidationResponse"', '"ColdCleanupJob"', '"match_type"', '"regex_insensitive"', '"rewrite"', '"response"', '"covered_hosts"', '"default": true']:
|
||||
assert marker in openapi, marker
|
||||
for marker in ['"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
|
||||
assert marker in openapi, marker
|
||||
@@ -1396,7 +1396,7 @@ assert '"error_log" => {' not in admin_0241 and 'request("/api/config/error_log"
|
||||
assert '"error_log"' in openapi and 'logging, error_log, forwarding' not in openapi
|
||||
assert 'logging_section_persists_error_log_settings_atomically' in admin_0241 and 'error_log_is_not_a_standalone_configuration_section' in admin_0241
|
||||
assert 'location.pathname === "/logging"' in settings_js_0241
|
||||
assert '"RuntimeInformation"' in openapi and '"version": "0.32.0"' in openapi
|
||||
assert '"RuntimeInformation"' in openapi and '"version": "0.32.1"' in openapi
|
||||
|
||||
# 0.24.1 compiler and hostname-cardinality regressions.
|
||||
tls_0241 = rust_source(root, "src/tls.rs")
|
||||
|
||||
@@ -14,7 +14,7 @@ parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--require-lock', action='store_true', help='Require the post-upgrade Cargo.lock produced by cargo update')
|
||||
args = parser.parse_args()
|
||||
manifest = tomllib.loads((ROOT / 'Cargo.toml').read_text())
|
||||
assert manifest['package']['version'] == '0.32.0'
|
||||
assert manifest['package']['version'] == '0.32.1'
|
||||
assert manifest['package']['rust-version'] == '1.99'
|
||||
deps = manifest['dependencies']
|
||||
expected_requirements = {
|
||||
|
||||
@@ -51,7 +51,7 @@ assert '"/docs/content.json"' in admin
|
||||
readme = (ROOT / "README.md").read_text(encoding="utf-8")
|
||||
roadmap = (ROOT / "ROADMAP.md").read_text(encoding="utf-8")
|
||||
changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
|
||||
assert readme.startswith("# RustProxy 0.32.0")
|
||||
assert readme.startswith("# RustProxy 0.32.1")
|
||||
assert "docs/WEB_SERVER.md" in readme and "docs/README.md" in readme
|
||||
assert readme.count("## What changed in ") == 1, "README duplicates historical release notes; use CHANGELOG/archive"
|
||||
assert "## Release history" in readme and "docs/ROADMAP_HISTORY.md" in readme
|
||||
|
||||
@@ -54,10 +54,10 @@ assert.match(logs, /cell\.dataset\.mobileLabel = label/);
|
||||
assert.match(logs, /if \(selectedIndex === index\) nodes\.push\(detailRow\(entry\)\)/);
|
||||
assert.match(css, /@media \(max-width: 700px\)/);
|
||||
assert.match(css, /#panel-filelogs \.file-logs-table tbody tr:not\(\.file-log-detail-row\) \{\s*display: grid/);
|
||||
assert.match(css, /#panel-filelogs \.file-logs-table tbody tr:not\(\.file-log-detail-row\) > td \{\s*display: block/);
|
||||
assert.match(css, /#panel-filelogs \.file-logs-table tbody tr:not\(\.file-log-detail-row\)\s*>\s*td \{\s*display: block/);
|
||||
assert.match(css, /overflow-wrap: anywhere/);
|
||||
assert.match(css, /#panel-filelogs \.file-logs-table,\s*#panel-filelogs \.file-log-aggregate-table \{[^}]*overflow-x: auto/s);
|
||||
assert.match(css, /#panel-filelogs \.file-log-inline-detail pre \{ max-width: 100%; overflow-x: auto; \}/);
|
||||
assert.match(css, /#panel-filelogs \.file-log-inline-detail pre \{[^}]*max-width:\s*100%;[^}]*overflow-x:\s*auto;[^}]*\}/s);
|
||||
|
||||
// Exercise the actual browser code's entry-summary function with a logged
|
||||
// H3 cancellation and a normal access event; this is a JS unit simulation.
|
||||
|
||||
@@ -4,7 +4,7 @@ from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
html = (ROOT / 'src/admin/web/index.html').read_text()
|
||||
assert '0.32.0' in (ROOT / "Cargo.toml").read_text()
|
||||
assert '0.32.1' in (ROOT / "Cargo.toml").read_text()
|
||||
assert '<a class="brand" href="/"' in html, 'logo must remain a true full-page navigation'
|
||||
assert 'addEventListener("click"' not in html.split('<a class="brand" href="/"',1)[1].split('</a>',1)[0]
|
||||
body = (ROOT / 'src/proxy/cache_helpers.rs').read_text()
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// 0.32.1 regression: restore pre-0.32.0 content-driven tile heights and
|
||||
// align inter-tile gaps with the same spacing as other Dashboard sections.
|
||||
// These are source/CSS contracts, not a browser or authenticated-UI test.
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
|
||||
const css = fs.readFileSync('src/admin/web/style.css', 'utf8');
|
||||
const html = fs.readFileSync('src/admin/web/index.html', 'utf8');
|
||||
|
||||
function rule(selector) {
|
||||
// Target precise blocks rather than accepting an unrelated matching rule.
|
||||
const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const match = css.match(new RegExp(`${escaped}\\s*\\{([^{}]*)\\}`));
|
||||
assert(match, `Missing CSS rule: ${selector}`);
|
||||
return match[1];
|
||||
}
|
||||
|
||||
for (const density of ['normal', 'compact', 'ultra']) {
|
||||
const declarations = rule(`html[data-dashboard-density="${density}"] #panel-overview .stats-grid`);
|
||||
assert.match(declarations, /gap:\s*var\(--ui-gap\)\s*;/,
|
||||
`${density} tiles must use the same density-aware gap as Dashboard sections`);
|
||||
}
|
||||
const sharedGap = rule('.overview-grid,\n.diagnostics-grid,\n.settings-grid,\n.config-columns');
|
||||
assert.match(sharedGap, /gap:\s*var\(--ui-gap\)/);
|
||||
for (const density of ['dense', 'comfortable', 'compact']) {
|
||||
assert.match(css, new RegExp(`body\\[data-density="${density}"\\]\\s*\\{[^}]*--ui-gap:`),
|
||||
`UI density ${density} must provide the shared gap variable`);
|
||||
}
|
||||
assert.match(css, /@media \(min-width: 1201px\)\s*\{\s*#panel-overview \.stats-grid\s*\{\s*grid-template-columns:\s*repeat\(6, minmax\(0, 1fr\)\);/,
|
||||
'six desktop columns are preserved');
|
||||
assert.doesNotMatch(css, /#panel-overview \.stats-grid\s*>\s*\.stat\s*\{[^}]*\bmin-height\s*:/s,
|
||||
'desktop cards should grow naturally to their legend/content height');
|
||||
assert.doesNotMatch(css, /#panel-overview \.stats-grid \.stat:is\(:hover, :focus-visible, :focus-within\)/,
|
||||
'card descriptions must remain visible, not hover-only');
|
||||
|
||||
const heading = rule('html[data-dashboard-density="compact"] #panel-overview .stat>span');
|
||||
const metric = rule('html[data-dashboard-density="compact"] #panel-overview .stat strong');
|
||||
const caption = rule('html[data-dashboard-density="compact"] #panel-overview .stat small');
|
||||
const payload = rule('html[data-dashboard-density="compact"] #panel-overview .payload-split');
|
||||
assert.match(heading, /font-size:\s*11px/);
|
||||
assert.match(metric, /font-size:\s*25px/);
|
||||
assert.match(caption, /font-size:\s*10px/);
|
||||
assert.match(payload, /font-size:\s*10px\s*!important/);
|
||||
assert.match(css, /#panel-overview \.stats-grid \.stat-chart>\.stat-chart-legend\s*\{[^}]*flex-wrap:\s*wrap/s);
|
||||
|
||||
const overview = html.slice(html.indexOf('id="panel-overview"'), html.indexOf('class="overview-grid"'));
|
||||
assert.equal([...overview.matchAll(/data-dashboard-widget="/g)].length, 16,
|
||||
'no metric/Stream cards may be removed');
|
||||
assert.match(overview, /id="metric-bytes-split"/);
|
||||
assert.match(overview, /id="http-protocol-chart"/);
|
||||
assert.match(overview, /id="ws-protocol-chart"/);
|
||||
console.log('PASS: 0.32.1 Overview six-column content height, 90% compact typography, shared tile spacing and inline legends.');
|
||||
@@ -8,9 +8,9 @@ from html.parser import HTMLParser
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
def read(rel): return (ROOT / rel).read_text(encoding='utf-8')
|
||||
assert tomllib.loads(read('Cargo.toml'))['package']['version'] == '0.32.0'
|
||||
assert 'name = "rust-proxy"\nversion = "0.32.0"' in read('Cargo.lock')
|
||||
assert json.loads(read('src/admin/openapi.json'))['info']['version'] == '0.32.0'
|
||||
assert tomllib.loads(read('Cargo.toml'))['package']['version'] == '0.32.1'
|
||||
assert 'name = "rust-proxy"\nversion = "0.32.1"' in read('Cargo.lock')
|
||||
assert json.loads(read('src/admin/openapi.json'))['info']['version'] == '0.32.1'
|
||||
|
||||
pages = read('src/pages.rs')
|
||||
for code in (400, 401, 403, 404, 408, 410, 413, 429, 500, 502, 503, 504):
|
||||
|
||||
@@ -135,7 +135,7 @@ def upgrade_smoke(tmp: Path) -> None:
|
||||
assert backup.is_dir()
|
||||
assert (backup / "src/obsolete.rs").is_file()
|
||||
assert not (target / "src/obsolete.rs").exists()
|
||||
assert 'version = "0.32.0"' in (target / "Cargo.toml").read_text(encoding="utf-8")
|
||||
assert 'version = "0.32.1"' in (target / "Cargo.toml").read_text(encoding="utf-8")
|
||||
assert (target / "data/keep.txt").read_text(encoding="utf-8") == "preserve\n"
|
||||
assert (target / ".env").read_text(encoding="utf-8") == "RUST_PROXY_DEMO=0\n"
|
||||
assert (target / "langs/site.json").is_file()
|
||||
@@ -146,7 +146,7 @@ def upgrade_smoke(tmp: Path) -> None:
|
||||
assert (target / "docker-agent/certificate.go").is_file()
|
||||
assert (target / "docker-agent/server.go").is_file()
|
||||
manifest = json.loads((backup / "upgrade.json").read_text(encoding="utf-8"))
|
||||
assert manifest["to_version"] == "0.32.0"
|
||||
assert manifest["to_version"] == "0.32.1"
|
||||
assert manifest["removed_obsolete_runtime_state"] is True
|
||||
assert not legacy.exists()
|
||||
assert not (backup / "legacy-data").exists()
|
||||
@@ -617,7 +617,7 @@ def source_contract_smoke() -> None:
|
||||
assert marker in admin, marker
|
||||
anonymous_block = admin.split("fn anonymous_allowed", 1)[1].split("const CERTIFICATE_API_KEY_HEADER", 1)[0]
|
||||
assert '"/api/status"' not in anonymous_block
|
||||
assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.32.0"' in openapi_0241
|
||||
assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.32.1"' in openapi_0241
|
||||
assert 'id="panel-logging"' not in html
|
||||
for marker in ['id="settings-tab-logging"', 'id="settings-page-logging"', 'id="logging-form"', 'id="error-log-form"']:
|
||||
assert marker in html, marker
|
||||
@@ -1102,7 +1102,7 @@ def source_contract_smoke() -> None:
|
||||
assert '.managed-update' in main and "flush_hot_to_cold_for_update" in main
|
||||
openapi = (ROOT / "src/admin/openapi.json").read_text(encoding="utf-8")
|
||||
assert '"/api/config/history/restore"' in openapi
|
||||
for marker in ['"version": "0.32.0"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
|
||||
for marker in ['"version": "0.32.1"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
|
||||
assert marker in openapi, marker
|
||||
assert '"software_update"' in openapi and '"update_available"' in openapi
|
||||
for marker in ['"/api/certificates/deploy"', '"CertificateDeploy"', '"CertificateDeployResult"', '"/api/certificates/reload"']:
|
||||
|
||||
@@ -21,7 +21,7 @@ const openapi = json('src/admin/openapi.json');
|
||||
const en = json('langs/en_US.json').translations;
|
||||
const pl = json('langs/pl_pl.json').translations;
|
||||
|
||||
assert.match(manifest, /version = "0\.32\.0"/);
|
||||
assert.match(manifest, /version = "0\.32\.1"/);
|
||||
assert.match(lib, /pub mod user_agent;/);
|
||||
assert.match(model, /user_agent_blocking: crate::user_agent::Policy/);
|
||||
assert.match(vhost, /user_agent_blocking: crate::user_agent::Policy/);
|
||||
@@ -52,7 +52,7 @@ assert.match(fileLogs, /localStorage\.setItem\(CONSOLIDATION_STORAGE_KEY/);
|
||||
assert.match(fileLogs, /aggregate_min_count/);
|
||||
assert.match(fileLogs, /file-log-aggregate-copy/);
|
||||
|
||||
assert.equal(openapi.info.version, '0.32.0');
|
||||
assert.equal(openapi.info.version, '0.32.1');
|
||||
assert.ok(openapi.components.schemas.Config.properties.user_agent_blocking);
|
||||
assert.ok(openapi.components.schemas.Vhost.properties.user_agent_blocking);
|
||||
assert.ok(openapi.components.schemas.FileLogSource.properties.group_id);
|
||||
|
||||
@@ -13,14 +13,14 @@ assert(html.includes('id="panel-overview"'));
|
||||
for (const id of ids) assert(html.includes(`data-dashboard-widget="${id}"`));
|
||||
assert.doesNotMatch(css, /#panel-overview \.stats-grid \.stat:is\(:hover, :focus-visible, :focus-within\)/,
|
||||
'Overview details must never become hover/focus popovers');
|
||||
assert.match(css, /#panel-overview \.stats-grid \.stat-chart > \.stat-chart-legend/,
|
||||
assert.match(css, /#panel-overview \.stats-grid \.stat-chart\s*>\s*\.stat-chart-legend/,
|
||||
'in-card chart legends must be visible, including on touch devices');
|
||||
assert.match(css, /#panel-overview \.stats-grid \{ grid-template-columns: repeat\(6, minmax\(0, 1fr\)\); \}/,
|
||||
assert.match(css, /#panel-overview \.stats-grid\s*\{\s*grid-template-columns:\s*repeat\(6, minmax\(0, 1fr\)\);\s*\}/,
|
||||
'desktop Overview must retain the original six cards per row');
|
||||
assert.doesNotMatch(css, /#panel-overview \.stats-grid \{ grid-template-columns: repeat\(4, minmax\(0, 1fr\)\); \}/,
|
||||
assert.doesNotMatch(css, /#panel-overview \.stats-grid\s*\{\s*grid-template-columns:\s*repeat\(4, minmax\(0, 1fr\)\);\s*\}/,
|
||||
'do not replace the original six-column Overview with a four-column layout');
|
||||
assert.match(css, /#panel-overview \.stats-grid > \.stat \{ min-height: 126px;/,
|
||||
'cards should be tall enough for their inline metric legends');
|
||||
assert.doesNotMatch(css, /#panel-overview \.stats-grid\s*>\s*\.stat\s*\{[^}]*\bmin-height\s*:/s,
|
||||
'0.32.0 fixed height produced empty tiles; let inline metric legends size the row');
|
||||
assert.match(html, /id="stream-dashboard-traffic-detail"/);
|
||||
assert.match(html, /id="stream-dashboard-rx-rate"/);
|
||||
assert.match(html, /id="stream-dashboard-tx-rate"/);
|
||||
@@ -95,9 +95,38 @@ assert.equal(elements.get('stream-dashboard-overview-detail').textContent, 'stre
|
||||
context.applyAdminPreferencesTest({ dashboard_widgets: cardIds, dashboard_hidden_widgets: ids });
|
||||
assert(ids.every(id => cards.get(id).hidden), 'hiding cards must still work');
|
||||
assert(!cards.get('requests').hidden, 'legacy widgets must retain their visibility');
|
||||
assert.match(css, /dashboard-widget-setting:is\(:hover, :focus-within\) \.dashboard-widget-description/, 'description is a tooltip');
|
||||
assert.match(css, /\.dashboard-widget-description \{[^}]*position: absolute/s, 'tooltip must not occupy a second row');
|
||||
assert.match(prefsSource, /toggle\.setAttribute\("aria-describedby", description\.id\)/, 'description must be accessible');
|
||||
// Native title tooltips do not participate in layout or create horizontal scroll.
|
||||
assert.doesNotMatch(css, /dashboard-widget-(?:description|setting-label-block)/, 'obsolete custom tooltip styles must be removed');
|
||||
assert.match(css, /grid-template-columns: repeat\(auto-fit, minmax\(min\(180px, 100%\), 1fr\)\)/,
|
||||
'settings card columns must fit even a narrower mobile container');
|
||||
assert.doesNotMatch(prefsSource, /role", "tooltip"|aria-describedby|dashboard-widget-description/,
|
||||
'old popover DOM and associations must be removed');
|
||||
const widgetList = { children: [], replaceChildren(...children) { this.children = children; } };
|
||||
const makeElement = (tag, text, className) => ({
|
||||
tag, textContent: text || '', className, dataset: {}, children: [], attributes: {},
|
||||
classList: { toggle() {} }, setAttribute(name, value) { this.attributes[name] = value; },
|
||||
append(...children) { this.children.push(...children); }, addEventListener() {},
|
||||
});
|
||||
for (const card of cards.values()) card.querySelector = () => ({ textContent: card.dataset.dashboardWidget });
|
||||
elements.set('dashboard-widget-list', widgetList);
|
||||
context.el = makeElement;
|
||||
context.CSS = { escape: value => value };
|
||||
vm.runInContext(prefsSource.slice(prefsSource.indexOf('function dashboardWidgetLabel'),
|
||||
prefsSource.indexOf('function refreshChartStylePalette')), context);
|
||||
context.renderDashboardWidgetSettings();
|
||||
assert.equal(widgetList.children.length, cardIds.length, 'all cards retain settings controls');
|
||||
for (const id of ids) {
|
||||
const row = widgetList.children.find(item => item.dataset.dashboardWidgetSetting === id);
|
||||
const button = row.children[0];
|
||||
const key = ({ stream_overview: 'streamWidgetOverviewDescription',
|
||||
stream_traffic: 'streamWidgetTrafficDescription', stream_sessions: 'streamWidgetSessionsDescription',
|
||||
stream_backend_health: 'streamWidgetHealthDescription' })[id];
|
||||
assert.equal(row.title, key, `${id} must have a localized browser tooltip`);
|
||||
assert.equal(button.attributes['aria-description'], key, 'the tooltip must remain accessible');
|
||||
assert.equal(button.children.length, 2, 'label must not contain an injected custom tooltip');
|
||||
}
|
||||
assert.equal(widgetList.children.filter(row => row.title).length, 4,
|
||||
'only the four translated Stream descriptions have tooltip text');
|
||||
// Editor-specific regressions: avoid the redundant enable hint and label the
|
||||
// two distinct inheritance layers instead of saying "Inherit" everywhere.
|
||||
const editor = fs.readFileSync('src/admin/web/streams.js', 'utf8');
|
||||
@@ -117,4 +146,4 @@ for (const locale of [pl, en]) {
|
||||
}
|
||||
assert.equal(pl.streamMetricsFromStream, 'Z ustawień Stream');
|
||||
assert.equal(pl.streamMetricsDefaultOff, 'Domyślnie wyłączone');
|
||||
console.log('PASS: Stream visibility, inline Overview legends, preserved Appearance tooltip and Vhost enable label.');
|
||||
console.log('PASS: Stream visibility, inline Overview legends, scroll-safe native Appearance tooltips and Vhost enable label.');
|
||||
@@ -43,7 +43,7 @@ const code = js.slice(js.indexOf(' const runtime = latestStatus?.runtime || {
|
||||
const elements = new Map();
|
||||
const dollar = id => { if (!elements.has(id)) elements.set(id, { textContent: '' }); return elements.get(id); };
|
||||
const status = {
|
||||
version: '0.32.0', uptime_seconds: 4201,
|
||||
version: '0.32.1', uptime_seconds: 4201,
|
||||
runtime: {
|
||||
platform: 'Linux', operating_system: 'Debian GNU/Linux 13', kernel_version: '6.12.0', architecture: 'x86_64',
|
||||
statistics_source: 'systemstat+sysinfo', data_directory: '/var/lib/rustproxy', database_path: '/var/lib/rustproxy/config.sqlite3',
|
||||
@@ -63,7 +63,7 @@ const context = {
|
||||
vm.runInNewContext(code, context);
|
||||
assert.equal(dollar('runtime-operating-system').textContent, 'Debian GNU/Linux 13');
|
||||
assert.equal(dollar('runtime-kernel-version').textContent, '6.12.0');
|
||||
assert.equal(dollar('runtime-version').textContent, '0.32.0');
|
||||
assert.equal(dollar('runtime-version').textContent, '0.32.1');
|
||||
assert.equal(dollar('runtime-logical-cpus').textContent, '8');
|
||||
assert.equal(dollar('runtime-cpu-usage').textContent, '17.2%');
|
||||
assert.equal(dollar('runtime-python').textContent, 'Available · 3.13.5');
|
||||
|
||||
@@ -5,8 +5,8 @@ import fs from 'node:fs';
|
||||
import vm from 'node:vm';
|
||||
|
||||
const editor = fs.readFileSync('src/admin/web/vhosts/editor.js', 'utf8');
|
||||
const first = editor.indexOf(' const wwwPanel = el("section", undefined, "vh-wide vh-www-panel");');
|
||||
const last = editor.indexOf(' field(g, "forward-client-ip",', first);
|
||||
const first = editor.indexOf('const wwwPanel = el("section", undefined, "vh-wide vh-www-panel");');
|
||||
const last = editor.indexOf('field(g, "forward-client-ip",', first);
|
||||
if (first === -1 || last === -1) throw new Error('WWW editor component missing');
|
||||
const source = editor.slice(first, last);
|
||||
if (editor.indexOf('readHosts = hostTagEditor(g, value.frontend?.hosts') < editor.indexOf('g = group("vhFrontends")')) {
|
||||
|
||||
@@ -6,8 +6,8 @@ import vm from "node:vm";
|
||||
// Exercise the actual domain-choice sync function from the vhost editor with
|
||||
// DOM stand-ins. This does not replace browser or Rust runtime acceptance.
|
||||
const source = fs.readFileSync("src/admin/web/vhosts/editor.js", "utf8");
|
||||
const start = source.indexOf(" const syncCanonical = () => {");
|
||||
const end = source.indexOf(" refreshCanonical = syncCanonical;", start);
|
||||
const start = source.search(/^\s*const syncCanonical = \(\) => \{/m);
|
||||
const end = source.indexOf("refreshCanonical = syncCanonical;", start);
|
||||
assert.ok(start >= 0 && end > start, "WWW selection logic must remain discoverable");
|
||||
const selectedLogic = source.slice(start, end);
|
||||
|
||||
|
||||
Reference in new issue
Block a user