+ _reorder: function()
+ {
+ var v = this.state.values;
+ var n = this.state.creates;
+ var r = this.state.reorder;
+
+ if ($.isEmptyObject(r))
+ return _luci2.deferrable();
+
+ _luci2.rpc.batch();
+
+ /*
+ gather all created and existing sections, sort them according
+ to their index value and issue an uci order call
+ */
+ for (var c in r)
+ {
+ var o = [ ];
+
+ if (n && n[c])
+ for (var s in n[c])
+ o.push(n[c][s]);
+
+ for (var s in v[c])
+ o.push(v[c][s]);
+
+ if (o.length > 0)
+ {
+ o.sort(function(a, b) {
+ return (a['.index'] - b['.index']);
+ });
+
+ var sids = [ ];
+
+ for (var i = 0; i < o.length; i++)
+ sids.push(o[i]['.name']);
+
+ this._order(c, sids);
+ }
+ }
+
+ this.state.reorder = { };
+ return _luci2.rpc.flush();
+ },
+
+ swap: function(conf, sid1, sid2)
+ {
+ var s1 = this.get(conf, sid1);
+ var s2 = this.get(conf, sid2);
+ var n1 = s1 ? s1['.index'] : NaN;
+ var n2 = s2 ? s2['.index'] : NaN;
+
+ if (isNaN(n1) || isNaN(n2))
+ return false;
+
+ s1['.index'] = n2;
+ s2['.index'] = n1;
+
+ this.state.reorder[conf] = true;
+
+ return true;
+ },
+
+ save: function()
+ {
+ _luci2.rpc.batch();
+
+ var self = this;
+ var snew = [ ];
+
+ if (self.state.creates)
+ for (var c in self.state.creates)
+ for (var s in self.state.creates[c])
+ {
+ var r = {
+ config: c,
+ values: { }
+ };
+
+ for (var k in self.state.creates[c][s])
+ {
+ if (k == '.type')
+ r.type = self.state.creates[c][s][k];
+ else if (k == '.create')
+ r.name = self.state.creates[c][s][k];
+ else if (k.charAt(0) != '.')
+ r.values[k] = self.state.creates[c][s][k];
+ }
+
+ snew.push(self.state.creates[c][s]);
+
+ self._add(r.config, r.type, r.name, r.values);
+ }
+
+ if (self.state.changes)
+ for (var c in self.state.changes)
+ for (var s in self.state.changes[c])
+ self._set(c, s, self.state.changes[c][s]);
+
+ if (self.state.deletes)
+ for (var c in self.state.deletes)
+ for (var s in self.state.deletes[c])
+ {
+ var o = self.state.deletes[c][s];
+ self._delete(c, s, (o === true) ? undefined : o);
+ }
+
+ return _luci2.rpc.flush().then(function(responses) {
+ /*
+ array "snew" holds references to the created uci sections,
+ use it to assign the returned names of the new sections
+ */
+ for (var i = 0; i < snew.length; i++)
+ snew[i]['.name'] = responses[i];
+
+ return self._reorder();
+ });
+ },
+
+ _apply: _luci2.rpc.declare({
+ object: 'uci',
+ method: 'apply',
+ params: [ 'timeout', 'rollback' ]
+ }),
+
+ _confirm: _luci2.rpc.declare({
+ object: 'uci',
+ method: 'confirm'
+ }),
+
+ apply: function(timeout)
+ {
+ var self = this;
+ var date = new Date();
+ var deferred = $.Deferred();
+
+ if (typeof(timeout) != 'number' || timeout < 1)
+ timeout = 10;
+
+ self._apply(timeout, true).then(function(rv) {
+ if (rv != 0)
+ {
+ deferred.rejectWith(self, [ rv ]);
+ return;
+ }
+
+ var try_deadline = date.getTime() + 1000 * timeout;
+ var try_confirm = function()
+ {
+ return self._confirm().then(function(rv) {
+ if (rv != 0)
+ {
+ if (date.getTime() < try_deadline)
+ window.setTimeout(try_confirm, 250);
+ else
+ deferred.rejectWith(self, [ rv ]);
+
+ return;
+ }
+
+ deferred.resolveWith(self, [ rv ]);
+ });
+ };
+
+ window.setTimeout(try_confirm, 1000);
+ });
+
+ return deferred;
+ },
+
+ changes: _luci2.rpc.declare({
+ object: 'uci',
+ method: 'changes',
+ expect: { changes: { } }
+ }),
+
+ readable: function(conf)
+ {
+ return _luci2.session.hasACL('uci', conf, 'read');
+ },
+
+ writable: function(conf)
+ {
+ return _luci2.session.hasACL('uci', conf, 'write');
+ }
+ });
+
+ this.uci = new this.UCIContext();
+
+ this.wireless = {
+ listDeviceNames: _luci2.rpc.declare({
+ object: 'iwinfo',
+ method: 'devices',
+ expect: { 'devices': [ ] },
+ filter: function(data) {
+ data.sort();
+ return data;
+ }
+ }),
+
+ getDeviceStatus: _luci2.rpc.declare({
+ object: 'iwinfo',
+ method: 'info',
+ params: [ 'device' ],
+ expect: { '': { } },
+ filter: function(data, params) {
+ if (!$.isEmptyObject(data))
+ {
+ data['device'] = params['device'];
+ return data;
+ }
+ return undefined;
+ }
+ }),
+
+ getAssocList: _luci2.rpc.declare({
+ object: 'iwinfo',
+ method: 'assoclist',
+ params: [ 'device' ],
+ expect: { results: [ ] },
+ filter: function(data, params) {
+ for (var i = 0; i < data.length; i++)
+ data[i]['device'] = params['device'];
+
+ data.sort(function(a, b) {
+ if (a.bssid < b.bssid)
+ return -1;
+ else if (a.bssid > b.bssid)
+ return 1;
+ else
+ return 0;
+ });
+
+ return data;
+ }
+ }),
+
+ getWirelessStatus: function() {
+ return this.listDeviceNames().then(function(names) {
+ _luci2.rpc.batch();
+
+ for (var i = 0; i < names.length; i++)
+ _luci2.wireless.getDeviceStatus(names[i]);
+
+ return _luci2.rpc.flush();
+ }).then(function(networks) {
+ var rv = { };
+
+ var phy_attrs = [
+ 'country', 'channel', 'frequency', 'frequency_offset',
+ 'txpower', 'txpower_offset', 'hwmodes', 'hardware', 'phy'
+ ];
+
+ var net_attrs = [
+ 'ssid', 'bssid', 'mode', 'quality', 'quality_max',
+ 'signal', 'noise', 'bitrate', 'encryption'
+ ];
+
+ for (var i = 0; i < networks.length; i++)
+ {
+ var phy = rv[networks[i].phy] || (
+ rv[networks[i].phy] = { networks: [ ] }
+ );
+
+ var net = {
+ device: networks[i].device
+ };
+
+ for (var j = 0; j < phy_attrs.length; j++)
+ phy[phy_attrs[j]] = networks[i][phy_attrs[j]];
+
+ for (var j = 0; j < net_attrs.length; j++)
+ net[net_attrs[j]] = networks[i][net_attrs[j]];
+
+ phy.networks.push(net);
+ }
+
+ return rv;
+ });
+ },
+
+ getAssocLists: function()
+ {
+ return this.listDeviceNames().then(function(names) {
+ _luci2.rpc.batch();
+
+ for (var i = 0; i < names.length; i++)
+ _luci2.wireless.getAssocList(names[i]);
+
+ return _luci2.rpc.flush();
+ }).then(function(assoclists) {
+ var rv = [ ];
+
+ for (var i = 0; i < assoclists.length; i++)
+ for (var j = 0; j < assoclists[i].length; j++)
+ rv.push(assoclists[i][j]);
+
+ return rv;
+ });
+ },
+
+ formatEncryption: function(enc)
+ {
+ var format_list = function(l, s)
+ {
+ var rv = [ ];
+ for (var i = 0; i < l.length; i++)
+ rv.push(l[i].toUpperCase());
+ return rv.join(s ? s : ', ');
+ }
+
+ if (!enc || !enc.enabled)
+ return _luci2.tr('None');
+
+ if (enc.wep)
+ {
+ if (enc.wep.length == 2)
+ return _luci2.tr('WEP Open/Shared') + ' (%s)'.format(format_list(enc.ciphers, ', '));
+ else if (enc.wep[0] == 'shared')
+ return _luci2.tr('WEP Shared Auth') + ' (%s)'.format(format_list(enc.ciphers, ', '));
+ else
+ return _luci2.tr('WEP Open System') + ' (%s)'.format(format_list(enc.ciphers, ', '));
+ }
+ else if (enc.wpa)
+ {
+ if (enc.wpa.length == 2)
+ return _luci2.tr('mixed WPA/WPA2') + ' %s (%s)'.format(
+ format_list(enc.authentication, '/'),
+ format_list(enc.ciphers, ', ')
+ );
+ else if (enc.wpa[0] == 2)
+ return 'WPA2 %s (%s)'.format(
+ format_list(enc.authentication, '/'),
+ format_list(enc.ciphers, ', ')
+ );
+ else
+ return 'WPA %s (%s)'.format(
+ format_list(enc.authentication, '/'),
+ format_list(enc.ciphers, ', ')
+ );
+ }
+
+ return _luci2.tr('Unknown');
+ }
+ };
+
+ this.firewall = {
+ getZoneColor: function(zone)
+ {
+ if ($.isPlainObject(zone))
+ zone = zone.name;
+
+ if (zone == 'lan')
+ return '#90f090';
+ else if (zone == 'wan')
+ return '#f09090';
+
+ for (var i = 0, hash = 0;
+ i < zone.length;
+ hash = zone.charCodeAt(i++) + ((hash << 5) - hash));
+
+ for (var i = 0, color = '#';
+ i < 3;
+ color += ('00' + ((hash >> i++ * 8) & 0xFF).tostring(16)).slice(-2));
+
+ return color;
+ },
+
+ findZoneByNetwork: function(network)
+ {
+ var self = this;
+ var zone = undefined;
+
+ return _luci2.uci.sections('firewall', 'zone', function(z) {
+ if (!z.name || !z.network)
+ return;
+
+ if (!$.isArray(z.network))
+ z.network = z.network.split(/\s+/);
+
+ for (var i = 0; i < z.network.length; i++)
+ {
+ if (z.network[i] == network)
+ {
+ zone = z;
+ break;
+ }
+ }
+ }).then(function() {
+ if (zone)
+ zone.color = self.getZoneColor(zone);
+
+ return zone;
+ });
+ }
+ };
+
+ 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']);