Remove TomatoAnon
[tomato.git] / release / src / router / www / tomato.js
blobbf83048a9a1510f965dbc847346bc78fae3d06b2
1 /*
2         Tomato GUI
3         Copyright (C) 2006-2010 Jonathan Zarate
4         http://www.polarcloud.com/tomato/
6         For use with Tomato Firmware only.
7         No part of this file may be used without permission.
8 */
10 // -----------------------------------------------------------------------------
12 Array.prototype.find = function(v) {
13         for (var i = 0; i < this.length; ++i)
14                 if (this[i] == v) return i;
15         return -1;
18 Array.prototype.remove = function(v) {
19         for (var i = 0; i < this.length; ++i) {
20                 if (this[i] == v) {
21                         this.splice(i, 1);
22                         return true;
23                 }
24         }
25         return false;
28 // -----------------------------------------------------------------------------
30 String.prototype.trim = function() {
31         return this.replace(/^\s+/, '').replace(/\s+$/, '');
34 // -----------------------------------------------------------------------------
36 Number.prototype.pad = function(min) {
37         var s = this.toString();
38         while (s.length < min) s = '0' + s;
39         return s;
42 Number.prototype.hex = function(min)
44         var h = '0123456789ABCDEF';
45         var n = this;
46         var s = '';
47         do {
48                 s = h.charAt(n & 15) + s;
49                 n = n >>> 4;
50         } while ((--min > 0) || (n > 0));
51         return s;
54 // -----------------------------------------------------------------------------
56 // ---- Element.protoype. doesn't work with all browsers
58 var elem = {
59         getOffset: function(e) {
60                 var r = { x: 0, y: 0 };
61                 e = E(e);
62                 while (e.offsetParent) {
63                         r.x += e.offsetLeft;
64                         r.y += e.offsetTop;
65                         e = e.offsetParent;
66                 }
67                 return r;
68         },
70         addClass: function(e, name) {
71                 if ((e = E(e)) == null) return;
72                 var a = e.className.split(/\s+/);
73                 var k = 0;
74                 for (var i = 1; i < arguments.length; ++i) {
75                         if (a.find(arguments[i]) == -1) {
76                                 a.push(arguments[i]);
77                                 k = 1;
78                         }
79                 }
80                 if (k) e.className = a.join(' ');
81         },
83         removeClass: function(e, name) {
84                 if ((e = E(e)) == null) return;
85                 var a = e.className.split(/\s+/);
86                 var k = 0;
87                 for (var i = 1; i < arguments.length; ++i)
88                         k |= a.remove(arguments[i]);
89                 if (k) e.className = a.join(' ');
90         },
92         remove: function(e) {
93                  if ((e = E(e)) != null) e.parentNode.removeChild(e);
94         },
96     parentElem: function(e, tagName) {
97                 e = E(e);
98                 tagName = tagName.toUpperCase();
99                 while (e.parentNode) {
100                         e = e.parentNode;
101                         if (e.tagName == tagName) return e;
102                 }
103                 return null;
104         },
106         display: function() {
107                 var enable = arguments[arguments.length - 1];
108                 for (var i = 0; i < arguments.length - 1; ++i) {
109                         E(arguments[i]).style.display = enable ? '' : 'none';
110                 }
111         },
113         isVisible: function(e) {
114                 e = E(e);
115                 while (e) {
116                         if ((e.style.visibility != 'visible') || (e.style.display == 'none')) return false;
117                         e = e.parentNode;
118                 }
119                 return true;
120         },
122         setInnerHTML: function(e, html) {
123                  e = E(e);
124                  if (e.innerHTML != html) e.innerHTML = html;   // reduce flickering
125         }
128 // -----------------------------------------------------------------------------
130 var docu = {
131         getViewSize: function() {
132                 if (window.innerHeight) {
133                         return { width: window.innerWidth, height: window.innerHeight };
134                 }
135                 else if (document.documentElement && document.documentElement.clientHeight) {
136                         return { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight };
137                 }
138                 return { width: document.body.clientWidth, height: document.body.clientHeight };
139         },
141         getPageOffset: function()
142         {
143                 if (typeof(window.pageYOffset) != 'undefined') {
144                         return { x: window.pageXOffset, y: window.pageYOffset };
145                 }
146                 else if ((document.documentElement) && (typeof(document.documentElement.scrollTop) != 'undefined')) {
147                         return { x: document.documentElement.scrollLeft, y: document.documentElement.scrollTop };
148                 }
149                 return { x: document.body.scrollLeft, y: document.body.scrollTop };
150         }
153 // -----------------------------------------------------------------------------
155 var fields = {
156         getAll: function(e) {
157                 var a = [];
158                 switch (e.tagName) {
159                 case 'INPUT':
160                 case 'SELECT':
161                         a.push(e);
162                         break;
163                 default:
164                         if (e.childNodes) {
165                                 for (var i = 0; i < e.childNodes.length; ++i) {
166                                         a = a.concat(fields.getAll(e.childNodes[i]));
167                                 }
168                         }
169                 }
170                 return a;
171         },
172         disableAll: function(e, d) {
173                 var i;
175                 if ((typeof(e.tagName) == 'undefined') && (typeof(e) != 'string')) {
176                         for (i = e.length - 1; i >= 0; --i) {
177                                 e[i].disabled = d;
178                         }
179                 }
180                 else {
181                         var a = this.getAll(E(e));
182                         for (var i = a.length - 1; i >= 0; --i) {
183                                 a[i].disabled = d;
184                         }
185                 }
186         },
187         radio: {
188                 selected: function(e) {
189                         for (var i = 0; i < e.length; ++i) {
190                                 if (e[i].checked) return e[i];
191                         }
192                         return null;
193                 },
194                 find: function(e, value) {
195                         for (var i = 0; i < e.length; ++i) {
196                                 if (e[i].value == value) return e[i];
197                         }
198                         return null;
199                 }
200         }
203 // -----------------------------------------------------------------------------
205 var form = {
206         submitHidden: function(url, fields) {
207                 var fom, body;
209                 fom = document.createElement('FORM');
210                 fom.action = url;
211                 fom.method = 'post';
212                 for (var f in fields) {
213                         var e = document.createElement('INPUT');
214                         e.type = 'hidden';
215                         e.name = f;
216                         e.value = fields[f];
217                         fom.appendChild(e);
218                 }
219                 body = document.getElementsByTagName('body')[0];
220                 fom = body.appendChild(fom);
221                 this.submit(fom);
222                 body.removeChild(fom);
223         },
225         submit: function(fom, async, url) {
226                 var e, v, f, i, wait, msg, sb, cb;
228                 fom = E(fom);
230                 if (isLocal()) {
231                         this.dump(fom, async, url);
232                         return;
233                 }
235                 if (this.xhttp) return;
237                 if ((sb = E('save-button')) != null) sb.disabled = 1;
238                 if ((cb = E('cancel-button')) != null) cb.disabled = 1;
240                 if ((!async) || (!useAjax())) {
241                         this.addId(fom);
242                         if (url) fom.action = url;
243                         fom.submit();
244                         return;
245                 }
247                 v = ['_ajax=1'];
248                 wait = 5;
249                 for (var i = 0; i < fom.elements.length; ++i) {
250                         f = fom.elements[i];
251                         if ((f.disabled) || (f.name == '') || (f.name.substr(0, 2) == 'f_')) continue;
252                         if ((f.tagName == 'INPUT') && ((f.type == 'CHECKBOX') || (f.type == 'RADIO')) && (!f.checked)) continue;
253                         if (f.name == '_nextwait') {
254                                 wait = f.value * 1;
255                                 if (isNaN(wait)) wait = 5;
256                                         else wait = Math.abs(wait);
257                         }
258                         v.push(escapeCGI(f.name) + '=' + escapeCGI(f.value));
259                 }
261                 if ((msg = E('footer-msg')) != null) {
262                         msg.innerHTML = 'Saving...';
263                         msg.style.visibility = 'visible';
264                 }
266                 this.xhttp = new XmlHttp();
267                 this.xhttp.onCompleted = function(text, xml) {
268                         if (msg) {
269                                 if (text.match(/@msg:(.+)/)) msg.innerHTML = escapeHTML(RegExp.$1);
270                                         else msg.innerHTML = 'Saved';
271                         }
272                         setTimeout(
273                                 function() {
274                                         if (sb) sb.disabled = 0;
275                                         if (cb) cb.disabled = 0;
276                                         if (msg) msg.style.visibility = 'hidden';
277                                         if (typeof(submit_complete) != 'undefined') submit_complete();
278                                 }, wait * 1100);
279                         form.xhttp = null;
280                 }
281                 this.xhttp.onError = function(x) {
282                         if (url) fom.action = url;
283                         fom.submit();
284                 }
286                 this.xhttp.post(url ? url : fom.action, v.join('&'));
287         },
289         addId: function(fom) {
290                 var e;
292                 if (typeof(fom._http_id) == 'undefined') {
293                         e = document.createElement('INPUT');
294                         e.type = 'hidden';
295                         e.name = '_http_id';
296                         e.value = nvram.http_id;
297                         fom.appendChild(e);
298                 }
299                 else {
300                         fom._http_id.value = nvram.http_id;
301                 }
302         },
304         addIdAction: function(fom) {
305                 if (fom.action.indexOf('?') != -1) fom.action += '&_http_id=' + nvram.http_id;
306                         else fom.action += '?_http_id=' + nvram.http_id;
307         },
309         dump: function(fom, async, url) {
310         }
313 // -----------------------------------------------------------------------------
315 var ferror = {
316         set: function(e, message, quiet) {
317                 if ((e = E(e)) == null) return;
318                 e._error_msg = message;
319                 e._error_org = e.title;
320                 e.title = message;
321                 elem.addClass(e, 'error');
322                 if (!quiet) this.show(e);
323         },
325         clear: function(e) {
326                 if ((e = E(e)) == null) return;
327                 e.title = e._error_org || '';
328                 elem.removeClass(e, 'error');
329                 e._error_msg = null;
330                 e._error_org = null;
331         },
333         clearAll: function(e) {
334                 for (var i = 0; i < e.length; ++i)
335                         this.clear(e[i]);
336         },
338         show: function(e) {
339                 if ((e = E(e)) == null) return;
340                 if (!e._error_msg) return;
341                 elem.addClass(e, 'error-focused');
342                 e.focus();
343                 alert(e._error_msg);
344                 elem.removeClass(e, 'error-focused');
345         },
347         ok: function(e) {
348                 if ((e = E(e)) == null) return 0;
349         return !e._error_msg;
350         }
353 // -----------------------------------------------------------------------------
355 function fixFile(name)
357         var i;
358         if (((i = name.lastIndexOf('/')) > 0) || ((i = name.lastIndexOf('\\')) > 0))
359                 name = name.substring(i + 1, name.length);
360         return name;
363 function _v_range(e, quiet, min, max, name)
365         if ((e = E(e)) == null) return 0;
366         var v = e.value;
367         if ((!v.match(/^ *[-\+]?\d+ *$/)) || (v < min) || (v > max)) {
368                 ferror.set(e, 'Invalid ' + name + '. Valid range: ' + min + '-' + max, quiet);
369                 return 0;
370         }
371         e.value = v * 1;
372         ferror.clear(e);
373         return 1;
376 function v_range(e, quiet, min, max)
378         return _v_range(e, quiet, min, max, 'number');
381 function v_port(e, quiet)
383         return _v_range(e, quiet, 1, 0xFFFF, 'port');
386 function v_octet(e, quiet)
388         return _v_range(e, quiet, 1, 254, 'address');
391 function v_mins(e, quiet, min, max)
393         var v, m;
395         if ((e = E(e)) == null) return 0;
396         if (e.value.match(/^\s*(.+?)([mhd])?\s*$/)) {
397                 m = 1;
398                 if (RegExp.$2 == 'h') m = 60;
399                         else if (RegExp.$2 == 'd') m = 60 * 24;
400                 v = Math.round(RegExp.$1 * m);
401                 if (!isNaN(v)) {
402                         e.value = v;
403                         return _v_range(e, quiet, min, max, 'minutes');
404                 }
405         }
406         ferror.set(e, 'Invalid number of minutes.', quiet);
407         return 0;
410 function v_macip(e, quiet, bok, 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 = '';
425                         }
426                         else {
427                                 ferror.set(e, 'Invalid MAC or IP address');
428                                 return false;
429                         }
430                 }
431                 else e.value = a;
432                 ferror.clear(e);
433                 return true;
434         }
436         a = s.split('-');
437     
438         if (a.length > 2) {
439                 ferror.set(e, 'Invalid IP address range', quiet);
440                 return false;
441         }
442         
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];
447         }
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];
452                 }
453         }
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;
460                 }
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;
465                 }
467                 d = (b.split('.'))[3];
468                 if (parseInt(d) <= parseInt(c)) {
469                         ferror.set(e, 'Invalid IP address range', quiet);
470                         return false;
471                 }
473                 a[i] = c = d;
474         }
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;
493         }
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;
507         }
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';
525         }
526         else {
527                 var s = e.value.split(':');
528                 if (s.length == 1) {
529                         s.push(53);
530                 }
531                 else if (s.length != 2) {
532                         ferror.set(e, 'Invalid IP address or port', quiet);
533                         return false;
534                 }
535                 
536                 if ((s[0] = fixIP(s[0])) == null) {
537                         ferror.set(e, 'Invalid IP address', quiet);
538                         return false;
539                 }
541                 if ((s[1] = fixPort(s[1], -1)) == -1) {
542                         ferror.set(e, 'Invalid port', quiet);
543                         return false;
544                 }
545         
546                 if (s[1] == 53) {
547                         e.value = s[0];
548                 }
549                 else {
550                         e.value = s.join(':');
551                 }
552         }
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;
590                 }
591                 ferror.clear(e);
593                 if (aton(a) > aton(b)) return b + '-' + a;
594                 return a + '-' + b;
595         }
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;
611                         }
612                 }
613                 else {
614                         if ((ma < 0) || (ma > 32)) {
615                                 ferror.set(e, oip + ' - invalid netmask', quiet);
616                                 return null;
617                         }
618                 }
619         }
621         ip = fixIP(ip);
622         if (!ip) {
623                 ferror.set(e, oip + ' - invalid IP address', quiet);
624                 return null;
625         }
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;
641                 }
642         }
643         else {
644                 if (v.length > 1) {
645                         ferror.set(e, 'Invalid IP address', quiet);
646                         return 0;
647                 }
648         }
649         for (i = 0; i < v.length; ++i) {
650                 if ((v[i] = _v_iptip(e, v[i], quiet)) == null) return 0;
651         }
652         e.value = v.join(', ');
653         return 1;
656 function _v_subnet(e, ip, quiet)
658         var ma, oip;
659         oip = ip;
661         // x.x.x.x/nn
662         if (ip.match(/^(.*)\/(.*)$/)) {
663                 ip = RegExp.$1;
664                 ma = RegExp.$2;
666                 if ((ma < 0) || (ma > 32)) {
667                         ferror.set(e, oip + ' - invalid subnet', quiet);
668                         return null;
669                 }
670         }
671         else {
672                 ferror.set(e, oip + ' - invalid subnet', quiet);
673                 return null;
674         }
676         ferror.clear(e);
677         return ip + ((ma != '') ? ('/' + ma) : '');
680 function v_subnet(e, quiet)
682         if ((_v_subnet(e, e.value, quiet)) == null) return 0;
684         return 1;
687 function _v_domain(e, dom, quiet)
689         var s;
691         s = dom.replace(/\s+/g, ' ').trim();
692         if (s.length > 0) {
693                 s = _v_hostname(e, s, 1, 1, 7, '.', true);
694                 if (s == null) {
695                         ferror.set(e, "Invalid name. Only characters \"A-Z 0-9 . -\" are allowed.", quiet);
696                         return null;
697                 }
698         }
699         ferror.clear(e);
700         return s;
703 function v_domain(e, quiet)
705         var v;
707         if ((e = E(e)) == null) return 0;
708         if ((v = _v_domain(e, e.value, quiet)) == null) return 0;
710         e.value = v;
711         return 1;
714 /* IPV6-BEGIN */
715 function ExpandIPv6Address(ip)
717         var a, pre, n, i, fill, post;
719         ip = ip.toLowerCase();
720         if (!ip.match(/^(::)?([a-f0-9]{1,4}::?){0,7}([a-f0-9]{1,4})(::)?$/)) return null;
722         a = ip.split('::');
723         switch (a.length) {
724         case 1:
725                 if (a[0] == '') return null;
726                 pre = a[0].split(':');
727                 if (pre.length != 8) return null;
728                 ip = pre.join(':');
729                 break;
730         case 2:
731                 pre = a[0].split(':');
732                 post = a[1].split(':');
733                 n = 8 - pre.length - post.length;
734                 for (i=0; i<2; i++) {
735                         if (a[i]=='') n++;
736                 }
737                 if (n < 0) return null;
738                 fill = '';
739                 while (n-- > 0) fill += ':0';
740                 ip = pre.join(':') + fill + ':' + post.join(':');
741                 ip = ip.replace(/^:/, '').replace(/:$/, '');
742                 break;
743         default:
744                 return null;
745         }
746         
747         ip = ip.replace(/([a-f0-9]{1,4})/ig, '000$1');
748         ip = ip.replace(/0{0,3}([a-f0-9]{4})/ig, '$1');
749         return ip;
752 function CompressIPv6Address(ip)
754         var a, segments;
755         
756         ip = ExpandIPv6Address(ip);
757         if (!ip) return null;
758         
759         // if (ip.match(/(?:^00)|(?:^fe[8-9a-b])|(?:^ff)/)) return null; // not valid routable unicast address
761         ip = ip.replace(/(^|:)0{1,3}/g, '$1');
762         ip = ip.replace(/(:0)+$/, '::');
763         ip = ip.replace(/(?:(?:^|:)0){2,}(?!.*(?:::|(?::0){3,}))/, ':');
764         return ip;
767 function ZeroIPv6PrefixBits(ip, prefix_length)
769         var b, c, m, n;
770         ip = ExpandIPv6Address(ip);
771         ip = ip.replace(/:/g,'');
772         n = Math.floor(prefix_length/4);
773         m = 32 - Math.ceil(prefix_length/4);
774         b = prefix_length % 4;
775         if (b != 0) 
776                 c = (parseInt(ip.charAt(n), 16) & (0xf << 4-b)).toString(16);
777         else
778                 c = '';
779         
780         ip = ip.substring(0, n) + c + Array((m%4)+1).join('0') + (m>=4 ? '::' : '');
781         ip = ip.replace(/([a-f0-9]{4})(?=[a-f0-9])/g,'$1:');
782         ip = ip.replace(/(^|:)0{1,3}/g, '$1');
783         return ip;
786 function ipv6ton(ip)
788         var o, x, i;
790         ip = ExpandIPv6Address(ip);
791         if (!ip) return 0;
793         o = ip.split(':');
794         x = '';
795         for (i = 0; i < 8; ++i) x += (('0x' + o[i]) * 1).hex(4);
796         return parseInt(x, 16);
799 function _v_ipv6_addr(e, ip, ipt, quiet)
801         var oip;
802         var a, b;
804         oip = ip;
806         // ip range
807         if ((ipt) && ip.match(/^(.*)-(.*)$/)) {
808                 a = RegExp.$1;
809                 b = RegExp.$2;
810                 a = CompressIPv6Address(a);
811                 b = CompressIPv6Address(b);
812                 if ((a == null) || (b == null)) {
813                         ferror.set(e, oip + ' - invalid IPv6 address range', quiet);
814                         return null;
815                 }
816                 ferror.clear(e);
818                 if (ipv6ton(a) > ipv6ton(b)) return b + '-' + a;
819                 return a + '-' + b;
820         }
822         // mask matches
823         if ((ipt) && ip.match(/^([A-Fa-f0-9:]+)\/([A-Fa-f0-9:]+)$/)) {
824                 a = RegExp.$1;
825                 b = RegExp.$2;
826                 a = CompressIPv6Address(a);
827                 b = CompressIPv6Address(b);
828                 if ((a == null) || (b == null)) {
829                         ferror.set(e, oip + ' - invalid IPv6 address with mask', quiet);
830                         return null;
831                 }
832                 ferror.clear(e);
834                 return ip;
835         }
837         
838         if ((ipt) && ip.match(/^([A-Fa-f0-9:]+)\/(\d+)$/)) {
839                 a = RegExp.$1;
840                 b = parseInt(RegExp.$2, 10);
841                 a = ExpandIPv6Address(a);
842                 if ((a == null) || (b == null)) {
843                         ferror.set(e, oip + ' - invalid IPv6 address', quiet);
844                         return null;
845                 }
846                 if (b < 0 || b > 128) {
847                         ferror.set(e, oip + ' - invalid CIDR notation on IPv6 address', quiet);
848                         return null;
849                 }
850                 ferror.clear(e);
852                 ip = ZeroIPv6PrefixBits(a, b);
853                 return ip + '/' + b.toString(10);
854         }
856         ip = CompressIPv6Address(oip);
857         if (!ip) {
858                 ferror.set(e, oip + ' - invalid IPv6 address', quiet);
859                 return null;
860         }
862         ferror.clear(e);
863         return ip;
866 function v_ipv6_addr(e, quiet)
868         if ((e = E(e)) == null) return 0;
870         ip = _v_ipv6_addr(e, e.value, false, quiet);
871         if (ip) e.value = ip;
872         return (ip != null);
874 /* IPV6-END */
876 function fixPort(p, def)
878         if (def == null) def = -1;
879         if (p == null) return def;
880         p *= 1;
881         if ((isNaN(p) || (p < 1) || (p > 65535) || (('' + p).indexOf('.') != -1))) return def;
882         return p;
885 function _v_portrange(e, quiet, v)
887         if (v.match(/^(.*)[-:](.*)$/)) {
888                 var x = RegExp.$1;
889                 var y = RegExp.$2;
891                 x = fixPort(x, -1);
892                 y = fixPort(y, -1);
893                 if ((x == -1) || (y == -1)) {
894                         ferror.set(e, 'Invalid port range: ' + v, quiet);
895                         return null;
896                 }
897                 if (x > y) {
898                         v = x;
899                         x = y;
900                         y = v;
901                 }
902                 ferror.clear(e);
903                 if (x == y) return x;
904                 return x + '-' + y;
905         }
907         v = fixPort(v, -1);
908         if (v == -1) {
909                 ferror.set(e, 'Invalid port', quiet);
910                 return null;
911         }
913         ferror.clear(e);
914         return v;
917 function v_portrange(e, quiet)
919         var v;
921         if ((e = E(e)) == null) return 0;
922         v = _v_portrange(e, quiet, e.value);
923         if (v == null) return 0;
924         e.value = v;
925         return 1;
928 function v_iptport(e, quiet)
930         var a, i, v, q;
932         if ((e = E(e)) == null) return 0;
934         a = e.value.split(/[,\.]/);
936         if (a.length == 0) {
937                 ferror.set(e, 'Expecting a list of ports or port range.', quiet);
938                 return 0;
939         }
940         if (a.length > 10) {
941                 ferror.set(e, 'Only 10 ports/range sets are allowed.', quiet);
942                 return 0;
943         }
945         q = [];
946         for (i = 0; i < a.length; ++i) {
947                 v = _v_portrange(e, quiet, a[i]);
948                 if (v == null) return 0;
949                 q.push(v);
950         }
952         e.value = q.join(',');
953         ferror.clear(e);
954         return 1;
957 function _v_netmask(mask)
959         var v = aton(mask) ^ 0xFFFFFFFF;
960         return (((v + 1) & v) == 0);
963 function v_netmask(e, quiet)
965         var n, b;
967         if ((e = E(e)) == null) return 0;
968         n = fixIP(e.value);
969         if (n) {
970                 if (_v_netmask(n)) {
971                         e.value = n;
972                         ferror.clear(e);
973                         return 1;
974                 }
975         }
976         else if (e.value.match(/^\s*\/\s*(\d+)\s*$/)) {
977                 b = RegExp.$1 * 1;
978                 if ((b >= 1) && (b <= 32)) {
979                         if (b == 32) n = 0xFFFFFFFF;    // js quirk
980                                 else n = (0xFFFFFFFF >>> b) ^ 0xFFFFFFFF;
981                         e.value = (n >>> 24) + '.' + ((n >>> 16) & 0xFF) + '.' + ((n >>> 8) & 0xFF) + '.' + (n & 0xFF);
982                         ferror.clear(e);
983                         return 1;
984                 }
985         }
986         ferror.set(e, 'Invalid netmask', quiet);
987         return 0;
990 function fixMAC(mac)
992         var t, i;
994         mac = mac.replace(/\s+/g, '').toUpperCase();
995         if (mac.length == 0) {
996                 mac = [0,0,0,0,0,0];
997         }
998         else if (mac.length == 12) {
999                 mac = mac.match(/../g);
1000         }
1001         else {
1002                 mac = mac.split(/[:\-]/);
1003                 if (mac.length != 6) return null;
1004         }
1005         for (i = 0; i < 6; ++i) {
1006                 t = '' + mac[i];
1007                 if (t.search(/^[0-9A-F]+$/) == -1) return null;
1008                 if ((t = parseInt(t, 16)) > 255) return null;
1009                 mac[i] = t.hex(2);
1010         }
1011         return mac.join(':');
1014 function v_mac(e, quiet)
1016         var mac;
1018         if ((e = E(e)) == null) return 0;
1019         mac = fixMAC(e.value);
1020         if ((!mac) || (isMAC0(mac))) {
1021                 ferror.set(e, 'Invalid MAC address', quiet);
1022                 return 0;
1023         }
1024         e.value = mac;
1025         ferror.clear(e);
1026         return 1;
1029 function v_macz(e, quiet)
1031         var mac;
1033         if ((e = E(e)) == null) return 0;
1034         mac = fixMAC(e.value);
1035         if (!mac) {
1036                 ferror.set(e, 'Invalid MAC address', quiet);
1037                 return false;
1038         }
1039         e.value = mac;
1040         ferror.clear(e);
1041         return true;
1044 function v_length(e, quiet, min, max)
1046         var s, n;
1048         if ((e = E(e)) == null) return 0;
1049         s = e.value.trim();
1050         n = s.length;
1051         if (min == undefined) min = 1;
1052         if (n < min) {
1053                 ferror.set(e, 'Invalid length. Please enter at least ' + min + ' character' + (min == 1 ? '.' : 's.'), quiet);
1054                 return 0;
1055         }
1056         max = max || e.maxlength;
1057         if (n > max) {
1058                 ferror.set(e, 'Invalid length. Please reduce the length to ' + max + ' characters or less.', quiet);
1059                 return 0;
1060         }
1061         e.value = s;
1062         ferror.clear(e);
1063         return 1;
1066 function _v_iptaddr(e, quiet, multi, ipv4, ipv6)
1068         var v, t, i;
1070         if ((e = E(e)) == null) return 0;
1071         v = e.value.split(',');
1072         if (multi) {
1073                 if (v.length > multi) {
1074                         ferror.set(e, 'Too many addresses', quiet);
1075                         return 0;
1076                 }
1077         }
1078         else {
1079                 if (v.length > 1) {
1080                         ferror.set(e, 'Invalid domain name or IP address', quiet);
1081                         return 0;
1082                 }
1083         }
1085         for (i = 0; i < v.length; ++i) {
1086                 if ((t = _v_domain(e, v[i], 1)) == null) {
1087 /* IPV6-BEGIN */
1088                         if ((!ipv6) && (!ipv4)) {
1089                                 if (!quiet) ferror.show(e);
1090                                 return 0;
1091                         }
1092                         if ((!ipv6) || ((t = _v_ipv6_addr(e, v[i], 1, 1)) == null)) {
1093 /* IPV6-END */
1094                                 if (!ipv4) {
1095                                         if (!quiet) ferror.show(e);
1096                                         return 0;
1097                                 }
1098                                 if ((t = _v_iptip(e, v[i], 1)) == null) {
1099                                         ferror.set(e, e._error_msg + ', or invalid domain name', quiet);
1100                                         return 0;
1101                                 }
1102 /* IPV6-BEGIN */
1103                         }
1104 /* IPV6-END */
1105                 }
1106                 v[i] = t;
1107         }
1109         e.value = v.join(', ');
1110         ferror.clear(e);
1111         return 1;
1114 function v_iptaddr(e, quiet, multi)
1116         return _v_iptaddr(e, quiet, multi, 1, 0);
1119 function _v_hostname(e, h, quiet, required, multi, delim, cidr)
1121         var s;
1122         var v, i;
1123         var re;
1125         v = (typeof(delim) == 'undefined') ? h.split(/\s+/) : h.split(delim);
1127         if (multi) {
1128                 if (v.length > multi) {
1129                         ferror.set(e, 'Too many hostnames.', quiet);
1130                         return null;
1131                 }
1132         }
1133         else {
1134                 if (v.length > 1) {
1135                         ferror.set(e, 'Invalid hostname.', quiet);
1136                         return null;
1137                 }
1138         }
1140         re = /^[a-zA-Z0-9](([a-zA-Z0-9\-]{0,61})[a-zA-Z0-9]){0,1}$/;
1142         for (i = 0; i < v.length; ++i) {
1143                 s = v[i].replace(/_+/g, '-').replace(/\s+/g, '-');
1144                 if (s.length > 0) {
1145                         if (cidr && i == v.length-1)
1146                                 re = /^[a-zA-Z0-9](([a-zA-Z0-9\-]{0,61})[a-zA-Z0-9]){0,1}(\/\d{1,3})?$/;
1147                         if (s.search(re) == -1 || s.search(/^\d+$/) != -1) {
1148                                 ferror.set(e, 'Invalid hostname. Only "A-Z 0-9" and "-" in the middle are allowed (up to 63 characters).', quiet);
1149                                 return null;
1150                         }
1151                 } else if (required) {
1152                         ferror.set(e, 'Invalid hostname.', quiet);
1153                         return null;
1154                 }
1155                 v[i] = s;
1156         }
1158         ferror.clear(e);
1159         return v.join((typeof(delim) == 'undefined') ? ' ' : delim);
1162 function v_hostname(e, quiet, multi, delim)
1164         var v;
1166         if ((e = E(e)) == null) return 0;
1168         v = _v_hostname(e, e.value, quiet, 0, multi, delim, false);
1170         if (v == null) return 0;
1172         e.value = v;
1173         return 1;
1176 function v_nodelim(e, quiet, name, checklist)
1178         if ((e = E(e)) == null) return 0;
1180         e.value = e.value.trim();
1181         if (e.value.indexOf('<') != -1 ||
1182            (checklist && e.value.indexOf('>') != -1)) {
1183                 ferror.set(e, 'Invalid ' + name + ': \"<\" ' + (checklist ? 'or \">\" are' : 'is') + ' not allowed.', quiet);
1184                 return 0;
1185         }
1186         ferror.clear(e);
1187         return 1;
1190 function v_path(e, quiet, required)
1192         if ((e = E(e)) == null) return 0;
1193         if (required && !v_length(e, quiet, 1)) return 0;
1195         if (!required && e.value.trim().length == 0) {
1196                 ferror.clear(e);
1197                 return 1;
1198         }
1199         if (e.value.substr(0, 1) != '/') {
1200                 ferror.set(e, 'Please start at the / root directory.', quiet);
1201                 return 0;
1202         }
1203         ferror.clear(e);
1204         return 1;
1207 function isMAC0(mac)
1209         return (mac == '00:00:00:00:00:00');
1212 // -----------------------------------------------------------------------------
1214 function cmpIP(a, b)
1216         if ((a = fixIP(a)) == null) a = '255.255.255.255';
1217         if ((b = fixIP(b)) == null) b = '255.255.255.255';
1218         return aton(a) - aton(b);
1221 function cmpText(a, b)
1223         if (a == '') a = '\xff';
1224         if (b == '') b = '\xff';
1225         return (a < b) ? -1 : ((a > b) ? 1 : 0);
1228 function cmpInt(a, b)
1230         a = parseInt(a, 10);
1231         b = parseInt(b, 10);
1232         return ((isNaN(a)) ? -0x7FFFFFFF : a) - ((isNaN(b)) ? -0x7FFFFFFF : b);
1235 function cmpFloat(a, b)
1237         a = parseFloat(a);
1238         b = parseFloat(b);
1239         return ((isNaN(a)) ? -Number.MAX_VALUE : a) - ((isNaN(b)) ? -Number.MAX_VALUE : b);
1242 function cmpDate(a, b)
1244         return b.getTime() - a.getTime();
1247 // -----------------------------------------------------------------------------
1249 // ---- todo: cleanup this mess
1251 function TGO(e)
1253         return elem.parentElem(e, 'TABLE').gridObj;
1256 function tgHideIcons()
1258         var e;
1259         while ((e = document.getElementById('tg-row-panel')) != null) e.parentNode.removeChild(e);
1262 // ---- options = sort, move, delete
1263 function TomatoGrid(tb, options, maxAdd, editorFields)
1265         this.init(tb, options, maxAdd, editorFields);
1266         return this;
1269 TomatoGrid.prototype = {
1270         init: function(tb, options, maxAdd, editorFields) {
1271                 if (tb) {
1272                         this.tb = E(tb);
1273                         this.tb.gridObj = this;
1274                 }
1275                 else {
1276                         this.tb = null;
1277                 }
1278                 if (!options) options = '';
1279                 this.header = null;
1280                 this.footer = null;
1281                 this.editor = null;
1282                 this.canSort = options.indexOf('sort') != -1;
1283                 this.canMove = options.indexOf('move') != -1;
1284                 this.maxAdd = maxAdd || 500;
1285                 this.canEdit = (editorFields != null);
1286                 this.canDelete = this.canEdit || (options.indexOf('delete') != -1);
1287                 this.editorFields = editorFields;
1288                 this.sortColumn = -1;
1289                 this.sortAscending = true;
1290         },
1292         _insert: function(at, cells, escCells) {
1293                 var tr, td, c;
1294                 var i, t;
1296                 tr = this.tb.insertRow(at);
1297                 for (i = 0; i < cells.length; ++i) {
1298                         c = cells[i];
1299                         if (typeof(c) == 'string') {
1300                                 td = tr.insertCell(i);
1301                                 td.className = 'co' + (i + 1);
1302                                 if (escCells) td.appendChild(document.createTextNode(c));
1303                                         else td.innerHTML = c;
1304                         }
1305                         else {
1306                                 tr.appendChild(c);
1307                         }
1308                 }
1309                 return tr;
1310         },
1312         // ---- header
1314         headerClick: function(cell) {
1315                 if (this.canSort) {
1316                         this.sort(cell.cellN);
1317                 }
1318         },
1320         headerSet: function(cells, escCells) {
1321                 var e, i;
1323                 elem.remove(this.header);
1324                 this.header = e = this._insert(0, cells, escCells);
1325                 e.className = 'header';
1327                 for (i = 0; i < e.cells.length; ++i) {
1328                         e.cells[i].cellN = i;   // cellIndex broken in Safari
1329                         e.cells[i].onclick = function() { return TGO(this).headerClick(this); };
1330                 }
1331                 return e;
1332         },
1334         // ---- footer
1336         footerClick: function(cell) {
1337         },
1339         footerSet: function(cells, escCells) {
1340                 var e, i;
1342                 elem.remove(this.footer);
1343                 this.footer = e = this._insert(-1, cells, escCells);
1344                 e.className = 'footer';
1345                 for (i = 0; i < e.cells.length; ++i) {
1346                         e.cells[i].cellN = i;
1347                         e.cells[i].onclick = function() { TGO(this).footerClick(this) };
1348                 }
1349                 return e;
1350         },
1352         // ----
1354         rpUp: function(e) {
1355                 var i;
1357                 e = PR(e);
1358                 TGO(e).moving = null;
1359                 i = e.previousSibling;
1360                 if (i == this.header) return;
1361                 e.parentNode.removeChild(e);
1362                 i.parentNode.insertBefore(e, i);
1364                 this.recolor();
1365                 this.rpHide();
1366         },
1368         rpDn: function(e) {
1369                 var i;
1371                 e = PR(e);
1372                 TGO(e).moving = null;
1373                 i = e.nextSibling;
1374                 if (i == this.footer) return;
1375                 e.parentNode.removeChild(e);
1376                 i.parentNode.insertBefore(e, i.nextSibling);
1378                 this.recolor();
1379                 this.rpHide();
1380         },
1382         rpMo: function(img, e) {
1383                 var me;
1385                 e = PR(e);
1386                 me = TGO(e);
1387                 if (me.moving == e) {
1388                         me.moving = null;
1389                         this.rpHide();
1390                         return;
1391                 }
1392                 me.moving = e;
1393                 img.style.border = "1px dotted red";
1394         },
1396         rpDel: function(e) {
1397                 e = PR(e);
1398                 TGO(e).moving = null;
1399                 e.parentNode.removeChild(e);
1400                 this.recolor();
1401                 this.rpHide();
1402         },
1404         rpMouIn: function(evt) {
1405                 var e, x, ofs, me, s, n;
1407                 if ((evt = checkEvent(evt)) == null) return;
1409                 me = TGO(evt.target);
1410                 if (me.isEditing()) return;
1411                 if (me.moving) return;
1413                 me.rpHide();
1414                 e = document.createElement('div');
1415                 e.tgo = me;
1416                 e.ref = evt.target;
1417                 e.setAttribute('id', 'tg-row-panel');
1419                 n = 0;
1420                 s = '';
1421                 if (me.canMove) {
1422                         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">';
1423                         n += 3;
1424                 }
1425                 if (me.canDelete) {
1426                         s += '<img src="rpx.gif" onclick="this.parentNode.tgo.rpDel(this.parentNode.ref)" title="Delete">';
1427                         ++n;
1428                 }
1429                 x = PR(evt.target);
1430                 x = x.cells[x.cells.length - 1];
1431                 ofs = elem.getOffset(x);
1432                 n *= 18;
1433                 e.style.left = (ofs.x + x.offsetWidth - n) + 'px';
1434                 e.style.top = ofs.y + 'px';
1435                 e.style.width = n + 'px';
1436                 e.innerHTML = s;
1438                 document.body.appendChild(e);
1439         },
1441         rpHide: tgHideIcons,
1443         // ----
1445         onClick: function(cell) {
1446                 if (this.canEdit) {
1447                         if (this.moving) {
1448                                 var p = this.moving.parentNode;
1449                                 var q = PR(cell);
1450                                 if (this.moving != q) {
1451                                         var v = this.moving.rowIndex > q.rowIndex;
1452                                         p.removeChild(this.moving);
1453                                         if (v) p.insertBefore(this.moving, q);
1454                                                 else p.insertBefore(this.moving, q.nextSibling);
1455                                         this.recolor();
1456                                 }
1457                                 this.moving = null;
1458                                 this.rpHide();
1459                                 return;
1460                         }
1461                         this.edit(cell);
1462                 }
1463         },
1465         insert: function(at, data, cells, escCells) {
1466                 var e, i;
1468                 if ((this.footer) && (at == -1)) at = this.footer.rowIndex;
1469                 e = this._insert(at, cells, escCells);
1470                 e.className = (e.rowIndex & 1) ? 'even' : 'odd';
1472                 for (i = 0; i < e.cells.length; ++i) {
1473                         e.cells[i].onclick = function() { return TGO(this).onClick(this); };
1474                 }
1476                 e._data = data;
1477                 e.getRowData = function() { return this._data; }
1478                 e.setRowData = function(data) { this._data = data; }
1480                 if ((this.canMove) || (this.canEdit) || (this.canDelete)) {
1481                         e.onmouseover = this.rpMouIn;
1482 // ----                 e.onmouseout = this.rpMouOut;
1483                         if (this.canEdit) e.title = 'Click to edit';
1484                 }
1486                 return e;
1487         },
1489         // ----
1491         insertData: function(at, data) {
1492                 return this.insert(at, data, this.dataToView(data), false);
1493         },
1495         dataToView: function(data) {
1496                 var v = [];
1497                 for (var i = 0; i < data.length; ++i) {
1498                         var s = escapeHTML('' + data[i]);
1499                         if (this.editorFields && this.editorFields.length > i) {
1500                                 var ef = this.editorFields[i].multi;
1501                                 if (!ef) ef = [this.editorFields[i]];
1502                                 var f = (ef && ef.length > 0 ? ef[0] : null);
1503                                 if (f && f.type == 'password') {
1504                                         if (!f.peekaboo || get_config('web_pb', '1') != '0')
1505                                                 s = s.replace(/./g, '&#x25CF;');
1506                                 }
1507                         }
1508                         v.push(s);
1509                 }
1510                 return v;
1511         },
1513         dataToFieldValues: function(data) {
1514                 return data;
1515         },
1517         fieldValuesToData: function(row) {
1518                 var e, i, data;
1520                 data = [];
1521                 e = fields.getAll(row);
1522                 for (i = 0; i < e.length; ++i) data.push(e[i].value);
1523                 return data;
1524         },
1526         // ----
1528         edit: function(cell) {
1529                 var sr, er, e, c;
1531                 if (this.isEditing()) return;
1533                 sr = PR(cell);
1534                 sr.style.display = 'none';
1535                 elem.removeClass(sr, 'hover');
1536                 this.source = sr;
1538                 er = this.createEditor('edit', sr.rowIndex, sr);
1539                 er.className = 'editor';
1540                 this.editor = er;
1542                 c = er.cells[cell.cellIndex || 0];
1543                 e = c.getElementsByTagName('input');
1544                 if ((e) && (e.length > 0)) {
1545                         try {   // IE quirk
1546                                 e[0].focus();
1547                         }
1548                         catch (ex) {
1549                         }
1550                 }
1552                 this.controls = this.createControls('edit', sr.rowIndex);
1554                 this.disableNewEditor(true);
1555                 this.rpHide();
1556                 this.verifyFields(this.editor, true);
1557         },
1559         createEditor: function(which, rowIndex, source) {
1560                 var values;
1562                 if (which == 'edit') values = this.dataToFieldValues(source.getRowData());
1564                 var row = this.tb.insertRow(rowIndex);
1565                 row.className = 'editor';
1567                 var common = ' onkeypress="return TGO(this).onKey(\'' + which + '\', event)" onchange="TGO(this).onChange(\'' + which + '\', this)"';
1569                 var vi = 0;
1570                 for (var i = 0; i < this.editorFields.length; ++i) {
1571                         var s = '';
1572                         var ef = this.editorFields[i].multi;
1573                         if (!ef) ef = [this.editorFields[i]];
1575                         for (var j = 0; j < ef.length; ++j) {
1576                                 var f = ef[j];
1578                                 if (f.prefix) s += f.prefix;
1579                                 var attrib = ' class="fi' + (vi + 1) + '" ' + (f.attrib || '');
1580                                 var id = (this.tb ? ('_' + this.tb + '_' + (vi + 1)) : null);
1581                                 if (id) attrib += ' id="' + id + '"';
1582                                 switch (f.type) {
1583                                 case 'password':
1584                                         if (f.peekaboo) {
1585                                                 switch (get_config('web_pb', '1')) {
1586                                                 case '0':
1587                                                         f.type = 'text';
1588                                                 case '2':
1589                                                         f.peekaboo = 0;
1590                                                         break;
1591                                                 }
1592                                         }
1593                                         attrib += ' autocomplete="off"';
1594                                         if (f.peekaboo && id) attrib += ' onfocus=\'peekaboo("' + id + '",1)\'';
1595                                         // drop
1596                                 case 'text':
1597                                         s += '<input type="' + f.type + '" maxlength=' + f.maxlen + common + attrib;
1598                                         if (which == 'edit') s += ' value="' + escapeHTML('' + values[vi]) + '">';
1599                                                 else s += '>';
1600                                         break;
1601                                 case 'clear':
1602                                         s += '';
1603                                         break;
1604                                 case 'select':
1605                                         s += '<select' + common + attrib + '>';
1606                                         for (var k = 0; k < f.options.length; ++k) {
1607                                                 a = f.options[k];
1608                                                 if (which == 'edit') {
1609                                                         s += '<option value="' + a[0] + '"' + ((a[0] == values[vi]) ? ' selected>' : '>') + a[1] + '</option>';
1610                                                 }
1611                                                 else {
1612                                                         s += '<option value="' + a[0] + '">' + a[1] + '</option>';
1613                                                 }
1614                                         }
1615                                         s += '</select>';
1616                                         break;
1617                                 case 'checkbox':
1618                                         s += '<input type="checkbox"' + common + attrib;
1619                                         if ((which == 'edit') && (values[vi])) s += ' checked';
1620                                         s += '>';
1621                                         break;
1622                                 case 'textarea':
1623                                         if (which == 'edit'){
1624                                                 document.getElementById(f.proxy).value = values[vi];
1625                                         }
1626                                         break;
1627                                 default:
1628                                         s += f.custom.replace(/\$which\$/g, which);
1629                                 }
1630                                 if (f.suffix) s += f.suffix;
1632                                 ++vi;
1633                         }
1634                         if(this.editorFields[i].type != 'textarea'){
1635                                 var c = row.insertCell(i);
1636                                 c.innerHTML = s;
1637                                 if (this.editorFields[i].vtop) c.vAlign = 'top';
1638                         }
1639                 }
1641                 return row;
1642         },
1644         createControls: function(which, rowIndex) {
1645                 var r, c;
1647                 r = this.tb.insertRow(rowIndex);
1648                 r.className = 'controls';
1650                 c = r.insertCell(0);
1651                 c.colSpan = this.header.cells.length;
1652                 if (which == 'edit') {
1653                         c.innerHTML =
1654                                 '<input type=button value="Delete" onclick="TGO(this).onDelete()"> &nbsp; ' +
1655                                 '<input type=button value="OK" onclick="TGO(this).onOK()"> ' +
1656                                 '<input type=button value="Cancel" onclick="TGO(this).onCancel()">';
1657                 }
1658                 else {
1659                         c.innerHTML =
1660                                 '<input type=button value="Add" onclick="TGO(this).onAdd()">';
1661                 }
1662                 return r;
1663         },
1665         removeEditor: function() {
1666                 if (this.editor) {
1668                         elem.remove(this.editor);
1669                         this.editor = null;
1670                 }
1671                 if (this.controls) {
1672                         elem.remove(this.controls);
1673                         this.controls = null;
1674                 }
1675         },
1677         showSource: function() {
1678                 if (this.source) {
1679                         this.source.style.display = '';
1680                         this.source = null;
1681                 }
1682         },
1684         onChange: function(which, cell) {
1685                 return this.verifyFields((which == 'new') ? this.newEditor : this.editor, true);
1686         },
1688         onKey: function(which, ev) {
1689                 switch (ev.keyCode) {
1690                 case 27:
1691                         if (which == 'edit') this.onCancel();
1692                         return false;
1693                 case 13:
1694                         if (((ev.srcElement) && (ev.srcElement.tagName == 'SELECT')) ||
1695                                 ((ev.target) && (ev.target.tagName == 'SELECT'))) return true;
1696                         if (which == 'edit') this.onOK();
1697                                 else this.onAdd();
1698                         return false;
1699                 }
1700                 return true;
1701         },
1703         onDelete: function() {
1704                 this.removeEditor();
1705                 elem.remove(this.source);
1706                 this.source = null;
1707                 this.disableNewEditor(false);
1708                 this.clearTextarea();
1709         },
1711         onCancel: function() {
1712                 this.removeEditor();
1713                 this.showSource();
1714                 this.disableNewEditor(false);
1715                 this.clearTextarea();
1716         },
1718         onOK: function() {
1719                 var i, data, view;
1721                 if (!this.verifyFields(this.editor, false)) return;
1723                 data = this.fieldValuesToData(this.editor);
1724                 view = this.dataToView(data);
1726                 this.source.setRowData(data);
1727                 for (i = 0; i < this.source.cells.length; ++i) {
1728                         this.source.cells[i].innerHTML = view[i];
1729                 }
1731                 this.removeEditor();
1732                 this.showSource();
1733                 this.disableNewEditor(false);
1734                 this.clearTextarea();
1735         },
1737         onAdd: function() {
1738                 var data;
1740                 this.moving = null;
1741                 this.rpHide();
1743                 if (!this.verifyFields(this.newEditor, false)) return;
1745                 data = this.fieldValuesToData(this.newEditor);
1746                 this.insertData(-1, data);
1748                 this.disableNewEditor(false);
1749                 this.resetNewEditor();
1750         },
1752         clearTextarea: function() {
1753                 for (var i = 0; i < this.editorFields.length; ++i){
1754                         if(this.editorFields[i].type == 'textarea'){
1755                                 document.getElementById(this.editorFields[i].proxy).value = '';
1756                                 ferror.clear(document.getElementById(this.editorFields[i].proxy));
1757                         }
1758                 }
1759         },
1761         verifyFields: function(row, quiet) {
1762                 return true;
1763         },
1765         showNewEditor: function() {
1766                 var r;
1768                 r = this.createEditor('new', -1, null);
1769                 this.footer = this.newEditor = r;
1771                 r = this.createControls('new', -1);
1772                 this.newControls = r;
1774                 this.disableNewEditor(false);
1775         },
1777         disableNewEditor: function(disable) {
1778                 if (this.getDataCount() >= this.maxAdd) disable = true;
1779                 if (this.newEditor) fields.disableAll(this.newEditor, disable);
1780                 if (this.newControls) fields.disableAll(this.newControls, disable);
1781         },
1783         resetNewEditor: function() {
1784                 var i, e;
1786                 e = fields.getAll(this.newEditor);
1787                 ferror.clearAll(e);
1788                 for (i = 0; i < e.length; ++i) {
1789                         var f = e[i];
1790                         if (f.selectedIndex) f.selectedIndex = 0;
1791                                 else f.value = '';
1792                 }
1793                 try { if (e.length) e[0].focus(); } catch (er) { }
1794         },
1796         getDataCount: function() {
1797                 var n;
1798                 n = this.tb.rows.length;
1799                 if (this.footer) n = this.footer.rowIndex;
1800                 if (this.header) n -= this.header.rowIndex + 1;
1801                 return n;
1802         },
1804         sortCompare: function(a, b) {
1805                 var obj = TGO(a);
1806                 var col = obj.sortColumn;
1807                 var r = cmpText(a.cells[col].innerHTML, b.cells[col].innerHTML);
1808                 return obj.sortAscending ? r : -r;
1809         },
1811         sort: function(column) {
1812                 if (this.editor) return;
1814                 if (this.sortColumn >= 0) {
1815                         elem.removeClass(this.header.cells[this.sortColumn], 'sortasc', 'sortdes');
1816                 }
1817                 if (column == this.sortColumn) {
1818                         this.sortAscending = !this.sortAscending;
1819                 }
1820                 else {
1821                         this.sortAscending = true;
1822                         this.sortColumn = column;
1823                 }
1824                 elem.addClass(this.header.cells[column], this.sortAscending ? 'sortasc' : 'sortdes');
1826                 this.resort();
1827         },
1829         resort: function() {
1830                 if ((this.sortColumn < 0) || (this.getDataCount() == 0) || (this.editor)) return;
1832                 var p = this.header.parentNode;
1833                 var a = [];
1834                 var i, j, max, e, p;
1835                 var top;
1837                 this.moving = null;
1839                 top = this.header ? this.header.rowIndex + 1 : 0;
1840                 max = this.footer ? this.footer.rowIndex : this.tb.rows.length;
1841                 for (i = top; i < max; ++i) a.push(p.rows[i]);
1842                 a.sort(THIS(this, this.sortCompare));
1843                 this.removeAllData();
1844                 j = top;
1845                 for (i = 0; i < a.length; ++i) {
1846                         e = p.insertBefore(a[i], this.footer);
1847                         e.className = (j & 1) ? 'even' : 'odd';
1848                         ++j;
1849                 }
1850         },
1852         recolor: function() {
1853                  var i, e, o;
1855                  i = this.header ? this.header.rowIndex + 1 : 0;
1856                  e = this.footer ? this.footer.rowIndex : this.tb.rows.length;
1857                  for (; i < e; ++i) {
1858                          o = this.tb.rows[i];
1859                          o.className = (o.rowIndex & 1) ? 'even' : 'odd';
1860                  }
1861         },
1863         removeAllData: function() {
1864                 var i, count;
1866                 i = this.header ? this.header.rowIndex + 1 : 0;
1867                 count = (this.footer ? this.footer.rowIndex : this.tb.rows.length) - i;
1868                 while (count-- > 0) elem.remove(this.tb.rows[i]);
1869         },
1871         getAllData: function() {
1872                 var i, max, data, r;
1874                 data = [];
1875                 max = this.footer ? this.footer.rowIndex : this.tb.rows.length;
1876                 for (i = this.header ? this.header.rowIndex + 1 : 0; i < max; ++i) {
1877                         r = this.tb.rows[i];
1878                         if ((r.style.display != 'none') && (r._data)) data.push(r._data);
1879                 }
1880                 return data;
1881         },
1883         isEditing: function() {
1884                 return (this.editor != null);
1885         }
1889 // -----------------------------------------------------------------------------
1892 function xmlHttpObj()
1894         var ob;
1895         try {
1896                 ob = new XMLHttpRequest();
1897                 if (ob) return ob;
1898         }
1899         catch (ex) { }
1900         try {
1901                 ob = new ActiveXObject('Microsoft.XMLHTTP');
1902                 if (ob) return ob;
1903         }
1904         catch (ex) { }
1905         return null;
1908 var _useAjax = -1;
1909 var _holdAjax = null;
1911 function useAjax()
1913         if (_useAjax == -1) _useAjax = ((_holdAjax = xmlHttpObj()) != null);
1914         return _useAjax;
1917 function XmlHttp()
1919         if ((!useAjax()) || ((this.xob = xmlHttpObj()) == null)) return null;
1920         return this;
1923 XmlHttp.prototype = {
1924         addId: function(vars) {
1925                 if (vars) vars += '&';
1926                         else vars = '';
1927                 vars += '_http_id=' + escapeCGI(nvram.http_id);
1928                 return vars;
1929         },
1931         get: function(url, vars) {
1932                 try {
1933                         vars = this.addId(vars);
1934                         url += '?' + vars;
1936                         this.xob.onreadystatechange = THIS(this, this.onReadyStateChange);
1937                         this.xob.open('GET', url, true);
1938                         this.xob.send(null);
1939                 }
1940                 catch (ex) {
1941                         this.onError(ex);
1942                 }
1943         },
1945         post: function(url, vars) {
1946                 try {
1947                         vars = this.addId(vars);
1949                         this.xob.onreadystatechange = THIS(this, this.onReadyStateChange);
1950                         this.xob.open('POST', url, true);
1951                         this.xob.send(vars);
1952                 }
1953                 catch (ex) {
1954                         this.onError(ex);
1955                 }
1956         },
1958         abort: function() {
1959                 try {
1960                         this.xob.onreadystatechange = function () { }
1961                         this.xob.abort();
1962                 }
1963                 catch (ex) {
1964                 }
1965         },
1967         onReadyStateChange: function() {
1968                 try {
1969                         if (typeof(E) == 'undefined') return;   // oddly late? testing for bug...
1971                         if (this.xob.readyState == 4) {
1972                                 if (this.xob.status == 200) {
1973                                         this.onCompleted(this.xob.responseText, this.xob.responseXML);
1974                                 }
1975                                 else {
1976                                         this.onError('' + (this.xob.status || 'unknown'));
1977                                 }
1978                         }
1979                 }
1980                 catch (ex) {
1981                         this.onError(ex);
1982                 }
1983         },
1985         onCompleted: function(text, xml) { },
1986         onError: function(ex) { }
1990 // -----------------------------------------------------------------------------
1993 function TomatoTimer(func, ms)
1995         this.tid = null;
1996         this.onTimer = func;
1997         if (ms) this.start(ms);
1998         return this;
2001 TomatoTimer.prototype = {
2002         start: function(ms) {
2003                 this.stop();
2004                 this.tid = setTimeout(THIS(this, this._onTimer), ms);
2005         },
2006         stop: function() {
2007                 if (this.tid) {
2008                         clearTimeout(this.tid);
2009                         this.tid = null;
2010                 }
2011         },
2013         isRunning: function() {
2014                 return (this.tid != null);
2015         },
2017         _onTimer: function() {
2018                 this.tid = null;
2019                 this.onTimer();
2020         },
2022         onTimer: function() {
2023         }
2027 // -----------------------------------------------------------------------------
2030 function TomatoRefresh(actionURL, postData, refreshTime, cookieTag)
2032         this.setup(actionURL, postData, refreshTime, cookieTag);
2033         this.timer = new TomatoTimer(THIS(this, this.start));
2036 TomatoRefresh.prototype = {
2037         running: 0,
2039         setup: function(actionURL, postData, refreshTime, cookieTag) {
2040                 var e, v;
2042                 this.actionURL = actionURL;
2043                 this.postData = postData;
2044                 this.refreshTime = refreshTime * 1000;
2045                 this.cookieTag = cookieTag;
2046         },
2048         start: function() {
2049                 var e;
2051                 if ((e = E('refresh-time')) != null) {
2052                         if (this.cookieTag) cookie.set(this.cookieTag, e.value);
2053                         this.refreshTime = e.value * 1000;
2054                 }
2055                 e = undefined;
2057                 this.updateUI('start');
2059                 this.running = 1;
2060                 if ((this.http = new XmlHttp()) == null) {
2061                         reloadPage();
2062                         return;
2063                 }
2065                 this.http.parent = this;
2067                 this.http.onCompleted = function(text, xml) {
2068                         var p = this.parent;
2070                         if (p.cookieTag) cookie.unset(p.cookieTag + '-error');
2071                         if (!p.running) {
2072                                 p.stop();
2073                                 return;
2074                         }
2076                         p.refresh(text);
2078                         if ((p.refreshTime > 0) && (!p.once)) {
2079                                 p.updateUI('wait');
2080                                 p.timer.start(Math.round(p.refreshTime));
2081                         }
2082                         else {
2083                                 p.stop();
2084                         }
2086                         p.errors = 0;
2087                 }
2089                 this.http.onError = function(ex) {
2090                         var p = this.parent;
2091                         if ((!p) || (!p.running)) return;
2093                         p.timer.stop();
2095                         if (++p.errors <= 3) {
2096                                 p.updateUI('wait');
2097                                 p.timer.start(3000);
2098                                 return;
2099                         }
2101                         if (p.cookieTag) {
2102                                 var e = cookie.get(p.cookieTag + '-error') * 1;
2103                                 if (isNaN(e)) e = 0;
2104                                         else ++e;
2105                                 cookie.unset(p.cookieTag);
2106                                 cookie.set(p.cookieTag + '-error', e, 1);
2107                                 if (e >= 3) {
2108                                         alert('XMLHTTP: ' + ex);
2109                                         return;
2110                                 }
2111                         }
2113                         setTimeout(reloadPage, 2000);
2114                 }
2116                 this.errors = 0;
2117                 this.http.post(this.actionURL, this.postData);
2118         },
2120         stop: function() {
2121                 if (this.cookieTag) cookie.set(this.cookieTag, -(this.refreshTime / 1000));
2122                 this.running = 0;
2123                 this.updateUI('stop');
2124                 this.timer.stop();
2125                 this.http = null;
2126                 this.once = undefined;
2127         },
2129         toggle: function(delay) {
2130                 if (this.running) this.stop();
2131                         else this.start(delay);
2132         },
2134         updateUI: function(mode) {
2135                 var e, b;
2137                 if (typeof(E) == 'undefined') return;   // for a bizzare bug...
2139                 b = (mode != 'stop') && (this.refreshTime > 0);
2140                 if ((e = E('refresh-button')) != null) {
2141                         e.value = b ? 'Stop' : 'Refresh';
2142                         e.disabled = ((mode == 'start') && (!b));
2143                 }
2144                 if ((e = E('refresh-time')) != null) e.disabled = b;
2145                 if ((e = E('refresh-spinner')) != null) e.style.visibility = b ? 'visible' : 'hidden';
2146         },
2148         initPage: function(delay, def) {
2149                 var e, v;
2151                 e = E('refresh-time');
2152                 if (((this.cookieTag) && (e != null)) &&
2153                         ((v = cookie.get(this.cookieTag)) != null) && (!isNaN(v *= 1))) {
2154                         e.value = Math.abs(v);
2155                         if (v > 0) v = (v * 1000) + (delay || 0);
2156                 }
2157                 else if (def) {
2158                         v = def;
2159                         if (e) e.value = def;
2160                 }
2161                 else v = 0;
2163                 if (delay < 0) {
2164                         v = -delay;
2165                         this.once = 1;
2166                 }
2168                 if (v > 0) {
2169                         this.running = 1;
2170                         this.refreshTime = v;
2171                         this.timer.start(v);
2172                         this.updateUI('wait');
2173                 }
2174         }
2177 function genStdTimeList(id, zero, min)
2179         var b = [];
2180         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];
2181         var i, v;
2183         if (min >= 0) {
2184                 b.push('<select id="' + id + '"><option value=0>' + zero);
2185                 for (i = 0; i < t.length; ++i) {
2186                         v = t[i];
2187                         if (v < min) continue;
2188                         b.push('<option value=' + v + '>');
2189                         if (v == 60) b.push('1 minute');
2190                                 else if (v > 60) b.push((v / 60) + ' minutes');
2191                                 else b.push(v + ' seconds');
2192                 }
2193                 b.push('</select> ');
2194         }
2195         document.write(b.join(''));
2198 function genStdRefresh(spin, min, exec)
2200         W('<div style="text-align:right">');
2201         if (spin) W('<img src="spin.gif" id="refresh-spinner"> ');
2202         genStdTimeList('refresh-time', 'Auto Refresh', min);
2203         W('<input type="button" value="Refresh" onclick="' + (exec ? exec : 'refreshClick()') + '" id="refresh-button"></div>');
2207 // -----------------------------------------------------------------------------
2210 function _tabCreate(tabs)
2212         var buf = [];
2213         buf.push('<ul id="tabs">');
2214         for (var i = 0; i < arguments.length; ++i)
2215                 buf.push('<li><a href="javascript:tabSelect(\'' + arguments[i][0] + '\')" id="' + arguments[i][0] + '">' + arguments[i][1] + '</a>');
2216         buf.push('</ul><div id="tabs-bottom"></div>');
2217         return buf.join('');
2220 function tabCreate(tabs)
2222         document.write(_tabCreate.apply(this, arguments));
2225 function tabHigh(id)
2227         var a = E('tabs').getElementsByTagName('A');
2228         for (var i = 0; i < a.length; ++i) {
2229                 if (id != a[i].id) elem.removeClass(a[i], 'active');
2230         }
2231         elem.addClass(id, 'active');
2234 // -----------------------------------------------------------------------------
2236 var cookie = {
2237         // The value 2147483647000 is ((2^31)-1)*1000, which is the number of
2238         // milliseconds (minus 1 second) which correlates with the year 2038 counter
2239         // rollover. This effectively makes the cookie never expire.
2241         set: function(key, value, days) {
2242                 document.cookie = 'tomato_' + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '; expires=' +
2243                 new Date(2147483647000).toUTCString() + '; path=/';
2244         },
2245         get: function(key) {
2246                 var r = ('; ' + document.cookie + ';').match('; tomato_' + encodeURIComponent(key) + '=(.*?);');
2247                 return r ? decodeURIComponent(r[1]) : null;
2248         },
2249         unset: function(key) {
2250                 document.cookie = 'tomato_' + encodeURIComponent(key) + '=; expires=' +
2251                 (new Date(1)).toUTCString() + '; path=/';
2252         }
2255 // -----------------------------------------------------------------------------
2257 function checkEvent(evt)
2259         if (typeof(evt) == 'undefined') {
2260                 // ---- IE
2261                 evt = event;
2262                 evt.target = evt.srcElement;
2263                 evt.relatedTarget = evt.toElement;
2264         }
2265         return evt;
2268 function W(s)
2270         document.write(s);
2273 function E(e)
2275         return (typeof(e) == 'string') ? document.getElementById(e) : e;
2278 function PR(e)
2280         return elem.parentElem(e, 'TR');
2283 function THIS(obj, func)
2285         return function() { return func.apply(obj, arguments); }
2288 function UT(v)
2290         return (typeof(v) == 'undefined') ? '' : '' + v;
2293 function escapeHTML(s)
2295         function esc(c) {
2296                 return '&#' + c.charCodeAt(0) + ';';
2297         }
2298         return s.replace(/[&"'<>\r\n]/g, esc);
2301 function escapeCGI(s)
2303         return escape(s).replace(/\+/g, '%2B'); // escape() doesn't handle +
2306 function escapeD(s)
2308         function esc(c) {
2309                 return '%' + c.charCodeAt(0).hex(2);
2310         }
2311         return s.replace(/[<>|%]/g, esc);
2314 function ellipsis(s, max) {
2315         return (s.length <= max) ? s : s.substr(0, max - 3) + '...';
2318 function MIN(a, b)
2320         return (a < b) ? a : b;
2323 function MAX(a, b)
2325         return (a > b) ? a : b;
2328 function fixInt(n, min, max, def)
2330         if (n === null) return def;
2331         n *= 1;
2332         if (isNaN(n)) return def;
2333         if (n < min) return min;
2334         if (n > max) return max;
2335         return n;
2338 function comma(n)
2340         n = '' + n;
2341         var p = n;
2342         while ((n = n.replace(/(\d+)(\d{3})/g, '$1,$2')) != p) p = n;
2343         return n;
2346 function doScaleSize(n, sm)
2348         if (isNaN(n *= 1)) return '-';
2349         if (n <= 9999) return '' + n;
2350         var s = -1;
2351         do {
2352                 n /= 1024;
2353                 ++s;
2354         } while ((n > 9999) && (s < 2));
2355         return comma(n.toFixed(2)) + (sm ? '<small> ' : ' ') + (['KB', 'MB', 'GB'])[s] + (sm ? '</small>' : '');
2358 function scaleSize(n)
2360         return doScaleSize(n, 1);
2363 function timeString(mins)
2365         var h = Math.floor(mins / 60);
2366         if ((new Date(2000, 0, 1, 23, 0, 0, 0)).toLocaleString().indexOf('23') != -1)
2367                 return h + ':' + (mins % 60).pad(2);
2368         return ((h == 0) ? 12 : ((h > 12) ? h - 12 : h)) + ':' + (mins % 60).pad(2) + ((h >= 12) ? ' PM' : ' AM');
2371 function features(s)
2373         var features = ['ses','brau','aoss','wham','hpamp','!nve','11n','1000et'];
2374         var i;
2376         for (i = features.length - 1; i >= 0; --i) {
2377                 if (features[i] == s) return (parseInt(nvram.t_features) & (1 << i)) != 0;
2378         }
2379         return 0;
2382 function get_config(name, def)
2384         return ((typeof(nvram) != 'undefined') && (typeof(nvram[name]) != 'undefined')) ? nvram[name] : def;
2387 function nothing()
2391 // -----------------------------------------------------------------------------
2393 function show_notice1(s)
2395 // ---- !!TB - USB Support: multi-line notices
2396         if (s.length) document.write('<div id="notice1">' + s.replace(/\n/g, '<br>') + '</div><br style="clear:both">');
2399 // -----------------------------------------------------------------------------
2401 function myName()
2403         var name, i;
2405         name = document.location.pathname;
2406         name = name.replace(/\\/g, '/');        // IE local testing
2407         if ((i = name.lastIndexOf('/')) != -1) name = name.substring(i + 1, name.length);
2408         if (name == '') name = 'status-overview.asp';
2409         return name;
2412 function navi()
2414         var menu = [
2415                 ['Status',                      'status', 0, [
2416                         ['Overview',                    'overview.asp'],
2417                         ['Device List',                 'devices.asp'],
2418                         ['Web Usage',                   'webmon.asp'],
2419                         ['Logs',                        'log.asp'] ] ],
2420                 ['Bandwidth',                   'bwm', 0, [
2421                         ['Real-Time',                   'realtime.asp'],
2422                         ['Last 24 Hours',               '24.asp'],
2423                         ['Daily',                       'daily.asp'],
2424                         ['Weekly',                      'weekly.asp'],
2425                         ['Monthly',                     'monthly.asp']
2426                         ] ],
2427                 ['IP Traffic',                  'ipt', 0, [
2428                         ['Real-Time',                   'realtime.asp'],
2429                         ['Last 24 Hours',               '24.asp'],
2430                         ['View Graphs',                 'graphs.asp'],
2431                         ['Transfer Rates',              'details.asp'],
2432                         ['Daily',                       'daily.asp'],
2433                         ['Monthly',                     'monthly.asp']
2434                         ] ],
2435                 ['Tools',                       'tools', 0, [
2436                         ['Ping',                        'ping.asp'],
2437                         ['Trace',                       'trace.asp'],
2438                         ['System Commands',             'shell.asp'],
2439                         ['Wireless Survey',             'survey.asp'],
2440                         ['WOL',                         'wol.asp'] ] ],
2441                 null,
2442                 ['Basic',                       'basic', 0, [
2443                         ['Network',                     'network.asp'],
2444 /* IPV6-BEGIN */
2445                         ['IPv6',                        'ipv6.asp'],
2446 /* IPV6-END */
2447                         ['Identification',              'ident.asp'],
2448                         ['Time',                        'time.asp'],
2449                         ['DDNS',                        'ddns.asp'],
2450                         ['Static DHCP/ARP/IPT',         'static.asp'],
2451                         ['Wireless Filter',             'wfilter.asp'] ] ],
2452                 ['Advanced',                    'advanced', 0, [
2453                         ['Conntrack/Netfilter',         'ctnf.asp'],
2454                         ['DHCP/DNS',                    'dhcpdns.asp'],
2455                         ['Firewall',                    'firewall.asp'],
2456                         ['MAC Address',                 'mac.asp'],
2457                         ['Miscellaneous',               'misc.asp'],
2458                         ['Routing',                     'routing.asp'],
2459 /* TOR-BEGIN */
2460                         ['TOR Project',                 'tor.asp'],
2461 /* TOR-END */
2462                         ['VLAN',                        'vlan.asp'],
2463                         ['LAN Access',                  'access.asp'],
2464                         ['Virtual Wireless',            'wlanvifs.asp'],
2465                         ['Wireless',                    'wireless.asp'] ] ],
2466                 ['Port Forwarding',             'forward', 0, [
2467                         ['Basic',                       'basic.asp'],
2468 /* IPV6-BEGIN */
2469                         ['Basic IPv6',                  'basic-ipv6.asp'],
2470 /* IPV6-END */
2471                         ['DMZ',                         'dmz.asp'],
2472                         ['Triggered',                   'triggered.asp'],
2473                         ['UPnP/NAT-PMP',                'upnp.asp'] ] ],
2474                 ['Access Restriction',          'restrict.asp'],
2475                 ['QoS',                         'qos', 0, [
2476                         ['Basic Settings',              'settings.asp'],
2477                         ['Classification',              'classify.asp'],
2478                         ['View Graphs',                 'graphs.asp'],
2479                         ['View Details',                'detailed.asp'],
2480                         ['Transfer Rates',              'ctrate.asp']
2481                         ] ],
2482                 ['Bandwidth Limiter',           'bwlimit.asp'],
2483                 null,
2484 /* NOCAT-BEGIN */
2485                 ['Captive Portal',              'splashd.asp'],
2486 /* NOCAT-END */
2487 /* NGINX-BEGIN */
2488                 ['Web Server',                  'web', 0, [
2489                         ['Nginx & PHP',         'nginx.asp'],
2490                         ['MySQL Server',        'mysql.asp']
2491                         ] ],
2492 /* NGINX-END */
2493 /* REMOVE-BEGIN
2494                 ['Scripts',                             'sc', 0, [
2495                         ['Startup',             'startup.asp'],
2496                         ['Shutdown',            'shutdown.asp'],
2497                         ['Firewall',            'firewall.asp'],
2498                         ['WAN Up',              'wanup.asp']
2499                         ] ],
2500 REMOVE-END */
2501 /* USB-BEGIN */
2502 // ---- !!TB - USB, FTP, Samba, Media Server
2503                 ['USB and NAS',                 'nas', 0, [
2504                         ['USB Support',                 'usb.asp']
2505 /* FTP-BEGIN */
2506                         ,['FTP Server',                 'ftp.asp']
2507 /* FTP-END */
2508 /* SAMBA-BEGIN */
2509                         ,['File Sharing',               'samba.asp']
2510 /* SAMBA-END */
2511 /* MEDIA-SRV-BEGIN */
2512                         ,['Media Server',               'media.asp']
2513 /* MEDIA-SRV-END */
2514 /* UPS-BEGIN */
2515                         ,['UPS Monitor',                'ups.asp']
2516 /* UPS-END */
2517 /* BT-BEGIN */
2518                         ,['BitTorrent Client',          'bittorrent.asp']
2519 /* BT-END */
2520                         ] ],
2521 /* USB-END */
2522 /* VPN-BEGIN */
2523                 ['VPN Tunneling',                       'vpn', 0, [
2524 /* OPENVPN-BEGIN */
2525                         ['OpenVPN Server',              'server.asp'],
2526                         ['OpenVPN Client',              'client.asp'],
2527 /* OPENVPN-END */
2528 /* PPTPD-BEGIN */
2529                         ['PPTP Server',                 'pptp-server.asp'],
2530                         ['PPTP Online',                 'pptp-online.asp'],
2531                         ['PPTP Client',                 'pptp.asp']
2532 /* PPTPD-END */
2533 /* TINC-BEGIN */
2534                         ,['Tinc Daemon',                'tinc.asp']
2535 /* TINC-END */
2536                 ] ],
2537 /* VPN-END */
2538                 null,
2539                 ['Administration',              'admin', 0, [
2540                         ['Admin Access',                'access.asp'],
2541                         ['Bandwidth Monitoring',        'bwm.asp'],
2542                         ['IP Traffic Monitoring',       'iptraffic.asp'],
2543                         ['Buttons/LED',                 'buttons.asp'],
2544 /* CIFS-BEGIN */
2545                         ['CIFS Client',                 'cifs.asp'],
2546 /* CIFS-END */
2547 /* SDHC-BEGIN */
2548                         ['SDHC/MMC',                    'sdhc.asp'],
2549 /* SDHC-END */
2550                         ['Configuration',               'config.asp'],
2551                         ['Debugging',                   'debug.asp'],
2552 /* JFFS2-BEGIN */
2553                         ['JFFS',                        'jffs2.asp'],
2554 /* JFFS2-END */
2555 /* NFS-BEGIN */
2556                         ['NFS Server',                  'nfs.asp'],
2557 /* NFS-END */
2558 /* SNMP-BEGIN */
2559                         ['SNMP',                        'snmp.asp'],
2560 /* SNMP-END */
2561                         ['Logging',                     'log.asp'],
2562                         ['Scheduler',                   'sched.asp'],
2563                         ['Scripts',                     'scripts.asp'],
2564                         ['Upgrade',                     'upgrade.asp'] ] ],
2565                 null,
2566                 ['About',                       'about.asp'],
2567                 ['Reboot...',                   'javascript:reboot()'],
2568                 ['Shutdown...',                 'javascript:shutdown()'],
2569                 ['Logout',                      'javascript:logout()']
2570         ];
2571         var name, base;
2572         var i, j;
2573         var buf = [];
2574         var sm;
2575         var a, b, c;
2576         var on1;
2577         var cexp = get_config('web_mx', '').toLowerCase();
2579         name = myName();
2580         if (name == 'restrict-edit.asp') name = 'restrict.asp';
2581         if ((i = name.indexOf('-')) != -1) {
2582                 base = name.substring(0, i);
2583                 name = name.substring(i + 1, name.length);
2584         }
2585         else base = '';
2587         for (i = 0; i < menu.length; ++i) {
2588                 var m = menu[i];
2589                 if (!m) {
2590                         buf.push("<br>");
2591                         continue;
2592                 }
2593                 if (m.length == 2) {
2594                         buf.push('<a href="' + m[1] + '" class="indent1' + (((base == '') && (name == m[1])) ? ' active' : '') + '">' + m[0] + '</a>');
2595                 }
2596                 else {
2597                         if (base == m[1]) {
2598                                 b = name;
2599                         }
2600                         else {
2601                                 a = cookie.get('menu_' + m[1]);
2602                                 b = m[3][0][1];
2603                                 for (j = 0; j < m[3].length; ++j) {
2604                                         if (m[3][j][1] == a) {
2605                                                 b = a;
2606                                                 break;
2607                                         }
2608                                 }
2609                         }
2610                         a = m[1] + '-' + b;
2611                         if (a == 'status-overview.asp') a = '/';
2612                         on1 = (base == m[1]);
2613                         buf.push('<a href="' + a + '" class="indent1' + (on1 ? ' active' : '') + '">' + m[0] + '</a>');
2614                         if ((!on1) && (m[2] == 0) && (cexp.indexOf(m[1]) == -1)) continue;
2616                         for (j = 0; j < m[3].length; ++j) {
2617                                 sm = m[3][j];
2618                                 a = m[1] + '-' + sm[1];
2619                                 if (a == 'status-overview.asp') a = '/';
2620                                 buf.push('<a href="' + a + '" class="indent2' + (((on1) && (name == sm[1])) ? ' active' : '') + '">' + sm[0] + '</a>');
2621                         }
2622                 }
2623         }
2624         document.write(buf.join(''));
2626         if (base.length) {
2627                 if ((base == 'qos') && (name == 'detailed.asp')) name = 'view.asp';
2628                 cookie.set('menu_' + base, name);
2629         }
2632 function createFieldTable(flags, desc)
2634         var common;
2635         var i, n;
2636         var name;
2637         var id;
2638         var fields;
2639         var f;
2640         var a;
2641         var buf = [];
2642         var buf2;
2643         var id1;
2644         var tr;
2646         if ((flags.indexOf('noopen') == -1)) buf.push('<table class="fields">');
2647         for (desci = 0; desci < desc.length; ++desci) {
2648                 var v = desc[desci];
2650                 if (!v) {
2651                         buf.push('<tr><td colspan=2 class="spacer">&nbsp;</td></tr>');
2652                         continue;
2653                 }
2655                 if (v.ignore) continue;
2657                 buf.push('<tr');
2658                 if (v.rid) buf.push(' id="' + v.rid + '"');
2659                 if (v.hidden) buf.push(' style="display:none"');
2660                 buf.push('>');
2662                 if (v.text) {
2663                         if (v.title) {
2664                                 buf.push('<td class="title indent' + (v.indent || 1) + '">' + v.title + '</td><td class="content">' + v.text + '</td></tr>');
2665                         }
2666                         else {
2667                                 buf.push('<td colspan=2>' + v.text + '</td></tr>');
2668                         }
2669                         continue;
2670                 }
2672                 id1 = '';
2673                 buf2 = [];
2674                 buf2.push('<td class="content">');
2676                 if (v.multi) fields = v.multi;
2677                         else fields = [v];
2679                 for (n = 0; n < fields.length; ++n) {
2680                         f = fields[n];
2681                         if (f.prefix) buf2.push(f.prefix);
2683                         if ((f.type == 'radio') && (!f.id)) id = '_' + f.name + '_' + i;
2684                                 else id = (f.id ? f.id : ('_' + f.name));
2686                         if (id1 == '') id1 = id;
2688                         common = ' onchange="verifyFields(this, 1)" id="' + id + '"';
2689                         if (f.attrib) common += ' ' + f.attrib;
2690                         name = f.name ? (' name="' + f.name + '"') : '';
2692                         switch (f.type) {
2693                         case 'checkbox':
2694                                 buf2.push('<input type="checkbox"' + name + (f.value ? ' checked' : '') + ' onclick="verifyFields(this, 1)"' + common + '>');
2695                                 break;
2696                         case 'radio':
2697                                 buf2.push('<input type="radio"' + name + (f.value ? ' checked' : '') + ' onclick="verifyFields(this, 1)"' + common + '>');
2698                                 break;
2699                         case 'password':
2700                                 if (f.peekaboo) {
2701                                         switch (get_config('web_pb', '1')) {
2702                                         case '0':
2703                                                 f.type = 'text';
2704                                         case '2':
2705                                                 f.peekaboo = 0;
2706                                                 break;
2707                                         }
2708                                 }
2709                                 if (f.type == 'password') {
2710                                         common += ' autocomplete="off"';
2711                                         if (f.peekaboo) common += ' onfocus=\'peekaboo("' + id + '",1)\'';
2712                                 }
2713                                 // drop
2714                         case 'text':
2715                                 buf2.push('<input type="' + f.type + '"' + name + ' value="' + escapeHTML(UT(f.value)) + '" maxlength=' + f.maxlen + (f.size ? (' size=' + f.size) : '') + common + '>');
2716                                 break;
2717                         case 'clear':
2718                                 s += '';
2719                                 break;
2720                         case 'select':
2721                                 buf2.push('<select' + name + common + '>');
2722                                 for (i = 0; i < f.options.length; ++i) {
2723                                         a = f.options[i];
2724                                         if (a.length == 1) a.push(a[0]);
2725                                         buf2.push('<option value="' + a[0] + '"' + ((a[0] == f.value) ? ' selected' : '') + '>' + a[1] + '</option>');
2726                                 }
2727                                 buf2.push('</select>');
2728                                 break;
2729                         case 'textarea':
2730                                 buf2.push('<textarea' + name + common + (f.wrap ? (' wrap=' + f.wrap) : '') + '>' + escapeHTML(UT(f.value)) + '</textarea>');
2731                                 break;
2732                         default:
2733                                 if (f.custom) buf2.push(f.custom);
2734                                 break;
2735                         }
2736                         if (f.suffix) buf2.push(f.suffix);
2737                 }
2738                 buf2.push('</td>');
2740                 buf.push('<td class="title indent' + (v.indent ? v.indent : 1) + '">');
2741                 if (id1 != '') buf.push('<label for="' + id + '">' + v.title + '</label></td>');
2742                         else buf.push(+ v.title + '</td>');
2744                 buf.push(buf2.join(''));
2745                 buf.push('</tr>');
2746         }
2747         if ((!flags) || (flags.indexOf('noclose') == -1)) buf.push('</table>');
2748         document.write(buf.join(''));
2751 function peekaboo(id, show)
2753         try {
2754                 var o = document.createElement('INPUT');
2755                 var e = E(id);
2756                 var name = e.name;
2757                 o.type = show ? 'text' : 'password';
2758                 o.value = e.value;
2759                 o.size = e.size;
2760                 o.maxLength = e.maxLength;
2761                 o.autocomplete = e.autocomplete;
2762                 o.title = e.title;
2763                 o.disabled = e.disabled;
2764                 o.onchange = e.onchange;
2765                 e.parentNode.replaceChild(o, e);
2766                 e = null;
2767                 o.id = id;
2768                 o.name = name;
2770                 if (show) {
2771                         o.onblur = function(ev) { setTimeout('peekaboo("' + this.id + '", 0)', 0) };
2772                         setTimeout('try { E("' + id + '").focus() } catch (ex) { }', 0)
2773                 }
2774                 else {
2775                         o.onfocus = function(ev) { peekaboo(this.id, 1); };
2776                 }
2777         }
2778         catch (ex) {
2779 //              alert(ex);
2780         }
2782 /* REMOVE-BEGIN
2783 notes:
2784  - e.type= doesn't work in IE, ok in FF
2785  - may mess keyboard tabing (bad: IE; ok: FF, Opera)... setTimeout() delay seems to help a little.
2786 REMOVE-END */
2789 // -----------------------------------------------------------------------------
2791 function reloadPage()
2793         document.location.reload(1);
2796 function reboot()
2798         if (confirm("Reboot?")) form.submitHidden('tomato.cgi', { _reboot: 1, _commit: 0, _nvset: 0 });
2801 function shutdown()
2803         if (confirm("Shutdown?")) form.submitHidden('shutdown.cgi', { });
2806 function logout()
2808         form.submitHidden('logout.asp', { });
2811 // -----------------------------------------------------------------------------
2815 // ---- debug
2817 function isLocal()
2819         return location.href.search('file://') == 0;
2822 function console(s)