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.
10 // -----------------------------------------------------------------------------
12 Array
.prototype.find = function(v
) {
13 for (var i
= 0; i
< this.length
; ++i
)
14 if (this[i
] == v
) return i
;
18 Array
.prototype.remove = function(v
) {
19 for (var i
= 0; i
< this.length
; ++i
) {
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
;
42 Number
.prototype.hex = function(min
)
44 var h
= '0123456789ABCDEF';
48 s
= h
.charAt(n
& 15) + s
;
50 } while ((--min
> 0) || (n
> 0));
54 // -----------------------------------------------------------------------------
56 // ---- Element.protoype. doesn't work with all browsers
59 getOffset: function(e
) {
60 var r
= { x
: 0, y
: 0 };
62 while (e
.offsetParent
) {
70 addClass: function(e
, name
) {
71 if ((e
= E(e
)) == null) return;
72 var a
= e
.className
.split(/\s+/);
74 for (var i
= 1; i
< arguments
.length
; ++i
) {
75 if (a
.find(arguments
[i
]) == -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+/);
87 for (var i
= 1; i
< arguments
.length
; ++i
)
88 k
|= a
.remove(arguments
[i
]);
89 if (k
) e
.className
= a
.join(' ');
93 if ((e
= E(e
)) != null) e
.parentNode
.removeChild(e
);
96 parentElem: function(e
, tagName
) {
98 tagName
= tagName
.toUpperCase();
99 while (e
.parentNode
) {
101 if (e
.tagName
== tagName
) return e
;
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
) {
116 if ((e
.style
.visibility
!= 'visible') || (e
.style
.display
== 'none')) return false;
122 setInnerHTML: function(e
, html
) {
124 if (e
.innerHTML
!= html
) e
.innerHTML
= html
; // reduce flickering
128 // -----------------------------------------------------------------------------
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 // -----------------------------------------------------------------------------
156 getAll: function(e
) {
165 for (var i
= 0; i
< e
.childNodes
.length
; ++i
) {
166 a
= a
.concat(fields
.getAll(e
.childNodes
[i
]));
172 disableAll: function(e
, d
) {
175 if ((typeof(e
.tagName
) == 'undefined') && (typeof(e
) != 'string')) {
176 for (i
= e
.length
- 1; i
>= 0; --i
) {
181 var a
= this.getAll(E(e
));
182 for (var i
= a
.length
- 1; i
>= 0; --i
) {
188 selected: function(e
) {
189 for (var i
= 0; i
< e
.length
; ++i
) {
190 if (e
[i
].checked
) return e
[i
];
194 find: function(e
, value
) {
195 for (var i
= 0; i
< e
.length
; ++i
) {
196 if (e
[i
].value
== value
) return e
[i
];
203 // -----------------------------------------------------------------------------
206 submitHidden: function(url
, fields
) {
209 fom
= document
.createElement('FORM');
212 for (var f
in fields
) {
213 var e
= document
.createElement('INPUT');
219 body
= document
.getElementsByTagName('body')[0];
220 fom
= body
.appendChild(fom
);
222 body
.removeChild(fom
);
225 submit: function(fom
, async
, url
) {
226 var e
, v
, f
, i
, wait
, msg
, sb
, cb
;
231 this.dump(fom
, async
, url
);
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())) {
242 if (url
) fom
.action
= url
;
249 for (var i
= 0; i
< fom
.elements
.length
; ++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') {
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
) {
269 if (text
.match(/@msg:(.+)/)) msg
.innerHTML
= escapeHTML(RegExp
.$1);
270 else msg
.innerHTML
= 'Saved';
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();
281 this.xhttp
.onError = function(x
) {
282 if (url
) fom
.action
= url
;
286 this.xhttp
.post(url
? url
: fom
.action
, v
.join('&'));
289 addId: function(fom
) {
292 if (typeof(fom
._http_id
) == 'undefined') {
293 e
= document
.createElement('INPUT');
296 e
.value
= nvram
.http_id
;
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 // -----------------------------------------------------------------------------
316 set: function(e
, message
, quiet
) {
317 if ((e
= E(e
)) == null) return;
318 e
._error_msg
= message
;
319 e
._error_org
= e
.title
;
321 elem
.addClass(e
, 'error');
322 if (!quiet
) this.show(e
);
326 if ((e
= E(e
)) == null) return;
327 e
.title
= e
._error_org
|| '';
328 elem
.removeClass(e
, 'error');
333 clearAll: function(e
) {
334 for (var i
= 0; i
< e
.length
; ++i
)
339 if ((e
= E(e
)) == null) return;
340 if (!e
._error_msg
) return;
341 elem
.addClass(e
, 'error-focused');
344 elem
.removeClass(e
, 'error-focused');
348 if ((e
= E(e
)) == null) return 0;
349 return !e
._error_msg
;
353 // -----------------------------------------------------------------------------
355 function fixFile(name
)
358 if (((i
= name
.lastIndexOf('/')) > 0) || ((i
= name
.lastIndexOf('\\')) > 0))
359 name
= name
.substring(i
+ 1, name
.length
);
363 function _v_range(e
, quiet
, min
, max
, name
)
365 if ((e
= E(e
)) == null) return 0;
367 if ((!v
.match(/^ *[-\+]?\d+ *$/)) || (v
< min
) || (v
> max
)) {
368 ferror
.set(e
, 'Invalid ' + name
+ '. Valid range: ' + min
+ '-' + max
, quiet
);
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
)
395 if ((e
= E(e
)) == null) return 0;
396 if (e
.value
.match(/^\s*(.+?)([mhd])?\s*$/)) {
398 if (RegExp
.$2 == 'h') m
= 60;
399 else if (RegExp
.$2 == 'd') m
= 60 * 24;
400 v
= Math
.round(RegExp
.$1 * m
);
403 return _v_range(e
, quiet
, min
, max
, 'minutes');
406 ferror
.set(e
, 'Invalid number of minutes.', quiet
);
410 function v_macip(e
, quiet
, bok
, lan_ipaddr
, lan_netmask
)
412 var s
, a
, b
, c
, d
, i
;
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) {
427 ferror
.set(e
, 'Invalid MAC or IP address');
439 ferror
.set(e
, 'Invalid IP address range', quiet
);
443 if (a
[0].match(/^\d+$/)){
445 if ((a
.length
== 2) && (a
[1].match(/^\d+$/)))
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
) {
458 ferror
.set(e
, 'Invalid IP address', quiet
);
462 if ((aton(b
) & aton(lan_netmask
))!=(aton(lan_ipaddr
) & aton(lan_netmask
))) {
463 ferror
.set(e
, 'IP address outside of LAN', quiet
);
467 d
= (b
.split('.'))[3];
468 if (parseInt(d
) <= parseInt(c
)) {
469 ferror
.set(e
, 'Invalid IP address range', quiet
);
475 e
.value
= b
.split('.')[0] + '.' + b
.split('.')[1] + '.' + b
.split('.')[2] + '.' + a
.join('-');
479 function fixIP(ip
, x
)
483 i
= a
.indexOf("<br>");
488 if (a
.length
!= 4) return null;
489 for (i
= 0; i
< 4; ++i
) {
491 if ((isNaN(n
)) || (n
< 0) || (n
> 255)) return null;
494 if ((x
) && ((a
[3] == 0) || (a
[3] == 255))) return null;
498 function v_ip(e
, quiet
, x
)
502 if ((e
= E(e
)) == null) return 0;
503 ip
= fixIP(e
.value
, x
);
505 ferror
.set(e
, 'Invalid IP address', quiet
);
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;
527 var s
= e
.value
.split(':');
531 else if (s
.length
!= 2) {
532 ferror
.set(e
, 'Invalid IP address or port', quiet
);
536 if ((s
[0] = fixIP(s
[0])) == null) {
537 ferror
.set(e
, 'Invalid IP address', quiet
);
541 if ((s
[1] = fixPort(s
[1], -1)) == -1) {
542 ferror
.set(e
, 'Invalid port', quiet
);
550 e
.value
= s
.join(':');
562 // ---- this is goofy because << mangles numbers as signed
565 for (i
= 0; i
< 4; ++i
) x
+= (o
[i
] * 1).hex(2);
566 return parseInt(x
, 16);
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
;
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
);
593 if (aton(a
) > aton(b
)) return b
+ '-' + a
;
601 if (ip
.match(/^(.*)\/(.*)$/)) {
608 if ((ma
== null) || (!_v_netmask(ma
))) {
609 ferror
.set(e
, oip
+ ' - invalid netmask', quiet
);
614 if ((ma
< 0) || (ma
> 32)) {
615 ferror
.set(e
, oip
+ ' - invalid netmask', quiet
);
623 ferror
.set(e
, oip
+ ' - invalid IP address', quiet
);
628 return ip
+ ((ma
!= '') ? ('/' + ma
) : '');
631 function v_iptip(e
, quiet
, multi
)
635 if ((e
= E(e
)) == null) return 0;
636 v
= e
.value
.split(',');
638 if (v
.length
> multi
) {
639 ferror
.set(e
, 'Too many IP addresses', quiet
);
645 ferror
.set(e
, 'Invalid IP address', quiet
);
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(', ');
656 function _v_domain(e
, dom
, quiet
)
660 s
= dom
.replace(/\s+/g, ' ').trim();
662 s
= _v_hostname(e
, s
, 1, 1, 7, '.', true);
664 ferror
.set(e
, "Invalid name. Only characters \"A-Z 0-9 . -\" are allowed.", quiet
);
672 function v_domain(e
, quiet
)
676 if ((e
= E(e
)) == null) return 0;
677 if ((v
= _v_domain(e
, e
.value
, quiet
)) == null) return 0;
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;
694 if (a
[0] == '') return null;
695 pre
= a
[0].split(':');
696 if (pre
.length
!= 8) return null;
700 pre
= a
[0].split(':');
701 post
= a
[1].split(':');
702 n
= 8 - pre
.length
- post
.length
;
703 for (i
=0; i
<2; i
++) {
706 if (n
< 0) return null;
708 while (n
-- > 0) fill
+= ':0';
709 ip
= pre
.join(':') + fill
+ ':' + post
.join(':');
710 ip
= ip
.replace(/^:/, '').replace(/:$/, '');
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');
721 function CompressIPv6Address(ip
)
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,}))/, ':');
736 function ZeroIPv6PrefixBits(ip
, prefix_length
)
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;
745 c
= (parseInt(ip
.charAt(n
), 16) & (0xf << 4-b
)).toString(16);
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');
759 ip
= ExpandIPv6Address(ip
);
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
)
776 if ((ipt
) && ip
.match(/^(.*)-(.*)$/)) {
779 a
= CompressIPv6Address(a
);
780 b
= CompressIPv6Address(b
);
781 if ((a
== null) || (b
== null)) {
782 ferror
.set(e
, oip
+ ' - invalid IPv6 address range', quiet
);
787 if (ipv6ton(a
) > ipv6ton(b
)) return b
+ '-' + a
;
791 if ((ipt
) && ip
.match(/^([A-Fa-f0-9:]+)\/(\d+)$/)) {
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
);
799 if (b
< 0 || b
> 128) {
800 ferror
.set(e
, oip
+ ' - invalid CIDR notation on IPv6 address', quiet
);
805 ip
= ZeroIPv6PrefixBits(a
, b
);
806 return ip
+ '/' + b
.toString(10);
809 ip
= CompressIPv6Address(oip
);
811 ferror
.set(e
, oip
+ ' - invalid IPv6 address', quiet
);
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
;
829 function fixPort(p
, def
)
831 if (def
== null) def
= -1;
832 if (p
== null) return def
;
834 if ((isNaN(p
) || (p
< 1) || (p
> 65535) || (('' + p
).indexOf('.') != -1))) return def
;
838 function _v_portrange(e
, quiet
, v
)
840 if (v
.match(/^(.*)[-:](.*)$/)) {
846 if ((x
== -1) || (y
== -1)) {
847 ferror
.set(e
, 'Invalid port range: ' + v
, quiet
);
856 if (x
== y
) return x
;
862 ferror
.set(e
, 'Invalid port', quiet
);
870 function v_portrange(e
, quiet
)
874 if ((e
= E(e
)) == null) return 0;
875 v
= _v_portrange(e
, quiet
, e
.value
);
876 if (v
== null) return 0;
881 function v_iptport(e
, quiet
)
885 if ((e
= E(e
)) == null) return 0;
887 a
= e
.value
.split(/[,\.]/);
890 ferror
.set(e
, 'Expecting a list of ports or port range.', quiet
);
894 ferror
.set(e
, 'Only 10 ports/range sets are allowed.', quiet
);
899 for (i
= 0; i
< a
.length
; ++i
) {
900 v
= _v_portrange(e
, quiet
, a
[i
]);
901 if (v
== null) return 0;
905 e
.value
= q
.join(',');
910 function _v_netmask(mask
)
912 var v
= aton(mask
) ^ 0xFFFFFFFF;
913 return (((v
+ 1) & v
) == 0);
916 function v_netmask(e
, quiet
)
920 if ((e
= E(e
)) == null) return 0;
929 else if (e
.value
.match(/^\s*\/\s*(\d+)\s*$/)) {
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);
939 ferror
.set(e
, 'Invalid netmask', quiet
);
947 mac
= mac
.replace(/\s+/g, '').toUpperCase();
948 if (mac
.length
== 0) {
951 else if (mac
.length
== 12) {
952 mac
= mac
.match(/../g);
955 mac
= mac
.split(/[:\-]/);
956 if (mac
.length
!= 6) return null;
958 for (i
= 0; i
< 6; ++i
) {
960 if (t
.search(/^[0-9A-F]+$/) == -1) return null;
961 if ((t
= parseInt(t
, 16)) > 255) return null;
964 return mac
.join(':');
967 function v_mac(e
, quiet
)
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
);
982 function v_macz(e
, quiet
)
986 if ((e
= E(e
)) == null) return 0;
987 mac
= fixMAC(e
.value
);
989 ferror
.set(e
, 'Invalid MAC address', quiet
);
997 function v_length(e
, quiet
, min
, max
)
1001 if ((e
= E(e
)) == null) return 0;
1004 if (min
== undefined) min
= 1;
1006 ferror
.set(e
, 'Invalid length. Please enter at least ' + min
+ ' character' + (min
== 1 ? '.' : 's.'), quiet
);
1009 max
= max
|| e
.maxlength
;
1011 ferror
.set(e
, 'Invalid length. Please reduce the length to ' + max
+ ' characters or less.', quiet
);
1019 function _v_iptaddr(e
, quiet
, multi
, ipv4
, ipv6
)
1023 if ((e
= E(e
)) == null) return 0;
1024 v
= e
.value
.split(',');
1026 if (v
.length
> multi
) {
1027 ferror
.set(e
, 'Too many addresses', quiet
);
1033 ferror
.set(e
, 'Invalid domain name or IP address', quiet
);
1038 for (i
= 0; i
< v
.length
; ++i
) {
1039 if ((t
= _v_domain(e
, v
[i
], 1)) == null) {
1041 if ((!ipv6
) && (!ipv4
)) {
1042 if (!quiet
) ferror
.show(e
);
1045 if ((!ipv6
) || ((t
= _v_ipv6_addr(e
, v
[i
], 1, 1)) == null)) {
1048 if (!quiet
) ferror
.show(e
);
1051 if ((t
= _v_iptip(e
, v
[i
], 1)) == null) {
1052 ferror
.set(e
, e
._error_msg
+ ', or invalid domain name', quiet
);
1062 e
.value
= v
.join(', ');
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
)
1078 v
= (typeof(delim
) == 'undefined') ? h
.split(/\s+/) : h
.split(delim
);
1081 if (v
.length
> multi
) {
1082 ferror
.set(e
, 'Too many hostnames.', quiet
);
1088 ferror
.set(e
, 'Invalid hostname.', quiet
);
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, '-');
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
);
1104 } else if (required
) {
1105 ferror
.set(e
, 'Invalid hostname.', quiet
);
1112 return v
.join((typeof(delim
) == 'undefined') ? ' ' : delim
);
1115 function v_hostname(e
, quiet
, multi
, delim
)
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;
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
);
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) {
1152 if (e
.value
.substr(0, 1) != '/') {
1153 ferror
.set(e
, 'Please start at the / root directory.', quiet
);
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
)
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
1206 return elem
.parentElem(e
, 'TABLE').gridObj
;
1209 function tgHideIcons()
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
);
1222 TomatoGrid
.prototype = {
1223 init: function(tb
, options
, maxAdd
, editorFields
) {
1226 this.tb
.gridObj
= this;
1231 if (!options
) options
= '';
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
) {
1249 tr
= this.tb
.insertRow(at
);
1250 for (i
= 0; i
< cells
.length
; ++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
;
1267 headerClick: function(cell
) {
1269 this.sort(cell
.cellN
);
1273 headerSet: function(cells
, escCells
) {
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); };
1289 footerClick: function(cell
) {
1292 footerSet: function(cells
, escCells
) {
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) };
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
);
1325 TGO(e
).moving
= null;
1327 if (i
== this.footer
) return;
1328 e
.parentNode
.removeChild(e
);
1329 i
.parentNode
.insertBefore(e
, i
.nextSibling
);
1335 rpMo: function(img
, e
) {
1340 if (me
.moving
== e
) {
1346 img
.style
.border
= "1px dotted red";
1349 rpDel: function(e
) {
1351 TGO(e
).moving
= null;
1352 e
.parentNode
.removeChild(e
);
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;
1367 e
= document
.createElement('div');
1370 e
.setAttribute('id', 'tg-row-panel');
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">';
1379 s
+= '<img src="rpx.gif" onclick="this.parentNode.tgo.rpDel(this.parentNode.ref)" title="Delete">';
1383 x
= x
.cells
[x
.cells
.length
- 1];
1384 ofs
= elem
.getOffset(x
);
1386 e
.style
.left
= (ofs
.x
+ x
.offsetWidth
- n
) + 'px';
1387 e
.style
.top
= ofs
.y
+ 'px';
1388 e
.style
.width
= n
+ 'px';
1391 document
.body
.appendChild(e
);
1394 rpHide
: tgHideIcons
,
1398 onClick: function(cell
) {
1401 var p
= this.moving
.parentNode
;
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
);
1418 insert: function(at
, data
, cells
, escCells
) {
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); };
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';
1444 insertData: function(at
, data
) {
1445 return this.insert(at
, data
, this.dataToView(data
), false);
1448 dataToView: function(data
) {
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
, '●');
1466 dataToFieldValues: function(data
) {
1470 fieldValuesToData: function(row
) {
1474 e
= fields
.getAll(row
);
1475 for (i
= 0; i
< e
.length
; ++i
) data
.push(e
[i
].value
);
1481 edit: function(cell
) {
1484 if (this.isEditing()) return;
1487 sr
.style
.display
= 'none';
1488 elem
.removeClass(sr
, 'hover');
1491 er
= this.createEditor('edit', sr
.rowIndex
, sr
);
1492 er
.className
= 'editor';
1495 c
= er
.cells
[cell
.cellIndex
|| 0];
1496 e
= c
.getElementsByTagName('input');
1497 if ((e
) && (e
.length
> 0)) {
1505 this.controls
= this.createControls('edit', sr
.rowIndex
);
1507 this.disableNewEditor(true);
1509 this.verifyFields(this.editor
, true);
1512 createEditor: function(which
, rowIndex
, source
) {
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)"';
1523 for (var i
= 0; i
< this.editorFields
.length
; ++i
) {
1525 var ef
= this.editorFields
[i
].multi
;
1526 if (!ef
) ef
= [this.editorFields
[i
]];
1528 for (var j
= 0; j
< ef
.length
; ++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
+ '"';
1538 switch (get_config('web_pb', '1')) {
1546 attrib
+= ' autocomplete="off"';
1547 if (f
.peekaboo
&& id
) attrib
+= ' onfocus=\'peekaboo("' + id
+ '",1)\'';
1550 s
+= '<input type="' + f
.type
+ '" maxlength=' + f
.maxlen
+ common
+ attrib
;
1551 if (which
== 'edit') s
+= ' value="' + escapeHTML('' + values
[vi
]) + '">';
1558 s
+= '<select' + common
+ attrib
+ '>';
1559 for (var k
= 0; k
< f
.options
.length
; ++k
) {
1561 if (which
== 'edit') {
1562 s
+= '<option value="' + a
[0] + '"' + ((a
[0] == values
[vi
]) ? ' selected>' : '>') + a
[1] + '</option>';
1565 s
+= '<option value="' + a
[0] + '">' + a
[1] + '</option>';
1571 s
+= '<input type="checkbox"' + common
+ attrib
;
1572 if ((which
== 'edit') && (values
[vi
])) s
+= ' checked';
1576 s
+= f
.custom
.replace(/\$which\$/g, which
);
1578 if (f
.suffix
) s
+= f
.suffix
;
1582 var c
= row
.insertCell(i
);
1584 if (this.editorFields
[i
].vtop
) c
.vAlign
= 'top';
1590 createControls: function(which
, rowIndex
) {
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') {
1600 '<input type=button value="Delete" onclick="TGO(this).onDelete()"> ' +
1601 '<input type=button value="OK" onclick="TGO(this).onOK()"> ' +
1602 '<input type=button value="Cancel" onclick="TGO(this).onCancel()">';
1606 '<input type=button value="Add" onclick="TGO(this).onAdd()">';
1611 removeEditor: function() {
1614 elem
.remove(this.editor
);
1617 if (this.controls
) {
1618 elem
.remove(this.controls
);
1619 this.controls
= null;
1623 showSource: function() {
1625 this.source
.style
.display
= '';
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
) {
1637 if (which
== 'edit') this.onCancel();
1640 if (((ev
.srcElement
) && (ev
.srcElement
.tagName
== 'SELECT')) ||
1641 ((ev
.target
) && (ev
.target
.tagName
== 'SELECT'))) return true;
1642 if (which
== 'edit') this.onOK();
1649 onDelete: function() {
1650 this.removeEditor();
1651 elem
.remove(this.source
);
1653 this.disableNewEditor(false);
1656 onCancel: function() {
1657 this.removeEditor();
1659 this.disableNewEditor(false);
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();
1677 this.disableNewEditor(false);
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
) {
1699 showNewEditor: function() {
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() {
1720 e
= fields
.getAll(this.newEditor
);
1722 for (i
= 0; i
< e
.length
; ++i
) {
1724 if (f
.selectedIndex
) f
.selectedIndex
= 0;
1727 try { if (e
.length
) e
[0].focus(); } catch (er
) { }
1730 getDataCount: function() {
1732 n
= this.tb
.rows
.length
;
1733 if (this.footer
) n
= this.footer
.rowIndex
;
1734 if (this.header
) n
-= this.header
.rowIndex
+ 1;
1738 sortCompare: function(a
, b
) {
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
;
1755 this.sortAscending
= true;
1756 this.sortColumn
= column
;
1758 elem
.addClass(this.header
.cells
[column
], this.sortAscending
? 'sortasc' : 'sortdes');
1763 resort: function() {
1764 if ((this.sortColumn
< 0) || (this.getDataCount() == 0) || (this.editor
)) return;
1766 var p
= this.header
.parentNode
;
1768 var i
, j
, max
, e
, p
;
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();
1779 for (i
= 0; i
< a
.length
; ++i
) {
1780 e
= p
.insertBefore(a
[i
], this.footer
);
1781 e
.className
= (j
& 1) ? 'even' : 'odd';
1786 recolor: function() {
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() {
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
;
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
);
1817 isEditing: function() {
1818 return (this.editor
!= null);
1823 // -----------------------------------------------------------------------------
1826 function xmlHttpObj()
1830 ob
= new XMLHttpRequest();
1835 ob
= new ActiveXObject('Microsoft.XMLHTTP');
1843 var _holdAjax
= null;
1847 if (_useAjax
== -1) _useAjax
= ((_holdAjax
= xmlHttpObj()) != null);
1853 if ((!useAjax()) || ((this.xob
= xmlHttpObj()) == null)) return null;
1857 XmlHttp
.prototype = {
1858 addId: function(vars
) {
1859 if (vars
) vars
+= '&';
1861 vars
+= '_http_id=' + escapeCGI(nvram
.http_id
);
1865 get: function(url
, vars
) {
1867 vars
= this.addId(vars
);
1870 this.xob
.onreadystatechange
= THIS(this, this.onReadyStateChange
);
1871 this.xob
.open('GET', url
, true);
1872 this.xob
.send(null);
1879 post: function(url
, vars
) {
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
);
1894 this.xob
.onreadystatechange = function () { }
1901 onReadyStateChange: function() {
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
);
1910 this.onError('' + (this.xob
.status
|| 'unknown'));
1919 onCompleted: function(text
, xml
) { },
1920 onError: function(ex
) { }
1924 // -----------------------------------------------------------------------------
1927 function TomatoTimer(func
, ms
)
1930 this.onTimer
= func
;
1931 if (ms
) this.start(ms
);
1935 TomatoTimer
.prototype = {
1936 start: function(ms
) {
1938 this.tid
= setTimeout(THIS(this, this._onTimer
), ms
);
1942 clearTimeout(this.tid
);
1947 isRunning: function() {
1948 return (this.tid
!= null);
1951 _onTimer: function() {
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 = {
1973 setup: function(actionURL
, postData
, refreshTime
, cookieTag
) {
1976 this.actionURL
= actionURL
;
1977 this.postData
= postData
;
1978 this.refreshTime
= refreshTime
* 1000;
1979 this.cookieTag
= cookieTag
;
1985 if ((e
= E('refresh-time')) != null) {
1986 if (this.cookieTag
) cookie
.set(this.cookieTag
, e
.value
);
1987 this.refreshTime
= e
.value
* 1000;
1991 this.updateUI('start');
1994 if ((this.http
= new XmlHttp()) == null) {
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');
2012 if ((p
.refreshTime
> 0) && (!p
.once
)) {
2014 p
.timer
.start(Math
.round(p
.refreshTime
));
2023 this.http
.onError = function(ex
) {
2024 var p
= this.parent
;
2025 if ((!p
) || (!p
.running
)) return;
2029 if (++p
.errors
<= 3) {
2031 p
.timer
.start(3000);
2036 var e
= cookie
.get(p
.cookieTag
+ '-error') * 1;
2037 if (isNaN(e
)) e
= 0;
2039 cookie
.unset(p
.cookieTag
);
2040 cookie
.set(p
.cookieTag
+ '-error', e
, 1);
2042 alert('XMLHTTP: ' + ex
);
2047 setTimeout(reloadPage
, 2000);
2051 this.http
.post(this.actionURL
, this.postData
);
2055 if (this.cookieTag
) cookie
.set(this.cookieTag
, -(this.refreshTime
/ 1000));
2057 this.updateUI('stop');
2060 this.once
= undefined;
2063 toggle: function(delay
) {
2064 if (this.running
) this.stop();
2065 else this.start(delay
);
2068 updateUI: function(mode
) {
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
) {
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);
2093 if (e
) e
.value
= def
;
2104 this.refreshTime
= v
;
2105 this.timer
.start(v
);
2106 this.updateUI('wait');
2111 function genStdTimeList(id
, zero
, min
)
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];
2118 b
.push('<select id="' + id
+ '"><option value=0>' + zero
);
2119 for (i
= 0; i
< t
.length
; ++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
)
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 // -----------------------------------------------------------------------------
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') {
2197 evt
.target
= evt
.srcElement
;
2198 evt
.relatedTarget
= evt
.toElement
;
2210 return (typeof(e
) == 'string') ? document
.getElementById(e
) : e
;
2215 return elem
.parentElem(e
, 'TR');
2218 function THIS(obj
, func
)
2220 return function() { return func
.apply(obj
, arguments
); }
2225 return (typeof(v
) == 'undefined') ? '' : '' + v
;
2228 function escapeHTML(s
)
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 +
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) + '...';
2255 return (a
< b
) ? a
: b
;
2260 return (a
> b
) ? a
: b
;
2263 function fixInt(n
, min
, max
, def
)
2265 if (n
=== null) return def
;
2267 if (isNaN(n
)) return def
;
2268 if (n
< min
) return min
;
2269 if (n
> max
) return max
;
2277 while ((n
= n
.replace(/(\d+)(\d{3})/g, '$1,$2')) != p
) p
= n
;
2281 function doScaleSize(n
, sm
)
2283 if (isNaN(n
*= 1)) return '-';
2284 if (n
<= 9999) return '' + n
;
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'];
2311 for (i
= features
.length
- 1; i
>= 0; --i
) {
2312 if (features
[i
] == s
) return (parseInt(nvram
.t_features
) & (1 << i
)) != 0;
2317 function get_config(name
, def
)
2319 return ((typeof(nvram
) != 'undefined') && (typeof(nvram
[name
]) != 'undefined')) ? nvram
[name
] : def
;
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 // -----------------------------------------------------------------------------
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
';
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
'],
2359 ['Client Monitor
', 'client
.asp
'],
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
'] ] ],
2377 ['Basic
', 'basic
', 0, [
2378 ['Network
', 'network
.asp
'],
2380 ['IPv6
', 'ipv6
.asp
'],
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
']
2396 ,['VLAN
', 'vlan
.asp
'],
2397 ['LAN Access
', 'access
.asp
'],
2398 ['Virtual Wireless
', 'wlanvifs
.asp
']
2401 ['Port Forwarding
', 'forward
', 0, [
2402 ['Basic
', 'basic
.asp
'],
2404 ['Basic IPv6
', 'basic
-ipv6
.asp
'],
2407 ['Triggered
', 'triggered
.asp
'],
2408 ['UPnP
/NAT
-PMP
', 'upnp
.asp
'] ] ],
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
'],
2419 ['Bandwidth Limiter
', 'bwlimit
.asp
'],
2421 ['Captive Portal
', 'splashd
.asp
'],
2424 ['Web Server
', 'nginx
.asp
'],
2427 // ---- !!TB - USB, FTP, Samba, Media Server
2428 ['USB and NAS
', 'nas
', 0, [
2429 ['USB Support
', 'usb
.asp
']
2431 ,['FTP Server
', 'ftp
.asp
']
2434 ,['File Sharing
', 'samba
.asp
']
2436 /* MEDIA-SRV-BEGIN */
2437 ,['Media Server
', 'media
.asp
']
2442 ['VPN Tunneling
', 'vpn
', 0, [
2444 ['OpenVPN Server
', 'server
.asp
'],
2445 ['OpenVPN Client
', 'client
.asp
'],
2448 ['PPTP Server
', 'pptp
-server
.asp
'],
2449 ['PPTP Online
', 'pptp
-online
.asp
'],
2451 /* USERPPTP-BEGIN */
2452 ['PPTP Client
', 'pptp
.asp
']
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
'],
2463 ['CIFS Client
', 'cifs
.asp
'],
2465 ['Configuration
', 'config
.asp
'],
2466 ['Debugging
', 'debug
.asp
'],
2468 ['JFFS
', 'jffs2
.asp
'],
2470 ['Logging
', 'log
.asp
'],
2471 ['Scheduler
', 'sched
.asp
'],
2472 ['Scripts
', 'scripts
.asp
'],
2474 ['SNMP
', 'snmp
.asp
'],
2476 ['Upgrade
', 'upgrade
.asp
'] ] ],
2478 ['About
', 'about
.asp
'],
2479 ['Reboot
...', 'javascript
:reboot()'],
2480 ['Shutdown
...', 'javascript
:shutdown()'],
2481 ['Logout
', 'javascript
:logout()']
2489 var cexp = get_config('web_mx
', '').toLowerCase();
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);
2499 for (i = 0; i < menu.length; ++i) {
2505 if (m.length == 2) {
2506 buf.push('<a href
="' + m[1] + '" class="indent1' + (((base == '') && (name == m[1])) ? ' active' : '') + '">' + m[0] + '</a
>');
2513 a = cookie.get('menu_
' + m[1]);
2515 for (j = 0; j < m[3].length; ++j) {
2516 if (m[3][j][1] == a) {
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) {
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(''));
2539 if ((base == 'qos
') && (name == 'detailed
.asp
')) name = 'view
.asp
';
2540 cookie.set('menu_
' + base, name);
2544 function createFieldTable(flags, desc)
2558 if ((flags.indexOf('noopen
') == -1)) buf.push('<table
class="fields">');
2559 for (desci = 0; desci < desc.length; ++desci) {
2560 var v = desc[desci];
2563 buf.push('<tr
><td colspan
=2 class="spacer"> 
;</td></tr
>');
2567 if (v.ignore) continue;
2570 if (v.rid) buf.push(' id
="' + v.rid + '"');
2571 if (v.hidden) buf.push(' style
="display:none"');
2576 buf.push('<td
class="title indent' + (v.indent || 1) + '">' + v.title + '</td><td class="content">' + v.text + '</td
></tr
>');
2579 buf.push('<td colspan
=2>' + v.text + '</td></tr
>');
2586 buf2.push('<td
class="content">');
2588 if (v.multi) fields = v.multi;
2591 for (n = 0; n < fields.length; ++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 + '"') : '';
2606 buf2.push('<input type
="checkbox"' + name + (f.value ? ' checked
' : '') + ' onclick
="verifyFields(this, 1)"' + common + '>');
2609 buf2.push('<input type
="radio"' + name + (f.value ? ' checked
' : '') + ' onclick
="verifyFields(this, 1)"' + common + '>');
2613 switch (get_config('web_pb
', '1')) {
2621 if (f.type == 'password
') {
2622 common += ' autocomplete
="off"';
2623 if (f.peekaboo) common += ' onfocus
=\'peekaboo("' + id + '",1)\'';
2627 buf2.push('<input type
="' + f.type + '"' + name + ' value
="' + escapeHTML(UT(f.value)) + '" maxlength
=' + f.maxlen + (f.size ? (' size
=' + f.size) : '') + common + '>');
2633 buf2.push('<select
' + name + common + '>');
2634 for (i = 0; i < f.options.length; ++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
>');
2642 buf2.push('<textarea
' + name + common + (f.wrap ? (' wrap
=' + f.wrap) : '') + '>' + escapeHTML(UT(f.value)) + '</textarea
>');
2645 if (f.custom) buf2.push(f.custom);
2648 if (f.suffix) buf2.push(f.suffix);
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(''));
2659 if ((!flags) || (flags.indexOf('noclose
') == -1)) buf.push('</table
>');
2660 document.write(buf.join(''));
2663 function peekaboo(id, show)
2666 var o = document.createElement('INPUT
');
2669 o.type = show ? 'text
' : 'password
';
2672 o.maxLength = e.maxLength;
2673 o.autocomplete = e.autocomplete;
2675 o.disabled = e.disabled;
2676 o.onchange = e.onchange;
2677 e.parentNode.replaceChild(o, e);
2683 o.onblur = function(ev) { setTimeout('peekaboo("' + this.id + '", 0)', 0) };
2684 setTimeout('try { E("' + id + '").focus() } catch (ex
) { }', 0)
2687 o.onfocus = function(ev) { peekaboo(this.id, 1); };
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
.
2701 // -----------------------------------------------------------------------------
2703 function reloadPage()
2705 document
.location
.reload(1);
2710 if (confirm("Reboot?")) form
.submitHidden('tomato.cgi', { _reboot
: 1, _commit
: 0, _nvset
: 0 });
2715 if (confirm("Shutdown?")) form
.submitHidden('shutdown.cgi', { });
2720 form
.submitHidden('logout.asp', { });
2723 // -----------------------------------------------------------------------------
2731 return location
.href
.search('file://') == 0;