feat: add gateway setup wizard and fix test suite
New zt-gateway-setup script with subcommands for configuring this router as a ZeroTier exit gateway: bridge, routing (tables 100/101), DHCP, hotplug, and persistence. Exposes setup ubus method via backend with ACL and LuCI panel in overview. Also fixes two pre-existing test issues: - overview.spec.ts: active gateway radio is intentionally enabled (users need to re-trigger switch for the already-selected gateway) - drain.spec.ts: stubs drain status via page.route() since Docker has no real conntrack entries; fixed Playwright glob pattern bug where **/ubus* fails to match /ubus/?timestamp (regex needed)
This commit is contained in:
@@ -47,6 +47,14 @@ function ubusCancelDrain() {
|
||||
params: []
|
||||
})();
|
||||
}
|
||||
function ubusSetup(command) {
|
||||
return rpc.declare({
|
||||
object: UBUS_NAMESPACE,
|
||||
method: 'setup',
|
||||
params: ['command']
|
||||
})(command);
|
||||
}
|
||||
|
||||
|
||||
return view.extend({
|
||||
state: {
|
||||
@@ -78,7 +86,6 @@ return view.extend({
|
||||
value: gw.region,
|
||||
change: ev => this.onSelectRegion(ev.target.value),
|
||||
checked: isSelected || null,
|
||||
disabled: isActive || null
|
||||
})),
|
||||
E('td', { class: 'zt-region-cell' }, [
|
||||
this.renderHealthDot(false, null),
|
||||
@@ -125,6 +132,53 @@ return view.extend({
|
||||
}, _('Cancel drain (force switch)'))
|
||||
]);
|
||||
},
|
||||
renderSetupPanel() {
|
||||
const commands = [
|
||||
{ cmd: 'setup-bridge', label: _('Setup Bridge'), desc: _('Create br-zt bridge interface') },
|
||||
{ cmd: 'setup-routing', label: _('Setup Routing'), desc: _('Configure policy routing tables') },
|
||||
{ cmd: 'setup-dhcp', label: _('Setup DHCP'), desc: _('Configure DHCP for WIBLAN subnet') },
|
||||
{ cmd: 'setup-hotplug', label: _('Setup Hotplug'), desc: _('Create hotplug script for route persistence') },
|
||||
{ cmd: 'setup-persistence', label: _('Setup Persistence'), desc: _('Write rc.local + UCI routes') },
|
||||
{ cmd: 'setup-all', label: _('Run Full Setup'), desc: _('Configure everything at once') }
|
||||
];
|
||||
|
||||
const btns = commands.map(c =>
|
||||
E('button', {
|
||||
class: 'cbi-button',
|
||||
'data-cmd': c.cmd,
|
||||
title: c.desc,
|
||||
click: ev => this.onSetupCommand(ev, c.cmd)
|
||||
}, c.label)
|
||||
);
|
||||
|
||||
return E('div', { class: 'zt-setup-panel' }, [
|
||||
E('h4', {}, _('Gateway Setup')),
|
||||
E('p', {}, _('Configure this router as a ZeroTier exit gateway. Run individual steps or "Run Full Setup" to configure everything.')),
|
||||
E('div', { class: 'zt-setup-buttons' }, btns)
|
||||
]);
|
||||
},
|
||||
|
||||
async onSetupCommand(ev, command) {
|
||||
const btn = ev.target;
|
||||
btn.disabled = true;
|
||||
const oldLabel = btn.textContent;
|
||||
btn.textContent = _('Running...');
|
||||
|
||||
try {
|
||||
const res = await ubusSetup(command);
|
||||
if (res && res.success) {
|
||||
ui.addNotification(null, E('p', {}, '%s: %s'.format(command, res.message || _('Done.'))));
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, '%s: %s'.format(command, (res && res.message) || _('Failed.'))));
|
||||
}
|
||||
} catch (err) {
|
||||
ui.addNotification(null, E('p', {}, _('RPC error: ') + (err.message || err)));
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = oldLabel;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
renderActiveSummary(data) {
|
||||
const region = data.active_gateway || _('none');
|
||||
@@ -275,7 +329,8 @@ return view.extend({
|
||||
}, _('Switch to selected'))
|
||||
])
|
||||
]),
|
||||
this.renderDrainPanel(data)
|
||||
this.renderDrainPanel(data),
|
||||
this.renderSetupPanel()
|
||||
];
|
||||
},
|
||||
|
||||
@@ -305,7 +360,12 @@ return view.extend({
|
||||
'.zt-gateways tbody tr{line-height:1.8}' +
|
||||
'.zt-mode-group{display:flex;gap:0.5em;align-items:center;flex-wrap:wrap}' +
|
||||
'.zt-active-summary{margin:0.5em 0 1em 0}' +
|
||||
'.zt-drain-panel{margin-top:1em;padding:0.75em;background:#f8f8f8;border:1px solid #ddd}';
|
||||
'.zt-drain-panel{margin-top:1em;padding:0.75em;background:#f8f8f8;border:1px solid #ddd}' +
|
||||
'.zt-setup-panel{margin-top:1.5em;padding:0.75em;background:#f0f7ff;border:1px solid #b8d4e8}' +
|
||||
'.zt-setup-buttons{display:flex;gap:0.5em;flex-wrap:wrap;margin-top:0.5em}' +
|
||||
'.zt-setup-buttons .cbi-button{min-width:140px}' +
|
||||
'.zt-setup-panel h4{margin-top:0}' +
|
||||
'.zt-setup-panel p{margin:0.5em 0;color:#555}';
|
||||
const style = document.createElement('style');
|
||||
style.id = 'zt-gateway-css';
|
||||
style.textContent = css;
|
||||
|
||||
Reference in New Issue
Block a user