luci2: make sort / remove column in L.cbi.TableSection as narrow as possible
[project/luci2/ui.git] / luci2 / htdocs / luci2 / luci2.js
1 /*
2         LuCI2 - OpenWrt Web Interface
3
4         Copyright 2013 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                 _load: L.rpc.declare({
833                         object: 'uci',
834                         method: 'get',
835                         params: [ 'config' ],
836                         expect: { values: { } }
837                 }),
838
839                 _order: L.rpc.declare({
840                         object: 'uci',
841                         method: 'order',
842                         params: [ 'config', 'sections' ]
843                 }),
844
845                 _add: L.rpc.declare({
846                         object: 'uci',
847                         method: 'add',
848                         params: [ 'config', 'type', 'name', 'values' ],
849                         expect: { section: '' }
850                 }),
851
852                 _set: L.rpc.declare({
853                         object: 'uci',
854                         method: 'set',
855                         params: [ 'config', 'section', 'values' ]
856                 }),
857
858                 _delete: L.rpc.declare({
859                         object: 'uci',
860                         method: 'delete',
861                         params: [ 'config', 'section', 'options' ]
862                 }),
863
864                 _newid: function(conf)
865                 {
866                         var v = this.state.values;
867                         var n = this.state.creates;
868                         var sid;
869
870                         do {
871                                 sid = "new%06x".format(Math.random() * 0xFFFFFF);
872                         } while ((n[conf] && n[conf][sid]) || (v[conf] && v[conf][sid]));
873
874                         return sid;
875                 },
876
877                 load: function(packages)
878                 {
879                         var self = this;
880                         var seen = { };
881                         var pkgs = [ ];
882
883                         if (!$.isArray(packages))
884                                 packages = [ packages ];
885
886                         L.rpc.batch();
887
888                         for (var i = 0; i < packages.length; i++)
889                                 if (!seen[packages[i]] && !self.state.values[packages[i]])
890                                 {
891                                         pkgs.push(packages[i]);
892                                         seen[packages[i]] = true;
893                                         self._load(packages[i]);
894                                 }
895
896                         return L.rpc.flush().then(function(responses) {
897                                 for (var i = 0; i < responses.length; i++)
898                                         self.state.values[pkgs[i]] = responses[i];
899
900                                 return pkgs;
901                         });
902                 },
903
904                 unload: function(packages)
905                 {
906                         if (!$.isArray(packages))
907                                 packages = [ packages ];
908
909                         for (var i = 0; i < packages.length; i++)
910                         {
911                                 delete this.state.values[packages[i]];
912                                 delete this.state.creates[packages[i]];
913                                 delete this.state.changes[packages[i]];
914                                 delete this.state.deletes[packages[i]];
915                         }
916                 },
917
918                 add: function(conf, type, name)
919                 {
920                         var n = this.state.creates;
921                         var sid = this._newid(conf);
922
923                         if (!n[conf])
924                                 n[conf] = { };
925
926                         n[conf][sid] = {
927                                 '.type':      type,
928                                 '.name':      sid,
929                                 '.create':    name,
930                                 '.anonymous': !name,
931                                 '.index':     1000 + this.state.newidx++
932                         };
933
934                         return sid;
935                 },
936
937                 remove: function(conf, sid)
938                 {
939                         var n = this.state.creates;
940                         var c = this.state.changes;
941                         var d = this.state.deletes;
942
943                         /* requested deletion of a just created section */
944                         if (n[conf] && n[conf][sid])
945                         {
946                                 delete n[conf][sid];
947                         }
948                         else
949                         {
950                                 if (c[conf])
951                                         delete c[conf][sid];
952
953                                 if (!d[conf])
954                                         d[conf] = { };
955
956                                 d[conf][sid] = true;
957                         }
958                 },
959
960                 sections: function(conf, type, cb)
961                 {
962                         var sa = [ ];
963                         var v = this.state.values[conf];
964                         var n = this.state.creates[conf];
965                         var c = this.state.changes[conf];
966                         var d = this.state.deletes[conf];
967
968                         if (!v)
969                                 return sa;
970
971                         for (var s in v)
972                                 if (!d || d[s] !== true)
973                                         if (!type || v[s]['.type'] == type)
974                                                 sa.push($.extend({ }, v[s], c ? c[s] : undefined));
975
976                         if (n)
977                                 for (var s in n)
978                                         if (!type || n[s]['.type'] == type)
979                                                 sa.push(n[s]);
980
981                         sa.sort(function(a, b) {
982                                 return a['.index'] - b['.index'];
983                         });
984
985                         for (var i = 0; i < sa.length; i++)
986                                 sa[i]['.index'] = i;
987
988                         if (typeof(cb) == 'function')
989                                 for (var i = 0; i < sa.length; i++)
990                                         cb.call(this, sa[i], sa[i]['.name']);
991
992                         return sa;
993                 },
994
995                 get: function(conf, sid, opt)
996                 {
997                         var v = this.state.values;
998                         var n = this.state.creates;
999                         var c = this.state.changes;
1000                         var d = this.state.deletes;
1001
1002                         if (typeof(sid) == 'undefined')
1003                                 return undefined;
1004
1005                         /* requested option in a just created section */
1006                         if (n[conf] && n[conf][sid])
1007                         {
1008                                 if (!n[conf])
1009                                         return undefined;
1010
1011                                 if (typeof(opt) == 'undefined')
1012                                         return n[conf][sid];
1013
1014                                 return n[conf][sid][opt];
1015                         }
1016
1017                         /* requested an option value */
1018                         if (typeof(opt) != 'undefined')
1019                         {
1020                                 /* check whether option was deleted */
1021                                 if (d[conf] && d[conf][sid])
1022                                 {
1023                                         if (d[conf][sid] === true)
1024                                                 return undefined;
1025
1026                                         for (var i = 0; i < d[conf][sid].length; i++)
1027                                                 if (d[conf][sid][i] == opt)
1028                                                         return undefined;
1029                                 }
1030
1031                                 /* check whether option was changed */
1032                                 if (c[conf] && c[conf][sid] && typeof(c[conf][sid][opt]) != 'undefined')
1033                                         return c[conf][sid][opt];
1034
1035                                 /* return base value */
1036                                 if (v[conf] && v[conf][sid])
1037                                         return v[conf][sid][opt];
1038
1039                                 return undefined;
1040                         }
1041
1042                         /* requested an entire section */
1043                         if (v[conf])
1044                                 return v[conf][sid];
1045
1046                         return undefined;
1047                 },
1048
1049                 set: function(conf, sid, opt, val)
1050                 {
1051                         var v = this.state.values;
1052                         var n = this.state.creates;
1053                         var c = this.state.changes;
1054                         var d = this.state.deletes;
1055
1056                         if (typeof(sid) == 'undefined' ||
1057                             typeof(opt) == 'undefined' ||
1058                             opt.charAt(0) == '.')
1059                                 return;
1060
1061                         if (n[conf] && n[conf][sid])
1062                         {
1063                                 if (typeof(val) != 'undefined')
1064                                         n[conf][sid][opt] = val;
1065                                 else
1066                                         delete n[conf][sid][opt];
1067                         }
1068                         else if (typeof(val) != 'undefined')
1069                         {
1070                                 /* do not set within deleted section */
1071                                 if (d[conf] && d[conf][sid] === true)
1072                                         return;
1073
1074                                 /* only set in existing sections */
1075                                 if (!v[conf] || !v[conf][sid])
1076                                         return;
1077
1078                                 if (!c[conf])
1079                                         c[conf] = { };
1080
1081                                 if (!c[conf][sid])
1082                                         c[conf][sid] = { };
1083
1084                                 /* undelete option */
1085                                 if (d[conf] && d[conf][sid])
1086                                         d[conf][sid] = L.filterArray(d[conf][sid], opt);
1087
1088                                 c[conf][sid][opt] = val;
1089                         }
1090                         else
1091                         {
1092                                 /* only delete in existing sections */
1093                                 if (!v[conf] || !v[conf][sid])
1094                                         return;
1095
1096                                 if (!d[conf])
1097                                         d[conf] = { };
1098
1099                                 if (!d[conf][sid])
1100                                         d[conf][sid] = [ ];
1101
1102                                 if (d[conf][sid] !== true)
1103                                         d[conf][sid].push(opt);
1104                         }
1105                 },
1106
1107                 unset: function(conf, sid, opt)
1108                 {
1109                         return this.set(conf, sid, opt, undefined);
1110                 },
1111
1112                 get_first: function(conf, type, opt)
1113                 {
1114                         var sid = undefined;
1115
1116                         L.uci.sections(conf, type, function(s) {
1117                                 if (typeof(sid) != 'string')
1118                                         sid = s['.name'];
1119                         });
1120
1121                         return this.get(conf, sid, opt);
1122                 },
1123
1124                 set_first: function(conf, type, opt, val)
1125                 {
1126                         var sid = undefined;
1127
1128                         L.uci.sections(conf, type, function(s) {
1129                                 if (typeof(sid) != 'string')
1130                                         sid = s['.name'];
1131                         });
1132
1133                         return this.set(conf, sid, opt, val);
1134                 },
1135
1136                 unset_first: function(conf, type, opt)
1137                 {
1138                         return this.set_first(conf, type, opt, undefined);
1139                 },
1140
1141                 _reload: function()
1142                 {
1143                         var pkgs = [ ];
1144
1145                         for (var pkg in this.state.values)
1146                                 pkgs.push(pkg);
1147
1148                         this.init();
1149
1150                         return this.load(pkgs);
1151                 },
1152
1153                 _reorder: function()
1154                 {
1155                         var v = this.state.values;
1156                         var n = this.state.creates;
1157                         var r = this.state.reorder;
1158
1159                         if ($.isEmptyObject(r))
1160                                 return L.deferrable();
1161
1162                         L.rpc.batch();
1163
1164                         /*
1165                          gather all created and existing sections, sort them according
1166                          to their index value and issue an uci order call
1167                         */
1168                         for (var c in r)
1169                         {
1170                                 var o = [ ];
1171
1172                                 if (n[c])
1173                                         for (var s in n[c])
1174                                                 o.push(n[c][s]);
1175
1176                                 for (var s in v[c])
1177                                         o.push(v[c][s]);
1178
1179                                 if (o.length > 0)
1180                                 {
1181                                         o.sort(function(a, b) {
1182                                                 return (a['.index'] - b['.index']);
1183                                         });
1184
1185                                         var sids = [ ];
1186
1187                                         for (var i = 0; i < o.length; i++)
1188                                                 sids.push(o[i]['.name']);
1189
1190                                         this._order(c, sids);
1191                                 }
1192                         }
1193
1194                         this.state.reorder = { };
1195                         return L.rpc.flush();
1196                 },
1197
1198                 swap: function(conf, sid1, sid2)
1199                 {
1200                         var s1 = this.get(conf, sid1);
1201                         var s2 = this.get(conf, sid2);
1202                         var n1 = s1 ? s1['.index'] : NaN;
1203                         var n2 = s2 ? s2['.index'] : NaN;
1204
1205                         if (isNaN(n1) || isNaN(n2))
1206                                 return false;
1207
1208                         s1['.index'] = n2;
1209                         s2['.index'] = n1;
1210
1211                         this.state.reorder[conf] = true;
1212
1213                         return true;
1214                 },
1215
1216                 save: function()
1217                 {
1218                         L.rpc.batch();
1219
1220                         var v = this.state.values;
1221                         var n = this.state.creates;
1222                         var c = this.state.changes;
1223                         var d = this.state.deletes;
1224
1225                         var self = this;
1226                         var snew = [ ];
1227                         var pkgs = { };
1228
1229                         if (n)
1230                                 for (var conf in n)
1231                                 {
1232                                         for (var sid in n[conf])
1233                                         {
1234                                                 var r = {
1235                                                         config: conf,
1236                                                         values: { }
1237                                                 };
1238
1239                                                 for (var k in n[conf][sid])
1240                                                 {
1241                                                         if (k == '.type')
1242                                                                 r.type = n[conf][sid][k];
1243                                                         else if (k == '.create')
1244                                                                 r.name = n[conf][sid][k];
1245                                                         else if (k.charAt(0) != '.')
1246                                                                 r.values[k] = n[conf][sid][k];
1247                                                 }
1248
1249                                                 snew.push(n[conf][sid]);
1250
1251                                                 self._add(r.config, r.type, r.name, r.values);
1252                                         }
1253
1254                                         pkgs[conf] = true;
1255                                 }
1256
1257                         if (c)
1258                                 for (var conf in c)
1259                                 {
1260                                         for (var sid in c[conf])
1261                                                 self._set(conf, sid, c[conf][sid]);
1262
1263                                         pkgs[conf] = true;
1264                                 }
1265
1266                         if (d)
1267                                 for (var conf in d)
1268                                 {
1269                                         for (var sid in d[conf])
1270                                         {
1271                                                 var o = d[conf][sid];
1272                                                 self._delete(conf, sid, (o === true) ? undefined : o);
1273                                         }
1274
1275                                         pkgs[conf] = true;
1276                                 }
1277
1278                         return L.rpc.flush().then(function(responses) {
1279                                 /*
1280                                  array "snew" holds references to the created uci sections,
1281                                  use it to assign the returned names of the new sections
1282                                 */
1283                                 for (var i = 0; i < snew.length; i++)
1284                                         snew[i]['.name'] = responses[i];
1285
1286                                 return self._reorder();
1287                         }).then(function() {
1288                                 pkgs = L.toArray(pkgs);
1289
1290                                 self.unload(pkgs);
1291
1292                                 return self.load(pkgs);
1293                         });
1294                 },
1295
1296                 _apply: L.rpc.declare({
1297                         object: 'uci',
1298                         method: 'apply',
1299                         params: [ 'timeout', 'rollback' ]
1300                 }),
1301
1302                 _confirm: L.rpc.declare({
1303                         object: 'uci',
1304                         method: 'confirm'
1305                 }),
1306
1307                 apply: function(timeout)
1308                 {
1309                         var self = this;
1310                         var date = new Date();
1311                         var deferred = $.Deferred();
1312
1313                         if (typeof(timeout) != 'number' || timeout < 1)
1314                                 timeout = 10;
1315
1316                         self._apply(timeout, true).then(function(rv) {
1317                                 if (rv != 0)
1318                                 {
1319                                         deferred.rejectWith(self, [ rv ]);
1320                                         return;
1321                                 }
1322
1323                                 var try_deadline = date.getTime() + 1000 * timeout;
1324                                 var try_confirm = function()
1325                                 {
1326                                         return self._confirm().then(function(rv) {
1327                                                 if (rv != 0)
1328                                                 {
1329                                                         if (date.getTime() < try_deadline)
1330                                                                 window.setTimeout(try_confirm, 250);
1331                                                         else
1332                                                                 deferred.rejectWith(self, [ rv ]);
1333
1334                                                         return;
1335                                                 }
1336
1337                                                 deferred.resolveWith(self, [ rv ]);
1338                                         });
1339                                 };
1340
1341                                 window.setTimeout(try_confirm, 1000);
1342                         });
1343
1344                         return deferred;
1345                 },
1346
1347                 changes: L.rpc.declare({
1348                         object: 'uci',
1349                         method: 'changes',
1350                         expect: { changes: { } }
1351                 }),
1352
1353                 readable: function(conf)
1354                 {
1355                         return L.session.hasACL('uci', conf, 'read');
1356                 },
1357
1358                 writable: function(conf)
1359                 {
1360                         return L.session.hasACL('uci', conf, 'write');
1361                 }
1362         });
1363
1364         this.uci = new this.UCIContext();
1365
1366         this.wireless = {
1367                 listDeviceNames: L.rpc.declare({
1368                         object: 'iwinfo',
1369                         method: 'devices',
1370                         expect: { 'devices': [ ] },
1371                         filter: function(data) {
1372                                 data.sort();
1373                                 return data;
1374                         }
1375                 }),
1376
1377                 getDeviceStatus: L.rpc.declare({
1378                         object: 'iwinfo',
1379                         method: 'info',
1380                         params: [ 'device' ],
1381                         expect: { '': { } },
1382                         filter: function(data, params) {
1383                                 if (!$.isEmptyObject(data))
1384                                 {
1385                                         data['device'] = params['device'];
1386                                         return data;
1387                                 }
1388                                 return undefined;
1389                         }
1390                 }),
1391
1392                 getAssocList: L.rpc.declare({
1393                         object: 'iwinfo',
1394                         method: 'assoclist',
1395                         params: [ 'device' ],
1396                         expect: { results: [ ] },
1397                         filter: function(data, params) {
1398                                 for (var i = 0; i < data.length; i++)
1399                                         data[i]['device'] = params['device'];
1400
1401                                 data.sort(function(a, b) {
1402                                         if (a.bssid < b.bssid)
1403                                                 return -1;
1404                                         else if (a.bssid > b.bssid)
1405                                                 return 1;
1406                                         else
1407                                                 return 0;
1408                                 });
1409
1410                                 return data;
1411                         }
1412                 }),
1413
1414                 getWirelessStatus: function() {
1415                         return this.listDeviceNames().then(function(names) {
1416                                 L.rpc.batch();
1417
1418                                 for (var i = 0; i < names.length; i++)
1419                                         L.wireless.getDeviceStatus(names[i]);
1420
1421                                 return L.rpc.flush();
1422                         }).then(function(networks) {
1423                                 var rv = { };
1424
1425                                 var phy_attrs = [
1426                                         'country', 'channel', 'frequency', 'frequency_offset',
1427                                         'txpower', 'txpower_offset', 'hwmodes', 'hardware', 'phy'
1428                                 ];
1429
1430                                 var net_attrs = [
1431                                         'ssid', 'bssid', 'mode', 'quality', 'quality_max',
1432                                         'signal', 'noise', 'bitrate', 'encryption'
1433                                 ];
1434
1435                                 for (var i = 0; i < networks.length; i++)
1436                                 {
1437                                         var phy = rv[networks[i].phy] || (
1438                                                 rv[networks[i].phy] = { networks: [ ] }
1439                                         );
1440
1441                                         var net = {
1442                                                 device: networks[i].device
1443                                         };
1444
1445                                         for (var j = 0; j < phy_attrs.length; j++)
1446                                                 phy[phy_attrs[j]] = networks[i][phy_attrs[j]];
1447
1448                                         for (var j = 0; j < net_attrs.length; j++)
1449                                                 net[net_attrs[j]] = networks[i][net_attrs[j]];
1450
1451                                         phy.networks.push(net);
1452                                 }
1453
1454                                 return rv;
1455                         });
1456                 },
1457
1458                 getAssocLists: function()
1459                 {
1460                         return this.listDeviceNames().then(function(names) {
1461                                 L.rpc.batch();
1462
1463                                 for (var i = 0; i < names.length; i++)
1464                                         L.wireless.getAssocList(names[i]);
1465
1466                                 return L.rpc.flush();
1467                         }).then(function(assoclists) {
1468                                 var rv = [ ];
1469
1470                                 for (var i = 0; i < assoclists.length; i++)
1471                                         for (var j = 0; j < assoclists[i].length; j++)
1472                                                 rv.push(assoclists[i][j]);
1473
1474                                 return rv;
1475                         });
1476                 },
1477
1478                 formatEncryption: function(enc)
1479                 {
1480                         var format_list = function(l, s)
1481                         {
1482                                 var rv = [ ];
1483                                 for (var i = 0; i < l.length; i++)
1484                                         rv.push(l[i].toUpperCase());
1485                                 return rv.join(s ? s : ', ');
1486                         }
1487
1488                         if (!enc || !enc.enabled)
1489                                 return L.tr('None');
1490
1491                         if (enc.wep)
1492                         {
1493                                 if (enc.wep.length == 2)
1494                                         return L.tr('WEP Open/Shared') + ' (%s)'.format(format_list(enc.ciphers, ', '));
1495                                 else if (enc.wep[0] == 'shared')
1496                                         return L.tr('WEP Shared Auth') + ' (%s)'.format(format_list(enc.ciphers, ', '));
1497                                 else
1498                                         return L.tr('WEP Open System') + ' (%s)'.format(format_list(enc.ciphers, ', '));
1499                         }
1500                         else if (enc.wpa)
1501                         {
1502                                 if (enc.wpa.length == 2)
1503                                         return L.tr('mixed WPA/WPA2') + ' %s (%s)'.format(
1504                                                 format_list(enc.authentication, '/'),
1505                                                 format_list(enc.ciphers, ', ')
1506                                         );
1507                                 else if (enc.wpa[0] == 2)
1508                                         return 'WPA2 %s (%s)'.format(
1509                                                 format_list(enc.authentication, '/'),
1510                                                 format_list(enc.ciphers, ', ')
1511                                         );
1512                                 else
1513                                         return 'WPA %s (%s)'.format(
1514                                                 format_list(enc.authentication, '/'),
1515                                                 format_list(enc.ciphers, ', ')
1516                                         );
1517                         }
1518
1519                         return L.tr('Unknown');
1520                 }
1521         };
1522
1523         this.firewall = {
1524                 getZoneColor: function(zone)
1525                 {
1526                         if ($.isPlainObject(zone))
1527                                 zone = zone.name;
1528
1529                         if (zone == 'lan')
1530                                 return '#90f090';
1531                         else if (zone == 'wan')
1532                                 return '#f09090';
1533
1534                         for (var i = 0, hash = 0;
1535                                  i < zone.length;
1536                                  hash = zone.charCodeAt(i++) + ((hash << 5) - hash));
1537
1538                         for (var i = 0, color = '#';
1539                                  i < 3;
1540                                  color += ('00' + ((hash >> i++ * 8) & 0xFF).tostring(16)).slice(-2));
1541
1542                         return color;
1543                 },
1544
1545                 findZoneByNetwork: function(network)
1546                 {
1547                         var self = this;
1548                         var zone = undefined;
1549
1550                         return L.uci.sections('firewall', 'zone', function(z) {
1551                                 if (!z.name || !z.network)
1552                                         return;
1553
1554                                 if (!$.isArray(z.network))
1555                                         z.network = z.network.split(/\s+/);
1556
1557                                 for (var i = 0; i < z.network.length; i++)
1558                                 {
1559                                         if (z.network[i] == network)
1560                                         {
1561                                                 zone = z;
1562                                                 break;
1563                                         }
1564                                 }
1565                         }).then(function() {
1566                                 if (zone)
1567                                         zone.color = self.getZoneColor(zone);
1568
1569                                 return zone;
1570                         });
1571                 }
1572         };
1573
1574         this.NetworkModel = {
1575                 _device_blacklist: [
1576                         /^gre[0-9]+$/,
1577                         /^gretap[0-9]+$/,
1578                         /^ifb[0-9]+$/,
1579                         /^ip6tnl[0-9]+$/,
1580                         /^sit[0-9]+$/,
1581                         /^wlan[0-9]+\.sta[0-9]+$/
1582                 ],
1583
1584                 _cache_functions: [
1585                         'protolist', 0, L.rpc.declare({
1586                                 object: 'network',
1587                                 method: 'get_proto_handlers',
1588                                 expect: { '': { } }
1589                         }),
1590                         'ifstate', 1, L.rpc.declare({
1591                                 object: 'network.interface',
1592                                 method: 'dump',
1593                                 expect: { 'interface': [ ] }
1594                         }),
1595                         'devstate', 2, L.rpc.declare({
1596                                 object: 'network.device',
1597                                 method: 'status',
1598                                 expect: { '': { } }
1599                         }),
1600                         'wifistate', 0, L.rpc.declare({
1601                                 object: 'network.wireless',
1602                                 method: 'status',
1603                                 expect: { '': { } }
1604                         }),
1605                         'bwstate', 2, L.rpc.declare({
1606                                 object: 'luci2.network.bwmon',
1607                                 method: 'statistics',
1608                                 expect: { 'statistics': { } }
1609                         }),
1610                         'devlist', 2, L.rpc.declare({
1611                                 object: 'luci2.network',
1612                                 method: 'device_list',
1613                                 expect: { 'devices': [ ] }
1614                         }),
1615                         'swlist', 0, L.rpc.declare({
1616                                 object: 'luci2.network',
1617                                 method: 'switch_list',
1618                                 expect: { 'switches': [ ] }
1619                         })
1620                 ],
1621
1622                 _fetch_protocol: function(proto)
1623                 {
1624                         var url = L.globals.resource + '/proto/' + proto + '.js';
1625                         var self = L.NetworkModel;
1626
1627                         var def = $.Deferred();
1628
1629                         $.ajax(url, {
1630                                 method: 'GET',
1631                                 cache: true,
1632                                 dataType: 'text'
1633                         }).then(function(data) {
1634                                 try {
1635                                         var protoConstructorSource = (
1636                                                 '(function(L, $) { ' +
1637                                                         'return %s' +
1638                                                 '})(L, $);\n\n' +
1639                                                 '//@ sourceURL=%s'
1640                                         ).format(data, url);
1641
1642                                         var protoClass = eval(protoConstructorSource);
1643
1644                                         self._protos[proto] = new protoClass();
1645                                 }
1646                                 catch(e) {
1647                                         alert('Unable to instantiate proto "%s": %s'.format(url, e));
1648                                 };
1649
1650                                 def.resolve();
1651                         }).fail(function() {
1652                                 def.resolve();
1653                         });
1654
1655                         return def;
1656                 },
1657
1658                 _fetch_protocols: function()
1659                 {
1660                         var self = L.NetworkModel;
1661                         var deferreds = [
1662                                 self._fetch_protocol('none')
1663                         ];
1664
1665                         for (var proto in self._cache.protolist)
1666                                 deferreds.push(self._fetch_protocol(proto));
1667
1668                         return $.when.apply($, deferreds);
1669                 },
1670
1671                 _fetch_swstate: L.rpc.declare({
1672                         object: 'luci2.network',
1673                         method: 'switch_info',
1674                         params: [ 'switch' ],
1675                         expect: { 'info': { } }
1676                 }),
1677
1678                 _fetch_swstate_cb: function(responses) {
1679                         var self = L.NetworkModel;
1680                         var swlist = self._cache.swlist;
1681                         var swstate = self._cache.swstate = { };
1682
1683                         for (var i = 0; i < responses.length; i++)
1684                                 swstate[swlist[i]] = responses[i];
1685                 },
1686
1687                 _fetch_cache_cb: function(level)
1688                 {
1689                         var self = L.NetworkModel;
1690                         var name = '_fetch_cache_cb_' + level;
1691
1692                         return self[name] || (
1693                                 self[name] = function(responses)
1694                                 {
1695                                         for (var i = 0; i < self._cache_functions.length; i += 3)
1696                                                 if (!level || self._cache_functions[i + 1] == level)
1697                                                         self._cache[self._cache_functions[i]] = responses.shift();
1698
1699                                         if (!level)
1700                                         {
1701                                                 L.rpc.batch();
1702
1703                                                 for (var i = 0; i < self._cache.swlist.length; i++)
1704                                                         self._fetch_swstate(self._cache.swlist[i]);
1705
1706                                                 return L.rpc.flush().then(self._fetch_swstate_cb);
1707                                         }
1708
1709                                         return L.deferrable();
1710                                 }
1711                         );
1712                 },
1713
1714                 _fetch_cache: function(level)
1715                 {
1716                         var self = L.NetworkModel;
1717
1718                         return L.uci.load(['network', 'wireless']).then(function() {
1719                                 L.rpc.batch();
1720
1721                                 for (var i = 0; i < self._cache_functions.length; i += 3)
1722                                         if (!level || self._cache_functions[i + 1] == level)
1723                                                 self._cache_functions[i + 2]();
1724
1725                                 return L.rpc.flush().then(self._fetch_cache_cb(level || 0));
1726                         });
1727                 },
1728
1729                 _get: function(pkg, sid, key)
1730                 {
1731                         return L.uci.get(pkg, sid, key);
1732                 },
1733
1734                 _set: function(pkg, sid, key, val)
1735                 {
1736                         return L.uci.set(pkg, sid, key, val);
1737                 },
1738
1739                 _is_blacklisted: function(dev)
1740                 {
1741                         for (var i = 0; i < this._device_blacklist.length; i++)
1742                                 if (dev.match(this._device_blacklist[i]))
1743                                         return true;
1744
1745                         return false;
1746                 },
1747
1748                 _sort_devices: function(a, b)
1749                 {
1750                         if (a.options.kind < b.options.kind)
1751                                 return -1;
1752                         else if (a.options.kind > b.options.kind)
1753                                 return 1;
1754
1755                         if (a.options.name < b.options.name)
1756                                 return -1;
1757                         else if (a.options.name > b.options.name)
1758                                 return 1;
1759
1760                         return 0;
1761                 },
1762
1763                 _get_dev: function(ifname)
1764                 {
1765                         var alias = (ifname.charAt(0) == '@');
1766                         return this._devs[ifname] || (
1767                                 this._devs[ifname] = {
1768                                         ifname:  ifname,
1769                                         kind:    alias ? 'alias' : 'ethernet',
1770                                         type:    alias ? 0 : 1,
1771                                         up:      false,
1772                                         changed: { }
1773                                 }
1774                         );
1775                 },
1776
1777                 _get_iface: function(name)
1778                 {
1779                         return this._ifaces[name] || (
1780                                 this._ifaces[name] = {
1781                                         name:    name,
1782                                         proto:   this._protos.none,
1783                                         changed: { }
1784                                 }
1785                         );
1786                 },
1787
1788                 _parse_devices: function()
1789                 {
1790                         var self = L.NetworkModel;
1791                         var wificount = { };
1792
1793                         for (var ifname in self._cache.devstate)
1794                         {
1795                                 if (self._is_blacklisted(ifname))
1796                                         continue;
1797
1798                                 var dev = self._cache.devstate[ifname];
1799                                 var entry = self._get_dev(ifname);
1800
1801                                 entry.up = dev.up;
1802
1803                                 switch (dev.type)
1804                                 {
1805                                 case 'IP tunnel':
1806                                         entry.kind = 'tunnel';
1807                                         break;
1808
1809                                 case 'Bridge':
1810                                         entry.kind = 'bridge';
1811                                         //entry.ports = dev['bridge-members'].sort();
1812                                         break;
1813                                 }
1814                         }
1815
1816                         for (var i = 0; i < self._cache.devlist.length; i++)
1817                         {
1818                                 var dev = self._cache.devlist[i];
1819
1820                                 if (self._is_blacklisted(dev.device))
1821                                         continue;
1822
1823                                 var entry = self._get_dev(dev.device);
1824
1825                                 entry.up   = dev.is_up;
1826                                 entry.type = dev.type;
1827
1828                                 switch (dev.type)
1829                                 {
1830                                 case 1: /* Ethernet */
1831                                         if (dev.is_bridge)
1832                                                 entry.kind = 'bridge';
1833                                         else if (dev.is_tuntap)
1834                                                 entry.kind = 'tunnel';
1835                                         else if (dev.is_wireless)
1836                                                 entry.kind = 'wifi';
1837                                         break;
1838
1839                                 case 512: /* PPP */
1840                                 case 768: /* IP-IP Tunnel */
1841                                 case 769: /* IP6-IP6 Tunnel */
1842                                 case 776: /* IPv6-in-IPv4 */
1843                                 case 778: /* GRE over IP */
1844                                         entry.kind = 'tunnel';
1845                                         break;
1846                                 }
1847                         }
1848
1849                         var net = L.uci.sections('network');
1850                         for (var i = 0; i < net.length; i++)
1851                         {
1852                                 var s = net[i];
1853                                 var sid = s['.name'];
1854
1855                                 if (s['.type'] == 'device' && s.name)
1856                                 {
1857                                         var entry = self._get_dev(s.name);
1858
1859                                         switch (s.type)
1860                                         {
1861                                         case 'macvlan':
1862                                         case 'tunnel':
1863                                                 entry.kind = 'tunnel';
1864                                                 break;
1865                                         }
1866
1867                                         entry.sid = sid;
1868                                 }
1869                                 else if (s['.type'] == 'interface' && !s['.anonymous'] && s.ifname)
1870                                 {
1871                                         var ifnames = L.toArray(s.ifname);
1872
1873                                         for (var j = 0; j < ifnames.length; j++)
1874                                                 self._get_dev(ifnames[j]);
1875
1876                                         if (s['.name'] != 'loopback')
1877                                         {
1878                                                 var entry = self._get_dev('@%s'.format(s['.name']));
1879
1880                                                 entry.type = 0;
1881                                                 entry.kind = 'alias';
1882                                                 entry.sid  = sid;
1883                                         }
1884                                 }
1885                                 else if (s['.type'] == 'switch_vlan' && s.device)
1886                                 {
1887                                         var sw = self._cache.swstate[s.device];
1888                                         var vid = parseInt(s.vid || s.vlan);
1889                                         var ports = L.toArray(s.ports);
1890
1891                                         if (!sw || !ports.length || isNaN(vid))
1892                                                 continue;
1893
1894                                         var ifname = undefined;
1895
1896                                         for (var j = 0; j < ports.length; j++)
1897                                         {
1898                                                 var port = parseInt(ports[j]);
1899                                                 var tag = (ports[j].replace(/[^tu]/g, '') == 't');
1900
1901                                                 if (port == sw.cpu_port)
1902                                                 {
1903                                                         // XXX: need a way to map switch to netdev
1904                                                         if (tag)
1905                                                                 ifname = 'eth0.%d'.format(vid);
1906                                                         else
1907                                                                 ifname = 'eth0';
1908
1909                                                         break;
1910                                                 }
1911                                         }
1912
1913                                         if (!ifname)
1914                                                 continue;
1915
1916                                         var entry = self._get_dev(ifname);
1917
1918                                         entry.kind = 'vlan';
1919                                         entry.sid  = sid;
1920                                         entry.vsw  = sw;
1921                                         entry.vid  = vid;
1922                                 }
1923                         }
1924
1925                         var wifi = L.uci.sections('wireless');
1926                         for (var i = 0; i < wifi.length; i++)
1927                         {
1928                                 var s = wifi[i];
1929                                 var sid = s['.name'];
1930
1931                                 if (s['.type'] == 'wifi-iface' && s.device)
1932                                 {
1933                                         var r = parseInt(s.device.replace(/^[^0-9]+/, ''));
1934                                         var n = wificount[s.device] = (wificount[s.device] || 0) + 1;
1935                                         var id = 'radio%d.network%d'.format(r, n);
1936                                         var ifname = id;
1937
1938                                         if (self._cache.wifistate[s.device])
1939                                         {
1940                                                 var ifcs = self._cache.wifistate[s.device].interfaces;
1941                                                 for (var ifc in ifcs)
1942                                                 {
1943                                                         if (ifcs[ifc].section == sid)
1944                                                         {
1945                                                                 ifname = ifcs[ifc].ifname;
1946                                                                 break;
1947                                                         }
1948                                                 }
1949                                         }
1950
1951                                         var entry = self._get_dev(ifname);
1952
1953                                         entry.kind   = 'wifi';
1954                                         entry.sid    = sid;
1955                                         entry.wid    = id;
1956                                         entry.wdev   = s.device;
1957                                         entry.wmode  = s.mode;
1958                                         entry.wssid  = s.ssid;
1959                                         entry.wbssid = s.bssid;
1960                                 }
1961                         }
1962
1963                         for (var i = 0; i < net.length; i++)
1964                         {
1965                                 var s = net[i];
1966                                 var sid = s['.name'];
1967
1968                                 if (s['.type'] == 'interface' && !s['.anonymous'] && s.type == 'bridge')
1969                                 {
1970                                         var ifnames = L.toArray(s.ifname);
1971
1972                                         for (var ifname in self._devs)
1973                                         {
1974                                                 var dev = self._devs[ifname];
1975
1976                                                 if (dev.kind != 'wifi')
1977                                                         continue;
1978
1979                                                 var wnets = L.toArray(L.uci.get('wireless', dev.sid, 'network'));
1980                                                 if ($.inArray(sid, wnets) > -1)
1981                                                         ifnames.push(ifname);
1982                                         }
1983
1984                                         entry = self._get_dev('br-%s'.format(s['.name']));
1985                                         entry.type  = 1;
1986                                         entry.kind  = 'bridge';
1987                                         entry.sid   = sid;
1988                                         entry.ports = ifnames.sort();
1989                                 }
1990                         }
1991                 },
1992
1993                 _parse_interfaces: function()
1994                 {
1995                         var self = L.NetworkModel;
1996                         var net = L.uci.sections('network');
1997
1998                         for (var i = 0; i < net.length; i++)
1999                         {
2000                                 var s = net[i];
2001                                 var sid = s['.name'];
2002
2003                                 if (s['.type'] == 'interface' && !s['.anonymous'] && s.proto)
2004                                 {
2005                                         var entry = self._get_iface(s['.name']);
2006                                         var proto = self._protos[s.proto] || self._protos.none;
2007
2008                                         var l3dev = undefined;
2009                                         var l2dev = undefined;
2010
2011                                         var ifnames = L.toArray(s.ifname);
2012
2013                                         for (var ifname in self._devs)
2014                                         {
2015                                                 var dev = self._devs[ifname];
2016
2017                                                 if (dev.kind != 'wifi')
2018                                                         continue;
2019
2020                                                 var wnets = L.toArray(L.uci.get('wireless', dev.sid, 'network'));
2021                                                 if ($.inArray(entry.name, wnets) > -1)
2022                                                         ifnames.push(ifname);
2023                                         }
2024
2025                                         if (proto.virtual)
2026                                                 l3dev = '%s-%s'.format(s.proto, entry.name);
2027                                         else if (s.type == 'bridge')
2028                                                 l3dev = 'br-%s'.format(entry.name);
2029                                         else
2030                                                 l3dev = ifnames[0];
2031
2032                                         if (!proto.virtual && s.type == 'bridge')
2033                                                 l2dev = 'br-%s'.format(entry.name);
2034                                         else if (!proto.virtual)
2035                                                 l2dev = ifnames[0];
2036
2037                                         entry.proto = proto;
2038                                         entry.sid   = sid;
2039                                         entry.l3dev = l3dev;
2040                                         entry.l2dev = l2dev;
2041                                 }
2042                         }
2043
2044                         for (var i = 0; i < self._cache.ifstate.length; i++)
2045                         {
2046                                 var iface = self._cache.ifstate[i];
2047                                 var entry = self._get_iface(iface['interface']);
2048                                 var proto = self._protos[iface.proto] || self._protos.none;
2049
2050                                 /* this is a virtual interface, either deleted from config but
2051                                    not applied yet or set up from external tools (6rd) */
2052                                 if (!entry.sid)
2053                                 {
2054                                         entry.proto = proto;
2055                                         entry.l2dev = iface.device;
2056                                         entry.l3dev = iface.l3_device;
2057                                 }
2058                         }
2059                 },
2060
2061                 init: function()
2062                 {
2063                         var self = this;
2064
2065                         if (self._cache)
2066                                 return L.deferrable();
2067
2068                         self._cache  = { };
2069                         self._devs   = { };
2070                         self._ifaces = { };
2071                         self._protos = { };
2072
2073                         return self._fetch_cache()
2074                                 .then(self._fetch_protocols)
2075                                 .then(self._parse_devices)
2076                                 .then(self._parse_interfaces);
2077                 },
2078
2079                 update: function()
2080                 {
2081                         delete this._cache;
2082                         return this.init();
2083                 },
2084
2085                 refreshInterfaceStatus: function()
2086                 {
2087                         return this._fetch_cache(1).then(this._parse_interfaces);
2088                 },
2089
2090                 refreshDeviceStatus: function()
2091                 {
2092                         return this._fetch_cache(2).then(this._parse_devices);
2093                 },
2094
2095                 refreshStatus: function()
2096                 {
2097                         return this._fetch_cache(1)
2098                                 .then(this._fetch_cache(2))
2099                                 .then(this._parse_devices)
2100                                 .then(this._parse_interfaces);
2101                 },
2102
2103                 getDevices: function()
2104                 {
2105                         var devs = [ ];
2106
2107                         for (var ifname in this._devs)
2108                                 if (ifname != 'lo')
2109                                         devs.push(new L.NetworkModel.Device(this._devs[ifname]));
2110
2111                         return devs.sort(this._sort_devices);
2112                 },
2113
2114                 getDeviceByInterface: function(iface)
2115                 {
2116                         if (iface instanceof L.NetworkModel.Interface)
2117                                 iface = iface.name();
2118
2119                         if (this._ifaces[iface])
2120                                 return this.getDevice(this._ifaces[iface].l3dev) ||
2121                                        this.getDevice(this._ifaces[iface].l2dev);
2122
2123                         return undefined;
2124                 },
2125
2126                 getDevice: function(ifname)
2127                 {
2128                         if (this._devs[ifname])
2129                                 return new L.NetworkModel.Device(this._devs[ifname]);
2130
2131                         return undefined;
2132                 },
2133
2134                 createDevice: function(name)
2135                 {
2136                         return new L.NetworkModel.Device(this._get_dev(name));
2137                 },
2138
2139                 getInterfaces: function()
2140                 {
2141                         var ifaces = [ ];
2142
2143                         for (var name in this._ifaces)
2144                                 if (name != 'loopback')
2145                                         ifaces.push(this.getInterface(name));
2146
2147                         ifaces.sort(function(a, b) {
2148                                 if (a.name() < b.name())
2149                                         return -1;
2150                                 else if (a.name() > b.name())
2151                                         return 1;
2152                                 else
2153                                         return 0;
2154                         });
2155
2156                         return ifaces;
2157                 },
2158
2159                 getInterfacesByDevice: function(dev)
2160                 {
2161                         var ifaces = [ ];
2162
2163                         if (dev instanceof L.NetworkModel.Device)
2164                                 dev = dev.name();
2165
2166                         for (var name in this._ifaces)
2167                         {
2168                                 var iface = this._ifaces[name];
2169                                 if (iface.l2dev == dev || iface.l3dev == dev)
2170                                         ifaces.push(this.getInterface(name));
2171                         }
2172
2173                         ifaces.sort(function(a, b) {
2174                                 if (a.name() < b.name())
2175                                         return -1;
2176                                 else if (a.name() > b.name())
2177                                         return 1;
2178                                 else
2179                                         return 0;
2180                         });
2181
2182                         return ifaces;
2183                 },
2184
2185                 getInterface: function(iface)
2186                 {
2187                         if (this._ifaces[iface])
2188                                 return new L.NetworkModel.Interface(this._ifaces[iface]);
2189
2190                         return undefined;
2191                 },
2192
2193                 getProtocols: function()
2194                 {
2195                         var rv = [ ];
2196
2197                         for (var proto in this._protos)
2198                         {
2199                                 var pr = this._protos[proto];
2200
2201                                 rv.push({
2202                                         name:        proto,
2203                                         description: pr.description,
2204                                         virtual:     pr.virtual,
2205                                         tunnel:      pr.tunnel
2206                                 });
2207                         }
2208
2209                         return rv.sort(function(a, b) {
2210                                 if (a.name < b.name)
2211                                         return -1;
2212                                 else if (a.name > b.name)
2213                                         return 1;
2214                                 else
2215                                         return 0;
2216                         });
2217                 },
2218
2219                 _find_wan: function(ipaddr)
2220                 {
2221                         for (var i = 0; i < this._cache.ifstate.length; i++)
2222                         {
2223                                 var ifstate = this._cache.ifstate[i];
2224
2225                                 if (!ifstate.route)
2226                                         continue;
2227
2228                                 for (var j = 0; j < ifstate.route.length; j++)
2229                                         if (ifstate.route[j].mask == 0 &&
2230                                             ifstate.route[j].target == ipaddr &&
2231                                             typeof(ifstate.route[j].table) == 'undefined')
2232                                         {
2233                                                 return this.getInterface(ifstate['interface']);
2234                                         }
2235                         }
2236
2237                         return undefined;
2238                 },
2239
2240                 findWAN: function()
2241                 {
2242                         return this._find_wan('0.0.0.0');
2243                 },
2244
2245                 findWAN6: function()
2246                 {
2247                         return this._find_wan('::');
2248                 },
2249
2250                 resolveAlias: function(ifname)
2251                 {
2252                         if (ifname instanceof L.NetworkModel.Device)
2253                                 ifname = ifname.name();
2254
2255                         var dev = this._devs[ifname];
2256                         var seen = { };
2257
2258                         while (dev && dev.kind == 'alias')
2259                         {
2260                                 // loop
2261                                 if (seen[dev.ifname])
2262                                         return undefined;
2263
2264                                 var ifc = this._ifaces[dev.sid];
2265
2266                                 seen[dev.ifname] = true;
2267                                 dev = ifc ? this._devs[ifc.l3dev] : undefined;
2268                         }
2269
2270                         return dev ? this.getDevice(dev.ifname) : undefined;
2271                 }
2272         };
2273
2274         this.NetworkModel.Device = Class.extend({
2275                 _wifi_modes: {
2276                         ap: L.tr('Master'),
2277                         sta: L.tr('Client'),
2278                         adhoc: L.tr('Ad-Hoc'),
2279                         monitor: L.tr('Monitor'),
2280                         wds: L.tr('Static WDS')
2281                 },
2282
2283                 _status: function(key)
2284                 {
2285                         var s = L.NetworkModel._cache.devstate[this.options.ifname];
2286
2287                         if (s)
2288                                 return key ? s[key] : s;
2289
2290                         return undefined;
2291                 },
2292
2293                 get: function(key)
2294                 {
2295                         var sid = this.options.sid;
2296                         var pkg = (this.options.kind == 'wifi') ? 'wireless' : 'network';
2297                         return L.NetworkModel._get(pkg, sid, key);
2298                 },
2299
2300                 set: function(key, val)
2301                 {
2302                         var sid = this.options.sid;
2303                         var pkg = (this.options.kind == 'wifi') ? 'wireless' : 'network';
2304                         return L.NetworkModel._set(pkg, sid, key, val);
2305                 },
2306
2307                 init: function()
2308                 {
2309                         if (typeof(this.options.type) == 'undefined')
2310                                 this.options.type = 1;
2311
2312                         if (typeof(this.options.kind) == 'undefined')
2313                                 this.options.kind = 'ethernet';
2314
2315                         if (typeof(this.options.networks) == 'undefined')
2316                                 this.options.networks = [ ];
2317                 },
2318
2319                 name: function()
2320                 {
2321                         return this.options.ifname;
2322                 },
2323
2324                 description: function()
2325                 {
2326                         switch (this.options.kind)
2327                         {
2328                         case 'alias':
2329                                 return L.tr('Alias for network "%s"').format(this.options.ifname.substring(1));
2330
2331                         case 'bridge':
2332                                 return L.tr('Network bridge');
2333
2334                         case 'ethernet':
2335                                 return L.tr('Network device');
2336
2337                         case 'tunnel':
2338                                 switch (this.options.type)
2339                                 {
2340                                 case 1: /* tuntap */
2341                                         return L.tr('TAP device');
2342
2343                                 case 512: /* PPP */
2344                                         return L.tr('PPP tunnel');
2345
2346                                 case 768: /* IP-IP Tunnel */
2347                                         return L.tr('IP-in-IP tunnel');
2348
2349                                 case 769: /* IP6-IP6 Tunnel */
2350                                         return L.tr('IPv6-in-IPv6 tunnel');
2351
2352                                 case 776: /* IPv6-in-IPv4 */
2353                                         return L.tr('IPv6-over-IPv4 tunnel');
2354                                         break;
2355
2356                                 case 778: /* GRE over IP */
2357                                         return L.tr('GRE-over-IP tunnel');
2358
2359                                 default:
2360                                         return L.tr('Tunnel device');
2361                                 }
2362
2363                         case 'vlan':
2364                                 return L.tr('VLAN %d on %s').format(this.options.vid, this.options.vsw.model);
2365
2366                         case 'wifi':
2367                                 var o = this.options;
2368                                 return L.trc('(Wifi-Mode) "(SSID)" on (radioX)', '%s "%h" on %s').format(
2369                                         o.wmode ? this._wifi_modes[o.wmode] : L.tr('Unknown mode'),
2370                                         o.wssid || '?', o.wdev
2371                                 );
2372                         }
2373
2374                         return L.tr('Unknown device');
2375                 },
2376
2377                 icon: function(up)
2378                 {
2379                         var kind = this.options.kind;
2380
2381                         if (kind == 'alias')
2382                                 kind = 'ethernet';
2383
2384                         if (typeof(up) == 'undefined')
2385                                 up = this.isUp();
2386
2387                         return L.globals.resource + '/icons/%s%s.png'.format(kind, up ? '' : '_disabled');
2388                 },
2389
2390                 isUp: function()
2391                 {
2392                         var l = L.NetworkModel._cache.devlist;
2393
2394                         for (var i = 0; i < l.length; i++)
2395                                 if (l[i].device == this.options.ifname)
2396                                         return (l[i].is_up === true);
2397
2398                         return false;
2399                 },
2400
2401                 isAlias: function()
2402                 {
2403                         return (this.options.kind == 'alias');
2404                 },
2405
2406                 isBridge: function()
2407                 {
2408                         return (this.options.kind == 'bridge');
2409                 },
2410
2411                 isBridgeable: function()
2412                 {
2413                         return (this.options.type == 1 && this.options.kind != 'bridge');
2414                 },
2415
2416                 isWireless: function()
2417                 {
2418                         return (this.options.kind == 'wifi');
2419                 },
2420
2421                 isInNetwork: function(net)
2422                 {
2423                         if (!(net instanceof L.NetworkModel.Interface))
2424                                 net = L.NetworkModel.getInterface(net);
2425
2426                         if (net)
2427                         {
2428                                 if (net.options.l3dev == this.options.ifname ||
2429                                     net.options.l2dev == this.options.ifname)
2430                                         return true;
2431
2432                                 var dev = L.NetworkModel._devs[net.options.l2dev];
2433                                 if (dev && dev.kind == 'bridge' && dev.ports)
2434                                         return ($.inArray(this.options.ifname, dev.ports) > -1);
2435                         }
2436
2437                         return false;
2438                 },
2439
2440                 getMTU: function()
2441                 {
2442                         var dev = L.NetworkModel._cache.devstate[this.options.ifname];
2443                         if (dev && !isNaN(dev.mtu))
2444                                 return dev.mtu;
2445
2446                         return undefined;
2447                 },
2448
2449                 getMACAddress: function()
2450                 {
2451                         if (this.options.type != 1)
2452                                 return undefined;
2453
2454                         var dev = L.NetworkModel._cache.devstate[this.options.ifname];
2455                         if (dev && dev.macaddr)
2456                                 return dev.macaddr.toUpperCase();
2457
2458                         return undefined;
2459                 },
2460
2461                 getInterfaces: function()
2462                 {
2463                         return L.NetworkModel.getInterfacesByDevice(this.options.name);
2464                 },
2465
2466                 getStatistics: function()
2467                 {
2468                         var s = this._status('statistics') || { };
2469                         return {
2470                                 rx_bytes: (s.rx_bytes || 0),
2471                                 tx_bytes: (s.tx_bytes || 0),
2472                                 rx_packets: (s.rx_packets || 0),
2473                                 tx_packets: (s.tx_packets || 0)
2474                         };
2475                 },
2476
2477                 getTrafficHistory: function()
2478                 {
2479                         var def = new Array(120);
2480
2481                         for (var i = 0; i < 120; i++)
2482                                 def[i] = 0;
2483
2484                         var h = L.NetworkModel._cache.bwstate[this.options.ifname] || { };
2485                         return {
2486                                 rx_bytes: (h.rx_bytes || def),
2487                                 tx_bytes: (h.tx_bytes || def),
2488                                 rx_packets: (h.rx_packets || def),
2489                                 tx_packets: (h.tx_packets || def)
2490                         };
2491                 },
2492
2493                 removeFromInterface: function(iface)
2494                 {
2495                         if (!(iface instanceof L.NetworkModel.Interface))
2496                                 iface = L.NetworkModel.getInterface(iface);
2497
2498                         if (!iface)
2499                                 return;
2500
2501                         var ifnames = L.toArray(iface.get('ifname'));
2502                         if ($.inArray(this.options.ifname, ifnames) > -1)
2503                                 iface.set('ifname', L.filterArray(ifnames, this.options.ifname));
2504
2505                         if (this.options.kind != 'wifi')
2506                                 return;
2507
2508                         var networks = L.toArray(this.get('network'));
2509                         if ($.inArray(iface.name(), networks) > -1)
2510                                 this.set('network', L.filterArray(networks, iface.name()));
2511                 },
2512
2513                 attachToInterface: function(iface)
2514                 {
2515                         if (!(iface instanceof L.NetworkModel.Interface))
2516                                 iface = L.NetworkModel.getInterface(iface);
2517
2518                         if (!iface)
2519                                 return;
2520
2521                         if (this.options.kind != 'wifi')
2522                         {
2523                                 var ifnames = L.toArray(iface.get('ifname'));
2524                                 if ($.inArray(this.options.ifname, ifnames) < 0)
2525                                 {
2526                                         ifnames.push(this.options.ifname);
2527                                         iface.set('ifname', (ifnames.length > 1) ? ifnames : ifnames[0]);
2528                                 }
2529                         }
2530                         else
2531                         {
2532                                 var networks = L.toArray(this.get('network'));
2533                                 if ($.inArray(iface.name(), networks) < 0)
2534                                 {
2535                                         networks.push(iface.name());
2536                                         this.set('network', (networks.length > 1) ? networks : networks[0]);
2537                                 }
2538                         }
2539                 }
2540         });
2541
2542         this.NetworkModel.Interface = Class.extend({
2543                 _status: function(key)
2544                 {
2545                         var s = L.NetworkModel._cache.ifstate;
2546
2547                         for (var i = 0; i < s.length; i++)
2548                                 if (s[i]['interface'] == this.options.name)
2549                                         return key ? s[i][key] : s[i];
2550
2551                         return undefined;
2552                 },
2553
2554                 get: function(key)
2555                 {
2556                         return L.NetworkModel._get('network', this.options.name, key);
2557                 },
2558
2559                 set: function(key, val)
2560                 {
2561                         return L.NetworkModel._set('network', this.options.name, key, val);
2562                 },
2563
2564                 name: function()
2565                 {
2566                         return this.options.name;
2567                 },
2568
2569                 protocol: function()
2570                 {
2571                         return (this.get('proto') || 'none');
2572                 },
2573
2574                 isUp: function()
2575                 {
2576                         return (this._status('up') === true);
2577                 },
2578
2579                 isVirtual: function()
2580                 {
2581                         return (typeof(this.options.sid) != 'string');
2582                 },
2583
2584                 getProtocol: function()
2585                 {
2586                         var prname = this.get('proto') || 'none';
2587                         return L.NetworkModel._protos[prname] || L.NetworkModel._protos.none;
2588                 },
2589
2590                 getUptime: function()
2591                 {
2592                         var uptime = this._status('uptime');
2593                         return isNaN(uptime) ? 0 : uptime;
2594                 },
2595
2596                 getDevice: function(resolveAlias)
2597                 {
2598                         if (this.options.l3dev)
2599                                 return L.NetworkModel.getDevice(this.options.l3dev);
2600
2601                         return undefined;
2602                 },
2603
2604                 getPhysdev: function()
2605                 {
2606                         if (this.options.l2dev)
2607                                 return L.NetworkModel.getDevice(this.options.l2dev);
2608
2609                         return undefined;
2610                 },
2611
2612                 getSubdevices: function()
2613                 {
2614                         var rv = [ ];
2615                         var dev = this.options.l2dev ?
2616                                 L.NetworkModel._devs[this.options.l2dev] : undefined;
2617
2618                         if (dev && dev.kind == 'bridge' && dev.ports && dev.ports.length)
2619                                 for (var i = 0; i < dev.ports.length; i++)
2620                                         rv.push(L.NetworkModel.getDevice(dev.ports[i]));
2621
2622                         return rv;
2623                 },
2624
2625                 getIPv4Addrs: function(mask)
2626                 {
2627                         var rv = [ ];
2628                         var addrs = this._status('ipv4-address');
2629
2630                         if (addrs)
2631                                 for (var i = 0; i < addrs.length; i++)
2632                                         if (!mask)
2633                                                 rv.push(addrs[i].address);
2634                                         else
2635                                                 rv.push('%s/%d'.format(addrs[i].address, addrs[i].mask));
2636
2637                         return rv;
2638                 },
2639
2640                 getIPv6Addrs: function(mask)
2641                 {
2642                         var rv = [ ];
2643                         var addrs;
2644
2645                         addrs = this._status('ipv6-address');
2646
2647                         if (addrs)
2648                                 for (var i = 0; i < addrs.length; i++)
2649                                         if (!mask)
2650                                                 rv.push(addrs[i].address);
2651                                         else
2652                                                 rv.push('%s/%d'.format(addrs[i].address, addrs[i].mask));
2653
2654                         addrs = this._status('ipv6-prefix-assignment');
2655
2656                         if (addrs)
2657                                 for (var i = 0; i < addrs.length; i++)
2658                                         if (!mask)
2659                                                 rv.push('%s1'.format(addrs[i].address));
2660                                         else
2661                                                 rv.push('%s1/%d'.format(addrs[i].address, addrs[i].mask));
2662
2663                         return rv;
2664                 },
2665
2666                 getDNSAddrs: function()
2667                 {
2668                         var rv = [ ];
2669                         var addrs = this._status('dns-server');
2670
2671                         if (addrs)
2672                                 for (var i = 0; i < addrs.length; i++)
2673                                         rv.push(addrs[i]);
2674
2675                         return rv;
2676                 },
2677
2678                 getIPv4DNS: function()
2679                 {
2680                         var rv = [ ];
2681                         var dns = this._status('dns-server');
2682
2683                         if (dns)
2684                                 for (var i = 0; i < dns.length; i++)
2685                                         if (dns[i].indexOf(':') == -1)
2686                                                 rv.push(dns[i]);
2687
2688                         return rv;
2689                 },
2690
2691                 getIPv6DNS: function()
2692                 {
2693                         var rv = [ ];
2694                         var dns = this._status('dns-server');
2695
2696                         if (dns)
2697                                 for (var i = 0; i < dns.length; i++)
2698                                         if (dns[i].indexOf(':') > -1)
2699                                                 rv.push(dns[i]);
2700
2701                         return rv;
2702                 },
2703
2704                 getIPv4Gateway: function()
2705                 {
2706                         var rt = this._status('route');
2707
2708                         if (rt)
2709                                 for (var i = 0; i < rt.length; i++)
2710                                         if (rt[i].target == '0.0.0.0' && rt[i].mask == 0)
2711                                                 return rt[i].nexthop;
2712
2713                         return undefined;
2714                 },
2715
2716                 getIPv6Gateway: function()
2717                 {
2718                         var rt = this._status('route');
2719
2720                         if (rt)
2721                                 for (var i = 0; i < rt.length; i++)
2722                                         if (rt[i].target == '::' && rt[i].mask == 0)
2723                                                 return rt[i].nexthop;
2724
2725                         return undefined;
2726                 },
2727
2728                 getStatistics: function()
2729                 {
2730                         var dev = this.getDevice() || new L.NetworkModel.Device({});