Merge branch 'tomato-ND-usbmod-base' into tomato-K26-usbmod-base
[tomato.git] / release / src / router / www / tomato.js
blob38e204708c80940579d34500eb0516de714827c0
1 /*
2         Tomato GUI
3         Copyright (C) 2006-2009 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 _v_range(e, quiet, min, max, name)
357         var v;
359         if ((e = E(e)) == null) return 0;
360         v = e.value * 1;
361         if ((isNaN(v)) || (v < min) || (v > max)) {
362                 ferror.set(e, 'Invalid ' + name + '. Valid range: ' + min + '-' + max, quiet);
363                 return 0;
364         }
365         e.value = v;
366         ferror.clear(e);
367         return 1;
370 function v_range(e, quiet, min, max)
372         return _v_range(e, quiet, min, max, 'number');
375 function v_port(e, quiet)
377         return _v_range(e, quiet, 1, 0xFFFF, 'port');
380 function v_octet(e, quiet)
382         return _v_range(e, quiet, 1, 254, 'address');
385 function v_mins(e, quiet, min, max)
387         var v, m;
389         if ((e = E(e)) == null) return 0;
390         if (e.value.match(/^\s*(.+?)([mhd])?\s*$/)) {
391                 m = 1;
392                 if (RegExp.$2 == 'h') m = 60;
393                         else if (RegExp.$2 == 'd') m = 60 * 24;
394                 v = Math.round(RegExp.$1 * m);
395                 if (!isNaN(v)) {
396                         e.value = v;
397                         return _v_range(e, quiet, min, max, 'minutes');
398                 }
399         }
400         ferror.set(e, 'Invalid number of minutes.', quiet);
401         return 0;
404 function v_macip(e, quiet, bok, ipp)
406         var s, a, b, c, d, i;
408         if ((e = E(e)) == null) return 0;
409         s = e.value.replace(/\s+/g, '');
411         if ((a = fixMAC(s)) != null) {
412                 if (isMAC0(a)) {
413                         if (bok) {
414                                 e.value = '';
415                         }
416                         else {
417                                 ferror.set(e, 'Invalid MAC or IP address');
418                                 return false;
419                         }
420                 }
421         else e.value = a;
422                 ferror.clear(e);
423                 return true;
424         }
426         a = s.split('-');
427         if (a.length > 2) {
428                 ferror.set(e, 'Invalid IP address range', quiet);
429                 return false;
430         }
431         c = 0;
432         for (i = 0; i < a.length; ++i) {
433                 b = a[i];
434                 if (b.match(/^\d+$/)) b = ipp + b;
436                 b = fixIP(b);
437                 if (!b) {
438                         ferror.set(e, 'Invalid IP address', quiet);
439                         return false;
440                 }
442                 if (b.indexOf(ipp) != 0) {
443                         ferror.set(e, 'IP address outside of LAN', quiet);
444                         return false;
445                 }
447                 d = (b.split('.'))[3];
448                 if (d <= c) {
449                         ferror.set(e, 'Invalid IP address range', quiet);
450                         return false;
451                 }
453                 a[i] = c = d;
454         }
455         e.value = ipp + a.join('-');
456         return true;
459 function fixIP(ip, x)
461         var a, n, i;
463         a = ip.split('.');
464         if (a.length != 4) return null;
465         for (i = 0; i < 4; ++i) {
466                 n = a[i] * 1;
467                 if ((isNaN(n)) || (n < 0) || (n > 255)) return null;
468                 a[i] = n;
469         }
470         if ((x) && ((a[3] == 0) || (a[3] == 255))) return null;
471         return a.join('.');
474 function v_ip(e, quiet, x)
476         var ip;
478         if ((e = E(e)) == null) return 0;
479         ip = fixIP(e.value, x);
480         if (!ip) {
481                 ferror.set(e, 'Invalid IP address', quiet);
482                 return false;
483         }
484         e.value = ip;
485         ferror.clear(e);
486         return true;
489 function v_ipz(e, quiet)
491         if ((e = E(e)) == null) return 0;
492         if (e.value == '') e.value = '0.0.0.0';
493         return v_ip(e, quiet);
496 function v_dns(e, quiet)
498         if ((e = E(e)) == null) return 0;       
499         if (e.value == '') {
500                 e.value = '0.0.0.0';
501         }
502         else {
503                 var s = e.value.split(':');
504                 if (s.length == 1) {
505                         s.push(53);
506                 }
507                 else if (s.length != 2) {
508                         ferror.set(e, 'Invalid IP address or port', quiet);
509                         return false;
510                 }
511                 
512                 if ((s[0] = fixIP(s[0])) == null) {
513                         ferror.set(e, 'Invalid IP address', quiet);
514                         return false;
515                 }
517                 if ((s[1] = fixPort(s[1], -1)) == -1) {
518                         ferror.set(e, 'Invalid port', quiet);
519                         return false;
520                 }
521         
522                 if (s[1] == 53) {
523                         e.value = s[0];
524                 }
525                 else {
526                         e.value = s.join(':');
527                 }
528         }
530         ferror.clear(e);
531         return true;
534 function aton(ip)
536         var o, x, i;
538         // this is goofy because << mangles numbers as signed
539         o = ip.split('.');
540         x = '';
541         for (i = 0; i < 4; ++i) x += (o[i] * 1).hex(2);
542         return parseInt(x, 16);
545 function ntoa(ip)
547         return ((ip >> 24) & 255) + '.' + ((ip >> 16) & 255) + '.' + ((ip >> 8) & 255) + '.' + (ip & 255);
551 // 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
552 function _v_iptip(e, ip, quiet)
554         var ma, x, y, z, oip;
555         var a, b;
557         oip = ip;
559         // x.x.x.x - y.y.y.y
560         if (ip.match(/^(.*)-(.*)$/)) {
561                 a = fixIP(RegExp.$1);
562                 b = fixIP(RegExp.$2);
563                 if ((a == null) || (b == null)) {
564                         ferror.set(e, 'Invalid IP address range - ' + oip, quiet);
565                         return null;
566                 }
567                 ferror.clear(e);
569                 if (aton(a) > aton(b)) return b + '-' + a;
570                 return a + '-' + b;
571         }
573         ma = '';
575         // x.x.x.x/nn
576         // x.x.x.x/y.y.y.y
577         if (ip.match(/^(.*)\/(.*)$/)) {
578                 ip = RegExp.$1;
579                 b = RegExp.$2;
581                 ma = b * 1;
582                 if (isNaN(ma)) {
583                         ma = fixIP(b);
584                         if ((ma == null) || (!_v_netmask(ma))) {
585                                 ferror.set(e, 'Invalid netmask - ' + oip, quiet);
586                                 return null;
587                         }
588                 }
589                 else {
590                         if ((ma < 0) || (ma > 32)) {
591                                 ferror.set(e, 'Invalid netmask - ' + oip, quiet);
592                                 return null;
593                         }
594                 }
595         }
597         ip = fixIP(ip);
598         if (!ip) {
599                 ferror.set(e, 'Invalid IP address - ' + oip, quiet);
600                 return null;
601         }
603         ferror.clear(e);
604         return ip + ((ma != '') ? ('/' + ma) : '');
607 function v_iptip(e, quiet, multi)
609         var v, i;
611         if ((e = E(e)) == null) return 0;
612         v = e.value.split(',');
613         if (multi) {
614                 if (v.length > multi) {
615                         ferror.set(e, 'Too many IP addresses', quiet);
616                         return 0;
617                 }
618         }
619         else {
620                 if (v.length > 1) {
621                         ferror.set(e, 'Invalid IP address', quiet);
622                         return 0;
623                 }
624         }
625         for (i = 0; i < v.length; ++i) {
626                 if ((v[i] = _v_iptip(e, v[i], quiet)) == null) return 0;
627         }
628         e.value = v.join(', ');
629         return 1;
633 function fixPort(p, def)
635         if (def == null) def = -1;
636         if (p == null) return def;
637         p *= 1;
638         if ((isNaN(p)) || (p < 1) || (p > 65535)) return def;
639         return p;
642 function _v_portrange(e, quiet, v)
644         var x, y;
646         if (v.match(/^(.*)[-:](.*)$/)) {
647                 x = fixPort(RegExp.$1, -1);
648                 y = fixPort(RegExp.$2, -1);
649                 if ((x == -1) || (y == -1)) {
650                         ferror.set(e, 'Invalid port range: ' + v, quiet);
651                         return null;
652                 }
653                 if (x > y) {
654                         v = x;
655                         x = y;
656                         y = v;
657                 }
658                 ferror.clear(e);
659                 if (x == y) return x;
660                 return x + '-' + y;
661         }
663         v = fixPort(v, -1);
664         if (v == -1) {
665                 ferror.set(e, 'Invalid port', quiet);
666                 return null;
667         }
669         ferror.clear(e);
670         return v;
673 function v_portrange(e, quiet)
675         var v;
677         if ((e = E(e)) == null) return 0;
678         v = _v_portrange(e, quiet, e.value);
679         if (v == null) return 0;
680         e.value = v;
681         return 1;
684 function v_iptport(e, quiet)
686         var a, i, v, q;
688         if ((e = E(e)) == null) return 0;
690         a = e.value.split(/,/);
692         if (a.length == 0) {
693                 ferror.set(e, 'Expecting a list of ports or port range.', quiet);
694                 return 0;
695         }
696         if (a.length > 10) {
697                 ferror.set(e, 'Only 10 ports/range sets are allowed.', quiet);
698                 return 0;
699         }
701         q = [];
702         for (i = 0; i < a.length; ++i) {
703                 v = _v_portrange(e, quiet, a[i]);
704                 if (v == null) return 0;
705                 q.push(v);
706         }
708         e.value = q.join(',');
709         ferror.clear(e);
710         return 1;
713 function _v_netmask(mask)
715         var v = aton(mask) ^ 0xFFFFFFFF;
716         return (((v + 1) & v) == 0);
719 function v_netmask(e, quiet)
721         var n, b;
723         if ((e = E(e)) == null) return 0;
724         n = fixIP(e.value);
725         if (n) {
726                 if (_v_netmask(n)) {
727                         e.value = n;
728                         ferror.clear(e);
729                         return 1;
730                 }
731         }
732         else if (e.value.match(/^\s*\/\s*(\d+)\s*$/)) {
733                 b = RegExp.$1 * 1;
734                 if ((b >= 1) && (b <= 32)) {
735                         if (b == 32) n = 0xFFFFFFFF;    // js quirk
736                                 else n = (0xFFFFFFFF >>> b) ^ 0xFFFFFFFF;
737                         e.value = (n >>> 24) + '.' + ((n >>> 16) & 0xFF) + '.' + ((n >>> 8) & 0xFF) + '.' + (n & 0xFF);
738                         ferror.clear(e);
739                         return 1;
740                 }
741         }
742         ferror.set(e, 'Invalid netmask', quiet);
743         return 0;
746 function fixMAC(mac)
748         var t, i;
750         mac = mac.replace(/\s+/g, '').toUpperCase();
751         if (mac.length == 0) {
752                 mac = [0,0,0,0,0,0];
753         }
754         else if (mac.length == 12) {
755                 mac = mac.match(/../g);
756         }
757         else {
758                 mac = mac.split(/[:\-]/);
759                 if (mac.length != 6) return null;
760         }
761         for (i = 0; i < 6; ++i) {
762                 t = '' + mac[i];
763                 if (t.search(/^[0-9A-F]+$/) == -1) return null;
764                 if ((t = parseInt(t, 16)) > 255) return null;
765                 mac[i] = t.hex(2);
766         }
767         return mac.join(':');
770 function v_mac(e, quiet)
772         var mac;
774         if ((e = E(e)) == null) return 0;
775         mac = fixMAC(e.value);
776         if ((!mac) || (isMAC0(mac))) {
777                 ferror.set(e, 'Invalid MAC address', quiet);
778                 return 0;
779         }
780         e.value = mac;
781         ferror.clear(e);
782         return 1;
785 function v_macz(e, quiet)
787         var mac;
789         if ((e = E(e)) == null) return 0;
790         mac = fixMAC(e.value);
791         if (!mac) {
792                 ferror.set(e, 'Invalid MAC address', quiet);
793                 return false;
794         }
795         e.value = mac;
796         ferror.clear(e);
797         return true;
800 function v_length(e, quiet, min, max)
802         var s, n;
804         if ((e = E(e)) == null) return 0;
805         s = e.value.trim();
806         n = s.length;
807         if (min == undefined) min = 1;
808         if (n < min) {
809                 ferror.set(e, 'Invalid length. Please enter at least ' + min + ' character' + (min == 1 ? '.' : 's.'), quiet);
810                 return 0;
811         }
812         max = max || e.maxlength;
813     if (n > max) {
814                 ferror.set(e, 'Invalid length. Please reduce the length to ' + max + ' characters or less.', quiet);
815                 return 0;
816         }
817         e.value = s;
818         ferror.clear(e);
819         return 1;
822 function v_domain(e, quiet)
824         var s;
826         if ((e = E(e)) == null) return 0;
827         s = e.value.trim().replace(/\s+/g, ' ');
828         if ((s.length > 32) || ((s.length > 0) && (s.search(/^[.a-zA-Z0-9_\- ]+$/) == -1))) {
829                 ferror.set(e, "Invalid name. Only characters \"A-Z 0-9 . - _\" are allowed.", quiet);
830                 return 0;
831         }
832         e.value = s;
833         ferror.clear(e);
834         return 1;
837 function isMAC0(mac)
839         return (mac == '00:00:00:00:00:00');
842 // -----------------------------------------------------------------------------
844 function cmpIP(a, b)
846         if ((a = fixIP(a)) == null) a = '255.255.255.255';
847         if ((b = fixIP(b)) == null) b = '255.255.255.255';
848         return aton(a) - aton(b);
851 function cmpText(a, b)
853         if (a == '') a = '\xff';
854         if (b == '') b = '\xff';
855         return (a < b) ? -1 : ((a > b) ? 1 : 0);
858 function cmpInt(a, b)
860         a = parseInt(a, 10);
861         b = parseInt(b, 10);
862         return ((isNaN(a)) ? -0x7FFFFFFF : a) - ((isNaN(b)) ? -0x7FFFFFFF : b);
865 function cmpDate(a, b)
867         return b.getTime() - a.getTime();
870 // -----------------------------------------------------------------------------
872 // todo: cleanup this mess
874 function TGO(e)
876         return elem.parentElem(e, 'TABLE').gridObj;
879 function tgHideIcons()
881         var e;
882         while ((e = document.getElementById('tg-row-panel')) != null) e.parentNode.removeChild(e);
885 // options = sort, move, delete
886 function TomatoGrid(tb, options, maxAdd, editorFields)
888         this.init(tb, options, maxAdd, editorFields);
889         return this;
892 TomatoGrid.prototype = {
893         init: function(tb, options, maxAdd, editorFields) {
894                 if (tb) {
895                         this.tb = E(tb);
896                         this.tb.gridObj = this;
897                 }
898                 else {
899                         this.tb = null;
900                 }
901                 if (!options) options = '';
902                 this.header = null;
903                 this.footer = null;
904                 this.editor = null;
905                 this.canSort = options.indexOf('sort') != -1;
906                 this.canMove = options.indexOf('move') != -1;
907                 this.maxAdd = maxAdd || 100;
908                 this.canEdit = (editorFields != null);
909                 this.canDelete = this.canEdit || (options.indexOf('delete') != -1);
910                 this.editorFields = editorFields;
911                 this.sortColumn = -1;
912                 this.sortAscending = true;
913         },
915         _insert: function(at, cells, escCells) {
916                 var tr, td, c;
917                 var i, t;
919                 tr = this.tb.insertRow(at);
920                 for (i = 0; i < cells.length; ++i) {
921                         c = cells[i];
922                         if (typeof(c) == 'string') {
923                                 td = tr.insertCell(i);
924                                 td.className = 'co' + (i + 1);
925                                 if (escCells) td.appendChild(document.createTextNode(c));
926                                         else td.innerHTML = c;
927                         }
928                         else {
929                                 tr.appendChild(c);
930                         }
931                 }
932                 return tr;
933         },
935         // header
937         headerClick: function(cell) {
938                 if (this.canSort) {
939                         this.sort(cell.cellN);
940                 }
941         },
943         headerSet: function(cells, escCells) {
944                 var e, i;
946                 elem.remove(this.header);
947                 this.header = e = this._insert(0, cells, escCells);
948                 e.className = 'header';
950                 for (i = 0; i < e.cells.length; ++i) {
951                         e.cells[i].cellN = i;   // cellIndex broken in Safari
952                         e.cells[i].onclick = function() { return TGO(this).headerClick(this); };
953                 }
954                 return e;
955         },
957         // footer
959         footerClick: function(cell) {
960         },
962         footerSet: function(cells, escCells) {
963                 var e, i;
965                 elem.remove(this.footer);
966                 this.footer = e = this._insert(-1, cells, escCells);
967                 e.className = 'footer';
968                 for (i = 0; i < e.cells.length; ++i) {
969                         e.cells[i].cellN = i;
970                         e.cells[i].onclick = function() { TGO(this).footerClick(this) };
971                 }
972                 return e;
973         },
975         //
977         rpUp: function(e) {
978                 var i;
980                 e = PR(e);
981                 TGO(e).moving = null;
982                 i = e.previousSibling;
983                 if (i == this.header) return;
984                 e.parentNode.removeChild(e);
985                 i.parentNode.insertBefore(e, i);
987                 this.recolor();
988                 this.rpHide();
989         },
991         rpDn: function(e) {
992                 var i;
994                 e = PR(e);
995                 TGO(e).moving = null;
996                 i = e.nextSibling;
997                 if (i == this.footer) return;
998                 e.parentNode.removeChild(e);
999                 i.parentNode.insertBefore(e, i.nextSibling);
1001                 this.recolor();
1002                 this.rpHide();
1003         },
1005         rpMo: function(img, e) {
1006                 var me;
1008                 e = PR(e);
1009                 me = TGO(e);
1010                 if (me.moving == e) {
1011                         me.moving = null;
1012                         this.rpHide();
1013                         return;
1014                 }
1015                 me.moving = e;
1016                 img.style.border = "1px dotted red";
1017         },
1019         rpDel: function(e) {
1020                 e = PR(e);
1021                 TGO(e).moving = null;
1022                 e.parentNode.removeChild(e);
1023                 this.recolor();
1024                 this.rpHide();
1025         },
1027         rpMouIn: function(evt) {
1028                 var e, x, ofs, me, s, n;
1030                 if ((evt = checkEvent(evt)) == null) return;
1032                 me = TGO(evt.target);
1033                 if (me.isEditing()) return;
1034                 if (me.moving) return;
1036                 me.rpHide();
1037                 e = document.createElement('div');
1038                 e.tgo = me;
1039                 e.ref = evt.target;
1040                 e.setAttribute('id', 'tg-row-panel');
1042                 n = 0;
1043                 s = '';
1044                 if (me.canMove) {
1045                         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">';
1046                         n += 3;
1047                 }
1048                 if (me.canDelete) {
1049                         s += '<img src="rpx.gif" onclick="this.parentNode.tgo.rpDel(this.parentNode.ref)" title="Delete">';
1050                         ++n;
1051                 }
1052                 x = PR(evt.target);
1053                 x = x.cells[x.cells.length - 1];
1054                 ofs = elem.getOffset(x);
1055                 n *= 18;
1056                 e.style.left = (ofs.x + x.offsetWidth - n) + 'px';
1057                 e.style.top = ofs.y + 'px';
1058                 e.style.width = n + 'px';
1059                 e.innerHTML = s;
1061                 document.body.appendChild(e);
1062         },
1064         rpHide: tgHideIcons,
1066         //
1068         onClick: function(cell) {
1069                 if (this.canEdit) {
1070                         if (this.moving) {
1071                                 var p = this.moving.parentNode;
1072                                 var q = PR(cell);
1073                                 if (this.moving != q) {
1074                                         var v = this.moving.rowIndex > q.rowIndex;
1075                                         p.removeChild(this.moving);
1076                                         if (v) p.insertBefore(this.moving, q);
1077                                                 else p.insertBefore(this.moving, q.nextSibling);
1078                                         this.recolor();
1079                                 }
1080                                 this.moving = null;
1081                                 this.rpHide();
1082                                 return;
1083                         }
1084                         this.edit(cell);
1085                 }
1086         },
1088         insert: function(at, data, cells, escCells) {
1089                 var e, i;
1091                 if ((this.footer) && (at == -1)) at = this.footer.rowIndex;
1092                 e = this._insert(at, cells, escCells);
1093                 e.className = (e.rowIndex & 1) ? 'even' : 'odd';
1095                 for (i = 0; i < e.cells.length; ++i) {
1096                         e.cells[i].onclick = function() { return TGO(this).onClick(this); };
1097                 }
1099                 e._data = data;
1100                 e.getRowData = function() { return this._data; }
1101                 e.setRowData = function(data) { this._data = data; }
1103                 if ((this.canMove) || (this.canEdit) || (this.canDelete)) {
1104                         e.onmouseover = this.rpMouIn;
1105 //                      e.onmouseout = this.rpMouOut;
1106                         if (this.canEdit) e.title = 'Click to edit';
1107                 }
1109                 return e;
1110         },
1112         //
1114         insertData: function(at, data) {
1115                 return this.insert(at, data, this.dataToView(data), false);
1116         },
1118         dataToView: function(data) {
1119                 var v = [];
1120                 for (var i = 0; i < data.length; ++i)
1121                         v.push(escapeHTML('' + data[i]));
1122                 return v;
1123         },
1125         dataToFieldValues: function(data) {
1126                 return data;
1127         },
1129         fieldValuesToData: function(row) {
1130                 var e, i, data;
1132                 data = [];
1133                 e = fields.getAll(row);
1134                 for (i = 0; i < e.length; ++i) data.push(e[i].value);
1135                 return data;
1136         },
1138         //
1140         edit: function(cell) {
1141                 var sr, er, e, c;
1143                 if (this.isEditing()) return;
1145                 sr = PR(cell);
1146                 sr.style.display = 'none';
1147                 elem.removeClass(sr, 'hover');
1148                 this.source = sr;
1150                 er = this.createEditor('edit', sr.rowIndex, sr);
1151         er.className = 'editor';
1152                 this.editor = er;
1154                 c = er.cells[cell.cellIndex || 0];
1155                 e = c.getElementsByTagName('input');
1156                 if ((e) && (e.length > 0)) {
1157                         try {   // IE quirk
1158                                 e[0].focus();
1159                         }
1160                         catch (ex) {
1161                         }
1162                 }
1164                 this.controls = this.createControls('edit', sr.rowIndex);
1166                 this.disableNewEditor(true);
1167                 this.rpHide();
1168                 this.verifyFields(this.editor, true);
1169         },
1171         createEditor: function(which, rowIndex, source) {
1172                 var values;
1174                 if (which == 'edit') values = this.dataToFieldValues(source.getRowData());
1176                 var row = this.tb.insertRow(rowIndex);
1177                 row.className = 'editor';
1179                 var common = ' onkeypress="return TGO(this).onKey(\'' + which + '\', event)" onchange="TGO(this).onChange(\'' + which + '\', this)"';
1181                 var vi = 0;
1182                 for (var i = 0; i < this.editorFields.length; ++i) {
1183                         var s = '';
1184                 var ef = this.editorFields[i].multi;
1185                         if (!ef) ef = [this.editorFields[i]];
1187                         for (var j = 0; j < ef.length; ++j) {
1188                                 var f = ef[j];
1190                                 if (f.prefix) s += f.prefix;
1191                                 var attrib = ' class="fi' + (vi + 1) + '" ' + (f.attrib || '');
1192                                 switch (f.type) {
1193                                 case 'text':
1194                                         s += '<input type="text" maxlength=' + f.maxlen + common + attrib;
1195                                         if (which == 'edit') s += ' value="' + escapeHTML('' + values[vi]) + '">';
1196                                                 else s += '>';
1197                                         break;
1198                                 case 'select':
1199                                         s += '<select' + common + attrib + '>';
1200                                         for (var k = 0; k < f.options.length; ++k) {
1201                                                 a = f.options[k];
1202                                                 if (which == 'edit') {
1203                                                         s += '<option value="' + a[0] + '"' + ((a[0] == values[vi]) ? ' selected>' : '>') + a[1] + '</option>';
1204                                                 }
1205                                                 else {
1206                                                         s += '<option value="' + a[0] + '">' + a[1] + '</option>';
1207                                                 }
1208                                         }
1209                                         s += '</select>';
1210                                         break;
1211                                 case 'checkbox':
1212                                         s += '<input type="checkbox"' + common + attrib;
1213                                         if ((which == 'edit') && (values[vi])) s += ' checked';
1214                                         s += '>';
1215                                         break;
1216                                 default:
1217                                         s += f.custom.replace(/\$which\$/g, which);
1218                                 }
1219                                 if (f.suffix) s += f.suffix;
1221                                 ++vi;
1222                         }
1223                         var c = row.insertCell(i);
1224                         c.innerHTML = s;
1225                         if (this.editorFields[i].vtop) c.vAlign = 'top';
1226                 }
1228                 return row;
1229         },
1231         createControls: function(which, rowIndex) {
1232                 var r, c;
1234                 r = this.tb.insertRow(rowIndex);
1235                 r.className = 'controls';
1237                 c = r.insertCell(0);
1238                 c.colSpan = this.header.cells.length;
1239                 if (which == 'edit') {
1240                         c.innerHTML =
1241                                 '<input type=button value="Delete" onclick="TGO(this).onDelete()"> &nbsp; ' +
1242                                 '<input type=button value="OK" onclick="TGO(this).onOK()"> ' +
1243                                 '<input type=button value="Cancel" onclick="TGO(this).onCancel()">';
1244                 }
1245                 else {
1246                         c.innerHTML =
1247                                 '<input type=button value="Add" onclick="TGO(this).onAdd()">';
1248                 }
1249                 return r;
1250         },
1252         removeEditor: function() {
1253                 if (this.editor) {
1255                         elem.remove(this.editor);
1256                         this.editor = null;
1257                 }
1258                 if (this.controls) {
1259                         elem.remove(this.controls);
1260                         this.controls = null;
1261                 }
1262         },
1264         showSource: function() {
1265                 if (this.source) {
1266                         this.source.style.display = '';
1267                         this.source = null;
1268                 }
1269         },
1271         onChange: function(which, cell) {
1272                 return this.verifyFields((which == 'new') ? this.newEditor : this.editor, true);
1273         },
1275         onKey: function(which, ev) {
1276                 switch (ev.keyCode) {
1277                 case 27:
1278                         if (which == 'edit') this.onCancel();
1279                         return false;
1280                 case 13:
1281                         if (((ev.srcElement) && (ev.srcElement.tagName == 'SELECT')) ||
1282                                 ((ev.target) && (ev.target.tagName == 'SELECT'))) return true;
1283                         if (which == 'edit') this.onOK();
1284                                 else this.onAdd();
1285                         return false;
1286                 }
1287                 return true;
1288         },
1290         onDelete: function() {
1291                 this.removeEditor();
1292                 elem.remove(this.source);
1293                 this.source = null;
1294                 this.disableNewEditor(false);
1295         },
1297         onCancel: function() {
1298                 this.removeEditor();
1299                 this.showSource();
1300                 this.disableNewEditor(false);
1301         },
1303         onOK: function() {
1304                 var i, data, view;
1306                 if (!this.verifyFields(this.editor, false)) return;
1308                 data = this.fieldValuesToData(this.editor);
1309                 view = this.dataToView(data);
1311                 this.source.setRowData(data);
1312                 for (i = 0; i < this.source.cells.length; ++i) {
1313                         this.source.cells[i].innerHTML = view[i];
1314                 }
1316                 this.removeEditor();
1317                 this.showSource();
1318                 this.disableNewEditor(false);
1319         },
1321         onAdd: function() {
1322                 var data;
1324                 this.moving = null;
1325                 this.rpHide();
1327                 if (!this.verifyFields(this.newEditor, false)) return;
1329                 data = this.fieldValuesToData(this.newEditor);
1330                 this.insertData(-1, data);
1332                 this.disableNewEditor(false);
1333                 this.resetNewEditor();
1334         },
1336         verifyFields: function(row, quiet) {
1337                 return true;
1338         },
1340         showNewEditor: function() {
1341                 var r;
1343                 r = this.createEditor('new', -1, null);
1344                 this.footer = this.newEditor = r;
1346                 r = this.createControls('new', -1);
1347                 this.newControls = r;
1349                 this.disableNewEditor(false);
1350         },
1352         disableNewEditor: function(disable) {
1353                 if (this.getDataCount() >= this.maxAdd) disable = true;
1354                 if (this.newEditor) fields.disableAll(this.newEditor, disable);
1355                 if (this.newControls) fields.disableAll(this.newControls, disable);
1356         },
1358         resetNewEditor: function() {
1359                 var i, e;
1361                 e = fields.getAll(this.newEditor);
1362                 ferror.clearAll(e);
1363                 for (i = 0; i < e.length; ++i) {
1364                         var f = e[i];
1365                         if (f.selectedIndex) f.selectedIndex = 0;
1366                                 else f.value = '';
1367                 }
1368                 if (e.length) e[0].focus();
1369         },
1371         getDataCount: function() {
1372                 var n;
1373                 n = this.tb.rows.length;
1374                 if (this.footer) n = this.footer.rowIndex;
1375                 if (this.header) n -= this.header.rowIndex + 1;
1376                 return n;
1377         },
1379         sortCompare: function(a, b) {
1380                 var obj = TGO(a);
1381                 var col = obj.sortColumn;
1382                 var r = cmpText(a.cells[col].innerHTML, b.cells[col].innerHTML);
1383                 return obj.sortAscending ? r : -r;
1384         },
1386         sort: function(column) {
1387                 if (this.editor) return;
1389                 if (this.sortColumn >= 0) {
1390                         elem.removeClass(this.header.cells[this.sortColumn], 'sortasc', 'sortdes');
1391                 }
1392                 if (column == this.sortColumn) {
1393                         this.sortAscending = !this.sortAscending;
1394                 }
1395                 else {
1396                         this.sortAscending = true;
1397                         this.sortColumn = column;
1398                 }
1399                 elem.addClass(this.header.cells[column], this.sortAscending ? 'sortasc' : 'sortdes');
1401                 this.resort();
1402         },
1404         resort: function() {
1405                 if ((this.sortColumn < 0) || (this.getDataCount() == 0) || (this.editor)) return;
1407                 var p = this.header.parentNode;
1408                 var a = [];
1409                 var i, j, max, e, p;
1410                 var top;
1412                 this.moving = null;
1414                 top = this.header ? this.header.rowIndex + 1 : 0;
1415                 max = this.footer ? this.footer.rowIndex : this.tb.rows.length;
1416                 for (i = top; i < max; ++i) a.push(p.rows[i]);
1417                 a.sort(THIS(this, this.sortCompare));
1418                 this.removeAllData();
1419                 j = top;
1420                 for (i = 0; i < a.length; ++i) {
1421                         e = p.insertBefore(a[i], this.footer);
1422                         e.className = (j & 1) ? 'even' : 'odd';
1423                         ++j;
1424                 }
1425         },
1427         recolor: function() {
1428                  var i, e, o;
1430                  i = this.header ? this.header.rowIndex + 1 : 0;
1431                  e = this.footer ? this.footer.rowIndex : this.tb.rows.length;
1432                  for (; i < e; ++i) {
1433                          o = this.tb.rows[i];
1434                          o.className = (o.rowIndex & 1) ? 'even' : 'odd';
1435                  }
1436         },
1438         removeAllData: function() {
1439                 var i, count;
1441                 i = this.header ? this.header.rowIndex + 1 : 0;
1442                 count = (this.footer ? this.footer.rowIndex : this.tb.rows.length) - i;
1443                 while (count-- > 0) elem.remove(this.tb.rows[i]);
1444         },
1446         getAllData: function() {
1447                 var i, max, data, r;
1449                 data = [];
1450                 max = this.footer ? this.footer.rowIndex : this.tb.rows.length;
1451                 for (i = this.header ? this.header.rowIndex + 1 : 0; i < max; ++i) {
1452                         r = this.tb.rows[i];
1453                         if ((r.style.display != 'none') && (r._data)) data.push(r._data);
1454                 }
1455                 return data;
1456         },
1458         isEditing: function() {
1459                 return (this.editor != null);
1460         }
1464 // -----------------------------------------------------------------------------
1467 function xmlHttpObj()
1469         var ob;
1470         try {
1471                 ob = new XMLHttpRequest();
1472                 if (ob) return ob;
1473         }
1474         catch (ex) { }
1475         try {
1476                 ob = new ActiveXObject('Microsoft.XMLHTTP');
1477                 if (ob) return ob;
1478         }
1479         catch (ex) { }
1480         return null;
1483 var _useAjax = -1;
1484 var _holdAjax = null;
1486 function useAjax()
1488         if (_useAjax == -1) _useAjax = ((_holdAjax = xmlHttpObj()) != null);
1489         return _useAjax;
1492 function XmlHttp()
1494         if ((!useAjax()) || ((this.xob = xmlHttpObj()) == null)) return null;
1495         return this;
1498 XmlHttp.prototype = {
1499         addId: function(vars) {
1500                 if (vars) vars += '&';
1501                         else vars = '';
1502                 vars += '_http_id=' + escapeCGI(nvram.http_id);
1503                 return vars;
1504         },
1506     get: function(url, vars) {
1507                 try {
1508                         vars = this.addId(vars);
1509                         url += '?' + vars;
1511                         this.xob.onreadystatechange = THIS(this, this.onReadyStateChange);
1512                         this.xob.open('GET', url, true);
1513                         this.xob.send(null);
1514                 }
1515                 catch (ex) {
1516                         this.onError(ex);
1517                 }
1518         },
1520         post: function(url, vars) {
1521                 try {
1522                         vars = this.addId(vars);
1524                         this.xob.onreadystatechange = THIS(this, this.onReadyStateChange);
1525                         this.xob.open('POST', url, true);
1526                         this.xob.send(vars);
1527                 }
1528                 catch (ex) {
1529                         this.onError(ex);
1530                 }
1531         },
1533         abort: function() {
1534                 try {
1535                         this.xob.onreadystatechange = function () { }
1536                         this.xob.abort();
1537                 }
1538                 catch (ex) {
1539                 }
1540         },
1542         onReadyStateChange: function() {
1543                 try {
1544                         if (typeof(E) == 'undefined') return;   // oddly late? testing for bug...
1546                         if (this.xob.readyState == 4) {
1547                                 if (this.xob.status == 200) {
1548                                         this.onCompleted(this.xob.responseText, this.xob.responseXML);
1549                                 }
1550                                 else {
1551                                         this.onError('' + (this.xob.status || 'unknown'));
1552                                 }
1553                         }
1554                 }
1555                 catch (ex) {
1556                         this.onError(ex);
1557                 }
1558         },
1560         onCompleted: function(text, xml) { },
1561         onError: function(ex) { }
1565 // -----------------------------------------------------------------------------
1568 function TomatoTimer(func, ms)
1570         this.tid = null;
1571         this.onTimer = func;
1572         if (ms) this.start(ms);
1573         return this;
1576 TomatoTimer.prototype = {
1577         start: function(ms) {
1578                 this.stop();
1579                 this.tid = setTimeout(THIS(this, this._onTimer), ms);
1580         },
1581         stop: function() {
1582                 if (this.tid) {
1583                         clearTimeout(this.tid);
1584                         this.tid = null;
1585                 }
1586         },
1588         isRunning: function() {
1589                 return (this.tid != null);
1590         },
1592         _onTimer: function() {
1593                 this.tid = null;
1594                 this.onTimer();
1595         },
1597         onTimer: function() {
1598         }
1602 // -----------------------------------------------------------------------------
1605 function TomatoRefresh(actionURL, postData, refreshTime, cookieTag)
1607         this.setup(actionURL, postData, refreshTime, cookieTag);
1608         this.timer = new TomatoTimer(THIS(this, this.start));
1611 TomatoRefresh.prototype = {
1612         running: 0,
1614         setup: function(actionURL, postData, refreshTime, cookieTag) {
1615                 var e, v;
1617                 this.actionURL = actionURL;
1618                 this.postData = postData;
1619                 this.refreshTime = refreshTime * 1000;
1620                 this.cookieTag = cookieTag;
1621         },
1623         start: function() {
1624                 var e;
1626                 if ((e = E('refresh-time')) != null) {
1627                         if (this.cookieTag) cookie.set(this.cookieTag, e.value);
1628                         this.refreshTime = e.value * 1000;
1629                 }
1630                 e = undefined;
1632                 this.updateUI('start');
1634                 this.running = 1;
1635                 if ((this.http = new XmlHttp()) == null) {
1636                         reloadPage();
1637                         return;
1638                 }
1640                 this.http.parent = this;
1642                 this.http.onCompleted = function(text, xml) {
1643                         var p = this.parent;
1645                         if (p.cookieTag) cookie.unset(p.cookieTag + '-error');
1646                         if (!p.running) {
1647                                 p.stop();
1648                                 return;
1649                         }
1651                         p.refresh(text);
1653                         if ((p.refreshTime > 0) && (!p.once)) {
1654                                 p.updateUI('wait');
1655                                 p.timer.start(Math.round(p.refreshTime));
1656                         }
1657                         else {
1658                                 p.stop();
1659                         }
1661                         p.errors = 0;
1662                 }
1664                 this.http.onError = function(ex) {
1665                         var p = this.parent;
1666                         if ((!p) || (!p.running)) return;
1668                         p.timer.stop();
1670                         if (++p.errors <= 3) {
1671                                 p.updateUI('wait');
1672                                 p.timer.start(3000);
1673                                 return;
1674                         }
1676                         if (p.cookieTag) {
1677                                 var e = cookie.get(p.cookieTag + '-error') * 1;
1678                                 if (isNaN(e)) e = 0;
1679                                         else ++e;
1680                                 cookie.unset(p.cookieTag);
1681                                 cookie.set(p.cookieTag + '-error', e, 1);
1682                                 if (e >= 3) {
1683                                         alert('XMLHTTP: ' + ex);
1684                                         return;
1685                                 }
1686                         }
1688                         setTimeout(reloadPage, 2000);
1689                 }
1691                 this.errors = 0;
1692                 this.http.post(this.actionURL, this.postData);
1693         },
1695         stop: function() {
1696                 if (this.cookieTag) cookie.set(this.cookieTag, -(this.refreshTime / 1000));
1697                 this.running = 0;
1698                 this.updateUI('stop');
1699                 this.timer.stop();
1700                 this.http = null;
1701                 this.once = undefined;
1702         },
1704         toggle: function(delay) {
1705                 if (this.running) this.stop();
1706                         else this.start(delay);
1707         },
1709         updateUI: function(mode) {
1710                 var e, b;
1712                 if (typeof(E) == 'undefined') return;   // for a bizzare bug...
1714                 b = (mode != 'stop') && (this.refreshTime > 0);
1715                 if ((e = E('refresh-button')) != null) {
1716                         e.value = b ? 'Stop' : 'Refresh';
1717                         e.disabled = ((mode == 'start') && (!b));
1718                 }
1719                 if ((e = E('refresh-time')) != null) e.disabled = b;
1720                 if ((e = E('refresh-spinner')) != null) e.style.visibility = b ? 'visible' : 'hidden';
1721         },
1723         initPage: function(delay, def) {
1724                 var e, v;
1726                 e = E('refresh-time');
1727                 if (((this.cookieTag) && (e != null)) &&
1728                         ((v = cookie.get(this.cookieTag)) != null) && (!isNaN(v *= 1))) {
1729                         e.value = Math.abs(v);
1730                         if (v > 0) v = (v * 1000) + (delay || 0);
1731                 }
1732                 else if (def) {
1733                         v = def;
1734                         if (e) e.value = def;
1735                 }
1736                 else v = 0;
1738                 if (delay < 0) {
1739                         v = -delay;
1740                         this.once = 1;
1741                 }
1743                 if (v > 0) {
1744                         this.running = 1;
1745                         this.refreshTime = v;
1746                         this.timer.start(v);
1747                         this.updateUI('wait');
1748                 }
1749         }
1752 function genStdTimeList(id, zero, min)
1754         var b = [];
1755         var t = [3,4,5,10,15,30,60,120,180,240,300,10*60,15*60,20*60,30*60];
1756         var i, v;
1758         if (min >= 0) {
1759                 b.push('<select id="' + id + '"><option value=0>' + zero);
1760                 for (i = 0; i < t.length; ++i) {
1761                         v = t[i];
1762                         if (v < min) continue;
1763                         b.push('<option value=' + v + '>');
1764                         if (v == 60) b.push('1 minute');
1765                                 else if (v > 60) b.push((v / 60) + ' minutes');
1766                                 else b.push(v + ' seconds');
1767                 }
1768                 b.push('</select> ');
1769         }
1770         document.write(b.join(''));
1773 function genStdRefresh(spin, min, exec)
1775         W('<div style="text-align:right">');
1776         if (spin) W('<img src="spin.gif" id="refresh-spinner"> ');
1777         genStdTimeList('refresh-time', 'Auto Refresh', min);
1778         W('<input type="button" value="Refresh" onclick="' + (exec ? exec : 'refreshClick()') + '" id="refresh-button"></div>');
1782 // -----------------------------------------------------------------------------
1785 function _tabCreate(tabs)
1787         var buf = [];
1788         buf.push('<ul id="tabs">');
1789         for (var i = 0; i < arguments.length; ++i)
1790                 buf.push('<li><a href="javascript:tabSelect(\'' + arguments[i][0] + '\')" id="' + arguments[i][0] + '">' + arguments[i][1] + '</a>');
1791         buf.push('</ul><div id="tabs-bottom"></div>');
1792         return buf.join('');
1795 function tabCreate(tabs)
1797         document.write(_tabCreate.apply(this, arguments));
1800 function tabHigh(id)
1802         var a = E('tabs').getElementsByTagName('A');
1803         for (var i = 0; i < a.length; ++i) {
1804                 if (id != a[i].id) elem.removeClass(a[i], 'active');
1805         }
1806         elem.addClass(id, 'active');
1809 // -----------------------------------------------------------------------------
1811 var cookie = {
1812         set: function(key, value, days) {
1813                 document.cookie = 'tomato_' + key + '=' + value + '; expires=' +
1814                         (new Date(new Date().getTime() + ((days ? days : 14) * 86400000))).toUTCString() + '; path=/';
1815         },
1817         get: function(key) {
1818                 var r = ('; ' + document.cookie + ';').match('; tomato_' + key + '=(.*?);');
1819                 return r ? r[1] : null;
1820         },
1822         unset: function(key) {
1823                 document.cookie = 'tomato_' + key + '=; expires=' +
1824                         (new Date(1)).toUTCString() + '; path=/';
1825         }
1828 // -----------------------------------------------------------------------------
1830 function checkEvent(evt)
1832         if (typeof(evt) == 'undefined') {
1833                 // IE
1834                 evt = event;
1835                 evt.target = evt.srcElement;
1836                 evt.relatedTarget = evt.toElement;
1837         }
1838         return evt;
1841 function W(s)
1843         document.write(s);
1846 function E(e)
1848         return (typeof(e) == 'string') ? document.getElementById(e) : e;
1851 function PR(e)
1853         return elem.parentElem(e, 'TR');
1856 function THIS(obj, func)
1858         return function() { return func.apply(obj, arguments); }
1861 function UT(v)
1863         return (typeof(v) == 'undefined') ? '' : '' + v;
1866 function escapeHTML(s)
1868         function esc(c) {
1869                 return '&#' + c.charCodeAt(0) + ';';
1870         }
1871         return s.replace(/[&"'<>\r\n]/g, esc);
1874 function escapeCGI(s)
1876         return escape(s).replace(/\+/g, '%2B'); // escape() doesn't handle +
1879 function escapeD(s)
1881         function esc(c) {
1882                 return '%' + c.charCodeAt(0).hex(2);
1883         }
1884         return s.replace(/[<>|%]/g, esc);
1887 function ellipsis(s, max) {
1888         return (s.length <= max) ? s : s.substr(0, max - 3) + '...';
1891 function MIN(a, b)
1893         return a < b ? a : b;
1896 function MAX(a, b)
1898         return a > b ? a : b;
1901 function fixInt(n, min, max, def)
1903         if (n === null) return def;
1904         n *= 1;
1905         if (isNaN(n)) return def;
1906         if (n < min) return min;
1907         if (n > max) return max;
1908         return n;
1911 function comma(n)
1913         n = '' + n;
1914         var p = n;
1915         while ((n = n.replace(/(\d+)(\d{3})/g, '$1,$2')) != p) p = n;
1916         return n;
1919 function doScaleSize(n, sm)
1921         if (isNaN(n *= 1)) return '-';
1922         if (n <= 9999) return '' + n;
1923         var s = -1;
1924         do {
1925                 n /= 1024;
1926                 ++s;
1927         } while ((n > 9999) && (s < 2));
1928         return comma(n.toFixed(2)) + (sm ? '<small> ' : ' ') + (['KB', 'MB', 'GB'])[s] + (sm ? '</small>' : '');
1931 function scaleSize(n)
1933         return doScaleSize(n, 1);
1936 function timeString(mins)
1938         var h = Math.floor(mins / 60);
1939         if ((new Date(2000, 0, 1, 23, 0, 0, 0)).toLocaleString().indexOf('23') != -1)
1940                 return h + ':' + (mins % 60).pad(2);
1941         return ((h == 0) ? 12 : ((h > 12) ? h - 12 : h)) + ':' + (mins % 60).pad(2) + ((h >= 12) ? ' PM' : ' AM');
1944 function features(s)
1946         var features = ['ses','brau','aoss','wham','hpamp','!nve','11n'];
1947         var i;
1949         for (i = features.length - 1; i >= 0; --i) {
1950                 if (features[i] == s) return (parseInt(nvram.t_features) & (1 << i)) != 0;
1951         }
1952         return 0;
1955 function get_config(name, def)
1957         return ((typeof(nvram) != 'undefined') && (typeof(nvram[name]) != 'undefined')) ? nvram[name] : def;
1960 function nothing()
1964 // -----------------------------------------------------------------------------
1966 function show_notice1(s)
1968 // ---- !!TB - USB Support: multi-line notices
1969         if (s.length) document.write('<div id="notice1">' + s.replace(/\n/g, '<br>') + '</div><br style="clear:both">');
1972 // -----------------------------------------------------------------------------
1974 function myName()
1976         var name, i;
1978         name = document.location.pathname;
1979         name = name.replace(/\\/g, '/');        // IE local testing
1980         if ((i = name.lastIndexOf('/')) != -1) name = name.substring(i + 1, name.length);
1981         if (name == '') name = 'status-overview.asp';
1982         return name;
1985 function navi()
1987         var menu = [
1988                 ['Status',                              'status', 0, [
1989                         ['Overview',            'overview.asp'],
1990                         ['Device List',         'devices.asp'],
1991                         ['Logs',                        'log.asp'] ] ],
1992                 ['Bandwidth',                   'bwm', 0, [
1993                         ['Real-Time',           'realtime.asp'],
1994                         ['Last 24 Hours',       '24.asp'],
1995                         ['Daily',                       'daily.asp'],
1996                         ['Weekly',                      'weekly.asp'],
1997                         ['Monthly',                     'monthly.asp'] ] ],
1998                 ['Tools',                               'tools', 0, [
1999                         ['Ping',                        'ping.asp'],
2000                         ['Trace',                       'trace.asp'],
2001                         ['Wireless Survey',     'survey.asp'],
2002                         ['WOL',                         'wol.asp'] ] ],
2003                 null,
2004                 ['Basic',                               'basic', 0, [
2005                         ['Network',                     'network.asp'],
2006                         ['Identification',      'ident.asp'],
2007                         ['Time',                        'time.asp'],
2008                         ['DDNS',                        'ddns.asp'],
2009                         ['Static DHCP',         'static.asp'],
2010                         ['Wireless Filter',     'wfilter.asp'] ] ],
2011                 ['Advanced',                    'advanced', 0, [
2012                         ['Conntrack / Netfilter',       'ctnf.asp'],
2013                         ['DHCP / DNS',          'dhcpdns.asp'],
2014                         ['Firewall',            'firewall.asp'],
2015                         ['MAC Address',         'mac.asp'],
2016                         ['Miscellaneous',       'misc.asp'],
2017                         ['Routing',                     'routing.asp'],
2018                         ['Wireless',            'wireless.asp'] ] ],
2019                 ['Port Forwarding',     'forward', 0, [
2020                         ['Basic',                       'basic.asp'],
2021                         ['DMZ',                         'dmz.asp'],
2022                         ['Triggered',           'triggered.asp'],
2023                         ['UPnP / NAT-PMP',      'upnp.asp'] ] ],
2024                 ['QoS',                                 'qos', 0, [
2025                         ['Basic Settings',      'settings.asp'],
2026                         ['Classification',      'classify.asp'],
2027                         ['View Graphs',         'graphs.asp'],
2028                         ['View Details',        'detailed.asp']
2029                         ] ],
2030                 ['Access Restriction',  'restrict.asp'],
2031 /* REMOVE-BEGIN
2032                 ['Scripts',                             'sc', 0, [
2033                         ['Startup',                     'startup.asp'],
2034                         ['Shutdown',            'shutdown.asp'],
2035                         ['Firewall',            'firewall.asp'],
2036                         ['WAN Up',                      'wanup.asp']
2037                         ] ],
2038 REMOVE-END */
2039 /* USB-BEGIN */
2040 // ---- !!TB - USB, FTP, Samba
2041                 ['USB and NAS',                 'nas', 0, [
2042                         ['USB Support',         'usb.asp']
2043 /* FTP-BEGIN */
2044                         ,['FTP Server',         'ftp.asp']
2045 /* FTP-END */
2046 /* SAMBA-BEGIN */
2047                         ,['File Sharing',       'samba.asp']
2048 /* SAMBA-END */
2049                         ] ],
2050 /* USB-END */
2051                 null,
2052                 ['Administration',              'admin', 0, [
2053                         ['Admin Access',        'access.asp'],
2054                         ['Bandwidth Monitoring','bwm.asp'],
2055                         ['Buttons / LED',       'buttons.asp'],
2056 /* CIFS-BEGIN */
2057                         ['CIFS Client',         'cifs.asp'],
2058 /* CIFS-END */
2059                         ['Configuration',       'config.asp'],
2060                         ['Debugging',           'debug.asp'],
2061 /* JFFS2-BEGIN */
2062                         ['JFFS',                        'jffs2.asp'],
2063 /* JFFS2-END */
2064                         ['Logging',                     'log.asp'],
2065                         ['Scheduler',           'sched.asp'],
2066                         ['Scripts',                     'scripts.asp'],
2067                         ['Upgrade',                     'upgrade.asp'] ] ],
2068                 null,
2069                 ['About',                               'about.asp'],
2070                 ['Reboot...',                   'javascript:reboot()'],
2071                 ['Shutdown...',                 'javascript:shutdown()'],
2072                 ['Logout',                              'javascript:logout()']
2073         ];
2074         var name, base;
2075         var i, j;
2076         var buf = [];
2077         var sm;
2078         var a, b, c;
2079         var on1;
2080         var cexp = get_config('web_mx', '').toLowerCase();
2082         name = myName();
2083         if (name == 'restrict-edit.asp') name = 'restrict.asp';
2084         if ((i = name.indexOf('-')) != -1) {
2085                 base = name.substring(0, i);
2086                 name = name.substring(i + 1, name.length);
2087         }
2088         else base = '';
2090         for (i = 0; i < menu.length; ++i) {
2091                 var m = menu[i];
2092                 if (!m) {
2093                         buf.push("<br>");
2094                         continue;
2095                 }
2096                 if (m.length == 2) {
2097                         buf.push('<a href="' + m[1] + '" class="indent1' + (((base == '') && (name == m[1])) ? ' active' : '') + '">' + m[0] + '</a>');
2098                 }
2099                 else {
2100                         if (base == m[1]) {
2101                                 b = name;
2102                         }
2103                         else {
2104                                 a = cookie.get('menu_' + m[1]);
2105                                 b = m[3][0][1];
2106                                 for (j = 0; j < m[3].length; ++j) {
2107                                         if (m[3][j][1] == a) {
2108                                                 b = a;
2109                                                 break;
2110                                         }
2111                                 }
2112                         }
2113                         a = m[1] + '-' + b;
2114                         if (a == 'status-overview.asp') a = '/';
2115                         on1 = (base == m[1]);
2116                         buf.push('<a href="' + a + '" class="indent1' + (on1 ? ' active' : '') + '">' + m[0] + '</a>');
2117                         if ((!on1) && (m[2] == 0) && (cexp.indexOf(m[1]) == -1)) continue;
2119                         for (j = 0; j < m[3].length; ++j) {
2120                                 sm = m[3][j];
2121                                 a = m[1] + '-' + sm[1];
2122                                 if (a == 'status-overview.asp') a = '/';
2123                                 buf.push('<a href="' + a + '" class="indent2' + (((on1) && (name == sm[1])) ? ' active' : '') + '">' + sm[0] + '</a>');
2124                         }
2125                 }
2126         }
2127         document.write(buf.join(''));
2129         if (base.length) {
2130                 if ((base == 'qos') && (name == 'detailed.asp')) name = 'view.asp';
2131                 cookie.set('menu_' + base, name);
2132         }
2135 function createFieldTable(flags, desc)
2137         var common;
2138         var i, n;
2139         var name;
2140         var id;
2141         var fields;
2142         var f;
2143         var a;
2144         var buf = [];
2145         var buf2;
2146         var id1;
2147         var tr;
2149         if ((flags.indexOf('noopen') == -1)) buf.push('<table class="fields">');
2150         for (desci = 0; desci < desc.length; ++desci) {
2151                 var v = desc[desci];
2153                 if (!v) {
2154                         buf.push('<tr><td colspan=2 class="spacer">&nbsp;</td></tr>');
2155                         continue;
2156                 }
2158                 if (v.ignore) continue;
2160                 buf.push('<tr');
2161                 if (v.rid) buf.push(' id="' + v.rid + '"');
2162                 if (v.hidden) buf.push(' style="display:none"');
2163                 buf.push('>');
2165                 if (v.text) {
2166                         if (v.title) {
2167                                 buf.push('<td class="title indent' + (v.indent || 1) + '">' + v.title + '</td><td class="content">' + v.text + '</td></tr>');
2168                         }
2169                         else {
2170                                 buf.push('<td colspan=2>' + v.text + '</td></tr>');
2171                         }
2172                         continue;
2173                 }
2175                 id1 = '';
2176                 buf2 = [];
2177                 buf2.push('<td class="content">');
2179                 if (v.multi) fields = v.multi;
2180                         else fields = [v];
2182                 for (n = 0; n < fields.length; ++n) {
2183                         f = fields[n];
2184                         if (f.prefix) buf2.push(f.prefix);
2186                         if ((f.type == 'radio') && (!f.id)) id = '_' + f.name + '_' + i;
2187                                 else id = (f.id ? f.id : ('_' + f.name));
2189                         if (id1 == '') id1 = id;
2191                         common = ' onchange="verifyFields(this, 1)" id="' + id + '"';
2192                         if (f.attrib) common += ' ' + f.attrib;
2193                         name = f.name ? (' name="' + f.name + '"') : '';
2195                         switch (f.type) {
2196                         case 'checkbox':
2197                                 buf2.push('<input type="checkbox"' + name + (f.value ? ' checked' : '') + ' onclick="verifyFields(this, 1)"' + common + '>');
2198                                 break;
2199                         case 'radio':
2200                                 buf2.push('<input type="radio"' + name + (f.value ? ' checked' : '') + ' onclick="verifyFields(this, 1)"' + common + '>');
2201                                 break;
2202                         case 'password':
2203                                 if (f.peekaboo) {
2204                                         switch (get_config('web_pb', '1')) {
2205                                         case '0':
2206                                                 f.type = 'text';
2207                                         case '2':
2208                                                 f.peekaboo = 0;
2209                                                 break;
2210                                         }
2211                                 }
2212                                 if (f.type == 'password') {
2213                                         common += ' autocomplete="off"';
2214                                         if (f.peekaboo) common += ' onfocus=\'peekaboo("' + id + '",1)\'';
2215                                 }
2216                                 // drop
2217                         case 'text':
2218                                 buf2.push('<input type="' + f.type + '"' + name + ' value="' + escapeHTML(UT(f.value)) + '" maxlength=' + f.maxlen + (f.size ? (' size=' + f.size) : '') + common + '>');
2219                                 break;
2220                         case 'select':
2221                                 buf2.push('<select' + name + common + '>');
2222                                 for (i = 0; i < f.options.length; ++i) {
2223                                         a = f.options[i];
2224                                         if (a.length == 1) a.push(a[0]);
2225                                         buf2.push('<option value="' + a[0] + '"' + ((a[0] == f.value) ? ' selected' : '') + '>' + a[1] + '</option>');
2226                                 }
2227                                 buf2.push('</select>');
2228                                 break;
2229                         case 'textarea':
2230                                 buf2.push('<textarea' + name + common + '>' + escapeHTML(UT(f.value)) + '</textarea>');
2231                                 break;
2232                         default:
2233                                 if (f.custom) buf2.push(f.custom);
2234                                 break;
2235                         }
2236                         if (f.suffix) buf2.push(f.suffix);
2237                 }
2238                 buf2.push('</td>');
2240                 buf.push('<td class="title indent' + (v.indent ? v.indent : 1) + '">');
2241                 if (id1 != '') buf.push('<label for="' + id + '">' + v.title + '</label></td>');
2242                         else buf.push(+ v.title + '</td>');
2244                 buf.push(buf2.join(''));
2245                 buf.push('</tr>');
2246         }
2247         if ((!flags) || (flags.indexOf('noclose') == -1)) buf.push('</table>');
2248         document.write(buf.join(''));
2251 function peekaboo(id, show)
2253         try {
2254                 var o = document.createElement('INPUT');
2255                 var e = E(id);
2256                 var name = e.name;
2257                 o.type = show ? 'text' : 'password';
2258                 o.value = e.value;
2259                 o.size = e.size;
2260                 o.maxLength = e.maxLength;
2261                 o.autocomplete = e.autocomplete;
2262                 o.title = e.title;
2263                 o.disabled = e.disabled;
2264                 o.onchange = e.onchange;
2265                 e.parentNode.replaceChild(o, e);
2266                 e = null;
2267                 o.id = id;
2268                 o.name = name;
2270                 if (show) {
2271                         o.onblur = function(ev) { setTimeout('peekaboo("' + this.id + '", 0)', 0) };
2272                         setTimeout('try { E("' + id + '").focus() } catch (ex) { }', 0)
2273                 }
2274                 else {
2275                         o.onfocus = function(ev) { peekaboo(this.id, 1); };
2276                 }
2277         }
2278         catch (ex) {
2279 //              alert(ex);
2280         }
2282 /* REMOVE-BEGIN
2283 notes:
2284  - e.type= doesn't work in IE, ok in FF
2285  - may mess keyboard tabing (bad: IE; ok: FF, Opera)... setTimeout() delay seems to help a little.
2286 REMOVE-END */
2289 // -----------------------------------------------------------------------------
2291 function reloadPage()
2293         document.location.reload(1);
2296 function reboot()
2298         if (confirm("Reboot?")) form.submitHidden('tomato.cgi', { _reboot: 1, _commit: 0, _nvset: 0 });
2301 function shutdown()
2303         if (confirm("Shutdown?")) form.submitHidden('shutdown.cgi', { });
2306 function logout()
2308         form.submitHidden('logout.asp', { });
2311 // -----------------------------------------------------------------------------
2315 // debug
2317 function isLocal()
2319         return location.href.search('file://') == 0;
2322 function console(s)