v0.33.4
This commit is contained in:
1 parent
de090d20fa
commit
c6486bfe85
16 files changed
+254
-104
No files matched your search
@@ -116,7 +116,7 @@ with sync_playwright() as p:
|
||||
envelope = {config:{web_services:[{id:'public-site', name:'Public site',document_root:'/srv/www/public'}],vhosts:[]}};
|
||||
window.rustProxyWebServer.render();
|
||||
const details = document.querySelector('.www-service-panel');
|
||||
details.open = true;
|
||||
if (!details || details.tagName !== 'SECTION') throw Error('Missing always-visible WWW metrics');
|
||||
window.__clock = 7000;
|
||||
window.rustProxyWebServer.renderLive(before, true);
|
||||
window.__clock = 10000;
|
||||
|
||||
@@ -98,7 +98,7 @@ with sync_playwright() as p:
|
||||
assert '/s' in state['top'], state
|
||||
assert state['layoutWidth'] <= state['screenWidth'] + 2, (width, state)
|
||||
assert not errors, (width, errors)
|
||||
# Exercise the Web Server per-service accordion with the same status,
|
||||
# Exercise the always-visible per-service metrics with the same status,
|
||||
# without any real HTTP/PHP request or extra polling.
|
||||
details = page.evaluate("""(status) => {
|
||||
envelope = { revision: 10, config: {
|
||||
@@ -110,11 +110,11 @@ with sync_playwright() as p:
|
||||
window.rustProxyWebServer.render();
|
||||
const detail = document.querySelector('#web-service-list .www-service-panel');
|
||||
if (!detail) return { missing: true };
|
||||
detail.open = true;
|
||||
window.rustProxyWebServer.renderLive(status, false);
|
||||
return { text: detail.textContent, rows: detail.querySelectorAll('[data-web-live-field]').length };
|
||||
return { text: detail.textContent, rows: detail.querySelectorAll('[data-web-live-field]').length,
|
||||
alwaysVisible: detail.tagName === 'SECTION' && !detail.querySelector('summary') };
|
||||
}""", second)
|
||||
assert not details.get('missing') and details['rows'] == 7, (width, details)
|
||||
assert not details.get('missing') and details['rows'] == 7 and details['alwaysVisible'], (width, details)
|
||||
assert '18 / 32' in details['text'], (width, details)
|
||||
# The first reading after an overdue browser interval must establish a
|
||||
# fresh baseline without adding a fake 0/s dip to the service chart.
|
||||
@@ -129,7 +129,7 @@ with sync_playwright() as p:
|
||||
const during = chart.getAttribute('points'), unavailable = rate.textContent;
|
||||
window.__clock = 103000; api.renderLive(resumed);
|
||||
return { before, during, unavailable, after: chart.getAttribute('points'), resumedRate: rate.textContent };
|
||||
}""", [observation(2), observation(3), observation(10), observation(11)])
|
||||
}""", [observation(2), observation(3), observation(10), observation(12)])
|
||||
assert continuity['before'] == continuity['during'], (width, continuity)
|
||||
assert continuity['unavailable'] == '-', (width, continuity)
|
||||
assert continuity['resumedRate'] != '-', (width, continuity)
|
||||
@@ -146,7 +146,7 @@ with sync_playwright() as p:
|
||||
}""")
|
||||
assert old == {'rate': '-', 'status': '-', 'top': 0}, (width, old)
|
||||
assert not errors, (width, errors)
|
||||
print(f'PASS simulated Web Server cards and service accordion at {width}px: {state["rps"]}, {state["php"]}, layout {state["layoutWidth"]}px')
|
||||
print(f'PASS simulated Web Server cards and always-visible service metrics at {width}px: {state["rps"]}, {state["php"]}, layout {state["layoutWidth"]}px')
|
||||
page.close()
|
||||
browser.close()
|
||||
print('PASS production JS bundle + all twelve visible WWW widgets (simulated data only)')
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
"""WWW/Vhost visual parity in production UI files, using simulated status.
|
||||
|
||||
Chromium exercises the real assembled admin JS/CSS and responsive layouts.
|
||||
It does not establish Rust, SSE or PHP-FPM runtime acceptance.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import re
|
||||
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()
|
||||
|
||||
|
||||
def app_bundle():
|
||||
found = re.search(r'fn admin_app_javascript\([^)]*\)[^{]*\{', ROUTER)
|
||||
assert found
|
||||
code = ROUTER[found.end():]
|
||||
code = code[:re.search(r'\]\s*\.join\("\\n"\)', code).start()]
|
||||
fragments = []
|
||||
for line in code.splitlines():
|
||||
line = line.strip()
|
||||
include = re.search(r'include_str!\("([^"]+)"\)', line)
|
||||
if include:
|
||||
fragments.append((ROOT / 'src/admin' / include.group(1)).read_text())
|
||||
elif line == 'admin_vhosts_javascript(),':
|
||||
fragments.append('(() => {\n' + '\n'.join(
|
||||
(ROOT / 'src/admin/web/vhosts' / (name + '.js')).read_text()
|
||||
for name in ('core', 'protocol', 'health', 'rates', 'editor', 'render')) + '\n})();')
|
||||
elif line.startswith('"') and line.endswith('",'):
|
||||
fragments.append(json.loads(line[:-1]))
|
||||
return '\n'.join(fragments)
|
||||
|
||||
|
||||
def status(step):
|
||||
return {'web_server_metrics': {
|
||||
'site-A': {'web': {
|
||||
'requests': step * 120, 'response_headers_us': step * 2_400_000,
|
||||
'status_5xx': step * 4, 'response_bytes': step * 20_480,
|
||||
'static_requests': step * 80, 'php_requests': step * 30,
|
||||
'other_requests': step * 10,
|
||||
}, 'php': {'capacity_used': 3, 'capacity_limit': 16}},
|
||||
'site-B': {'web': {
|
||||
'requests': step * 24, 'response_headers_us': step * 100_000,
|
||||
'status_5xx': 0, 'response_bytes': step * 8_192,
|
||||
'static_requests': step * 24, 'php_requests': 0,
|
||||
'other_requests': 0,
|
||||
}, 'php': None},
|
||||
}}
|
||||
|
||||
|
||||
with sync_playwright() as playwright:
|
||||
browser = playwright.chromium.launch(
|
||||
headless=True, executable_path='/usr/bin/chromium', args=['--no-sandbox'])
|
||||
for width in (320, 375, 640, 800, 980, 1440, 1920):
|
||||
page = browser.new_page(viewport={'width': width, 'height': 900})
|
||||
errors = []
|
||||
page.on('pageerror', lambda e: errors.append(str(e)))
|
||||
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=app_bundle())
|
||||
initial = page.evaluate('''(status) => {
|
||||
document.documentElement.classList.remove('app-loading', 'app-startup-failed');
|
||||
document.getElementById('login').hidden = true;
|
||||
document.getElementById('workspace').hidden = false;
|
||||
document.getElementById('panel-web-server').hidden = false;
|
||||
currentPanel = 'web-server';
|
||||
envelope = {revision: 10, config: {web_services: [
|
||||
{id: 'site-A', name: 'Example service', document_root: '/var/www/a', index: ['index.html'], php: {enabled: true}},
|
||||
{id: 'site-B', name: 'Static service', document_root: '/var/www/b', index: ['index.html']}
|
||||
], vhosts: [], php_fpm_profiles: []}};
|
||||
window.__clock = 1000;
|
||||
Object.defineProperty(performance, 'now', {configurable: true, value: () => window.__clock});
|
||||
latestStatus = status;
|
||||
window.rustProxyWebServer.render();
|
||||
const list = document.querySelector('#web-service-list');
|
||||
const cards = [...list.querySelectorAll(':scope > .vhost-card')];
|
||||
const metricPanels = cards.map(card => card.querySelector('.www-service-panel'));
|
||||
const grid = metricPanels[0].querySelector('.vhost-live-grid');
|
||||
const chart = metricPanels[0].querySelector('.vhost-chart');
|
||||
const svg = chart.querySelector('svg');
|
||||
const svgBox = svg.getBoundingClientRect(), svgMatrix = svg.getScreenCTM();
|
||||
const rect = node => node.getBoundingClientRect();
|
||||
const expectedMetricLabels = ['webLiveRequests','webLiveRate','webLiveErrors',
|
||||
'webLiveLatency','webLivePhpCapacity','webLiveBytes'];
|
||||
return {
|
||||
cards: cards.length, sectionTags: metricPanels.map(panel => panel.tagName),
|
||||
collapsibles: list.querySelectorAll('details, summary').length,
|
||||
liveFields: metricPanels.map(panel => panel.querySelectorAll('[data-web-live-field]').length),
|
||||
labels: [...grid.children].map(tile => tile.querySelector('span')?.dataset.i18n),
|
||||
tileClasses: [...grid.children].map(tile => tile.className),
|
||||
values: [...grid.querySelectorAll('strong')].map(el => el.textContent),
|
||||
phpMissing: metricPanels[1].querySelector('[data-web-live-field="php"]').textContent,
|
||||
columns: getComputedStyle(grid).gridTemplateColumns.split(' ').length,
|
||||
chartWidth: rect(chart).width, gridWidth: rect(grid).width,
|
||||
cardWidth: rect(cards[0]).width, listWidth: rect(list).width,
|
||||
svgWidth: svgBox.width, svgContentWidth: svg.viewBox.baseVal.width * Math.hypot(svgMatrix.a, svgMatrix.b),
|
||||
preserveAspect: svg.getAttribute('preserveAspectRatio'),
|
||||
stroke: getComputedStyle(svg.querySelector('polyline')).vectorEffect,
|
||||
chartLabel: !!chart.querySelector('.vhost-chart-head .small-label'),
|
||||
rendered: getComputedStyle(metricPanels[0]).display !== 'none' && rect(chart).height > 60,
|
||||
fitsViewport: document.documentElement.scrollWidth <= innerWidth + 2,
|
||||
expectedMetricLabels,
|
||||
};
|
||||
}''', status(1))
|
||||
expected_cols = 2 if width <= 640 else 3 if width <= 980 else 6
|
||||
assert initial['cards'] == 2, (width, initial)
|
||||
assert initial['sectionTags'] == ['SECTION', 'SECTION'], (width, initial)
|
||||
assert initial['collapsibles'] == 0, (width, initial)
|
||||
assert initial['liveFields'] == [7, 7], (width, initial)
|
||||
assert initial['labels'] == initial['expectedMetricLabels'], (width, initial)
|
||||
assert initial['tileClasses'] == ['vhost-live-metric'] * 6, (width, initial)
|
||||
assert initial['values'][0] != '-' and initial['values'][4] == '3 / 16', (width, initial)
|
||||
assert initial['phpMissing'] != '-', (width, initial)
|
||||
assert initial['columns'] == expected_cols, (width, initial)
|
||||
assert initial['chartWidth'] > 0.95 * initial['gridWidth'], (width, initial)
|
||||
assert initial['listWidth'] - initial['cardWidth'] < 3, (width, initial)
|
||||
assert abs(initial['svgWidth'] - initial['svgContentWidth']) <= 2, (width, initial)
|
||||
assert initial['preserveAspect'] == 'none' and initial['stroke'] == 'non-scaling-stroke', (width, initial)
|
||||
assert initial['chartLabel'] and initial['rendered'] and initial['fitsViewport'], (width, initial)
|
||||
|
||||
sampled = page.evaluate('''([first, second, last]) => {
|
||||
const api = window.rustProxyWebServer;
|
||||
const panel = document.querySelector('[data-web-live-id="site-A"]');
|
||||
const rate = panel.querySelector('[data-web-live-field="rate"]');
|
||||
window.__clock = 4000; api.renderLive(first, true);
|
||||
window.__clock = 7000; api.renderLive(second, true);
|
||||
const before = {rate: rate.textContent, points: panel.querySelector('polyline').getAttribute('points')};
|
||||
// Service-list redraw must show the current status without opening anything.
|
||||
latestStatus = last;
|
||||
api.render();
|
||||
const updated = document.querySelector('[data-web-live-id="site-A"]');
|
||||
const total = updated.querySelector('[data-web-live-field="requests"]').textContent;
|
||||
const split = updated.querySelector('[data-web-live-field="split"]').textContent;
|
||||
return {before, total, split,
|
||||
pointsAfterRedraw: updated.querySelector('polyline').getAttribute('points'),
|
||||
metricsRemainVisible: !!updated.querySelector('.vhost-live-metric'),
|
||||
overflow: document.documentElement.scrollWidth - innerWidth};
|
||||
}''', [status(2), status(3), status(4)])
|
||||
assert sampled['before']['rate'] not in ('-', '0/s', '0.0/s'), (width, sampled)
|
||||
assert sampled['before']['points'].count(' ') >= 1, (width, sampled)
|
||||
assert sampled['total'] != initial['values'][0], (width, sampled)
|
||||
assert sampled['split'] != '-' and sampled['metricsRemainVisible'], (width, sampled)
|
||||
assert sampled['pointsAfterRedraw'] == sampled['before']['points'], (width, sampled)
|
||||
assert sampled['overflow'] <= 2, (width, sampled)
|
||||
assert not errors, (width, errors)
|
||||
print(f'PASS 0.33.4 WWW live metrics visual parity at {width}px; {expected_cols} columns')
|
||||
page.close()
|
||||
browser.close()
|
||||
print('PASS always-visible full-width WWW/Vhost parity with simulated updates (not Rust runtime)')
|
||||
Reference in new issue
Block a user