'use strict';
'require view';
'require dom';
'require poll';
'require ui';
'require rpc';
'require uci';
'require form';
'require network';
function parse_portvalue(section_id) {
var ports = L.toArray(uci.get('network', section_id, 'ports'));
for (var i = 0; i < ports.length; i++) {
var m = ports[i].match(/^(\d+)([tu]?)/);
if (m && m[1] == this.option)
return m[2] || 'u';
}
return '';
}
function validate_portvalue(section_id, value) {
if (value != 'u')
return true;
var sections = this.section.cfgsections();
for (var i = 0; i < sections.length; i++) {
if (sections[i] == section_id)
continue;
if (this.formvalue(sections[i]) == 'u')
return _('%s is untagged in multiple VLANs!').format(this.title);
}
return true;
}
function update_interfaces(old_ifname, new_ifname) {
var interfaces = uci.sections('network', 'interface');
for (var i = 0; i < interfaces.length; i++) {
var old_ifnames = L.toArray(interfaces[i].ifname),
new_ifnames = [],
changed = false;
for (var j = 0; j < old_ifnames.length; j++) {
if (old_ifnames[j] == old_ifname) {
new_ifnames.push(new_ifname);
changed = true;
}
else {
new_ifnames.push(old_ifnames[j]);
}
}
if (changed) {
uci.set('network', interfaces[i]['.name'], 'ifname', new_ifnames.join(' '));
ui.addNotification(null, E('p', _('Interface %q device auto-migrated from %q to %q.')
.replace(/%q/g, '"%s"').format(interfaces[i]['.name'], old_ifname, new_ifname)));
}
}
}
function render_port_status(node, portstate) {
if (!node)
return null;
if (!portstate || !portstate.link)
dom.content(node, [
E('img', { src: L.resource('icons/port_down.png') }),
E('br'),
_('no link')
]);
else
dom.content(node, [
E('img', { src: L.resource('icons/port_up.png') }),
E('br'),
'%d'.format(portstate.speed) + _('baseT'),
E('br'),
portstate.duplex ? _('full-duplex') : _('half-duplex')
]);
return node;
}
function update_port_status(topologies) {
var tasks = [];
for (var switch_name in topologies)
tasks.push(callSwconfigPortState(switch_name).then(L.bind(function(switch_name, ports) {
for (var i = 0; i < ports.length; i++) {
var node = document.querySelector('[data-switch="%s"][data-port="%d"]'.format(switch_name, ports[i].port));
render_port_status(node, ports[i]);
}
}, topologies[switch_name], switch_name)));
return Promise.all(tasks);
}
var callSwconfigFeatures = rpc.declare({
object: 'luci',
method: 'getSwconfigFeatures',
params: [ 'switch' ],
expect: { '': {} }
});
var callSwconfigPortState = rpc.declare({
object: 'luci',
method: 'getSwconfigPortState',
params: [ 'switch' ],
expect: { result: [] }
});
return view.extend({
load: function() {
return network.getSwitchTopologies().then(function(topologies) {
var tasks = [];
for (var switch_name in topologies) {
tasks.push(callSwconfigFeatures(switch_name).then(L.bind(function(features) {
this.features = features;
}, topologies[switch_name])));
tasks.push(callSwconfigPortState(switch_name).then(L.bind(function(ports) {
this.portstate = ports;
}, topologies[switch_name])));
}
return Promise.all(tasks).then(function() { return topologies });
});
},
render: function(topologies) {
var m, s, o;
m = new form.Map('network', _('Switch'), _('The network ports on this device can be combined to several VLANs in which computers can communicate directly with each other. VLANs are often used to separate different network segments. Often there is by default one Uplink port for a connection to the next greater network like the internet and other ports for a local network.'));
var switchSections = uci.sections('network', 'switch');
for (var i = 0; i < switchSections.length; i++) {
var switchSection = switchSections[i],
sid = switchSection['.name'],
switch_name = switchSection.name || sid,
topology = topologies[switch_name];
if (!topology) {
ui.addNotification(null, _('Switch %q has an unknown topology - the VLAN settings might not be accurate.').replace(/%q/, switch_name));
topologies[switch_name] = topology = {
features: {},
netdevs: {
5: 'eth0'
},
ports: [
{ num: 0, label: 'Port 1' },
{ num: 1, label: 'Port 2' },
{ num: 2, label: 'Port 3' },
{ num: 3, label: 'Port 4' },
{ num: 4, label: 'Port 5' },
{ num: 5, label: 'CPU (eth0)', device: 'eth0', need_tag: false }
]
};
}
var feat = topology.features,
min_vid = feat.min_vid || 0,
max_vid = feat.max_vid || 16,
num_vlans = feat.num_vlans || 16,
switch_title = _('Switch %q').replace(/%q/, '"%s"'.format(switch_name)),
vlan_title = _('VLANs on %q').replace(/%q/, '"%s"'.format(switch_name));
if (feat.switch_title) {
switch_title += ' (%s)'.format(feat.switch_title);
vlan_title += ' (%s)'.format(feat.switch_title);
}
s = m.section(form.NamedSection, sid, 'switch', switch_title);
s.addremove = false;
if (feat.vlan_option) {
o = s.option(form.Flag, feat.vlan_option, _('Enable VLAN functionality'));
o.rmempty = false;
}
if (feat.learning_option) {
o = s.option(form.Flag, feat.learning_option, _('Enable learning and aging'));
o.default = o.enabled;
}
if (feat.jumbo_option) {
o = s.option(form.Flag, feat.jumbo_option, _('Enable Jumbo Frame passthrough'));
o.enabled = '3';
o.rmempty = true;
}
if (feat.mirror_option) {
s.option(form.Flag, 'enable_mirror_rx', _('Enable mirroring of incoming packets'));
s.option(form.Flag, 'enable_mirror_tx', _('Enable mirroring of outgoing packets'));
var sp = s.option(form.ListValue, 'mirror_source_port', _('Mirror source port')),
mp = s.option(form.ListValue, 'mirror_monitor_port', _('Mirror monitor port'));
sp.depends('enable_mirror_rx', '1');
sp.depends('enable_mirror_tx', '1');
mp.depends('enable_mirror_rx', '1');
mp.depends('enable_mirror_tx', '1');
for (var j = 0; j < topology.ports.length; j++) {
sp.value(topology.ports[j].num, topology.ports[j].label);
mp.value(topology.ports[j].num, topology.ports[j].label);
}
}
s = m.section(form.TableSection, 'switch_vlan', vlan_title);
s.anonymous = true;
s.addremove = true;
s.addbtntitle = _('Add VLAN');
s.topology = topology;
s.device = switch_name;
s.filter = function(section_id) {
var device = uci.get('network', section_id, 'device');
return (device == this.device);
};
s.cfgsections = function() {
var sections = form.TableSection.prototype.cfgsections.apply(this);
return sections.sort(function(a, b) {
var vidA = feat.vid_option ? uci.get('network', a, feat.vid_option) : null,
vidB = feat.vid_option ? uci.get('network', b, feat.vid_option) : null;
vidA = +(vidA != null ? vidA : uci.get('network', a, 'vlan') || 9999);
vidB = +(vidB != null ? vidB : uci.get('network', b, 'vlan') || 9999);
return (vidA - vidB);
});
};
s.handleAdd = function(ev) {
var sections = uci.sections('network', 'switch_vlan'),
section_id = uci.add('network', 'switch_vlan'),
max_vlan = 0,
max_vid = 0;
for (var j = 0; j < sections.length; j++) {
if (sections[j].device != this.device)
continue;
var vlan = +sections[j].vlan,
vid = feat.vid_option ? +sections[j][feat.vid_option] : null;
if (vlan > max_vlan)
max_vlan = vlan;
if (vid > max_vid)
max_vid = vid;
}
uci.set('network', section_id, 'device', this.device);
uci.set('network', section_id, 'vlan', max_vlan + 1);
if (feat.vid_option)
uci.set('network', section_id, feat.vid_option, max_vid + 1);
return this.map.save(null, true);
};
o = s.option(form.Value, feat.vid_option || 'vlan', 'VLAN ID');
o.rmempty = false;
o.forcewrite = true;
o.vlan_used = {};
o.datatype = 'range(%u,%u)'.format(min_vid, feat.vid_option ? 4094 : num_vlans - 1);
o.description = _('Port status:');
o.validate = function(section_id, value) {
var v = +value,
m = feat.vid_option ? 4094 : num_vlans - 1;
if (isNaN(v) || v < min_vid || v > m)
return _('Invalid VLAN ID given! Only IDs between %d and %d are allowed.').format(min_vid, m);
var sections = this.section.cfgsections();
for (var i = 0; i < sections.length; i++) {
if (sections[i] == section_id)
continue;
if (this.formvalue(sections[i]) == v)
return _('Invalid VLAN ID given! Only unique IDs are allowed');
}
return true;
};
var port_opts = o.port_opts = [];
o.write = function(section_id, value) {
var topology = this.section.topology,
values = [];
for (var i = 0; i < this.port_opts.length; i++) {
var tagging = this.port_opts[i].formvalue(section_id),
portspec = Array.isArray(topology.ports) ? topology.ports[i] : null;
if (tagging == 't')
values.push(this.port_opts[i].option + tagging);
else if (tagging == 'u')
values.push(this.port_opts[i].option);
if (portspec && portspec.device) {
var old_tag = this.port_opts[i].cfgvalue(section_id),
old_vid = this.cfgvalue(section_id);
if (old_tag != tagging || old_vid != value) {
var old_ifname = portspec.device + (old_tag != 'u' ? '.' + old_vid : ''),
new_ifname = portspec.device + (tagging != 'u' ? '.' + value : '');
if (old_ifname != new_ifname)
update_interfaces(old_ifname, new_ifname);
}
}
}
if (feat.vlan4k_option)
uci.set('network', sid, feat.vlan4k_option, '1');
uci.set('network', section_id, 'ports', values.join(' '));
return form.Value.prototype.write.apply(this, [section_id, value]);
};
o.cfgvalue = function(section_id) {
var value = feat.vid_option ? uci.get('network', section_id, feat.vid_option) : null;
return (value || uci.get('network', section_id, 'vlan'));
};
s.option(form.Value, 'description', _('Description'));
for (var j = 0; Array.isArray(topology.ports) && j < topology.ports.length; j++) {
var portspec = topology.ports[j],
portstate = Array.isArray(topology.portstate) ? topology.portstate[portspec.num] : null;
o = s.option(form.ListValue, String(portspec.num), portspec.label);
o.value('', _('off'));
if (!portspec.need_tag)
o.value('u', _('untagged'));
o.value('t', _('tagged'));
o.cfgvalue = parse_portvalue;
o.validate = validate_portvalue;
o.write = function() {};
o.description = render_port_status(E('small', {
'data-switch': switch_name,
'data-port': portspec.num
}), portstate);
port_opts.push(o);
}
port_opts.sort(function(a, b) {
return a.option > b.option;
});
}
poll.add(L.bind(update_port_status, m, topologies));
return m.render();
}
});