luci2: fix undefined ifname exception when processing wireless status with disabled...
[project/luci2/ui.git] / luci2 / htdocs / luci2 / luci2.js
1 /*
2         LuCI2 - OpenWrt Web Interface
3
4         Copyright 2013-2014 Jo-Philipp Wich <jow@openwrt.org>
5
6         Licensed under the Apache License, Version 2.0 (the "License");
7         you may not use this file except in compliance with the License.
8         You may obtain a copy of the License at
9
10                 http://www.apache.org/licenses/LICENSE-2.0
11 */
12
13 String.prototype.format = function()
14 {
15         var html_esc = [/&/g, '&#38;', /"/g, '&#34;', /'/g, '&#39;', /</g, '&#60;', />/g, '&#62;'];
16         var quot_esc = [/"/g, '&#34;', /'/g, '&#39;'];
17
18         function esc(s, r) {
19                 for( var i = 0; i < r.length; i += 2 )
20                         s = s.replace(r[i], r[i+1]);
21                 return s;
22         }
23
24         var str = this;
25         var out = '';
26         var re = /^(([^%]*)%('.|0|\x20)?(-)?(\d+)?(\.\d+)?(%|b|c|d|u|f|o|s|x|X|q|h|j|t|m))/;
27         var a = b = [], numSubstitutions = 0, numMatches = 0;
28
29         while ((a = re.exec(str)) != null)
30         {
31                 var m = a[1];
32                 var leftpart = a[2], pPad = a[3], pJustify = a[4], pMinLength = a[5];
33                 var pPrecision = a[6], pType = a[7];
34
35                 numMatches++;
36
37                 if (pType == '%')
38                 {
39                         subst = '%';
40                 }
41                 else
42                 {
43                         if (numSubstitutions < arguments.length)
44                         {
45                                 var param = arguments[numSubstitutions++];
46
47                                 var pad = '';
48                                 if (pPad && pPad.substr(0,1) == "'")
49                                         pad = leftpart.substr(1,1);
50                                 else if (pPad)
51                                         pad = pPad;
52
53                                 var justifyRight = true;
54                                 if (pJustify && pJustify === "-")
55                                         justifyRight = false;
56
57                                 var minLength = -1;
58                                 if (pMinLength)
59                                         minLength = parseInt(pMinLength);
60
61                                 var precision = -1;
62                                 if (pPrecision && pType == 'f')
63                                         precision = parseInt(pPrecision.substring(1));
64
65                                 var subst = param;
66
67                                 switch(pType)
68                                 {
69                                         case 'b':
70                                                 subst = (parseInt(param) || 0).toString(2);
71                                                 break;
72
73                                         case 'c':
74                                                 subst = String.fromCharCode(parseInt(param) || 0);
75                                                 break;
76
77                                         case 'd':
78                                                 subst = (parseInt(param) || 0);
79                                                 break;
80
81                                         case 'u':
82                                                 subst = Math.abs(parseInt(param) || 0);
83                                                 break;
84
85                                         case 'f':
86                                                 subst = (precision > -1)
87                                                         ? ((parseFloat(param) || 0.0)).toFixed(precision)
88                                                         : (parseFloat(param) || 0.0);
89                                                 break;
90
91                                         case 'o':
92                                                 subst = (parseInt(param) || 0).toString(8);
93                                                 break;
94
95                                         case 's':
96                                                 subst = param;
97                                                 break;
98
99                                         case 'x':
100                                                 subst = ('' + (parseInt(param) || 0).toString(16)).toLowerCase();
101                                                 break;
102
103                                         case 'X':
104                                                 subst = ('' + (parseInt(param) || 0).toString(16)).toUpperCase();
105                                                 break;
106
107                                         case 'h':
108                                                 subst = esc(param, html_esc);
109                                                 break;
110
111                                         case 'q':
112                                                 subst = esc(param, quot_esc);
113                                                 break;
114
115                                         case 'j':
116                                                 subst = String.serialize(param);
117                                                 break;
118
119                                         case 't':
120                                                 var td = 0;
121                                                 var th = 0;
122                                                 var tm = 0;
123                                                 var ts = (param || 0);
124
125                                                 if (ts > 60) {
126                                                         tm = Math.floor(ts / 60);
127                                                         ts = (ts % 60);
128                                                 }
129
130                                                 if (tm > 60) {
131                                                         th = Math.floor(tm / 60);
132                                                         tm = (tm % 60);
133                                                 }
134
135                                                 if (th > 24) {
136                                                         td = Math.floor(th / 24);
137                                                         th = (th % 24);
138                                                 }
139
140                                                 subst = (td > 0)
141                                                         ? '%dd %dh %dm %ds'.format(td, th, tm, ts)
142                                                         : '%dh %dm %ds'.format(th, tm, ts);
143
144                                                 break;
145
146                                         case 'm':
147                                                 var mf = pMinLength ? parseInt(pMinLength) : 1000;
148                                                 var pr = pPrecision ? Math.floor(10*parseFloat('0'+pPrecision)) : 2;
149
150                                                 var i = 0;
151                                                 var val = parseFloat(param || 0);
152                                                 var units = [ '', 'K', 'M', 'G', 'T', 'P', 'E' ];
153
154                                                 for (i = 0; (i < units.length) && (val > mf); i++)
155                                                         val /= mf;
156
157                                                 subst = val.toFixed(pr) + ' ' + units[i];
158                                                 break;
159                                 }
160
161                                 subst = (typeof(subst) == 'undefined') ? '' : subst.toString();
162
163                                 if (minLength > 0 && pad.length > 0)
164                                         for (var i = 0; i < (minLength - subst.length); i++)
165                                                 subst = justifyRight ? (pad + subst) : (subst + pad);
166                         }
167                 }
168
169                 out += leftpart + subst;
170                 str = str.substr(m.length);
171         }
172
173         return out + str;
174 }
175
176 function LuCI2()
177 {
178         var L = this;
179
180         var Class = function() { };
181
182         Class.extend = function(properties)
183         {
184                 Class.initializing = true;
185
186                 var prototype = new this();
187                 var superprot = this.prototype;
188
189                 Class.initializing = false;
190
191                 $.extend(prototype, properties, {
192                         callSuper: function() {
193                                 var args = [ ];
194                                 var meth = arguments[0];
195
196                                 if (typeof(superprot[meth]) != 'function')
197                                         return undefined;
198
199                                 for (var i = 1; i < arguments.length; i++)
200                                         args.push(arguments[i]);
201
202                                 return superprot[meth].apply(this, args);
203                         }
204                 });
205
206                 function _class()
207                 {
208                         this.options = arguments[0] || { };
209
210                         if (!Class.initializing && typeof(this.init) == 'function')
211                                 this.init.apply(this, arguments);
212                 }
213
214                 _class.prototype = prototype;
215                 _class.prototype.constructor = _class;
216
217                 _class.extend = Class.extend;
218
219                 return _class;
220         };
221
222         this.defaults = function(obj, def)
223         {
224                 for (var key in def)
225                         if (typeof(obj[key]) == 'undefined')
226                                 obj[key] = def[key];
227
228                 return obj;
229         };
230
231         this.isDeferred = function(x)
232         {
233                 return (typeof(x) == 'object' &&
234                         typeof(x.then) == 'function' &&
235                         typeof(x.promise) == 'function');
236         };
237
238         this.deferrable = function()
239         {
240                 if (this.isDeferred(arguments[0]))
241                         return arguments[0];
242
243                 var d = $.Deferred();
244                     d.resolve.apply(d, arguments);
245
246                 return d.promise();
247         };
248
249         this.i18n = {
250
251                 loaded: false,
252                 catalog: { },
253                 plural:  function(n) { return 0 + (n != 1) },
254
255                 init: function() {
256                         if (L.i18n.loaded)
257                                 return;
258
259                         var lang = (navigator.userLanguage || navigator.language || 'en').toLowerCase();
260                         var langs = (lang.indexOf('-') > -1) ? [ lang, lang.split(/-/)[0] ] : [ lang ];
261
262                         for (var i = 0; i < langs.length; i++)
263                                 $.ajax('%s/i18n/base.%s.json'.format(L.globals.resource, langs[i]), {
264                                         async:    false,
265                                         cache:    true,
266                                         dataType: 'json',
267                                         success:  function(data) {
268                                                 $.extend(L.i18n.catalog, data);
269
270                                                 var pe = L.i18n.catalog[''];
271                                                 if (pe)
272                                                 {
273                                                         delete L.i18n.catalog[''];
274                                                         try {
275                                                                 var pf = new Function('n', 'return 0 + (' + pe + ')');
276                                                                 L.i18n.plural = pf;
277                                                         } catch (e) { };
278                                                 }
279                                         }
280                                 });
281
282                         L.i18n.loaded = true;
283                 }
284
285         };
286
287         this.tr = function(msgid)
288         {
289                 L.i18n.init();
290
291                 var msgstr = L.i18n.catalog[msgid];
292
293                 if (typeof(msgstr) == 'undefined')
294                         return msgid;
295                 else if (typeof(msgstr) == 'string')
296                         return msgstr;
297                 else
298                         return msgstr[0];
299         };
300
301         this.trp = function(msgid, msgid_plural, count)
302         {
303                 L.i18n.init();
304
305                 var msgstr = L.i18n.catalog[msgid];
306
307                 if (typeof(msgstr) == 'undefined')
308                         return (count == 1) ? msgid : msgid_plural;
309                 else if (typeof(msgstr) == 'string')
310                         return msgstr;
311                 else
312                         return msgstr[L.i18n.plural(count)];
313         };
314
315         this.trc = function(msgctx, msgid)
316         {
317                 L.i18n.init();
318
319                 var msgstr = L.i18n.catalog[msgid + '\u0004' + msgctx];
320
321                 if (typeof(msgstr) == 'undefined')
322                         return msgid;
323                 else if (typeof(msgstr) == 'string')
324                         return msgstr;
325                 else
326                         return msgstr[0];
327         };
328
329         this.trcp = function(msgctx, msgid, msgid_plural, count)
330         {
331                 L.i18n.init();
332
333                 var msgstr = L.i18n.catalog[msgid + '\u0004' + msgctx];
334
335                 if (typeof(msgstr) == 'undefined')
336                         return (count == 1) ? msgid : msgid_plural;
337                 else if (typeof(msgstr) == 'string')
338                         return msgstr;
339                 else
340                         return msgstr[L.i18n.plural(count)];
341         };
342
343         this.setHash = function(key, value)
344         {
345                 var h = '';
346                 var data = this.getHash(undefined);
347
348                 if (typeof(value) == 'undefined')
349                         delete data[key];
350                 else
351                         data[key] = value;
352
353                 var keys = [ ];
354                 for (var k in data)
355                         keys.push(k);
356
357                 keys.sort();
358
359                 for (var i = 0; i < keys.length; i++)
360                 {
361                         if (i > 0)
362                                 h += ',';
363
364                         h += keys[i] + ':' + data[keys[i]];
365                 }
366
367                 if (h.length)
368                         location.hash = '#' + h;
369                 else
370                         location.hash = '';
371         };
372
373         this.getHash = function(key)
374         {
375                 var data = { };
376                 var tuples = (location.hash || '#').substring(1).split(/,/);
377
378                 for (var i = 0; i < tuples.length; i++)
379                 {
380                         var tuple = tuples[i].split(/:/);
381                         if (tuple.length == 2)
382                                 data[tuple[0]] = tuple[1];
383                 }
384
385                 if (typeof(key) != 'undefined')
386                         return data[key];
387
388                 return data;
389         };
390
391         this.toArray = function(x)
392         {
393                 switch (typeof(x))
394                 {
395                 case 'number':
396                 case 'boolean':
397                         return [ x ];
398
399                 case 'string':
400                         var r = [ ];
401                         var l = x.split(/\s+/);
402                         for (var i = 0; i < l.length; i++)
403                                 if (l[i].length > 0)
404                                         r.push(l[i]);
405                         return r;
406
407                 case 'object':
408                         if ($.isArray(x))
409                         {
410                                 var r = [ ];
411                                 for (var i = 0; i < x.length; i++)
412                                         r.push(x[i]);
413                                 return r;
414                         }
415                         else if ($.isPlainObject(x))
416                         {
417                                 var r = [ ];
418                                 for (var k in x)
419                                         if (x.hasOwnProperty(k))
420                                                 r.push(k);
421                                 return r.sort();
422                         }
423                 }
424
425                 return [ ];
426         };
427
428         this.toObject = function(x)
429         {
430                 switch (typeof(x))
431                 {
432                 case 'number':
433                 case 'boolean':
434                         return { x: true };
435
436                 case 'string':
437                         var r = { };
438                         var l = x.split(/\x+/);
439                         for (var i = 0; i < l.length; i++)
440                                 if (l[i].length > 0)
441                                         r[l[i]] = true;
442                         return r;
443
444                 case 'object':
445                         if ($.isArray(x))
446                         {
447                                 var r = { };
448                                 for (var i = 0; i < x.length; i++)
449                                         r[x[i]] = true;
450                                 return r;
451                         }
452                         else if ($.isPlainObject(x))
453                         {
454                                 return x;
455                         }
456                 }
457
458                 return { };
459         };
460
461         this.filterArray = function(array, item)
462         {
463                 if (!$.isArray(array))
464                         return [ ];
465
466                 for (var i = 0; i < array.length; i++)
467                         if (array[i] === item)
468                                 array.splice(i--, 1);
469
470                 return array;
471         };
472
473         this.toClassName = function(str, suffix)
474         {
475                 var n = '';
476                 var l = str.split(/[\/.]/);
477
478                 for (var i = 0; i < l.length; i++)
479                         if (l[i].length > 0)
480                                 n += l[i].charAt(0).toUpperCase() + l[i].substr(1).toLowerCase();
481
482                 if (typeof(suffix) == 'string')
483                         n += suffix;
484
485                 return n;
486         };
487
488         this.toColor = function(str)
489         {
490                 if (typeof(str) != 'string' || str.length == 0)
491                         return '#CCCCCC';
492
493                 if (str == 'wan')
494                         return '#F09090';
495                 else if (str == 'lan')
496                         return '#90F090';
497
498                 var i = 0, hash = 0;
499
500                 while (i < str.length)
501                         hash = str.charCodeAt(i++) + ((hash << 5) - hash);
502
503                 var r = (hash & 0xFF) % 128;
504                 var g = ((hash >> 8) & 0xFF) % 128;
505
506                 var min = 0;
507                 var max = 128;
508
509                 if ((r + g) < 128)
510                         min = 128 - r - g;
511                 else
512                         max = 255 - r - g;
513
514                 var b = min + (((hash >> 16) & 0xFF) % (max - min));
515
516                 return '#%02X%02X%02X'.format(0xFF - r, 0xFF - g, 0xFF - b);
517         };
518
519         this.parseIPv4 = function(str)
520         {
521                 if ((typeof(str) != 'string' && !(str instanceof String)) ||
522                     !str.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/))
523                         return undefined;
524
525                 var num = [ ];
526                 var parts = str.split(/\./);
527
528                 for (var i = 0; i < parts.length; i++)
529                 {
530                         var n = parseInt(parts[i], 10);
531                         if (isNaN(n) || n > 255)
532                                 return undefined;
533
534                         num.push(n);
535                 }
536
537                 return num;
538         };
539
540         this.parseIPv6 = function(str)
541         {
542                 if ((typeof(str) != 'string' && !(str instanceof String)) ||
543                     !str.match(/^[a-fA-F0-9:]+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/))
544                         return undefined;
545
546                 var parts = str.split(/::/);
547                 if (parts.length == 0 || parts.length > 2)
548                         return undefined;
549
550                 var lnum = [ ];
551                 if (parts[0].length > 0)
552                 {
553                         var left = parts[0].split(/:/);
554                         for (var i = 0; i < left.length; i++)
555                         {
556                                 var n = parseInt(left[i], 16);
557                                 if (isNaN(n))
558                                         return undefined;
559
560                                 lnum.push((n / 256) >> 0);
561                                 lnum.push(n % 256);
562                         }
563                 }
564
565                 var rnum = [ ];
566                 if (parts.length > 1 && parts[1].length > 0)
567                 {
568                         var right = parts[1].split(/:/);
569
570                         for (var i = 0; i < right.length; i++)
571                         {
572                                 if (right[i].indexOf('.') > 0)
573                                 {
574                                         var addr = L.parseIPv4(right[i]);
575                                         if (!addr)
576                                                 return undefined;
577
578                                         rnum.push.apply(rnum, addr);
579                                         continue;
580                                 }
581
582                                 var n = parseInt(right[i], 16);
583                                 if (isNaN(n))
584                                         return undefined;
585
586                                 rnum.push((n / 256) >> 0);
587                                 rnum.push(n % 256);
588                         }
589                 }
590
591                 if (rnum.length > 0 && (lnum.length + rnum.length) > 15)
592                         return undefined;
593
594                 var num = [ ];
595
596                 num.push.apply(num, lnum);
597
598                 for (var i = 0; i < (16 - lnum.length - rnum.length); i++)
599                         num.push(0);
600
601                 num.push.apply(num, rnum);
602
603                 if (num.length > 16)
604                         return undefined;
605
606                 return num;
607         };
608
609         this.isNetmask = function(addr)
610         {
611                 if (!$.isArray(addr))
612                         return false;
613
614                 var c;
615
616                 for (c = 0; (c < addr.length) && (addr[c] == 255); c++);
617
618                 if (c == addr.length)
619                         return true;
620
621                 if ((addr[c] == 254) || (addr[c] == 252) || (addr[c] == 248) ||
622                         (addr[c] == 240) || (addr[c] == 224) || (addr[c] == 192) ||
623                         (addr[c] == 128) || (addr[c] == 0))
624                 {
625                         for (c++; (c < addr.length) && (addr[c] == 0); c++);
626
627                         if (c == addr.length)
628                                 return true;
629                 }
630
631                 return false;
632         };
633
634         this.globals = {
635                 timeout:  15000,
636                 resource: '/luci2',
637                 sid:      '00000000000000000000000000000000'
638         };
639
640         this.rpc = {
641
642                 _id: 1,
643                 _batch: undefined,
644                 _requests: { },
645
646                 _call: function(req, cb)
647                 {
648                         return $.ajax('/ubus', {
649                                 cache:       false,
650                                 contentType: 'application/json',
651                                 data:        JSON.stringify(req),
652                                 dataType:    'json',
653                                 type:        'POST',
654                                 timeout:     L.globals.timeout,
655                                 _rpc_req:   req
656                         }).then(cb, cb);
657                 },
658
659                 _list_cb: function(msg)
660                 {
661                         var list = msg.result;
662
663                         /* verify message frame */
664                         if (typeof(msg) != 'object' || msg.jsonrpc != '2.0' || !msg.id || !$.isArray(list))
665                                 list = [ ];
666
667                         return $.Deferred().resolveWith(this, [ list ]);
668                 },
669
670                 _call_cb: function(msg)
671                 {
672                         var data = [ ];
673                         var type = Object.prototype.toString;
674                         var reqs = this._rpc_req;
675
676                         if (!$.isArray(reqs))
677                         {
678                                 msg = [ msg ];
679                                 reqs = [ reqs ];
680                         }
681
682                         for (var i = 0; i < msg.length; i++)
683                         {
684                                 /* fetch related request info */
685                                 var req = L.rpc._requests[reqs[i].id];
686                                 if (typeof(req) != 'object')
687                                         throw 'No related request for JSON response';
688
689                                 /* fetch response attribute and verify returned type */
690                                 var ret = undefined;
691
692                                 /* verify message frame */
693                                 if (typeof(msg[i]) == 'object' && msg[i].jsonrpc == '2.0')
694                                         if ($.isArray(msg[i].result) && msg[i].result[0] == 0)
695                                                 ret = (msg[i].result.length > 1) ? msg[i].result[1] : msg[i].result[0];
696
697                                 if (req.expect)
698                                 {
699                                         for (var key in req.expect)
700                                         {
701                                                 if (typeof(ret) != 'undefined' && key != '')
702                                                         ret = ret[key];
703
704                                                 if (typeof(ret) == 'undefined' || type.call(ret) != type.call(req.expect[key]))
705                                                         ret = req.expect[key];
706
707                                                 break;
708                                         }
709                                 }
710
711                                 /* apply filter */
712                                 if (typeof(req.filter) == 'function')
713                                 {
714                                         req.priv[0] = ret;
715                                         req.priv[1] = req.params;
716                                         ret = req.filter.apply(L.rpc, req.priv);
717                                 }
718
719                                 /* store response data */
720                                 if (typeof(req.index) == 'number')
721                                         data[req.index] = ret;
722                                 else
723                                         data = ret;
724
725                                 /* delete request object */
726                                 delete L.rpc._requests[reqs[i].id];
727                         }
728
729                         return $.Deferred().resolveWith(this, [ data ]);
730                 },
731
732                 list: function()
733                 {
734                         var params = [ ];
735                         for (var i = 0; i < arguments.length; i++)
736                                 params[i] = arguments[i];
737
738                         var msg = {
739                                 jsonrpc: '2.0',
740                                 id:      this._id++,
741                                 method:  'list',
742                                 params:  (params.length > 0) ? params : undefined
743                         };
744
745                         return this._call(msg, this._list_cb);
746                 },
747
748                 batch: function()
749                 {
750                         if (!$.isArray(this._batch))
751                                 this._batch = [ ];
752                 },
753
754                 flush: function()
755                 {
756                         if (!$.isArray(this._batch))
757                                 return L.deferrable([ ]);
758
759                         var req = this._batch;
760                         delete this._batch;
761
762                         /* call rpc */
763                         return this._call(req, this._call_cb);
764                 },
765
766                 declare: function(options)
767                 {
768                         var _rpc = this;
769
770                         return function() {
771                                 /* build parameter object */
772                                 var p_off = 0;
773                                 var params = { };
774                                 if ($.isArray(options.params))
775                                         for (p_off = 0; p_off < options.params.length; p_off++)
776                                                 params[options.params[p_off]] = arguments[p_off];
777
778                                 /* all remaining arguments are private args */
779                                 var priv = [ undefined, undefined ];
780                                 for (; p_off < arguments.length; p_off++)
781                                         priv.push(arguments[p_off]);
782
783                                 /* store request info */
784                                 var req = _rpc._requests[_rpc._id] = {
785                                         expect: options.expect,
786                                         filter: options.filter,
787                                         params: params,
788                                         priv:   priv
789                                 };
790
791                                 /* build message object */
792                                 var msg = {
793                                         jsonrpc: '2.0',
794                                         id:      _rpc._id++,
795                                         method:  'call',
796                                         params:  [
797                                                 L.globals.sid,
798                                                 options.object,
799                                                 options.method,
800                                                 params
801                                         ]
802                                 };
803
804                                 /* when a batch is in progress then store index in request data
805                                  * and push message object onto the stack */
806                                 if ($.isArray(_rpc._batch))
807                                 {
808                                         req.index = _rpc._batch.push(msg) - 1;
809                                         return L.deferrable(msg);
810                                 }
811
812                                 /* call rpc */
813                                 return _rpc._call(msg, _rpc._call_cb);
814                         };
815                 }
816         };
817
818         this.UCIContext = Class.extend({
819
820                 init: function()
821                 {
822                         this.state = {
823                                 newidx:  0,
824                                 values:  { },
825                                 creates: { },
826                                 changes: { },
827                                 deletes: { },
828                                 reorder: { }
829                         };
830                 },
831
832                 callLoad: L.rpc.declare({
833                         object: 'uci',
834                         method: 'get',
835                         params: [ 'config' ],
836                         expect: { values: { } }
837                 }),
838
839                 callOrder: L.rpc.declare({
840                         object: 'uci',
841                         method: 'order',
842                         params: [ 'config', 'sections' ]
843                 }),
844
845                 callAdd: L.rpc.declare({
846                         object: 'uci',
847                         method: 'add',
848                         params: [ 'config', 'type', 'name', 'values' ],
849                         expect: { section: '' }
850                 }),
851
852                 callSet: L.rpc.declare({
853                         object: 'uci',
854                         method: 'set',
855                         params: [ 'config', 'section', 'values' ]
856                 }),
857
858                 callDelete: L.rpc.declare({
859                         object: 'uci',
860                         method: 'delete',
861                         params: [ 'config', 'section', 'options' ]
862                 }),
863
864                 callApply: L.rpc.declare({
865                         object: 'uci',
866                         method: 'apply',
867                         params: [ 'timeout', 'rollback' ]
868                 }),
869
870                 callConfirm: L.rpc.declare({
871                         object: 'uci',
872                         method: 'confirm'
873                 }),
874
875                 createSID: function(conf)
876                 {
877                         var v = this.state.values;
878                         var n = this.state.creates;
879                         var sid;
880
881                         do {
882                                 sid = "new%06x".format(Math.random() * 0xFFFFFF);
883                         } while ((n[conf] && n[conf][sid]) || (v[conf] && v[conf][sid]));
884
885                         return sid;
886                 },
887
888                 reorderSections: function()
889                 {
890                         var v = this.state.values;
891                         var n = this.state.creates;
892                         var r = this.state.reorder;
893
894                         if ($.isEmptyObject(r))
895                                 return L.deferrable();
896
897                         L.rpc.batch();
898
899                         /*
900                          gather all created and existing sections, sort them according
901                          to their index value and issue an uci order call
902                         */
903                         for (var c in r)
904                         {
905                                 var o = [ ];
906
907                                 if (n[c])
908                                         for (var s in n[c])
909                                                 o.push(n[c][s]);
910
911                                 for (var s in v[c])
912                                         o.push(v[c][s]);
913
914                                 if (o.length > 0)
915                                 {
916                                         o.sort(function(a, b) {
917                                                 return (a['.index'] - b['.index']);
918                                         });
919
920                                         var sids = [ ];
921
922                                         for (var i = 0; i < o.length; i++)
923                                                 sids.push(o[i]['.name']);
924
925                                         this.callOrder(c, sids);
926                                 }
927                         }
928
929                         this.state.reorder = { };
930                         return L.rpc.flush();
931                 },
932
933                 load: function(packages)
934                 {
935                         var self = this;
936                         var seen = { };
937                         var pkgs = [ ];
938
939                         if (!$.isArray(packages))
940                                 packages = [ packages ];
941
942                         L.rpc.batch();
943
944                         for (var i = 0; i < packages.length; i++)
945                                 if (!seen[packages[i]] && !self.state.values[packages[i]])
946                                 {
947                                         pkgs.push(packages[i]);
948                                         seen[packages[i]] = true;
949                                         self.callLoad(packages[i]);
950                                 }
951
952                         return L.rpc.flush().then(function(responses) {
953                                 for (var i = 0; i < responses.length; i++)
954                                         self.state.values[pkgs[i]] = responses[i];
955
956                                 return pkgs;
957                         });
958                 },
959
960                 unload: function(packages)
961                 {
962                         if (!$.isArray(packages))
963                                 packages = [ packages ];
964
965                         for (var i = 0; i < packages.length; i++)
966                         {
967                                 delete this.state.values[packages[i]];
968                                 delete this.state.creates[packages[i]];
969                                 delete this.state.changes[packages[i]];
970                                 delete this.state.deletes[packages[i]];
971                         }
972                 },
973
974                 add: function(conf, type, name)
975                 {
976                         var n = this.state.creates;
977                         var sid = this.createSID(conf);
978
979                         if (!n[conf])
980                                 n[conf] = { };
981
982                         n[conf][sid] = {
983                                 '.type':      type,
984                                 '.name':      sid,
985                                 '.create':    name,
986                                 '.anonymous': !name,
987                                 '.index':     1000 + this.state.newidx++
988                         };
989
990                         return sid;
991                 },
992
993                 remove: function(conf, sid)
994                 {
995                         var n = this.state.creates;
996                         var c = this.state.changes;
997                         var d = this.state.deletes;
998
999                         /* requested deletion of a just created section */
1000                         if (n[conf] && n[conf][sid])
1001                         {
1002                                 delete n[conf][sid];
1003                         }
1004                         else
1005                         {
1006                                 if (c[conf])
1007                                         delete c[conf][sid];
1008
1009                                 if (!d[conf])
1010                                         d[conf] = { };
1011
1012                                 d[conf][sid] = true;
1013                         }
1014                 },
1015
1016                 sections: function(conf, type, cb)
1017                 {
1018                         var sa = [ ];
1019                         var v = this.state.values[conf];
1020                         var n = this.state.creates[conf];
1021                         var c = this.state.changes[conf];
1022                         var d = this.state.deletes[conf];
1023
1024                         if (!v)
1025                                 return sa;
1026
1027                         for (var s in v)
1028                                 if (!d || d[s] !== true)
1029                                         if (!type || v[s]['.type'] == type)
1030                                                 sa.push($.extend({ }, v[s], c ? c[s] : undefined));
1031
1032                         if (n)
1033                                 for (var s in n)
1034                                         if (!type || n[s]['.type'] == type)
1035                                                 sa.push(n[s]);
1036
1037                         sa.sort(function(a, b) {
1038                                 return a['.index'] - b['.index'];
1039                         });
1040
1041                         for (var i = 0; i < sa.length; i++)
1042                                 sa[i]['.index'] = i;
1043
1044                         if (typeof(cb) == 'function')
1045                                 for (var i = 0; i < sa.length; i++)
1046                                         cb.call(this, sa[i], sa[i]['.name']);
1047
1048                         return sa;
1049                 },
1050
1051                 get: function(conf, sid, opt)
1052                 {
1053                         var v = this.state.values;
1054                         var n = this.state.creates;
1055                         var c = this.state.changes;
1056                         var d = this.state.deletes;
1057
1058                         if (typeof(sid) == 'undefined')
1059                                 return undefined;
1060
1061                         /* requested option in a just created section */
1062                         if (n[conf] && n[conf][sid])
1063                         {
1064                                 if (!n[conf])
1065                                         return undefined;
1066
1067                                 if (typeof(opt) == 'undefined')
1068                                         return n[conf][sid];
1069
1070                                 return n[conf][sid][opt];
1071                         }
1072
1073                         /* requested an option value */
1074                         if (typeof(opt) != 'undefined')
1075                         {
1076                                 /* check whether option was deleted */
1077                                 if (d[conf] && d[conf][sid])
1078                                 {
1079                                         if (d[conf][sid] === true)
1080                                                 return undefined;
1081
1082                                         for (var i = 0; i < d[conf][sid].length; i++)
1083                                                 if (d[conf][sid][i] == opt)
1084                                                         return undefined;
1085                                 }
1086
1087                                 /* check whether option was changed */
1088                                 if (c[conf] && c[conf][sid] && typeof(c[conf][sid][opt]) != 'undefined')
1089                                         return c[conf][sid][opt];
1090
1091                                 /* return base value */
1092                                 if (v[conf] && v[conf][sid])
1093                                         return v[conf][sid][opt];
1094
1095                                 return undefined;
1096                         }
1097
1098                         /* requested an entire section */
1099                         if (v[conf])
1100                                 return v[conf][sid];
1101
1102                         return undefined;
1103                 },
1104
1105                 set: function(conf, sid, opt, val)
1106                 {
1107                         var v = this.state.values;
1108                         var n = this.state.creates;
1109                         var c = this.state.changes;
1110                         var d = this.state.deletes;
1111
1112                         if (typeof(sid) == 'undefined' ||
1113                             typeof(opt) == 'undefined' ||
1114                             opt.charAt(0) == '.')
1115                                 return;
1116
1117                         if (n[conf] && n[conf][sid])
1118                         {
1119                                 if (typeof(val) != 'undefined')
1120                                         n[conf][sid][opt] = val;
1121                                 else
1122                                         delete n[conf][sid][opt];
1123                         }
1124                         else if (typeof(val) != 'undefined')
1125                         {
1126                                 /* do not set within deleted section */
1127                                 if (d[conf] && d[conf][sid] === true)
1128                                         return;
1129
1130                                 /* only set in existing sections */
1131                                 if (!v[conf] || !v[conf][sid])
1132                                         return;
1133
1134                                 if (!c[conf])
1135                                         c[conf] = { };
1136
1137                                 if (!c[conf][sid])
1138                                         c[conf][sid] = { };
1139
1140                                 /* undelete option */
1141                                 if (d[conf] && d[conf][sid])
1142                                         d[conf][sid] = L.filterArray(d[conf][sid], opt);
1143
1144                                 c[conf][sid][opt] = val;
1145                         }
1146                         else
1147                         {
1148                                 /* only delete in existing sections */
1149                                 if (!v[conf] || !v[conf][sid])
1150                                         return;
1151
1152                                 if (!d[conf])
1153                                         d[conf] = { };
1154
1155                                 if (!d[conf][sid])
1156                                         d[conf][sid] = [ ];
1157
1158                                 if (d[conf][sid] !== true)
1159                                         d[conf][sid].push(opt);
1160                         }
1161                 },
1162
1163                 unset: function(conf, sid, opt)
1164                 {
1165                         return this.set(conf, sid, opt, undefined);
1166                 },
1167
1168                 get_first: function(conf, type, opt)
1169                 {
1170                         var sid = undefined;
1171
1172                         L.uci.sections(conf, type, function(s) {
1173                                 if (typeof(sid) != 'string')
1174                                         sid = s['.name'];
1175                         });
1176
1177                         return this.get(conf, sid, opt);
1178                 },
1179
1180                 set_first: function(conf, type, opt, val)
1181                 {
1182                         var sid = undefined;
1183
1184                         L.uci.sections(conf, type, function(s) {
1185                                 if (typeof(sid) != 'string')
1186                                         sid = s['.name'];
1187                         });
1188
1189                         return this.set(conf, sid, opt, val);
1190                 },
1191
1192                 unset_first: function(conf, type, opt)
1193                 {
1194                         return this.set_first(conf, type, opt, undefined);
1195                 },
1196
1197                 swap: function(conf, sid1, sid2)
1198                 {
1199                         var s1 = this.get(conf, sid1);
1200                         var s2 = this.get(conf, sid2);
1201                         var n1 = s1 ? s1['.index'] : NaN;
1202                         var n2 = s2 ? s2['.index'] : NaN;
1203
1204                         if (isNaN(n1) || isNaN(n2))
1205                                 return false;
1206
1207                         s1['.index'] = n2;
1208                         s2['.index'] = n1;
1209
1210                         this.state.reorder[conf] = true;
1211
1212                         return true;
1213                 },
1214
1215                 save: function()
1216                 {
1217                         L.rpc.batch();
1218
1219                         var v = this.state.values;
1220                         var n = this.state.creates;
1221                         var c = this.state.changes;
1222                         var d = this.state.deletes;
1223
1224                         var self = this;
1225                         var snew = [ ];
1226                         var pkgs = { };
1227
1228                         if (n)
1229                                 for (var conf in n)
1230                                 {
1231                                         for (var sid in n[conf])
1232                                         {
1233                                                 var r = {
1234                                                         config: conf,
1235                                                         values: { }
1236                                                 };
1237
1238                                                 for (var k in n[conf][sid])
1239                                                 {
1240                                                         if (k == '.type')
1241                                                                 r.type = n[conf][sid][k];
1242                                                         else if (k == '.create')
1243                                                                 r.name = n[conf][sid][k];
1244                                                         else if (k.charAt(0) != '.')
1245                                                                 r.values[k] = n[conf][sid][k];
1246                                                 }
1247
1248                                                 snew.push(n[conf][sid]);
1249
1250                                                 self.callAdd(r.config, r.type, r.name, r.values);
1251                                         }
1252
1253                                         pkgs[conf] = true;
1254                                 }
1255
1256                         if (c)
1257                                 for (var conf in c)
1258                                 {
1259                                         for (var sid in c[conf])
1260                                                 self.callSet(conf, sid, c[conf][sid]);
1261
1262                                         pkgs[conf] = true;
1263                                 }
1264
1265                         if (d)
1266                                 for (var conf in d)
1267                                 {
1268                                         for (var sid in d[conf])
1269                                         {
1270                                                 var o = d[conf][sid];
1271                                                 self.callDelete(conf, sid, (o === true) ? undefined : o);
1272                                         }
1273
1274                                         pkgs[conf] = true;
1275                                 }
1276
1277                         return L.rpc.flush().then(function(responses) {
1278                                 /*
1279                                  array "snew" holds references to the created uci sections,
1280                                  use it to assign the returned names of the new sections
1281                                 */
1282                                 for (var i = 0; i < snew.length; i++)
1283                                         snew[i]['.name'] = responses[i];
1284
1285                                 return self.reorderSections();
1286                         }).then(function() {
1287                                 pkgs = L.toArray(pkgs);
1288
1289                                 self.unload(pkgs);
1290
1291                                 return self.load(pkgs);
1292                         });
1293                 },
1294
1295                 apply: function(timeout)
1296                 {
1297                         var self = this;
1298                         var date = new Date();
1299                         var deferred = $.Deferred();
1300
1301                         if (typeof(timeout) != 'number' || timeout < 1)
1302                                 timeout = 10;
1303
1304                         self.callApply(timeout, true).then(function(rv) {
1305                                 if (rv != 0)
1306                                 {
1307                                         deferred.rejectWith(self, [ rv ]);
1308                                         return;
1309                                 }
1310
1311                                 var try_deadline = date.getTime() + 1000 * timeout;
1312                                 var try_confirm = function()
1313                                 {
1314                                         return self.callConfirm().then(function(rv) {
1315                                                 if (rv != 0)
1316                                                 {
1317                                                         if (date.getTime() < try_deadline)
1318                                                                 window.setTimeout(try_confirm, 250);
1319                                                         else
1320                                                                 deferred.rejectWith(self, [ rv ]);
1321
1322                                                         return;
1323                                                 }
1324
1325                                                 deferred.resolveWith(self, [ rv ]);
1326                                         });
1327                                 };
1328
1329                                 window.setTimeout(try_confirm, 1000);
1330                         });
1331
1332                         return deferred;
1333                 },
1334
1335                 changes: L.rpc.declare({
1336                         object: 'uci',
1337                         method: 'changes',
1338                         expect: { changes: { } }
1339                 }),
1340
1341                 readable: function(conf)
1342                 {
1343                         return L.session.hasACL('uci', conf, 'read');
1344                 },
1345
1346                 writable: function(conf)
1347                 {
1348                         return L.session.hasACL('uci', conf, 'write');
1349                 }
1350         });
1351
1352         this.uci = new this.UCIContext();
1353
1354         this.wireless = {
1355                 listDeviceNames: L.rpc.declare({
1356                         object: 'iwinfo',
1357                         method: 'devices',
1358                         expect: { 'devices': [ ] },
1359                         filter: function(data) {
1360                                 data.sort();
1361                                 return data;
1362                         }
1363                 }),
1364
1365                 getDeviceStatus: L.rpc.declare({
1366                         object: 'iwinfo',
1367                         method: 'info',
1368                         params: [ 'device' ],
1369                         expect: { '': { } },
1370                         filter: function(data, params) {
1371                                 if (!$.isEmptyObject(data))
1372                                 {
1373                                         data['device'] = params['device'];
1374                                         return data;
1375                                 }
1376                                 return undefined;
1377                         }
1378                 }),
1379
1380                 getAssocList: L.rpc.declare({
1381                         object: 'iwinfo',
1382                         method: 'assoclist',
1383                         params: [ 'device' ],
1384                         expect: { results: [ ] },
1385                         filter: function(data, params) {
1386                                 for (var i = 0; i < data.length; i++)
1387                                         data[i]['device'] = params['device'];
1388
1389                                 data.sort(function(a, b) {
1390                                         if (a.bssid < b.bssid)
1391                                                 return -1;
1392                                         else if (a.bssid > b.bssid)
1393                                                 return 1;
1394                                         else
1395                                                 return 0;
1396                                 });
1397
1398                                 return data;
1399                         }
1400                 }),
1401
1402                 getWirelessStatus: function() {
1403                         return this.listDeviceNames().then(function(names) {
1404                                 L.rpc.batch();
1405
1406                                 for (var i = 0; i < names.length; i++)
1407                                         L.wireless.getDeviceStatus(names[i]);
1408
1409                                 return L.rpc.flush();
1410                         }).then(function(networks) {
1411                                 var rv = { };
1412
1413                                 var phy_attrs = [
1414                                         'country', 'channel', 'frequency', 'frequency_offset',
1415                                         'txpower', 'txpower_offset', 'hwmodes', 'hardware', 'phy'
1416                                 ];
1417
1418                                 var net_attrs = [
1419                                         'ssid', 'bssid', 'mode', 'quality', 'quality_max',
1420                                         'signal', 'noise', 'bitrate', 'encryption'
1421                                 ];
1422
1423                                 for (var i = 0; i < networks.length; i++)
1424                                 {
1425                                         var phy = rv[networks[i].phy] || (
1426                                                 rv[networks[i].phy] = { networks: [ ] }
1427                                         );
1428
1429                                         var net = {
1430                                                 device: networks[i].device
1431                                         };
1432
1433                                         for (var j = 0; j < phy_attrs.length; j++)
1434                                                 phy[phy_attrs[j]] = networks[i][phy_attrs[j]];
1435
1436                                         for (var j = 0; j < net_attrs.length; j++)
1437                                                 net[net_attrs[j]] = networks[i][net_attrs[j]];
1438
1439                                         phy.networks.push(net);
1440                                 }
1441
1442                                 return rv;
1443                         });
1444                 },
1445
1446                 getAssocLists: function()
1447                 {
1448                         return this.listDeviceNames().then(function(names) {
1449                                 L.rpc.batch();
1450
1451                                 for (var i = 0; i < names.length; i++)
1452                                         L.wireless.getAssocList(names[i]);
1453
1454                                 return L.rpc.flush();
1455                         }).then(function(assoclists) {
1456                                 var rv = [ ];
1457
1458                                 for (var i = 0; i < assoclists.length; i++)
1459                                         for (var j = 0; j < assoclists[i].length; j++)
1460                                                 rv.push(assoclists[i][j]);
1461
1462                                 return rv;
1463                         });
1464                 },
1465
1466                 formatEncryption: function(enc)
1467                 {
1468                         var format_list = function(l, s)
1469                         {
1470                                 var rv = [ ];
1471                                 for (var i = 0; i < l.length; i++)
1472                                         rv.push(l[i].toUpperCase());
1473                                 return rv.join(s ? s : ', ');
1474                         }
1475
1476                         if (!enc || !enc.enabled)
1477                                 return L.tr('None');
1478
1479                         if (enc.wep)
1480                         {
1481                                 if (enc.wep.length == 2)
1482                                         return L.tr('WEP Open/Shared') + ' (%s)'.format(format_list(enc.ciphers, ', '));
1483                                 else if (enc.wep[0] == 'shared')
1484                                         return L.tr('WEP Shared Auth') + ' (%s)'.format(format_list(enc.ciphers, ', '));
1485                                 else
1486                                         return L.tr('WEP Open System') + ' (%s)'.format(format_list(enc.ciphers, ', '));
1487                         }
1488                         else if (enc.wpa)
1489                         {
1490                                 if (enc.wpa.length == 2)
1491                                         return L.tr('mixed WPA/WPA2') + ' %s (%s)'.format(
1492                                                 format_list(enc.authentication, '/'),
1493                                                 format_list(enc.ciphers, ', ')
1494                                         );
1495                                 else if (enc.wpa[0] == 2)
1496                                         return 'WPA2 %s (%s)'.format(
1497                                                 format_list(enc.authentication, '/'),
1498                                                 format_list(enc.ciphers, ', ')
1499                                         );
1500                                 else
1501                                         return 'WPA %s (%s)'.format(
1502                                                 format_list(enc.authentication, '/'),
1503                                                 format_list(enc.ciphers, ', ')
1504                                         );
1505                         }
1506
1507                         return L.tr('Unknown');
1508                 }
1509         };
1510
1511         this.firewall = {
1512                 getZoneColor: function(zone)
1513                 {
1514                         if ($.isPlainObject(zone))
1515                                 zone = zone.name;
1516
1517                         if (zone == 'lan')
1518                                 return '#90f090';
1519                         else if (zone == 'wan')
1520                                 return '#f09090';
1521
1522                         for (var i = 0, hash = 0;
1523                                  i < zone.length;
1524                                  hash = zone.charCodeAt(i++) + ((hash << 5) - hash));
1525
1526                         for (var i = 0, color = '#';
1527                                  i < 3;
1528                                  color += ('00' + ((hash >> i++ * 8) & 0xFF).tostring(16)).slice(-2));
1529
1530                         return color;
1531                 },
1532
1533                 findZoneByNetwork: function(network)
1534                 {
1535                         var self = this;
1536                         var zone = undefined;
1537
1538                         return L.uci.sections('firewall', 'zone', function(z) {
1539                                 if (!z.name || !z.network)
1540                                         return;
1541
1542                                 if (!$.isArray(z.network))
1543                                         z.network = z.network.split(/\s+/);
1544
1545                                 for (var i = 0; i < z.network.length; i++)
1546                                 {
1547                                         if (z.network[i] == network)
1548                                         {
1549                                                 zone = z;
1550                                                 break;
1551                                         }
1552                                 }
1553                         }).then(function() {
1554                                 if (zone)
1555                                         zone.color = self.getZoneColor(zone);
1556
1557                                 return zone;
1558                         });
1559                 }
1560         };
1561
1562         this.NetworkModel = {
1563                 deviceBlacklist: [
1564                         /^gre[0-9]+$/,
1565                         /^gretap[0-9]+$/,
1566                         /^ifb[0-9]+$/,
1567                         /^ip6tnl[0-9]+$/,
1568                         /^sit[0-9]+$/,
1569                         /^wlan[0-9]+\.sta[0-9]+$/
1570                 ],
1571
1572                 rpcCacheFunctions: [
1573                         'protolist', 0, L.rpc.declare({
1574                                 object: 'network',
1575                                 method: 'get_proto_handlers',
1576                                 expect: { '': { } }
1577                         }),
1578                         'ifstate', 1, L.rpc.declare({
1579                                 object: 'network.interface',
1580                                 method: 'dump',
1581                                 expect: { 'interface': [ ] }
1582                         }),
1583                         'devstate', 2, L.rpc.declare({
1584                                 object: 'network.device',
1585                                 method: 'status',
1586                                 expect: { '': { } }
1587                         }),
1588                         'wifistate', 0, L.rpc.declare({
1589                                 object: 'network.wireless',
1590                                 method: 'status',
1591                                 expect: { '': { } }
1592                         }),
1593                         'bwstate', 2, L.rpc.declare({
1594                                 object: 'luci2.network.bwmon',
1595                                 method: 'statistics',
1596                                 expect: { 'statistics': { } }
1597                         }),
1598                         'devlist', 2, L.rpc.declare({
1599                                 object: 'luci2.network',
1600                                 method: 'device_list',
1601                                 expect: { 'devices': [ ] }
1602                         }),
1603                         'swlist', 0, L.rpc.declare({
1604                                 object: 'luci2.network',
1605                                 method: 'switch_list',
1606                                 expect: { 'switches': [ ] }
1607                         })
1608                 ],
1609
1610                 loadProtocolHandler: function(proto)
1611                 {
1612                         var url = L.globals.resource + '/proto/' + proto + '.js';
1613                         var self = L.NetworkModel;
1614
1615                         var def = $.Deferred();
1616
1617                         $.ajax(url, {
1618                                 method: 'GET',
1619                                 cache: true,
1620                                 dataType: 'text'
1621                         }).then(function(data) {
1622                                 try {
1623                                         var protoConstructorSource = (
1624                                                 '(function(L, $) { ' +
1625                                                         'return %s' +
1626                                                 '})(L, $);\n\n' +
1627                                                 '//@ sourceURL=%s'
1628                                         ).format(data, url);
1629
1630                                         var protoClass = eval(protoConstructorSource);
1631
1632                                         self.protocolHandlers[proto] = new protoClass();
1633                                 }
1634                                 catch(e) {
1635                                         alert('Unable to instantiate proto "%s": %s'.format(url, e));
1636                                 };
1637
1638                                 def.resolve();
1639                         }).fail(function() {
1640                                 def.resolve();
1641                         });
1642
1643                         return def;
1644                 },
1645
1646                 loadProtocolHandlers: function()
1647                 {
1648                         var self = L.NetworkModel;
1649                         var deferreds = [
1650                                 self.loadProtocolHandler('none')
1651                         ];
1652
1653                         for (var proto in self.rpcCache.protolist)
1654                                 deferreds.push(self.loadProtocolHandler(proto));
1655
1656                         return $.when.apply($, deferreds);
1657                 },
1658
1659                 callSwitchInfo: L.rpc.declare({
1660                         object: 'luci2.network',
1661                         method: 'switch_info',
1662                         params: [ 'switch' ],
1663                         expect: { 'info': { } }
1664                 }),
1665
1666                 callSwitchInfoCallback: function(responses) {
1667                         var self = L.NetworkModel;
1668                         var swlist = self.rpcCache.swlist;
1669                         var swstate = self.rpcCache.swstate = { };
1670
1671                         for (var i = 0; i < responses.length; i++)
1672                                 swstate[swlist[i]] = responses[i];
1673                 },
1674
1675                 loadCacheCallback: function(level)
1676                 {
1677                         var self = L.NetworkModel;
1678                         var name = '_fetch_cache_cb_' + level;
1679
1680                         return self[name] || (
1681                                 self[name] = function(responses)
1682                                 {
1683                                         for (var i = 0; i < self.rpcCacheFunctions.length; i += 3)
1684                                                 if (!level || self.rpcCacheFunctions[i + 1] == level)
1685                                                         self.rpcCache[self.rpcCacheFunctions[i]] = responses.shift();
1686
1687                                         if (!level)
1688                                         {
1689                                                 L.rpc.batch();
1690
1691                                                 for (var i = 0; i < self.rpcCache.swlist.length; i++)
1692                                                         self.callSwitchInfo(self.rpcCache.swlist[i]);
1693
1694                                                 return L.rpc.flush().then(self.callSwitchInfoCallback);
1695                                         }
1696
1697                                         return L.deferrable();
1698                                 }
1699                         );
1700                 },
1701
1702                 loadCache: function(level)
1703                 {
1704                         var self = L.NetworkModel;
1705
1706                         return L.uci.load(['network', 'wireless']).then(function() {
1707                                 L.rpc.batch();
1708
1709                                 for (var i = 0; i < self.rpcCacheFunctions.length; i += 3)
1710                                         if (!level || self.rpcCacheFunctions[i + 1] == level)
1711                                                 self.rpcCacheFunctions[i + 2]();
1712
1713                                 return L.rpc.flush().then(self.loadCacheCallback(level || 0));
1714                         });
1715                 },
1716
1717                 isBlacklistedDevice: function(dev)
1718                 {
1719                         for (var i = 0; i < this.deviceBlacklist.length; i++)
1720                                 if (dev.match(this.deviceBlacklist[i]))
1721                                         return true;
1722
1723                         return false;
1724                 },
1725
1726                 sortDevicesCallback: function(a, b)
1727                 {
1728                         if (a.options.kind < b.options.kind)
1729                                 return -1;
1730                         else if (a.options.kind > b.options.kind)
1731                                 return 1;
1732
1733                         if (a.options.name < b.options.name)
1734                                 return -1;
1735                         else if (a.options.name > b.options.name)
1736                                 return 1;
1737
1738                         return 0;
1739                 },
1740
1741                 getDeviceObject: function(ifname)
1742                 {
1743                         var alias = (ifname.charAt(0) == '@');
1744                         return this.deviceObjects[ifname] || (
1745                                 this.deviceObjects[ifname] = {
1746                                         ifname:  ifname,
1747                                         kind:    alias ? 'alias' : 'ethernet',
1748                                         type:    alias ? 0 : 1,
1749                                         up:      false,
1750                                         changed: { }
1751                                 }
1752                         );
1753                 },
1754
1755                 getInterfaceObject: function(name)
1756                 {
1757                         return this.interfaceObjects[name] || (
1758                                 this.interfaceObjects[name] = {
1759                                         name:    name,
1760                                         proto:   this.protocolHandlers.none,
1761                                         changed: { }
1762                                 }
1763                         );
1764                 },
1765
1766                 loadDevicesCallback: function()
1767                 {
1768                         var self = L.NetworkModel;
1769                         var wificount = { };
1770
1771                         for (var ifname in self.rpcCache.devstate)
1772                         {
1773                                 if (self.isBlacklistedDevice(ifname))
1774                                         continue;
1775
1776                                 var dev = self.rpcCache.devstate[ifname];
1777                                 var entry = self.getDeviceObject(ifname);
1778
1779                                 entry.up = dev.up;
1780
1781                                 switch (dev.type)
1782                                 {
1783                                 case 'IP tunnel':
1784                                         entry.kind = 'tunnel';
1785                                         break;
1786
1787                                 case 'Bridge':
1788                                         entry.kind = 'bridge';
1789                                         //entry.ports = dev['bridge-members'].sort();
1790                                         break;
1791                                 }
1792                         }
1793
1794                         for (var i = 0; i < self.rpcCache.devlist.length; i++)
1795                         {
1796                                 var dev = self.rpcCache.devlist[i];
1797
1798                                 if (self.isBlacklistedDevice(dev.device))
1799                                         continue;
1800
1801                                 var entry = self.getDeviceObject(dev.device);
1802
1803                                 entry.up   = dev.is_up;
1804                                 entry.type = dev.type;
1805
1806                                 switch (dev.type)
1807                                 {
1808                                 case 1: /* Ethernet */
1809                                         if (dev.is_bridge)
1810                                                 entry.kind = 'bridge';
1811                                         else if (dev.is_tuntap)
1812                                                 entry.kind = 'tunnel';
1813                                         else if (dev.is_wireless)
1814                                                 entry.kind = 'wifi';
1815                                         break;
1816
1817                                 case 512: /* PPP */
1818                                 case 768: /* IP-IP Tunnel */
1819                                 case 769: /* IP6-IP6 Tunnel */
1820                                 case 776: /* IPv6-in-IPv4 */
1821                                 case 778: /* GRE over IP */
1822                                         entry.kind = 'tunnel';
1823                                         break;
1824                                 }
1825                         }
1826
1827                         var net = L.uci.sections('network');
1828                         for (var i = 0; i < net.length; i++)
1829                         {
1830                                 var s = net[i];
1831                                 var sid = s['.name'];
1832
1833                                 if (s['.type'] == 'device' && s.name)
1834                                 {
1835                                         var entry = self.getDeviceObject(s.name);
1836
1837                                         switch (s.type)
1838                                         {
1839                                         case 'macvlan':
1840                                         case 'tunnel':
1841                                                 entry.kind = 'tunnel';
1842                                                 break;
1843                                         }
1844
1845                                         entry.sid = sid;
1846                                 }
1847                                 else if (s['.type'] == 'interface' && !s['.anonymous'] && s.ifname)
1848                                 {
1849                                         var ifnames = L.toArray(s.ifname);
1850
1851                                         for (var j = 0; j < ifnames.length; j++)
1852                                                 self.getDeviceObject(ifnames[j]);
1853
1854                                         if (s['.name'] != 'loopback')
1855                                         {
1856                                                 var entry = self.getDeviceObject('@%s'.format(s['.name']));
1857
1858                                                 entry.type = 0;
1859                                                 entry.kind = 'alias';
1860                                                 entry.sid  = sid;
1861                                         }
1862                                 }
1863                                 else if (s['.type'] == 'switch_vlan' && s.device)
1864                                 {
1865                                         var sw = self.rpcCache.swstate[s.device];
1866                                         var vid = parseInt(s.vid || s.vlan);
1867                                         var ports = L.toArray(s.ports);
1868
1869                                         if (!sw || !ports.length || isNaN(vid))
1870                                                 continue;
1871
1872                                         var ifname = undefined;
1873
1874                                         for (var j = 0; j < ports.length; j++)
1875                                         {
1876                                                 var port = parseInt(ports[j]);
1877                                                 var tag = (ports[j].replace(/[^tu]/g, '') == 't');
1878
1879                                                 if (port == sw.cpu_port)
1880                                                 {
1881                                                         // XXX: need a way to map switch to netdev
1882                                                         if (tag)
1883                                                                 ifname = 'eth0.%d'.format(vid);
1884                                                         else
1885                                                                 ifname = 'eth0';
1886
1887                                                         break;
1888                                                 }
1889                                         }
1890
1891                                         if (!ifname)
1892                                                 continue;
1893
1894                                         var entry = self.getDeviceObject(ifname);
1895
1896                                         entry.kind = 'vlan';
1897                                         entry.sid  = sid;
1898                                         entry.vsw  = sw;
1899                                         entry.vid  = vid;
1900                                 }
1901                         }
1902
1903                         var wifi = L.uci.sections('wireless');
1904                         for (var i = 0; i < wifi.length; i++)
1905                         {
1906                                 var s = wifi[i];
1907                                 var sid = s['.name'];
1908
1909                                 if (s['.type'] == 'wifi-iface' && s.device)
1910                                 {
1911                                         var r = parseInt(s.device.replace(/^[^0-9]+/, ''));
1912                                         var n = wificount[s.device] = (wificount[s.device] || 0) + 1;
1913                                         var id = 'radio%d.network%d'.format(r, n);
1914                                         var ifname = id;
1915
1916                                         if (self.rpcCache.wifistate[s.device])
1917                                         {
1918                                                 var ifcs = self.rpcCache.wifistate[s.device].interfaces;
1919                                                 for (var ifc in ifcs)
1920                                                 {
1921                                                         if (ifcs[ifc].section == sid && ifcs[ifc].ifname)
1922                                                         {
1923                                                                 ifname = ifcs[ifc].ifname;
1924                                                                 break;
1925                                                         }
1926                                                 }
1927                                         }
1928
1929                                         var entry = self.getDeviceObject(ifname);
1930
1931                                         entry.kind   = 'wifi';
1932                                         entry.sid    = sid;
1933                                         entry.wid    = id;
1934                                         entry.wdev   = s.device;
1935                                         entry.wmode  = s.mode;
1936                                         entry.wssid  = s.ssid;
1937                                         entry.wbssid = s.bssid;
1938                                 }
1939                         }
1940
1941                         for (var i = 0; i < net.length; i++)
1942                         {
1943                                 var s = net[i];
1944                                 var sid = s['.name'];
1945
1946                                 if (s['.type'] == 'interface' && !s['.anonymous'] && s.type == 'bridge')
1947                                 {
1948                                         var ifnames = L.toArray(s.ifname);
1949
1950                                         for (var ifname in self.deviceObjects)
1951                                         {
1952                                                 var dev = self.deviceObjects[ifname];
1953
1954                                                 if (dev.kind != 'wifi')
1955                                                         continue;
1956
1957                                                 var wnets = L.toArray(L.uci.get('wireless', dev.sid, 'network'));
1958                                                 if ($.inArray(sid, wnets) > -1)
1959                                                         ifnames.push(ifname);
1960                                         }
1961
1962                                         entry = self.getDeviceObject('br-%s'.format(s['.name']));
1963                                         entry.type  = 1;
1964                                         entry.kind  = 'bridge';
1965                                         entry.sid   = sid;
1966                                         entry.ports = ifnames.sort();
1967                                 }
1968                         }
1969                 },
1970
1971                 loadInterfacesCallback: function()
1972                 {
1973                         var self = L.NetworkModel;
1974                         var net = L.uci.sections('network');
1975
1976                         for (var i = 0; i < net.length; i++)
1977                         {
1978                                 var s = net[i];
1979                                 var sid = s['.name'];
1980
1981                                 if (s['.type'] == 'interface' && !s['.anonymous'] && s.proto)
1982                                 {
1983                                         var entry = self.getInterfaceObject(s['.name']);
1984                                         var proto = self.protocolHandlers[s.proto] || self.protocolHandlers.none;
1985
1986                                         var l3dev = undefined;
1987                                         var l2dev = undefined;
1988
1989                                         var ifnames = L.toArray(s.ifname);
1990
1991                                         for (var ifname in self.deviceObjects)
1992                                         {
1993                                                 var dev = self.deviceObjects[ifname];
1994
1995                                                 if (dev.kind != 'wifi')
1996                                                         continue;
1997
1998                                                 var wnets = L.toArray(L.uci.get('wireless', dev.sid, 'network'));
1999                                                 if ($.inArray(entry.name, wnets) > -1)
2000                                                         ifnames.push(ifname);
2001                                         }
2002
2003                                         if (proto.virtual)
2004                                                 l3dev = '%s-%s'.format(s.proto, entry.name);
2005                                         else if (s.type == 'bridge')
2006                                                 l3dev = 'br-%s'.format(entry.name);
2007                                         else
2008                                                 l3dev = ifnames[0];
2009
2010                                         if (!proto.virtual && s.type == 'bridge')
2011                                                 l2dev = 'br-%s'.format(entry.name);
2012                                         else if (!proto.virtual)
2013                                                 l2dev = ifnames[0];
2014
2015                                         entry.proto = proto;
2016                                         entry.sid   = sid;
2017                                         entry.l3dev = l3dev;
2018                                         entry.l2dev = l2dev;
2019                                 }
2020                         }
2021
2022                         for (var i = 0; i < self.rpcCache.ifstate.length; i++)
2023                         {
2024                                 var iface = self.rpcCache.ifstate[i];
2025                                 var entry = self.getInterfaceObject(iface['interface']);
2026                                 var proto = self.protocolHandlers[iface.proto] || self.protocolHandlers.none;
2027
2028                                 /* this is a virtual interface, either deleted from config but
2029                                    not applied yet or set up from external tools (6rd) */
2030                                 if (!entry.sid)
2031                                 {
2032                                         entry.proto = proto;
2033                                         entry.l2dev = iface.device;
2034                                         entry.l3dev = iface.l3_device;
2035                                 }
2036                         }
2037                 },
2038
2039                 init: function()
2040                 {
2041                         var self = this;
2042
2043                         if (self.rpcCache)
2044                                 return L.deferrable();
2045
2046                         self.rpcCache         = { };
2047                         self.deviceObjects    = { };
2048                         self.interfaceObjects = { };
2049                         self.protocolHandlers = { };
2050
2051                         return self.loadCache()
2052                                 .then(self.loadProtocolHandlers)
2053                                 .then(self.loadDevicesCallback)
2054                                 .then(self.loadInterfacesCallback);
2055                 },
2056
2057                 update: function()
2058                 {
2059                         delete this.rpcCache;
2060                         return this.init();
2061                 },
2062
2063                 refreshInterfaceStatus: function()
2064                 {
2065                         return this.loadCache(1).then(this.loadInterfacesCallback);
2066                 },
2067
2068                 refreshDeviceStatus: function()
2069                 {
2070                         return this.loadCache(2).then(this.loadDevicesCallback);
2071                 },
2072
2073                 refreshStatus: function()
2074                 {
2075                         return this.loadCache(1)
2076                                 .then(this.loadCache(2))
2077                                 .then(this.loadDevicesCallback)
2078                                 .then(this.loadInterfacesCallback);
2079                 },
2080
2081                 getDevices: function()
2082                 {
2083                         var devs = [ ];
2084
2085                         for (var ifname in this.deviceObjects)
2086                                 if (ifname != 'lo')
2087                                         devs.push(new L.NetworkModel.Device(this.deviceObjects[ifname]));
2088
2089                         return devs.sort(this.sortDevicesCallback);
2090                 },
2091
2092                 getDeviceByInterface: function(iface)
2093                 {
2094                         if (iface instanceof L.NetworkModel.Interface)
2095                                 iface = iface.name();
2096
2097                         if (this.interfaceObjects[iface])
2098                                 return this.getDevice(this.interfaceObjects[iface].l3dev) ||
2099                                        this.getDevice(this.interfaceObjects[iface].l2dev);
2100
2101                         return undefined;
2102                 },
2103
2104                 getDevice: function(ifname)
2105                 {
2106                         if (this.deviceObjects[ifname])
2107                                 return new L.NetworkModel.Device(this.deviceObjects[ifname]);
2108
2109                         return undefined;
2110                 },
2111
2112                 createDevice: function(name)
2113                 {
2114                         return new L.NetworkModel.Device(this.getDeviceObject(name));
2115                 },
2116
2117                 getInterfaces: function()
2118                 {
2119                         var ifaces = [ ];
2120
2121                         for (var name in this.interfaceObjects)
2122                                 if (name != 'loopback')
2123                                         ifaces.push(this.getInterface(name));
2124
2125                         ifaces.sort(function(a, b) {
2126                                 if (a.name() < b.name())
2127                                         return -1;
2128                                 else if (a.name() > b.name())
2129                                         return 1;
2130                                 else
2131                                         return 0;
2132                         });
2133
2134                         return ifaces;
2135                 },
2136
2137                 getInterfacesByDevice: function(dev)
2138                 {
2139                         var ifaces = [ ];
2140
2141                         if (dev instanceof L.NetworkModel.Device)
2142                                 dev = dev.name();
2143
2144                         for (var name in this.interfaceObjects)
2145                         {
2146                                 var iface = this.interfaceObjects[name];
2147                                 if (iface.l2dev == dev || iface.l3dev == dev)
2148                                         ifaces.push(this.getInterface(name));
2149                         }
2150
2151                         ifaces.sort(function(a, b) {
2152                                 if (a.name() < b.name())
2153                                         return -1;
2154                                 else if (a.name() > b.name())
2155                                         return 1;
2156                                 else
2157                                         return 0;
2158                         });
2159
2160                         return ifaces;
2161                 },
2162
2163                 getInterface: function(iface)
2164                 {
2165                         if (this.interfaceObjects[iface])
2166                                 return new L.NetworkModel.Interface(this.interfaceObjects[iface]);
2167
2168                         return undefined;
2169                 },
2170
2171                 getProtocols: function()
2172                 {
2173                         var rv = [ ];
2174
2175                         for (var proto in this.protocolHandlers)
2176                         {
2177                                 var pr = this.protocolHandlers[proto];
2178
2179                                 rv.push({
2180                                         name:        proto,
2181                                         description: pr.description,
2182                                         virtual:     pr.virtual,
2183                                         tunnel:      pr.tunnel
2184                                 });
2185                         }
2186
2187                         return rv.sort(function(a, b) {
2188                                 if (a.name < b.name)
2189                                         return -1;
2190                                 else if (a.name > b.name)
2191                                         return 1;
2192                                 else
2193                                         return 0;
2194                         });
2195                 },
2196
2197                 findWANByAddr: function(ipaddr)
2198                 {
2199                         for (var i = 0; i < this.rpcCache.ifstate.length; i++)
2200                         {
2201                                 var ifstate = this.rpcCache.ifstate[i];
2202
2203                                 if (!ifstate.route)
2204                                         continue;
2205
2206                                 for (var j = 0; j < ifstate.route.length; j++)
2207                                         if (ifstate.route[j].mask == 0 &&
2208                                             ifstate.route[j].target == ipaddr &&
2209                                             typeof(ifstate.route[j].table) == 'undefined')
2210                                         {
2211                                                 return this.getInterface(ifstate['interface']);
2212                                         }
2213                         }
2214
2215                         return undefined;
2216                 },
2217
2218                 findWAN: function()
2219                 {
2220                         return this.findWANByAddr('0.0.0.0');
2221                 },
2222
2223                 findWAN6: function()
2224                 {
2225                         return this.findWANByAddr('::');
2226                 },
2227
2228                 resolveAlias: function(ifname)
2229                 {
2230                         if (ifname instanceof L.NetworkModel.Device)
2231                                 ifname = ifname.name();
2232
2233                         var dev = this.deviceObjects[ifname];
2234                         var seen = { };
2235
2236                         while (dev && dev.kind == 'alias')
2237                         {
2238                                 // loop
2239                                 if (seen[dev.ifname])
2240                                         return undefined;
2241
2242                                 var ifc = this.interfaceObjects[dev.sid];
2243
2244                                 seen[dev.ifname] = true;
2245                                 dev = ifc ? this.deviceObjects[ifc.l3dev] : undefined;
2246                         }
2247
2248                         return dev ? this.getDevice(dev.ifname) : undefined;
2249                 }
2250         };
2251
2252         this.NetworkModel.Device = Class.extend({
2253                 wifiModeStrings: {
2254                         ap: L.tr('Master'),
2255                         sta: L.tr('Client'),
2256                         adhoc: L.tr('Ad-Hoc'),
2257                         monitor: L.tr('Monitor'),
2258                         wds: L.tr('Static WDS')
2259                 },
2260
2261                 getStatus: function(key)
2262                 {
2263                         var s = L.NetworkModel.rpcCache.devstate[this.options.ifname];
2264
2265                         if (s)
2266                                 return key ? s[key] : s;
2267
2268                         return undefined;
2269                 },
2270
2271                 get: function(key)
2272                 {
2273                         var sid = this.options.sid;
2274                         var pkg = (this.options.kind == 'wifi') ? 'wireless' : 'network';
2275                         return L.uci.get(pkg, sid, key);
2276                 },
2277
2278                 set: function(key, val)
2279                 {
2280                         var sid = this.options.sid;
2281                         var pkg = (this.options.kind == 'wifi') ? 'wireless' : 'network';
2282                         return L.uci.set(pkg, sid, key, val);
2283                 },
2284
2285                 init: function()
2286                 {
2287                         if (typeof(this.options.type) == 'undefined')
2288                                 this.options.type = 1;
2289
2290                         if (typeof(this.options.kind) == 'undefined')
2291                                 this.options.kind = 'ethernet';
2292
2293                         if (typeof(this.options.networks) == 'undefined')
2294                                 this.options.networks = [ ];
2295                 },
2296
2297                 name: function()
2298                 {
2299                         return this.options.ifname;
2300                 },
2301
2302                 description: function()
2303                 {
2304                         switch (this.options.kind)
2305                         {
2306                         case 'alias':
2307                                 return L.tr('Alias for network "%s"').format(this.options.ifname.substring(1));
2308
2309                         case 'bridge':
2310                                 return L.tr('Network bridge');
2311
2312                         case 'ethernet':
2313                                 return L.tr('Network device');
2314
2315                         case 'tunnel':
2316                                 switch (this.options.type)
2317                                 {
2318                                 case 1: /* tuntap */
2319                                         return L.tr('TAP device');
2320
2321                                 case 512: /* PPP */
2322                                         return L.tr('PPP tunnel');
2323
2324                                 case 768: /* IP-IP Tunnel */
2325                                         return L.tr('IP-in-IP tunnel');
2326
2327                                 case 769: /* IP6-IP6 Tunnel */
2328                                         return L.tr('IPv6-in-IPv6 tunnel');
2329
2330                                 case 776: /* IPv6-in-IPv4 */
2331                                         return L.tr('IPv6-over-IPv4 tunnel');
2332                                         break;
2333
2334                                 case 778: /* GRE over IP */
2335                                         return L.tr('GRE-over-IP tunnel');
2336
2337                                 default:
2338                                         return L.tr('Tunnel device');
2339                                 }
2340
2341                         case 'vlan':
2342                                 return L.tr('VLAN %d on %s').format(this.options.vid, this.options.vsw.model);
2343
2344                         case 'wifi':
2345                                 var o = this.options;
2346                                 return L.trc('(Wifi-Mode) "(SSID)" on (radioX)', '%s "%h" on %s').format(
2347                                         o.wmode ? this.wifiModeStrings[o.wmode] : L.tr('Unknown mode'),
2348                                         o.wssid || '?', o.wdev
2349                                 );
2350                         }
2351
2352                         return L.tr('Unknown device');
2353                 },
2354
2355                 icon: function(up)
2356                 {
2357                         var kind = this.options.kind;
2358
2359                         if (kind == 'alias')
2360                                 kind = 'ethernet';
2361
2362                         if (typeof(up) == 'undefined')
2363                                 up = this.isUp();
2364
2365                         return L.globals.resource + '/icons/%s%s.png'.format(kind, up ? '' : '_disabled');
2366                 },
2367
2368                 isUp: function()
2369                 {
2370                         var l = L.NetworkModel.rpcCache.devlist;
2371
2372                         for (var i = 0; i < l.length; i++)
2373                                 if (l[i].device == this.options.ifname)
2374                                         return (l[i].is_up === true);
2375
2376                         return false;
2377                 },
2378
2379                 isAlias: function()
2380                 {
2381                         return (this.options.kind == 'alias');
2382                 },
2383
2384                 isBridge: function()
2385                 {
2386                         return (this.options.kind == 'bridge');
2387                 },
2388
2389                 isBridgeable: function()
2390                 {
2391                         return (this.options.type == 1 && this.options.kind != 'bridge');
2392                 },
2393
2394                 isWireless: function()
2395                 {
2396                         return (this.options.kind == 'wifi');
2397                 },
2398
2399                 isInNetwork: function(net)
2400                 {
2401                         if (!(net instanceof L.NetworkModel.Interface))
2402                                 net = L.NetworkModel.getInterface(net);
2403
2404                         if (net)
2405                         {
2406                                 if (net.options.l3dev == this.options.ifname ||
2407                                     net.options.l2dev == this.options.ifname)
2408                                         return true;
2409
2410                                 var dev = L.NetworkModel.deviceObjects[net.options.l2dev];
2411                                 if (dev && dev.kind == 'bridge' && dev.ports)
2412                                         return ($.inArray(this.options.ifname, dev.ports) > -1);
2413                         }
2414
2415                         return false;
2416                 },
2417
2418                 getMTU: function()
2419                 {
2420                         var dev = L.NetworkModel.rpcCache.devstate[this.options.ifname];
2421                         if (dev && !isNaN(dev.mtu))
2422                                 return dev.mtu;
2423
2424                         return undefined;
2425                 },
2426
2427                 getMACAddress: function()
2428                 {
2429                         if (this.options.type != 1)
2430                                 return undefined;
2431
2432                         var dev = L.NetworkModel.rpcCache.devstate[this.options.ifname];
2433                         if (dev && dev.macaddr)
2434                                 return dev.macaddr.toUpperCase();
2435
2436                         return undefined;
2437                 },
2438
2439                 getInterfaces: function()
2440                 {
2441                         return L.NetworkModel.getInterfacesByDevice(this.options.name);
2442                 },
2443
2444                 getStatistics: function()
2445                 {
2446                         var s = this.getStatus('statistics') || { };
2447                         return {
2448                                 rx_bytes: (s.rx_bytes || 0),
2449                                 tx_bytes: (s.tx_bytes || 0),
2450                                 rx_packets: (s.rx_packets || 0),
2451                                 tx_packets: (s.tx_packets || 0)
2452                         };
2453                 },
2454
2455                 getTrafficHistory: function()
2456                 {
2457                         var def = new Array(120);
2458
2459                         for (var i = 0; i < 120; i++)
2460                                 def[i] = 0;
2461
2462                         var h = L.NetworkModel.rpcCache.bwstate[this.options.ifname] || { };
2463                         return {
2464                                 rx_bytes: (h.rx_bytes || def),
2465                                 tx_bytes: (h.tx_bytes || def),
2466                                 rx_packets: (h.rx_packets || def),
2467                                 tx_packets: (h.tx_packets || def)
2468                         };
2469                 },
2470
2471                 removeFromInterface: function(iface)
2472                 {
2473                         if (!(iface instanceof L.NetworkModel.Interface))
2474                                 iface = L.NetworkModel.getInterface(iface);
2475
2476                         if (!iface)
2477                                 return;
2478
2479                         var ifnames = L.toArray(iface.get('ifname'));
2480                         if ($.inArray(this.options.ifname, ifnames) > -1)
2481                                 iface.set('ifname', L.filterArray(ifnames, this.options.ifname));
2482
2483                         if (this.options.kind != 'wifi')
2484                                 return;
2485
2486                         var networks = L.toArray(this.get('network'));
2487                         if ($.inArray(iface.name(), networks) > -1)
2488                                 this.set('network', L.filterArray(networks, iface.name()));
2489                 },
2490
2491                 attachToInterface: function(iface)
2492                 {
2493                         if (!(iface instanceof L.NetworkModel.Interface))
2494                                 iface = L.NetworkModel.getInterface(iface);
2495
2496                         if (!iface)
2497                                 return;
2498
2499                         if (this.options.kind != 'wifi')
2500                         {
2501                                 var ifnames = L.toArray(iface.get('ifname'));
2502                                 if ($.inArray(this.options.ifname, ifnames) < 0)
2503                                 {
2504                                         ifnames.push(this.options.ifname);
2505                                         iface.set('ifname', (ifnames.length > 1) ? ifnames : ifnames[0]);
2506                                 }
2507                         }
2508                         else
2509                         {
2510                                 var networks = L.toArray(this.get('network'));
2511                                 if ($.inArray(iface.name(), networks) < 0)
2512                                 {
2513                                         networks.push(iface.name());
2514                                         this.set('network', (networks.length > 1) ? networks : networks[0]);
2515                                 }
2516                         }
2517                 }
2518         });
2519
2520         this.NetworkModel.Interface = Class.extend({
2521                 getStatus: function(key)
2522                 {
2523                         var s = L.NetworkModel.rpcCache.ifstate;
2524
2525                         for (var i = 0; i < s.length; i++)
2526                                 if (s[i]['interface'] == this.options.name)
2527                                         return key ? s[i][key] : s[i];
2528
2529                         return undefined;
2530                 },
2531
2532                 get: function(key)
2533                 {
2534                         return L.uci.get('network', this.options.name, key);
2535                 },
2536
2537                 set: function(key, val)
2538                 {
2539                         return L.uci.set('network', this.options.name, key, val);
2540                 },
2541
2542                 name: function()
2543                 {
2544                         return this.options.name;
2545                 },
2546
2547                 protocol: function()
2548                 {
2549                         return (this.get('proto') || 'none');
2550                 },
2551
2552                 isUp: function()
2553                 {
2554                         return (this.getStatus('up') === true);
2555                 },
2556
2557                 isVirtual: function()
2558                 {
2559                         return (typeof(this.options.sid) != 'string');
2560                 },
2561
2562                 getProtocol: function()
2563                 {
2564                         var prname = this.get('proto') || 'none';
2565                         return L.NetworkModel.protocolHandlers[prname] || L.NetworkModel.protocolHandlers.none;
2566                 },
2567
2568                 getUptime: function()
2569                 {
2570                         var uptime = this.getStatus('uptime');
2571                         return isNaN(uptime) ? 0 : uptime;
2572                 },
2573
2574                 getDevice: function(resolveAlias)
2575                 {
2576                         if (this.options.l3dev)
2577                                 return L.NetworkModel.getDevice(this.options.l3dev);
2578
2579                         return undefined;
2580                 },
2581
2582                 getPhysdev: function()
2583                 {
2584                         if (this.options.l2dev)
2585                                 return L.NetworkModel.getDevice(this.options.l2dev);
2586
2587                         return undefined;
2588                 },
2589
2590                 getSubdevices: function()
2591                 {
2592                         var rv = [ ];
2593                         var dev = this.options.l2dev ?
2594                                 L.NetworkModel.deviceObjects[this.options.l2dev] : undefined;
2595
2596                         if (dev && dev.kind == 'bridge' && dev.ports && dev.ports.length)
2597                                 for (var i = 0; i < dev.ports.length; i++)
2598                                         rv.push(L.NetworkModel.getDevice(dev.ports[i]));
2599
2600                         return rv;
2601                 },
2602
2603                 getIPv4Addrs: function(mask)
2604                 {
2605                         var rv = [ ];
2606                         var addrs = this.getStatus('ipv4-address');
2607
2608                         if (addrs)
2609                                 for (var i = 0; i < addrs.length; i++)
2610                                         if (!mask)
2611                                                 rv.push(addrs[i].address);
2612                                         else
2613                                                 rv.push('%s/%d'.format(addrs[i].address, addrs[i].mask));
2614
2615                         return rv;
2616                 },
2617
2618                 getIPv6Addrs: function(mask)
2619                 {
2620                         var rv = [ ];
2621                         var addrs;
2622
2623                         addrs = this.getStatus('ipv6-address');
2624
2625                         if (addrs)
2626                                 for (var i = 0; i < addrs.length; i++)
2627                                         if (!mask)
2628                                                 rv.push(addrs[i].address);
2629                                         else
2630                                                 rv.push('%s/%d'.format(addrs[i].address, addrs[i].mask));
2631
2632                         addrs = this.getStatus('ipv6-prefix-assignment');
2633
2634                         if (addrs)
2635                                 for (var i = 0; i < addrs.length; i++)
2636                                         if (!mask)
2637                                                 rv.push('%s1'.format(addrs[i].address));
2638                                         else
2639                                                 rv.push('%s1/%d'.format(addrs[i].address, addrs[i].mask));
2640
2641                         return rv;
2642                 },
2643
2644                 getDNSAddrs: function()
2645                 {
2646                         var rv = [ ];
2647                         var addrs = this.getStatus('dns-server');
2648
2649                         if (addrs)
2650                                 for (var i = 0; i < addrs.length; i++)
2651                                         rv.push(addrs[i]);
2652
2653                         return rv;
2654                 },
2655
2656                 getIPv4DNS: function()
2657                 {
2658                         var rv = [ ];
2659                         var dns = this.getStatus('dns-server');
2660
2661                         if (dns)
2662                                 for (var i = 0; i < dns.length; i++)
2663                                         if (dns[i].indexOf(':') == -1)
2664                                                 rv.push(dns[i]);
2665
2666                         return rv;
2667                 },
2668
2669                 getIPv6DNS: function()
2670                 {
2671                         var rv = [ ];
2672                         var dns = this.getStatus('dns-server');
2673
2674                         if (dns)
2675                                 for (var i = 0; i < dns.length; i++)
2676                                         if (dns[i].indexOf(':') > -1)
2677                                                 rv.push(dns[i]);
2678
2679                         return rv;
2680                 },
2681
2682                 getIPv4Gateway: function()
2683                 {
2684                         var rt = this.getStatus('route');
2685
2686                         if (rt)
2687                                 for (var i = 0; i < rt.length; i++)
2688                                         if (rt[i].target == '0.0.0.0' && rt[i].mask == 0)
2689                                                 return rt[i].nexthop;
2690
2691                         return undefined;
2692                 },
2693
2694                 getIPv6Gateway: function()
2695                 {
2696                         var rt = this.getStatus('route');
2697
2698                         if (rt)
2699                                 for (var i = 0; i < rt.length; i++)
2700                                         if (rt[i].target == '::' && rt[i].mask == 0)
2701                                                 return rt[i].nexthop;
2702
2703                         return undefined;
2704                 },
2705
2706                 getStatistics: function()
2707                 {
2708                         var dev = this.getDevice() || new L.NetworkModel.Device({});
2709                         return dev.getStatistics();
2710                 },
2711
2712                 getTrafficHistory: function()
2713                 {
2714                         var dev = this.getDevice() || new L.NetworkModel.Device({});
2715                         return dev.getTrafficHistory();
2716                 },
2717
2718                 renderBadge: function()
2719                 {
2720                         var badge = $('<span />')
2721                                 .addClass('badge')
2722                                 .text('%s: '.format(this.name()));
2723
2724                         var dev = this.getDevice();
2725                         var subdevs = this.getSubdevices();
2726
2727