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