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