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 })();