From: Jo-Philipp Wich Date: Fri, 31 Jan 2014 21:33:04 +0000 (+0000) Subject: luci2: implement LuCI2.NetworkModel, LuCI2.NetworkModel.Interface, LuCI2.NetworkModel... X-Git-Url: http://git.archive.openwrt.org/?p=project%2Fluci2%2Fui.git;a=commitdiff_plain;h=e2e4f3caad043cbf1a7b3a53d923cd804a83d276 luci2: implement LuCI2.NetworkModel, LuCI2.NetworkModel.Interface, LuCI2.NetworkModel.Device and LuCI2.NetworkModel.Protocol classes --- diff --git a/luci2/htdocs/luci2/luci2.js b/luci2/htdocs/luci2/luci2.js index 69832a8..bbfb6d3 100644 --- a/luci2/htdocs/luci2/luci2.js +++ b/luci2/htdocs/luci2/luci2.js @@ -1354,6 +1354,1419 @@ function LuCI2() } }; + this.NetworkModel = { + _device_blacklist: [ + /^gre[0-9]+$/, + /^gretap[0-9]+$/, + /^ifb[0-9]+$/, + /^ip6tnl[0-9]+$/, + /^sit[0-9]+$/, + /^wlan[0-9]+\.sta[0-9]+$/ + ], + + _cache_functions: [ + 'protolist', 0, _luci2.rpc.declare({ + object: 'network', + method: 'get_proto_handlers', + expect: { '': { } } + }), + 'ifstate', 1, _luci2.rpc.declare({ + object: 'network.interface', + method: 'dump', + expect: { 'interface': [ ] } + }), + 'devstate', 2, _luci2.rpc.declare({ + object: 'network.device', + method: 'status', + expect: { '': { } } + }), + 'wifistate', 0, _luci2.rpc.declare({ + object: 'network.wireless', + method: 'status', + expect: { '': { } } + }), + 'bwstate', 2, _luci2.rpc.declare({ + object: 'luci2.network.bwmon', + method: 'statistics', + expect: { 'statistics': { } } + }), + 'devlist', 2, _luci2.rpc.declare({ + object: 'luci2.network', + method: 'device_list', + expect: { 'devices': [ ] } + }), + 'swlist', 0, _luci2.rpc.declare({ + object: 'luci2.network', + method: 'switch_list', + expect: { 'switches': [ ] } + }) + ], + + _fetch_protocol: function(proto) + { + var url = _luci2.globals.resource + '/proto/' + proto + '.js'; + var self = _luci2.NetworkModel; + + var def = $.Deferred(); + + $.ajax(url, { + method: 'GET', + cache: true, + dataType: 'text' + }).then(function(data) { + try { + var protoConstructorSource = ( + '(function(L, $) { ' + + 'return %s' + + '})(_luci2, $);\n\n' + + '//@ sourceURL=%s' + ).format(data, url); + + var protoClass = eval(protoConstructorSource); + + self._protos[proto] = new protoClass(); + } + catch(e) { + alert('Unable to instantiate proto "%s": %s'.format(url, e)); + }; + + def.resolve(); + }).fail(function() { + def.resolve(); + }); + + return def; + }, + + _fetch_protocols: function() + { + var self = _luci2.NetworkModel; + var deferreds = [ ]; + + for (var proto in self._cache.protolist) + deferreds.push(self._fetch_protocol(proto)); + + return $.when.apply($, deferreds); + }, + + _fetch_swstate: _luci2.rpc.declare({ + object: 'luci2.network', + method: 'switch_info', + params: [ 'switch' ], + expect: { 'info': { } } + }), + + _fetch_swstate_cb: function(responses) { + var self = _luci2.NetworkModel; + var swlist = self._cache.swlist; + var swstate = self._cache.swstate = { }; + + for (var i = 0; i < responses.length; i++) + swstate[swlist[i]] = responses[i]; + }, + + _fetch_cache_cb: function(level) + { + var self = _luci2.NetworkModel; + var name = '_fetch_cache_cb_' + level; + + return self[name] || ( + self[name] = function(responses) + { + for (var i = 0; i < self._cache_functions.length; i += 3) + if (!level || self._cache_functions[i + 1] == level) + self._cache[self._cache_functions[i]] = responses.shift(); + + if (!level) + { + _luci2.rpc.batch(); + + for (var i = 0; i < self._cache.swlist.length; i++) + self._fetch_swstate(self._cache.swlist[i]); + + return _luci2.rpc.flush().then(self._fetch_swstate_cb); + } + + return _luci2.deferrable(); + } + ); + }, + + _fetch_cache: function(level) + { + var self = _luci2.NetworkModel; + + return _luci2.uci.load(['network', 'wireless']).then(function() { + _luci2.rpc.batch(); + + for (var i = 0; i < self._cache_functions.length; i += 3) + if (!level || self._cache_functions[i + 1] == level) + self._cache_functions[i + 2](); + + return _luci2.rpc.flush().then(self._fetch_cache_cb(level || 0)); + }); + }, + + _get: function(pkg, sid, key) + { + return _luci2.uci.get(pkg, sid, key); + }, + + _set: function(pkg, sid, key, val) + { + return _luci2.uci.set(pkg, sid, key, val); + }, + + _is_blacklisted: function(dev) + { + for (var i = 0; i < this._device_blacklist.length; i++) + if (dev.match(this._device_blacklist[i])) + return true; + + return false; + }, + + _sort_devices: function(a, b) + { + if (a.options.kind < b.options.kind) + return -1; + else if (a.options.kind > b.options.kind) + return 1; + + if (a.options.name < b.options.name) + return -1; + else if (a.options.name > b.options.name) + return 1; + + return 0; + }, + + _get_dev: function(ifname) + { + var alias = (ifname.charAt(0) == '@'); + return this._devs[ifname] || ( + this._devs[ifname] = { + ifname: ifname, + kind: alias ? 'alias' : 'ethernet', + type: alias ? 0 : 1, + up: false, + changed: { } + } + ); + }, + + _get_iface: function(name) + { + return this._ifaces[name] || ( + this._ifaces[name] = { + name: name, + proto: this._protos.none, + changed: { } + } + ); + }, + + _parse_devices: function() + { + var self = _luci2.NetworkModel; + var wificount = { }; + + for (var ifname in self._cache.devstate) + { + if (self._is_blacklisted(ifname)) + continue; + + var dev = self._cache.devstate[ifname]; + var entry = self._get_dev(ifname); + + entry.up = dev.up; + + switch (dev.type) + { + case 'IP tunnel': + entry.kind = 'tunnel'; + break; + + case 'Bridge': + entry.kind = 'bridge'; + //entry.ports = dev['bridge-members'].sort(); + break; + } + } + + for (var i = 0; i < self._cache.devlist.length; i++) + { + var dev = self._cache.devlist[i]; + + if (self._is_blacklisted(dev.device)) + continue; + + var entry = self._get_dev(dev.device); + + entry.up = dev.is_up; + entry.type = dev.type; + + switch (dev.type) + { + case 1: /* Ethernet */ + if (dev.is_bridge) + entry.kind = 'bridge'; + else if (dev.is_tuntap) + entry.kind = 'tunnel'; + else if (dev.is_wireless) + entry.kind = 'wifi'; + break; + + case 512: /* PPP */ + case 768: /* IP-IP Tunnel */ + case 769: /* IP6-IP6 Tunnel */ + case 776: /* IPv6-in-IPv4 */ + case 778: /* GRE over IP */ + entry.kind = 'tunnel'; + break; + } + } + + var net = _luci2.uci.sections('network'); + for (var i = 0; i < net.length; i++) + { + var s = net[i]; + var sid = s['.name']; + + if (s['.type'] == 'device' && s.name) + { + var entry = self._get_dev(s.name); + + switch (s.type) + { + case 'macvlan': + case 'tunnel': + entry.kind = 'tunnel'; + break; + } + + entry.sid = sid; + } + else if (s['.type'] == 'interface' && !s['.anonymous'] && s.ifname) + { + var ifnames = _luci2.toArray(s.ifname); + + for (var j = 0; j < ifnames.length; j++) + self._get_dev(ifnames[j]); + + if (s['.name'] != 'loopback') + { + var entry = self._get_dev('@%s'.format(s['.name'])); + + entry.type = 0; + entry.kind = 'alias'; + entry.sid = sid; + } + } + else if (s['.type'] == 'switch_vlan' && s.device) + { + var sw = self._cache.swstate[s.device]; + var vid = parseInt(s.vid || s.vlan); + var ports = _luci2.toArray(s.ports); + + if (!sw || !ports.length || isNaN(vid)) + continue; + + var ifname = undefined; + + for (var j = 0; j < ports.length; j++) + { + var port = parseInt(ports[j]); + var tag = (ports[j].replace(/[^tu]/g, '') == 't'); + + if (port == sw.cpu_port) + { + // XXX: need a way to map switch to netdev + if (tag) + ifname = 'eth0.%d'.format(vid); + else + ifname = 'eth0'; + + break; + } + } + + if (!ifname) + continue; + + var entry = self._get_dev(ifname); + + entry.kind = 'vlan'; + entry.sid = sid; + entry.vsw = sw; + entry.vid = vid; + } + } + + var wifi = _luci2.uci.sections('wireless'); + for (var i = 0; i < wifi.length; i++) + { + var s = wifi[i]; + var sid = s['.name']; + + if (s['.type'] == 'wifi-iface' && s.device) + { + var r = parseInt(s.device.replace(/^[^0-9]+/, '')); + var n = wificount[s.device] = (wificount[s.device] || 0) + 1; + var id = 'radio%d.network%d'.format(r, n); + var ifname = id; + + if (self._cache.wifistate[s.device]) + { + var ifcs = self._cache.wifistate[s.device].interfaces; + for (var ifc in ifcs) + { + if (ifcs[ifc].section == sid) + { + ifname = ifcs[ifc].ifname; + break; + } + } + } + + var entry = self._get_dev(ifname); + + entry.kind = 'wifi'; + entry.sid = sid; + entry.wid = id; + entry.wdev = s.device; + entry.wmode = s.mode; + entry.wssid = s.ssid; + entry.wbssid = s.bssid; + } + } + + for (var i = 0; i < net.length; i++) + { + var s = net[i]; + var sid = s['.name']; + + if (s['.type'] == 'interface' && !s['.anonymous'] && s.type == 'bridge') + { + var ifnames = _luci2.toArray(s.ifname); + + for (var ifname in self._devs) + { + var dev = self._devs[ifname]; + + if (dev.kind != 'wifi') + continue; + + var wnets = _luci2.toArray(_luci2.uci.get('wireless', dev.sid, 'network')); + if ($.inArray(sid, wnets) > -1) + ifnames.push(ifname); + } + + entry = self._get_dev('br-%s'.format(s['.name'])); + entry.type = 1; + entry.kind = 'bridge'; + entry.sid = sid; + entry.ports = ifnames.sort(); + } + } + }, + + _parse_interfaces: function() + { + var self = _luci2.NetworkModel; + var net = _luci2.uci.sections('network'); + + for (var i = 0; i < net.length; i++) + { + var s = net[i]; + var sid = s['.name']; + + if (s['.type'] == 'interface' && !s['.anonymous'] && s.proto) + { + var entry = self._get_iface(s['.name']); + var proto = self._protos[s.proto] || self._protos.none; + + var l3dev = undefined; + var l2dev = undefined; + + var ifnames = _luci2.toArray(s.ifname); + + for (var ifname in self._devs) + { + var dev = self._devs[ifname]; + + if (dev.kind != 'wifi') + continue; + + var wnets = _luci2.toArray(_luci2.uci.get('wireless', dev.sid, 'network')); + if ($.inArray(entry.name, wnets) > -1) + ifnames.push(ifname); + } + + if (proto.virtual) + l3dev = '%s-%s'.format(s.proto, entry.name); + else if (s.type == 'bridge') + l3dev = 'br-%s'.format(entry.name); + else + l3dev = ifnames[0]; + + if (!proto.virtual && s.type == 'bridge') + l2dev = 'br-%s'.format(entry.name); + else if (!proto.virtual) + l2dev = ifnames[0]; + + entry.proto = proto; + entry.sid = sid; + entry.l3dev = l3dev; + entry.l2dev = l2dev; + } + } + + for (var i = 0; i < self._cache.ifstate.length; i++) + { + var iface = self._cache.ifstate[i]; + var entry = self._get_iface(iface['interface']); + var proto = self._protos[iface.proto] || self._protos.none; + + /* this is a virtual interface, either deleted from config but + not applied yet or set up from external tools (6rd) */ + if (!entry.sid) + { + entry.proto = proto; + entry.l2dev = iface.device; + entry.l3dev = iface.l3_device; + } + } + }, + + init: function() + { + var self = this; + + if (self._cache) + return _luci2.deferrable(); + + self._cache = { }; + self._devs = { }; + self._ifaces = { }; + self._protos = { }; + + return self._fetch_cache() + .then(self._fetch_protocols) + .then(self._parse_devices) + .then(self._parse_interfaces); + }, + + update: function() + { + delete this._cache; + return this.init(); + }, + + refreshInterfaceStatus: function() + { + return this._fetch_cache(1).then(this._parse_interfaces); + }, + + refreshDeviceStatus: function() + { + return this._fetch_cache(2).then(this._parse_devices); + }, + + refreshStatus: function() + { + return this._fetch_cache(1) + .then(this._fetch_cache(2)) + .then(this._parse_devices) + .then(this._parse_interfaces); + }, + + getDevices: function() + { + var devs = [ ]; + + for (var ifname in this._devs) + if (ifname != 'lo') + devs.push(new _luci2.NetworkModel.Device(this._devs[ifname])); + + return devs.sort(this._sort_devices); + }, + + getDeviceByInterface: function(iface) + { + if (iface instanceof _luci2.NetworkModel.Interface) + iface = iface.name(); + + if (this._ifaces[iface]) + return this.getDevice(this._ifaces[iface].l3dev) || + this.getDevice(this._ifaces[iface].l2dev); + + return undefined; + }, + + getDevice: function(ifname) + { + if (this._devs[ifname]) + return new _luci2.NetworkModel.Device(this._devs[ifname]); + + return undefined; + }, + + createDevice: function(name) + { + return new _luci2.NetworkModel.Device(this._get_dev(name)); + }, + + getInterfaces: function() + { + var ifaces = [ ]; + + for (var name in this._ifaces) + if (name != 'loopback') + ifaces.push(this.getInterface(name)); + + ifaces.sort(function(a, b) { + if (a.name() < b.name()) + return -1; + else if (a.name() > b.name()) + return 1; + else + return 0; + }); + + return ifaces; + }, + + getInterfacesByDevice: function(dev) + { + var ifaces = [ ]; + + if (dev instanceof _luci2.NetworkModel.Device) + dev = dev.name(); + + for (var name in this._ifaces) + { + var iface = this._ifaces[name]; + if (iface.l2dev == dev || iface.l3dev == dev) + ifaces.push(this.getInterface(name)); + } + + ifaces.sort(function(a, b) { + if (a.name() < b.name()) + return -1; + else if (a.name() > b.name()) + return 1; + else + return 0; + }); + + return ifaces; + }, + + getInterface: function(iface) + { + if (this._ifaces[iface]) + return new _luci2.NetworkModel.Interface(this._ifaces[iface]); + + return undefined; + }, + + getProtocols: function() + { + var rv = [ ]; + + for (var proto in this._protos) + { + var pr = this._protos[proto]; + + rv.push({ + name: proto, + description: pr.description, + virtual: pr.virtual, + tunnel: pr.tunnel + }); + } + + return rv.sort(function(a, b) { + if (a.name < b.name) + return -1; + else if (a.name > b.name) + return 1; + else + return 0; + }); + }, + + _find_wan: function(ipaddr) + { + for (var i = 0; i < this._cache.ifstate.length; i++) + { + var ifstate = this._cache.ifstate[i]; + + if (!ifstate.route) + continue; + + for (var j = 0; j < ifstate.route.length; j++) + if (ifstate.route[j].mask == 0 && + ifstate.route[j].target == ipaddr && + typeof(ifstate.route[j].table) == 'undefined') + { + return this.getInterface(ifstate['interface']); + } + } + + return undefined; + }, + + findWAN: function() + { + return this._find_wan('0.0.0.0'); + }, + + findWAN6: function() + { + return this._find_wan('::'); + }, + + resolveAlias: function(ifname) + { + if (ifname instanceof _luci2.NetworkModel.Device) + ifname = ifname.name(); + + var dev = this._devs[ifname]; + var seen = { }; + + while (dev && dev.kind == 'alias') + { + // loop + if (seen[dev.ifname]) + return undefined; + + var ifc = this._ifaces[dev.sid]; + + seen[dev.ifname] = true; + dev = ifc ? this._devs[ifc.l3dev] : undefined; + } + + return dev ? this.getDevice(dev.ifname) : undefined; + } + }; + + this.NetworkModel.Device = Class.extend({ + _wifi_modes: { + ap: _luci2.tr('Master'), + sta: _luci2.tr('Client'), + adhoc: _luci2.tr('Ad-Hoc'), + monitor: _luci2.tr('Monitor'), + wds: _luci2.tr('Static WDS') + }, + + _status: function(key) + { + var s = _luci2.NetworkModel._cache.devstate[this.options.ifname]; + + if (s) + return key ? s[key] : s; + + return undefined; + }, + + get: function(key) + { + var sid = this.options.sid; + var pkg = (this.options.kind == 'wifi') ? 'wireless' : 'network'; + return _luci2.NetworkModel._get(pkg, sid, key); + }, + + set: function(key, val) + { + var sid = this.options.sid; + var pkg = (this.options.kind == 'wifi') ? 'wireless' : 'network'; + return _luci2.NetworkModel._set(pkg, sid, key, val); + }, + + init: function() + { + if (typeof(this.options.type) == 'undefined') + this.options.type = 1; + + if (typeof(this.options.kind) == 'undefined') + this.options.kind = 'ethernet'; + + if (typeof(this.options.networks) == 'undefined') + this.options.networks = [ ]; + }, + + name: function() + { + return this.options.ifname; + }, + + description: function() + { + switch (this.options.kind) + { + case 'alias': + return _luci2.tr('Alias for network "%s"').format(this.options.ifname.substring(1)); + + case 'bridge': + return _luci2.tr('Network bridge'); + + case 'ethernet': + return _luci2.tr('Network device'); + + case 'tunnel': + switch (this.options.type) + { + case 1: /* tuntap */ + return _luci2.tr('TAP device'); + + case 512: /* PPP */ + return _luci2.tr('PPP tunnel'); + + case 768: /* IP-IP Tunnel */ + return _luci2.tr('IP-in-IP tunnel'); + + case 769: /* IP6-IP6 Tunnel */ + return _luci2.tr('IPv6-in-IPv6 tunnel'); + + case 776: /* IPv6-in-IPv4 */ + return _luci2.tr('IPv6-over-IPv4 tunnel'); + break; + + case 778: /* GRE over IP */ + return _luci2.tr('GRE-over-IP tunnel'); + + default: + return _luci2.tr('Tunnel device'); + } + + case 'vlan': + return _luci2.tr('VLAN %d on %s').format(this.options.vid, this.options.vsw.model); + + case 'wifi': + var o = this.options; + return _luci2.trc('(Wifi-Mode) "(SSID)" on (radioX)', '%s "%h" on %s').format( + o.wmode ? this._wifi_modes[o.wmode] : _luci2.tr('Unknown mode'), + o.wssid || '?', o.wdev + ); + } + + return _luci2.tr('Unknown device'); + }, + + icon: function(up) + { + var kind = this.options.kind; + + if (kind == 'alias') + kind = 'ethernet'; + + if (typeof(up) == 'undefined') + up = this.isUp(); + + return _luci2.globals.resource + '/icons/%s%s.png'.format(kind, up ? '' : '_disabled'); + }, + + isUp: function() + { + var l = _luci2.NetworkModel._cache.devlist; + + for (var i = 0; i < l.length; i++) + if (l[i].device == this.options.ifname) + return (l[i].is_up === true); + + return false; + }, + + isAlias: function() + { + return (this.options.kind == 'alias'); + }, + + isBridge: function() + { + return (this.options.kind == 'bridge'); + }, + + isBridgeable: function() + { + return (this.options.type == 1 && this.options.kind != 'bridge'); + }, + + isWireless: function() + { + return (this.options.kind == 'wifi'); + }, + + isInNetwork: function(net) + { + if (!(net instanceof _luci2.NetworkModel.Interface)) + net = _luci2.NetworkModel.getInterface(net); + + if (net) + { + if (net.options.l3dev == this.options.ifname || + net.options.l2dev == this.options.ifname) + return true; + + var dev = _luci2.NetworkModel._devs[net.options.l2dev]; + if (dev && dev.kind == 'bridge' && dev.ports) + return ($.inArray(this.options.ifname, dev.ports) > -1); + } + + return false; + }, + + getMTU: function() + { + var dev = _luci2.NetworkModel._cache.devstate[this.options.ifname]; + if (dev && !isNaN(dev.mtu)) + return dev.mtu; + + return undefined; + }, + + getMACAddress: function() + { + if (this.options.type != 1) + return undefined; + + var dev = _luci2.NetworkModel._cache.devstate[this.options.ifname]; + if (dev && dev.macaddr) + return dev.macaddr.toUpperCase(); + + return undefined; + }, + + getInterfaces: function() + { + return _luci2.NetworkModel.getInterfacesByDevice(this.options.name); + }, + + getStatistics: function() + { + var s = this._status('statistics') || { }; + return { + rx_bytes: (s.rx_bytes || 0), + tx_bytes: (s.tx_bytes || 0), + rx_packets: (s.rx_packets || 0), + tx_packets: (s.tx_packets || 0) + }; + }, + + getTrafficHistory: function() + { + var def = new Array(120); + + for (var i = 0; i < 120; i++) + def[i] = 0; + + var h = _luci2.NetworkModel._cache.bwstate[this.options.ifname] || { }; + return { + rx_bytes: (h.rx_bytes || def), + tx_bytes: (h.tx_bytes || def), + rx_packets: (h.rx_packets || def), + tx_packets: (h.tx_packets || def) + }; + }, + + removeFromInterface: function(iface) + { + if (!(iface instanceof _luci2.NetworkModel.Interface)) + iface = _luci2.NetworkModel.getInterface(iface); + + if (!iface) + return; + + var ifnames = _luci2.toArray(iface.get('ifname')); + if ($.inArray(this.options.ifname, ifnames) > -1) + iface.set('ifname', _luci2.filterArray(ifnames, this.options.ifname)); + + if (this.options.kind != 'wifi') + return; + + var networks = _luci2.toArray(this.get('network')); + if ($.inArray(iface.name(), networks) > -1) + this.set('network', _luci2.filterArray(networks, iface.name())); + }, + + attachToInterface: function(iface) + { + if (!(iface instanceof _luci2.NetworkModel.Interface)) + iface = _luci2.NetworkModel.getInterface(iface); + + if (!iface) + return; + + if (this.options.kind != 'wifi') + { + var ifnames = _luci2.toArray(iface.get('ifname')); + if ($.inArray(this.options.ifname, ifnames) < 0) + { + ifnames.push(this.options.ifname); + iface.set('ifname', (ifnames.length > 1) ? ifnames : ifnames[0]); + } + } + else + { + var networks = _luci2.toArray(this.get('network')); + if ($.inArray(iface.name(), networks) < 0) + { + networks.push(iface.name()); + this.set('network', (networks.length > 1) ? networks : networks[0]); + } + } + } + }); + + this.NetworkModel.Interface = Class.extend({ + _status: function(key) + { + var s = _luci2.NetworkModel._cache.ifstate; + + for (var i = 0; i < s.length; i++) + if (s[i]['interface'] == this.options.name) + return key ? s[i][key] : s[i]; + + return undefined; + }, + + get: function(key) + { + return _luci2.NetworkModel._get('network', this.options.name, key); + }, + + set: function(key, val) + { + return _luci2.NetworkModel._set('network', this.options.name, key, val); + }, + + name: function() + { + return this.options.name; + }, + + protocol: function() + { + return (this.get('proto') || 'none'); + }, + + isUp: function() + { + return (this._status('up') === true); + }, + + isVirtual: function() + { + return (typeof(this.options.sid) != 'string'); + }, + + getProtocol: function() + { + var prname = this.get('proto') || 'none'; + return _luci2.NetworkModel._protos[prname] || _luci2.NetworkModel._protos.none; + }, + + getUptime: function() + { + var uptime = this._status('uptime'); + return isNaN(uptime) ? 0 : uptime; + }, + + getDevice: function(resolveAlias) + { + if (this.options.l3dev) + return _luci2.NetworkModel.getDevice(this.options.l3dev); + + return undefined; + }, + + getPhysdev: function() + { + if (this.options.l2dev) + return _luci2.NetworkModel.getDevice(this.options.l2dev); + + return undefined; + }, + + getSubdevices: function() + { + var rv = [ ]; + var dev = this.options.l2dev ? + _luci2.NetworkModel._devs[this.options.l2dev] : undefined; + + if (dev && dev.kind == 'bridge' && dev.ports && dev.ports.length) + for (var i = 0; i < dev.ports.length; i++) + rv.push(_luci2.NetworkModel.getDevice(dev.ports[i])); + + return rv; + }, + + getIPv4Addrs: function(mask) + { + var rv = [ ]; + var addrs = this._status('ipv4-address'); + + if (addrs) + for (var i = 0; i < addrs.length; i++) + if (!mask) + rv.push(addrs[i].address); + else + rv.push('%s/%d'.format(addrs[i].address, addrs[i].mask)); + + return rv; + }, + + getIPv6Addrs: function(mask) + { + var rv = [ ]; + var addrs; + + addrs = this._status('ipv6-address'); + + if (addrs) + for (var i = 0; i < addrs.length; i++) + if (!mask) + rv.push(addrs[i].address); + else + rv.push('%s/%d'.format(addrs[i].address, addrs[i].mask)); + + addrs = this._status('ipv6-prefix-assignment'); + + if (addrs) + for (var i = 0; i < addrs.length; i++) + if (!mask) + rv.push('%s1'.format(addrs[i].address)); + else + rv.push('%s1/%d'.format(addrs[i].address, addrs[i].mask)); + + return rv; + }, + + getDNSAddrs: function() + { + var rv = [ ]; + var addrs = this._status('dns-server'); + + if (addrs) + for (var i = 0; i < addrs.length; i++) + rv.push(addrs[i]); + + return rv; + }, + + getIPv4DNS: function() + { + var rv = [ ]; + var dns = this._status('dns-server'); + + if (dns) + for (var i = 0; i < dns.length; i++) + if (dns[i].indexOf(':') == -1) + rv.push(dns[i]); + + return rv; + }, + + getIPv6DNS: function() + { + var rv = [ ]; + var dns = this._status('dns-server'); + + if (dns) + for (var i = 0; i < dns.length; i++) + if (dns[i].indexOf(':') > -1) + rv.push(dns[i]); + + return rv; + }, + + getIPv4Gateway: function() + { + var rt = this._status('route'); + + if (rt) + for (var i = 0; i < rt.length; i++) + if (rt[i].target == '0.0.0.0' && rt[i].mask == 0) + return rt[i].nexthop; + + return undefined; + }, + + getIPv6Gateway: function() + { + var rt = this._status('route'); + + if (rt) + for (var i = 0; i < rt.length; i++) + if (rt[i].target == '::' && rt[i].mask == 0) + return rt[i].nexthop; + + return undefined; + }, + + getStatistics: function() + { + var dev = this.getDevice() || new _luci2.NetworkModel.Device({}); + return dev.getStatistics(); + }, + + getTrafficHistory: function() + { + var dev = this.getDevice() || new _luci2.NetworkModel.Device({}); + return dev.getTrafficHistory(); + }, + + setDevices: function(devs) + { + var dev = this.getPhysdev(); + var old_devs = [ ]; + var changed = false; + + if (dev && dev.isBridge()) + old_devs = this.getSubdevices(); + else if (dev) + old_devs = [ dev ]; + + if (old_devs.length != devs.length) + changed = true; + else + for (var i = 0; i < old_devs.length; i++) + { + var dev = devs[i]; + + if (dev instanceof _luci2.NetworkModel.Device) + dev = dev.name(); + + if (!dev || old_devs[i].name() != dev) + { + changed = true; + break; + } + } + + if (changed) + { + for (var i = 0; i < old_devs.length; i++) + old_devs[i].removeFromInterface(this); + + for (var i = 0; i < devs.length; i++) + { + var dev = devs[i]; + + if (!(dev instanceof _luci2.NetworkModel.Device)) + dev = _luci2.NetworkModel.getDevice(dev); + + if (dev) + dev.attachToInterface(this); + } + } + }, + + changeProtocol: function(proto) + { + var pr = _luci2.NetworkModel._protos[proto]; + + if (!pr) + return; + + for (var opt in (this.get() || { })) + { + switch (opt) + { + case 'type': + case 'ifname': + case 'macaddr': + if (pr.virtual) + this.set(opt, undefined); + break; + + case 'auto': + case 'mtu': + break; + + case 'proto': + this.set(opt, pr.protocol); + break; + + default: + this.set(opt, undefined); + break; + } + } + }, + + createForm: function(mapwidget) + { + var self = this; + var proto = self.getProtocol(); + var device = self.getDevice(); + + if (!mapwidget) + mapwidget = _luci2.cbi.Map; + + var map = new mapwidget('network', { + caption: _luci2.tr('Configure "%s"').format(self.name()) + }); + + var section = map.section(_luci2.cbi.SingleSection, self.name(), { + anonymous: true + }); + + section.tab({ + id: 'general', + caption: _luci2.tr('General Settings') + }); + + section.tab({ + id: 'advanced', + caption: _luci2.tr('Advanced Settings') + }); + + section.tab({ + id: 'ipv6', + caption: _luci2.tr('IPv6') + }); + + section.tab({ + id: 'physical', + caption: _luci2.tr('Physical Settings') + }); + + + section.taboption('general', _luci2.cbi.CheckboxValue, 'auto', { + caption: _luci2.tr('Start on boot'), + optional: true, + initial: true + }); + + var pr = section.taboption('general', _luci2.cbi.ListValue, 'proto', { + caption: _luci2.tr('Protocol') + }); + + pr.ucivalue = function(sid) { + return self.get('proto') || 'none'; + }; + + var ok = section.taboption('general', _luci2.cbi.ButtonValue, '_confirm', { + caption: _luci2.tr('Really switch?'), + description: _luci2.tr('Changing the protocol will clear all configuration for this interface!'), + text: _luci2.tr('Change protocol') + }); + + ok.on('click', function(ev) { + self.changeProtocol(pr.formvalue(ev.data.sid)); + self.createForm(mapwidget).show(); + }); + + var protos = _luci2.NetworkModel.getProtocols(); + + for (var i = 0; i < protos.length; i++) + pr.value(protos[i].name, protos[i].description); + + proto.populateForm(section, self); + + if (!proto.virtual) + { + var br = section.taboption('physical', _luci2.cbi.CheckboxValue, 'type', { + caption: _luci2.tr('Network bridge'), + description: _luci2.tr('Merges multiple devices into one logical bridge'), + optional: true, + enabled: 'bridge', + disabled: '', + initial: '' + }); + + section.taboption('physical', _luci2.cbi.DeviceList, '__iface_multi', { + caption: _luci2.tr('Devices'), + multiple: true, + bridges: false + }).depends('type', true); + + section.taboption('physical', _luci2.cbi.DeviceList, '__iface_single', { + caption: _luci2.tr('Device'), + multiple: false, + bridges: true + }).depends('type', false); + + var mac = section.taboption('physical', _luci2.cbi.InputValue, 'macaddr', { + caption: _luci2.tr('Override MAC'), + optional: true, + placeholder: device ? device.getMACAddress() : undefined, + datatype: 'macaddr' + }) + + mac.ucivalue = function(sid) + { + if (device) + return device.get('macaddr'); + + return this.callSuper('ucivalue', sid); + }; + + mac.save = function(sid) + { + if (!this.changed(sid)) + return false; + + if (device) + device.set('macaddr', this.formvalue(sid)); + else + this.callSuper('set', sid); + + return true; + }; + } + + section.taboption('physical', _luci2.cbi.InputValue, 'mtu', { + caption: _luci2.tr('Override MTU'), + optional: true, + placeholder: device ? device.getMTU() : undefined, + datatype: 'range(1, 9000)' + }); + + section.taboption('physical', _luci2.cbi.InputValue, 'metric', { + caption: _luci2.tr('Override Metric'), + optional: true, + placeholder: 0, + datatype: 'uinteger' + }); + + for (var field in section.fields) + { + switch (field) + { + case 'proto': + break; + + case '_confirm': + for (var i = 0; i < protos.length; i++) + if (protos[i].name != (this.get('proto') || 'none')) + section.fields[field].depends('proto', protos[i].name); + break; + + default: + section.fields[field].depends('proto', this.get('proto') || 'none', true); + break; + } + } + + return map; + } + }); + + this.NetworkModel.Protocol = this.NetworkModel.Interface.extend({ + description: '__unknown__', + tunnel: false, + virtual: false, + + populateForm: function(section, iface) + { + + } + }); + this.system = { getSystemInfo: _luci2.rpc.declare({ object: 'system',