Merge pull request #1735 from sumpfralle/olsr-jsoninfo-parser-handle-empty-result
[oweals/luci.git] / modules / luci-base / htdocs / luci-static / resources / network.js
1 'use strict';
2 'require uci';
3 'require rpc';
4 'require validation';
5
6 var proto_errors = {
7         CONNECT_FAILED:                 _('Connection attempt failed'),
8         INVALID_ADDRESS:                _('IP address in invalid'),
9         INVALID_GATEWAY:                _('Gateway address is invalid'),
10         INVALID_LOCAL_ADDRESS:  _('Local IP address is invalid'),
11         MISSING_ADDRESS:                _('IP address is missing'),
12         MISSING_PEER_ADDRESS:   _('Peer address is missing'),
13         NO_DEVICE:                              _('Network device is not present'),
14         NO_IFACE:                               _('Unable to determine device name'),
15         NO_IFNAME:                              _('Unable to determine device name'),
16         NO_WAN_ADDRESS:                 _('Unable to determine external IP address'),
17         NO_WAN_LINK:                    _('Unable to determine upstream interface'),
18         PEER_RESOLVE_FAIL:              _('Unable to resolve peer host name'),
19         PIN_FAILED:                             _('PIN code rejected')
20 };
21
22 var iface_patterns_ignore = [
23         /^wmaster\d+/,
24         /^wifi\d+/,
25         /^hwsim\d+/,
26         /^imq\d+/,
27         /^ifb\d+/,
28         /^mon\.wlan\d+/,
29         /^sit\d+/,
30         /^gre\d+/,
31         /^gretap\d+/,
32         /^ip6gre\d+/,
33         /^ip6tnl\d+/,
34         /^tunl\d+/,
35         /^lo$/
36 ];
37
38 var iface_patterns_wireless = [
39         /^wlan\d+/,
40         /^wl\d+/,
41         /^ath\d+/,
42         /^\w+\.network\d+/
43 ];
44
45 var iface_patterns_virtual = [ ];
46
47 var callLuciNetdevs = rpc.declare({
48         object: 'luci',
49         method: 'getNetworkDevices',
50         expect: { '': {} }
51 });
52
53 var callLuciWifidevs = rpc.declare({
54         object: 'luci',
55         method: 'getWirelessDevices',
56         expect: { '': {} }
57 });
58
59 var callLuciIfaddrs = rpc.declare({
60         object: 'luci',
61         method: 'getIfaddrs',
62         expect: { result: [] }
63 });
64
65 var callLuciBoardjson = rpc.declare({
66         object: 'luci',
67         method: 'getBoardJSON'
68 });
69
70 var callIwinfoAssoclist = rpc.declare({
71         object: 'iwinfo',
72         method: 'assoclist',
73         params: [ 'device', 'mac' ],
74         expect: { results: [] }
75 });
76
77 var callIwinfoScan = rpc.declare({
78         object: 'iwinfo',
79         method: 'scan',
80         params: [ 'device' ],
81         nobatch: true,
82         expect: { results: [] }
83 });
84
85 var callNetworkInterfaceStatus = rpc.declare({
86         object: 'network.interface',
87         method: 'dump',
88         expect: { 'interface': [] }
89 });
90
91 var callNetworkDeviceStatus = rpc.declare({
92         object: 'network.device',
93         method: 'status',
94         expect: { '': {} }
95 });
96
97 var callGetProtoHandlers = rpc.declare({
98         object: 'network',
99         method: 'get_proto_handlers',
100         expect: { '': {} }
101 });
102
103 var callGetHostHints = rpc.declare({
104         object: 'luci',
105         method: 'getHostHints',
106         expect: { '': {} }
107 });
108
109 var _init = null,
110     _state = null,
111     _protocols = {},
112     _protospecs = {};
113
114 function getInterfaceState(cache) {
115         return callNetworkInterfaceStatus().then(function(state) {
116                 if (!Array.isArray(state))
117                         throw !1;
118                 return state;
119         }).catch(function() {
120                 return [];
121         });
122 }
123
124 function getDeviceState(cache) {
125         return callNetworkDeviceStatus().then(function(state) {
126                 if (!L.isObject(state))
127                         throw !1;
128                 return state;
129         }).catch(function() {
130                 return {};
131         });
132 }
133
134 function getIfaddrState(cache) {
135         return callLuciIfaddrs().then(function(addrs) {
136                 if (!Array.isArray(addrs))
137                         throw !1;
138                 return addrs;
139         }).catch(function() {
140                 return [];
141         });
142 }
143
144 function getNetdevState(cache) {
145         return callLuciNetdevs().then(function(state) {
146                 if (!L.isObject(state))
147                         throw !1;
148                 return state;
149         }).catch(function() {
150                 return {};
151         });
152 }
153
154 function getWifidevState(cache) {
155         return callLuciWifidevs().then(function(state) {
156                 if (!L.isObject(state))
157                         throw !1;
158                 return state;
159         }).catch(function() {
160                 return {};
161         });
162 }
163
164 function getBoardState(cache) {
165         return callLuciBoardjson().then(function(state) {
166                 if (!L.isObject(state))
167                         throw !1;
168                 return state;
169         }).catch(function() {
170                 return {};
171         });
172 }
173
174 function getProtocolHandlers(cache) {
175         return callGetProtoHandlers().then(function(protos) {
176                 if (!L.isObject(protos))
177                         throw !1;
178
179                 /* Register "none" protocol */
180                 if (!protos.hasOwnProperty('none'))
181                         Object.assign(protos, { none: { no_device: false } });
182
183                 /* Hack: emulate relayd protocol */
184                 if (!protos.hasOwnProperty('relay'))
185                         Object.assign(protos, { relay: { no_device: true } });
186
187                 Object.assign(_protospecs, protos);
188
189                 return Promise.all(Object.keys(protos).map(function(p) {
190                         return Promise.resolve(L.require('protocol.%s'.format(p))).catch(function(err) {
191                                 if (L.isObject(err) && err.name != 'NetworkError')
192                                         L.error(err);
193                         });
194                 })).then(function() {
195                         return protos;
196                 });
197         }).catch(function() {
198                 return {};
199         });
200 }
201
202 function getHostHints(cache) {
203         return callGetHostHints().then(function(hosts) {
204                 if (!L.isObject(hosts))
205                         throw !1;
206                 return hosts;
207         }).catch(function() {
208                 return {};
209         });
210 }
211
212 function getWifiStateBySid(sid) {
213         var s = uci.get('wireless', sid);
214
215         if (s != null && s['.type'] == 'wifi-iface') {
216                 for (var radioname in _state.radios) {
217                         for (var i = 0; i < _state.radios[radioname].interfaces.length; i++) {
218                                 var netstate = _state.radios[radioname].interfaces[i];
219
220                                 if (typeof(netstate.section) != 'string')
221                                         continue;
222
223                                 var s2 = uci.get('wireless', netstate.section);
224
225                                 if (s2 != null && s['.type'] == s2['.type'] && s['.name'] == s2['.name']) {
226                                         if (s2['.anonymous'] == false && netstate.section.charAt(0) == '@')
227                                                 return null;
228
229                                         return [ radioname, _state.radios[radioname], netstate ];
230                                 }
231                         }
232                 }
233         }
234
235         return null;
236 }
237
238 function getWifiStateByIfname(ifname) {
239         for (var radioname in _state.radios) {
240                 for (var i = 0; i < _state.radios[radioname].interfaces.length; i++) {
241                         var netstate = _state.radios[radioname].interfaces[i];
242
243                         if (typeof(netstate.ifname) != 'string')
244                                 continue;
245
246                         if (netstate.ifname == ifname)
247                                 return [ radioname, _state.radios[radioname], netstate ];
248                 }
249         }
250
251         return null;
252 }
253
254 function isWifiIfname(ifname) {
255         for (var i = 0; i < iface_patterns_wireless.length; i++)
256                 if (iface_patterns_wireless[i].test(ifname))
257                         return true;
258
259         return false;
260 }
261
262 function getWifiSidByNetid(netid) {
263         var m = /^(\w+)\.network(\d+)$/.exec(netid);
264         if (m) {
265                 var sections = uci.sections('wireless', 'wifi-iface');
266                 for (var i = 0, n = 0; i < sections.length; i++) {
267                         if (sections[i].device != m[1])
268                                 continue;
269
270                         if (++n == +m[2])
271                                 return sections[i]['.name'];
272                 }
273         }
274
275         return null;
276 }
277
278 function getWifiSidByIfname(ifname) {
279         var sid = getWifiSidByNetid(ifname);
280
281         if (sid != null)
282                 return sid;
283
284         var res = getWifiStateByIfname(ifname);
285
286         if (res != null && L.isObject(res[2]) && typeof(res[2].section) == 'string')
287                 return res[2].section;
288
289         return null;
290 }
291
292 function getWifiNetidBySid(sid) {
293         var s = uci.get('wireless', sid);
294         if (s != null && s['.type'] == 'wifi-iface') {
295                 var radioname = s.device;
296                 if (typeof(s.device) == 'string') {
297                         var i = 0, netid = null, sections = uci.sections('wireless', 'wifi-iface');
298                         for (var i = 0, n = 0; i < sections.length; i++) {
299                                 if (sections[i].device != s.device)
300                                         continue;
301
302                                 n++;
303
304                                 if (sections[i]['.name'] != s['.name'])
305                                         continue;
306
307                                 return [ '%s.network%d'.format(s.device, n), s.device ];
308                         }
309
310                 }
311         }
312
313         return null;
314 }
315
316 function getWifiNetidByNetname(name) {
317         var sections = uci.sections('wireless', 'wifi-iface');
318         for (var i = 0; i < sections.length; i++) {
319                 if (typeof(sections[i].network) != 'string')
320                         continue;
321
322                 var nets = sections[i].network.split(/\s+/);
323                 for (var j = 0; j < nets.length; j++) {
324                         if (nets[j] != name)
325                                 continue;
326
327                         return getWifiNetidBySid(sections[i]['.name']);
328                 }
329         }
330
331         return null;
332 }
333
334 function isVirtualIfname(ifname) {
335         for (var i = 0; i < iface_patterns_virtual.length; i++)
336                 if (iface_patterns_virtual[i].test(ifname))
337                         return true;
338
339         return false;
340 }
341
342 function isIgnoredIfname(ifname) {
343         for (var i = 0; i < iface_patterns_ignore.length; i++)
344                 if (iface_patterns_ignore[i].test(ifname))
345                         return true;
346
347         return false;
348 }
349
350 function appendValue(config, section, option, value) {
351         var values = uci.get(config, section, option),
352             isArray = Array.isArray(values),
353             rv = false;
354
355         if (isArray == false)
356                 values = L.toArray(values);
357
358         if (values.indexOf(value) == -1) {
359                 values.push(value);
360                 rv = true;
361         }
362
363         uci.set(config, section, option, isArray ? values : values.join(' '));
364
365         return rv;
366 }
367
368 function removeValue(config, section, option, value) {
369         var values = uci.get(config, section, option),
370             isArray = Array.isArray(values),
371             rv = false;
372
373         if (isArray == false)
374                 values = L.toArray(values);
375
376         for (var i = values.length - 1; i >= 0; i--) {
377                 if (values[i] == value) {
378                         values.splice(i, 1);
379                         rv = true;
380                 }
381         }
382
383         if (values.length > 0)
384                 uci.set(config, section, option, isArray ? values : values.join(' '));
385         else
386                 uci.unset(config, section, option);
387
388         return rv;
389 }
390
391 function prefixToMask(bits, v6) {
392         var w = v6 ? 128 : 32,
393             m = [];
394
395         if (bits > w)
396                 return null;
397
398         for (var i = 0; i < w / 16; i++) {
399                 var b = Math.min(16, bits);
400                 m.push((0xffff << (16 - b)) & 0xffff);
401                 bits -= b;
402         }
403
404         if (v6)
405                 return String.prototype.format.apply('%x:%x:%x:%x:%x:%x:%x:%x', m).replace(/:0(?::0)+$/, '::');
406         else
407                 return '%d.%d.%d.%d'.format(m[0] >>> 8, m[0] & 0xff, m[1] >>> 8, m[1] & 0xff);
408 }
409
410 function maskToPrefix(mask, v6) {
411         var m = v6 ? validation.parseIPv6(mask) : validation.parseIPv4(mask);
412
413         if (!m)
414                 return null;
415
416         var bits = 0;
417
418         for (var i = 0, z = false; i < m.length; i++) {
419                 z = z || !m[i];
420
421                 while (!z && (m[i] & (v6 ? 0x8000 : 0x80))) {
422                         m[i] = (m[i] << 1) & (v6 ? 0xffff : 0xff);
423                         bits++;
424                 }
425
426                 if (m[i])
427                         return null;
428         }
429
430         return bits;
431 }
432
433 function initNetworkState(refresh) {
434         if (_state == null || refresh) {
435                 _init = _init || Promise.all([
436                         getInterfaceState(), getDeviceState(), getBoardState(),
437                         getIfaddrState(), getNetdevState(), getWifidevState(),
438                         getHostHints(), getProtocolHandlers(),
439                         uci.load('network'), uci.load('wireless'), uci.load('luci')
440                 ]).then(function(data) {
441                         var board = data[2], ifaddrs = data[3], devices = data[4];
442                         var s = {
443                                 isTunnel: {}, isBridge: {}, isSwitch: {}, isWifi: {},
444                                 ifaces: data[0], devices: data[1], radios: data[5],
445                                 hosts: data[6], netdevs: {}, bridges: {}, switches: {}
446                         };
447
448                         for (var i = 0, a; (a = ifaddrs[i]) != null; i++) {
449                                 var name = a.name.replace(/:.+$/, '');
450
451                                 if (isVirtualIfname(name))
452                                         s.isTunnel[name] = true;
453
454                                 if (s.isTunnel[name] || !(isIgnoredIfname(name) || isVirtualIfname(name))) {
455                                         s.netdevs[name] = s.netdevs[name] || {
456                                                 idx:      a.ifindex || i,
457                                                 name:     name,
458                                                 rawname:  a.name,
459                                                 flags:    [],
460                                                 ipaddrs:  [],
461                                                 ip6addrs: []
462                                         };
463
464                                         if (a.family == 'packet') {
465                                                 s.netdevs[name].flags   = a.flags;
466                                                 s.netdevs[name].stats   = a.data;
467
468                                                 if (a.addr != null && a.addr != '00:00:00:00:00:00' && a.addr.length == 17)
469                                                         s.netdevs[name].macaddr = a.addr;
470                                         }
471                                         else if (a.family == 'inet') {
472                                                 s.netdevs[name].ipaddrs.push(a.addr + '/' + a.netmask);
473                                         }
474                                         else if (a.family == 'inet6') {
475                                                 s.netdevs[name].ip6addrs.push(a.addr + '/' + a.netmask);
476                                         }
477                                 }
478                         }
479
480                         for (var devname in devices) {
481                                 var dev = devices[devname];
482
483                                 if (dev.bridge) {
484                                         var b = {
485                                                 name:    devname,
486                                                 id:      dev.id,
487                                                 stp:     dev.stp,
488                                                 ifnames: []
489                                         };
490
491                                         for (var i = 0; dev.ports && i < dev.ports.length; i++) {
492                                                 var subdev = s.netdevs[dev.ports[i]];
493
494                                                 if (subdev == null)
495                                                         continue;
496
497                                                 b.ifnames.push(subdev);
498                                                 subdev.bridge = b;
499                                         }
500
501                                         s.bridges[devname] = b;
502                                         s.isBridge[devname] = true;
503                                 }
504
505                                 if (s.netdevs.hasOwnProperty(devname)) {
506                                         Object.assign(s.netdevs[devname], {
507                                                 macaddr: dev.mac,
508                                                 type:    dev.type,
509                                                 mtu:     dev.mtu,
510                                                 qlen:    dev.qlen
511                                         });
512                                 }
513                         }
514
515                         if (L.isObject(board.switch)) {
516                                 for (var switchname in board.switch) {
517                                         var layout = board.switch[switchname],
518                                             netdevs = {},
519                                             nports = {},
520                                             ports = [],
521                                             pnum = null,
522                                             role = null;
523
524                                         if (L.isObject(layout) && Array.isArray(layout.ports)) {
525                                                 for (var i = 0, port; (port = layout.ports[i]) != null; i++) {
526                                                         if (typeof(port) == 'object' && typeof(port.num) == 'number' &&
527                                                                 (typeof(port.role) == 'string' || typeof(port.device) == 'string')) {
528                                                                 var spec = {
529                                                                         num:   port.num,
530                                                                         role:  port.role || 'cpu',
531                                                                         index: (port.index != null) ? port.index : port.num
532                                                                 };
533
534                                                                 if (port.device != null) {
535                                                                         spec.device = port.device;
536                                                                         spec.tagged = spec.need_tag;
537                                                                         netdevs[port.num] = port.device;
538                                                                 }
539
540                                                                 ports.push(spec);
541
542                                                                 if (port.role != null)
543                                                                         nports[port.role] = (nports[port.role] || 0) + 1;
544                                                         }
545                                                 }
546
547                                                 ports.sort(function(a, b) {
548                                                         if (a.role != b.role)
549                                                                 return (a.role < b.role) ? -1 : 1;
550
551                                                         return (a.index - b.index);
552                                                 });
553
554                                                 for (var i = 0, port; (port = ports[i]) != null; i++) {
555                                                         if (port.role != role) {
556                                                                 role = port.role;
557                                                                 pnum = 1;
558                                                         }
559
560                                                         if (role == 'cpu')
561                                                                 port.label = 'CPU (%s)'.format(port.device);
562                                                         else if (nports[role] > 1)
563                                                                 port.label = '%s %d'.format(role.toUpperCase(), pnum++);
564                                                         else
565                                                                 port.label = role.toUpperCase();
566
567                                                         delete port.role;
568                                                         delete port.index;
569                                                 }
570
571                                                 s.switches[switchname] = {
572                                                         ports: ports,
573                                                         netdevs: netdevs
574                                                 };
575                                         }
576                                 }
577                         }
578
579                         if (L.isObject(board.dsl) && L.isObject(board.dsl.modem)) {
580                                 s.hasDSLModem = board.dsl.modem;
581                         }
582
583                         _init = null;
584
585                         return (_state = s);
586                 });
587         }
588
589         return (_state != null ? Promise.resolve(_state) : _init);
590 }
591
592 function ifnameOf(obj) {
593         if (obj instanceof Protocol)
594                 return obj.getIfname();
595         else if (obj instanceof Device)
596                 return obj.getName();
597         else if (obj instanceof WifiDevice)
598                 return obj.getName();
599         else if (obj instanceof WifiNetwork)
600                 return obj.getIfname();
601         else if (typeof(obj) == 'string')
602                 return obj.replace(/:.+$/, '');
603
604         return null;
605 }
606
607 function networkSort(a, b) {
608         return a.getName() > b.getName();
609 }
610
611 function deviceSort(a, b) {
612         var typeWeigth = { wifi: 2, alias: 3 },
613         weightA = typeWeigth[a.getType()] || 1,
614         weightB = typeWeigth[b.getType()] || 1;
615
616     if (weightA != weightB)
617         return weightA - weightB;
618
619         return a.getName() > b.getName();
620 }
621
622 function formatWifiEncryption(enc) {
623         if (!L.isObject(enc))
624                 return null;
625
626         if (!enc.enabled)
627                 return 'None';
628
629         var ciphers = Array.isArray(enc.ciphers)
630                 ? enc.ciphers.map(function(c) { return c.toUpperCase() }) : [ 'NONE' ];
631
632         if (Array.isArray(enc.wep)) {
633                 var has_open = false,
634                     has_shared = false;
635
636                 for (var i = 0; i < enc.wep.length; i++)
637                         if (enc.wep[i] == 'open')
638                                 has_open = true;
639                         else if (enc.wep[i] == 'shared')
640                                 has_shared = true;
641
642                 if (has_open && has_shared)
643                         return 'WEP Open/Shared (%s)'.format(ciphers.join(', '));
644                 else if (has_open)
645                         return 'WEP Open System (%s)'.format(ciphers.join(', '));
646                 else if (has_shared)
647                         return 'WEP Shared Auth (%s)'.format(ciphers.join(', '));
648
649                 return 'WEP';
650         }
651
652         if (Array.isArray(enc.wpa)) {
653                 var versions = [],
654                     suites = Array.isArray(enc.authentication)
655                         ? enc.authentication.map(function(a) { return a.toUpperCase() }) : [ 'NONE' ];
656
657                 for (var i = 0; i < enc.wpa.length; i++)
658                         switch (enc.wpa[i]) {
659                         case 1:
660                                 versions.push('WPA');
661                                 break;
662
663                         default:
664                                 versions.push('WPA%d'.format(enc.wpa[i]));
665                                 break;
666                         }
667
668                 if (versions.length > 1)
669                         return 'mixed %s %s (%s)'.format(versions.join('/'), suites.join(', '), ciphers.join(', '));
670
671                 return '%s %s (%s)'.format(versions[0], suites.join(', '), ciphers.join(', '));
672         }
673
674         return 'Unknown';
675 }
676
677 function enumerateNetworks() {
678         var uciInterfaces = uci.sections('network', 'interface'),
679             networks = {};
680
681         for (var i = 0; i < uciInterfaces.length; i++)
682                 networks[uciInterfaces[i]['.name']] = this.instantiateNetwork(uciInterfaces[i]['.name']);
683
684         for (var i = 0; i < _state.ifaces.length; i++)
685                 if (networks[_state.ifaces[i].interface] == null)
686                         networks[_state.ifaces[i].interface] =
687                                 this.instantiateNetwork(_state.ifaces[i].interface, _state.ifaces[i].proto);
688
689         var rv = [];
690
691         for (var network in networks)
692                 if (networks.hasOwnProperty(network))
693                         rv.push(networks[network]);
694
695         rv.sort(networkSort);
696
697         return rv;
698 }
699
700
701 var Hosts, Network, Protocol, Device, WifiDevice, WifiNetwork;
702
703 Network = L.Class.extend({
704         prefixToMask: prefixToMask,
705         maskToPrefix: maskToPrefix,
706         formatWifiEncryption: formatWifiEncryption,
707
708         flushCache: function() {
709                 initNetworkState(true);
710                 return _init;
711         },
712
713         getProtocol: function(protoname, netname) {
714                 var v = _protocols[protoname];
715                 if (v != null)
716                         return new v(netname || '__dummy__');
717
718                 return null;
719         },
720
721         getProtocols: function() {
722                 var rv = [];
723
724                 for (var protoname in _protocols)
725                         rv.push(new _protocols[protoname]('__dummy__'));
726
727                 return rv;
728         },
729
730         registerProtocol: function(protoname, methods) {
731                 var spec = L.isObject(_protospecs) ? _protospecs[protoname] : null;
732                 var proto = Protocol.extend(Object.assign({
733                         getI18n: function() {
734                                 return protoname;
735                         },
736
737                         isFloating: function() {
738                                 return false;
739                         },
740
741                         isVirtual: function() {
742                                 return (L.isObject(spec) && spec.no_device == true);
743                         },
744
745                         renderFormOptions: function(section) {
746
747                         }
748                 }, methods, {
749                         __init__: function(name) {
750                                 this.sid = name;
751                         },
752
753                         getProtocol: function() {
754                                 return protoname;
755                         }
756                 }));
757
758                 _protocols[protoname] = proto;
759
760                 return proto;
761         },
762
763         registerPatternVirtual: function(pat) {
764                 iface_patterns_virtual.push(pat);
765         },
766
767         registerErrorCode: function(code, message) {
768                 if (typeof(code) == 'string' &&
769                     typeof(message) == 'string' &&
770                     !proto_errors.hasOwnProperty(code)) {
771                         proto_errors[code] = message;
772                         return true;
773                 }
774
775                 return false;
776         },
777
778         addNetwork: function(name, options) {
779                 return this.getNetwork(name).then(L.bind(function(existingNetwork) {
780                         if (name != null && /^[a-zA-Z0-9_]+$/.test(name) && existingNetwork == null) {
781                                 var sid = uci.add('network', 'interface', name);
782
783                                 if (sid != null) {
784                                         if (L.isObject(options))
785                                                 for (var key in options)
786                                                         if (options.hasOwnProperty(key))
787                                                                 uci.set('network', sid, key, options[key]);
788
789                                         return this.instantiateNetwork(sid);
790                                 }
791                         }
792                         else if (existingNetwork != null && existingNetwork.isEmpty()) {
793                                 if (L.isObject(options))
794                                         for (var key in options)
795                                                 if (options.hasOwnProperty(key))
796                                                         existingNetwork.set(key, options[key]);
797
798                                 return existingNetwork;
799                         }
800                 }, this));
801         },
802
803         getNetwork: function(name) {
804                 return initNetworkState().then(L.bind(function() {
805                         var section = (name != null) ? uci.get('network', name) : null;
806
807                         if (section != null && section['.type'] == 'interface') {
808                                 return this.instantiateNetwork(name);
809                         }
810                         else if (name != null) {
811                                 for (var i = 0; i < _state.ifaces.length; i++)
812                                         if (_state.ifaces[i].interface == name)
813                                                 return this.instantiateNetwork(name, _state.ifaces[i].proto);
814                         }
815
816                         return null;
817                 }, this));
818         },
819
820         getNetworks: function() {
821                 return initNetworkState().then(L.bind(enumerateNetworks, this));
822         },
823
824         deleteNetwork: function(name) {
825                 return Promise.all([ L.require('firewall').catch(function() { return null }), initNetworkState() ]).then(function() {
826                         var uciInterface = uci.get('network', name);
827
828                         if (uciInterface != null && uciInterface['.type'] == 'interface') {
829                                 uci.remove('network', name);
830
831                                 uci.sections('luci', 'ifstate', function(s) {
832                                         if (s.interface == name)
833                                                 uci.remove('luci', s['.name']);
834                                 });
835
836                                 uci.sections('network', 'alias', function(s) {
837                                         if (s.interface == name)
838                                                 uci.remove('network', s['.name']);
839                                 });
840
841                                 uci.sections('network', 'route', function(s) {
842                                         if (s.interface == name)
843                                                 uci.remove('network', s['.name']);
844                                 });
845
846                                 uci.sections('network', 'route6', function(s) {
847                                         if (s.interface == name)
848                                                 uci.remove('network', s['.name']);
849                                 });
850
851                                 uci.sections('wireless', 'wifi-iface', function(s) {
852                                         var networks = L.toArray(s.network).filter(function(network) { return network != name });
853
854                                         if (networks.length > 0)
855                                                 uci.set('wireless', s['.name'], 'network', networks.join(' '));
856                                         else
857                                                 uci.unset('wireless', s['.name'], 'network');
858                                 });
859
860                                 if (L.firewall)
861                                         return L.firewall.deleteNetwork(name).then(function() { return true });
862
863                                 return true;
864                         }
865
866                         return false;
867                 });
868         },
869
870         renameNetwork: function(oldName, newName) {
871                 return initNetworkState().then(function() {
872                         if (newName == null || !/^[a-zA-Z0-9_]+$/.test(newName) || uci.get('network', newName) != null)
873                                 return false;
874
875                         var oldNetwork = uci.get('network', oldName);
876
877                         if (oldNetwork == null || oldNetwork['.type'] != 'interface')
878                                 return false;
879
880                         var sid = uci.add('network', 'interface', newName);
881
882                         for (var key in oldNetwork)
883                                 if (oldNetwork.hasOwnProperty(key) && key.charAt(0) != '.')
884                                         uci.set('network', sid, key, oldNetwork[key]);
885
886                         uci.sections('luci', 'ifstate', function(s) {
887                                 if (s.interface == oldName)
888                                         uci.set('luci', s['.name'], 'interface', newName);
889                         });
890
891                         uci.sections('network', 'alias', function(s) {
892                                 if (s.interface == oldName)
893                                         uci.set('network', s['.name'], 'interface', newName);
894                         });
895
896                         uci.sections('network', 'route', function(s) {
897                                 if (s.interface == oldName)
898                                         uci.set('network', s['.name'], 'interface', newName);
899                         });
900
901                         uci.sections('network', 'route6', function(s) {
902                                 if (s.interface == oldName)
903                                         uci.set('network', s['.name'], 'interface', newName);
904                         });
905
906                         uci.sections('wireless', 'wifi-iface', function(s) {
907                                 var networks = L.toArray(s.network).map(function(network) { return (network == oldName ? newName : network) });
908
909                                 if (networks.length > 0)
910                                         uci.set('wireless', s['.name'], 'network', networks.join(' '));
911                         });
912
913                         uci.remove('network', oldName);
914
915                         return true;
916                 });
917         },
918
919         getDevice: function(name) {
920                 return initNetworkState().then(L.bind(function() {
921                         if (name == null)
922                                 return null;
923
924                         if (_state.netdevs.hasOwnProperty(name) || isWifiIfname(name))
925                                 return this.instantiateDevice(name);
926
927                         var netid = getWifiNetidBySid(name);
928                         if (netid != null)
929                                 return this.instantiateDevice(netid[0]);
930
931                         return null;
932                 }, this));
933         },
934
935         getDevices: function() {
936                 return initNetworkState().then(L.bind(function() {
937                         var devices = {};
938
939                         /* find simple devices */
940                         var uciInterfaces = uci.sections('network', 'interface');
941                         for (var i = 0; i < uciInterfaces.length; i++) {
942                                 var ifnames = L.toArray(uciInterfaces[i].ifname);
943
944                                 for (var j = 0; j < ifnames.length; j++) {
945                                         if (ifnames[j].charAt(0) == '@')
946                                                 continue;
947
948                                         if (isIgnoredIfname(ifnames[j]) || isVirtualIfname(ifnames[j]) || isWifiIfname(ifnames[j]))
949                                                 continue;
950
951                                         devices[ifnames[j]] = this.instantiateDevice(ifnames[j]);
952                                 }
953                         }
954
955                         for (var ifname in _state.netdevs) {
956                                 if (devices.hasOwnProperty(ifname))
957                                         continue;
958
959                                 if (isIgnoredIfname(ifname) || isVirtualIfname(ifname) || isWifiIfname(ifname))
960                                         continue;
961
962                                 devices[ifname] = this.instantiateDevice(ifname);
963                         }
964
965                         /* find VLAN devices */
966                         var uciSwitchVLANs = uci.sections('network', 'switch_vlan');
967                         for (var i = 0; i < uciSwitchVLANs.length; i++) {
968                                 if (typeof(uciSwitchVLANs[i].ports) != 'string' ||
969                                     typeof(uciSwitchVLANs[i].device) != 'string' ||
970                                     !_state.switches.hasOwnProperty(uciSwitchVLANs[i].device))
971                                         continue;
972
973                                 var ports = uciSwitchVLANs[i].ports.split(/\s+/);
974                                 for (var j = 0; j < ports.length; j++) {
975                                         var m = ports[j].match(/^(\d+)([tu]?)$/);
976                                         if (m == null)
977                                                 continue;
978
979                                         var netdev = _state.switches[uciSwitchVLANs[i].device].netdevs[m[1]];
980                                         if (netdev == null)
981                                                 continue;
982
983                                         if (!devices.hasOwnProperty(netdev))
984                                                 devices[netdev] = this.instantiateDevice(netdev);
985
986                                         _state.isSwitch[netdev] = true;
987
988                                         if (m[2] != 't')
989                                                 continue;
990
991                                         var vid = uciSwitchVLANs[i].vid || uciSwitchVLANs[i].vlan;
992                                             vid = (vid != null ? +vid : null);
993
994                                         if (vid == null || vid < 0 || vid > 4095)
995                                                 continue;
996
997                                         var vlandev = '%s.%d'.format(netdev, vid);
998
999                                         if (!devices.hasOwnProperty(vlandev))
1000                                                 devices[vlandev] = this.instantiateDevice(vlandev);
1001
1002                                         _state.isSwitch[vlandev] = true;
1003                                 }
1004                         }
1005
1006                         /* find wireless interfaces */
1007                         var uciWifiIfaces = uci.sections('wireless', 'wifi-iface'),
1008                             networkCount = {};
1009
1010                         for (var i = 0; i < uciWifiIfaces.length; i++) {
1011                                 if (typeof(uciWifiIfaces[i].device) != 'string')
1012                                         continue;
1013
1014                                 networkCount[uciWifiIfaces[i].device] = (networkCount[uciWifiIfaces[i].device] || 0) + 1;
1015
1016                                 var netid = '%s.network%d'.format(uciWifiIfaces[i].device, networkCount[uciWifiIfaces[i].device]);
1017
1018                                 devices[netid] = this.instantiateDevice(netid);
1019                         }
1020
1021                         var rv = [];
1022
1023                         for (var netdev in devices)
1024                                 if (devices.hasOwnProperty(netdev))
1025                                         rv.push(devices[netdev]);
1026
1027                         rv.sort(deviceSort);
1028
1029                         return rv;
1030                 }, this));
1031         },
1032
1033         isIgnoredDevice: function(name) {
1034                 return isIgnoredIfname(name);
1035         },
1036
1037         getWifiDevice: function(devname) {
1038                 return initNetworkState().then(L.bind(function() {
1039                         var existingDevice = uci.get('wireless', devname);
1040
1041                         if (existingDevice == null || existingDevice['.type'] != 'wifi-device')
1042                                 return null;
1043
1044                         return this.instantiateWifiDevice(devname, _state.radios[devname] || {});
1045                 }, this));
1046         },
1047
1048         getWifiDevices: function() {
1049                 return initNetworkState().then(L.bind(function() {
1050                         var uciWifiDevices = uci.sections('wireless', 'wifi-device'),
1051                             rv = [];
1052
1053                         for (var i = 0; i < uciWifiDevices.length; i++) {
1054                                 var devname = uciWifiDevices[i]['.name'];
1055                                 rv.push(this.instantiateWifiDevice(devname, _state.radios[devname] || {}));
1056                         }
1057
1058                         return rv;
1059                 }, this));
1060         },
1061
1062         getWifiNetwork: function(netname) {
1063                 var sid, res, netid, radioname, radiostate, netstate;
1064
1065                 return initNetworkState().then(L.bind(function() {
1066                         sid = getWifiSidByNetid(netname);
1067
1068                         if (sid != null) {
1069                                 res        = getWifiStateBySid(sid);
1070                                 netid      = netname;
1071                                 radioname  = res ? res[0] : null;
1072                                 radiostate = res ? res[1] : null;
1073                                 netstate   = res ? res[2] : null;
1074                         }
1075                         else {
1076                                 res = getWifiStateByIfname(netname);
1077
1078                                 if (res != null) {
1079                                         radioname  = res[0];
1080                                         radiostate = res[1];
1081                                         netstate   = res[2];
1082                                         sid        = netstate.section;
1083                                         netid      = L.toArray(getWifiNetidBySid(sid))[0];
1084                                 }
1085                                 else {
1086                                         res = getWifiStateBySid(netname);
1087
1088                                         if (res != null) {
1089                                                 radioname  = res[0];
1090                                                 radiostate = res[1];
1091                                                 netstate   = res[2];
1092                                                 sid        = netname;
1093                                                 netid      = L.toArray(getWifiNetidBySid(sid))[0];
1094                                         }
1095                                         else {
1096                                                 res = getWifiNetidBySid(netname);
1097
1098                                                 if (res != null) {
1099                                                         netid     = res[0];
1100                                                         radioname = res[1];
1101                                                         sid       = netname;
1102                                                 }
1103                                         }
1104                                 }
1105                         }
1106
1107                         return this.instantiateWifiNetwork(sid || netname, radioname, radiostate, netid, netstate);
1108                 }, this));
1109         },
1110
1111         addWifiNetwork: function(options) {
1112                 return initNetworkState().then(L.bind(function() {
1113                         if (options == null ||
1114                             typeof(options) != 'object' ||
1115                             typeof(options.device) != 'string')
1116                             return null;
1117
1118                         var existingDevice = uci.get('wireless', options.device);
1119                         if (existingDevice == null || existingDevice['.type'] != 'wifi-device')
1120                                 return null;
1121
1122                         var sid = uci.add('wireless', 'wifi-iface');
1123                         for (var key in options)
1124                                 if (options.hasOwnProperty(key))
1125                                         uci.set('wireless', sid, key, options[key]);
1126
1127                         var radioname = existingDevice['.name'],
1128                             netid = getWifiNetidBySid(sid) || [];
1129
1130                         return this.instantiateWifiNetwork(sid, radioname, _state.radios[radioname], netid[0], null);
1131                 }, this));
1132         },
1133
1134         deleteWifiNetwork: function(netname) {
1135                 return initNetworkState().then(L.bind(function() {
1136                         var sid = getWifiSidByIfname(netname);
1137
1138                         if (sid == null)
1139                                 return false;
1140
1141                         uci.remove('wireless', sid);
1142                         return true;
1143                 }, this));
1144         },
1145
1146         getStatusByRoute: function(addr, mask) {
1147                 return initNetworkState().then(L.bind(function() {
1148                         var rv = [];
1149
1150                         for (var i = 0; i < _state.ifaces.length; i++) {
1151                                 if (!Array.isArray(_state.ifaces[i].route))
1152                                         continue;
1153
1154                                 for (var j = 0; j < _state.ifaces[i].route.length; j++) {
1155                                         if (typeof(_state.ifaces[i].route[j]) != 'object' ||
1156                                             typeof(_state.ifaces[i].route[j].target) != 'string' ||
1157                                             typeof(_state.ifaces[i].route[j].mask) != 'number')
1158                                             continue;
1159
1160                                         if (_state.ifaces[i].route[j].table)
1161                                                 continue;
1162
1163                                         if (_state.ifaces[i].route[j].target != addr ||
1164                                             _state.ifaces[i].route[j].mask != mask)
1165                                             continue;
1166
1167                                         rv.push(_state.ifaces[i]);
1168                                 }
1169                         }
1170
1171                         return rv;
1172                 }, this));
1173         },
1174
1175         getStatusByAddress: function(addr) {
1176                 return initNetworkState().then(L.bind(function() {
1177                         var rv = [];
1178
1179                         for (var i = 0; i < _state.ifaces.length; i++) {
1180                                 if (Array.isArray(_state.ifaces[i]['ipv4-address']))
1181                                         for (var j = 0; j < _state.ifaces[i]['ipv4-address'].length; j++)
1182                                                 if (typeof(_state.ifaces[i]['ipv4-address'][j]) == 'object' &&
1183                                                     _state.ifaces[i]['ipv4-address'][j].address == addr)
1184                                                         return _state.ifaces[i];
1185
1186                                 if (Array.isArray(_state.ifaces[i]['ipv6-address']))
1187                                         for (var j = 0; j < _state.ifaces[i]['ipv6-address'].length; j++)
1188                                                 if (typeof(_state.ifaces[i]['ipv6-address'][j]) == 'object' &&
1189                                                     _state.ifaces[i]['ipv6-address'][j].address == addr)
1190                                                         return _state.ifaces[i];
1191
1192                                 if (Array.isArray(_state.ifaces[i]['ipv6-prefix-assignment']))
1193                                         for (var j = 0; j < _state.ifaces[i]['ipv6-prefix-assignment'].length; j++)
1194                                                 if (typeof(_state.ifaces[i]['ipv6-prefix-assignment'][j]) == 'object' &&
1195                                                         typeof(_state.ifaces[i]['ipv6-prefix-assignment'][j]['local-address']) == 'object' &&
1196                                                     _state.ifaces[i]['ipv6-prefix-assignment'][j]['local-address'].address == addr)
1197                                                         return _state.ifaces[i];
1198                         }
1199
1200                         return null;
1201                 }, this));
1202         },
1203
1204         getWANNetworks: function() {
1205                 return this.getStatusByRoute('0.0.0.0', 0).then(L.bind(function(statuses) {
1206                         var rv = [];
1207
1208                         for (var i = 0; i < statuses.length; i++)
1209                                 rv.push(this.instantiateNetwork(statuses[i].interface, statuses[i].proto));
1210
1211                         return rv;
1212                 }, this));
1213         },
1214
1215         getWAN6Networks: function() {
1216                 return this.getStatusByRoute('::', 0).then(L.bind(function(statuses) {
1217                         var rv = [];
1218
1219                         for (var i = 0; i < statuses.length; i++)
1220                                 rv.push(this.instantiateNetwork(statuses[i].interface, statuses[i].proto));
1221
1222                         return rv;
1223                 }, this));
1224         },
1225
1226         getSwitchTopologies: function() {
1227                 return initNetworkState().then(function() {
1228                         return _state.switches;
1229                 });
1230         },
1231
1232         instantiateNetwork: function(name, proto) {
1233                 if (name == null)
1234                         return null;
1235
1236                 proto = (proto == null ? uci.get('network', name, 'proto') : proto);
1237
1238                 var protoClass = _protocols[proto] || Protocol;
1239                 return new protoClass(name);
1240         },
1241
1242         instantiateDevice: function(name, network, extend) {
1243                 if (extend != null)
1244                         return new (Device.extend(extend))(name, network);
1245
1246                 return new Device(name, network);
1247         },
1248
1249         instantiateWifiDevice: function(radioname, radiostate) {
1250                 return new WifiDevice(radioname, radiostate);
1251         },
1252
1253         instantiateWifiNetwork: function(sid, radioname, radiostate, netid, netstate) {
1254                 return new WifiNetwork(sid, radioname, radiostate, netid, netstate);
1255         },
1256
1257         getIfnameOf: function(obj) {
1258                 return ifnameOf(obj);
1259         },
1260
1261         getDSLModemType: function() {
1262                 return initNetworkState().then(function() {
1263                         return _state.hasDSLModem ? _state.hasDSLModem.type : null;
1264                 });
1265         },
1266
1267         getHostHints: function() {
1268                 return initNetworkState().then(function() {
1269                         return new Hosts(_state.hosts);
1270                 });
1271         }
1272 });
1273
1274 Hosts = L.Class.extend({
1275         __init__: function(hosts) {
1276                 this.hosts = hosts;
1277         },
1278
1279         getHostnameByMACAddr: function(mac) {
1280                 return this.hosts[mac] ? this.hosts[mac].name : null;
1281         },
1282
1283         getIPAddrByMACAddr: function(mac) {
1284                 return this.hosts[mac] ? this.hosts[mac].ipv4 : null;
1285         },
1286
1287         getIP6AddrByMACAddr: function(mac) {
1288                 return this.hosts[mac] ? this.hosts[mac].ipv6 : null;
1289         },
1290
1291         getHostnameByIPAddr: function(ipaddr) {
1292                 for (var mac in this.hosts)
1293                         if (this.hosts[mac].ipv4 == ipaddr && this.hosts[mac].name != null)
1294                                 return this.hosts[mac].name;
1295                 return null;
1296         },
1297
1298         getMACAddrByIPAddr: function(ipaddr) {
1299                 for (var mac in this.hosts)
1300                         if (this.hosts[mac].ipv4 == ipaddr)
1301                                 return mac;
1302                 return null;
1303         },
1304
1305         getHostnameByIP6Addr: function(ip6addr) {
1306                 for (var mac in this.hosts)
1307                         if (this.hosts[mac].ipv6 == ip6addr && this.hosts[mac].name != null)
1308                                 return this.hosts[mac].name;
1309                 return null;
1310         },
1311
1312         getMACAddrByIP6Addr: function(ip6addr) {
1313                 for (var mac in this.hosts)
1314                         if (this.hosts[mac].ipv6 == ip6addr)
1315                                 return mac;
1316                 return null;
1317         },
1318
1319         getMACHints: function(preferIp6) {
1320                 var rv = [];
1321                 for (var mac in this.hosts) {
1322                         var hint = this.hosts[mac].name ||
1323                                 this.hosts[mac][preferIp6 ? 'ipv6' : 'ipv4'] ||
1324                                 this.hosts[mac][preferIp6 ? 'ipv4' : 'ipv6'];
1325
1326                         rv.push([mac, hint]);
1327                 }
1328                 return rv.sort(function(a, b) { return a[0] > b[0] });
1329         }
1330 });
1331
1332 Protocol = L.Class.extend({
1333         __init__: function(name) {
1334                 this.sid = name;
1335         },
1336
1337         _get: function(opt) {
1338                 var val = uci.get('network', this.sid, opt);
1339
1340                 if (Array.isArray(val))
1341                         return val.join(' ');
1342
1343                 return val || '';
1344         },
1345
1346         _ubus: function(field) {
1347                 for (var i = 0; i < _state.ifaces.length; i++) {
1348                         if (_state.ifaces[i].interface != this.sid)
1349                                 continue;
1350
1351                         return (field != null ? _state.ifaces[i][field] : _state.ifaces[i]);
1352                 }
1353         },
1354
1355         get: function(opt) {
1356                 return uci.get('network', this.sid, opt);
1357         },
1358
1359         set: function(opt, val) {
1360                 return uci.set('network', this.sid, opt, val);
1361         },
1362
1363         getIfname: function() {
1364                 var ifname;
1365
1366                 if (this.isFloating())
1367                         ifname = this._ubus('l3_device');
1368                 else
1369                         ifname = this._ubus('device') || this._ubus('l3_device');
1370
1371                 if (ifname != null)
1372                         return ifname;
1373
1374                 var res = getWifiNetidByNetname(this.sid);
1375                 return (res != null ? res[0] : null);
1376         },
1377
1378         getProtocol: function() {
1379                 return null;
1380         },
1381
1382         getI18n: function() {
1383                 switch (this.getProtocol()) {
1384                 case 'none':   return _('Unmanaged');
1385                 case 'static': return _('Static address');
1386                 case 'dhcp':   return _('DHCP client');
1387                 default:       return _('Unknown');
1388                 }
1389         },
1390
1391         getType: function() {
1392                 return this._get('type');
1393         },
1394
1395         getName: function() {
1396                 return this.sid;
1397         },
1398
1399         getUptime: function() {
1400                 return this._ubus('uptime') || 0;
1401         },
1402
1403         getExpiry: function() {
1404                 var u = this._ubus('uptime'),
1405                     d = this._ubus('data');
1406
1407                 if (typeof(u) == 'number' && d != null &&
1408                     typeof(d) == 'object' && typeof(d.leasetime) == 'number') {
1409                         var r = d.leasetime - (u % d.leasetime);
1410                         return (r > 0 ? r : 0);
1411                 }
1412
1413                 return -1;
1414         },
1415
1416         getMetric: function() {
1417                 return this._ubus('metric') || 0;
1418         },
1419
1420         getZoneName: function() {
1421                 var d = this._ubus('data');
1422
1423                 if (L.isObject(d) && typeof(d.zone) == 'string')
1424                         return d.zone;
1425
1426                 return null;
1427         },
1428
1429         getIPAddr: function() {
1430                 var addrs = this._ubus('ipv4-address');
1431                 return ((Array.isArray(addrs) && addrs.length) ? addrs[0].address : null);
1432         },
1433
1434         getIPAddrs: function() {
1435                 var addrs = this._ubus('ipv4-address'),
1436                     rv = [];
1437
1438                 if (Array.isArray(addrs))
1439                         for (var i = 0; i < addrs.length; i++)
1440                                 rv.push('%s/%d'.format(addrs[i].address, addrs[i].mask));
1441
1442                 return rv;
1443         },
1444
1445         getNetmask: function() {
1446                 var addrs = this._ubus('ipv4-address');
1447                 if (Array.isArray(addrs) && addrs.length)
1448                         return prefixToMask(addrs[0].mask, false);
1449         },
1450
1451         getGatewayAddr: function() {
1452                 var routes = this._ubus('route');
1453
1454                 if (Array.isArray(routes))
1455                         for (var i = 0; i < routes.length; i++)
1456                                 if (typeof(routes[i]) == 'object' &&
1457                                     routes[i].target == '0.0.0.0' &&
1458                                     routes[i].mask == 0)
1459                                     return routes[i].nexthop;
1460
1461                 return null;
1462         },
1463
1464         getDNSAddrs: function() {
1465                 var addrs = this._ubus('dns-server'),
1466                     rv = [];
1467
1468                 if (Array.isArray(addrs))
1469                         for (var i = 0; i < addrs.length; i++)
1470                                 if (!/:/.test(addrs[i]))
1471                                         rv.push(addrs[i]);
1472
1473                 return rv;
1474         },
1475
1476         getIP6Addr: function() {
1477                 var addrs = this._ubus('ipv6-address');
1478
1479                 if (Array.isArray(addrs) && L.isObject(addrs[0]))
1480                         return '%s/%d'.format(addrs[0].address, addrs[0].mask);
1481
1482                 addrs = this._ubus('ipv6-prefix-assignment');
1483
1484                 if (Array.isArray(addrs) && L.isObject(addrs[0]) && L.isObject(addrs[0]['local-address']))
1485                         return '%s/%d'.format(addrs[0]['local-address'].address, addrs[0]['local-address'].mask);
1486
1487                 return null;
1488         },
1489
1490         getIP6Addrs: function() {
1491                 var addrs = this._ubus('ipv6-address'),
1492                     rv = [];
1493
1494                 if (Array.isArray(addrs))
1495                         for (var i = 0; i < addrs.length; i++)
1496                                 if (L.isObject(addrs[i]))
1497                                         rv.push('%s/%d'.format(addrs[i].address, addrs[i].mask));
1498
1499                 addrs = this._ubus('ipv6-prefix-assignment');
1500
1501                 if (Array.isArray(addrs))
1502                         for (var i = 0; i < addrs.length; i++)
1503                                 if (L.isObject(addrs[i]) && L.isObject(addrs[i]['local-address']))
1504                                         rv.push('%s/%d'.format(addrs[i]['local-address'].address, addrs[i]['local-address'].mask));
1505
1506                 return rv;
1507         },
1508
1509         getDNS6Addrs: function() {
1510                 var addrs = this._ubus('dns-server'),
1511                     rv = [];
1512
1513                 if (Array.isArray(addrs))
1514                         for (var i = 0; i < addrs.length; i++)
1515                                 if (/:/.test(addrs[i]))
1516                                         rv.push(addrs[i]);
1517
1518                 return rv;
1519         },
1520
1521         getIP6Prefix: function() {
1522                 var prefixes = this._ubus('ipv6-prefix');
1523
1524                 if (Array.isArray(prefixes) && L.isObject(prefixes[0]))
1525                         return '%s/%d'.format(prefixes[0].address, prefixes[0].mask);
1526
1527                 return null;
1528         },
1529
1530         getErrors: function() {
1531                 var errors = this._ubus('errors'),
1532                     rv = null;
1533
1534                 if (Array.isArray(errors)) {
1535                         for (var i = 0; i < errors.length; i++) {
1536                                 if (!L.isObject(errors[i]) || typeof(errors[i].code) != 'string')
1537                                         continue;
1538
1539                                 rv = rv || [];
1540                                 rv.push(proto_errors[errors[i].code] || _('Unknown error (%s)').format(errors[i].code));
1541                         }
1542                 }
1543
1544                 return rv;
1545         },
1546
1547         isBridge: function() {
1548                 return (!this.isVirtual() && this.getType() == 'bridge');
1549         },
1550
1551         getOpkgPackage: function() {
1552                 return null;
1553         },
1554
1555         isInstalled: function() {
1556                 return true;
1557         },
1558
1559         isVirtual: function() {
1560                 return false;
1561         },
1562
1563         isFloating: function() {
1564                 return false;
1565         },
1566
1567         isDynamic: function() {
1568                 return (this._ubus('dynamic') == true);
1569         },
1570
1571         isAlias: function() {
1572                 var ifnames = L.toArray(uci.get('network', this.sid, 'ifname')),
1573                     parent = null;
1574
1575                 for (var i = 0; i < ifnames.length; i++)
1576                         if (ifnames[i].charAt(0) == '@')
1577                                 parent = ifnames[i].substr(1);
1578                         else if (parent != null)
1579                                 parent = null;
1580
1581                 return parent;
1582         },
1583
1584         isEmpty: function() {
1585                 if (this.isFloating())
1586                         return false;
1587
1588                 var empty = true,
1589                     ifname = this._get('ifname');
1590
1591                 if (ifname != null && ifname.match(/\S+/))
1592                         empty = false;
1593
1594                 if (empty == true && getWifiNetidBySid(this.sid) != null)
1595                         empty = false;
1596
1597                 return empty;
1598         },
1599
1600         isUp: function() {
1601                 return (this._ubus('up') == true);
1602         },
1603
1604         addDevice: function(ifname) {
1605                 ifname = ifnameOf(ifname);
1606
1607                 if (ifname == null || this.isFloating())
1608                         return false;
1609
1610                 var wif = getWifiSidByIfname(ifname);
1611
1612                 if (wif != null)
1613                         return appendValue('wireless', wif, 'network', this.sid);
1614
1615                 return appendValue('network', this.sid, 'ifname', ifname);
1616         },
1617
1618         deleteDevice: function(ifname) {
1619                 var rv = false;
1620
1621                 ifname = ifnameOf(ifname);
1622
1623                 if (ifname == null || this.isFloating())
1624                         return false;
1625
1626                 var wif = getWifiSidByIfname(ifname);
1627
1628                 if (wif != null)
1629                         rv = removeValue('wireless', wif, 'network', this.sid);
1630
1631                 if (removeValue('network', this.sid, 'ifname', ifname))
1632                         rv = true;
1633
1634                 return rv;
1635         },
1636
1637         getDevice: function() {
1638                 if (this.isVirtual()) {
1639                         var ifname = '%s-%s'.format(this.getProtocol(), this.sid);
1640                         _state.isTunnel[this.getProtocol() + '-' + this.sid] = true;
1641                         return L.network.instantiateDevice(ifname, this);
1642                 }
1643                 else if (this.isBridge()) {
1644                         var ifname = 'br-%s'.format(this.sid);
1645                         _state.isBridge[ifname] = true;
1646                         return new Device(ifname, this);
1647                 }
1648                 else {
1649                         var ifnames = L.toArray(uci.get('network', this.sid, 'ifname'));
1650
1651                         for (var i = 0; i < ifnames.length; i++) {
1652                                 var m = ifnames[i].match(/^([^:/]+)/);
1653                                 return ((m && m[1]) ? L.network.instantiateDevice(m[1], this) : null);
1654                         }
1655
1656                         ifname = getWifiNetidByNetname(this.sid);
1657
1658                         return (ifname != null ? L.network.instantiateDevice(ifname[0], this) : null);
1659                 }
1660         },
1661
1662         getL2Device: function() {
1663                 var ifname = this._ubus('device');
1664                 return (ifname != null ? L.network.instantiateDevice(ifname, this) : null);
1665         },
1666
1667         getL3Device: function() {
1668                 var ifname = this._ubus('l3_device');
1669                 return (ifname != null ? L.network.instantiateDevice(ifname, this) : null);
1670         },
1671
1672         getDevices: function() {
1673                 var rv = [];
1674
1675                 if (!this.isBridge() && !(this.isVirtual() && !this.isFloating()))
1676                         return null;
1677
1678                 var ifnames = L.toArray(uci.get('network', this.sid, 'ifname'));
1679
1680                 for (var i = 0; i < ifnames.length; i++) {
1681                         if (ifnames[i].charAt(0) == '@')
1682                                 continue;
1683
1684                         var m = ifnames[i].match(/^([^:/]+)/);
1685                         if (m != null)
1686                                 rv.push(L.network.instantiateDevice(m[1], this));
1687                 }
1688
1689                 var uciWifiIfaces = uci.sections('wireless', 'wifi-iface');
1690
1691                 for (var i = 0; i < uciWifiIfaces.length; i++) {
1692                         if (typeof(uciWifiIfaces[i].device) != 'string')
1693                                 continue;
1694
1695                         var networks = L.toArray(uciWifiIfaces[i].network);
1696
1697                         for (var j = 0; j < networks.length; j++) {
1698                                 if (networks[j] != this.sid)
1699                                         continue;
1700
1701                                 var netid = getWifiNetidBySid(uciWifiIfaces[i]['.name']);
1702
1703                                 if (netid != null)
1704                                         rv.push(L.network.instantiateDevice(netid[0], this));
1705                         }
1706                 }
1707
1708                 rv.sort(deviceSort);
1709
1710                 return rv;
1711         },
1712
1713         containsDevice: function(ifname) {
1714                 ifname = ifnameOf(ifname);
1715
1716                 if (ifname == null)
1717                         return false;
1718                 else if (this.isVirtual() && '%s-%s'.format(this.getProtocol(), this.sid) == ifname)
1719                         return true;
1720                 else if (this.isBridge() && 'br-%s'.format(this.sid) == ifname)
1721                         return true;
1722
1723                 var ifnames = L.toArray(uci.get('network', this.sid, 'ifname'));
1724
1725                 for (var i = 0; i < ifnames.length; i++) {
1726                         var m = ifnames[i].match(/^([^:/]+)/);
1727                         if (m != null && m[1] == ifname)
1728                                 return true;
1729                 }
1730
1731                 var wif = getWifiSidByIfname(ifname);
1732
1733                 if (wif != null) {
1734                         var networks = L.toArray(uci.get('wireless', wif, 'network'));
1735
1736                         for (var i = 0; i < networks.length; i++)
1737                                 if (networks[i] == this.sid)
1738                                         return true;
1739                 }
1740
1741                 return false;
1742         }
1743 });
1744
1745 Device = L.Class.extend({
1746         __init__: function(ifname, network) {
1747                 var wif = getWifiSidByIfname(ifname);
1748
1749                 if (wif != null) {
1750                         var res = getWifiStateBySid(wif) || [],
1751                             netid = getWifiNetidBySid(wif) || [];
1752
1753                         this.wif    = new WifiNetwork(wif, res[0], res[1], netid[0], res[2], { ifname: ifname });
1754                         this.ifname = this.wif.getIfname();
1755                 }
1756
1757                 this.ifname  = this.ifname || ifname;
1758                 this.dev     = _state.netdevs[this.ifname];
1759                 this.network = network;
1760         },
1761
1762         _devstate: function(/* ... */) {
1763                 var rv = this.dev;
1764
1765                 for (var i = 0; i < arguments.length; i++)
1766                         if (L.isObject(rv))
1767                                 rv = rv[arguments[i]];
1768                         else
1769                                 return null;
1770
1771                 return rv;
1772         },
1773
1774         getName: function() {
1775                 return (this.wif != null ? this.wif.getIfname() : this.ifname);
1776         },
1777
1778         getMAC: function() {
1779                 var mac = this._devstate('macaddr');
1780                 return mac ? mac.toUpperCase() : null;
1781         },
1782
1783         getMTU: function() {
1784                 return this._devstate('mtu');
1785         },
1786
1787         getIPAddrs: function() {
1788                 var addrs = this._devstate('ipaddrs');
1789                 return (Array.isArray(addrs) ? addrs : []);
1790         },
1791
1792         getIP6Addrs: function() {
1793                 var addrs = this._devstate('ip6addrs');
1794                 return (Array.isArray(addrs) ? addrs : []);
1795         },
1796
1797         getType: function() {
1798                 if (this.ifname != null && this.ifname.charAt(0) == '@')
1799                         return 'alias';
1800                 else if (this.wif != null || isWifiIfname(this.ifname))
1801                         return 'wifi';
1802                 else if (_state.isBridge[this.ifname])
1803                         return 'bridge';
1804                 else if (_state.isTunnel[this.ifname])
1805                         return 'tunnel';
1806                 else if (this.ifname.indexOf('.') > -1)
1807                         return 'vlan';
1808                 else if (_state.isSwitch[this.ifname])
1809                         return 'switch';
1810                 else
1811                         return 'ethernet';
1812         },
1813
1814         getShortName: function() {
1815                 if (this.wif != null)
1816                         return this.wif.getShortName();
1817
1818                 return this.ifname;
1819         },
1820
1821         getI18n: function() {
1822                 if (this.wif != null) {
1823                         return '%s: %s "%s"'.format(
1824                                 _('Wireless Network'),
1825                                 this.wif.getActiveMode(),
1826                                 this.wif.getActiveSSID() || this.wif.getActiveBSSID() || this.wif.getID() || '?');
1827                 }
1828
1829                 return '%s: "%s"'.format(this.getTypeI18n(), this.getName());
1830         },
1831
1832         getTypeI18n: function() {
1833                 switch (this.getType()) {
1834                 case 'alias':
1835                         return _('Alias Interface');
1836
1837                 case 'wifi':
1838                         return _('Wireless Adapter');
1839
1840                 case 'bridge':
1841                         return _('Bridge');
1842
1843                 case 'switch':
1844                         return _('Ethernet Switch');
1845
1846                 case 'vlan':
1847                         return (_state.isSwitch[this.ifname] ? _('Switch VLAN') : _('Software VLAN'));
1848
1849                 case 'tunnel':
1850                         return _('Tunnel Interface');
1851
1852                 default:
1853                         return _('Ethernet Adapter');
1854                 }
1855         },
1856
1857         getPorts: function() {
1858                 var br = _state.bridges[this.ifname],
1859                     rv = [];
1860
1861                 if (br == null || !Array.isArray(br.ifnames))
1862                         return null;
1863
1864                 for (var i = 0; i < br.ifnames.length; i++)
1865                         rv.push(L.network.instantiateDevice(br.ifnames[i].name));
1866
1867                 rv.sort(deviceSort);
1868
1869                 return rv;
1870         },
1871
1872         getBridgeID: function() {
1873                 var br = _state.bridges[this.ifname];
1874                 return (br != null ? br.id : null);
1875         },
1876
1877         getBridgeSTP: function() {
1878                 var br = _state.bridges[this.ifname];
1879                 return (br != null ? !!br.stp : false);
1880         },
1881
1882         isUp: function() {
1883                 var up = this._devstate('flags', 'up');
1884
1885                 if (up == null)
1886                         up = (this.getType() == 'alias');
1887
1888                 return up;
1889         },
1890
1891         isBridge: function() {
1892                 return (this.getType() == 'bridge');
1893         },
1894
1895         isBridgePort: function() {
1896                 return (this._devstate('bridge') != null);
1897         },
1898
1899         getTXBytes: function() {
1900                 var stat = this._devstate('stats');
1901                 return (stat != null ? stat.tx_bytes || 0 : 0);
1902         },
1903
1904         getRXBytes: function() {
1905                 var stat = this._devstate('stats');
1906                 return (stat != null ? stat.rx_bytes || 0 : 0);
1907         },
1908
1909         getTXPackets: function() {
1910                 var stat = this._devstate('stats');
1911                 return (stat != null ? stat.tx_packets || 0 : 0);
1912         },
1913
1914         getRXPackets: function() {
1915                 var stat = this._devstate('stats');
1916                 return (stat != null ? stat.rx_packets || 0 : 0);
1917         },
1918
1919         getNetwork: function() {
1920                 return this.getNetworks()[0];
1921         },
1922
1923         getNetworks: function() {
1924                 if (this.networks == null) {
1925                         this.networks = [];
1926
1927                         var networks = enumerateNetworks.apply(L.network);
1928
1929                         for (var i = 0; i < networks.length; i++)
1930                                 if (networks[i].containsDevice(this.ifname) || networks[i].getIfname() == this.ifname)
1931                                         this.networks.push(networks[i]);
1932
1933                         this.networks.sort(networkSort);
1934                 }
1935
1936                 return this.networks;
1937         },
1938
1939         getWifiNetwork: function() {
1940                 return (this.wif != null ? this.wif : null);
1941         }
1942 });
1943
1944 WifiDevice = L.Class.extend({
1945         __init__: function(name, radiostate) {
1946                 var uciWifiDevice = uci.get('wireless', name);
1947
1948                 if (uciWifiDevice != null &&
1949                     uciWifiDevice['.type'] == 'wifi-device' &&
1950                     uciWifiDevice['.name'] != null) {
1951                         this.sid    = uciWifiDevice['.name'];
1952                 }
1953
1954                 this.sid    = this.sid || name;
1955                 this._ubusdata = {
1956                         radio: name,
1957                         dev:   radiostate
1958                 };
1959         },
1960
1961         ubus: function(/* ... */) {
1962                 var v = this._ubusdata;
1963
1964                 for (var i = 0; i < arguments.length; i++)
1965                         if (L.isObject(v))
1966                                 v = v[arguments[i]];
1967                         else
1968                                 return null;
1969
1970                 return v;
1971         },
1972
1973         get: function(opt) {
1974                 return uci.get('wireless', this.sid, opt);
1975         },
1976
1977         set: function(opt, value) {
1978                 return uci.set('wireless', this.sid, opt, value);
1979         },
1980
1981         isDisabled: function() {
1982                 return this.ubus('dev', 'disabled') || this.get('disabled') == '1';
1983         },
1984
1985         getName: function() {
1986                 return this.sid;
1987         },
1988
1989         getHWModes: function() {
1990                 var hwmodes = this.ubus('dev', 'iwinfo', 'hwmodes');
1991                 return Array.isArray(hwmodes) ? hwmodes : [ 'b', 'g' ];
1992         },
1993
1994         getHTModes: function() {
1995                 var htmodes = this.ubus('dev', 'iwinfo', 'htmodes');
1996                 return (Array.isArray(htmodes) && htmodes.length) ? htmodes : null;
1997         },
1998
1999         getI18n: function() {
2000                 var hw = this.ubus('dev', 'iwinfo', 'hardware'),
2001                     type = L.isObject(hw) ? hw.name : null;
2002
2003                 if (this.ubus('dev', 'iwinfo', 'type') == 'wl')
2004                         type = 'Broadcom';
2005
2006                 var hwmodes = this.getHWModes(),
2007                     modestr = '';
2008
2009                 hwmodes.sort(function(a, b) {
2010                         return (a.length != b.length ? a.length > b.length : a > b);
2011                 });
2012
2013                 modestr = hwmodes.join('');
2014
2015                 return '%s 802.11%s Wireless Controller (%s)'.format(type || 'Generic', modestr, this.getName());
2016         },
2017
2018         getScanList: function() {
2019                 return callIwinfoScan(this.sid);
2020         },
2021
2022         isUp: function() {
2023                 if (L.isObject(_state.radios[this.sid]))
2024                         return (_state.radios[this.sid].up == true);
2025
2026                 return false;
2027         },
2028
2029         getWifiNetwork: function(network) {
2030                 return L.network.getWifiNetwork(network).then(L.bind(function(networkInstance) {
2031                         var uciWifiIface = (networkInstance.sid ? uci.get('wireless', networkInstance.sid) : null);
2032
2033                         if (uciWifiIface == null || uciWifiIface['.type'] != 'wifi-iface' || uciWifiIface.device != this.sid)
2034                                 return Promise.reject();
2035
2036                         return networkInstance;
2037                 }, this));
2038         },
2039
2040         getWifiNetworks: function() {
2041                 var uciWifiIfaces = uci.sections('wireless', 'wifi-iface'),
2042                     tasks = [];
2043
2044                 for (var i = 0; i < uciWifiIfaces.length; i++)
2045                         if (uciWifiIfaces[i].device == this.sid)
2046                                 tasks.push(L.network.getWifiNetwork(uciWifiIfaces[i]['.name']));
2047
2048                 return Promise.all(tasks);
2049         },
2050
2051         addWifiNetwork: function(options) {
2052                 if (!L.isObject(options))
2053                         options = {};
2054
2055                 options.device = this.sid;
2056
2057                 return L.network.addWifiNetwork(options);
2058         },
2059
2060         deleteWifiNetwork: function(network) {
2061                 var sid = null;
2062
2063                 if (network instanceof WifiNetwork) {
2064                         sid = network.sid;
2065                 }
2066                 else {
2067                         var uciWifiIface = uci.get('wireless', network);
2068
2069                         if (uciWifiIface == null || uciWifiIface['.type'] != 'wifi-iface')
2070                                 sid = getWifiSidByIfname(network);
2071                 }
2072
2073                 if (sid == null || uci.get('wireless', sid, 'device') != this.sid)
2074                         return Promise.resolve(false);
2075
2076                 uci.delete('wireless', network);
2077
2078                 return Promise.resolve(true);
2079         }
2080 });
2081
2082 WifiNetwork = L.Class.extend({
2083         __init__: function(sid, radioname, radiostate, netid, netstate) {
2084                 this.sid    = sid;
2085                 this.netid  = netid;
2086                 this._ubusdata = {
2087                         radio: radioname,
2088                         dev:   radiostate,
2089                         net:   netstate
2090                 };
2091         },
2092
2093         ubus: function(/* ... */) {
2094                 var v = this._ubusdata;
2095
2096                 for (var i = 0; i < arguments.length; i++)
2097                         if (L.isObject(v))
2098                                 v = v[arguments[i]];
2099                         else
2100                                 return null;
2101
2102                 return v;
2103         },
2104
2105         get: function(opt) {
2106                 return uci.get('wireless', this.sid, opt);
2107         },
2108
2109         set: function(opt, value) {
2110                 return uci.set('wireless', this.sid, opt, value);
2111         },
2112
2113         isDisabled: function() {
2114                 return this.ubus('dev', 'disabled') || this.get('disabled') == '1';
2115         },
2116
2117         getMode: function() {
2118                 return this.ubus('net', 'config', 'mode') || this.get('mode') || 'ap';
2119         },
2120
2121         getSSID: function() {
2122                 return this.ubus('net', 'config', 'ssid') || this.get('ssid');
2123         },
2124
2125         getBSSID: function() {
2126                 return this.ubus('net', 'config', 'bssid') || this.get('bssid');
2127         },
2128
2129         getNetworkNames: function() {
2130                 return L.toArray(this.ubus('net', 'config', 'network') || this.get('network'));
2131         },
2132
2133         getID: function() {
2134                 return this.netid;
2135         },
2136
2137         getName: function() {
2138                 return this.sid;
2139         },
2140
2141         getIfname: function() {
2142                 var ifname = this.ubus('net', 'ifname') || this.ubus('net', 'iwinfo', 'ifname');
2143
2144                 if (ifname == null || ifname.match(/^(wifi|radio)\d/))
2145                         ifname = this.netid;
2146
2147                 return ifname;
2148         },
2149
2150         getWifiDeviceName: function() {
2151                 return this.ubus('radio') || this.get('device');
2152         },
2153
2154         getWifiDevice: function() {
2155                 var radioname = this.getWifiDeviceName();
2156
2157                 if (radioname == null)
2158                         return Promise.reject();
2159
2160                 return L.network.getWifiDevice(radioname);
2161         },
2162
2163         isUp: function() {
2164                 var device = this.getDevice();
2165
2166                 if (device == null)
2167                         return false;
2168
2169                 return device.isUp();
2170         },
2171
2172         getActiveMode: function() {
2173                 var mode = this.ubus('net', 'iwinfo', 'mode') || this.ubus('net', 'config', 'mode') || this.get('mode') || 'ap';
2174
2175                 switch (mode) {
2176                 case 'ap':      return 'Master';
2177                 case 'sta':     return 'Client';
2178                 case 'adhoc':   return 'Ad-Hoc';
2179                 case 'mesh':    return 'Mesh';
2180                 case 'monitor': return 'Monitor';
2181                 default:        return mode;
2182                 }
2183         },
2184
2185         getActiveModeI18n: function() {
2186                 var mode = this.getActiveMode();
2187
2188                 switch (mode) {
2189                 case 'Master':  return _('Master');
2190                 case 'Client':  return _('Client');
2191                 case 'Ad-Hoc':  return _('Ad-Hoc');
2192                 case 'Mash':    return _('Mesh');
2193                 case 'Monitor': return _('Monitor');
2194                 default:        return mode;
2195                 }
2196         },
2197
2198         getActiveSSID: function() {
2199                 return this.ubus('net', 'iwinfo', 'ssid') || this.ubus('net', 'config', 'ssid') || this.get('ssid');
2200         },
2201
2202         getActiveBSSID: function() {
2203                 return this.ubus('net', 'iwinfo', 'bssid') || this.ubus('net', 'config', 'bssid') || this.get('bssid');
2204         },
2205
2206         getActiveEncryption: function() {
2207                 return formatWifiEncryption(this.ubus('net', 'iwinfo', 'encryption')) || '-';
2208         },
2209
2210         getAssocList: function() {
2211                 return callIwinfoAssoclist(this.getIfname());
2212         },
2213
2214         getFrequency: function() {
2215                 var freq = this.ubus('net', 'iwinfo', 'frequency');
2216
2217                 if (freq != null && freq > 0)
2218                         return '%.03f'.format(freq / 1000);
2219
2220                 return null;
2221         },
2222
2223         getBitRate: function() {
2224                 var rate = this.ubus('net', 'iwinfo', 'bitrate');
2225
2226                 if (rate != null && rate > 0)
2227                         return (rate / 1000);
2228
2229                 return null;
2230         },
2231
2232         getChannel: function() {
2233                 return this.ubus('net', 'iwinfo', 'channel') || this.ubus('dev', 'config', 'channel') || this.get('channel');
2234         },
2235
2236         getSignal: function() {
2237                 return this.ubus('net', 'iwinfo', 'signal') || 0;
2238         },
2239
2240         getNoise: function() {
2241                 return this.ubus('net', 'iwinfo', 'noise') || 0;
2242         },
2243
2244         getCountryCode: function() {
2245                 return this.ubus('net', 'iwinfo', 'country') || this.ubus('dev', 'config', 'country') || '00';
2246         },
2247
2248         getTXPower: function() {
2249                 return this.ubus('net', 'iwinfo', 'txpower');
2250         },
2251
2252         getTXPowerOffset: function() {
2253                 return this.ubus('net', 'iwinfo', 'txpower_offset') || 0;
2254         },
2255
2256         getSignalLevel: function(signal, noise) {
2257                 if (this.getActiveBSSID() == '00:00:00:00:00:00')
2258                         return -1;
2259
2260                 signal = signal || this.getSignal();
2261                 noise  = noise  || this.getNoise();
2262
2263                 if (signal < 0 && noise < 0) {
2264                         var snr = -1 * (noise - signal);
2265                         return Math.floor(snr / 5);
2266                 }
2267
2268                 return 0;
2269         },
2270
2271         getSignalPercent: function() {
2272                 var qc = this.ubus('net', 'iwinfo', 'quality') || 0,
2273                     qm = this.ubus('net', 'iwinfo', 'quality_max') || 0;
2274
2275                 if (qc > 0 && qm > 0)
2276                         return Math.floor((100 / qm) * qc);
2277
2278                 return 0;
2279         },
2280
2281         getShortName: function() {
2282                 return '%s "%s"'.format(
2283                         this.getActiveModeI18n(),
2284                         this.getActiveSSID() || this.getActiveBSSID() || this.getID());
2285         },
2286
2287         getI18n: function() {
2288                 return '%s: %s "%s" (%s)'.format(
2289                         _('Wireless Network'),
2290                         this.getActiveModeI18n(),
2291                         this.getActiveSSID() || this.getActiveBSSID() || this.getID(),
2292                         this.getIfname());
2293         },
2294
2295         getNetwork: function() {
2296                 return this.getNetworks()[0];
2297         },
2298
2299         getNetworks: function() {
2300                 var networkNames = this.getNetworkNames(),
2301                     networks = [];
2302
2303                 for (var i = 0; i < networkNames.length; i++) {
2304                         var uciInterface = uci.get('network', networkNames[i]);
2305
2306                         if (uciInterface == null || uciInterface['.type'] != 'interface')
2307                                 continue;
2308
2309                         networks.push(L.network.instantiateNetwork(networkNames[i]));
2310                 }
2311
2312                 networks.sort(networkSort);
2313
2314                 return networks;
2315         },
2316
2317         getDevice: function() {
2318                 return L.network.instantiateDevice(this.getIfname());
2319         }
2320 });
2321
2322 return Network;