141 lines
9.0 KiB
Python
Executable File
141 lines
9.0 KiB
Python
Executable File
from pathlib import Path
|
|
from panel_html import render_admin_index
|
|
import re, json
|
|
from playwright.sync_api import sync_playwright
|
|
root=Path(__file__).resolve().parents[2]
|
|
router=(root/'src/admin/router.rs').read_text()
|
|
|
|
def concatenate(section):
|
|
pattern=r'fn '+section+r'\([^)]*\)[^{]*\{'
|
|
m=re.search(pattern,router)
|
|
assert m, section
|
|
text=router[m.end():]
|
|
# The array contents are bounded by ].join("\\n") before the next function.
|
|
text=text[:re.search(r'\]\s*\.join\("\\n"\)', text).start()]
|
|
result=[]
|
|
for line in text.splitlines():
|
|
line=line.strip()
|
|
if 'include_str!("' in line:
|
|
path=re.search(r'include_str!\("([^"]+)"\)',line).group(1)
|
|
result.append((root/'src/admin'/path).read_text())
|
|
elif line=='admin_vhosts_javascript(),':
|
|
result.append('(() => {\n'+''.join((root/'src/admin/web/vhosts'/f'{part}.js').read_text()+'\n' for part in ['core','protocol','health','rates','editor','render'])+'})();')
|
|
elif line.startswith('"') and line.endswith('",'):
|
|
result.append(json.loads(line[:-1]))
|
|
return '\n'.join(result)
|
|
bundle=concatenate('admin_app_javascript')
|
|
assert 'window.rustProxyOverviewSamples =' in bundle
|
|
assert bundle.index('window.rustProxyOverviewSamples =') < bundle.index('window.rustProxyOverviewSamples.onChange(')
|
|
css=(root/'src/admin/web/style.css').read_text()
|
|
html=render_admin_index(root)
|
|
with sync_playwright() as p:
|
|
browser=p.chromium.launch(headless=True, executable_path='/usr/bin/chromium',args=['--no-sandbox','--allow-file-access-from-files'])
|
|
for width in [320,375,800,1440]:
|
|
page=browser.new_page(viewport={'width':width,'height':900})
|
|
errors=[]
|
|
page.on('pageerror',lambda e: errors.append(str(e)))
|
|
# Execute in a fully populated DOM without disallowed filesystem/network navigation.
|
|
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_timeout(550)
|
|
state=page.evaluate('''() => ({ controller:typeof window.rustProxyOverviewSamples,
|
|
handle:typeof window.rustProxyOverviewSamples?.onChange,
|
|
web:typeof window.rustProxyWebOverview, services:typeof window.rustProxyWebServer,
|
|
registry: document.querySelectorAll('[data-dashboard-widget^="www_"]').length,
|
|
htmlWidth:document.documentElement.scrollWidth,windowWidth:window.innerWidth })''')
|
|
print(width,state,'errors',errors)
|
|
assert state['controller']=='object' and state['handle']=='function' and state['web']=='object'
|
|
assert state['registry']==12
|
|
assert not [e for e in errors if 'onChange' in e or 'ReferenceError' in e or 'SyntaxError' in e or 'TypeError' in e],errors
|
|
assert state['htmlWidth'] <= state['windowWidth'] + 2
|
|
# The selector belongs to the same header row as the core connection;
|
|
# hiding it on other panels must not leave an empty dashboard row.
|
|
layout = page.evaluate('''() => {
|
|
document.getElementById('workspace').hidden = false;
|
|
setPanel('overview', false);
|
|
const status = document.getElementById('connection-label');
|
|
const sample = document.getElementById('overview-samples-toolbar');
|
|
const row = document.querySelector('.page-intro-meta-row');
|
|
const rect = node => { const r = node.getBoundingClientRect();
|
|
return {x:r.x,y:r.y,width:r.width,height:r.height,right:r.right}; };
|
|
const overview = { status:rect(status),sample:rect(sample),row:rect(row),
|
|
visible: !sample.hidden,inside: row.contains(sample) && row.contains(status),
|
|
overflow: document.documentElement.scrollWidth > innerWidth + 2 };
|
|
setPanel('web-server', false);
|
|
const notOverview = sample.hidden && !status.hidden;
|
|
setPanel('overview', false);
|
|
return {overview,notOverview,visibleAgain:!sample.hidden};
|
|
}''')
|
|
assert layout['overview']['inside'] and layout['overview']['visible']
|
|
assert layout['notOverview'] and layout['visibleAgain']
|
|
assert not layout['overview']['overflow'], (width, layout)
|
|
if width >= 800:
|
|
assert abs(layout['overview']['sample']['y'] - layout['overview']['status']['y']) <= 20, (width, layout)
|
|
assert layout['overview']['sample']['right'] <= layout['overview']['row']['right'] + 2, (width, layout)
|
|
# A genuinely sampled WWW metrics payload (not a screenshot-only fixture).
|
|
report = page.evaluate('''() => {
|
|
Object.defineProperty(window.performance, "now", {configurable:true, value:()=>window.__clock});
|
|
window.__clock = 1000;
|
|
currentPanel = "overview";
|
|
document.getElementById('workspace').hidden = false;
|
|
document.getElementById('login').hidden = true;
|
|
document.getElementById('panel-overview').hidden = false;
|
|
for (const n of document.querySelectorAll('[data-dashboard-widget^="www_"]')) n.hidden = false;
|
|
function snap(k) {
|
|
return {revision:7, web_server_metrics:{
|
|
"public-site": {web: {requests:100+k*30, status_2xx:86+k*24, status_3xx:3+k,
|
|
status_4xx:8+k*2, status_5xx:3+k*3, response_headers_us:4000000+k*1300000,
|
|
response_bytes:100000+k*20000, static_requests:70+k*20, php_requests:25+k*7,
|
|
other_requests:5+k*3, methods:[90+k*25,8+k*4,0,0,2+k],
|
|
selected_status:[80+k*18,2,2,1,5+k,0,0,0], active_headers:0},
|
|
php: {metrics:{requests:25+k*7, response_headers_us:2000000+k*700000,
|
|
response_bytes:30000+k*5000,failures:[1+k,1+k,1+k,0,0,0,0,0,0]},
|
|
capacity_limit:32, capacity_used:8, capacity_available:24}}}
|
|
};
|
|
}
|
|
const before = snap(0), after = snap(1);
|
|
// Saved custom layout from before 0.33.0 must not enable new cards.
|
|
const savedLayout = applyDashboardLayout(['requests', 'errors'],
|
|
normalizeHiddenDashboardWidgets([], ['requests', 'errors']));
|
|
const newCardsHidden = [...document.querySelectorAll('[data-dashboard-widget^="www_"]')]
|
|
.every(card=>card.hidden);
|
|
for (const n of document.querySelectorAll('[data-dashboard-widget^="www_"]')) n.hidden = false;
|
|
window.rustProxyWebOverview.render(before, true);
|
|
window.__clock = 4000;
|
|
window.rustProxyWebOverview.render(after, true);
|
|
let ids = ['www_requests-all-line', 'www_requests-php-line',
|
|
'www_latency-web-line', 'www_traffic-php-line', 'www_status_trend-5xx-line'];
|
|
const linePaths = ids.map(id=>document.getElementById(id)?.getAttribute('d'));
|
|
const statusText = document.getElementById('www_status-value').textContent;
|
|
const capacityText = document.getElementById('www_capacity-value').textContent;
|
|
const rateText = document.getElementById('www_php_requests-value').textContent;
|
|
const bars = document.querySelectorAll('#www_top_services-bars .www-top-row').length;
|
|
window.rustProxyOverviewSamples.setLimit(120);
|
|
const historyLimit = window.rustProxyOverviewSamples.getLimit();
|
|
currentPanel = "web-server";
|
|
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');
|
|
if (!details || details.tagName !== 'SECTION') throw Error('Missing always-visible WWW metrics');
|
|
window.__clock = 7000;
|
|
window.rustProxyWebServer.renderLive(before, true);
|
|
window.__clock = 10000;
|
|
window.rustProxyWebServer.renderLive(after, true);
|
|
const value = details.querySelector('[data-web-live-field="requests"]')?.textContent;
|
|
const phpCapacity = details.querySelector('[data-web-live-field="php"]')?.textContent;
|
|
return {linePaths,statusText,capacityText,rateText,bars,historyLimit,value,phpCapacity,newCardsHidden,
|
|
width:document.documentElement.scrollWidth};
|
|
}''')
|
|
print('sampled WWW',width,report)
|
|
assert all(report['linePaths']), report
|
|
assert report['statusText'] != '-' and report['capacityText'] == '8 / 32', report
|
|
assert report['rateText'] not in ('-', '0.0/s'), report
|
|
assert report['bars'] == 1 and report['historyLimit'] == 120 and report['newCardsHidden'], report
|
|
assert report['value'] and report['phpCapacity'] == '8 / 32', report
|
|
assert report['width'] <= width + 2, report
|
|
assert not [e for e in errors if any(x in e for x in ('ReferenceError', 'TypeError', 'SyntaxError'))], errors
|
|
page.close()
|
|
browser.close()
|
|
print('PASS: actual composed admin bundle starts without a JS TypeError')
|