diff options
author | Jo-Philipp Wich <jo@mein.io> | 2019-09-12 11:06:38 +0200 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2019-09-12 11:09:57 +0200 |
commit | 82743b3bd4b4603f2c6b85566d07182cb0f1ce52 (patch) | |
tree | 9444cba040a334821e6593c13541792c718084ea /modules | |
parent | 5fd21bc9efedb29ff8376f8043ed2a4b74788d6b (diff) |
luci-mod-network: reimplement switch configuration as client side view
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Diffstat (limited to 'modules')
4 files changed, 370 insertions, 465 deletions
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/switch.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/switch.js new file mode 100644 index 0000000000..b281bb1808 --- /dev/null +++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/switch.js @@ -0,0 +1,369 @@ +'use strict'; +'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(' ')); + + L.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.link) + L.dom.content(node, [ + E('img', { src: L.resource('icons/port_down.png') }), + E('br'), + _('no link') + ]); + else + L.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 L.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 <abbr title=\"Virtual Local Area Network\">VLAN</abbr>s in which computers can communicate directly with each other. <abbr title=\"Virtual Local Area Network\">VLAN</abbr>s 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) { + L.ui.addNotification(null, _('Switch %q has an unknown topology - the VLAN settings might not be accurate.').replace(/%q/, 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) + s.option(form.Flag, feat.vlan_option, _('Enable VLAN functionality')); + + 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 == switch_name); + }; + + 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 != s.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', s.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); + }; + + var port_opts = []; + + 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; + }; + + o.write = function(section_id, value) { + var topology = this.section.topology, + values = []; + + for (var i = 0; i < port_opts.length; i++) { + var tagging = port_opts[i].formvalue(section_id), + portspec = Array.isArray(topology.ports) ? topology.ports[i] : null; + + if (tagging == 't') + values.push(port_opts[i].option + tagging); + else if (tagging == 'u') + values.push(port_opts[i].option); + + if (portspec && portspec.device) { + var old_tag = 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')); + }; + + 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; + }); + } + + L.Poll.add(L.bind(update_port_status, m, topologies)); + + return m.render(); + } +}); diff --git a/modules/luci-mod-network/luasrc/controller/admin/network.lua b/modules/luci-mod-network/luasrc/controller/admin/network.lua index a381bbc614..f8623be93e 100644 --- a/modules/luci-mod-network/luasrc/controller/admin/network.lua +++ b/modules/luci-mod-network/luasrc/controller/admin/network.lua @@ -18,13 +18,7 @@ function index() end) if has_switch then - page = node("admin", "network", "vlan") - page.target = cbi("admin_network/vlan") - page.title = _("Switch") - page.order = 20 - - page = entry({"admin", "network", "switch_status"}, call("switch_status"), nil) - page.leaf = true + entry({"admin", "network", "switch"}, view("network/switch"), _("Switch"), 20) end @@ -271,13 +265,6 @@ function wifi_reconnect(radio) end end -function switch_status(switches) - local s = require "luci.tools.status" - - luci.http.prepare_content("application/json") - luci.http.write_json(s.switch_status(switches)) -end - function diag_command(cmd, addr) if addr and addr:match("^[a-zA-Z0-9%-%.:_]+$") then luci.http.prepare_content("text/plain") diff --git a/modules/luci-mod-network/luasrc/model/cbi/admin_network/vlan.lua b/modules/luci-mod-network/luasrc/model/cbi/admin_network/vlan.lua deleted file mode 100644 index edeb193ef7..0000000000 --- a/modules/luci-mod-network/luasrc/model/cbi/admin_network/vlan.lua +++ /dev/null @@ -1,389 +0,0 @@ --- Copyright 2008 Steven Barth <steven@midlink.org> --- Copyright 2010-2011 Jo-Philipp Wich <jow@openwrt.org> --- Licensed to the public under the Apache License 2.0. - -m = Map("network", translate("Switch"), translate("The network ports on this device can be combined to several <abbr title=\"Virtual Local Area Network\">VLAN</abbr>s in which computers can communicate directly with each other. <abbr title=\"Virtual Local Area Network\">VLAN</abbr>s 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.")) - -local fs = require "nixio.fs" -local ut = require "luci.util" -local nw = require "luci.model.network" -local switches = { } - -nw.init(m.uci) - -local topologies = nw:get_switch_topologies() or {} - -local update_interfaces = function(old_ifname, new_ifname) - local info = { } - - m.uci:foreach("network", "interface", function(section) - local old_ifnames = section.ifname - local new_ifnames = { } - local cur_ifname - local changed = false - for cur_ifname in luci.util.imatch(old_ifnames) do - if cur_ifname == old_ifname then - new_ifnames[#new_ifnames+1] = new_ifname - changed = true - else - new_ifnames[#new_ifnames+1] = cur_ifname - end - end - if changed then - m.uci:set("network", section[".name"], "ifname", table.concat(new_ifnames, " ")) - - info[#info+1] = translatef("Interface %q device auto-migrated from %q to %q.", - section[".name"], old_ifname, new_ifname) - end - end) - - if #info > 0 then - m.message = (m.message and m.message .. "\n" or "") .. table.concat(info, "\n") - end -end - -local vlan_already_created - -m.uci:foreach("network", "switch", - function(x) - local sid = x['.name'] - local switch_name = x.name or sid - local has_vlan = nil - local has_learn = nil - local has_vlan4k = nil - local has_jumbo3 = nil - local has_mirror = nil - local min_vid = 0 - local max_vid = 16 - local num_vlans = 16 - - local switch_title - local enable_vlan4k = false - - local topo = topologies[switch_name] - - if not topo then - m.message = translatef("Switch %q has an unknown topology - the VLAN settings might not be accurate.", switch_name) - topo = { - 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)", tagged = false } - } - } - end - - -- Parse some common switch properties from swconfig help output. - local swc = io.popen("swconfig dev %s help 2>/dev/null" % ut.shellquote(switch_name)) - if swc then - - local is_port_attr = false - local is_vlan_attr = false - - while true do - local line = swc:read("*l") - if not line then break end - - if line:match("^%s+%-%-vlan") then - is_vlan_attr = true - - elseif line:match("^%s+%-%-port") then - is_vlan_attr = false - is_port_attr = true - - elseif line:match("cpu @") then - switch_title = line:match("^switch%d: %w+%((.-)%)") - num_vlans = tonumber(line:match("vlans: (%d+)")) or 16 - min_vid = 1 - - elseif line:match(": pvid") or line:match(": tag") or line:match(": vid") then - if is_vlan_attr then has_vlan4k = line:match(": (%w+)") end - - elseif line:match(": enable_vlan4k") then - enable_vlan4k = true - - elseif line:match(": enable_vlan") then - has_vlan = "enable_vlan" - - elseif line:match(": enable_learning") then - has_learn = "enable_learning" - - elseif line:match(": enable_mirror_rx") then - has_mirror = "enable_mirror_rx" - - elseif line:match(": max_length") then - has_jumbo3 = "max_length" - end - end - - swc:close() - end - - - -- Switch properties - s = m:section(NamedSection, x['.name'], "switch", - switch_title and translatef("Switch %q (%s)", switch_name, switch_title) - or translatef("Switch %q", switch_name)) - - s.addremove = false - - if has_vlan then - s:option(Flag, has_vlan, translate("Enable VLAN functionality")) - end - - if has_learn then - x = s:option(Flag, has_learn, translate("Enable learning and aging")) - x.default = x.enabled - end - - if has_jumbo3 then - x = s:option(Flag, has_jumbo3, translate("Enable Jumbo Frame passthrough")) - x.enabled = "3" - x.rmempty = true - end - - -- Does this switch support port mirroring? - if has_mirror then - s:option(Flag, "enable_mirror_rx", translate("Enable mirroring of incoming packets")) - s:option(Flag, "enable_mirror_tx", translate("Enable mirroring of outgoing packets")) - - local sp = s:option(ListValue, "mirror_source_port", translate("Mirror source port")) - local mp = s:option(ListValue, "mirror_monitor_port", translate("Mirror monitor port")) - - sp:depends("enable_mirror_tx", "1") - sp:depends("enable_mirror_rx", "1") - - mp:depends("enable_mirror_tx", "1") - mp:depends("enable_mirror_rx", "1") - - local _, pt - for _, pt in ipairs(topo.ports) do - sp:value(pt.num, pt.label) - mp:value(pt.num, pt.label) - end - end - - -- VLAN table - s = m:section(TypedSection, "switch_vlan", - switch_title and translatef("VLANs on %q (%s)", switch_name, switch_title) - or translatef("VLANs on %q", switch_name)) - - s.template = "cbi/tblsection" - s.addremove = true - s.anonymous = true - - -- Filter by switch - s.filter = function(self, section) - local device = m:get(section, "device") - return (device and device == switch_name) - end - - -- Override cfgsections callback to enforce row ordering by vlan id. - s.cfgsections = function(self) - local osections = TypedSection.cfgsections(self) - local sections = { } - local section - - for _, section in luci.util.spairs( - osections, - function(a, b) - return (tonumber(m:get(osections[a], has_vlan4k or "vlan")) or 9999) - < (tonumber(m:get(osections[b], has_vlan4k or "vlan")) or 9999) - end - ) do - sections[#sections+1] = section - end - - return sections - end - - -- When creating a new vlan, preset it with the highest found vid + 1. - s.create = function(self, section, origin) - -- VLAN has already been created for another switch - if vlan_already_created then - return - - -- VLAN add button was pressed in an empty VLAN section so only - -- accept the create event if our switch is without existing VLANs - elseif origin == "" then - local is_empty_switch = true - - m.uci:foreach("network", "switch_vlan", - function(s) - if s.device == switch_name then - is_empty_switch = false - return false - end - end) - - if not is_empty_switch then - return - end - - -- VLAN was created for another switch - elseif m:get(origin, "device") ~= switch_name then - return - end - - local sid = TypedSection.create(self, section) - - local max_nr = 0 - local max_id = 0 - - m.uci:foreach("network", "switch_vlan", - function(s) - if s.device == switch_name then - local nr = tonumber(s.vlan) - local id = has_vlan4k and tonumber(s[has_vlan4k]) - if nr ~= nil and nr > max_nr then max_nr = nr end - if id ~= nil and id > max_id then max_id = id end - end - end) - - m:set(sid, "device", switch_name) - m:set(sid, "vlan", max_nr + 1) - - if has_vlan4k then - m:set(sid, has_vlan4k, max_id + 1) - end - - vlan_already_created = true - - return sid - end - - - local port_opts = { } - local untagged = { } - - -- Parse current tagging state from the "ports" option. - local portvalue = function(self, section) - local pt - for pt in (m:get(section, "ports") or ""):gmatch("%w+") do - local pc, tu = pt:match("^(%d+)([tu]*)") - if pc == self.option then return (#tu > 0) and tu or "u" end - end - return "" - end - - -- Validate port tagging. Ensure that a port is only untagged once, - -- bail out if not. - local portvalidate = function(self, value, section) - -- ensure that the ports appears untagged only once - if value == "u" then - if not untagged[self.option] then - untagged[self.option] = true - else - return nil, - translatef("%s is untagged in multiple VLANs!", self.title) - end - end - return value - end - - - local vid = s:option(Value, has_vlan4k or "vlan", "VLAN ID") - local mx_vid = has_vlan4k and 4094 or (num_vlans - 1) - - vid.rmempty = false - vid.forcewrite = true - vid.vlan_used = { } - vid.datatype = "and(uinteger,range("..min_vid..","..mx_vid.."))" - - -- Validate user provided VLAN ID, make sure its within the bounds - -- allowed by the switch. - vid.validate = function(self, value, section) - local v = tonumber(value) - local m = has_vlan4k and 4094 or (num_vlans - 1) - if v ~= nil and v >= min_vid and v <= m then - if not self.vlan_used[v] then - self.vlan_used[v] = true - return value - else - return nil, - translatef("Invalid VLAN ID given! Only unique IDs are allowed") - end - else - return nil, - translatef("Invalid VLAN ID given! Only IDs between %d and %d are allowed.", min_vid, m) - end - end - - -- When writing the "vid" or "vlan" option, serialize the port states - -- as well and write them as "ports" option to uci. - vid.write = function(self, section, new_vid) - local o - local p = { } - for _, o in ipairs(port_opts) do - local new_tag = o:formvalue(section) - if new_tag == "t" then - p[#p+1] = o.option .. new_tag - elseif new_tag == "u" then - p[#p+1] = o.option - end - - if o.info and o.info.device then - local old_tag = o:cfgvalue(section) - local old_vid = self:cfgvalue(section) - if old_tag ~= new_tag or old_vid ~= new_vid then - local old_ifname = (old_tag == "u") and o.info.device - or "%s.%s" %{ o.info.device, old_vid } - - local new_ifname = (new_tag == "u") and o.info.device - or "%s.%s" %{ o.info.device, new_vid } - - if old_ifname ~= new_ifname then - update_interfaces(old_ifname, new_ifname) - end - end - end - end - - if enable_vlan4k then - m:set(sid, "enable_vlan4k", "1") - end - - m:set(section, "ports", table.concat(p, " ")) - return Value.write(self, section, new_vid) - end - - -- Fallback to "vlan" option if "vid" option is supported but unset. - vid.cfgvalue = function(self, section) - return m:get(section, has_vlan4k or "vlan") - or m:get(section, "vlan") - end - - local _, pt - for _, pt in ipairs(topo.ports) do - local po = s:option(ListValue, tostring(pt.num), pt.label) - - po:value("", translate("off")) - - if not pt.tagged then - po:value("u", translate("untagged")) - end - - po:value("t", translate("tagged")) - - po.cfgvalue = portvalue - po.validate = portvalidate - po.write = function() end - po.info = pt - - port_opts[#port_opts+1] = po - end - - table.sort(port_opts, function(a, b) return a.option < b.option end) - switches[#switches+1] = switch_name - end -) - --- Switch status template -s = m:section(SimpleSection) -s.template = "admin_network/switch_status" -s.switches = switches - -return m diff --git a/modules/luci-mod-network/luasrc/view/admin_network/switch_status.htm b/modules/luci-mod-network/luasrc/view/admin_network/switch_status.htm deleted file mode 100644 index 6e741b419a..0000000000 --- a/modules/luci-mod-network/luasrc/view/admin_network/switch_status.htm +++ /dev/null @@ -1,62 +0,0 @@ -<script type="text/javascript">//<![CDATA[ - var switches = [ '<%=table.concat(self.switches, "', '")%>' ], - tables = document.querySelectorAll('.cbi-section-table'); - - function add_status_row(table) { - var first_row = table.querySelector('.cbi-section-table-row'); - if (first_row.classList.contains('port-status')) - return first_row; - - var status_row = first_row.parentNode.insertBefore( - E('div', { 'class': first_row.className }), first_row); - - first_row.querySelectorAll('.td').forEach(function(td) { - status_row.appendChild(td.cloneNode(false)); - status_row.lastElementChild.removeAttribute('data-title'); - }); - - status_row.firstElementChild.innerHTML = '<%:Port status:%>'; - status_row.classList.add('port-status') ; - - return status_row; - } - - XHR.poll(-1, '<%=url('admin/network/switch_status')%>/' + switches.join(','), null, - function(x, st) - { - for (var i = 0; i < switches.length; i++) - { - var ports = st[switches[i]]; - var tr = add_status_row(tables[i]); - - if (tr && ports && ports.length) - { - for (var j = 0; j < ports.length; j++) - { - var th = tr.querySelector('[data-name="%d"]'.format(j)); - - if (!th) - continue; - - if (ports[j].link) - { - th.innerHTML = String.format( - '<small><img src="<%=resource%>/icons/port_up.png" />' + - '<br />%d<%:baseT%><br />%s</small>', - ports[j].speed, ports[j].duplex - ? '<%:full-duplex%>' : '<%:half-duplex%>' - ); - } - else - { - th.innerHTML = String.format( - '<small><img src="<%=resource%>/icons/port_down.png" />' + - '<br /><%:no link%></small>' - ); - } - } - } - } - } - ); -//]]></script> |