www: Changes for ipv6 menu, build options, Remote access with dmz enabled.
[tomato.git] / release / src / router / www / tomato.js
blob08029565a75cb32d4b1a351abe82422a60925dd1
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;
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;
67 return r;
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;
80 if (k) e.className = a.join(' ');
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(' ');
92 remove: function(e) {
93 if ((e = E(e)) != null) e.parentNode.removeChild(e);
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;
103 return null;
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';
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;
119 return true;
122 setInnerHTML: function(e, html) {
123 e = E(e);
124 if (e.innerHTML != html) e.innerHTML = html; // reduce flickering
128 // -----------------------------------------------------------------------------
130 var docu = {
131 getViewSize: function() {
132 if (window.innerHeight) {
133 return { width: window.innerWidth, height: window.innerHeight };
135 else if (document.documentElement && document.documentElement.clientHeight) {
136 return { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight };
138 return { width: document.body.clientWidth, height: document.body.clientHeight };
141 getPageOffset: function()
143 if (typeof(window.pageYOffset) != 'undefined') {
144 return { x: window.pageXOffset, y: window.pageYOffset };
146 else if ((document.documentElement) && (typeof(document.documentElement.scrollTop) != 'undefined')) {
147 return { x: document.documentElement.scrollLeft, y: document.documentElement.scrollTop };
149 return { x: document.body.scrollLeft, y: document.body.scrollTop };
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]));
170 return a;
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;
180 else {
181 var a = this.getAll(E(e));
182 for (var i = a.length - 1; i >= 0; --i) {
183 a[i].disabled = d;
187 radio: {
188 selected: function(e) {
189 for (var i = 0; i < e.length; ++i) {
190 if (e[i].checked) return e[i];
192 return null;
194 find: function(e, value) {
195 for (var i = 0; i < e.length; ++i) {
196 if (e[i].value == value) return e[i];
198 return null;
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);
219 body = document.getElementsByTagName('body')[0];
220 fom = body.appendChild(fom);
221 this.submit(fom);
222 body.removeChild(fom);
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;
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;
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);
258 v.push(escapeCGI(f.name) + '=' + escapeCGI(f.value));
261 if ((msg = E('footer-msg')) != null) {
262 msg.innerHTML = 'Saving...';
263 msg.style.visibility = 'visible';
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';
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;
281 this.xhttp.onError = function(x) {
282 if (url) fom.action = url;
283 fom.submit();
286 this.xhttp.post(url ? url : fom.action, v.join('&'));
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);
299 else {
300 fom._http_id.value = nvram.http_id;
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;
309 dump: function(fom, async, url) {
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);
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;
333 clearAll: function(e) {
334 for (var i = 0; i < e.length; ++i)
335 this.clear(e[i]);
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');
347 ok: function(e) {
348 if ((e = E(e)) == null) return 0;
349 return !e._error_msg;
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;
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');
406 ferror.set(e, 'Invalid number of minutes.', quiet);
407 return 0;
410 function v_macip(e, quiet, bok, lan_ipaddr, lan_netmask)
412 var s, a, b, c, d, i;
413 var ipp, temp;
415 temp = lan_ipaddr.split('.');
416 ipp = temp[0]+'.'+temp[1]+'.'+temp[2]+'.';
418 if ((e = E(e)) == null) return 0;
419 s = e.value.replace(/\s+/g, '');
421 if ((a = fixMAC(s)) != null) {
422 if (isMAC0(a)) {
423 if (bok) {
424 e.value = '';
426 else {
427 ferror.set(e, 'Invalid MAC or IP address');
428 return false;
431 else e.value = a;
432 ferror.clear(e);
433 return true;
436 a = s.split('-');
438 if (a.length > 2) {
439 ferror.set(e, 'Invalid IP address range', quiet);
440 return false;
443 if (a[0].match(/^\d+$/)){
444 a[0]=ipp+a[0];
445 if ((a.length == 2) && (a[1].match(/^\d+$/)))
446 a[1]=ipp+a[1];
448 else{
449 if ((a.length == 2) && (a[1].match(/^\d+$/))){
450 temp=a[0].split('.');
451 a[1]=temp[0]+'.'+temp[1]+'.'+temp[2]+'.'+a[1];
454 for (i = 0; i < a.length; ++i) {
455 b = a[i];
456 b = fixIP(b);
457 if (!b) {
458 ferror.set(e, 'Invalid IP address', quiet);
459 return false;
462 if ((aton(b) & aton(lan_netmask))!=(aton(lan_ipaddr) & aton(lan_netmask))) {
463 ferror.set(e, 'IP address outside of LAN', quiet);
464 return false;
467 d = (b.split('.'))[3];
468 if (parseInt(d) <= parseInt(c)) {
469 ferror.set(e, 'Invalid IP address range', quiet);
470 return false;
473 a[i] = c = d;
475 e.value = b.split('.')[0] + '.' + b.split('.')[1] + '.' + b.split('.')[2] + '.' + a.join('-');
476 return true;
479 function fixIP(ip, x)
481 var a, n, i;
482 a = ip;
483 i = a.indexOf("<br>");
484 if (i > 0)
485 a = a.slice(0,i);
487 a = a.split('.');
488 if (a.length != 4) return null;
489 for (i = 0; i < 4; ++i) {
490 n = a[i] * 1;
491 if ((isNaN(n)) || (n < 0) || (n > 255)) return null;
492 a[i] = n;
494 if ((x) && ((a[3] == 0) || (a[3] == 255))) return null;
495 return a.join('.');
498 function v_ip(e, quiet, x)
500 var ip;
502 if ((e = E(e)) == null) return 0;
503 ip = fixIP(e.value, x);
504 if (!ip) {
505 ferror.set(e, 'Invalid IP address', quiet);
506 return false;
508 e.value = ip;
509 ferror.clear(e);
510 return true;
513 function v_ipz(e, quiet)
515 if ((e = E(e)) == null) return 0;
516 if (e.value == '') e.value = '0.0.0.0';
517 return v_ip(e, quiet);
520 function v_dns(e, quiet)
522 if ((e = E(e)) == null) return 0;
523 if (e.value == '') {
524 e.value = '0.0.0.0';
526 else {
527 var s = e.value.split(':');
528 if (s.length == 1) {
529 s.push(53);
531 else if (s.length != 2) {
532 ferror.set(e, 'Invalid IP address or port', quiet);
533 return false;
536 if ((s[0] = fixIP(s[0])) == null) {
537 ferror.set(e, 'Invalid IP address', quiet);
538 return false;
541 if ((s[1] = fixPort(s[1], -1)) == -1) {
542 ferror.set(e, 'Invalid port', quiet);
543 return false;
546 if (s[1] == 53) {
547 e.value = s[0];
549 else {
550 e.value = s.join(':');
554 ferror.clear(e);
555 return true;
558 function aton(ip)
560 var o, x, i;
562 // ---- this is goofy because << mangles numbers as signed
563 o = ip.split('.');
564 x = '';
565 for (i = 0; i < 4; ++i) x += (o[i] * 1).hex(2);
566 return parseInt(x, 16);
569 function ntoa(ip)
571 return ((ip >> 24) & 255) + '.' + ((ip >> 16) & 255) + '.' + ((ip >> 8) & 255) + '.' + (ip & 255);
575 // ---- 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
576 function _v_iptip(e, ip, quiet)
578 var ma, x, y, z, oip;
579 var a, b;
581 oip = ip;
583 // x.x.x.x - y.y.y.y
584 if (ip.match(/^(.*)-(.*)$/)) {
585 a = fixIP(RegExp.$1);
586 b = fixIP(RegExp.$2);
587 if ((a == null) || (b == null)) {
588 ferror.set(e, oip + ' - invalid IP address range', quiet);
589 return null;
591 ferror.clear(e);
593 if (aton(a) > aton(b)) return b + '-' + a;
594 return a + '-' + b;
597 ma = '';
599 // x.x.x.x/nn
600 // x.x.x.x/y.y.y.y
601 if (ip.match(/^(.*)\/(.*)$/)) {
602 ip = RegExp.$1;
603 b = RegExp.$2;
605 ma = b * 1;
606 if (isNaN(ma)) {
607 ma = fixIP(b);
608 if ((ma == null) || (!_v_netmask(ma))) {
609 ferror.set(e, oip + ' - invalid netmask', quiet);
610 return null;
613 else {
614 if ((ma < 0) || (ma > 32)) {
615 ferror.set(e, oip + ' - invalid netmask', quiet);
616 return null;
621 ip = fixIP(ip);
622 if (!ip) {
623 ferror.set(e, oip + ' - invalid IP address', quiet);
624 return null;
627 ferror.clear(e);
628 return ip + ((ma != '') ? ('/' + ma) : '');
631 function v_iptip(e, quiet, multi)
633 var v, i;
635 if ((e = E(e)) == null) return 0;
636 v = e.value.split(',');
637 if (multi) {
638 if (v.length > multi) {
639 ferror.set(e, 'Too many IP addresses', quiet);
640 return 0;
643 else {
644 if (v.length > 1) {
645 ferror.set(e, 'Invalid IP address', quiet);
646 return 0;
649 for (i = 0; i < v.length; ++i) {
650 if ((v[i] = _v_iptip(e, v[i], quiet)) == null) return 0;
652 e.value = v.join(', ');
653 return 1;
656 function _v_domain(e, dom, quiet)
658 var s;
660 s = dom.replace(/\s+/g, ' ').trim();
661 if (s.length > 0) {
662 s = _v_hostname(e, s, 1, 1, 7, '.', true);
663 if (s == null) {
664 ferror.set(e, "Invalid name. Only characters \"A-Z 0-9 . -\" are allowed.", quiet);
665 return null;
668 ferror.clear(e);
669 return s;
672 function v_domain(e, quiet)
674 var v;
676 if ((e = E(e)) == null) return 0;
677 if ((v = _v_domain(e, e.value, quiet)) == null) return 0;
679 e.value = v;
680 return 1;
683 /* IPV6-BEGIN */
684 function ExpandIPv6Address(ip)
686 var a, pre, n, i, fill, post;
688 ip = ip.toLowerCase();
689 if (!ip.match(/^(::)?([a-f0-9]{1,4}::?){0,7}([a-f0-9]{1,4})(::)?$/)) return null;
691 a = ip.split('::');
692 switch (a.length) {
693 case 1:
694 if (a[0] == '') return null;
695 pre = a[0].split(':');
696 if (pre.length != 8) return null;
697 ip = pre.join(':');
698 break;
699 case 2:
700 pre = a[0].split(':');
701 post = a[1].split(':');
702 n = 8 - pre.length - post.length;
703 for (i=0; i<2; i++) {
704 if (a[i]=='') n++;
706 if (n < 0) return null;
707 fill = '';
708 while (n-- > 0) fill += ':0';
709 ip = pre.join(':') + fill + ':' + post.join(':');
710 ip = ip.replace(/^:/, '').replace(/:$/, '');
711 break;
712 default:
713 return null;
716 ip = ip.replace(/([a-f0-9]{1,4})/ig, '000$1');
717 ip = ip.replace(/0{0,3}([a-f0-9]{4})/ig, '$1');
718 return ip;
721 function CompressIPv6Address(ip)
723 var a, segments;
725 ip = ExpandIPv6Address(ip);
726 if (!ip) return null;
728 // if (ip.match(/(?:^00)|(?:^fe[8-9a-b])|(?:^ff)/)) return null; // not valid routable unicast address
730 ip = ip.replace(/(^|:)0{1,3}/g, '$1');
731 ip = ip.replace(/(:0)+$/, '::');
732 ip = ip.replace(/(?:(?:^|:)0){2,}(?!.*(?:::|(?::0){3,}))/, ':');
733 return ip;
736 function ZeroIPv6PrefixBits(ip, prefix_length)
738 var b, c, m, n;
739 ip = ExpandIPv6Address(ip);
740 ip = ip.replace(/:/g,'');
741 n = Math.floor(prefix_length/4);
742 m = 32 - Math.ceil(prefix_length/4);
743 b = prefix_length % 4;
744 if (b != 0)
745 c = (parseInt(ip.charAt(n), 16) & (0xf << 4-b)).toString(16);
746 else
747 c = '';
749 ip = ip.substring(0, n) + c + Array((m%4)+1).join('0') + (m>=4 ? '::' : '');
750 ip = ip.replace(/([a-f0-9]{4})(?=[a-f0-9])/g,'$1:');
751 ip = ip.replace(/(^|:)0{1,3}/g, '$1');
752 return ip;
755 function ipv6ton(ip)
757 var o, x, i;
759 ip = ExpandIPv6Address(ip);
760 if (!ip) return 0;
762 o = ip.split(':');
763 x = '';
764 for (i = 0; i < 8; ++i) x += (('0x' + o[i]) * 1).hex(4);
765 return parseInt(x, 16);
768 function _v_ipv6_addr(e, ip, ipt, quiet)
770 var oip;
771 var a, b;
773 oip = ip;
775 // ip range
776 if ((ipt) && ip.match(/^(.*)-(.*)$/)) {
777 a = RegExp.$1;
778 b = RegExp.$2;
779 a = CompressIPv6Address(a);
780 b = CompressIPv6Address(b);
781 if ((a == null) || (b == null)) {
782 ferror.set(e, oip + ' - invalid IPv6 address range', quiet);
783 return null;
785 ferror.clear(e);
787 if (ipv6ton(a) > ipv6ton(b)) return b + '-' + a;
788 return a + '-' + b;
791 if ((ipt) && ip.match(/^([A-Fa-f0-9:]+)\/(\d+)$/)) {
792 a = RegExp.$1;
793 b = parseInt(RegExp.$2, 10);
794 a = ExpandIPv6Address(a);
795 if ((a == null) || (b == null)) {
796 ferror.set(e, oip + ' - invalid IPv6 address', quiet);
797 return null;
799 if (b < 0 || b > 128) {
800 ferror.set(e, oip + ' - invalid CIDR notation on IPv6 address', quiet);
801 return null;
803 ferror.clear(e);
805 ip = ZeroIPv6PrefixBits(a, b);
806 return ip + '/' + b.toString(10);
809 ip = CompressIPv6Address(oip);
810 if (!ip) {
811 ferror.set(e, oip + ' - invalid IPv6 address', quiet);
812 return null;
815 ferror.clear(e);
816 return ip;
819 function v_ipv6_addr(e, quiet)
821 if ((e = E(e)) == null) return 0;
823 ip = _v_ipv6_addr(e, e.value, false, quiet);
824 if (ip) e.value = ip;
825 return (ip != null);
827 /* IPV6-END */
829 function fixPort(p, def)
831 if (def == null) def = -1;
832 if (p == null) return def;
833 p *= 1;
834 if ((isNaN(p) || (p < 1) || (p > 65535) || (('' + p).indexOf('.') != -1))) return def;
835 return p;
838 function _v_portrange(e, quiet, v)
840 if (v.match(/^(.*)[-:](.*)$/)) {
841 var x = RegExp.$1;
842 var y = RegExp.$2;
844 x = fixPort(x, -1);
845 y = fixPort(y, -1);
846 if ((x == -1) || (y == -1)) {
847 ferror.set(e, 'Invalid port range: ' + v, quiet);
848 return null;
850 if (x > y) {
851 v = x;
852 x = y;
853 y = v;
855 ferror.clear(e);
856 if (x == y) return x;
857 return x + '-' + y;
860 v = fixPort(v, -1);
861 if (v == -1) {
862 ferror.set(e, 'Invalid port', quiet);
863 return null;
866 ferror.clear(e);
867 return v;
870 function v_portrange(e, quiet)
872 var v;
874 if ((e = E(e)) == null) return 0;
875 v = _v_portrange(e, quiet, e.value);
876 if (v == null) return 0;
877 e.value = v;
878 return 1;
881 function v_iptport(e, quiet)
883 var a, i, v, q;
885 if ((e = E(e)) == null) return 0;
887 a = e.value.split(/[,\.]/);
889 if (a.length == 0) {
890 ferror.set(e, 'Expecting a list of ports or port range.', quiet);
891 return 0;
893 if (a.length > 10) {
894 ferror.set(e, 'Only 10 ports/range sets are allowed.', quiet);
895 return 0;
898 q = [];
899 for (i = 0; i < a.length; ++i) {
900 v = _v_portrange(e, quiet, a[i]);
901 if (v == null) return 0;
902 q.push(v);
905 e.value = q.join(',');
906 ferror.clear(e);
907 return 1;
910 function _v_netmask(mask)
912 var v = aton(mask) ^ 0xFFFFFFFF;
913 return (((v + 1) & v) == 0);
916 function v_netmask(e, quiet)
918 var n, b;
920 if ((e = E(e)) == null) return 0;
921 n = fixIP(e.value);
922 if (n) {
923 if (_v_netmask(n)) {
924 e.value = n;
925 ferror.clear(e);
926 return 1;
929 else if (e.value.match(/^\s*\/\s*(\d+)\s*$/)) {
930 b = RegExp.$1 * 1;
931 if ((b >= 1) && (b <= 32)) {
932 if (b == 32) n = 0xFFFFFFFF; // js quirk
933 else n = (0xFFFFFFFF >>> b) ^ 0xFFFFFFFF;
934 e.value = (n >>> 24) + '.' + ((n >>> 16) & 0xFF) + '.' + ((n >>> 8) & 0xFF) + '.' + (n & 0xFF);
935 ferror.clear(e);
936 return 1;
939 ferror.set(e, 'Invalid netmask', quiet);
940 return 0;
943 function fixMAC(mac)
945 var t, i;
947 mac = mac.replace(/\s+/g, '').toUpperCase();
948 if (mac.length == 0) {
949 mac = [0,0,0,0,0,0];
951 else if (mac.length == 12) {
952 mac = mac.match(/../g);
954 else {
955 mac = mac.split(/[:\-]/);
956 if (mac.length != 6) return null;
958 for (i = 0; i < 6; ++i) {
959 t = '' + mac[i];
960 if (t.search(/^[0-9A-F]+$/) == -1) return null;
961 if ((t = parseInt(t, 16)) > 255) return null;
962 mac[i] = t.hex(2);
964 return mac.join(':');
967 function v_mac(e, quiet)
969 var mac;
971 if ((e = E(e)) == null) return 0;
972 mac = fixMAC(e.value);
973 if ((!mac) || (isMAC0(mac))) {
974 ferror.set(e, 'Invalid MAC address', quiet);
975 return 0;
977 e.value = mac;
978 ferror.clear(e);
979 return 1;
982 function v_macz(e, quiet)
984 var mac;
986 if ((e = E(e)) == null) return 0;
987 mac = fixMAC(e.value);
988 if (!mac) {
989 ferror.set(e, 'Invalid MAC address', quiet);
990 return false;
992 e.value = mac;
993 ferror.clear(e);
994 return true;
997 function v_length(e, quiet, min, max)
999 var s, n;
1001 if ((e = E(e)) == null) return 0;
1002 s = e.value.trim();
1003 n = s.length;
1004 if (min == undefined) min = 1;
1005 if (n < min) {
1006 ferror.set(e, 'Invalid length. Please enter at least ' + min + ' character' + (min == 1 ? '.' : 's.'), quiet);
1007 return 0;
1009 max = max || e.maxlength;
1010 if (n > max) {
1011 ferror.set(e, 'Invalid length. Please reduce the length to ' + max + ' characters or less.', quiet);
1012 return 0;
1014 e.value = s;
1015 ferror.clear(e);
1016 return 1;
1019 function _v_iptaddr(e, quiet, multi, ipv4, ipv6)
1021 var v, t, i;
1023 if ((e = E(e)) == null) return 0;
1024 v = e.value.split(',');
1025 if (multi) {
1026 if (v.length > multi) {
1027 ferror.set(e, 'Too many addresses', quiet);
1028 return 0;
1031 else {
1032 if (v.length > 1) {
1033 ferror.set(e, 'Invalid domain name or IP address', quiet);
1034 return 0;
1038 for (i = 0; i < v.length; ++i) {
1039 if ((t = _v_domain(e, v[i], 1)) == null) {
1040 /* IPV6-BEGIN */
1041 if ((!ipv6) && (!ipv4)) {
1042 if (!quiet) ferror.show(e);
1043 return 0;
1045 if ((!ipv6) || ((t = _v_ipv6_addr(e, v[i], 1, 1)) == null)) {
1046 /* IPV6-END */
1047 if (!ipv4) {
1048 if (!quiet) ferror.show(e);
1049 return 0;
1051 if ((t = _v_iptip(e, v[i], 1)) == null) {
1052 ferror.set(e, e._error_msg + ', or invalid domain name', quiet);
1053 return 0;
1055 /* IPV6-BEGIN */
1057 /* IPV6-END */
1059 v[i] = t;
1062 e.value = v.join(', ');
1063 ferror.clear(e);
1064 return 1;
1067 function v_iptaddr(e, quiet, multi)
1069 return _v_iptaddr(e, quiet, multi, 1, 0);
1072 function _v_hostname(e, h, quiet, required, multi, delim, cidr)
1074 var s;
1075 var v, i;
1076 var re;
1078 v = (typeof(delim) == 'undefined') ? h.split(/\s+/) : h.split(delim);
1080 if (multi) {
1081 if (v.length > multi) {
1082 ferror.set(e, 'Too many hostnames.', quiet);
1083 return null;
1086 else {
1087 if (v.length > 1) {
1088 ferror.set(e, 'Invalid hostname.', quiet);
1089 return null;
1093 re = /^[a-zA-Z0-9](([a-zA-Z0-9\-]{0,61})[a-zA-Z0-9]){0,1}$/;
1095 for (i = 0; i < v.length; ++i) {
1096 s = v[i].replace(/_+/g, '-').replace(/\s+/g, '-');
1097 if (s.length > 0) {
1098 if (cidr && i == v.length-1)
1099 re = /^[a-zA-Z0-9](([a-zA-Z0-9\-]{0,61})[a-zA-Z0-9]){0,1}(\/\d{1,3})?$/;
1100 if (s.search(re) == -1 || s.search(/^\d+$/) != -1) {
1101 ferror.set(e, 'Invalid hostname. Only "A-Z 0-9" and "-" in the middle are allowed (up to 63 characters).', quiet);
1102 return null;
1104 } else if (required) {
1105 ferror.set(e, 'Invalid hostname.', quiet);
1106 return null;
1108 v[i] = s;
1111 ferror.clear(e);
1112 return v.join((typeof(delim) == 'undefined') ? ' ' : delim);
1115 function v_hostname(e, quiet, multi, delim)
1117 var v;
1119 if ((e = E(e)) == null) return 0;
1121 v = _v_hostname(e, e.value, quiet, 0, multi, delim, false);
1123 if (v == null) return 0;
1125 e.value = v;
1126 return 1;
1129 function v_nodelim(e, quiet, name, checklist)
1131 if ((e = E(e)) == null) return 0;
1133 e.value = e.value.trim();
1134 if (e.value.indexOf('<') != -1 ||
1135 (checklist && e.value.indexOf('>') != -1)) {
1136 ferror.set(e, 'Invalid ' + name + ': \"<\" ' + (checklist ? 'or \">\" are' : 'is') + ' not allowed.', quiet);
1137 return 0;
1139 ferror.clear(e);
1140 return 1;
1143 function v_path(e, quiet, required)
1145 if ((e = E(e)) == null) return 0;
1146 if (required && !v_length(e, quiet, 1)) return 0;
1148 if (!required && e.value.trim().length == 0) {
1149 ferror.clear(e);
1150 return 1;
1152 if (e.value.substr(0, 1) != '/') {
1153 ferror.set(e, 'Please start at the / root directory.', quiet);
1154 return 0;
1156 ferror.clear(e);
1157 return 1;
1160 function isMAC0(mac)
1162 return (mac == '00:00:00:00:00:00');
1165 // -----------------------------------------------------------------------------
1167 function cmpIP(a, b)
1169 if ((a = fixIP(a)) == null) a = '255.255.255.255';
1170 if ((b = fixIP(b)) == null) b = '255.255.255.255';
1171 return aton(a) - aton(b);
1174 function cmpText(a, b)
1176 if (a == '') a = '\xff';
1177 if (b == '') b = '\xff';
1178 return (a < b) ? -1 : ((a > b) ? 1 : 0);
1181 function cmpInt(a, b)
1183 a = parseInt(a, 10);
1184 b = parseInt(b, 10);
1185 return ((isNaN(a)) ? -0x7FFFFFFF : a) - ((isNaN(b)) ? -0x7FFFFFFF : b);
1188 function cmpFloat(a, b)
1190 a = parseFloat(a);
1191 b = parseFloat(b);
1192 return ((isNaN(a)) ? -Number.MAX_VALUE : a) - ((isNaN(b)) ? -Number.MAX_VALUE : b);
1195 function cmpDate(a, b)
1197 return b.getTime() - a.getTime();
1200 // -----------------------------------------------------------------------------
1202 // ---- todo: cleanup this mess
1204 function TGO(e)
1206 return elem.parentElem(e, 'TABLE').gridObj;
1209 function tgHideIcons()
1211 var e;
1212 while ((e = document.getElementById('tg-row-panel')) != null) e.parentNode.removeChild(e);
1215 // ---- options = sort, move, delete
1216 function TomatoGrid(tb, options, maxAdd, editorFields)
1218 this.init(tb, options, maxAdd, editorFields);
1219 return this;
1222 TomatoGrid.prototype = {
1223 init: function(tb, options, maxAdd, editorFields) {
1224 if (tb) {
1225 this.tb = E(tb);
1226 this.tb.gridObj = this;
1228 else {
1229 this.tb = null;
1231 if (!options) options = '';
1232 this.header = null;
1233 this.footer = null;
1234 this.editor = null;
1235 this.canSort = options.indexOf('sort') != -1;
1236 this.canMove = options.indexOf('move') != -1;
1237 this.maxAdd = maxAdd || 500;
1238 this.canEdit = (editorFields != null);
1239 this.canDelete = this.canEdit || (options.indexOf('delete') != -1);
1240 this.editorFields = editorFields;
1241 this.sortColumn = -1;
1242 this.sortAscending = true;
1245 _insert: function(at, cells, escCells) {
1246 var tr, td, c;
1247 var i, t;
1249 tr = this.tb.insertRow(at);
1250 for (i = 0; i < cells.length; ++i) {
1251 c = cells[i];
1252 if (typeof(c) == 'string') {
1253 td = tr.insertCell(i);
1254 td.className = 'co' + (i + 1);
1255 if (escCells) td.appendChild(document.createTextNode(c));
1256 else td.innerHTML = c;
1258 else {
1259 tr.appendChild(c);
1262 return tr;
1265 // ---- header
1267 headerClick: function(cell) {
1268 if (this.canSort) {
1269 this.sort(cell.cellN);
1273 headerSet: function(cells, escCells) {
1274 var e, i;
1276 elem.remove(this.header);
1277 this.header = e = this._insert(0, cells, escCells);
1278 e.className = 'header';
1280 for (i = 0; i < e.cells.length; ++i) {
1281 e.cells[i].cellN = i; // cellIndex broken in Safari
1282 e.cells[i].onclick = function() { return TGO(this).headerClick(this); };
1284 return e;
1287 // ---- footer
1289 footerClick: function(cell) {
1292 footerSet: function(cells, escCells) {
1293 var e, i;
1295 elem.remove(this.footer);
1296 this.footer = e = this._insert(-1, cells, escCells);
1297 e.className = 'footer';
1298 for (i = 0; i < e.cells.length; ++i) {
1299 e.cells[i].cellN = i;
1300 e.cells[i].onclick = function() { TGO(this).footerClick(this) };
1302 return e;
1305 // ----
1307 rpUp: function(e) {
1308 var i;
1310 e = PR(e);
1311 TGO(e).moving = null;
1312 i = e.previousSibling;
1313 if (i == this.header) return;
1314 e.parentNode.removeChild(e);
1315 i.parentNode.insertBefore(e, i);
1317 this.recolor();
1318 this.rpHide();
1321 rpDn: function(e) {
1322 var i;
1324 e = PR(e);
1325 TGO(e).moving = null;
1326 i = e.nextSibling;
1327 if (i == this.footer) return;
1328 e.parentNode.removeChild(e);
1329 i.parentNode.insertBefore(e, i.nextSibling);
1331 this.recolor();
1332 this.rpHide();
1335 rpMo: function(img, e) {
1336 var me;
1338 e = PR(e);
1339 me = TGO(e);
1340 if (me.moving == e) {
1341 me.moving = null;
1342 this.rpHide();
1343 return;
1345 me.moving = e;
1346 img.style.border = "1px dotted red";
1349 rpDel: function(e) {
1350 e = PR(e);
1351 TGO(e).moving = null;
1352 e.parentNode.removeChild(e);
1353 this.recolor();
1354 this.rpHide();
1357 rpMouIn: function(evt) {
1358 var e, x, ofs, me, s, n;
1360 if ((evt = checkEvent(evt)) == null) return;
1362 me = TGO(evt.target);
1363 if (me.isEditing()) return;
1364 if (me.moving) return;
1366 me.rpHide();
1367 e = document.createElement('div');
1368 e.tgo = me;
1369 e.ref = evt.target;
1370 e.setAttribute('id', 'tg-row-panel');
1372 n = 0;
1373 s = '';
1374 if (me.canMove) {
1375 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">';
1376 n += 3;
1378 if (me.canDelete) {
1379 s += '<img src="rpx.gif" onclick="this.parentNode.tgo.rpDel(this.parentNode.ref)" title="Delete">';
1380 ++n;
1382 x = PR(evt.target);
1383 x = x.cells[x.cells.length - 1];
1384 ofs = elem.getOffset(x);
1385 n *= 18;
1386 e.style.left = (ofs.x + x.offsetWidth - n) + 'px';
1387 e.style.top = ofs.y + 'px';
1388 e.style.width = n + 'px';
1389 e.innerHTML = s;
1391 document.body.appendChild(e);
1394 rpHide: tgHideIcons,
1396 // ----
1398 onClick: function(cell) {
1399 if (this.canEdit) {
1400 if (this.moving) {
1401 var p = this.moving.parentNode;
1402 var q = PR(cell);
1403 if (this.moving != q) {
1404 var v = this.moving.rowIndex > q.rowIndex;
1405 p.removeChild(this.moving);
1406 if (v) p.insertBefore(this.moving, q);
1407 else p.insertBefore(this.moving, q.nextSibling);
1408 this.recolor();
1410 this.moving = null;
1411 this.rpHide();
1412 return;
1414 this.edit(cell);
1418 insert: function(at, data, cells, escCells) {
1419 var e, i;
1421 if ((this.footer) && (at == -1)) at = this.footer.rowIndex;
1422 e = this._insert(at, cells, escCells);
1423 e.className = (e.rowIndex & 1) ? 'even' : 'odd';
1425 for (i = 0; i < e.cells.length; ++i) {
1426 e.cells[i].onclick = function() { return TGO(this).onClick(this); };
1429 e._data = data;
1430 e.getRowData = function() { return this._data; }
1431 e.setRowData = function(data) { this._data = data; }
1433 if ((this.canMove) || (this.canEdit) || (this.canDelete)) {
1434 e.onmouseover = this.rpMouIn;
1435 // ---- e.onmouseout = this.rpMouOut;
1436 if (this.canEdit) e.title = 'Click to edit';
1439 return e;
1442 // ----
1444 insertData: function(at, data) {
1445 return this.insert(at, data, this.dataToView(data), false);
1448 dataToView: function(data) {
1449 var v = [];
1450 for (var i = 0; i < data.length; ++i) {
1451 var s = escapeHTML('' + data[i]);
1452 if (this.editorFields && this.editorFields.length > i) {
1453 var ef = this.editorFields[i].multi;
1454 if (!ef) ef = [this.editorFields[i]];
1455 var f = (ef && ef.length > 0 ? ef[0] : null);
1456 if (f && f.type == 'password') {
1457 if (!f.peekaboo || get_config('web_pb', '1') != '0')
1458 s = s.replace(/./g, '&#x25CF;');
1461 v.push(s);
1463 return v;
1466 dataToFieldValues: function(data) {
1467 return data;
1470 fieldValuesToData: function(row) {
1471 var e, i, data;
1473 data = [];
1474 e = fields.getAll(row);
1475 for (i = 0; i < e.length; ++i) data.push(e[i].value);
1476 return data;
1479 // ----
1481 edit: function(cell) {
1482 var sr, er, e, c;
1484 if (this.isEditing()) return;
1486 sr = PR(cell);
1487 sr.style.display = 'none';
1488 elem.removeClass(sr, 'hover');
1489 this.source = sr;
1491 er = this.createEditor('edit', sr.rowIndex, sr);
1492 er.className = 'editor';
1493 this.editor = er;
1495 c = er.cells[cell.cellIndex || 0];
1496 e = c.getElementsByTagName('input');
1497 if ((e) && (e.length > 0)) {
1498 try { // IE quirk
1499 e[0].focus();
1501 catch (ex) {
1505 this.controls = this.createControls('edit', sr.rowIndex);
1507 this.disableNewEditor(true);
1508 this.rpHide();
1509 this.verifyFields(this.editor, true);
1512 createEditor: function(which, rowIndex, source) {
1513 var values;
1515 if (which == 'edit') values = this.dataToFieldValues(source.getRowData());
1517 var row = this.tb.insertRow(rowIndex);
1518 row.className = 'editor';
1520 var common = ' onkeypress="return TGO(this).onKey(\'' + which + '\', event)" onchange="TGO(this).onChange(\'' + which + '\', this)"';
1522 var vi = 0;
1523 for (var i = 0; i < this.editorFields.length; ++i) {
1524 var s = '';
1525 var ef = this.editorFields[i].multi;
1526 if (!ef) ef = [this.editorFields[i]];
1528 for (var j = 0; j < ef.length; ++j) {
1529 var f = ef[j];
1531 if (f.prefix) s += f.prefix;
1532 var attrib = ' class="fi' + (vi + 1) + '" ' + (f.attrib || '');
1533 var id = (this.tb ? ('_' + this.tb + '_' + (vi + 1)) : null);
1534 if (id) attrib += ' id="' + id + '"';
1535 switch (f.type) {
1536 case 'password':
1537 if (f.peekaboo) {
1538 switch (get_config('web_pb', '1')) {
1539 case '0':
1540 f.type = 'text';
1541 case '2':
1542 f.peekaboo = 0;
1543 break;
1546 attrib += ' autocomplete="off"';
1547 if (f.peekaboo && id) attrib += ' onfocus=\'peekaboo("' + id + '",1)\'';
1548 // drop
1549 case 'text':
1550 s += '<input type="' + f.type + '" maxlength=' + f.maxlen + common + attrib;
1551 if (which == 'edit') s += ' value="' + escapeHTML('' + values[vi]) + '">';
1552 else s += '>';
1553 break;
1554 case 'clear':
1555 s += '';
1556 break;
1557 case 'select':
1558 s += '<select' + common + attrib + '>';
1559 for (var k = 0; k < f.options.length; ++k) {
1560 a = f.options[k];
1561 if (which == 'edit') {
1562 s += '<option value="' + a[0] + '"' + ((a[0] == values[vi]) ? ' selected>' : '>') + a[1] + '</option>';
1564 else {
1565 s += '<option value="' + a[0] + '">' + a[1] + '</option>';
1568 s += '</select>';
1569 break;
1570 case 'checkbox':
1571 s += '<input type="checkbox"' + common + attrib;
1572 if ((which == 'edit') && (values[vi])) s += ' checked';
1573 s += '>';
1574 break;
1575 default:
1576 s += f.custom.replace(/\$which\$/g, which);
1578 if (f.suffix) s += f.suffix;
1580 ++vi;
1582 var c = row.insertCell(i);
1583 c.innerHTML = s;
1584 if (this.editorFields[i].vtop) c.vAlign = 'top';
1587 return row;
1590 createControls: function(which, rowIndex) {
1591 var r, c;
1593 r = this.tb.insertRow(rowIndex);
1594 r.className = 'controls';
1596 c = r.insertCell(0);
1597 c.colSpan = this.header.cells.length;
1598 if (which == 'edit') {
1599 c.innerHTML =
1600 '<input type=button value="Delete" onclick="TGO(this).onDelete()"> &nbsp; ' +
1601 '<input type=button value="OK" onclick="TGO(this).onOK()"> ' +
1602 '<input type=button value="Cancel" onclick="TGO(this).onCancel()">';
1604 else {
1605 c.innerHTML =
1606 '<input type=button value="Add" onclick="TGO(this).onAdd()">';
1608 return r;
1611 removeEditor: function() {
1612 if (this.editor) {
1614 elem.remove(this.editor);
1615 this.editor = null;
1617 if (this.controls) {
1618 elem.remove(this.controls);
1619 this.controls = null;
1623 showSource: function() {
1624 if (this.source) {
1625 this.source.style.display = '';
1626 this.source = null;
1630 onChange: function(which, cell) {
1631 return this.verifyFields((which == 'new') ? this.newEditor : this.editor, true);
1634 onKey: function(which, ev) {
1635 switch (ev.keyCode) {
1636 case 27:
1637 if (which == 'edit') this.onCancel();
1638 return false;
1639 case 13:
1640 if (((ev.srcElement) && (ev.srcElement.tagName == 'SELECT')) ||
1641 ((ev.target) && (ev.target.tagName == 'SELECT'))) return true;
1642 if (which == 'edit') this.onOK();
1643 else this.onAdd();
1644 return false;
1646 return true;
1649 onDelete: function() {
1650 this.removeEditor();
1651 elem.remove(this.source);
1652 this.source = null;
1653 this.disableNewEditor(false);
1656 onCancel: function() {
1657 this.removeEditor();
1658 this.showSource();
1659 this.disableNewEditor(false);
1662 onOK: function() {
1663 var i, data, view;
1665 if (!this.verifyFields(this.editor, false)) return;
1667 data = this.fieldValuesToData(this.editor);
1668 view = this.dataToView(data);
1670 this.source.setRowData(data);
1671 for (i = 0; i < this.source.cells.length; ++i) {
1672 this.source.cells[i].innerHTML = view[i];
1675 this.removeEditor();
1676 this.showSource();
1677 this.disableNewEditor(false);
1680 onAdd: function() {
1681 var data;
1683 this.moving = null;
1684 this.rpHide();
1686 if (!this.verifyFields(this.newEditor, false)) return;
1688 data = this.fieldValuesToData(this.newEditor);
1689 this.insertData(-1, data);
1691 this.disableNewEditor(false);
1692 this.resetNewEditor();
1695 verifyFields: function(row, quiet) {
1696 return true;
1699 showNewEditor: function() {
1700 var r;
1702 r = this.createEditor('new', -1, null);
1703 this.footer = this.newEditor = r;
1705 r = this.createControls('new', -1);
1706 this.newControls = r;
1708 this.disableNewEditor(false);
1711 disableNewEditor: function(disable) {
1712 if (this.getDataCount() >= this.maxAdd) disable = true;
1713 if (this.newEditor) fields.disableAll(this.newEditor, disable);
1714 if (this.newControls) fields.disableAll(this.newControls, disable);
1717 resetNewEditor: function() {
1718 var i, e;
1720 e = fields.getAll(this.newEditor);
1721 ferror.clearAll(e);
1722 for (i = 0; i < e.length; ++i) {
1723 var f = e[i];
1724 if (f.selectedIndex) f.selectedIndex = 0;
1725 else f.value = '';
1727 try { if (e.length) e[0].focus(); } catch (er) { }
1730 getDataCount: function() {
1731 var n;
1732 n = this.tb.rows.length;
1733 if (this.footer) n = this.footer.rowIndex;
1734 if (this.header) n -= this.header.rowIndex + 1;
1735 return n;
1738 sortCompare: function(a, b) {
1739 var obj = TGO(a);
1740 var col = obj.sortColumn;
1741 var r = cmpText(a.cells[col].innerHTML, b.cells[col].innerHTML);
1742 return obj.sortAscending ? r : -r;
1745 sort: function(column) {
1746 if (this.editor) return;
1748 if (this.sortColumn >= 0) {
1749 elem.removeClass(this.header.cells[this.sortColumn], 'sortasc', 'sortdes');
1751 if (column == this.sortColumn) {
1752 this.sortAscending = !this.sortAscending;
1754 else {
1755 this.sortAscending = true;
1756 this.sortColumn = column;
1758 elem.addClass(this.header.cells[column], this.sortAscending ? 'sortasc' : 'sortdes');
1760 this.resort();
1763 resort: function() {
1764 if ((this.sortColumn < 0) || (this.getDataCount() == 0) || (this.editor)) return;
1766 var p = this.header.parentNode;
1767 var a = [];
1768 var i, j, max, e, p;
1769 var top;
1771 this.moving = null;
1773 top = this.header ? this.header.rowIndex + 1 : 0;
1774 max = this.footer ? this.footer.rowIndex : this.tb.rows.length;
1775 for (i = top; i < max; ++i) a.push(p.rows[i]);
1776 a.sort(THIS(this, this.sortCompare));
1777 this.removeAllData();
1778 j = top;
1779 for (i = 0; i < a.length; ++i) {
1780 e = p.insertBefore(a[i], this.footer);
1781 e.className = (j & 1) ? 'even' : 'odd';
1782 ++j;
1786 recolor: function() {
1787 var i, e, o;
1789 i = this.header ? this.header.rowIndex + 1 : 0;
1790 e = this.footer ? this.footer.rowIndex : this.tb.rows.length;
1791 for (; i < e; ++i) {
1792 o = this.tb.rows[i];
1793 o.className = (o.rowIndex & 1) ? 'even' : 'odd';
1797 removeAllData: function() {
1798 var i, count;
1800 i = this.header ? this.header.rowIndex + 1 : 0;
1801 count = (this.footer ? this.footer.rowIndex : this.tb.rows.length) - i;
1802 while (count-- > 0) elem.remove(this.tb.rows[i]);
1805 getAllData: function() {
1806 var i, max, data, r;
1808 data = [];
1809 max = this.footer ? this.footer.rowIndex : this.tb.rows.length;
1810 for (i = this.header ? this.header.rowIndex + 1 : 0; i < max; ++i) {
1811 r = this.tb.rows[i];
1812 if ((r.style.display != 'none') && (r._data)) data.push(r._data);
1814 return data;
1817 isEditing: function() {
1818 return (this.editor != null);
1823 // -----------------------------------------------------------------------------
1826 function xmlHttpObj()
1828 var ob;
1829 try {
1830 ob = new XMLHttpRequest();
1831 if (ob) return ob;
1833 catch (ex) { }
1834 try {
1835 ob = new ActiveXObject('Microsoft.XMLHTTP');
1836 if (ob) return ob;
1838 catch (ex) { }
1839 return null;
1842 var _useAjax = -1;
1843 var _holdAjax = null;
1845 function useAjax()
1847 if (_useAjax == -1) _useAjax = ((_holdAjax = xmlHttpObj()) != null);
1848 return _useAjax;
1851 function XmlHttp()
1853 if ((!useAjax()) || ((this.xob = xmlHttpObj()) == null)) return null;
1854 return this;
1857 XmlHttp.prototype = {
1858 addId: function(vars) {
1859 if (vars) vars += '&';
1860 else vars = '';
1861 vars += '_http_id=' + escapeCGI(nvram.http_id);
1862 return vars;
1865 get: function(url, vars) {
1866 try {
1867 vars = this.addId(vars);
1868 url += '?' + vars;
1870 this.xob.onreadystatechange = THIS(this, this.onReadyStateChange);
1871 this.xob.open('GET', url, true);
1872 this.xob.send(null);
1874 catch (ex) {
1875 this.onError(ex);
1879 post: function(url, vars) {
1880 try {
1881 vars = this.addId(vars);
1883 this.xob.onreadystatechange = THIS(this, this.onReadyStateChange);
1884 this.xob.open('POST', url, true);
1885 this.xob.send(vars);
1887 catch (ex) {
1888 this.onError(ex);
1892 abort: function() {
1893 try {
1894 this.xob.onreadystatechange = function () { }
1895 this.xob.abort();
1897 catch (ex) {
1901 onReadyStateChange: function() {
1902 try {
1903 if (typeof(E) == 'undefined') return; // oddly late? testing for bug...
1905 if (this.xob.readyState == 4) {
1906 if (this.xob.status == 200) {
1907 this.onCompleted(this.xob.responseText, this.xob.responseXML);
1909 else {
1910 this.onError('' + (this.xob.status || 'unknown'));
1914 catch (ex) {
1915 this.onError(ex);
1919 onCompleted: function(text, xml) { },
1920 onError: function(ex) { }
1924 // -----------------------------------------------------------------------------
1927 function TomatoTimer(func, ms)
1929 this.tid = null;
1930 this.onTimer = func;
1931 if (ms) this.start(ms);
1932 return this;
1935 TomatoTimer.prototype = {
1936 start: function(ms) {
1937 this.stop();
1938 this.tid = setTimeout(THIS(this, this._onTimer), ms);
1940 stop: function() {
1941 if (this.tid) {
1942 clearTimeout(this.tid);
1943 this.tid = null;
1947 isRunning: function() {
1948 return (this.tid != null);
1951 _onTimer: function() {
1952 this.tid = null;
1953 this.onTimer();
1956 onTimer: function() {
1961 // -----------------------------------------------------------------------------
1964 function TomatoRefresh(actionURL, postData, refreshTime, cookieTag)
1966 this.setup(actionURL, postData, refreshTime, cookieTag);
1967 this.timer = new TomatoTimer(THIS(this, this.start));
1970 TomatoRefresh.prototype = {
1971 running: 0,
1973 setup: function(actionURL, postData, refreshTime, cookieTag) {
1974 var e, v;
1976 this.actionURL = actionURL;
1977 this.postData = postData;
1978 this.refreshTime = refreshTime * 1000;
1979 this.cookieTag = cookieTag;
1982 start: function() {
1983 var e;
1985 if ((e = E('refresh-time')) != null) {
1986 if (this.cookieTag) cookie.set(this.cookieTag, e.value);
1987 this.refreshTime = e.value * 1000;
1989 e = undefined;
1991 this.updateUI('start');
1993 this.running = 1;
1994 if ((this.http = new XmlHttp()) == null) {
1995 reloadPage();
1996 return;
1999 this.http.parent = this;
2001 this.http.onCompleted = function(text, xml) {
2002 var p = this.parent;
2004 if (p.cookieTag) cookie.unset(p.cookieTag + '-error');
2005 if (!p.running) {
2006 p.stop();
2007 return;
2010 p.refresh(text);
2012 if ((p.refreshTime > 0) && (!p.once)) {
2013 p.updateUI('wait');
2014 p.timer.start(Math.round(p.refreshTime));
2016 else {
2017 p.stop();
2020 p.errors = 0;
2023 this.http.onError = function(ex) {
2024 var p = this.parent;
2025 if ((!p) || (!p.running)) return;
2027 p.timer.stop();
2029 if (++p.errors <= 3) {
2030 p.updateUI('wait');
2031 p.timer.start(3000);
2032 return;
2035 if (p.cookieTag) {
2036 var e = cookie.get(p.cookieTag + '-error') * 1;
2037 if (isNaN(e)) e = 0;
2038 else ++e;
2039 cookie.unset(p.cookieTag);
2040 cookie.set(p.cookieTag + '-error', e, 1);
2041 if (e >= 3) {
2042 alert('XMLHTTP: ' + ex);
2043 return;
2047 setTimeout(reloadPage, 2000);
2050 this.errors = 0;
2051 this.http.post(this.actionURL, this.postData);
2054 stop: function() {
2055 if (this.cookieTag) cookie.set(this.cookieTag, -(this.refreshTime / 1000));
2056 this.running = 0;
2057 this.updateUI('stop');
2058 this.timer.stop();
2059 this.http = null;
2060 this.once = undefined;
2063 toggle: function(delay) {
2064 if (this.running) this.stop();
2065 else this.start(delay);
2068 updateUI: function(mode) {
2069 var e, b;
2071 if (typeof(E) == 'undefined') return; // for a bizzare bug...
2073 b = (mode != 'stop') && (this.refreshTime > 0);
2074 if ((e = E('refresh-button')) != null) {
2075 e.value = b ? 'Stop' : 'Refresh';
2076 e.disabled = ((mode == 'start') && (!b));
2078 if ((e = E('refresh-time')) != null) e.disabled = b;
2079 if ((e = E('refresh-spinner')) != null) e.style.visibility = b ? 'visible' : 'hidden';
2082 initPage: function(delay, def) {
2083 var e, v;
2085 e = E('refresh-time');
2086 if (((this.cookieTag) && (e != null)) &&
2087 ((v = cookie.get(this.cookieTag)) != null) && (!isNaN(v *= 1))) {
2088 e.value = Math.abs(v);
2089 if (v > 0) v = (v * 1000) + (delay || 0);
2091 else if (def) {
2092 v = def;
2093 if (e) e.value = def;
2095 else v = 0;
2097 if (delay < 0) {
2098 v = -delay;
2099 this.once = 1;
2102 if (v > 0) {
2103 this.running = 1;
2104 this.refreshTime = v;
2105 this.timer.start(v);
2106 this.updateUI('wait');
2111 function genStdTimeList(id, zero, min)
2113 var b = [];
2114 var t = [0.5,1,2,3,4,5,10,15,30,60,120,180,240,300,10*60,15*60,20*60,30*60];
2115 var i, v;
2117 if (min >= 0) {
2118 b.push('<select id="' + id + '"><option value=0>' + zero);
2119 for (i = 0; i < t.length; ++i) {
2120 v = t[i];
2121 if (v < min) continue;
2122 b.push('<option value=' + v + '>');
2123 if (v == 60) b.push('1 minute');
2124 else if (v > 60) b.push((v / 60) + ' minutes');
2125 else if (v == 1) b.push('1 second');
2126 else b.push(v + ' seconds');
2128 b.push('</select> ');
2130 document.write(b.join(''));
2133 function genStdRefresh(spin, min, exec)
2135 W('<div style="text-align:right">');
2136 if (spin) W('<img src="spin.gif" id="refresh-spinner"> ');
2137 genStdTimeList('refresh-time', 'Auto Refresh', min);
2138 W('<input type="button" value="Refresh" onclick="' + (exec ? exec : 'refreshClick()') + '" id="refresh-button"></div>');
2142 // -----------------------------------------------------------------------------
2145 function _tabCreate(tabs)
2147 var buf = [];
2148 buf.push('<ul id="tabs">');
2149 for (var i = 0; i < arguments.length; ++i)
2150 buf.push('<li><a href="javascript:tabSelect(\'' + arguments[i][0] + '\')" id="' + arguments[i][0] + '">' + arguments[i][1] + '</a>');
2151 buf.push('</ul><div id="tabs-bottom"></div>');
2152 return buf.join('');
2155 function tabCreate(tabs)
2157 document.write(_tabCreate.apply(this, arguments));
2160 function tabHigh(id)
2162 var a = E('tabs').getElementsByTagName('A');
2163 for (var i = 0; i < a.length; ++i) {
2164 if (id != a[i].id) elem.removeClass(a[i], 'active');
2166 elem.addClass(id, 'active');
2169 // -----------------------------------------------------------------------------
2171 var cookie = {
2172 // The value 2147483647000 is ((2^31)-1)*1000, which is the number of
2173 // milliseconds (minus 1 second) which correlates with the year 2038 counter
2174 // rollover. This effectively makes the cookie never expire.
2176 set: function(key, value, days) {
2177 document.cookie = 'tomato_' + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '; expires=' +
2178 new Date(2147483647000).toUTCString() + '; path=/';
2180 get: function(key) {
2181 var r = ('; ' + document.cookie + ';').match('; tomato_' + encodeURIComponent(key) + '=(.*?);');
2182 return r ? decodeURIComponent(r[1]) : null;
2184 unset: function(key) {
2185 document.cookie = 'tomato_' + encodeURIComponent(key) + '=; expires=' +
2186 (new Date(1)).toUTCString() + '; path=/';
2190 // -----------------------------------------------------------------------------
2192 function checkEvent(evt)
2194 if (typeof(evt) == 'undefined') {
2195 // ---- IE
2196 evt = event;
2197 evt.target = evt.srcElement;
2198 evt.relatedTarget = evt.toElement;
2200 return evt;
2203 function W(s)
2205 document.write(s);
2208 function E(e)
2210 return (typeof(e) == 'string') ? document.getElementById(e) : e;
2213 function PR(e)
2215 return elem.parentElem(e, 'TR');
2218 function THIS(obj, func)
2220 return function() { return func.apply(obj, arguments); }
2223 function UT(v)
2225 return (typeof(v) == 'undefined') ? '' : '' + v;
2228 function escapeHTML(s)
2230 function esc(c) {
2231 return '&#' + c.charCodeAt(0) + ';';
2233 return s.replace(/[&"'<>\r\n]/g, esc);
2236 function escapeCGI(s)
2238 return escape(s).replace(/\+/g, '%2B'); // escape() doesn't handle +
2241 function escapeD(s)
2243 function esc(c) {
2244 return '%' + c.charCodeAt(0).hex(2);
2246 return s.replace(/[<>|%]/g, esc);
2249 function ellipsis(s, max) {
2250 return (s.length <= max) ? s : s.substr(0, max - 3) + '...';
2253 function MIN(a, b)
2255 return (a < b) ? a : b;
2258 function MAX(a, b)
2260 return (a > b) ? a : b;
2263 function fixInt(n, min, max, def)
2265 if (n === null) return def;
2266 n *= 1;
2267 if (isNaN(n)) return def;
2268 if (n < min) return min;
2269 if (n > max) return max;
2270 return n;
2273 function comma(n)
2275 n = '' + n;
2276 var p = n;
2277 while ((n = n.replace(/(\d+)(\d{3})/g, '$1,$2')) != p) p = n;
2278 return n;
2281 function doScaleSize(n, sm)
2283 if (isNaN(n *= 1)) return '-';
2284 if (n <= 9999) return '' + n;
2285 var s = -1;
2286 do {
2287 n /= 1024;
2288 ++s;
2289 } while ((n > 9999) && (s < 2));
2290 return comma(n.toFixed(2)) + (sm ? '<small> ' : ' ') + (['KB', 'MB', 'GB'])[s] + (sm ? '</small>' : '');
2293 function scaleSize(n)
2295 return doScaleSize(n, 1);
2298 function timeString(mins)
2300 var h = Math.floor(mins / 60);
2301 if ((new Date(2000, 0, 1, 23, 0, 0, 0)).toLocaleString().indexOf('23') != -1)
2302 return h + ':' + (mins % 60).pad(2);
2303 return ((h == 0) ? 12 : ((h > 12) ? h - 12 : h)) + ':' + (mins % 60).pad(2) + ((h >= 12) ? ' PM' : ' AM');
2306 function features(s)
2308 var features = ['ses','brau','aoss','wham','hpamp','!nve','11n','1000et','11ac'];
2309 var i;
2311 for (i = features.length - 1; i >= 0; --i) {
2312 if (features[i] == s) return (parseInt(nvram.t_features) & (1 << i)) != 0;
2314 return 0;
2317 function get_config(name, def)
2319 return ((typeof(nvram) != 'undefined') && (typeof(nvram[name]) != 'undefined')) ? nvram[name] : def;
2322 function nothing()
2326 // -----------------------------------------------------------------------------
2328 function show_notice1(s)
2330 // ---- !!TB - USB Support: multi-line notices
2331 if (s.length) document.write('<div id="notice1">' + s.replace(/\n/g, '<br>') + '</div><br style="clear:both">');
2334 // -----------------------------------------------------------------------------
2336 function myName()
2338 var name, i;
2340 name = document.location.pathname;
2341 name = name.replace(/\\/g, '/'); // IE local testing
2342 if ((i = name.lastIndexOf('/')) != -1) name = name.substring(i + 1, name.length);
2343 if (name == '') name = 'status-overview.asp';
2344 return name;
2347 function navi()
2349 var menu = [
2350 ['Status', 'status', 0, [
2351 ['Overview', 'overview.asp'],
2352 ['Device List', 'devices.asp'],
2353 ['Web Usage', 'webmon.asp'],
2354 ['Logs', 'log.asp'] ] ],
2355 ['Bandwidth', 'bwm', 0, [
2356 ['Real-Time', 'realtime.asp'],
2357 ['Last 24 Hours', '24.asp'],
2358 /* REMOVE-BEGIN
2359 ['Client Monitor', 'client.asp'],
2360 REMOVE-END */
2361 ['Daily', 'daily.asp'],
2362 ['Weekly', 'weekly.asp'],
2363 ['Monthly', 'monthly.asp'] ] ],
2364 ['IP Traffic', 'ipt', 0, [
2365 ['Real-Time', 'realtime.asp'],
2366 ['Last 24 Hours', '24.asp'],
2367 ['Transfer Rates', 'details.asp'],
2368 ['Daily', 'daily.asp'],
2369 ['Monthly', 'monthly.asp'] ] ],
2370 ['Tools', 'tools', 0, [
2371 ['Ping', 'ping.asp'],
2372 ['Trace', 'trace.asp'],
2373 ['System', 'shell.asp'],
2374 ['Wireless Survey', 'survey.asp'],
2375 ['WOL', 'wol.asp'] ] ],
2376 null,
2377 ['Basic', 'basic', 0, [
2378 ['Network', 'network.asp'],
2379 /* IPV6-BEGIN */
2380 ['IPv6', 'ipv6.asp'],
2381 /* IPV6-END */
2382 ['Identification', 'ident.asp'],
2383 ['Time', 'time.asp'],
2384 ['DDNS', 'ddns.asp'],
2385 ['Static DHCP/ARP/IPT', 'static.asp'],
2386 ['Wireless Filter', 'wfilter.asp'] ] ],
2387 ['Advanced', 'advanced', 0, [
2388 ['Conntrack/Netfilter', 'ctnf.asp'],
2389 ['DHCP/DNS', 'dhcpdns.asp'],
2390 ['Firewall', 'firewall.asp'],
2391 ['MAC Address', 'mac.asp'],
2392 ['Miscellaneous', 'misc.asp'],
2393 ['Routing', 'routing.asp'],
2394 ['Wireless', 'wireless.asp']
2395 /* VLAN-BEGIN */
2396 ,['VLAN', 'vlan.asp'],
2397 ['LAN Access', 'access.asp'],
2398 ['Virtual Wireless', 'wlanvifs.asp']
2399 /* VLAN-END */
2400 ] ],
2401 ['Port Forwarding', 'forward', 0, [
2402 ['Basic', 'basic.asp'],
2403 /* IPV6-BEGIN */
2404 ['Basic IPv6', 'basic-ipv6.asp'],
2405 /* IPV6-END */
2406 ['DMZ', 'dmz.asp'],
2407 ['Triggered', 'triggered.asp'],
2408 ['UPnP/NAT-PMP', 'upnp.asp'] ] ],
2409 ['QoS', 'qos', 0, [
2410 ['Basic Settings', 'settings.asp'],
2411 ['Classification', 'classify.asp'],
2412 ['View Graphs', 'graphs.asp'],
2413 ['View Details', 'detailed.asp'],
2414 ['Transfer Rates', 'ctrate.asp'] ] ],
2417 ['Access Restriction', 'restrict.asp'],
2418 null,
2419 ['Bandwidth Limiter', 'bwlimit.asp'],
2420 /* NOCAT-BEGIN */
2421 ['Captive Portal', 'splashd.asp'],
2422 /* NOCAT-END */
2423 /* NGINX-BEGIN */
2424 ['Web Server', 'nginx.asp'],
2425 /* NGINX-END */
2426 /* USB-BEGIN */
2427 // ---- !!TB - USB, FTP, Samba, Media Server
2428 ['USB and NAS', 'nas', 0, [
2429 ['USB Support', 'usb.asp']
2430 /* FTP-BEGIN */
2431 ,['FTP Server', 'ftp.asp']
2432 /* FTP-END */
2433 /* SAMBA-BEGIN */
2434 ,['File Sharing', 'samba.asp']
2435 /* SAMBA-END */
2436 /* MEDIA-SRV-BEGIN */
2437 ,['Media Server', 'media.asp']
2438 /* MEDIA-SRV-END */
2439 ] ],
2440 /* USB-END */
2441 /* VPN-BEGIN */
2442 ['VPN Tunneling', 'vpn', 0, [
2443 /* OPENVPN-BEGIN */
2444 ['OpenVPN Server', 'server.asp'],
2445 ['OpenVPN Client', 'client.asp'],
2446 /* OPENVPN-END */
2447 /* PPTPD-BEGIN */
2448 ['PPTP Server', 'pptp-server.asp'],
2449 ['PPTP Online', 'pptp-online.asp'],
2450 /* PPTPD-END */
2451 /* USERPPTP-BEGIN */
2452 ['PPTP Client', 'pptp.asp']
2453 /* USERPPTP-END */
2454 ] ],
2455 /* VPN-END */
2456 null,
2457 ['Administration', 'admin', 0, [
2458 ['Admin Access', 'access.asp'],
2459 ['Bandwidth Monitoring','bwm.asp'],
2460 ['IP Traffic Monitoring','iptraffic.asp'],
2461 ['Buttons/LED', 'buttons.asp'],
2462 /* CIFS-BEGIN */
2463 ['CIFS Client', 'cifs.asp'],
2464 /* CIFS-END */
2465 ['Configuration', 'config.asp'],
2466 ['Debugging', 'debug.asp'],
2467 /* JFFS2-BEGIN */
2468 ['JFFS', 'jffs2.asp'],
2469 /* JFFS2-END */
2470 ['Logging', 'log.asp'],
2471 ['Scheduler', 'sched.asp'],
2472 ['Scripts', 'scripts.asp'],
2473 /* SNMP-BEGIN */
2474 ['SNMP', 'snmp.asp'],
2475 /* SNMP-END */
2476 ['Upgrade', 'upgrade.asp'] ] ],
2477 null,
2478 ['About', 'about.asp'],
2479 ['Reboot...', 'javascript:reboot()'],
2480 ['Shutdown...', 'javascript:shutdown()'],
2481 ['Logout', 'javascript:logout()']
2483 var name, base;
2484 var i, j;
2485 var buf = [];
2486 var sm;
2487 var a, b, c;
2488 var on1;
2489 var cexp = get_config('web_mx', '').toLowerCase();
2491 name = myName();
2492 if (name == 'restrict-edit.asp') name = 'restrict.asp';
2493 if ((i = name.indexOf('-')) != -1) {
2494 base = name.substring(0, i);
2495 name = name.substring(i + 1, name.length);
2497 else base = '';
2499 for (i = 0; i < menu.length; ++i) {
2500 var m = menu[i];
2501 if (!m) {
2502 buf.push("<br>");
2503 continue;
2505 if (m.length == 2) {
2506 buf.push('<a href="' + m[1] + '" class="indent1' + (((base == '') && (name == m[1])) ? ' active' : '') + '">' + m[0] + '</a>');
2508 else {
2509 if (base == m[1]) {
2510 b = name;
2512 else {
2513 a = cookie.get('menu_' + m[1]);
2514 b = m[3][0][1];
2515 for (j = 0; j < m[3].length; ++j) {
2516 if (m[3][j][1] == a) {
2517 b = a;
2518 break;
2522 a = m[1] + '-' + b;
2523 if (a == 'status-overview.asp') a = '/';
2524 on1 = (base == m[1]);
2525 buf.push('<a href="' + a + '" class="indent1' + (on1 ? ' active' : '') + '">' + m[0] + '</a>');
2526 if ((!on1) && (m[2] == 0) && (cexp.indexOf(m[1]) == -1)) continue;
2528 for (j = 0; j < m[3].length; ++j) {
2529 sm = m[3][j];
2530 a = m[1] + '-' + sm[1];
2531 if (a == 'status-overview.asp') a = '/';
2532 buf.push('<a href="' + a + '" class="indent2' + (((on1) && (name == sm[1])) ? ' active' : '') + '">' + sm[0] + '</a>');
2536 document.write(buf.join(''));
2538 if (base.length) {
2539 if ((base == 'qos') && (name == 'detailed.asp')) name = 'view.asp';
2540 cookie.set('menu_' + base, name);
2544 function createFieldTable(flags, desc)
2546 var common;
2547 var i, n;
2548 var name;
2549 var id;
2550 var fields;
2551 var f;
2552 var a;
2553 var buf = [];
2554 var buf2;
2555 var id1;
2556 var tr;
2558 if ((flags.indexOf('noopen') == -1)) buf.push('<table class="fields">');
2559 for (desci = 0; desci < desc.length; ++desci) {
2560 var v = desc[desci];
2562 if (!v) {
2563 buf.push('<tr><td colspan=2 class="spacer">&nbsp;</td></tr>');
2564 continue;
2567 if (v.ignore) continue;
2569 buf.push('<tr');
2570 if (v.rid) buf.push(' id="' + v.rid + '"');
2571 if (v.hidden) buf.push(' style="display:none"');
2572 buf.push('>');
2574 if (v.text) {
2575 if (v.title) {
2576 buf.push('<td class="title indent' + (v.indent || 1) + '">' + v.title + '</td><td class="content">' + v.text + '</td></tr>');
2578 else {
2579 buf.push('<td colspan=2>' + v.text + '</td></tr>');
2581 continue;
2584 id1 = '';
2585 buf2 = [];
2586 buf2.push('<td class="content">');
2588 if (v.multi) fields = v.multi;
2589 else fields = [v];
2591 for (n = 0; n < fields.length; ++n) {
2592 f = fields[n];
2593 if (f.prefix) buf2.push(f.prefix);
2595 if ((f.type == 'radio') && (!f.id)) id = '_' + f.name + '_' + i;
2596 else id = (f.id ? f.id : ('_' + f.name));
2598 if (id1 == '') id1 = id;
2600 common = ' onchange="verifyFields(this, 1)" id="' + id + '"';
2601 if (f.attrib) common += ' ' + f.attrib;
2602 name = f.name ? (' name="' + f.name + '"') : '';
2604 switch (f.type) {
2605 case 'checkbox':
2606 buf2.push('<input type="checkbox"' + name + (f.value ? ' checked' : '') + ' onclick="verifyFields(this, 1)"' + common + '>');
2607 break;
2608 case 'radio':
2609 buf2.push('<input type="radio"' + name + (f.value ? ' checked' : '') + ' onclick="verifyFields(this, 1)"' + common + '>');
2610 break;
2611 case 'password':
2612 if (f.peekaboo) {
2613 switch (get_config('web_pb', '1')) {
2614 case '0':
2615 f.type = 'text';
2616 case '2':
2617 f.peekaboo = 0;
2618 break;
2621 if (f.type == 'password') {
2622 common += ' autocomplete="off"';
2623 if (f.peekaboo) common += ' onfocus=\'peekaboo("' + id + '",1)\'';
2625 // drop
2626 case 'text':
2627 buf2.push('<input type="' + f.type + '"' + name + ' value="' + escapeHTML(UT(f.value)) + '" maxlength=' + f.maxlen + (f.size ? (' size=' + f.size) : '') + common + '>');
2628 break;
2629 case 'clear':
2630 s += '';
2631 break;
2632 case 'select':
2633 buf2.push('<select' + name + common + '>');
2634 for (i = 0; i < f.options.length; ++i) {
2635 a = f.options[i];
2636 if (a.length == 1) a.push(a[0]);
2637 buf2.push('<option value="' + a[0] + '"' + ((a[0] == f.value) ? ' selected' : '') + '>' + a[1] + '</option>');
2639 buf2.push('</select>');
2640 break;
2641 case 'textarea':
2642 buf2.push('<textarea' + name + common + (f.wrap ? (' wrap=' + f.wrap) : '') + '>' + escapeHTML(UT(f.value)) + '</textarea>');
2643 break;
2644 default:
2645 if (f.custom) buf2.push(f.custom);
2646 break;
2648 if (f.suffix) buf2.push(f.suffix);
2650 buf2.push('</td>');
2652 buf.push('<td class="title indent' + (v.indent ? v.indent : 1) + '">');
2653 if (id1 != '') buf.push('<label for="' + id + '">' + v.title + '</label></td>');
2654 else buf.push(+ v.title + '</td>');
2656 buf.push(buf2.join(''));
2657 buf.push('</tr>');
2659 if ((!flags) || (flags.indexOf('noclose') == -1)) buf.push('</table>');
2660 document.write(buf.join(''));
2663 function peekaboo(id, show)
2665 try {
2666 var o = document.createElement('INPUT');
2667 var e = E(id);
2668 var name = e.name;
2669 o.type = show ? 'text' : 'password';
2670 o.value = e.value;
2671 o.size = e.size;
2672 o.maxLength = e.maxLength;
2673 o.autocomplete = e.autocomplete;
2674 o.title = e.title;
2675 o.disabled = e.disabled;
2676 o.onchange = e.onchange;
2677 e.parentNode.replaceChild(o, e);
2678 e = null;
2679 o.id = id;
2680 o.name = name;
2682 if (show) {
2683 o.onblur = function(ev) { setTimeout('peekaboo("' + this.id + '", 0)', 0) };
2684 setTimeout('try { E("' + id + '").focus() } catch (ex) { }', 0)
2686 else {
2687 o.onfocus = function(ev) { peekaboo(this.id, 1); };
2690 catch (ex) {
2691 // alert(ex);
2694 /* REMOVE-BEGIN
2695 notes:
2696 - e.type= doesn't work in IE, ok in FF
2697 - may mess keyboard tabing (bad: IE; ok: FF, Opera)... setTimeout() delay seems to help a little.
2698 REMOVE-END */
2701 // -----------------------------------------------------------------------------
2703 function reloadPage()
2705 document.location.reload(1);
2708 function reboot()
2710 if (confirm("Reboot?")) form.submitHidden('tomato.cgi', { _reboot: 1, _commit: 0, _nvset: 0 });
2713 function shutdown()
2715 if (confirm("Shutdown?")) form.submitHidden('shutdown.cgi', { });
2718 function logout()
2720 form.submitHidden('logout.asp', { });
2723 // -----------------------------------------------------------------------------
2727 // ---- debug
2729 function isLocal()
2731 return location.href.search('file://') == 0;
2734 function console(s)