luci2: split into submodules
[project/luci2/ui.git] / luci2 / htdocs / luci2 / network.js
1 (function() {
2 var network_class = {
3 deviceBlacklist: [
4 /^gre[0-9]+$/,
5 /^gretap[0-9]+$/,
6 /^ifb[0-9]+$/,
7 /^ip6tnl[0-9]+$/,
8 /^sit[0-9]+$/,
9 /^wlan[0-9]+\.sta[0-9]+$/,
10 /^tunl[0-9]+$/,
11 /^ip6gre[0-9]+$/
12 ],
13
14 rpcCacheFunctions: [
15 'protolist', 0, L.rpc.declare({
16 object: 'network',
17 method: 'get_proto_handlers',
18 expect: { '': { } }
19 }),
20 'ifstate', 1, L.rpc.declare({
21 object: 'network.interface',
22 method: 'dump',
23 expect: { 'interface': [ ] }
24 }),
25 'devstate', 2, L.rpc.declare({
26 object: 'network.device',
27 method: 'status',
28 expect: { '': { } }
29 }),
30 'wifistate', 0, L.rpc.declare({
31 object: 'network.wireless',
32 method: 'status',
33 expect: { '': { } }
34 }),
35 'bwstate', 2, L.rpc.declare({
36 object: 'luci2.network.bwmon',
37 method: 'statistics',
38 expect: { 'statistics': { } }
39 }),
40 'devlist', 2, L.rpc.declare({
41 object: 'luci2.network',
42 method: 'device_list',
43 expect: { 'devices': [ ] }
44 }),
45 'swlist', 0, L.rpc.declare({
46 object: 'luci2.network',
47 method: 'switch_list',
48 expect: { 'switches': [ ] }
49 })
50 ],
51
52 loadProtocolHandler: function(proto)
53 {
54 var url = L.globals.resource + '/proto/' + proto + '.js';
55 var self = L.network;
56
57 var def = $.Deferred();
58
59 $.ajax(url, {
60 method: 'GET',
61 cache: true,
62 dataType: 'text'
63 }).then(function(data) {
64 try {
65 var protoConstructorSource = (
66 '(function(L, $) { ' +
67 'return %s' +
68 '})(L, $);\n\n' +
69 '//@ sourceURL=%s/%s'
70 ).format(data, window.location.origin, url);
71
72 var protoClass = eval(protoConstructorSource);
73
74 self.protocolHandlers[proto] = new protoClass();
75 }
76 catch(e) {
77 alert('Unable to instantiate proto "%s": %s'.format(url, e));
78 };
79
80 def.resolve();
81 }).fail(function() {
82 def.resolve();
83 });
84
85 return def;
86 },
87
88 loadProtocolHandlers: function()
89 {
90 var self = L.network;
91 var deferreds = [
92 self.loadProtocolHandler('none')
93 ];
94
95 for (var proto in self.rpcCache.protolist)
96 deferreds.push(self.loadProtocolHandler(proto));
97
98 return $.when.apply($, deferreds);
99 },
100
101 callSwitchInfo: L.rpc.declare({
102 object: 'luci2.network',
103 method: 'switch_info',
104 params: [ 'switch' ],
105 expect: { 'info': { } }
106 }),
107
108 callSwitchInfoCallback: function(responses) {
109 var self = L.network;
110 var swlist = self.rpcCache.swlist;
111 var swstate = self.rpcCache.swstate = { };
112
113 for (var i = 0; i < responses.length; i++)
114 swstate[swlist[i]] = responses[i];
115 },
116
117 loadCacheCallback: function(level)
118 {
119 var self = L.network;
120 var name = '_fetch_cache_cb_' + level;
121
122 return self[name] || (
123 self[name] = function(responses)
124 {
125 for (var i = 0; i < self.rpcCacheFunctions.length; i += 3)
126 if (!level || self.rpcCacheFunctions[i + 1] == level)
127 self.rpcCache[self.rpcCacheFunctions[i]] = responses.shift();
128
129 if (!level)
130 {
131 L.rpc.batch();
132
133 for (var i = 0; i < self.rpcCache.swlist.length; i++)
134 self.callSwitchInfo(self.rpcCache.swlist[i]);
135
136 return L.rpc.flush().then(self.callSwitchInfoCallback);
137 }
138
139 return L.deferrable();
140 }
141 );
142 },
143
144 loadCache: function(level)
145 {
146 var self = L.network;
147
148 return L.uci.load(['network', 'wireless']).then(function() {
149 L.rpc.batch();
150
151 for (var i = 0; i < self.rpcCacheFunctions.length; i += 3)
152 if (!level || self.rpcCacheFunctions[i + 1] == level)
153 self.rpcCacheFunctions[i + 2]();
154
155 return L.rpc.flush().then(self.loadCacheCallback(level || 0));
156 });
157 },
158
159 isBlacklistedDevice: function(dev)
160 {
161 for (var i = 0; i < this.deviceBlacklist.length; i++)
162 if (dev.match(this.deviceBlacklist[i]))
163 return true;
164
165 return false;
166 },
167
168 sortDevicesCallback: function(a, b)
169 {
170 if (a.options.kind < b.options.kind)
171 return -1;
172 else if (a.options.kind > b.options.kind)
173 return 1;
174
175 if (a.options.name < b.options.name)
176 return -1;
177 else if (a.options.name > b.options.name)
178 return 1;
179
180 return 0;
181 },
182
183 getDeviceObject: function(ifname)
184 {
185 var alias = (ifname.charAt(0) == '@');
186 return this.deviceObjects[ifname] || (
187 this.deviceObjects[ifname] = {
188 ifname: ifname,
189 kind: alias ? 'alias' : 'ethernet',
190 type: alias ? 0 : 1,
191 up: false,
192 changed: { }
193 }
194 );
195 },
196
197 getInterfaceObject: function(name)
198 {
199 return this.interfaceObjects[name] || (
200 this.interfaceObjects[name] = {
201 name: name,
202 proto: this.protocolHandlers.none,
203 changed: { }
204 }
205 );
206 },
207
208 loadDevicesCallback: function()
209 {
210 var self = L.network;
211 var wificount = { };
212
213 for (var ifname in self.rpcCache.devstate)
214 {
215 if (self.isBlacklistedDevice(ifname))
216 continue;
217
218 var dev = self.rpcCache.devstate[ifname];
219 var entry = self.getDeviceObject(ifname);
220
221 entry.up = dev.up;
222
223 switch (dev.type)
224 {
225 case 'IP tunnel':
226 entry.kind = 'tunnel';
227 break;
228
229 case 'Bridge':
230 entry.kind = 'bridge';
231 //entry.ports = dev['bridge-members'].sort();
232 break;
233 }
234 }
235
236 for (var i = 0; i < self.rpcCache.devlist.length; i++)
237 {
238 var dev = self.rpcCache.devlist[i];
239
240 if (self.isBlacklistedDevice(dev.device))
241 continue;
242
243 var entry = self.getDeviceObject(dev.device);
244
245 entry.up = dev.is_up;
246 entry.type = dev.type;
247
248 switch (dev.type)
249 {
250 case 1: /* Ethernet */
251 if (dev.is_bridge)
252 entry.kind = 'bridge';
253 else if (dev.is_tuntap)
254 entry.kind = 'tunnel';
255 else if (dev.is_wireless)
256 entry.kind = 'wifi';
257 break;
258
259 case 512: /* PPP */
260 case 768: /* IP-IP Tunnel */
261 case 769: /* IP6-IP6 Tunnel */
262 case 776: /* IPv6-in-IPv4 */
263 case 778: /* GRE over IP */
264 entry.kind = 'tunnel';
265 break;
266 }
267 }
268
269 var net = L.uci.sections('network');
270 for (var i = 0; i < net.length; i++)
271 {
272 var s = net[i];
273 var sid = s['.name'];
274
275 if (s['.type'] == 'device' && s.name)
276 {
277 var entry = self.getDeviceObject(s.name);
278
279 switch (s.type)
280 {
281 case 'macvlan':
282 case 'tunnel':
283 entry.kind = 'tunnel';
284 break;
285 }
286
287 entry.sid = sid;
288 }
289 else if (s['.type'] == 'interface' && !s['.anonymous'] && s.ifname)
290 {
291 var ifnames = L.toArray(s.ifname);
292
293 for (var j = 0; j < ifnames.length; j++)
294 self.getDeviceObject(ifnames[j]);
295
296 if (s['.name'] != 'loopback')
297 {
298 var entry = self.getDeviceObject('@%s'.format(s['.name']));
299
300 entry.type = 0;
301 entry.kind = 'alias';
302 entry.sid = sid;
303 }
304 }
305 else if (s['.type'] == 'switch_vlan' && s.device)
306 {
307 var sw = self.rpcCache.swstate[s.device];
308 var vid = parseInt(s.vid || s.vlan);
309 var ports = L.toArray(s.ports);
310
311 if (!sw || !ports.length || isNaN(vid))
312 continue;
313
314 var ifname = undefined;
315
316 for (var j = 0; j < ports.length; j++)
317 {
318 var port = parseInt(ports[j]);
319 var tag = (ports[j].replace(/[^tu]/g, '') == 't');
320
321 if (port == sw.cpu_port)
322 {
323 // XXX: need a way to map switch to netdev
324 if (tag)
325 ifname = 'eth0.%d'.format(vid);
326 else
327 ifname = 'eth0';
328
329 break;
330 }
331 }
332
333 if (!ifname)
334 continue;
335
336 var entry = self.getDeviceObject(ifname);
337
338 entry.kind = 'vlan';
339 entry.sid = sid;
340 entry.vsw = sw;
341 entry.vid = vid;
342 }
343 }
344
345 var wifi = L.uci.sections('wireless');
346 for (var i = 0, c = 0; i < wifi.length; i++)
347 {
348 var s = wifi[i];
349
350 if (s['.type'] == 'wifi-iface')
351 {
352 var sid = '@wifi-iface[%d]'.format(c++);
353
354 if (!s.device)
355 continue;
356
357 var r = parseInt(s.device.replace(/^[^0-9]+/, ''));
358 var n = wificount[s.device] = (wificount[s.device] || 0) + 1;
359 var id = 'radio%d.network%d'.format(r, n);
360 var ifname = id;
361
362 if (self.rpcCache.wifistate[s.device])
363 {
364 var ifcs = self.rpcCache.wifistate[s.device].interfaces;
365 for (var ifc in ifcs)
366 {
367 if (ifcs[ifc].section == sid && ifcs[ifc].ifname)
368 {
369 ifname = ifcs[ifc].ifname;
370 break;
371 }
372 }
373 }
374
375 var entry = self.getDeviceObject(ifname);
376
377 entry.kind = 'wifi';
378 entry.sid = s['.name'];
379 entry.wid = id;
380 entry.wdev = s.device;
381 entry.wmode = s.mode;
382 entry.wssid = s.ssid;
383 entry.wbssid = s.bssid;
384 }
385 }
386
387 for (var i = 0; i < net.length; i++)
388 {
389 var s = net[i];
390 var sid = s['.name'];
391
392 if (s['.type'] == 'interface' && !s['.anonymous'] && s.type == 'bridge')
393 {
394 var ifnames = L.toArray(s.ifname);
395
396 for (var ifname in self.deviceObjects)
397 {
398 var dev = self.deviceObjects[ifname];
399
400 if (dev.kind != 'wifi')
401 continue;
402
403 var wnets = L.toArray(L.uci.get('wireless', dev.sid, 'network'));
404 if ($.inArray(sid, wnets) > -1)
405 ifnames.push(ifname);
406 }
407
408 entry = self.getDeviceObject('br-%s'.format(s['.name']));
409 entry.type = 1;
410 entry.kind = 'bridge';
411 entry.sid = sid;
412 entry.ports = ifnames.sort();
413 }
414 }
415 },
416
417 loadInterfacesCallback: function()
418 {
419 var self = L.network;
420 var net = L.uci.sections('network');
421
422 for (var i = 0; i < net.length; i++)
423 {
424 var s = net[i];
425 var sid = s['.name'];
426
427 if (s['.type'] == 'interface' && !s['.anonymous'] && s.proto)
428 {
429 var entry = self.getInterfaceObject(s['.name']);
430 var proto = self.protocolHandlers[s.proto] || self.protocolHandlers.none;
431
432 var l3dev = undefined;
433 var l2dev = undefined;
434
435 var ifnames = L.toArray(s.ifname);
436
437 for (var ifname in self.deviceObjects)
438 {
439 var dev = self.deviceObjects[ifname];
440
441 if (dev.kind != 'wifi')
442 continue;
443
444 var wnets = L.toArray(L.uci.get('wireless', dev.sid, 'network'));
445 if ($.inArray(entry.name, wnets) > -1)
446 ifnames.push(ifname);
447 }
448
449 if (proto.virtual)
450 l3dev = '%s-%s'.format(s.proto, entry.name);
451 else if (s.type == 'bridge')
452 l3dev = 'br-%s'.format(entry.name);
453 else
454 l3dev = ifnames[0];
455
456 if (!proto.virtual && s.type == 'bridge')
457 l2dev = 'br-%s'.format(entry.name);
458 else if (!proto.virtual)
459 l2dev = ifnames[0];
460
461 entry.proto = proto;
462 entry.sid = sid;
463 entry.l3dev = l3dev;
464 entry.l2dev = l2dev;
465 }
466 }
467
468 for (var i = 0; i < self.rpcCache.ifstate.length; i++)
469 {
470 var iface = self.rpcCache.ifstate[i];
471 var entry = self.getInterfaceObject(iface['interface']);
472 var proto = self.protocolHandlers[iface.proto] || self.protocolHandlers.none;
473
474 /* this is a virtual interface, either deleted from config but
475 not applied yet or set up from external tools (6rd) */
476 if (!entry.sid)
477 {
478 entry.proto = proto;
479 entry.l2dev = iface.device;
480 entry.l3dev = iface.l3_device;
481 }
482 }
483 },
484
485 load: function()
486 {
487 var self = this;
488
489 if (self.rpcCache)
490 return L.deferrable();
491
492 self.rpcCache = { };
493 self.deviceObjects = { };
494 self.interfaceObjects = { };
495 self.protocolHandlers = { };
496
497 return self.loadCache()
498 .then(self.loadProtocolHandlers)
499 .then(self.loadDevicesCallback)
500 .then(self.loadInterfacesCallback);
501 },
502
503 update: function()
504 {
505 delete this.rpcCache;
506 return this.load();
507 },
508
509 refreshInterfaceStatus: function()
510 {
511 return this.loadCache(1).then(this.loadInterfacesCallback);
512 },
513
514 refreshDeviceStatus: function()
515 {
516 return this.loadCache(2).then(this.loadDevicesCallback);
517 },
518
519 refreshStatus: function()
520 {
521 return this.loadCache(1)
522 .then(this.loadCache(2))
523 .then(this.loadDevicesCallback)
524 .then(this.loadInterfacesCallback);
525 },
526
527 getDevices: function()
528 {
529 var devs = [ ];
530
531 for (var ifname in this.deviceObjects)
532 if (ifname != 'lo')
533 devs.push(new L.network.Device(this.deviceObjects[ifname]));
534
535 return devs.sort(this.sortDevicesCallback);
536 },
537
538 getDeviceByInterface: function(iface)
539 {
540 if (iface instanceof L.network.Interface)
541 iface = iface.name();
542
543 if (this.interfaceObjects[iface])
544 return this.getDevice(this.interfaceObjects[iface].l3dev) ||
545 this.getDevice(this.interfaceObjects[iface].l2dev);
546
547 return undefined;
548 },
549
550 getDevice: function(ifname)
551 {
552 if (this.deviceObjects[ifname])
553 return new L.network.Device(this.deviceObjects[ifname]);
554
555 return undefined;
556 },
557
558 createDevice: function(name)
559 {
560 return new L.network.Device(this.getDeviceObject(name));
561 },
562
563 getInterfaces: function()
564 {
565 var ifaces = [ ];
566
567 for (var name in this.interfaceObjects)
568 if (name != 'loopback')
569 ifaces.push(this.getInterface(name));
570
571 ifaces.sort(function(a, b) {
572 if (a.name() < b.name())
573 return -1;
574 else if (a.name() > b.name())
575 return 1;
576 else
577 return 0;
578 });
579
580 return ifaces;
581 },
582
583 getInterfacesByDevice: function(dev)
584 {
585 var ifaces = [ ];
586
587 if (dev instanceof L.network.Device)
588 dev = dev.name();
589
590 for (var name in this.interfaceObjects)
591 {
592 var iface = this.interfaceObjects[name];
593 if (iface.l2dev == dev || iface.l3dev == dev)
594 ifaces.push(this.getInterface(name));
595 }
596
597 ifaces.sort(function(a, b) {
598 if (a.name() < b.name())
599 return -1;
600 else if (a.name() > b.name())
601 return 1;
602 else
603 return 0;
604 });
605
606 return ifaces;
607 },
608
609 getInterface: function(iface)
610 {
611 if (this.interfaceObjects[iface])
612 return new L.network.Interface(this.interfaceObjects[iface]);
613
614 return undefined;
615 },
616
617 getProtocols: function()
618 {
619 var rv = [ ];
620
621 for (var proto in this.protocolHandlers)
622 {
623 var pr = this.protocolHandlers[proto];
624
625 rv.push({
626 name: proto,
627 description: pr.description,
628 virtual: pr.virtual,
629 tunnel: pr.tunnel
630 });
631 }
632
633 return rv.sort(function(a, b) {
634 if (a.name < b.name)
635 return -1;
636 else if (a.name > b.name)
637 return 1;
638 else
639 return 0;
640 });
641 },
642
643 findWANByAddr: function(ipaddr)
644 {
645 for (var i = 0; i < this.rpcCache.ifstate.length; i++)
646 {
647 var ifstate = this.rpcCache.ifstate[i];
648
649 if (!ifstate.route)
650 continue;
651
652 for (var j = 0; j < ifstate.route.length; j++)
653 if (ifstate.route[j].mask == 0 &&
654 ifstate.route[j].target == ipaddr &&
655 typeof(ifstate.route[j].table) == 'undefined')
656 {
657 return this.getInterface(ifstate['interface']);
658 }
659 }
660
661 return undefined;
662 },
663
664 findWAN: function()
665 {
666 return this.findWANByAddr('0.0.0.0');
667 },
668
669 findWAN6: function()
670 {
671 return this.findWANByAddr('::');
672 },
673
674 resolveAlias: function(ifname)
675 {
676 if (ifname instanceof L.network.Device)
677 ifname = ifname.name();
678
679 var dev = this.deviceObjects[ifname];
680 var seen = { };
681
682 while (dev && dev.kind == 'alias')
683 {
684 // loop
685 if (seen[dev.ifname])
686 return undefined;
687
688 var ifc = this.interfaceObjects[dev.sid];
689
690 seen[dev.ifname] = true;
691 dev = ifc ? this.deviceObjects[ifc.l3dev] : undefined;
692 }
693
694 return dev ? this.getDevice(dev.ifname) : undefined;
695 }
696 };
697
698 network_class.Interface = Class.extend({
699 getStatus: function(key)
700 {
701 var s = L.network.rpcCache.ifstate;
702
703 for (var i = 0; i < s.length; i++)
704 if (s[i]['interface'] == this.options.name)
705 return key ? s[i][key] : s[i];
706
707 return undefined;
708 },
709
710 get: function(key)
711 {
712 return L.uci.get('network', this.options.name, key);
713 },
714
715 set: function(key, val)
716 {
717 return L.uci.set('network', this.options.name, key, val);
718 },
719
720 name: function()
721 {
722 return this.options.name;
723 },
724
725 protocol: function()
726 {
727 return (this.get('proto') || 'none');
728 },
729
730 isUp: function()
731 {
732 return (this.getStatus('up') === true);
733 },
734
735 isVirtual: function()
736 {
737 return (typeof(this.options.sid) != 'string');
738 },
739
740 getProtocol: function()
741 {
742 var prname = this.get('proto') || 'none';
743 return L.network.protocolHandlers[prname] || L.network.protocolHandlers.none;
744 },
745
746 getUptime: function()
747 {
748 var uptime = this.getStatus('uptime');
749 return isNaN(uptime) ? 0 : uptime;
750 },
751
752 getDevice: function(resolveAlias)
753 {
754 if (this.options.l3dev)
755 return L.network.getDevice(this.options.l3dev);
756
757 return undefined;
758 },
759
760 getPhysdev: function()
761 {
762 if (this.options.l2dev)
763 return L.network.getDevice(this.options.l2dev);
764
765 return undefined;
766 },
767
768 getSubdevices: function()
769 {
770 var rv = [ ];
771 var dev = this.options.l2dev ?
772 L.network.deviceObjects[this.options.l2dev] : undefined;
773
774 if (dev && dev.kind == 'bridge' && dev.ports && dev.ports.length)
775 for (var i = 0; i < dev.ports.length; i++)
776 rv.push(L.network.getDevice(dev.ports[i]));
777
778 return rv;
779 },
780
781 getIPv4Addrs: function(mask)
782 {
783 var rv = [ ];
784 var addrs = this.getStatus('ipv4-address');
785
786 if (addrs)
787 for (var i = 0; i < addrs.length; i++)
788 if (!mask)
789 rv.push(addrs[i].address);
790 else
791 rv.push('%s/%d'.format(addrs[i].address, addrs[i].mask));
792
793 return rv;
794 },
795
796 getIPv6Addrs: function(mask)
797 {
798 var rv = [ ];
799 var addrs;
800
801 addrs = this.getStatus('ipv6-address');
802
803 if (addrs)
804 for (var i = 0; i < addrs.length; i++)
805 if (!mask)
806 rv.push(addrs[i].address);
807 else
808 rv.push('%s/%d'.format(addrs[i].address, addrs[i].mask));
809
810 addrs = this.getStatus('ipv6-prefix-assignment');
811
812 if (addrs)
813 for (var i = 0; i < addrs.length; i++)
814 if (!mask)
815 rv.push('%s1'.format(addrs[i].address));
816 else
817 rv.push('%s1/%d'.format(addrs[i].address, addrs[i].mask));
818
819 return rv;
820 },
821
822 getDNSAddrs: function()
823 {
824 var rv = [ ];
825 var addrs = this.getStatus('dns-server');
826
827 if (addrs)
828 for (var i = 0; i < addrs.length; i++)
829 rv.push(addrs[i]);
830
831 return rv;
832 },
833
834 getIPv4DNS: function()
835 {
836 var rv = [ ];
837 var dns = this.getStatus('dns-server');
838
839 if (dns)
840 for (var i = 0; i < dns.length; i++)
841 if (dns[i].indexOf(':') == -1)
842 rv.push(dns[i]);
843
844 return rv;
845 },
846
847 getIPv6DNS: function()
848 {
849 var rv = [ ];
850 var dns = this.getStatus('dns-server');
851
852 if (dns)
853 for (var i = 0; i < dns.length; i++)
854 if (dns[i].indexOf(':') > -1)
855 rv.push(dns[i]);
856
857 return rv;
858 },
859
860 getIPv4Gateway: function()
861 {
862 var rt = this.getStatus('route');
863
864 if (rt)
865 for (var i = 0; i < rt.length; i++)
866 if (rt[i].target == '0.0.0.0' && rt[i].mask == 0)
867 return rt[i].nexthop;
868
869 return undefined;
870 },
871
872 getIPv6Gateway: function()
873 {
874 var rt = this.getStatus('route');
875
876 if (rt)
877 for (var i = 0; i < rt.length; i++)
878 if (rt[i].target == '::' && rt[i].mask == 0)
879 return rt[i].nexthop;
880
881 return undefined;
882 },
883
884 getStatistics: function()
885 {
886 var dev = this.getDevice() || new L.network.Device({});
887 return dev.getStatistics();
888 },
889
890 getTrafficHistory: function()
891 {
892 var dev = this.getDevice() || new L.network.Device({});
893 return dev.getTrafficHistory();
894 },
895
896 renderBadge: function()
897 {
898 var badge = $('<span />')
899 .addClass('badge')
900 .text('%s: '.format(this.name()));
901
902 var dev = this.getDevice();
903 var subdevs = this.getSubdevices();
904
905 if (subdevs.length)
906 for (var j = 0; j < subdevs.length; j++)
907 badge.append($('<img />')
908 .attr('src', subdevs[j].icon())
909 .attr('title', '%s (%s)'.format(subdevs[j].description(), subdevs[j].name() || '?')));
910 else if (dev)
911 badge.append($('<img />')
912 .attr('src', dev.icon())
913 .attr('title', '%s (%s)'.format(dev.description(), dev.name() || '?')));
914 else
915 badge.append($('<em />').text(L.tr('(No devices attached)')));
916
917 return badge;
918 },
919
920 setDevices: function(devs)
921 {
922 var dev = this.getPhysdev();
923 var old_devs = [ ];
924 var changed = false;
925
926 if (dev && dev.isBridge())
927 old_devs = this.getSubdevices();
928 else if (dev)
929 old_devs = [ dev ];
930
931 if (old_devs.length != devs.length)
932 changed = true;
933 else
934 for (var i = 0; i < old_devs.length; i++)
935 {
936 var dev = devs[i];
937
938 if (dev instanceof L.network.Device)
939 dev = dev.name();
940
941 if (!dev || old_devs[i].name() != dev)
942 {
943 changed = true;
944 break;
945 }
946 }
947
948 if (changed)
949 {
950 for (var i = 0; i < old_devs.length; i++)
951 old_devs[i].removeFromInterface(this);
952
953 for (var i = 0; i < devs.length; i++)
954 {
955 var dev = devs[i];
956
957 if (!(dev instanceof L.network.Device))
958 dev = L.network.getDevice(dev);
959
960 if (dev)
961 dev.attachToInterface(this);
962 }
963 }
964 },
965
966 changeProtocol: function(proto)
967 {
968 var pr = L.network.protocolHandlers[proto];
969
970 if (!pr)
971 return;
972
973 for (var opt in (this.get() || { }))
974 {
975 switch (opt)
976 {
977 case 'type':
978 case 'ifname':
979 case 'macaddr':
980 if (pr.virtual)
981 this.set(opt, undefined);
982 break;
983
984 case 'auto':
985 case 'mtu':
986 break;
987
988 case 'proto':
989 this.set(opt, pr.protocol);
990 break;
991
992 default:
993 this.set(opt, undefined);
994 break;
995 }
996 }
997 },
998
999 createFormPrepareCallback: function()
1000 {
1001 var map = this;
1002 var iface = map.options.netIface;
1003 var proto = iface.getProtocol();
1004 var device = iface.getDevice();
1005
1006 map.options.caption = L.tr('Configure "%s"').format(iface.name());
1007
1008 var section = map.section(L.cbi.SingleSection, iface.name(), {
1009 anonymous: true
1010 });
1011
1012 section.tab({
1013 id: 'general',
1014 caption: L.tr('General Settings')
1015 });
1016
1017 section.tab({
1018 id: 'advanced',
1019 caption: L.tr('Advanced Settings')
1020 });
1021
1022 section.tab({
1023 id: 'ipv6',
1024 caption: L.tr('IPv6')
1025 });
1026
1027 section.tab({
1028 id: 'physical',
1029 caption: L.tr('Physical Settings')
1030 });
1031
1032
1033 section.taboption('general', L.cbi.CheckboxValue, 'auto', {
1034 caption: L.tr('Start on boot'),
1035 optional: true,
1036 initial: true
1037 });
1038
1039 var pr = section.taboption('general', L.cbi.ListValue, 'proto', {
1040 caption: L.tr('Protocol')
1041 });
1042
1043 pr.ucivalue = function(sid) {
1044 return iface.get('proto') || 'none';
1045 };
1046
1047 var ok = section.taboption('general', L.cbi.ButtonValue, '_confirm', {
1048 caption: L.tr('Really switch?'),
1049 description: L.tr('Changing the protocol will clear all configuration for this interface!'),
1050 text: L.tr('Change protocol')
1051 });
1052
1053 ok.on('click', function(ev) {
1054 iface.changeProtocol(pr.formvalue(ev.data.sid));
1055 iface.createForm(mapwidget).show();
1056 });
1057
1058 var protos = L.network.getProtocols();
1059
1060 for (var i = 0; i < protos.length; i++)
1061 pr.value(protos[i].name, protos[i].description);
1062
1063 proto.populateForm(section, iface);
1064
1065 if (!proto.virtual)
1066 {
1067 var br = section.taboption('physical', L.cbi.CheckboxValue, 'type', {
1068 caption: L.tr('Network bridge'),
1069 description: L.tr('Merges multiple devices into one logical bridge'),
1070 optional: true,
1071 enabled: 'bridge',
1072 disabled: '',
1073 initial: ''
1074 });
1075
1076 section.taboption('physical', L.cbi.DeviceList, '__iface_multi', {
1077 caption: L.tr('Devices'),
1078 multiple: true,
1079 bridges: false
1080 }).depends('type', true);
1081
1082 section.taboption('physical', L.cbi.DeviceList, '__iface_single', {
1083 caption: L.tr('Device'),
1084 multiple: false,
1085 bridges: true
1086 }).depends('type', false);
1087
1088 var mac = section.taboption('physical', L.cbi.InputValue, 'macaddr', {
1089 caption: L.tr('Override MAC'),
1090 optional: true,
1091 placeholder: device ? device.getMACAddress() : undefined,
1092 datatype: 'macaddr'
1093 })
1094
1095 mac.ucivalue = function(sid)
1096 {
1097 if (device)
1098 return device.get('macaddr');
1099
1100 return this.callSuper('ucivalue', sid);
1101 };
1102
1103 mac.save = function(sid)
1104 {
1105 if (!this.changed(sid))
1106 return false;
1107
1108 if (device)
1109 device.set('macaddr', this.formvalue(sid));
1110 else
1111 this.callSuper('set', sid);
1112
1113 return true;
1114 };
1115 }
1116
1117 section.taboption('physical', L.cbi.InputValue, 'mtu', {
1118 caption: L.tr('Override MTU'),
1119 optional: true,
1120 placeholder: device ? device.getMTU() : undefined,
1121 datatype: 'range(1, 9000)'
1122 });
1123
1124 section.taboption('physical', L.cbi.InputValue, 'metric', {
1125 caption: L.tr('Override Metric'),
1126 optional: true,
1127 placeholder: 0,
1128 datatype: 'uinteger'
1129 });
1130
1131 for (var field in section.fields)
1132 {
1133 switch (field)
1134 {
1135 case 'proto':
1136 break;
1137
1138 case '_confirm':
1139 for (var i = 0; i < protos.length; i++)
1140 if (protos[i].name != proto.protocol)
1141 section.fields[field].depends('proto', protos[i].name);
1142 break;
1143
1144 default:
1145 section.fields[field].depends('proto', proto.protocol, true);
1146 break;
1147 }
1148 }
1149 },
1150
1151 createForm: function(mapwidget)
1152 {
1153 var self = this;
1154
1155 if (!mapwidget)
1156 mapwidget = L.cbi.Map;
1157
1158 var map = new mapwidget('network', {
1159 prepare: self.createFormPrepareCallback,
1160 netIface: self
1161 });
1162
1163 return map;
1164 }
1165 });
1166
1167 network_class.Device = Class.extend({
1168 wifiModeStrings: {
1169 ap: L.tr('Master'),
1170 sta: L.tr('Client'),
1171 adhoc: L.tr('Ad-Hoc'),
1172 monitor: L.tr('Monitor'),
1173 wds: L.tr('Static WDS')
1174 },
1175
1176 getStatus: function(key)
1177 {
1178 var s = L.network.rpcCache.devstate[this.options.ifname];
1179
1180 if (s)
1181 return key ? s[key] : s;
1182
1183 return undefined;
1184 },
1185
1186 get: function(key)
1187 {
1188 var sid = this.options.sid;
1189 var pkg = (this.options.kind == 'wifi') ? 'wireless' : 'network';
1190 return L.uci.get(pkg, sid, key);
1191 },
1192
1193 set: function(key, val)
1194 {
1195 var sid = this.options.sid;
1196 var pkg = (this.options.kind == 'wifi') ? 'wireless' : 'network';
1197 return L.uci.set(pkg, sid, key, val);
1198 },
1199
1200 init: function()
1201 {
1202 if (typeof(this.options.type) == 'undefined')
1203 this.options.type = 1;
1204
1205 if (typeof(this.options.kind) == 'undefined')
1206 this.options.kind = 'ethernet';
1207
1208 if (typeof(this.options.networks) == 'undefined')
1209 this.options.networks = [ ];
1210 },
1211
1212 name: function()
1213 {
1214 return this.options.ifname;
1215 },
1216
1217 description: function()
1218 {
1219 switch (this.options.kind)
1220 {
1221 case 'alias':
1222 return L.tr('Alias for network "%s"').format(this.options.ifname.substring(1));
1223
1224 case 'bridge':
1225 return L.tr('Network bridge');
1226
1227 case 'ethernet':
1228 return L.tr('Network device');
1229
1230 case 'tunnel':
1231 switch (this.options.type)
1232 {
1233 case 1: /* tuntap */
1234 return L.tr('TAP device');
1235
1236 case 512: /* PPP */
1237 return L.tr('PPP tunnel');
1238
1239 case 768: /* IP-IP Tunnel */
1240 return L.tr('IP-in-IP tunnel');
1241
1242 case 769: /* IP6-IP6 Tunnel */
1243 return L.tr('IPv6-in-IPv6 tunnel');
1244
1245 case 776: /* IPv6-in-IPv4 */
1246 return L.tr('IPv6-over-IPv4 tunnel');
1247 break;
1248
1249 case 778: /* GRE over IP */
1250 return L.tr('GRE-over-IP tunnel');
1251
1252 default:
1253 return L.tr('Tunnel device');
1254 }
1255
1256 case 'vlan':
1257 return L.tr('VLAN %d on %s').format(this.options.vid, this.options.vsw.model);
1258
1259 case 'wifi':
1260 var o = this.options;
1261 return L.trc('(Wifi-Mode) "(SSID)" on (radioX)', '%s "%h" on %s').format(
1262 o.wmode ? this.wifiModeStrings[o.wmode] : L.tr('Unknown mode'),
1263 o.wssid || '?', o.wdev
1264 );
1265 }
1266
1267 return L.tr('Unknown device');
1268 },
1269
1270 icon: function(up)
1271 {
1272 var kind = this.options.kind;
1273
1274 if (kind == 'alias')
1275 kind = 'ethernet';
1276
1277 if (typeof(up) == 'undefined')
1278 up = this.isUp();
1279
1280 return L.globals.resource + '/icons/%s%s.png'.format(kind, up ? '' : '_disabled');
1281 },
1282
1283 isUp: function()
1284 {
1285 var l = L.network.rpcCache.devlist;
1286
1287 for (var i = 0; i < l.length; i++)
1288 if (l[i].device == this.options.ifname)
1289 return (l[i].is_up === true);
1290
1291 return false;
1292 },
1293
1294 isAlias: function()
1295 {
1296 return (this.options.kind == 'alias');
1297 },
1298
1299 isBridge: function()
1300 {
1301 return (this.options.kind == 'bridge');
1302 },
1303
1304 isBridgeable: function()
1305 {
1306 return (this.options.type == 1 && this.options.kind != 'bridge');
1307 },
1308
1309 isWireless: function()
1310 {
1311 return (this.options.kind == 'wifi');
1312 },
1313
1314 isInNetwork: function(net)
1315 {
1316 if (!(net instanceof L.network.Interface))
1317 net = L.network.getInterface(net);
1318
1319 if (net)
1320 {
1321 if (net.options.l3dev == this.options.ifname ||
1322 net.options.l2dev == this.options.ifname)
1323 return true;
1324
1325 var dev = L.network.deviceObjects[net.options.l2dev];
1326 if (dev && dev.kind == 'bridge' && dev.ports)
1327 return ($.inArray(this.options.ifname, dev.ports) > -1);
1328 }
1329
1330 return false;
1331 },
1332
1333 getMTU: function()
1334 {
1335 var dev = L.network.rpcCache.devstate[this.options.ifname];
1336 if (dev && !isNaN(dev.mtu))
1337 return dev.mtu;
1338
1339 return undefined;
1340 },
1341
1342 getMACAddress: function()
1343 {
1344 if (this.options.type != 1)
1345 return undefined;
1346
1347 var dev = L.network.rpcCache.devstate[this.options.ifname];
1348 if (dev && dev.macaddr)
1349 return dev.macaddr.toUpperCase();
1350
1351 return undefined;
1352 },
1353
1354 getInterfaces: function()
1355 {
1356 return L.network.getInterfacesByDevice(this.options.name);
1357 },
1358
1359 getStatistics: function()
1360 {
1361 var s = this.getStatus('statistics') || { };
1362 return {
1363 rx_bytes: (s.rx_bytes || 0),
1364 tx_bytes: (s.tx_bytes || 0),
1365 rx_packets: (s.rx_packets || 0),
1366 tx_packets: (s.tx_packets || 0)
1367 };
1368 },
1369
1370 getTrafficHistory: function()
1371 {
1372 var def = new Array(120);
1373
1374 for (var i = 0; i < 120; i++)
1375 def[i] = 0;
1376
1377 var h = L.network.rpcCache.bwstate[this.options.ifname] || { };
1378 return {
1379 rx_bytes: (h.rx_bytes || def),
1380 tx_bytes: (h.tx_bytes || def),
1381 rx_packets: (h.rx_packets || def),
1382 tx_packets: (h.tx_packets || def)
1383 };
1384 },
1385
1386 removeFromInterface: function(iface)
1387 {
1388 if (!(iface instanceof L.network.Interface))
1389 iface = L.network.getInterface(iface);
1390
1391 if (!iface)
1392 return;
1393
1394 var ifnames = L.toArray(iface.get('ifname'));
1395 if ($.inArray(this.options.ifname, ifnames) > -1)
1396 iface.set('ifname', L.filterArray(ifnames, this.options.ifname));
1397
1398 if (this.options.kind != 'wifi')
1399 return;
1400
1401 var networks = L.toArray(this.get('network'));
1402 if ($.inArray(iface.name(), networks) > -1)
1403 this.set('network', L.filterArray(networks, iface.name()));
1404 },
1405
1406 attachToInterface: function(iface)
1407 {
1408 if (!(iface instanceof L.network.Interface))
1409 iface = L.network.getInterface(iface);
1410
1411 if (!iface)
1412 return;
1413
1414 if (this.options.kind != 'wifi')
1415 {
1416 var ifnames = L.toArray(iface.get('ifname'));
1417 if ($.inArray(this.options.ifname, ifnames) < 0)
1418 {
1419 ifnames.push(this.options.ifname);
1420 iface.set('ifname', (ifnames.length > 1) ? ifnames : ifnames[0]);
1421 }
1422 }
1423 else
1424 {
1425 var networks = L.toArray(this.get('network'));
1426 if ($.inArray(iface.name(), networks) < 0)
1427 {
1428 networks.push(iface.name());
1429 this.set('network', (networks.length > 1) ? networks : networks[0]);
1430 }
1431 }
1432 }
1433 });
1434
1435 network_class.Protocol = network_class.Interface.extend({
1436 description: '__unknown__',
1437 tunnel: false,
1438 virtual: false,
1439
1440 populateForm: function(section, iface)
1441 {
1442
1443 }
1444 });
1445
1446 return Class.extend(network_class);
1447 })();