Latest updates from Teaman-IPTraffic
[tomato.git] / release / src / router / www / tomato.js
blob0cb7aa162d47c2eed80e814b60548f5cc0966da0
1 /*
2         Tomato GUI
3         Copyright (C) 2006-2010 Jonathan Zarate
4         http://www.polarcloud.com/tomato/
6         For use with Tomato Firmware only.
7         No part of this file may be used without permission.
8 */
10 // -----------------------------------------------------------------------------
12 Array.prototype.find = function(v) {
13         for (var i = 0; i < this.length; ++i)
14                 if (this[i] == v) return i;
15         return -1;
18 Array.prototype.remove = function(v) {
19         for (var i = 0; i < this.length; ++i) {
20                 if (this[i] == v) {
21                         this.splice(i, 1);
22                         return true;
23                 }
24         }
25         return false;
28 // -----------------------------------------------------------------------------
30 String.prototype.trim = function() {
31         return this.replace(/^\s+/, '').replace(/\s+$/, '');
34 // -----------------------------------------------------------------------------
36 Number.prototype.pad = function(min) {
37         var s = this.toString();
38         while (s.length < min) s = '0' + s;
39         return s;
42 Number.prototype.hex = function(min)
44         var h = '0123456789ABCDEF';
45         var n = this;
46         var s = '';
47         do {
48                 s = h.charAt(n & 15) + s;
49                 n = n >>> 4;
50         } while ((--min > 0) || (n > 0));
51         return s;
54 // -----------------------------------------------------------------------------
56 // ---- Element.protoype. doesn't work with all browsers
58 var elem = {
59         getOffset: function(e) {
60                 var r = { x: 0, y: 0 };
61                 e = E(e);
62                 while (e.offsetParent) {
63                         r.x += e.offsetLeft;
64                         r.y += e.offsetTop;
65                         e = e.offsetParent;
66                 }
67                 return r;
68         },
70         addClass: function(e, name) {
71                 if ((e = E(e)) == null) return;
72                 var a = e.className.split(/\s+/);
73                 var k = 0;
74                 for (var i = 1; i < arguments.length; ++i) {
75                         if (a.find(arguments[i]) == -1) {
76                                 a.push(arguments[i]);
77                                 k = 1;
78                         }
79                 }
80                 if (k) e.className = a.join(' ');
81         },
83         removeClass: function(e, name) {
84                 if ((e = E(e)) == null) return;
85                 var a = e.className.split(/\s+/);
86                 var k = 0;
87                 for (var i = 1; i < arguments.length; ++i)
88                         k |= a.remove(arguments[i]);
89                 if (k) e.className = a.join(' ');
90         },
92         remove: function(e) {
93                  if ((e = E(e)) != null) e.parentNode.removeChild(e);
94         },
96     parentElem: function(e, tagName) {
97                 e = E(e);
98                 tagName = tagName.toUpperCase();
99                 while (e.parentNode) {
100                         e = e.parentNode;
101                         if (e.tagName == tagName) return e;
102                 }
103                 return null;
104         },
106         display: function() {
107                 var enable = arguments[arguments.length - 1];
108                 for (var i = 0; i < arguments.length - 1; ++i) {
109                         E(arguments[i]).style.display = enable ? '' : 'none';
110                 }
111         },
113         isVisible: function(e) {
114                 e = E(e);
115                 while (e) {
116                         if ((e.style.visibility != 'visible') || (e.style.display == 'none')) return false;
117                         e = e.parentNode;
118                 }
119                 return true;
120         },
122         setInnerHTML: function(e, html) {
123                  e = E(e);
124                  if (e.innerHTML != html) e.innerHTML = html;   // reduce flickering
125         }
128 // -----------------------------------------------------------------------------
130 var docu = {
131         getViewSize: function() {
132                 if (window.innerHeight) {
133                         return { width: window.innerWidth, height: window.innerHeight };
134                 }
135                 else if (document.documentElement && document.documentElement.clientHeight) {
136                         return { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight };
137                 }
138                 return { width: document.body.clientWidth, height: document.body.clientHeight };
139         },
141         getPageOffset: function()
142         {
143                 if (typeof(window.pageYOffset) != 'undefined') {
144                         return { x: window.pageXOffset, y: window.pageYOffset };
145                 }
146                 else if ((document.documentElement) && (typeof(document.documentElement.scrollTop) != 'undefined')) {
147                         return { x: document.documentElement.scrollLeft, y: document.documentElement.scrollTop };
148                 }
149                 return { x: document.body.scrollLeft, y: document.body.scrollTop };
150         }
153 // -----------------------------------------------------------------------------
155 var fields = {
156         getAll: function(e) {
157                 var a = [];
158                 switch (e.tagName) {
159                 case 'INPUT':
160                 case 'SELECT':
161                         a.push(e);
162                         break;
163                 default:
164                         if (e.childNodes) {
165                                 for (var i = 0; i < e.childNodes.length; ++i) {
166                                         a = a.concat(fields.getAll(e.childNodes[i]));
167                                 }
168                         }
169                 }
170                 return a;
171         },
172         disableAll: function(e, d) {
173                 var i;
175                 if ((typeof(e.tagName) == 'undefined') && (typeof(e) != 'string')) {
176                         for (i = e.length - 1; i >= 0; --i) {
177                                 e[i].disabled = d;
178                         }
179                 }
180                 else {
181                         var a = this.getAll(E(e));
182                         for (var i = a.length - 1; i >= 0; --i) {
183                                 a[i].disabled = d;
184                         }
185                 }
186         },
187         radio: {
188                 selected: function(e) {
189                         for (var i = 0; i < e.length; ++i) {
190                                 if (e[i].checked) return e[i];
191                         }
192                         return null;
193                 },
194                 find: function(e, value) {
195                         for (var i = 0; i < e.length; ++i) {
196                                 if (e[i].value == value) return e[i];
197                         }
198                         return null;
199                 }
200         }
203 // -----------------------------------------------------------------------------
205 var form = {
206         submitHidden: function(url, fields) {
207                 var fom, body;
209                 fom = document.createElement('FORM');
210                 fom.action = url;
211                 fom.method = 'post';
212                 for (var f in fields) {
213                         var e = document.createElement('INPUT');
214                         e.type = 'hidden';
215                         e.name = f;
216                         e.value = fields[f];
217                         fom.appendChild(e);
218                 }
219                 body = document.getElementsByTagName('body')[0];
220                 fom = body.appendChild(fom);
221                 this.submit(fom);
222                 body.removeChild(fom);
223         },
225         submit: function(fom, async, url) {
226                 var e, v, f, i, wait, msg, sb, cb;
228                 fom = E(fom);
230                 if (isLocal()) {
231                         this.dump(fom, async, url);
232                         return;
233                 }
235                 if (this.xhttp) return;
237                 if ((sb = E('save-button')) != null) sb.disabled = 1;
238                 if ((cb = E('cancel-button')) != null) cb.disabled = 1;
240                 if ((!async) || (!useAjax())) {
241                         this.addId(fom);
242                         if (url) fom.action = url;
243                         fom.submit();
244                         return;
245                 }
247                 v = ['_ajax=1'];
248                 wait = 5;
249                 for (var i = 0; i < fom.elements.length; ++i) {
250                         f = fom.elements[i];
251                         if ((f.disabled) || (f.name == '') || (f.name.substr(0, 2) == 'f_')) continue;
252                         if ((f.tagName == 'INPUT') && ((f.type == 'CHECKBOX') || (f.type == 'RADIO')) && (!f.checked)) continue;
253                         if (f.name == '_nextwait') {
254                                 wait = f.value * 1;
255                                 if (isNaN(wait)) wait = 5;
256                                         else wait = Math.abs(wait);
257                         }
258                         v.push(escapeCGI(f.name) + '=' + escapeCGI(f.value));
259                 }
261                 if ((msg = E('footer-msg')) != null) {
262                         msg.innerHTML = 'Saving...';
263                         msg.style.visibility = 'visible';
264                 }
266                 this.xhttp = new XmlHttp();
267                 this.xhttp.onCompleted = function(text, xml) {
268                         if (msg) {
269                                 if (text.match(/@msg:(.+)/)) msg.innerHTML = escapeHTML(RegExp.$1);
270                                         else msg.innerHTML = 'Saved';
271                         }
272                         setTimeout(
273                                 function() {
274                                         if (sb) sb.disabled = 0;
275                                         if (cb) cb.disabled = 0;
276                                         if (msg) msg.style.visibility = 'hidden';
277                                         if (typeof(submit_complete) != 'undefined') submit_complete();
278                                 }, wait * 1100);
279                         form.xhttp = null;
280                 }
281                 this.xhttp.onError = function(x) {
282                         if (url) fom.action = url;
283                         fom.submit();
284                 }
286                 this.xhttp.post(url ? url : fom.action, v.join('&'));
287         },
289         addId: function(fom) {
290                 var e;
292                 if (typeof(fom._http_id) == 'undefined') {
293                         e = document.createElement('INPUT');
294                         e.type = 'hidden';
295                         e.name = '_http_id';
296                         e.value = nvram.http_id;
297                         fom.appendChild(e);
298                 }
299                 else {
300                         fom._http_id.value = nvram.http_id;
301                 }
302         },
304         addIdAction: function(fom) {
305                 if (fom.action.indexOf('?') != -1) fom.action += '&_http_id=' + nvram.http_id;
306                         else fom.action += '?_http_id=' + nvram.http_id;
307         },
309         dump: function(fom, async, url) {
310         }
313 // -----------------------------------------------------------------------------
315 var ferror = {
316         set: function(e, message, quiet) {
317                 if ((e = E(e)) == null) return;
318                 e._error_msg = message;
319                 e._error_org = e.title;
320                 e.title = message;
321                 elem.addClass(e, 'error');
322                 if (!quiet) this.show(e);
323         },
325         clear: function(e) {
326                 if ((e = E(e)) == null) return;
327                 e.title = e._error_org || '';
328                 elem.removeClass(e, 'error');
329                 e._error_msg = null;
330                 e._error_org = null;
331         },
333         clearAll: function(e) {
334                 for (var i = 0; i < e.length; ++i)
335                         this.clear(e[i]);
336         },
338         show: function(e) {
339                 if ((e = E(e)) == null) return;
340                 if (!e._error_msg) return;
341                 elem.addClass(e, 'error-focused');
342                 e.focus();
343                 alert(e._error_msg);
344                 elem.removeClass(e, 'error-focused');
345         },
347         ok: function(e) {
348                 if ((e = E(e)) == null) return 0;
349         return !e._error_msg;
350         }
353 // -----------------------------------------------------------------------------
355 function fixFile(name)
357         var i;
358         if (((i = name.lastIndexOf('/')) > 0) || ((i = name.lastIndexOf('\\')) > 0))
359                 name = name.substring(i + 1, name.length);
360         return name;
363 function _v_range(e, quiet, min, max, name)
365         if ((e = E(e)) == null) return 0;
366         var v = e.value;
367         if ((!v.match(/^ *[-\+]?\d+ *$/)) || (v < min) || (v > max)) {
368                 ferror.set(e, 'Invalid ' + name + '. Valid range: ' + min + '-' + max, quiet);
369                 return 0;
370         }
371         e.value = v * 1;
372         ferror.clear(e);
373         return 1;
376 function v_range(e, quiet, min, max)
378         return _v_range(e, quiet, min, max, 'number');
381 function v_port(e, quiet)
383         return _v_range(e, quiet, 1, 0xFFFF, 'port');
386 function v_octet(e, quiet)
388         return _v_range(e, quiet, 1, 254, 'address');
391 function v_mins(e, quiet, min, max)
393         var v, m;
395         if ((e = E(e)) == null) return 0;
396         if (e.value.match(/^\s*(.+?)([mhd])?\s*$/)) {
397                 m = 1;
398                 if (RegExp.$2 == 'h') m = 60;
399                         else if (RegExp.$2 == 'd') m = 60 * 24;
400                 v = Math.round(RegExp.$1 * m);
401                 if (!isNaN(v)) {
402                         e.value = v;
403                         return _v_range(e, quiet, min, max, 'minutes');
404                 }
405         }
406         ferror.set(e, 'Invalid number of minutes.', quiet);
407         return 0;
410 function v_macip(e, quiet, bok, ipp)
412         var s, a, b, c, d, i;
414         if ((e = E(e)) == null) return 0;
415         s = e.value.replace(/\s+/g, '');
417         if ((a = fixMAC(s)) != null) {
418                 if (isMAC0(a)) {
419                         if (bok) {
420                                 e.value = '';
421                         }
422                         else {
423                                 ferror.set(e, 'Invalid MAC or IP address');
424                                 return false;
425                         }
426                 }
427         else e.value = a;
428                 ferror.clear(e);
429                 return true;
430         }
432         a = s.split('-');
433         if (a.length > 2) {
434                 ferror.set(e, 'Invalid IP address range', quiet);
435                 return false;
436         }
437         c = 0;
438         for (i = 0; i < a.length; ++i) {
439                 b = a[i];
440                 if (b.match(/^\d+$/)) b = ipp + b;
442                 b = fixIP(b);
443                 if (!b) {
444                         ferror.set(e, 'Invalid IP address', quiet);
445                         return false;
446                 }
448                 if (b.indexOf(ipp) != 0) {
449                         ferror.set(e, 'IP address outside of LAN', quiet);
450                         return false;
451                 }
453                 d = (b.split('.'))[3];
454                 if (d <= c) {
455                         ferror.set(e, 'Invalid IP address range', quiet);
456                         return false;
457                 }
459                 a[i] = c = d;
460         }
461         e.value = ipp + a.join('-');
462         return true;
465 function fixIP(ip, x)
467         var a, n, i;
469         a = ip.split('.');
470         if (a.length != 4) return null;
471         for (i = 0; i < 4; ++i) {
472                 n = a[i] * 1;
473                 if ((isNaN(n)) || (n < 0) || (n > 255)) return null;
474                 a[i] = n;
475         }
476         if ((x) && ((a[3] == 0) || (a[3] == 255))) return null;
477         return a.join('.');
480 function v_ip(e, quiet, x)
482         var ip;
484         if ((e = E(e)) == null) return 0;
485         ip = fixIP(e.value, x);
486         if (!ip) {
487                 ferror.set(e, 'Invalid IP address', quiet);
488                 return false;
489         }
490         e.value = ip;
491         ferror.clear(e);
492         return true;
495 function v_ipz(e, quiet)
497         if ((e = E(e)) == null) return 0;
498         if (e.value == '') e.value = '0.0.0.0';
499         return v_ip(e, quiet);
502 function v_dns(e, quiet)
504         if ((e = E(e)) == null) return 0;       
505         if (e.value == '') {
506                 e.value = '0.0.0.0';
507         }
508         else {
509                 var s = e.value.split(':');
510                 if (s.length == 1) {
511                         s.push(53);
512                 }
513                 else if (s.length != 2) {
514                         ferror.set(e, 'Invalid IP address or port', quiet);
515                         return false;
516                 }
517                 
518                 if ((s[0] = fixIP(s[0])) == null) {
519                         ferror.set(e, 'Invalid IP address', quiet);
520                         return false;
521                 }
523                 if ((s[1] = fixPort(s[1], -1)) == -1) {
524                         ferror.set(e, 'Invalid port', quiet);
525                         return false;
526                 }
527         
528                 if (s[1] == 53) {
529                         e.value = s[0];
530                 }
531                 else {
532                         e.value = s.join(':');
533                 }
534         }
536         ferror.clear(e);
537         return true;
540 function aton(ip)
542         var o, x, i;
544         // ---- this is goofy because << mangles numbers as signed
545         o = ip.split('.');
546         x = '';
547         for (i = 0; i < 4; ++i) x += (o[i] * 1).hex(2);
548         return parseInt(x, 16);
551 function ntoa(ip)
553         return ((ip >> 24) & 255) + '.' + ((ip >> 16) & 255) + '.' + ((ip >> 8) & 255) + '.' + (ip & 255);
557 // ---- 1.2.3.4, 1.2.3.4/24, 1.2.3.4/255.255.255.0, 1.2.3.4-1.2.3.5
558 function _v_iptip(e, ip, quiet)
560         var ma, x, y, z, oip;
561         var a, b;
563         oip = ip;
565         // x.x.x.x - y.y.y.y
566         if (ip.match(/^(.*)-(.*)$/)) {
567                 a = fixIP(RegExp.$1);
568                 b = fixIP(RegExp.$2);
569                 if ((a == null) || (b == null)) {
570                         ferror.set(e, 'Invalid IP address range - ' + oip, quiet);
571                         return null;
572                 }
573                 ferror.clear(e);
575                 if (aton(a) > aton(b)) return b + '-' + a;
576                 return a + '-' + b;
577         }
579         ma = '';
581         // x.x.x.x/nn
582         // x.x.x.x/y.y.y.y
583         if (ip.match(/^(.*)\/(.*)$/)) {
584                 ip = RegExp.$1;
585                 b = RegExp.$2;
587                 ma = b * 1;
588                 if (isNaN(ma)) {
589                         ma = fixIP(b);
590                         if ((ma == null) || (!_v_netmask(ma))) {
591                                 ferror.set(e, 'Invalid netmask - ' + oip, quiet);
592                                 return null;
593                         }
594                 }
595                 else {
596                         if ((ma < 0) || (ma > 32)) {
597                                 ferror.set(e, 'Invalid netmask - ' + oip, quiet);
598                                 return null;
599                         }
600                 }
601         }
603         ip = fixIP(ip);
604         if (!ip) {
605                 ferror.set(e, 'Invalid IP address - ' + oip, quiet);
606                 return null;
607         }
609         ferror.clear(e);
610         return ip + ((ma != '') ? ('/' + ma) : '');
613 function v_iptip(e, quiet, multi)
615         var v, i;
617         if ((e = E(e)) == null) return 0;
618         v = e.value.split(',');
619         if (multi) {
620                 if (v.length > multi) {
621                         ferror.set(e, 'Too many IP addresses', quiet);
622                         return 0;
623                 }
624         }
625         else {
626                 if (v.length > 1) {
627                         ferror.set(e, 'Invalid IP address', quiet);
628                         return 0;
629                 }
630         }
631         for (i = 0; i < v.length; ++i) {
632                 if ((v[i] = _v_iptip(e, v[i], quiet)) == null) return 0;
633         }
634         e.value = v.join(', ');
635         return 1;
638 function _v_domain(e, dom, quiet)
640         var s;
642         s = dom.replace(/\s+/g, ' ').trim();
643         if (s.length > 0) {
644                 if ((s.search(/^[a-zA-Z0-9][.a-zA-Z0-9_\- ]+$/) == -1) ||
645                     (s.search(/\-$/) >= 0)) {
646                         ferror.set(e, "Invalid name. Only characters \"A-Z 0-9 . - _\" are allowed.", quiet);
647                         return null;
648                 }
649         }
650         ferror.clear(e);
651         return s;
654 function v_domain(e, quiet)
656         var v;
658         if ((e = E(e)) == null) return 0;
659         if ((v = _v_domain(e, e.value, quiet)) == null) return 0;
661         e.value = v;
662         return 1;
665 /* IPV6-BEGIN */
666 function ExpandIPv6Address(ip)
668         var a, pre, n, i, fill, post;
670         ip = ip.toLowerCase();
671         if (!ip.match(/^(::)?([a-f0-9]{1,4}::?){0,7}([a-f0-9]{1,4})(::)?$/)) return null;
673         a = ip.split('::');
674         switch (a.length) {
675         case 1:
676                 if (a[0] == '') return null;
677                 pre = a[0].split(':');
678                 if (pre.length != 8) return null;
679                 ip = pre.join(':');
680                 break;
681         case 2:
682                 pre = a[0].split(':');
683                 post = a[1].split(':');
684                 n = 8 - pre.length - post.length;
685                 for (i=0; i<2; i++) {
686                         if (a[i]=='') n++;
687                 }
688                 if (n < 0) return null;
689                 fill = '';
690                 while (n-- > 0) fill += ':0';
691                 ip = pre.join(':') + fill + ':' + post.join(':');
692                 ip = ip.replace(/^:/, '').replace(/:$/, '');
693                 break;
694         default:
695                 return null;
696         }
697         
698         ip = ip.replace(/([a-f0-9]{1,4})/ig, '000$1');
699         ip = ip.replace(/0{0,3}([a-f0-9]{4})/ig, '$1');
700         return ip;
703 function CompressIPv6Address(ip)
705         var a, segments;
706         
707         ip = ExpandIPv6Address(ip);
708         if (!ip) return null;
709         
710         if (ip.match(/(?:^00)|(?:^fe[8-9a-b])|(?:^ff)/)) return null; // not valid routable unicast address
712         ip = ip.replace(/(^|:)0{1,3}/g, '$1');
713         ip = ip.replace(/(:0)+$/, '::');
714         ip = ip.replace(/(:0){2,}(:[a-f0-9]{1,4})$/, ':$2');
715         return ip;      
718 function ipv6ton(ip)
720         var o, x, i;
722         ip = ExpandIPv6Address(ip);
723         if (!ip) return 0;
725         o = ip.split(':');
726         x = '';
727         for (i = 0; i < 8; ++i) x += (('0x' + o[i]) * 1).hex(4);
728         return parseInt(x, 16);
731 function _v_ipv6_addr(e, ip, ipt, quiet)
733         var oip;
734         var a, b;
736         oip = ip;
738         // ip range
739         if ((ipt) && ip.match(/^(.*)-(.*)$/)) {
740                 a = CompressIPv6Address(RegExp.$1);
741                 b = CompressIPv6Address(RegExp.$2);
742                 if ((a == null) || (b == null)) {
743                         ferror.set(e, 'Invalid IPv6 address range - ' + oip, quiet);
744                         return null;
745                 }
746                 ferror.clear(e);
748                 if (ipv6ton(a) > ipv6ton(b)) return b + '-' + a;
749                 return a + '-' + b;
750         }
752         ip = CompressIPv6Address(oip);
753         if (!ip) {
754                 ferror.set(e, 'Invalid IPv6 address - ' + oip, quiet);
755                 return null;
756         }
758         ferror.clear(e);
759         return ip;
762 function v_ipv6_addr(e, quiet)
764         if ((e = E(e)) == null) return 0;
766         ip = _v_ipv6_addr(e, e.value, false, quiet);
767         if (ip) e.value = ip;
768         return (ip != null);
770 /* IPV6-END */
772 function fixPort(p, def)
774         if (def == null) def = -1;
775         if (p == null) return def;
776         p *= 1;
777         if ((isNaN(p) || (p < 1) || (p > 65535) || (('' + p).indexOf('.') != -1))) return def;
778         return p;
781 function _v_portrange(e, quiet, v)
783         if (v.match(/^(.*)[-:](.*)$/)) {
784                 var x = RegExp.$1;
785                 var y = RegExp.$2;
787                 x = fixPort(x, -1);
788                 y = fixPort(y, -1);
789                 if ((x == -1) || (y == -1)) {
790                         ferror.set(e, 'Invalid port range: ' + v, quiet);
791                         return null;
792                 }
793                 if (x > y) {
794                         v = x;
795                         x = y;
796                         y = v;
797                 }
798                 ferror.clear(e);
799                 if (x == y) return x;
800                 return x + '-' + y;
801         }
803         v = fixPort(v, -1);
804         if (v == -1) {
805                 ferror.set(e, 'Invalid port', quiet);
806                 return null;
807         }
809         ferror.clear(e);
810         return v;
813 function v_portrange(e, quiet)
815         var v;
817         if ((e = E(e)) == null) return 0;
818         v = _v_portrange(e, quiet, e.value);
819         if (v == null) return 0;
820         e.value = v;
821         return 1;
824 function v_iptport(e, quiet)
826         var a, i, v, q;
828         if ((e = E(e)) == null) return 0;
830         a = e.value.split(/[,\.]/);
832         if (a.length == 0) {
833                 ferror.set(e, 'Expecting a list of ports or port range.', quiet);
834                 return 0;
835         }
836         if (a.length > 10) {
837                 ferror.set(e, 'Only 10 ports/range sets are allowed.', quiet);
838                 return 0;
839         }
841         q = [];
842         for (i = 0; i < a.length; ++i) {
843                 v = _v_portrange(e, quiet, a[i]);
844                 if (v == null) return 0;
845                 q.push(v);
846         }
848         e.value = q.join(',');
849         ferror.clear(e);
850         return 1;
853 function _v_netmask(mask)
855         var v = aton(mask) ^ 0xFFFFFFFF;
856         return (((v + 1) & v) == 0);
859 function v_netmask(e, quiet)
861         var n, b;
863         if ((e = E(e)) == null) return 0;
864         n = fixIP(e.value);
865         if (n) {
866                 if (_v_netmask(n)) {
867                         e.value = n;
868                         ferror.clear(e);
869                         return 1;
870                 }
871         }
872         else if (e.value.match(/^\s*\/\s*(\d+)\s*$/)) {
873                 b = RegExp.$1 * 1;
874                 if ((b >= 1) && (b <= 32)) {
875                         if (b == 32) n = 0xFFFFFFFF;    // js quirk
876                                 else n = (0xFFFFFFFF >>> b) ^ 0xFFFFFFFF;
877                         e.value = (n >>> 24) + '.' + ((n >>> 16) & 0xFF) + '.' + ((n >>> 8) & 0xFF) + '.' + (n & 0xFF);
878                         ferror.clear(e);
879                         return 1;
880                 }
881         }
882         ferror.set(e, 'Invalid netmask', quiet);
883         return 0;
886 function fixMAC(mac)
888         var t, i;
890         mac = mac.replace(/\s+/g, '').toUpperCase();
891         if (mac.length == 0) {
892                 mac = [0,0,0,0,0,0];
893         }
894         else if (mac.length == 12) {
895                 mac = mac.match(/../g);
896         }
897         else {
898                 mac = mac.split(/[:\-]/);
899                 if (mac.length != 6) return null;
900         }
901         for (i = 0; i < 6; ++i) {
902                 t = '' + mac[i];
903                 if (t.search(/^[0-9A-F]+$/) == -1) return null;
904                 if ((t = parseInt(t, 16)) > 255) return null;
905                 mac[i] = t.hex(2);
906         }
907         return mac.join(':');
910 function v_mac(e, quiet)
912         var mac;
914         if ((e = E(e)) == null) return 0;
915         mac = fixMAC(e.value);
916         if ((!mac) || (isMAC0(mac))) {
917                 ferror.set(e, 'Invalid MAC address', quiet);
918                 return 0;
919         }
920         e.value = mac;
921         ferror.clear(e);
922         return 1;
925 function v_macz(e, quiet)
927         var mac;
929         if ((e = E(e)) == null) return 0;
930         mac = fixMAC(e.value);
931         if (!mac) {
932                 ferror.set(e, 'Invalid MAC address', quiet);
933                 return false;
934         }
935         e.value = mac;
936         ferror.clear(e);
937         return true;
940 function v_length(e, quiet, min, max)
942         var s, n;
944         if ((e = E(e)) == null) return 0;
945         s = e.value.trim();
946         n = s.length;
947         if (min == undefined) min = 1;
948         if (n < min) {
949                 ferror.set(e, 'Invalid length. Please enter at least ' + min + ' character' + (min == 1 ? '.' : 's.'), quiet);
950                 return 0;
951         }
952         max = max || e.maxlength;
953         if (n > max) {
954                 ferror.set(e, 'Invalid length. Please reduce the length to ' + max + ' characters or less.', quiet);
955                 return 0;
956         }
957         e.value = s;
958         ferror.clear(e);
959         return 1;
962 function _v_iptaddr(e, quiet, multi, ipv4, ipv6)
964         var v, t, i;
966         if ((e = E(e)) == null) return 0;
967         v = e.value.split(',');
968         if (multi) {
969                 if (v.length > multi) {
970                         ferror.set(e, 'Too many addresses', quiet);
971                         return 0;
972                 }
973         }
974         else {
975                 if (v.length > 1) {
976                         ferror.set(e, 'Invalid domain name or IP address', quiet);
977                         return 0;
978                 }
979         }
981         for (i = 0; i < v.length; ++i) {
982                 if ((t = _v_domain(e, v[i], 1)) == null) {
983 /* IPV6-BEGIN */
984                         if ((!ipv6) && (!ipv4)) {
985                                 if (!quiet) ferror.show(e);
986                                 return 0;
987                         }
988                         if ((!ipv6) || ((t = _v_ipv6_addr(e, v[i], 1, 1)) == null)) {
989 /* IPV6-END */
990                                 if (!ipv4) {
991                                         if (!quiet) ferror.show(e);
992                                         return 0;
993                                 }
994                                 if ((t = _v_iptip(e, v[i], 1)) == null) {
995                                         ferror.set(e, e._error_msg + ', or invalid domain name', quiet);
996                                         return 0;
997                                 }
998 /* IPV6-BEGIN */
999                         }
1000 /* IPV6-END */
1001                 }
1002                 v[i] = t;
1003         }
1005         e.value = v.join(', ');
1006         ferror.clear(e);
1007         return 1;
1010 function v_iptaddr(e, quiet, multi)
1012         return _v_iptaddr(e, quiet, multi, 1, 0);
1015 function v_hostname(e, quiet, multi, delim)
1017         var s;
1018         var v, i;
1020         if ((e = E(e)) == null) return 0;
1021         v = (typeof(delim) == 'undefined') ? e.value.split(/\s+/) : e.value.split(delim);
1023         if (multi) {
1024                 if (v.length > multi) {
1025                         ferror.set(e, 'Too many hostnames.', quiet);
1026                         return 0;
1027                 }
1028         }
1029         else {
1030                 if (v.length > 1) {
1031                         ferror.set(e, 'Invalid hostname.', quiet);
1032                         return 0;
1033                 }
1034         }
1036         for (i = 0; i < v.length; ++i) {
1037                 s = v[i].replace(/\s+/g, '_');
1038                 if (s.length > 0) {
1039                         if (s.length > 63) {
1040                                 ferror.set(e, 'Hostname length should not exceed 63 characters.', quiet);
1041                                 return 0;
1042                         }
1043                         if ((s.search(/^[a-zA-Z0-9][a-zA-Z0-9_\-]+$/) == -1) ||
1044                             (s.search(/\-$/) >= 0)) {
1045                                 ferror.set(e, 'Invalid hostname. Only characters "A-Z 0-9 _" and "-" in the middle are allowed.', quiet);
1046                                 return 0;
1047                         }
1048                 }
1049                 v[i] = s;
1050         }
1051         e.value = v.join(' ');
1053         ferror.clear(e);
1054         return 1;
1057 function v_nodelim(e, quiet, name, checklist)
1059         if ((e = E(e)) == null) return 0;
1061         e.value = e.value.trim();
1062         if (e.value.indexOf('<') != -1 ||
1063            (checklist && e.value.indexOf('>') != -1)) {
1064                 ferror.set(e, 'Invalid ' + name + ': \"<\" ' + (checklist ? 'or \">\" are' : 'is') + ' not allowed.', quiet);
1065                 return 0;
1066         }
1067         ferror.clear(e);
1068         return 1;
1071 function v_path(e, quiet, required)
1073         if ((e = E(e)) == null) return 0;
1074         if (required && !v_length(e, quiet, 1)) return 0;
1076         if (!required && e.value.trim().length == 0) {
1077                 ferror.clear(e);
1078                 return 1;
1079         }
1080         if (e.value.substr(0, 1) != '/') {
1081                 ferror.set(e, 'Please start at the / root directory.', quiet);
1082                 return 0;
1083         }
1084         ferror.clear(e);
1085         return 1;
1088 function isMAC0(mac)
1090         return (mac == '00:00:00:00:00:00');
1093 // -----------------------------------------------------------------------------
1095 function cmpIP(a, b)
1097         if ((a = fixIP(a)) == null) a = '255.255.255.255';
1098         if ((b = fixIP(b)) == null) b = '255.255.255.255';
1099         return aton(a) - aton(b);
1102 function cmpText(a, b)
1104         if (a == '') a = '\xff';
1105         if (b == '') b = '\xff';
1106         return (a < b) ? -1 : ((a > b) ? 1 : 0);
1109 function cmpInt(a, b)
1111         a = parseInt(a, 10);
1112         b = parseInt(b, 10);
1113         return ((isNaN(a)) ? -0x7FFFFFFF : a) - ((isNaN(b)) ? -0x7FFFFFFF : b);
1116 function cmpFloat(a, b)
1118         a = parseFloat(a);
1119         b = parseFloat(b);
1120         return ((isNaN(a)) ? -Number.MAX_VALUE : a) - ((isNaN(b)) ? -Number.MAX_VALUE : b);
1123 function cmpDate(a, b)
1125         return b.getTime() - a.getTime();
1128 // -----------------------------------------------------------------------------
1130 // ---- todo: cleanup this mess
1132 function TGO(e)
1134         return elem.parentElem(e, 'TABLE').gridObj;
1137 function tgHideIcons()
1139         var e;
1140         while ((e = document.getElementById('tg-row-panel')) != null) e.parentNode.removeChild(e);
1143 // ---- options = sort, move, delete
1144 function TomatoGrid(tb, options, maxAdd, editorFields)
1146         this.init(tb, options, maxAdd, editorFields);
1147         return this;
1150 TomatoGrid.prototype = {
1151         init: function(tb, options, maxAdd, editorFields) {
1152                 if (tb) {
1153                         this.tb = E(tb);
1154                         this.tb.gridObj = this;
1155                 }
1156                 else {
1157                         this.tb = null;
1158                 }
1159                 if (!options) options = '';
1160                 this.header = null;
1161                 this.footer = null;
1162                 this.editor = null;
1163                 this.canSort = options.indexOf('sort') != -1;
1164                 this.canMove = options.indexOf('move') != -1;
1165                 this.maxAdd = maxAdd || 140;
1166                 this.canEdit = (editorFields != null);
1167                 this.canDelete = this.canEdit || (options.indexOf('delete') != -1);
1168                 this.editorFields = editorFields;
1169                 this.sortColumn = -1;
1170                 this.sortAscending = true;
1171         },
1173         _insert: function(at, cells, escCells) {
1174                 var tr, td, c;
1175                 var i, t;
1177                 tr = this.tb.insertRow(at);
1178                 for (i = 0; i < cells.length; ++i) {
1179                         c = cells[i];
1180                         if (typeof(c) == 'string') {
1181                                 td = tr.insertCell(i);
1182                                 td.className = 'co' + (i + 1);
1183                                 if (escCells) td.appendChild(document.createTextNode(c));
1184                                         else td.innerHTML = c;
1185                         }
1186                         else {
1187                                 tr.appendChild(c);
1188                         }
1189                 }
1190                 return tr;
1191         },
1193         // ---- header
1195         headerClick: function(cell) {
1196                 if (this.canSort) {
1197                         this.sort(cell.cellN);
1198                 }
1199         },
1201         headerSet: function(cells, escCells) {
1202                 var e, i;
1204                 elem.remove(this.header);
1205                 this.header = e = this._insert(0, cells, escCells);
1206                 e.className = 'header';
1208                 for (i = 0; i < e.cells.length; ++i) {
1209                         e.cells[i].cellN = i;   // cellIndex broken in Safari
1210                         e.cells[i].onclick = function() { return TGO(this).headerClick(this); };
1211                 }
1212                 return e;
1213         },
1215         // ---- footer
1217         footerClick: function(cell) {
1218         },
1220         footerSet: function(cells, escCells) {
1221                 var e, i;
1223                 elem.remove(this.footer);
1224                 this.footer = e = this._insert(-1, cells, escCells);
1225                 e.className = 'footer';
1226                 for (i = 0; i < e.cells.length; ++i) {
1227                         e.cells[i].cellN = i;
1228                         e.cells[i].onclick = function() { TGO(this).footerClick(this) };
1229                 }
1230                 return e;
1231         },
1233         // ----
1235         rpUp: function(e) {
1236                 var i;
1238                 e = PR(e);
1239                 TGO(e).moving = null;
1240                 i = e.previousSibling;
1241                 if (i == this.header) return;
1242                 e.parentNode.removeChild(e);
1243                 i.parentNode.insertBefore(e, i);
1245                 this.recolor();
1246                 this.rpHide();
1247         },
1249         rpDn: function(e) {
1250                 var i;
1252                 e = PR(e);
1253                 TGO(e).moving = null;
1254                 i = e.nextSibling;
1255                 if (i == this.footer) return;
1256                 e.parentNode.removeChild(e);
1257                 i.parentNode.insertBefore(e, i.nextSibling);
1259                 this.recolor();
1260                 this.rpHide();
1261         },
1263         rpMo: function(img, e) {
1264                 var me;
1266                 e = PR(e);
1267                 me = TGO(e);
1268                 if (me.moving == e) {
1269                         me.moving = null;
1270                         this.rpHide();
1271                         return;
1272                 }
1273                 me.moving = e;
1274                 img.style.border = "1px dotted red";
1275         },
1277         rpDel: function(e) {
1278                 e = PR(e);
1279                 TGO(e).moving = null;
1280                 e.parentNode.removeChild(e);
1281                 this.recolor();
1282                 this.rpHide();
1283         },
1285         rpMouIn: function(evt) {
1286                 var e, x, ofs, me, s, n;
1288                 if ((evt = checkEvent(evt)) == null) return;
1290                 me = TGO(evt.target);
1291                 if (me.isEditing()) return;
1292                 if (me.moving) return;
1294                 me.rpHide();
1295                 e = document.createElement('div');
1296                 e.tgo = me;
1297                 e.ref = evt.target;
1298                 e.setAttribute('id', 'tg-row-panel');
1300                 n = 0;
1301                 s = '';
1302                 if (me.canMove) {
1303                         s = '<img src="rpu.gif" onclick="this.parentNode.tgo.rpUp(this.parentNode.ref)" title="Move Up"><img src="rpd.gif" onclick="this.parentNode.tgo.rpDn(this.parentNode.ref)" title="Move Down"><img src="rpm.gif" onclick="this.parentNode.tgo.rpMo(this,this.parentNode.ref)" title="Move">';
1304                         n += 3;
1305                 }
1306                 if (me.canDelete) {
1307                         s += '<img src="rpx.gif" onclick="this.parentNode.tgo.rpDel(this.parentNode.ref)" title="Delete">';
1308                         ++n;
1309                 }
1310                 x = PR(evt.target);
1311                 x = x.cells[x.cells.length - 1];
1312                 ofs = elem.getOffset(x);
1313                 n *= 18;
1314                 e.style.left = (ofs.x + x.offsetWidth - n) + 'px';
1315                 e.style.top = ofs.y + 'px';
1316                 e.style.width = n + 'px';
1317                 e.innerHTML = s;
1319                 document.body.appendChild(e);
1320         },
1322         rpHide: tgHideIcons,
1324         // ----
1326         onClick: function(cell) {
1327                 if (this.canEdit) {
1328                         if (this.moving) {
1329                                 var p = this.moving.parentNode;
1330                                 var q = PR(cell);
1331                                 if (this.moving != q) {
1332                                         var v = this.moving.rowIndex > q.rowIndex;
1333                                         p.removeChild(this.moving);
1334                                         if (v) p.insertBefore(this.moving, q);
1335                                                 else p.insertBefore(this.moving, q.nextSibling);
1336                                         this.recolor();
1337                                 }
1338                                 this.moving = null;
1339                                 this.rpHide();
1340                                 return;
1341                         }
1342                         this.edit(cell);
1343                 }
1344         },
1346         insert: function(at, data, cells, escCells) {
1347                 var e, i;
1349                 if ((this.footer) && (at == -1)) at = this.footer.rowIndex;
1350                 e = this._insert(at, cells, escCells);
1351                 e.className = (e.rowIndex & 1) ? 'even' : 'odd';
1353                 for (i = 0; i < e.cells.length; ++i) {
1354                         e.cells[i].onclick = function() { return TGO(this).onClick(this); };
1355                 }
1357                 e._data = data;
1358                 e.getRowData = function() { return this._data; }
1359                 e.setRowData = function(data) { this._data = data; }
1361                 if ((this.canMove) || (this.canEdit) || (this.canDelete)) {
1362                         e.onmouseover = this.rpMouIn;
1363 // ----                 e.onmouseout = this.rpMouOut;
1364                         if (this.canEdit) e.title = 'Click to edit';
1365                 }
1367                 return e;
1368         },
1370         // ----
1372         insertData: function(at, data) {
1373                 return this.insert(at, data, this.dataToView(data), false);
1374         },
1376         dataToView: function(data) {
1377                 var v = [];
1378                 for (var i = 0; i < data.length; ++i) {
1379                         var s = escapeHTML('' + data[i]);
1380                         if (this.editorFields && this.editorFields.length > i) {
1381                                 var ef = this.editorFields[i].multi;
1382                                 if (!ef) ef = [this.editorFields[i]];
1383                                 var f = (ef && ef.length > 0 ? ef[0] : null);
1384                                 if (f && f.type == 'password') {
1385                                         if (!f.peekaboo || get_config('web_pb', '1') != '0')
1386                                                 s = s.replace(/./g, '&#x25CF;');
1387                                 }
1388                         }
1389                         v.push(s);
1390                 }
1391                 return v;
1392         },
1394         dataToFieldValues: function(data) {
1395                 return data;
1396         },
1398         fieldValuesToData: function(row) {
1399                 var e, i, data;
1401                 data = [];
1402                 e = fields.getAll(row);
1403                 for (i = 0; i < e.length; ++i) data.push(e[i].value);
1404                 return data;
1405         },
1407         // ----
1409         edit: function(cell) {
1410                 var sr, er, e, c;
1412                 if (this.isEditing()) return;
1414                 sr = PR(cell);
1415                 sr.style.display = 'none';
1416                 elem.removeClass(sr, 'hover');
1417                 this.source = sr;
1419                 er = this.createEditor('edit', sr.rowIndex, sr);
1420                 er.className = 'editor';
1421                 this.editor = er;
1423                 c = er.cells[cell.cellIndex || 0];
1424                 e = c.getElementsByTagName('input');
1425                 if ((e) && (e.length > 0)) {
1426                         try {   // IE quirk
1427                                 e[0].focus();
1428                         }
1429                         catch (ex) {
1430                         }
1431                 }
1433                 this.controls = this.createControls('edit', sr.rowIndex);
1435                 this.disableNewEditor(true);
1436                 this.rpHide();
1437                 this.verifyFields(this.editor, true);
1438         },
1440         createEditor: function(which, rowIndex, source) {
1441                 var values;
1443                 if (which == 'edit') values = this.dataToFieldValues(source.getRowData());
1445                 var row = this.tb.insertRow(rowIndex);
1446                 row.className = 'editor';
1448                 var common = ' onkeypress="return TGO(this).onKey(\'' + which + '\', event)" onchange="TGO(this).onChange(\'' + which + '\', this)"';
1450                 var vi = 0;
1451                 for (var i = 0; i < this.editorFields.length; ++i) {
1452                         var s = '';
1453                         var ef = this.editorFields[i].multi;
1454                         if (!ef) ef = [this.editorFields[i]];
1456                         for (var j = 0; j < ef.length; ++j) {
1457                                 var f = ef[j];
1459                                 if (f.prefix) s += f.prefix;
1460                                 var attrib = ' class="fi' + (vi + 1) + '" ' + (f.attrib || '');
1461                                 var id = (this.tb ? ('_' + this.tb + '_' + (vi + 1)) : null);
1462                                 if (id) attrib += ' id="' + id + '"';
1463                                 switch (f.type) {
1464                                 case 'password':
1465                                         if (f.peekaboo) {
1466                                                 switch (get_config('web_pb', '1')) {
1467                                                 case '0':
1468                                                         f.type = 'text';
1469                                                 case '2':
1470                                                         f.peekaboo = 0;
1471                                                         break;
1472                                                 }
1473                                         }
1474                                         attrib += ' autocomplete="off"';
1475                                         if (f.peekaboo && id) attrib += ' onfocus=\'peekaboo("' + id + '",1)\'';
1476                                         // drop
1477                                 case 'text':
1478                                         s += '<input type="' + f.type + '" maxlength=' + f.maxlen + common + attrib;
1479                                         if (which == 'edit') s += ' value="' + escapeHTML('' + values[vi]) + '">';
1480                                                 else s += '>';
1481                                         break;
1482                                 case 'select':
1483                                         s += '<select' + common + attrib + '>';
1484                                         for (var k = 0; k < f.options.length; ++k) {
1485                                                 a = f.options[k];
1486                                                 if (which == 'edit') {
1487                                                         s += '<option value="' + a[0] + '"' + ((a[0] == values[vi]) ? ' selected>' : '>') + a[1] + '</option>';
1488                                                 }
1489                                                 else {
1490                                                         s += '<option value="' + a[0] + '">' + a[1] + '</option>';
1491                                                 }
1492                                         }
1493                                         s += '</select>';
1494                                         break;
1495                                 case 'checkbox':
1496                                         s += '<input type="checkbox"' + common + attrib;
1497                                         if ((which == 'edit') && (values[vi])) s += ' checked';
1498                                         s += '>';
1499                                         break;
1500                                 default:
1501                                         s += f.custom.replace(/\$which\$/g, which);
1502                                 }
1503                                 if (f.suffix) s += f.suffix;
1505                                 ++vi;
1506                         }
1507                         var c = row.insertCell(i);
1508                         c.innerHTML = s;
1509                         if (this.editorFields[i].vtop) c.vAlign = 'top';
1510                 }
1512                 return row;
1513         },
1515         createControls: function(which, rowIndex) {
1516                 var r, c;
1518                 r = this.tb.insertRow(rowIndex);
1519                 r.className = 'controls';
1521                 c = r.insertCell(0);
1522                 c.colSpan = this.header.cells.length;
1523                 if (which == 'edit') {
1524                         c.innerHTML =
1525                                 '<input type=button value="Delete" onclick="TGO(this).onDelete()"> &nbsp; ' +
1526                                 '<input type=button value="OK" onclick="TGO(this).onOK()"> ' +
1527                                 '<input type=button value="Cancel" onclick="TGO(this).onCancel()">';
1528                 }
1529                 else {
1530                         c.innerHTML =
1531                                 '<input type=button value="Add" onclick="TGO(this).onAdd()">';
1532                 }
1533                 return r;
1534         },
1536         removeEditor: function() {
1537                 if (this.editor) {
1539                         elem.remove(this.editor);
1540                         this.editor = null;
1541                 }
1542                 if (this.controls) {
1543                         elem.remove(this.controls);
1544                         this.controls = null;
1545                 }
1546         },
1548         showSource: function() {
1549                 if (this.source) {
1550                         this.source.style.display = '';
1551                         this.source = null;
1552                 }
1553         },
1555         onChange: function(which, cell) {
1556                 return this.verifyFields((which == 'new') ? this.newEditor : this.editor, true);
1557         },
1559         onKey: function(which, ev) {
1560                 switch (ev.keyCode) {
1561                 case 27:
1562                         if (which == 'edit') this.onCancel();
1563                         return false;
1564                 case 13:
1565                         if (((ev.srcElement) && (ev.srcElement.tagName == 'SELECT')) ||
1566                                 ((ev.target) && (ev.target.tagName == 'SELECT'))) return true;
1567                         if (which == 'edit') this.onOK();
1568                                 else this.onAdd();
1569                         return false;
1570                 }
1571                 return true;
1572         },
1574         onDelete: function() {
1575                 this.removeEditor();
1576                 elem.remove(this.source);
1577                 this.source = null;
1578                 this.disableNewEditor(false);
1579         },
1581         onCancel: function() {
1582                 this.removeEditor();
1583                 this.showSource();
1584                 this.disableNewEditor(false);
1585         },
1587         onOK: function() {
1588                 var i, data, view;
1590                 if (!this.verifyFields(this.editor, false)) return;
1592                 data = this.fieldValuesToData(this.editor);
1593                 view = this.dataToView(data);
1595                 this.source.setRowData(data);
1596                 for (i = 0; i < this.source.cells.length; ++i) {
1597                         this.source.cells[i].innerHTML = view[i];
1598                 }
1600                 this.removeEditor();
1601                 this.showSource();
1602                 this.disableNewEditor(false);
1603         },
1605         onAdd: function() {
1606                 var data;
1608                 this.moving = null;
1609                 this.rpHide();
1611                 if (!this.verifyFields(this.newEditor, false)) return;
1613                 data = this.fieldValuesToData(this.newEditor);
1614                 this.insertData(-1, data);
1616                 this.disableNewEditor(false);
1617                 this.resetNewEditor();
1618         },
1620         verifyFields: function(row, quiet) {
1621                 return true;
1622         },
1624         showNewEditor: function() {
1625                 var r;
1627                 r = this.createEditor('new', -1, null);
1628                 this.footer = this.newEditor = r;
1630                 r = this.createControls('new', -1);
1631                 this.newControls = r;
1633                 this.disableNewEditor(false);
1634         },
1636         disableNewEditor: function(disable) {
1637                 if (this.getDataCount() >= this.maxAdd) disable = true;
1638                 if (this.newEditor) fields.disableAll(this.newEditor, disable);
1639                 if (this.newControls) fields.disableAll(this.newControls, disable);
1640         },
1642         resetNewEditor: function() {
1643                 var i, e;
1645                 e = fields.getAll(this.newEditor);
1646                 ferror.clearAll(e);
1647                 for (i = 0; i < e.length; ++i) {
1648                         var f = e[i];
1649                         if (f.selectedIndex) f.selectedIndex = 0;
1650                                 else f.value = '';
1651                 }
1652                 try { if (e.length) e[0].focus(); } catch (er) { }
1653         },
1655         getDataCount: function() {
1656                 var n;
1657                 n = this.tb.rows.length;
1658                 if (this.footer) n = this.footer.rowIndex;
1659                 if (this.header) n -= this.header.rowIndex + 1;
1660                 return n;
1661         },
1663         sortCompare: function(a, b) {
1664                 var obj = TGO(a);
1665                 var col = obj.sortColumn;
1666                 var r = cmpText(a.cells[col].innerHTML, b.cells[col].innerHTML);
1667                 return obj.sortAscending ? r : -r;
1668         },
1670         sort: function(column) {
1671                 if (this.editor) return;
1673                 if (this.sortColumn >= 0) {
1674                         elem.removeClass(this.header.cells[this.sortColumn], 'sortasc', 'sortdes');
1675                 }
1676                 if (column == this.sortColumn) {
1677                         this.sortAscending = !this.sortAscending;
1678                 }
1679                 else {
1680                         this.sortAscending = true;
1681                         this.sortColumn = column;
1682                 }
1683                 elem.addClass(this.header.cells[column], this.sortAscending ? 'sortasc' : 'sortdes');
1685                 this.resort();
1686         },
1688         resort: function() {
1689                 if ((this.sortColumn < 0) || (this.getDataCount() == 0) || (this.editor)) return;
1691                 var p = this.header.parentNode;
1692                 var a = [];
1693                 var i, j, max, e, p;
1694                 var top;
1696                 this.moving = null;
1698                 top = this.header ? this.header.rowIndex + 1 : 0;
1699                 max = this.footer ? this.footer.rowIndex : this.tb.rows.length;
1700                 for (i = top; i < max; ++i) a.push(p.rows[i]);
1701                 a.sort(THIS(this, this.sortCompare));
1702                 this.removeAllData();
1703                 j = top;
1704                 for (i = 0; i < a.length; ++i) {
1705                         e = p.insertBefore(a[i], this.footer);
1706                         e.className = (j & 1) ? 'even' : 'odd';
1707                         ++j;
1708                 }
1709         },
1711         recolor: function() {
1712                  var i, e, o;
1714                  i = this.header ? this.header.rowIndex + 1 : 0;
1715                  e = this.footer ? this.footer.rowIndex : this.tb.rows.length;
1716                  for (; i < e; ++i) {
1717                          o = this.tb.rows[i];
1718                          o.className = (o.rowIndex & 1) ? 'even' : 'odd';
1719                  }
1720         },
1722         removeAllData: function() {
1723                 var i, count;
1725                 i = this.header ? this.header.rowIndex + 1 : 0;
1726                 count = (this.footer ? this.footer.rowIndex : this.tb.rows.length) - i;
1727                 while (count-- > 0) elem.remove(this.tb.rows[i]);
1728         },
1730         getAllData: function() {
1731                 var i, max, data, r;
1733                 data = [];
1734                 max = this.footer ? this.footer.rowIndex : this.tb.rows.length;
1735                 for (i = this.header ? this.header.rowIndex + 1 : 0; i < max; ++i) {
1736                         r = this.tb.rows[i];
1737                         if ((r.style.display != 'none') && (r._data)) data.push(r._data);
1738                 }
1739                 return data;
1740         },
1742         isEditing: function() {
1743                 return (this.editor != null);
1744         }
1748 // -----------------------------------------------------------------------------
1751 function xmlHttpObj()
1753         var ob;
1754         try {
1755                 ob = new XMLHttpRequest();
1756                 if (ob) return ob;
1757         }
1758         catch (ex) { }
1759         try {
1760                 ob = new ActiveXObject('Microsoft.XMLHTTP');
1761                 if (ob) return ob;
1762         }
1763         catch (ex) { }
1764         return null;
1767 var _useAjax = -1;
1768 var _holdAjax = null;
1770 function useAjax()
1772         if (_useAjax == -1) _useAjax = ((_holdAjax = xmlHttpObj()) != null);
1773         return _useAjax;
1776 function XmlHttp()
1778         if ((!useAjax()) || ((this.xob = xmlHttpObj()) == null)) return null;
1779         return this;
1782 XmlHttp.prototype = {
1783         addId: function(vars) {
1784                 if (vars) vars += '&';
1785                         else vars = '';
1786                 vars += '_http_id=' + escapeCGI(nvram.http_id);
1787                 return vars;
1788         },
1790         get: function(url, vars) {
1791                 try {
1792                         vars = this.addId(vars);
1793                         url += '?' + vars;
1795                         this.xob.onreadystatechange = THIS(this, this.onReadyStateChange);
1796                         this.xob.open('GET', url, true);
1797                         this.xob.send(null);
1798                 }
1799                 catch (ex) {
1800                         this.onError(ex);
1801                 }
1802         },
1804         post: function(url, vars) {
1805                 try {
1806                         vars = this.addId(vars);
1808                         this.xob.onreadystatechange = THIS(this, this.onReadyStateChange);
1809                         this.xob.open('POST', url, true);
1810                         this.xob.send(vars);
1811                 }
1812                 catch (ex) {
1813                         this.onError(ex);
1814                 }
1815         },
1817         abort: function() {
1818                 try {
1819                         this.xob.onreadystatechange = function () { }
1820                         this.xob.abort();
1821                 }
1822                 catch (ex) {
1823                 }
1824         },
1826         onReadyStateChange: function() {
1827                 try {
1828                         if (typeof(E) == 'undefined') return;   // oddly late? testing for bug...
1830                         if (this.xob.readyState == 4) {
1831                                 if (this.xob.status == 200) {
1832                                         this.onCompleted(this.xob.responseText, this.xob.responseXML);
1833                                 }
1834                                 else {
1835                                         this.onError('' + (this.xob.status || 'unknown'));
1836                                 }
1837                         }
1838                 }
1839                 catch (ex) {
1840                         this.onError(ex);
1841                 }
1842         },
1844         onCompleted: function(text, xml) { },
1845         onError: function(ex) { }
1849 // -----------------------------------------------------------------------------
1852 function TomatoTimer(func, ms)
1854         this.tid = null;
1855         this.onTimer = func;
1856         if (ms) this.start(ms);
1857         return this;
1860 TomatoTimer.prototype = {
1861         start: function(ms) {
1862                 this.stop();
1863                 this.tid = setTimeout(THIS(this, this._onTimer), ms);
1864         },
1865         stop: function() {
1866                 if (this.tid) {
1867                         clearTimeout(this.tid);
1868                         this.tid = null;
1869                 }
1870         },
1872         isRunning: function() {
1873                 return (this.tid != null);
1874         },
1876         _onTimer: function() {
1877                 this.tid = null;
1878                 this.onTimer();
1879         },
1881         onTimer: function() {
1882         }
1886 // -----------------------------------------------------------------------------
1889 function TomatoRefresh(actionURL, postData, refreshTime, cookieTag)
1891         this.setup(actionURL, postData, refreshTime, cookieTag);
1892         this.timer = new TomatoTimer(THIS(this, this.start));
1895 TomatoRefresh.prototype = {
1896         running: 0,
1898         setup: function(actionURL, postData, refreshTime, cookieTag) {
1899                 var e, v;
1901                 this.actionURL = actionURL;
1902                 this.postData = postData;
1903                 this.refreshTime = refreshTime * 1000;
1904                 this.cookieTag = cookieTag;
1905         },
1907         start: function() {
1908                 var e;
1910                 if ((e = E('refresh-time')) != null) {
1911                         if (this.cookieTag) cookie.set(this.cookieTag, e.value);
1912                         this.refreshTime = e.value * 1000;
1913                 }
1914                 e = undefined;
1916                 this.updateUI('start');
1918                 this.running = 1;
1919                 if ((this.http = new XmlHttp()) == null) {
1920                         reloadPage();
1921                         return;
1922                 }
1924                 this.http.parent = this;
1926                 this.http.onCompleted = function(text, xml) {
1927                         var p = this.parent;
1929                         if (p.cookieTag) cookie.unset(p.cookieTag + '-error');
1930                         if (!p.running) {
1931                                 p.stop();
1932                                 return;
1933                         }
1935                         p.refresh(text);
1937                         if ((p.refreshTime > 0) && (!p.once)) {
1938                                 p.updateUI('wait');
1939                                 p.timer.start(Math.round(p.refreshTime));
1940                         }
1941                         else {
1942                                 p.stop();
1943                         }
1945                         p.errors = 0;
1946                 }
1948                 this.http.onError = function(ex) {
1949                         var p = this.parent;
1950                         if ((!p) || (!p.running)) return;
1952                         p.timer.stop();
1954                         if (++p.errors <= 3) {
1955                                 p.updateUI('wait');
1956                                 p.timer.start(3000);
1957                                 return;
1958                         }
1960                         if (p.cookieTag) {
1961                                 var e = cookie.get(p.cookieTag + '-error') * 1;
1962                                 if (isNaN(e)) e = 0;
1963                                         else ++e;
1964                                 cookie.unset(p.cookieTag);
1965                                 cookie.set(p.cookieTag + '-error', e, 1);
1966                                 if (e >= 3) {
1967                                         alert('XMLHTTP: ' + ex);
1968                                         return;
1969                                 }
1970                         }
1972                         setTimeout(reloadPage, 2000);
1973                 }
1975                 this.errors = 0;
1976                 this.http.post(this.actionURL, this.postData);
1977         },
1979         stop: function() {
1980                 if (this.cookieTag) cookie.set(this.cookieTag, -(this.refreshTime / 1000));
1981                 this.running = 0;
1982                 this.updateUI('stop');
1983                 this.timer.stop();
1984                 this.http = null;
1985                 this.once = undefined;
1986         },
1988         toggle: function(delay) {
1989                 if (this.running) this.stop();
1990                         else this.start(delay);
1991         },
1993         updateUI: function(mode) {
1994                 var e, b;
1996                 if (typeof(E) == 'undefined') return;   // for a bizzare bug...
1998                 b = (mode != 'stop') && (this.refreshTime > 0);
1999                 if ((e = E('refresh-button')) != null) {
2000                         e.value = b ? 'Stop' : 'Refresh';
2001                         e.disabled = ((mode == 'start') && (!b));
2002                 }
2003                 if ((e = E('refresh-time')) != null) e.disabled = b;
2004                 if ((e = E('refresh-spinner')) != null) e.style.visibility = b ? 'visible' : 'hidden';
2005         },
2007         initPage: function(delay, def) {
2008                 var e, v;
2010                 e = E('refresh-time');
2011                 if (((this.cookieTag) && (e != null)) &&
2012                         ((v = cookie.get(this.cookieTag)) != null) && (!isNaN(v *= 1))) {
2013                         e.value = Math.abs(v);
2014                         if (v > 0) v = (v * 1000) + (delay || 0);
2015                 }
2016                 else if (def) {
2017                         v = def;
2018                         if (e) e.value = def;
2019                 }
2020                 else v = 0;
2022                 if (delay < 0) {
2023                         v = -delay;
2024                         this.once = 1;
2025                 }
2027                 if (v > 0) {
2028                         this.running = 1;
2029                         this.refreshTime = v;
2030                         this.timer.start(v);
2031                         this.updateUI('wait');
2032                 }
2033         }
2036 function genStdTimeList(id, zero, min)
2038         var b = [];
2039         var t = [3,4,5,10,15,30,60,120,180,240,300,10*60,15*60,20*60,30*60];
2040         var i, v;
2042         if (min >= 0) {
2043                 b.push('<select id="' + id + '"><option value=0>' + zero);
2044                 for (i = 0; i < t.length; ++i) {
2045                         v = t[i];
2046                         if (v < min) continue;
2047                         b.push('<option value=' + v + '>');
2048                         if (v == 60) b.push('1 minute');
2049                                 else if (v > 60) b.push((v / 60) + ' minutes');
2050                                 else b.push(v + ' seconds');
2051                 }
2052                 b.push('</select> ');
2053         }
2054         document.write(b.join(''));
2057 function genStdRefresh(spin, min, exec)
2059         W('<div style="text-align:right">');
2060         if (spin) W('<img src="spin.gif" id="refresh-spinner"> ');
2061         genStdTimeList('refresh-time', 'Auto Refresh', min);
2062         W('<input type="button" value="Refresh" onclick="' + (exec ? exec : 'refreshClick()') + '" id="refresh-button"></div>');
2066 // -----------------------------------------------------------------------------
2069 function _tabCreate(tabs)
2071         var buf = [];
2072         buf.push('<ul id="tabs">');
2073         for (var i = 0; i < arguments.length; ++i)
2074                 buf.push('<li><a href="javascript:tabSelect(\'' + arguments[i][0] + '\')" id="' + arguments[i][0] + '">' + arguments[i][1] + '</a>');
2075         buf.push('</ul><div id="tabs-bottom"></div>');
2076         return buf.join('');
2079 function tabCreate(tabs)
2081         document.write(_tabCreate.apply(this, arguments));
2084 function tabHigh(id)
2086         var a = E('tabs').getElementsByTagName('A');
2087         for (var i = 0; i < a.length; ++i) {
2088                 if (id != a[i].id) elem.removeClass(a[i], 'active');
2089         }
2090         elem.addClass(id, 'active');
2093 // -----------------------------------------------------------------------------
2095 var cookie = {
2096         set: function(key, value, days) {
2097                 document.cookie = 'tomato_' + key + '=' + value + '; expires=' +
2098                         (new Date(new Date().getTime() + ((days ? days : 14) * 86400000))).toUTCString() + '; path=/';
2099         },
2101         get: function(key) {
2102                 var r = ('; ' + document.cookie + ';').match('; tomato_' + key + '=(.*?);');
2103                 return r ? r[1] : null;
2104         },
2106         unset: function(key) {
2107                 document.cookie = 'tomato_' + key + '=; expires=' +
2108                         (new Date(1)).toUTCString() + '; path=/';
2109         }
2112 // -----------------------------------------------------------------------------
2114 function checkEvent(evt)
2116         if (typeof(evt) == 'undefined') {
2117                 // ---- IE
2118                 evt = event;
2119                 evt.target = evt.srcElement;
2120                 evt.relatedTarget = evt.toElement;
2121         }
2122         return evt;
2125 function W(s)
2127         document.write(s);
2130 function E(e)
2132         return (typeof(e) == 'string') ? document.getElementById(e) : e;
2135 function PR(e)
2137         return elem.parentElem(e, 'TR');
2140 function THIS(obj, func)
2142         return function() { return func.apply(obj, arguments); }
2145 function UT(v)
2147         return (typeof(v) == 'undefined') ? '' : '' + v;
2150 function escapeHTML(s)
2152         function esc(c) {
2153                 return '&#' + c.charCodeAt(0) + ';';
2154         }
2155         return s.replace(/[&"'<>\r\n]/g, esc);
2158 function escapeCGI(s)
2160         return escape(s).replace(/\+/g, '%2B'); // escape() doesn't handle +
2163 function escapeD(s)
2165         function esc(c) {
2166                 return '%' + c.charCodeAt(0).hex(2);
2167         }
2168         return s.replace(/[<>|%]/g, esc);
2171 function ellipsis(s, max) {
2172         return (s.length <= max) ? s : s.substr(0, max - 3) + '...';
2175 function MIN(a, b)
2177         return a < b ? a : b;
2180 function MAX(a, b)
2182         return a > b ? a : b;
2185 function fixInt(n, min, max, def)
2187         if (n === null) return def;
2188         n *= 1;
2189         if (isNaN(n)) return def;
2190         if (n < min) return min;
2191         if (n > max) return max;
2192         return n;
2195 function comma(n)
2197         n = '' + n;
2198         var p = n;
2199         while ((n = n.replace(/(\d+)(\d{3})/g, '$1,$2')) != p) p = n;
2200         return n;
2203 function doScaleSize(n, sm)
2205         if (isNaN(n *= 1)) return '-';
2206         if (n <= 9999) return '' + n;
2207         var s = -1;
2208         do {
2209                 n /= 1024;
2210                 ++s;
2211         } while ((n > 9999) && (s < 2));
2212         return comma(n.toFixed(2)) + (sm ? '<small> ' : ' ') + (['KB', 'MB', 'GB'])[s] + (sm ? '</small>' : '');
2215 function scaleSize(n)
2217         return doScaleSize(n, 1);
2220 function timeString(mins)
2222         var h = Math.floor(mins / 60);
2223         if ((new Date(2000, 0, 1, 23, 0, 0, 0)).toLocaleString().indexOf('23') != -1)
2224                 return h + ':' + (mins % 60).pad(2);
2225         return ((h == 0) ? 12 : ((h > 12) ? h - 12 : h)) + ':' + (mins % 60).pad(2) + ((h >= 12) ? ' PM' : ' AM');
2228 function features(s)
2230         var features = ['ses','brau','aoss','wham','hpamp','!nve','11n','1000et'];
2231         var i;
2233         for (i = features.length - 1; i >= 0; --i) {
2234                 if (features[i] == s) return (parseInt(nvram.t_features) & (1 << i)) != 0;
2235         }
2236         return 0;
2239 function get_config(name, def)
2241         return ((typeof(nvram) != 'undefined') && (typeof(nvram[name]) != 'undefined')) ? nvram[name] : def;
2244 function nothing()
2248 // -----------------------------------------------------------------------------
2250 function show_notice1(s)
2252 // ---- !!TB - USB Support: multi-line notices
2253         if (s.length) document.write('<div id="notice1">' + s.replace(/\n/g, '<br>') + '</div><br style="clear:both">');
2256 // -----------------------------------------------------------------------------
2258 function myName()
2260         var name, i;
2262         name = document.location.pathname;
2263         name = name.replace(/\\/g, '/');        // IE local testing
2264         if ((i = name.lastIndexOf('/')) != -1) name = name.substring(i + 1, name.length);
2265         if (name == '') name = 'status-overview.asp';
2266         return name;
2269 function navi()
2271         var menu = [
2272                 ['Status',                              'status', 0, [
2273                         ['Overview',            'overview.asp'],
2274                         ['Device List',         'devices.asp'],
2275                         ['Web Usage',           'webmon.asp'],
2276                         ['Logs',                        'log.asp'] ] ],
2277                 ['Bandwidth',                   'bwm', 0, [
2278                         ['Real-Time',           'realtime.asp'],
2279                         ['Last 24 Hours',       '24.asp'],
2280                         ['Daily',                       'daily.asp'],
2281                         ['Weekly',                      'weekly.asp'],
2282                         ['Monthly',                     'monthly.asp'] ] ],
2284                 ['IP Traffic',                  'ipt', 0, [
2285                         ['Real-Time',           'realtime.asp'],
2286                         ['Last 24 Hours',       '24.asp'],
2287                         ['Transfer Rates',      'details.asp'],
2288                         ['Daily',                       'daily.asp'],
2289                         ['Monthly',                     'monthly.asp'] ] ],
2290                 ['Tools',                               'tools', 0, [
2291                         ['Ping',                        'ping.asp'],
2292                         ['Trace',                       'trace.asp'],
2293                         ['System',                      'shell.asp'],
2294                         ['Wireless Survey',     'survey.asp'],
2295                         ['WOL',                         'wol.asp'] ] ],
2296                 null,
2297                 ['Basic',                               'basic', 0, [
2298                         ['Network',                     'network.asp'],
2299 /* IPV6-BEGIN */
2300                         ['IPv6',                        'ipv6.asp'],
2301 /* IPV6-END */
2302                         ['Identification',      'ident.asp'],
2303                         ['Time',                        'time.asp'],
2304                         ['DDNS',                        'ddns.asp'],
2305                         ['Static DHCP/ARP',     'static.asp'],
2306                         ['Wireless Filter',     'wfilter.asp'] ] ],
2307                 ['Advanced',                    'advanced', 0, [
2308                         ['Conntrack/Netfilter', 'ctnf.asp'],
2309                         ['DHCP/DNS',            'dhcpdns.asp'],
2310                         ['Firewall',            'firewall.asp'],
2311                         ['MAC Address',         'mac.asp'],
2312                         ['Miscellaneous',       'misc.asp'],
2313                         ['Routing',                     'routing.asp'],
2314                         ['VLAN',                        'vlan.asp'],
2315                         ['LAN Access',                  'access.asp'],
2316                         ['Wireless',            'wireless.asp'] ] ],
2317                 ['Port Forwarding',     'forward', 0, [
2318                         ['Basic',                       'basic.asp'],
2319 /* IPV6-BEGIN */
2320                         ['Basic IPv6',          'basic-ipv6.asp'],
2321 /* IPV6-END */
2322                         ['DMZ',                         'dmz.asp'],
2323                         ['Triggered',           'triggered.asp'],
2324                         ['UPnP/NAT-PMP',        'upnp.asp'] ] ],
2325                 ['QoS',                                 'qos', 0, [
2326                         ['Basic Settings',      'settings.asp'],
2327                         ['Classification',      'classify.asp'],
2328                         ['View Graphs',         'graphs.asp'],
2329                         ['View Details',        'detailed.asp'],
2330                         ['Transfer Rates',      'ctrate.asp']
2331                         ] ],
2332                 ['Access Restriction',  'restrict.asp'],
2333 /* REMOVE-BEGIN
2334                 ['Scripts',                             'sc', 0, [
2335                         ['Startup',                     'startup.asp'],
2336                         ['Shutdown',            'shutdown.asp'],
2337                         ['Firewall',            'firewall.asp'],
2338                         ['WAN Up',                      'wanup.asp']
2339                         ] ],
2340 REMOVE-END */
2341 /* USB-BEGIN */
2342 // ---- !!TB - USB, FTP, Samba, Media Server
2343                 ['USB and NAS',                 'nas', 0, [
2344                         ['USB Support',         'usb.asp']
2345 /* FTP-BEGIN */
2346                         ,['FTP Server',         'ftp.asp']
2347 /* FTP-END */
2348 /* SAMBA-BEGIN */
2349                         ,['File Sharing',       'samba.asp']
2350 /* SAMBA-END */
2351 /* MEDIA-SRV-BEGIN */
2352                         ,['Media Server',       'media.asp']
2353 /* MEDIA-SRV-END */
2354                         ] ],
2355 /* USB-END */
2356 /* VPN-BEGIN */
2357                 ['VPN Tunneling',               'vpn', 0, [
2358                         ['Server',                      'server.asp'],
2359                         ['Client',                      'client.asp'] ] ],
2360 /* VPN-END */
2361                 null,
2362                 ['Administration',              'admin', 0, [
2363                         ['Admin Access',        'access.asp'],
2364                         ['Bandwidth Monitoring','bwm.asp'],
2365                         ['IP Traffic Monitoring','iptraffic.asp'],
2366                         ['Buttons/LED', 'buttons.asp'],
2367 /* CIFS-BEGIN */
2368                         ['CIFS Client',         'cifs.asp'],
2369 /* CIFS-END */
2370                         ['Configuration',       'config.asp'],
2371                         ['Debugging',           'debug.asp'],
2372 /* JFFS2-BEGIN */
2373                         ['JFFS',                        'jffs2.asp'],
2374 /* JFFS2-END */
2375                         ['Logging',                     'log.asp'],
2376                         ['Scheduler',           'sched.asp'],
2377                         ['Scripts',                     'scripts.asp'],
2378 /* SNMP-BEGIN */
2379                         ['SNMP',                'snmp.asp'],
2380 /* SNMP-END */
2381                         ['Upgrade',                     'upgrade.asp'] ] ],
2382                 null,
2383                 ['About',                               'about.asp'],
2384                 ['Reboot...',                   'javascript:reboot()'],
2385                 ['Shutdown...',                 'javascript:shutdown()'],
2386                 ['Logout',                              'javascript:logout()']
2387         ];
2388         var name, base;
2389         var i, j;
2390         var buf = [];
2391         var sm;
2392         var a, b, c;
2393         var on1;
2394         var cexp = get_config('web_mx', '').toLowerCase();
2396         name = myName();
2397         if (name == 'restrict-edit.asp') name = 'restrict.asp';
2398         if ((i = name.indexOf('-')) != -1) {
2399                 base = name.substring(0, i);
2400                 name = name.substring(i + 1, name.length);
2401         }
2402         else base = '';
2404         for (i = 0; i < menu.length; ++i) {
2405                 var m = menu[i];
2406                 if (!m) {
2407                         buf.push("<br>");
2408                         continue;
2409                 }
2410                 if (m.length == 2) {
2411                         buf.push('<a href="' + m[1] + '" class="indent1' + (((base == '') && (name == m[1])) ? ' active' : '') + '">' + m[0] + '</a>');
2412                 }
2413                 else {
2414                         if (base == m[1]) {
2415                                 b = name;
2416                         }
2417                         else {
2418                                 a = cookie.get('menu_' + m[1]);
2419                                 b = m[3][0][1];
2420                                 for (j = 0; j < m[3].length; ++j) {
2421                                         if (m[3][j][1] == a) {
2422                                                 b = a;
2423                                                 break;
2424                                         }
2425                                 }
2426                         }
2427                         a = m[1] + '-' + b;
2428                         if (a == 'status-overview.asp') a = '/';
2429                         on1 = (base == m[1]);
2430                         buf.push('<a href="' + a + '" class="indent1' + (on1 ? ' active' : '') + '">' + m[0] + '</a>');
2431                         if ((!on1) && (m[2] == 0) && (cexp.indexOf(m[1]) == -1)) continue;
2433                         for (j = 0; j < m[3].length; ++j) {
2434                                 sm = m[3][j];
2435                                 a = m[1] + '-' + sm[1];
2436                                 if (a == 'status-overview.asp') a = '/';
2437                                 buf.push('<a href="' + a + '" class="indent2' + (((on1) && (name == sm[1])) ? ' active' : '') + '">' + sm[0] + '</a>');
2438                         }
2439                 }
2440         }
2441         document.write(buf.join(''));
2443         if (base.length) {
2444                 if ((base == 'qos') && (name == 'detailed.asp')) name = 'view.asp';
2445                 cookie.set('menu_' + base, name);
2446         }
2449 function createFieldTable(flags, desc)
2451         var common;
2452         var i, n;
2453         var name;
2454         var id;
2455         var fields;
2456         var f;
2457         var a;
2458         var buf = [];
2459         var buf2;
2460         var id1;
2461         var tr;
2463         if ((flags.indexOf('noopen') == -1)) buf.push('<table class="fields">');
2464         for (desci = 0; desci < desc.length; ++desci) {
2465                 var v = desc[desci];
2467                 if (!v) {
2468                         buf.push('<tr><td colspan=2 class="spacer">&nbsp;</td></tr>');
2469                         continue;
2470                 }
2472                 if (v.ignore) continue;
2474                 buf.push('<tr');
2475                 if (v.rid) buf.push(' id="' + v.rid + '"');
2476                 if (v.hidden) buf.push(' style="display:none"');
2477                 buf.push('>');
2479                 if (v.text) {
2480                         if (v.title) {
2481                                 buf.push('<td class="title indent' + (v.indent || 1) + '">' + v.title + '</td><td class="content">' + v.text + '</td></tr>');
2482                         }
2483                         else {
2484                                 buf.push('<td colspan=2>' + v.text + '</td></tr>');
2485                         }
2486                         continue;
2487                 }
2489                 id1 = '';
2490                 buf2 = [];
2491                 buf2.push('<td class="content">');
2493                 if (v.multi) fields = v.multi;
2494                         else fields = [v];
2496                 for (n = 0; n < fields.length; ++n) {
2497                         f = fields[n];
2498                         if (f.prefix) buf2.push(f.prefix);
2500                         if ((f.type == 'radio') && (!f.id)) id = '_' + f.name + '_' + i;
2501                                 else id = (f.id ? f.id : ('_' + f.name));
2503                         if (id1 == '') id1 = id;
2505                         common = ' onchange="verifyFields(this, 1)" id="' + id + '"';
2506                         if (f.attrib) common += ' ' + f.attrib;
2507                         name = f.name ? (' name="' + f.name + '"') : '';
2509                         switch (f.type) {
2510                         case 'checkbox':
2511                                 buf2.push('<input type="checkbox"' + name + (f.value ? ' checked' : '') + ' onclick="verifyFields(this, 1)"' + common + '>');
2512                                 break;
2513                         case 'radio':
2514                                 buf2.push('<input type="radio"' + name + (f.value ? ' checked' : '') + ' onclick="verifyFields(this, 1)"' + common + '>');
2515                                 break;
2516                         case 'password':
2517                                 if (f.peekaboo) {
2518                                         switch (get_config('web_pb', '1')) {
2519                                         case '0':
2520                                                 f.type = 'text';
2521                                         case '2':
2522                                                 f.peekaboo = 0;
2523                                                 break;
2524                                         }
2525                                 }
2526                                 if (f.type == 'password') {
2527                                         common += ' autocomplete="off"';
2528                                         if (f.peekaboo) common += ' onfocus=\'peekaboo("' + id + '",1)\'';
2529                                 }
2530                                 // drop
2531                         case 'text':
2532                                 buf2.push('<input type="' + f.type + '"' + name + ' value="' + escapeHTML(UT(f.value)) + '" maxlength=' + f.maxlen + (f.size ? (' size=' + f.size) : '') + common + '>');
2533                                 break;
2534                         case 'select':
2535                                 buf2.push('<select' + name + common + '>');
2536                                 for (i = 0; i < f.options.length; ++i) {
2537                                         a = f.options[i];
2538                                         if (a.length == 1) a.push(a[0]);
2539                                         buf2.push('<option value="' + a[0] + '"' + ((a[0] == f.value) ? ' selected' : '') + '>' + a[1] + '</option>');
2540                                 }
2541                                 buf2.push('</select>');
2542                                 break;
2543                         case 'textarea':
2544                                 buf2.push('<textarea' + name + common + (f.wrap ? (' wrap=' + f.wrap) : '') + '>' + escapeHTML(UT(f.value)) + '</textarea>');
2545                                 break;
2546                         default:
2547                                 if (f.custom) buf2.push(f.custom);
2548                                 break;
2549                         }
2550                         if (f.suffix) buf2.push(f.suffix);
2551                 }
2552                 buf2.push('</td>');
2554                 buf.push('<td class="title indent' + (v.indent ? v.indent : 1) + '">');
2555                 if (id1 != '') buf.push('<label for="' + id + '">' + v.title + '</label></td>');
2556                         else buf.push(+ v.title + '</td>');
2558                 buf.push(buf2.join(''));
2559                 buf.push('</tr>');
2560         }
2561         if ((!flags) || (flags.indexOf('noclose') == -1)) buf.push('</table>');
2562         document.write(buf.join(''));
2565 function peekaboo(id, show)
2567         try {
2568                 var o = document.createElement('INPUT');
2569                 var e = E(id);
2570                 var name = e.name;
2571                 o.type = show ? 'text' : 'password';
2572                 o.value = e.value;
2573                 o.size = e.size;
2574                 o.maxLength = e.maxLength;
2575                 o.autocomplete = e.autocomplete;
2576                 o.title = e.title;
2577                 o.disabled = e.disabled;
2578                 o.onchange = e.onchange;
2579                 e.parentNode.replaceChild(o, e);
2580                 e = null;
2581                 o.id = id;
2582                 o.name = name;
2584                 if (show) {
2585                         o.onblur = function(ev) { setTimeout('peekaboo("' + this.id + '", 0)', 0) };
2586                         setTimeout('try { E("' + id + '").focus() } catch (ex) { }', 0)
2587                 }
2588                 else {
2589                         o.onfocus = function(ev) { peekaboo(this.id, 1); };
2590                 }
2591         }
2592         catch (ex) {
2593 //              alert(ex);
2594         }
2596 /* REMOVE-BEGIN
2597 notes:
2598  - e.type= doesn't work in IE, ok in FF
2599  - may mess keyboard tabing (bad: IE; ok: FF, Opera)... setTimeout() delay seems to help a little.
2600 REMOVE-END */
2603 // -----------------------------------------------------------------------------
2605 function reloadPage()
2607         document.location.reload(1);
2610 function reboot()
2612         if (confirm("Reboot?")) form.submitHidden('tomato.cgi', { _reboot: 1, _commit: 0, _nvset: 0 });
2615 function shutdown()
2617         if (confirm("Shutdown?")) form.submitHidden('shutdown.cgi', { });
2620 function logout()
2622         form.submitHidden('logout.asp', { });
2625 // -----------------------------------------------------------------------------
2629 // ---- debug
2631 function isLocal()
2633         return location.href.search('file://') == 0;
2636 function console(s)