412 lines
14 KiB
JavaScript
412 lines
14 KiB
JavaScript
"use strict";
|
|
|
|
function makeFieldset() {
|
|
return document.createElement('fieldset');
|
|
}
|
|
|
|
function makeLegend(...legend) {
|
|
let ret = document.createElement('legend');
|
|
ret.append(...legend);
|
|
return ret;
|
|
}
|
|
|
|
function makeButton(label, onClick) {
|
|
let ret = document.createElement('button');
|
|
ret.setAttribute('type', 'button');
|
|
ret.append(label);
|
|
ret.addEventListener('click', onClick);
|
|
return ret;
|
|
}
|
|
|
|
function makeText(text) {
|
|
return document.createTextNode(text);
|
|
}
|
|
|
|
function makeInput(id, attrs) {
|
|
let ret = document.createElement('input');
|
|
ret.setAttribute('id', id);
|
|
for (const [attr, value] of Object.entries(attrs)) {
|
|
ret.setAttribute(attr, value);
|
|
}
|
|
return ret;
|
|
}
|
|
function makeLabel(label, forId) {
|
|
let ret = document.createElement('label');
|
|
ret.setAttribute('for', forId);
|
|
ret.innerHTML = label;
|
|
return ret;
|
|
}
|
|
|
|
class L3Section {
|
|
constructor(legend, presetname, ...inputs) {
|
|
this.legend = legend;
|
|
this.presetname = presetname;
|
|
this.inputs = inputs;
|
|
}
|
|
node() {
|
|
let fieldset = makeFieldset();
|
|
let legend = this.legend;
|
|
if (this.presetname instanceof HTMLElement) {
|
|
legend = this.presetname;
|
|
}
|
|
fieldset.append(makeLegend(legend));
|
|
let sep = undefined;
|
|
for (const input of this.inputs) {
|
|
fieldset.append(input.node());
|
|
}
|
|
return fieldset;
|
|
}
|
|
render() {
|
|
const options = this.inputs
|
|
.flatMap(input => input.option())
|
|
.filter(option => !!option);
|
|
|
|
if (options.length == 0) {
|
|
return undefined;
|
|
}
|
|
|
|
var compiledopts = new Map();
|
|
for (const option of options) {
|
|
let c = compiledopts.get(option.optionName) ?? [];
|
|
c.push(option.value);
|
|
compiledopts.set(option.optionName, c);
|
|
}
|
|
|
|
let optstrs = [];
|
|
for (const [opt, values] of compiledopts) {
|
|
if (values.length == 1) {
|
|
optstrs.push(`option ${opt} '${values[0]}'`);
|
|
} else {
|
|
for (const value of values) {
|
|
optstrs.push(`list ${opt} '${value}'`);
|
|
}
|
|
}
|
|
}
|
|
let sectionname = "";
|
|
if (typeof this.presetname === 'string') {
|
|
sectionname = ` '${this.presetname}'`;
|
|
} else if (this.presetname instanceof HTMLElement
|
|
&& this.presetname.tagName === 'INPUT'
|
|
&& this.presetname.value) {
|
|
sectionname = ` '${this.presetname.value}'`;
|
|
}
|
|
return `config ${this.legend}${sectionname}\n\t` + optstrs.join('\n\t');
|
|
}
|
|
}
|
|
|
|
class L3MultiSection {
|
|
constructor(legend, template) {
|
|
this.legend = legend;
|
|
this.template = template;
|
|
this.sections = [];
|
|
this.nsections = 0;
|
|
}
|
|
node() {
|
|
let fieldset = makeFieldset();
|
|
fieldset.append(
|
|
makeLegend(
|
|
makeButton(`+ ${this.legend}`, () => fieldset.appendChild(this.#makeSection())),
|
|
)
|
|
);
|
|
return fieldset;
|
|
}
|
|
#makeSection() {
|
|
const idsuffix = `-${this.nsections++}`;
|
|
let newsection = this.template(idsuffix);
|
|
this.sections.push(newsection);
|
|
let nodes = newsection.node();
|
|
|
|
let del = makeButton('Remove', () => this.#removeSection(newsection, nodes));
|
|
del.classList.add('del');
|
|
|
|
nodes.append(del);
|
|
return nodes;
|
|
}
|
|
#removeSection(del, nodes) {
|
|
this.sections = this.sections.filter(section => section != del);
|
|
nodes.remove();
|
|
}
|
|
render() {
|
|
return this.sections.map(section => section.render()).filter(section => !!section)
|
|
}
|
|
}
|
|
|
|
class L3Input {
|
|
constructor(label, id, optionName, attrs, datalist) {
|
|
this.label = label;
|
|
this.id = id;
|
|
this.optionName = optionName;
|
|
this.attrs = attrs;
|
|
this.datalist = datalist
|
|
this.input = undefined;
|
|
}
|
|
node() {
|
|
let div = document.createElement('div')
|
|
if (this.label) {
|
|
div.appendChild(makeLabel(this.label, this.id));
|
|
}
|
|
|
|
this.input = makeInput(this.id, this.attrs);
|
|
div.appendChild(this.input);
|
|
|
|
if (this.datalist) {
|
|
let datalist = document.createElement('datalist');
|
|
let datalistid = this.id + '-datalist';
|
|
datalist.setAttribute('id', datalistid);
|
|
this.input.setAttribute('list', datalistid);
|
|
|
|
for (const value of this.datalist) {
|
|
let option = document.createElement('option');
|
|
option.setAttribute('value', value);
|
|
datalist.append(option);
|
|
}
|
|
|
|
div.appendChild(datalist);
|
|
}
|
|
|
|
return div;
|
|
|
|
}
|
|
option() {
|
|
switch (this.input.type) {
|
|
case 'radio':
|
|
case 'checkbox':
|
|
if (!this.input.checked || this.input.value == 'undefined') return undefined;
|
|
break;
|
|
default:
|
|
if (!this.input.value) return undefined;
|
|
break;
|
|
}
|
|
|
|
const ret = {
|
|
optionName: this.optionName,
|
|
value: this.input.value,
|
|
};
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
class L3Select {
|
|
constructor(label, id, optionName, attrs, ...optionList) {
|
|
this.label = label;
|
|
this.id = id;
|
|
this.optionName = optionName;
|
|
this.attrs = attrs;
|
|
this.optionList = optionList;
|
|
this.input = undefined;
|
|
}
|
|
node() {
|
|
let div = document.createElement('div')
|
|
if (this.label) {
|
|
div.appendChild(makeLabel(this.label, this.id));
|
|
}
|
|
|
|
this.input = document.createElement('select');
|
|
this.input.setAttribute('id', this.id);
|
|
for (const [attr, value] of Object.entries(this.attrs)) {
|
|
this.input.setAttribute(attr, value);
|
|
}
|
|
div.appendChild(this.input);
|
|
|
|
for (const [optgrouplabel, options] of this.optionList) {
|
|
let target = this.input;
|
|
if (optgrouplabel != '') {
|
|
target = document.createElement('optgroup');
|
|
target.setAttribute('label', optgrouplabel);
|
|
this.input.appendChild(target);
|
|
}
|
|
for (const [label, value] of options) {
|
|
let option = document.createElement('option');
|
|
option.setAttribute('value', value);
|
|
option.appendChild(makeText(label));
|
|
target.appendChild(option);
|
|
}
|
|
}
|
|
|
|
return div;
|
|
|
|
}
|
|
option() {
|
|
if (this.input.value == 'undefined') { return undefined; }
|
|
|
|
const ret = {
|
|
optionName: this.optionName,
|
|
value: this.input.value,
|
|
};
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
class L3MultiInput {
|
|
constructor(label, template) {
|
|
this.label = label;
|
|
this.template = template;
|
|
this.inputs = [];
|
|
this.ninputs = 0;
|
|
}
|
|
node() {
|
|
let fieldset = makeFieldset();
|
|
fieldset.append(
|
|
makeLegend(
|
|
makeButton(`+ ${this.label}`, () => fieldset.append(this.#makeInput())),
|
|
)
|
|
);
|
|
return fieldset;
|
|
}
|
|
#makeInput() {
|
|
const idsuffix = `-${this.ninputs++}`;
|
|
let newinput = this.template(idsuffix);
|
|
this.inputs.push(newinput);
|
|
let div = newinput.node();
|
|
let delbutton = document.createElement('button');
|
|
delbutton.setAttribute('type', 'button');
|
|
delbutton.classList.add('del');
|
|
delbutton.innerHTML = '-';
|
|
delbutton.addEventListener('click', () => this.#removeInput(newinput, div));
|
|
div.prepend(delbutton);
|
|
return div;
|
|
}
|
|
#removeInput(del, div) {
|
|
this.inputs = this.inputs.filter(input => input != del);
|
|
div.remove();
|
|
}
|
|
option() {
|
|
return this.inputs.map(input => input.option());
|
|
}
|
|
}
|
|
|
|
class L3Config {
|
|
constructor() {
|
|
this.sections = [];
|
|
}
|
|
addSection(section) {
|
|
this.sections.push(section)
|
|
}
|
|
handleUpdate() {
|
|
renderConfig(this.sections.flatMap(section => section.render()).filter(section => !!section));
|
|
}
|
|
node() {
|
|
return this.sections.map(section => section.node())
|
|
}
|
|
}
|
|
|
|
function initForm() {
|
|
let l3configinput = document.getElementById('l3configinput');
|
|
let form = document.createElement('form');
|
|
l3configinput.appendChild(form);
|
|
|
|
const label5ghz = (chan) => [`${chan} (${5000+chan*5}MHz)`, chan.toString()]
|
|
const label2ghz = (chan) => [`${chan} (${2407+chan*5}MHz)`, chan.toString()]
|
|
const UNII1Channels = [36, 40, 44, 48].map(label5ghz);
|
|
const UNII2Channels = [52, 56, 60, 64].map(label5ghz);
|
|
const UNII2EChannels = [100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140].map(label5ghz);
|
|
const Channels5GHz = [...UNII1Channels, ...UNII2Channels, ...UNII2EChannels];
|
|
const Channels2GHz = Array(13).fill().map((_, chan) => [`${chan+1} (${2412 + chan*5}MHz)`, (chan+1).toString()]);
|
|
const Channels2GHzOFDM = [1,5,9,13].map(label2ghz);
|
|
const Channels2GHzDSSS = [1,6,11].map(label2ghz);
|
|
|
|
const l3cfg = new L3Config();
|
|
l3cfg.addSection(new L3Section('gateway', 'meta',
|
|
new L3Input('Router Name','gatewayName', 'name', {type: 'search', placeholder: 'Router Name'}),
|
|
new L3MultiInput('Router IPv4 Address', function(idsuffix) {
|
|
return new L3Input(undefined,'gatewayRouterIP4'+idsuffix, 'router_ip', {type: 'search', maxlength: 15, placeholder: 'Router IPv4 Address'});
|
|
}),
|
|
new L3MultiInput('Router IPv6 Address', function(idsuffix) {
|
|
return new L3Input(undefined,'gatewayRouterIP6'+idsuffix, 'router_ip6', {type: 'text', maxlength: 39, placeholder: 'Router IPv6 Address'});
|
|
}),
|
|
new L3Input(undefined, 'gatewayConfigVersion', 'config_version', {type: 'hidden', value: 2, disabled: ''}),
|
|
));
|
|
l3cfg.addSection(new L3Section('dns', undefined,
|
|
new L3Select('Preset','dnsAnycastSelect', 'server', {name: 'anycast'},
|
|
['', [
|
|
['Anycast DNS', 'fd43:5602:29bd:ffff:1:1:1:1'],
|
|
['Anycast DNS64', 'fd43:5602:29bd:ffff:1:1:1:64'],
|
|
['---', undefined],
|
|
]],
|
|
),
|
|
new L3MultiInput('Custom DNS Server', function(idsuffix) {
|
|
return new L3Input(undefined,'dnsOther'+idsuffix, 'server', {type: 'text', maxlength: 39, placeholder: 'Custom DNS Server'});
|
|
}),
|
|
));
|
|
l3cfg.addSection(new L3Section('wan', undefined,
|
|
new L3Input('Use VLAN','babelpeerVLAN', 'vlan', {type: 'number', min: 1, max: 4094}),
|
|
new L3Input('Use Interface directly','babelpeerIFACE', 'iface', {type: 'text', placeholder: 'eth0, eth0.4, ...'}),
|
|
));
|
|
l3cfg.addSection(new L3Section('client', undefined,
|
|
new L3Input('Use VLAN','clientVLAN', 'vlan', {type: 'number', min: 1, max: 4094}),
|
|
new L3Input('Use Interface directly','clientIFACE', 'iface', {type: 'text', placeholder: 'eth0, eth0.4, ...'}),
|
|
new L3MultiInput('Client IPv6 Subnet', function(idsuffix) {
|
|
return new L3Input(undefined,'clientIP6Addr'+idsuffix, 'ip6addr', {type: 'text', maxlength: 39, placeholder: 'IPv6 CIDR, 2a0b:f4c0:XX:YYYY::/64, fd43:5602:29bd:XXXX::/64,...'});
|
|
}),
|
|
new L3MultiInput('Client IPv4 Subnet', function(idsuffix) {
|
|
return new L3Input(undefined,'clientIPAddr'+idsuffix, 'ipaddr', {type: 'text', maxlength: 15, placeholder: 'IPv4 CIDR, 10.XX.YY.ZZ/24'});
|
|
}),
|
|
new L3Input('IPv4 SNAT','clientSNAT', 'snat', {type: 'checkbox', value: 1}),
|
|
new L3Input('DHCP Start Address','clientDHCPStart', 'dhcp_start', {type: 'text', maxlength: 15, placeholder: 'IPv4 Address, 10.XX.YY.10'}),
|
|
new L3Input('DHCP Number of Addresses','clientDHCPLimit', 'dhcp_limit', {type: 'number', min: 1, max: 65535}),
|
|
new L3Input('Wifi ESSID','clientESSID', 'essid', {type: 'text'}),
|
|
new L3Select('Wifi 2.4 GHz Channel','client2GHZ', 'chan2ghz', {name: 'chan2ghz'},
|
|
['', [['---', undefined]]],
|
|
['Non-Overlapping Channels (OFDM)', Channels2GHzOFDM],
|
|
['Non-Overlapping Channels (DSSS)', Channels2GHzDSSS],
|
|
['All Channels', Channels2GHz],
|
|
),
|
|
new L3Select('Wifi 5 GHz Channel','client5GHZ', 'chan5ghz', {name: 'chan5ghz'},
|
|
['', [['---', undefined]]],
|
|
['U-NII-1 Channels (Indoor)', UNII1Channels],
|
|
['U-NII-2 Channels (Indoor + DFS)', UNII2Channels],
|
|
['U-NII-2E Channels (Indoor + Outdoor + DFS)', UNII2EChannels],
|
|
),
|
|
));
|
|
l3cfg.addSection(new L3MultiSection('VLAN', function(idsuffix) {
|
|
return new L3Section('vlan', makeInput('vlanSectionName'+idsuffix, {type: 'number', min: 1, max: 4094, required: '', placeholder: '1..4094'}),
|
|
new L3Input('Comment','vlanComment'+idsuffix, 'comment', {type: 'text'}, ['client', 'wan']),
|
|
new L3Input('Ports','vlanPorts'+idsuffix, 'ports', {type: 'text', placeholder: 'eth0:*, eth1.4:u, ...'}),
|
|
);
|
|
}));
|
|
l3cfg.addSection(new L3MultiSection('Direct Babel Peering', function(idsuffix) {
|
|
return new L3Section('babelpeer', makeInput('babelpeerSectionName'+idsuffix, {type: 'text'}),
|
|
new L3Input('Use VLAN','babelpeerVLAN'+idsuffix, 'vlan', {type: 'number', min: 1, max: 4094}),
|
|
new L3Input('Use Interface directly','babelpeerIFACE'+idsuffix, 'iface', {type: 'text', placeholder: 'eth0, eth0.4, ...'}),
|
|
new L3Select('Babel Interface Type','babelpeerType'+idsuffix, 'type', {name: 'type'},
|
|
['', [
|
|
['Default', undefined],
|
|
['Wired', 'wired'],
|
|
['Wireless', 'wireless'],
|
|
]],
|
|
),
|
|
new L3Input('rxcost','babelpeerRXCost'+idsuffix, 'rxcost', {type: 'number', min: 96, max: 65535, placeholder: 'LAN: 96, Tunnel: 4096, ...'}),
|
|
);
|
|
}));
|
|
l3cfg.addSection(new L3MultiSection('WireGuard Peering', function(idsuffix) {
|
|
return new L3Section('wireguardpeer', makeInput('wireguardpeerSectionName'+idsuffix, {type: 'text'}),
|
|
new L3Input('Endpoint Host', 'wireguardpeerEPHost'+idsuffix, 'endpoint_host', {type: 'text', required: ''}, ['peering.nue3.fff.community','ff1.zbau.f3netze.de','nsvm.f3netze.de','2a0b:f4c0:400::']),
|
|
new L3Input('Endpoint Port','wireguardpeerEPPort'+idsuffix, 'endpoint_port', {type: 'number', required: '', min:1, max: 65535}),
|
|
new L3Input('Persistent Keepalive','wireguardpeerPersistentKeepalive', 'persistent_keepalive', {type: 'number', min: 0, max: 65535, placeholder: '1 - 65535 Seconds...'}),
|
|
new L3Input('Remote Public Key', 'wireguardpeerRemotePubKey'+idsuffix, 'remote_public_key', {type: 'text', minlength: 44, maxlength: 44, required: '', placeholder: 'base64 encoded public key'}),
|
|
new L3Input('Local Private Key', 'wireguardpeerLocalPrivKey'+idsuffix, 'local_private_key', {type: 'text', minlength: 44, maxlength: 44, placeholder: 'base64 encoded private key'}),
|
|
new L3Input('rxcost','wireguardpeerRXCost'+idsuffix, 'rxcost', {type: 'number', min: 96, max: 65535, value: 4096, placeholder: '4096'}),
|
|
new L3Input('mtu','wireguardpeerMTU'+idsuffix, 'mtu', {type: 'number', min: 1280, max: 65535, value: 1412, placeholder: '1412'}),
|
|
);
|
|
}));
|
|
|
|
form.addEventListener('input', () => l3cfg.handleUpdate());
|
|
form.replaceChildren(...l3cfg.node())
|
|
renderConfig(l3cfg.sections.flatMap(section => section.render()).filter(section => !!section));
|
|
}
|
|
|
|
function renderConfig(sections) {
|
|
let l3configoutput = document.getElementById('l3configoutput');
|
|
let l3configoutputpre = document.createElement('pre');
|
|
let code = document.createElement('code');
|
|
l3configoutputpre.appendChild(code);
|
|
|
|
code.innerHTML += sections.join('\n\n');
|
|
|
|
l3configoutput.replaceChildren(l3configoutputpre);
|
|
}
|
|
|
|
initForm();
|