diff --git a/scripts/test_admin_config_browser.py b/scripts/test_admin_config_browser.py
deleted file mode 100755
index 8721cbe..0000000
--- a/scripts/test_admin_config_browser.py
+++ /dev/null
@@ -1,124 +0,0 @@
-from pathlib import Path
-import json
-import os
-import re
-from playwright.sync_api import sync_playwright
-
-ROOT=Path(__file__).resolve().parents[1]
-admin=ROOT/'src/admin'
-router=(admin/'router.rs').read_text()
-main=router.split('fn admin_app_javascript()',1)[1].split('fn admin_vhosts_javascript()',1)[0]
-web=admin/'web'
-vhosts=['core.js','protocol.js','health.js','rates.js','editor.js','render.js']
-chunk='\n'.join((web/'vhosts'/f).read_text() for f in vhosts)
-app=['"use strict";', 'const ADMIN_DASHBOARD_DEFAULTS = Object.freeze('+ (web/'dashboard-defaults.json').read_text()+');']
-parts=re.findall(r'include_str!\("(web/[^"\n]+)"\)|(?{\n'+chunk+'\n})();')
-js='\n'.join(app)
-startup='if (document.readyState === "complete") startAdminPanel();\nelse document.addEventListener("DOMContentLoaded", startAdminPanel, { once: true });'
-assert startup in js, 'runtime script bootstrap not found'
-js=js.replace(startup,'// Browser mock harness: startup disabled')
-# App JS is reconstructed in the same module order as router.rs.
-
-html=(web/'index.html').read_text()
-for marker,rel,split in [('RP_METRICS_MAIN','partials/overview-metrics.html',''),('RP_CHARTS_MAIN','partials/overview-charts.html','')]:
- raw=(web/rel).read_text(); one,two=raw.split(split)
- html=html.replace(f'',one).replace(f'',two)
-html=re.sub(r'', '', html)
-html=re.sub(r']*href="/assets/style_[^\"]+"[^>]*>', '', html)
-html=html.replace('__RUSTPROXY_VERSION__','0.34.13')
-
-mock_cfg={
- 'global_security': {'user_agent_blocking':{'enabled':True,'patterns':['bad-bot','curl/*']},'country_access':{'mode':'block','countries':['RU']},'acl':None,'method_policy':{'mode':'block','methods':['TRACE']},'content_type_policy':{'enabled':False,'blocked':[]},'rate_limit':{'enabled':False,'requests':100,'window_seconds':60,'burst':20},'security_headers':{'hsts':{'enabled':False,'max_age_seconds':31536000,'include_subdomains':False,'preload':False},'nosniff':True,'frame_options':'deny','referrer_policy':None,'content_security_policy':None,'permissions_policy':None}},
- 'geoip_updates':{'enabled':False,'format':'csv_pair','ipv4_url':'','ipv6_url':'','mmdb_url':'','max_bytes':1000000,'interval_seconds':86400},
- 'logging': {'capture_user_agent': True, 'capture_country': False,'file':{'enabled':False},'loki':{'enabled':False},'logstash':{'enabled':False},'format':{}},
- 'error_log':{'enabled':False,'path':'/var/log/rustproxy/error.log','format':'JSON'},
- 'cache':{},'transform':{},'compression':{},'server_header':{},'diagnostic_headers':{},
- 'vhosts':[],'redirects':[],'streams':[],'not_found_hosts':[],'host_policies':[],'acl_profiles':[],'listeners':{},'tls':{},
-}
-with sync_playwright() as p:
- browser=p.chromium.launch(headless=True,executable_path='/usr/bin/chromium',args=['--no-sandbox','--disable-dev-shm-usage'])
- page=browser.new_page()
- page.set_default_timeout(4000)
- errors=[]
- page.on('pageerror',lambda exc: errors.append(str(exc)))
- page.set_content(html,wait_until='load')
- page.evaluate('''() => { window.__calls=[]; window.__mockFetch=async function(path,options={}) {
- const request={path:typeof path==='string'?path:path.url,method:options.method||'GET',body:options.body?JSON.parse(options.body):null};
- __calls.push(request);
- const reply=JSON.parse(JSON.stringify(envelope||{revision:0,config:{}}));
- if (request.method==='PUT') {
- if (request.path==='/api/config/global-security') reply.config.global_security=request.body.value;
- if (request.path==='/api/config/logging') {reply.config.logging=request.body.value.logging;reply.config.error_log=request.body.value.error_log;}
- if (request.path==='/api/config/geoip') reply.config.geoip_updates=request.body.value;
- reply.revision+=1;
- }
- if(request.method==='POST' && request.path==='/api/config/geoip/update')return new Response(JSON.stringify({outcome:'current'}),{status:200,headers:{'content-type':'application/json'}});
- return new Response(JSON.stringify(reply),{status:200,headers:{'content-type':'application/json'}});
- }; window.fetch=window.__mockFetch; }''')
- try:
- page.add_script_tag(content=js)
- except Exception as e:
- print('INIT ERRORS',errors)
- raise
- print('script page errors:', errors[:8]); assert not errors, errors
- page.evaluate('''(config) => {authenticated=true; csrf='test'; envelope={revision:10,config};
- renderConfig=()=>{}; refresh=async()=>{}; renderSettings();}''',mock_cfg)
- assert not errors,errors
- print('initial global UA:',page.locator('#global-ua-block-patterns').input_value())
- # Global rules save: UA + header untouched
- page.locator('#global-ua-block-patterns').evaluate('(el)=>{el.value="bad-bot\\nvery-bad";el.dispatchEvent(new Event("input",{bubbles:true}));}')
- page.locator('#global-security-form').evaluate('(form)=>form.dispatchEvent(new Event("submit",{bubbles:true,cancelable:true}))')
- page.wait_for_function('window.__calls.length >= 1')
- result=page.evaluate('window.__calls.at(-1)')
- print('global rules save',result['path'],result['body']['value']['user_agent_blocking'],result['body']['value']['security_headers']['frame_options'])
- assert result['path']=='/api/config/global-security'
- assert result['body']['value']['user_agent_blocking']['patterns']==['bad-bot','very-bad']
- assert result['body']['value']['security_headers']['frame_options']=='deny'
- page.wait_for_timeout(200)
- # Global headers save: UA untouched, changed nosniff
- page.locator('#global-nosniff').evaluate('(el)=>{el.checked=false;el.dispatchEvent(new Event("change",{bubbles:true}));}')
- page.locator('#global-security-headers-form').evaluate('(form)=>form.dispatchEvent(new Event("submit",{bubbles:true,cancelable:true}))')
- page.wait_for_function('window.__calls.length >= 2')
- result=page.evaluate('window.__calls.at(-1)')
- print('global headers save',result['path'],result['body']['value']['user_agent_blocking'],result['body']['value']['security_headers']['nosniff'])
- assert result['body']['value']['user_agent_blocking']['patterns']==['bad-bot','very-bad']
- assert result['body']['value']['security_headers']['nosniff']==False
- page.wait_for_timeout(200)
- # GeoIP triggered as action not section save
- page.locator('#geoip-update-now').evaluate('(el)=>el.click()')
- page.wait_for_function('window.__calls.some(x=>x.method==="POST" && x.path==="/api/config/geoip/update")')
- result=page.evaluate('window.__calls.at(-1)')
- print('geoip update',result['method'],result['path'])
- assert result['path']=='/api/config/geoip/update'
- # Error-log subsection must save logging and error_log together.
- page.evaluate('renderSettings()')
- page.locator('#error-log-enabled').evaluate('(el)=>{el.checked=true;el.dispatchEvent(new Event("change",{bubbles:true}));}')
- page.locator('#error-log-form').evaluate('(form)=>form.dispatchEvent(new Event("submit",{bubbles:true,cancelable:true}))')
- page.wait_for_function('window.__calls.some(x=>x.path==="/api/config/logging" && x.method==="PUT")')
- result=page.evaluate('window.__calls.at(-1)')
- print('logging group save',result['path'],list(result['body']['value']))
- assert result['path']=='/api/config/logging'
- assert set(result['body']['value'])=={'logging','error_log'}
- assert result['body']['value']['error_log']['enabled']==True
- assert result['body']['value']['logging']['capture_user_agent']==True
- page.wait_for_timeout(120)
- # GeoIP settings are saved through its section, while update remains POST.
- page.evaluate('renderSettings()')
- page.locator('#geoip-interval-hours').evaluate('(el)=>{el.value="48";el.dispatchEvent(new Event("input",{bubbles:true}));}')
- page.locator('#geoip-form').evaluate('(form)=>form.dispatchEvent(new Event("submit",{bubbles:true,cancelable:true}))')
- page.wait_for_function('window.__calls.some(x=>x.path==="/api/config/geoip" && x.method==="PUT")')
- result=page.evaluate('window.__calls.at(-1)')
- print('geoip section save',result['path'],result['body']['value']['interval_seconds'])
- assert result['path']=='/api/config/geoip'
- assert result['body']['value']['interval_seconds']==48*3600
- assert result['body']['expected_revision']==13
- print('browser final errors',errors[:10]); assert not errors,errors
- browser.close()