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