luci2: split into submodules
[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         Class.instantiate('luci2.rpc');
697         Class.instantiate('luci2.uci');
698         Class.instantiate('luci2.network');
699         Class.instantiate('luci2.wireless');
700         Class.instantiate('luci2.firewall');
701         Class.instantiate('luci2.system');
702         Class.instantiate('luci2.session');
703         Class.instantiate('luci2.ui');
704         Class.instantiate('luci2.cbi');
705 };