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