'use strict';
-'require view';
'require fs';
'require rpc';
-'require validation';
'require ui';
+'require validation';
+'require view';
-var callNetworkInterfaceDump = rpc.declare({
+const callNetworkInterfaceDump = rpc.declare({
object: 'network.interface',
method: 'dump',
expect: { interface: [] }
});
-var checkUfpInstalled = rpc.declare({
+const checkUfpInstalled = rpc.declare({
object: 'file',
method: 'stat',
params: [ 'path' ]
});
-var callUfpList = rpc.declare({
+const callUfpList = rpc.declare({
object: 'fingerprint',
method: 'fingerprint',
expect: { '': {} }
});
function applyMask(addr, mask, v6) {
- var words = v6 ? validation.parseIPv6(addr) : validation.parseIPv4(addr);
- var bword = v6 ? 0xffff : 0xff;
- var bwlen = v6 ? 16 : 8;
+ const words = v6 ? validation.parseIPv6(addr) : validation.parseIPv4(addr);
+ const bword = v6 ? 0xffff : 0xff;
+ const bwlen = v6 ? 16 : 8;
if (!words || mask < 0 || mask > (v6 ? 128 : 32))
return null;
- for (var i = 0; i < words.length; i++) {
- var b = Math.min(mask, bwlen);
+ for (let i = 0; i < words.length; i++) {
+ const b = Math.min(mask, bwlen);
words[i] &= (bword << (bwlen - b)) & bword;
mask -= b;
}
}
return view.extend({
- load: function() {
+ load() {
return Promise.all([
checkUfpInstalled('/usr/sbin/ufpd')
- ]).then(data => {
- var promises = [
+ ]).then(([ufpcheck]) => {
+ return Promise.all([
callNetworkInterfaceDump(),
L.resolveDefault(fs.exec('/sbin/ip', [ '-4', 'neigh', 'show' ]), {}),
L.resolveDefault(fs.exec('/sbin/ip', [ '-4', 'route', 'show', 'table', 'all' ]), {}),
L.resolveDefault(fs.exec('/sbin/ip', [ '-6', 'neigh', 'show' ]), {}),
L.resolveDefault(fs.exec('/sbin/ip', [ '-6', 'route', 'show', 'table', 'all' ]), {}),
L.resolveDefault(fs.exec('/sbin/ip', [ '-6', 'rule', 'show' ]), {}),
- data[0].type === 'file' ? callUfpList() : null
- ];
-
- return Promise.all(promises);
+ ufpcheck?.type === 'file' ? callUfpList() : null
+ ]);
});
},
getNetworkByDevice(networks, dev, addr, mask, v6) {
- var addr_arrays = [ 'ipv4-address', 'ipv6-address', 'ipv6-prefix', 'ipv6-prefix-assignment', 'route' ],
- matching_iface = null,
- matching_prefix = -1;
+ const addr_arrays = [ 'ipv4-address', 'ipv6-address', 'ipv6-prefix', 'ipv6-prefix-assignment', 'route' ];
+ let matching_iface = null;
+ let matching_prefix = -1;
- for (var i = 0; i < networks.length; i++) {
- if (!L.isObject(networks[i]))
+ for (const net of networks) {
+ if (!L.isObject(net) || (net.l3_device !== dev && net.device !== dev))
continue;
- if (networks[i].l3_device != dev && networks[i].device != dev)
- continue;
-
- for (var j = 0; j < addr_arrays.length; j++) {
- var addr_list = networks[i][addr_arrays[j]];
-
- if (!Array.isArray(addr_list) || addr_list.length == 0)
- continue;
-
- for (var k = 0; k < addr_list.length; k++) {
- var cmp_addr = addr_list[k].address || addr_list[k].target,
- cmp_mask = addr_list[k].mask;
-
- if (cmp_addr == null)
- continue;
+ for (const key of addr_arrays) {
+ const list = net[key];
+ if (!Array.isArray(list)) continue;
- var addr1 = applyMask(cmp_addr, cmp_mask, v6),
- addr2 = applyMask(addr, cmp_mask, v6);
+ for (const { address, target, mask: cmp_mask } of list) {
+ const cmp_addr = address || target;
+ if (!cmp_addr) continue;
- if (addr1 != addr2 || mask < cmp_mask)
+ if (applyMask(cmp_addr, cmp_mask, v6) !== applyMask(addr, cmp_mask, v6) || mask < cmp_mask)
continue;
if (cmp_mask > matching_prefix) {
- matching_iface = networks[i].interface;
+ matching_iface = net.interface;
matching_prefix = cmp_mask;
}
}
return matching_iface;
},
- parseNeigh: function(s, macs, networks, v6) {
- var lines = s.trim().split(/\n/),
- res = [];
+ parseNeighbs(nbs, macs, networks, v6) {
+ const res = [];
- for (var i = 0; i < lines.length; i++) {
- var m = lines[i].match(/^([0-9a-f:.]+) (.+) (\S+) *$/),
- addr = m ? m[1] : null,
- flags = m ? m[2].trim().split(/\s+/) : [],
- state = (m ? m[3] : null) || 'FAILED',
- vendor;
+ for (const line of nbs.trim().split(/\n/)) {
+ const [, addr = null, f = [], state = null] = line.match(/^([0-9a-f:.]+) (.+) (\S+) *$/);
+ const flags = f?.trim?.().split?.(/\s+/);
+ let vendor;
- if (!addr || state == 'FAILED' || addr.match(/^fe[89a-f][0-9a-f]:/))
+ if (!addr || !state || addr.match(/^fe[89a-f][0-9a-f]:/))
continue;
- for (var j = 0; j < flags.length; j += 2)
+ for (let j = 0; j < flags.length; j += 2)
flags[flags[j]] = flags[j + 1];
if (!flags.lladdr)
vendor = macs[mac].vendor;
}
- var net = this.getNetworkByDevice(networks, flags.dev, addr, v6 ? 128 : 32, v6);
+ const net = this.getNetworkByDevice(networks, flags.dev, addr, v6 ? 128 : 32, v6);
res.push([
addr,
vendor ? flags.lladdr.toUpperCase() + ` (${vendor})` : flags.lladdr.toUpperCase(),
- E('span', { 'class': 'ifacebadge' }, [ net ? net : '(%s)'.format(flags.dev) ])
+ E('span', { 'class': 'ifacebadge' }, [ net ? net : '(%s)'.format(flags.dev) ]),
]);
}
return res;
},
- parseRoute: function(s, macs, networks, v6) {
- var lines = s.trim().split(/\n/),
- res = [];
+ parseRoutes(routes, macs, networks, v6) {
+ const res = [];
- for (var i = 0; i < lines.length; i++) {
- var m = lines[i].match(/^(?:([a-z_]+|\d+) )?(default|[0-9a-f:.\/]+) (.+)$/),
- type = (m ? m[1] : null) || 'unicast',
- dest = m ? (m[2] == 'default' ? (v6 ? '::/0' : '0.0.0.0/0') : m[2]) : null,
- flags = m ? m[3].trim().split(/\s+/) : [];
+ for (const line of routes.trim().split(/\n/)) {
+ const [, type = 'unicast', d, f = [] ] = line.match(/^(?:([a-z_]+|\d+) )?(default|[0-9a-f:.\/]+) (.+)$/);
+ const dest = d == 'default' ? (v6 ? '::/0' : '0.0.0.0/0') : d;
+ const flags = f?.trim?.().split?.(/\s+/);
if (!dest || type != 'unicast' || dest == 'fe80::/64' || dest == 'ff00::/8')
continue;
- for (var j = 0; j < flags.length; j += 2)
+ for (let j = 0; j < flags.length; j += 2)
flags[flags[j]] = flags[j + 1];
- var addr = dest.split('/'),
- bits = (addr[1] != null) ? +addr[1] : (v6 ? 128 : 32),
- net = this.getNetworkByDevice(networks, flags.dev, addr[0], bits, v6);
+ const [addr, bits = (v6 ? 128 : 32)] = dest.split('/');
+ const net = this.getNetworkByDevice(networks, flags.dev, addr, bits, v6);
res.push([
E('span', { 'class': 'ifacebadge' }, [ net ? net : '(%s)'.format(flags.dev) ]),
return res;
},
- parseRule: function(s) {
- var lines = s.trim().split(/\n/),
- res = [];
-
- for (var i = 0; i < lines.length; i++) {
- var m = lines[i].match(/^(\d+):\s+(.+)$/),
- prio = m ? m[1] : null,
- rule = m ? m[2] : null;
-
- res.push([
- prio,
- rule
- ]);
- }
-
- return res;
- },
-
- render: function(data) {
- var networks = data[0],
- ip4neigh = data[1].stdout || '',
- ip4route = data[2].stdout || '',
- ip4rule = data[3].stdout || '',
- ip6neigh = data[4].stdout || '',
- ip6route = data[5].stdout || '',
- ip6rule = data[6].stdout || '',
- macdata = data[7];
-
- var device_title = _('Which is used to access this %s').format(_('Target'));
- var target_title = _('Network and its mask that define the size of the destination');
- var gateway_title = _('The address through which this %s is reachable').format(_('Target'));
- var metric_title = _('Quantifies the cost or distance to a destination in a way that allows routers to make informed decisions about the optimal path to forward data packets');
- var table_title = _('Common name or numeric ID of the %s in which this route is found').format(_('Table'));
- var proto_title = _('The routing protocol identifier of this route');
- var source_title = _('Network and its mask that define which source addresses use this route');
-
- var neigh4tbl = E('table', { 'class': 'table' }, [
+ parseRules: rules => rules.trim().split('\n').map(l => {
+ const [, prio=null, rule=null] = l.match(/^(\d+):\s+(.+)$/) || [];
+ return [prio, rule];
+ }),
+
+ render([
+ networks,
+ { stdout: ip4neigh = '' } = {},
+ { stdout: ip4route = '' } = {},
+ { stdout: ip4rule = '' } = {},
+ { stdout: ip6neigh = '' } = {},
+ { stdout: ip6route = '' } = {},
+ { stdout: ip6rule = '' } = {},
+ macdata,
+ ]) {
+
+ const device_title = _('Which is used to access this %s').format(_('Target'));
+ const target_title = _('Network and its mask that define the size of the destination');
+ const gateway_title = _('The address through which this %s is reachable').format(_('Target'));
+ const metric_title = _('Quantifies the cost or distance to a destination in a way that allows routers to make informed decisions about the optimal path to forward data packets');
+ const table_title = _('Common name or numeric ID of the %s in which this route is found').format(_('Table'));
+ const proto_title = _('The routing protocol identifier of this route');
+ const source_title = _('Network and its mask that define which source addresses use this route');
+
+ const neigh4tbl = E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, [ _('IP address') ]),
E('th', { 'class': 'th' }, [ _('MAC address') ]),
- E('th', { 'class': 'th' }, [ _('Interface') ])
+ E('th', { 'class': 'th' }, [ _('Interface') ]),
])
]);
- var route4tbl = E('table', { 'class': 'table' }, [
+ const route4tbl = E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th', 'title': device_title }, [ _('Device') ]),
E('th', { 'class': 'th', 'title': target_title }, [ _('Target') ]),
E('th', { 'class': 'th', 'title': source_title }, [ _('Source') ]),
E('th', { 'class': 'th', 'title': metric_title }, [ _('Metric') ]),
E('th', { 'class': 'th', 'title': table_title }, [ _('Table') ]),
- E('th', { 'class': 'th', 'title': proto_title }, [ _('Protocol') ])
+ E('th', { 'class': 'th', 'title': proto_title }, [ _('Protocol') ]),
])
]);
- var rule4tbl = E('table', { 'class': 'table' }, [
+ const rule4tbl = E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, [ _('Priority') ]),
- E('th', { 'class': 'th' }, [ _('Rule') ])
+ E('th', { 'class': 'th' }, [ _('Rule') ]),
])
]);
- var neigh6tbl = E('table', { 'class': 'table' }, [
+ const neigh6tbl = E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, [ _('IP address') ]),
E('th', { 'class': 'th' }, [ _('MAC address') ]),
- E('th', { 'class': 'th' }, [ _('Interface') ])
+ E('th', { 'class': 'th' }, [ _('Interface') ]),
])
]);
- var route6tbl = E('table', { 'class': 'table' }, [
+ const route6tbl = E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th', 'title': device_title }, [ _('Device') ]),
E('th', { 'class': 'th', 'title': target_title }, [ _('Target') ]),
E('th', { 'class': 'th', 'title': source_title }, [ _('Source') ]),
E('th', { 'class': 'th', 'title': metric_title }, [ _('Metric') ]),
E('th', { 'class': 'th', 'title': table_title }, [ _('Table') ]),
- E('th', { 'class': 'th', 'title': proto_title }, [ _('Protocol') ])
+ E('th', { 'class': 'th', 'title': proto_title }, [ _('Protocol') ]),
])
]);
- var rule6tbl = E('table', { 'class': 'table' }, [
+ const rule6tbl = E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, [ _('Priority') ]),
- E('th', { 'class': 'th' }, [ _('Rule') ])
+ E('th', { 'class': 'th' }, [ _('Rule') ]),
])
]);
- cbi_update_table(neigh4tbl, this.parseNeigh(ip4neigh, macdata, networks, false),
+ cbi_update_table(neigh4tbl, this.parseNeighbs(ip4neigh, macdata, networks, false),
E('em', _('No entries available'))
);
- cbi_update_table(route4tbl, this.parseRoute(ip4route, macdata, networks, false),
+ cbi_update_table(route4tbl, this.parseRoutes(ip4route, macdata, networks, false),
E('em', _('No entries available'))
);
- cbi_update_table(rule4tbl, this.parseRule(ip4rule, networks, false),
+ cbi_update_table(rule4tbl, this.parseRules(ip4rule),
E('em', _('No entries available'))
);
- cbi_update_table(neigh6tbl, this.parseNeigh(ip6neigh, macdata, networks, true),
+ cbi_update_table(neigh6tbl, this.parseNeighbs(ip6neigh, macdata, networks, true),
E('em', _('No entries available'))
);
- cbi_update_table(route6tbl, this.parseRoute(ip6route, macdata, networks, true),
+ cbi_update_table(route6tbl, this.parseRoutes(ip6route, macdata, networks, true),
E('em', _('No entries available'))
);
- cbi_update_table(rule6tbl, this.parseRule(ip6rule, networks, false),
+ cbi_update_table(rule6tbl, this.parseRules(ip6rule),
E('em', _('No entries available'))
);
- var view = E([], [
+ const view = E([], [
E('h2', {}, [ _('Routing') ]),
E('p', {}, [ _('The following rules are currently active on this system.') ]),
E('div', {}, [
route4tbl,
E('h3', {}, [ _('Active IPv4 Rules') ]),
- rule4tbl
+ rule4tbl,
]),
E('div', { 'class': 'cbi-section', 'data-tab': 'ipv6routing', 'data-tab-title': _('IPv6 Routing') }, [
E('h3', {}, [ _('IPv6 Neighbours') ]),
route6tbl,
E('h3', {}, [ _('Active IPv6 Rules') ]),
- rule6tbl
+ rule6tbl,
])
])
]);
--- /dev/null
+'use strict';
+'require fs';
+'require rpc';
+'require tools.network as tn';
+'require ui';
+'require validation';
+'require view';
+
+const callNetworkInterfaceDump = rpc.declare({
+ object: 'network.interface',
+ method: 'dump',
+ expect: { interface: [] }
+});
+
+const checkUfpInstalled = rpc.declare({
+ object: 'file',
+ method: 'stat',
+ params: [ 'path' ]
+});
+
+const callUfpList = rpc.declare({
+ object: 'fingerprint',
+ method: 'fingerprint',
+ expect: { '': {} }
+});
+
+function applyMask(addr, mask, v6) {
+ const words = v6 ? validation.parseIPv6(addr) : validation.parseIPv4(addr);
+ const bword = v6 ? 0xffff : 0xff;
+ const bwlen = v6 ? 16 : 8;
+
+ if (!words || mask < 0 || mask > (v6 ? 128 : 32))
+ return null;
+
+ for (let i = 0; i < words.length; i++) {
+ const b = Math.min(mask, bwlen);
+ words[i] &= (bword << (bwlen - b)) & bword;
+ mask -= b;
+ }
+
+ return String.prototype.format.apply(
+ v6 ? '%x:%x:%x:%x:%x:%x:%x:%x' : '%d.%d.%d.%d', words);
+}
+
+return view.extend({
+ load() {
+ return Promise.all([
+ checkUfpInstalled('/usr/sbin/ufpd')
+ ]).then(([ufpcheck]) => {
+ return Promise.all([
+ callNetworkInterfaceDump(),
+ L.resolveDefault(fs.exec('/sbin/ip', [ '-4', '-j', 'neigh', 'show' ]), {}),
+ L.resolveDefault(fs.exec('/sbin/ip', [ '-4', '-j', 'route', 'show', 'table', 'all' ]), {}),
+ L.resolveDefault(fs.exec('/sbin/ip', [ '-4', '-j', 'rule', 'show' ]), {}),
+ L.resolveDefault(fs.exec('/sbin/ip', [ '-6', '-j', 'neigh', 'show' ]), {}),
+ L.resolveDefault(fs.exec('/sbin/ip', [ '-6', '-j', 'route', 'show', 'table', 'all' ]), {}),
+ L.resolveDefault(fs.exec('/sbin/ip', [ '-6', '-j', 'rule', 'show' ]), {}),
+ ufpcheck?.type === 'file' ? callUfpList() : null,
+ ]);
+ });
+ },
+
+ getNetworkByDevice(networks, dev, addr, mask, v6) {
+ const addr_arrays = [ 'ipv4-address', 'ipv6-address', 'ipv6-prefix', 'ipv6-prefix-assignment', 'route' ];
+ let matching_iface = null;
+ let matching_prefix = -1;
+
+ for (const net of networks) {
+ if (!L.isObject(net) || (net.l3_device !== dev && net.device !== dev))
+ continue;
+
+ for (const key of addr_arrays) {
+ const list = net[key];
+ if (!Array.isArray(list)) continue;
+
+ for (const { address, target, mask: cmp_mask } of list) {
+ const cmp_addr = address || target;
+ if (!cmp_addr) continue;
+
+ if (applyMask(cmp_addr, cmp_mask, v6) !== applyMask(addr, cmp_mask, v6) || mask < cmp_mask)
+ continue;
+
+ if (cmp_mask > matching_prefix) {
+ matching_iface = net.interface;
+ matching_prefix = cmp_mask;
+ }
+ }
+ }
+ }
+
+ return matching_iface;
+ },
+
+ parseJSON(string) {
+ try {
+ return JSON.parse(string);
+ } catch (e) {
+ return [];
+ }
+ },
+
+ parseNeighbs(nbs, macs, networks, v6) {
+ const res = [];
+
+ for (const n of this.parseJSON(nbs)) {
+ let vendor;
+ if (n.dst.match(/^fe[89a-f][0-9a-f]:/))
+ continue;
+
+ if (n.state.find(f => {return f == 'FAILED'}))
+ continue;
+
+ for (let mac in macs) {
+ if (n?.lladdr === mac)
+ vendor = macs[mac].vendor;
+ }
+
+ const net = this.getNetworkByDevice(networks, n?.dev, n?.dst, v6 ? 128 : 32, v6);
+
+ res.push([
+ E('div', { 'data-tooltip': JSON.stringify(n) }, [
+ '#',
+ n?.nud ? `; ${_('NUD')}: ${n?.nud}` : '',
+ n?.proxy === null ? `; ${_('Proxy')}: ✅` : '',
+ n?.nomaster === null ? `; ${_('No master')} : ✅` : '',
+ n?.vrf ? `; ${_('VRF')}: ${n?.vrf}` : '',
+ ]),
+
+ n?.dst,
+ n?.lladdr?.toUpperCase() + (vendor ? ` (${vendor})` : ''),
+ E('span', { 'class': 'ifacebadge' }, [ net ? net : '(%s)'.format(n?.dev) ]),
+ ]);
+ }
+
+ return res;
+ },
+
+ parseRoutes(routes, macs, networks, v6) {
+ const res = [];
+
+ for (const rt of this.parseJSON(routes)) {
+ const dest = rt.dst == 'default' ? (v6 ? '::/0' : '0.0.0.0/0') : rt.dst;
+ if (dest == 'fe80::/64' || dest == 'ff00::/8')
+ continue;
+
+ const [addr, bits = (v6 ? 128 : 32)] = dest.split('/');
+ const net = this.getNetworkByDevice(networks, rt.dev, addr, bits, v6);
+
+ res.push([
+ E('span', { 'class': 'ifacebadge' }, [ net ? net : '(%s)'.format(rt.dev) ]),
+ dest,
+ rt?.gateway || '-',
+ rt?.src || rt?.from || '-',
+ String(rt?.metric || '-'),
+ rt?.table || 'main',
+ rt?.protocol,
+ ]);
+ }
+
+ return res;
+ },
+
+ parseRules(rules) {
+ const r = [];
+ for (const rl of this.parseJSON(rules)) {
+ r.push([
+ E('div', { 'data-tooltip': JSON.stringify(rl) }, [
+ '#',
+ rl?.not === null ? `; ${_('Not')}: ✅` : '',
+ rl?.nop === null ? `; ${_('No-op')}: ✅` : '',
+ rl?.l3mdev === null ? `; ${_('L3Mdev')}: ✅` : '',
+ rl?.fwmark ? `; ${_('Fwmark')}:${rl?.fwmark}` : '',
+ rl?.from ? `; ${_('From')}:${rl?.from}` : '',
+ rl?.to ? `; ${_('To')}:${rl?.to}` : '',
+ rl?.tos ? `; ${_('ToS')}:${rl?.tos}` : '',
+ rl?.dsfield ? `; ${_('DSCP')}:${rl?.dsfield}` : '',
+ rl?.uidrange ? `; ${_('UID-range')}:${rl?.uidrange}` : '',
+ rl?.goto ? `; ${_('goto')}:${rl?.goto}` : '',
+ rl?.nat ? `; ${_('NAT')}:${rl?.nat}` : '',
+ ]),
+
+ rl?.priority,
+ rl?.iif ? E('span', { 'class': 'ifacebadge' }, [ rl?.iif ]) : '-',
+ rl?.src ? (rl?.srclen ? rl?.src + '/' + rl?.srclen : rl?.src) : _('any'),
+ rl?.sport || '-',
+ rl?.action || '-',
+ tn.protocols.find(f => {return f.i == rl?.ipproto?.split?.('-')[1] })?.d || '-',
+ rl?.oif ? E('span', { 'class': 'ifacebadge' }, [ rl?.oif ]) : '-',
+ rl?.dst ? (rl?.dstlen ? rl?.dst + '/' + rl?.dstlen : rl?.dst) : _('any'),
+ rl?.dport || '-',
+ rl?.table || '-',
+ ]);
+ }
+ return r;
+ },
+
+ render([
+ networks,
+ { stdout: ip4neighbs = '' } = {},
+ { stdout: ip4routes = '' } = {},
+ { stdout: ip4rules = '' } = {},
+ { stdout: ip6neighbs = '' } = {},
+ { stdout: ip6routes = '' } = {},
+ { stdout: ip6rules = '' } = {},
+ macdata,
+ ]) {
+
+ const device_title = _('Which is used to access this %s').format(_('Target'));
+ const target_title = _('Network and its mask that define the size of the destination');
+ const gateway_title = _('The address through which this %s is reachable').format(_('Target'));
+ const metric_title = _('Quantifies the cost or distance to a destination in a way that allows routers to make informed decisions about the optimal path to forward data packets');
+ const table_title = _('Common name or numeric ID of the %s in which this route is found').format(_('Table'));
+ const proto_title = _('The routing protocol identifier of this route');
+ const source_title = _('Network and its mask that define which source addresses use this route');
+
+ const neigh4tbl = E('table', { 'class': 'table' }, [
+ E('tr', { 'class': 'tr table-titles' }, [
+ E('th', { 'class': 'th' }, [ _('Entry') ]),
+ E('th', { 'class': 'th' }, [ _('IP address') ]),
+ E('th', { 'class': 'th' }, [ _('MAC address') ]),
+ E('th', { 'class': 'th' }, [ _('Interface') ]),
+ ])
+ ]);
+
+ const route4tbl = E('table', { 'class': 'table' }, [
+ E('tr', { 'class': 'tr table-titles' }, [
+ E('th', { 'class': 'th', 'title': device_title }, [ _('Device') ]),
+ E('th', { 'class': 'th', 'title': target_title }, [ _('Target') ]),
+ E('th', { 'class': 'th', 'title': gateway_title }, [ _('Gateway') ]),
+ E('th', { 'class': 'th', 'title': source_title }, [ _('Source') ]),
+ E('th', { 'class': 'th', 'title': metric_title }, [ _('Metric') ]),
+ E('th', { 'class': 'th', 'title': table_title }, [ _('Table') ]),
+ E('th', { 'class': 'th', 'title': proto_title }, [ _('Protocol') ]),
+ ])
+ ]);
+
+ const rule4tbl = E('table', { 'class': 'table' }, [
+ E('tr', { 'class': 'tr table-titles' }, [
+ E('th', { 'class': 'th' }, [ _('Rule') ]),
+ E('th', { 'class': 'th' }, [ _('Priority') ]),
+ E('th', { 'class': 'th' }, [ _('Ingress') ]),
+ E('th', { 'class': 'th' }, [ _('Source') ]),
+ E('th', { 'class': 'th' }, [ _('Src Port') ]),
+ E('th', { 'class': 'th' }, [ _('Action') ]),
+ E('th', { 'class': 'th' }, [ _('IP Protocol') ]),
+ E('th', { 'class': 'th' }, [ _('Egress') ]),
+ E('th', { 'class': 'th' }, [ _('Destination') ]),
+ E('th', { 'class': 'th' }, [ _('Dest Port') ]),
+ E('th', { 'class': 'th' }, [ _('Table') ]),
+ ])
+ ]);
+
+ const neigh6tbl = E('table', { 'class': 'table' }, [
+ E('tr', { 'class': 'tr table-titles' }, [
+ E('th', { 'class': 'th' }, [ _('Entry') ]),
+ E('th', { 'class': 'th' }, [ _('IP address') ]),
+ E('th', { 'class': 'th' }, [ _('MAC address') ]),
+ E('th', { 'class': 'th' }, [ _('Interface') ]),
+ ])
+ ]);
+
+ const route6tbl = E('table', { 'class': 'table' }, [
+ E('tr', { 'class': 'tr table-titles' }, [
+ E('th', { 'class': 'th', 'title': device_title }, [ _('Device') ]),
+ E('th', { 'class': 'th', 'title': target_title }, [ _('Target') ]),
+ E('th', { 'class': 'th', 'title': gateway_title }, [ _('Gateway') ]),
+ E('th', { 'class': 'th', 'title': source_title }, [ _('Source') ]),
+ E('th', { 'class': 'th', 'title': metric_title }, [ _('Metric') ]),
+ E('th', { 'class': 'th', 'title': table_title }, [ _('Table') ]),
+ E('th', { 'class': 'th', 'title': proto_title }, [ _('Protocol') ]),
+ ])
+ ]);
+
+ const rule6tbl = E('table', { 'class': 'table' }, [
+ E('tr', { 'class': 'tr table-titles' }, [
+ E('th', { 'class': 'th' }, [ _('Rule') ]),
+ E('th', { 'class': 'th' }, [ _('Priority') ]),
+ E('th', { 'class': 'th' }, [ _('Ingress') ]),
+ E('th', { 'class': 'th' }, [ _('Source') ]),
+ E('th', { 'class': 'th' }, [ _('Src Port') ]),
+ E('th', { 'class': 'th' }, [ _('Action') ]),
+ E('th', { 'class': 'th' }, [ _('IP Protocol') ]),
+ E('th', { 'class': 'th' }, [ _('Egress') ]),
+ E('th', { 'class': 'th' }, [ _('Destination') ]),
+ E('th', { 'class': 'th' }, [ _('Dest Port') ]),
+ E('th', { 'class': 'th' }, [ _('Table') ]),
+ ])
+ ]);
+
+ cbi_update_table(neigh4tbl, this.parseNeighbs(ip4neighbs, macdata, networks, false),
+ E('em', _('No entries available'))
+ );
+ cbi_update_table(route4tbl, this.parseRoutes(ip4routes, macdata, networks, false),
+ E('em', _('No entries available'))
+ );
+ cbi_update_table(rule4tbl, this.parseRules(ip4rules),
+ E('em', _('No entries available'))
+ );
+ cbi_update_table(neigh6tbl, this.parseNeighbs(ip6neighbs, macdata, networks, true),
+ E('em', _('No entries available'))
+ );
+ cbi_update_table(route6tbl, this.parseRoutes(ip6routes, macdata, networks, true),
+ E('em', _('No entries available'))
+ );
+ cbi_update_table(rule6tbl, this.parseRules(ip6rules),
+ E('em', _('No entries available'))
+ );
+
+ const view = E([], [
+ E('h2', {}, [ _('Routing') ]),
+ E('p', {}, [ _('The following rules are currently active on this system.') ]),
+ E('div', {}, [
+ E('div', { 'class': 'cbi-section', 'data-tab': 'ipv4routing', 'data-tab-title': _('IPv4 Routing') }, [
+ E('h3', {}, [ _('IPv4 Neighbours') ]),
+ neigh4tbl,
+
+ E('h3', {}, [ _('Active IPv4 Routes') ]),
+ route4tbl,
+
+ E('h3', {}, [ _('Active IPv4 Rules') ]),
+ rule4tbl,
+ ]),
+ E('div', { 'class': 'cbi-section', 'data-tab': 'ipv6routing', 'data-tab-title': _('IPv6 Routing') }, [
+ E('h3', {}, [ _('IPv6 Neighbours') ]),
+ neigh6tbl,
+
+ E('h3', {}, [ _('Active IPv6 Routes') ]),
+ route6tbl,
+
+ E('h3', {}, [ _('Active IPv6 Rules') ]),
+ rule6tbl,
+ ])
+ ])
+ ]);
+
+ ui.tabs.initTabGroup(view.lastElementChild.childNodes);
+
+ return view;
+ },
+
+ handleSaveApply: null,
+ handleSave: null,
+ handleReset: null
+});