3 Copyright (C) 2006-2010 Jonathan Zarate
4 http://www.polarcloud.com/tomato/
6 For use with Tomato Firmware only.
7 No part of this file may be used without permission.
10 // -----------------------------------------------------------------------------
12 Array.prototype.find = function(v) {
13 for (var i = 0; i < this.length; ++i)
14 if (this[i] == v) return i;
18 Array.prototype.remove = function(v) {
19 for (var i = 0; i < this.length; ++i) {
28 // -----------------------------------------------------------------------------
30 String.prototype.trim = function() {
31 return this.replace(/^\s+/, '').replace(/\s+$/, '');
34 // -----------------------------------------------------------------------------
36 Number.prototype.pad = function(min) {
37 var s = this.toString();
38 while (s.length < min) s = '0' + s;
42 Number.prototype.hex = function(min)
44 var h = '0123456789ABCDEF';
48 s = h.charAt(n & 15) + s;
50 } while ((--min > 0) || (n > 0));
54 // -----------------------------------------------------------------------------
56 // ---- Element.protoype. doesn't work with all browsers
59 getOffset: function(e) {
60 var r = { x: 0, y: 0 };
62 while (e.offsetParent) {
70 addClass: function(e, name) {
71 if ((e = E(e)) == null) return;
72 var a = e.className.split(/\s+/);
74 for (var i = 1; i < arguments.length; ++i) {
75 if (a.find(arguments[i]) == -1) {
80 if (k) e.className = a.join(' ');
83 removeClass: function(e, name) {
84 if ((e = E(e)) == null) return;
85 var a = e.className.split(/\s+/);
87 for (var i = 1; i < arguments.length; ++i)
88 k |= a.remove(arguments[i]);
89 if (k) e.className = a.join(' ');
93 if ((e = E(e)) != null) e.parentNode.removeChild(e);
96 parentElem: function(e, tagName) {
98 tagName = tagName.toUpperCase();
99 while (e.parentNode) {
101 if (e.tagName == tagName) return e;
106 display: function() {
107 var enable = arguments[arguments.length - 1];
108 for (var i = 0; i < arguments.length - 1; ++i) {
109 E(arguments[i]).style.display = enable ? '' : 'none';
113 isVisible: function(e) {
116 if ((e.style.visibility != 'visible') || (e.style.display == 'none')) return false;
122 setInnerHTML: function(e, html) {
124 if (e.innerHTML != html) e.innerHTML = html; // reduce flickering
128 // -----------------------------------------------------------------------------
131 getViewSize: function() {
132 if (window.innerHeight) {
133 return { width: window.innerWidth, height: window.innerHeight };
135 else if (document.documentElement && document.documentElement.clientHeight) {
136 return { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight };
138 return { width: document.body.clientWidth, height: document.body.clientHeight };
141 getPageOffset: function()
143 if (typeof(window.pageYOffset) != 'undefined') {
144 return { x: window.pageXOffset, y: window.pageYOffset };
146 else if ((document.documentElement) && (typeof(document.documentElement.scrollTop) != 'undefined')) {
147 return { x: document.documentElement.scrollLeft, y: document.documentElement.scrollTop };
149 return { x: document.body.scrollLeft, y: document.body.scrollTop };
153 // -----------------------------------------------------------------------------
156 getAll: function(e) {
165 for (var i = 0; i < e.childNodes.length; ++i) {
166 a = a.concat(fields.getAll(e.childNodes[i]));
172 disableAll: function(e, d) {
175 if ((typeof(e.tagName) == 'undefined') && (typeof(e) != 'string')) {
176 for (i = e.length - 1; i >= 0; --i) {
181 var a = this.getAll(E(e));
182 for (var i = a.length - 1; i >= 0; --i) {
188 selected: function(e) {
189 for (var i = 0; i < e.length; ++i) {
190 if (e[i].checked) return e[i];
194 find: function(e, value) {
195 for (var i = 0; i < e.length; ++i) {
196 if (e[i].value == value) return e[i];
203 // -----------------------------------------------------------------------------
206 submitHidden: function(url, fields) {
209 fom = document.createElement('FORM');
212 for (var f in fields) {
213 var e = document.createElement('INPUT');
219 body = document.getElementsByTagName('body')[0];
220 fom = body.appendChild(fom);
222 body.removeChild(fom);
225 submit: function(fom, async, url) {
226 var e, v, f, i, wait, msg, sb, cb;
231 this.dump(fom, async, url);
235 if (this.xhttp) return;
237 if ((sb = E('save-button')) != null) sb.disabled = 1;
238 if ((cb = E('cancel-button')) != null) cb.disabled = 1;
240 if ((!async) || (!useAjax())) {
242 if (url) fom.action = url;
249 for (var i = 0; i < fom.elements.length; ++i) {
251 if ((f.disabled) || (f.name == '') || (f.name.substr(0, 2) == 'f_')) continue;
252 if ((f.tagName == 'INPUT') && ((f.type == 'CHECKBOX') || (f.type == 'RADIO')) && (!f.checked)) continue;
253 if (f.name == '_nextwait') {
255 if (isNaN(wait)) wait = 5;
256 else wait = Math.abs(wait);
258 v.push(escapeCGI(f.name) + '=' + escapeCGI(f.value));
261 if ((msg = E('footer-msg')) != null) {
262 msg.innerHTML = 'Saving...';
263 msg.style.visibility = 'visible';
266 this.xhttp = new XmlHttp();
267 this.xhttp.onCompleted = function(text, xml) {
269 if (text.match(/@msg:(.+)/)) msg.innerHTML = escapeHTML(RegExp.$1);
270 else msg.innerHTML = 'Saved';
274 if (sb) sb.disabled = 0;
275 if (cb) cb.disabled = 0;
276 if (msg) msg.style.visibility = 'hidden';
277 if (typeof(submit_complete) != 'undefined') submit_complete();
281 this.xhttp.onError = function(x) {
282 if (url) fom.action = url;
286 this.xhttp.post(url ? url : fom.action, v.join('&'));
289 addId: function(fom) {
292 if (typeof(fom._http_id) == 'undefined') {
293 e = document.createElement('INPUT');
296 e.value = nvram.http_id;
300 fom._http_id.value = nvram.http_id;
304 addIdAction: function(fom) {
305 if (fom.action.indexOf('?') != -1) fom.action += '&_http_id=' + nvram.http_id;
306 else fom.action += '?_http_id=' + nvram.http_id;
309 dump: function(fom, async, url) {
313 // -----------------------------------------------------------------------------
316 set: function(e, message, quiet) {
317 if ((e = E(e)) == null) return;
318 e._error_msg = message;
319 e._error_org = e.title;
321 elem.addClass(e, 'error');
322 if (!quiet) this.show(e);
326 if ((e = E(e)) == null) return;
327 e.title = e._error_org || '';
328 elem.removeClass(e, 'error');
333 clearAll: function(e) {
334 for (var i = 0; i < e.length; ++i)
339 if ((e = E(e)) == null) return;
340 if (!e._error_msg) return;
341 elem.addClass(e, 'error-focused');
344 elem.removeClass(e, 'error-focused');
348 if ((e = E(e)) == null) return 0;
349 return !e._error_msg;
353 // -----------------------------------------------------------------------------
355 function fixFile(name)
358 if (((i = name.lastIndexOf('/')) > 0) || ((i = name.lastIndexOf('\\')) > 0))
359 name = name.substring(i + 1, name.length);
363 function _v_range(e, quiet, min, max, name)
365 if ((e = E(e)) == null) return 0;
367 if ((!v.match(/^ *[-\+]?\d+ *$/)) || (v < min) || (v > max)) {
368 ferror.set(e, 'Invalid ' + name + '. Valid range: ' + min + '-' + max, quiet);
376 function v_range(e, quiet, min, max)
378 return _v_range(e, quiet, min, max, 'number');
381 function v_port(e, quiet)
383 return _v_range(e, quiet, 1, 0xFFFF, 'port');
386 function v_octet(e, quiet)
388 return _v_range(e, quiet, 1, 254, 'address');
391 function v_mins(e, quiet, min, max)
395 if ((e = E(e)) == null) return 0;
396 if (e.value.match(/^\s*(.+?)([mhd])?\s*$/)) {
398 if (RegExp.$2 == 'h') m = 60;
399 else if (RegExp.$2 == 'd') m = 60 * 24;
400 v = Math.round(RegExp.$1 * m);
403 return _v_range(e, quiet, min, max, 'minutes');
406 ferror.set(e, 'Invalid number of minutes.', quiet);
410 function v_macip(e, quiet, bok, ipp)
412 var s, a, b, c, d, i;
414 if ((e = E(e)) == null) return 0;
415 s = e.value.replace(/\s+/g, '');
417 if ((a = fixMAC(s)) != null) {
423 ferror.set(e, 'Invalid MAC or IP address');
434 ferror.set(e, 'Invalid IP address range', quiet);
438 for (i = 0; i < a.length; ++i) {
440 if (b.match(/^\d+$/)) b = ipp + b;
444 ferror.set(e, 'Invalid IP address', quiet);
448 if (b.indexOf(ipp) != 0) {
449 ferror.set(e, 'IP address outside of LAN', quiet);
453 d = (b.split('.'))[3];
455 ferror.set(e, 'Invalid IP address range', quiet);
461 e.value = ipp + a.join('-');
465 function fixIP(ip, x)
470 if (a.length != 4) return null;
471 for (i = 0; i < 4; ++i) {
473 if ((isNaN(n)) || (n < 0) || (n > 255)) return null;
476 if ((x) && ((a[3] == 0) || (a[3] == 255))) return null;
480 function v_ip(e, quiet, x)
484 if ((e = E(e)) == null) return 0;
485 ip = fixIP(e.value, x);
487 ferror.set(e, 'Invalid IP address', quiet);
495 function v_ipz(e, quiet)
497 if ((e = E(e)) == null) return 0;
498 if (e.value == '') e.value = '0.0.0.0';
499 return v_ip(e, quiet);
502 function v_dns(e, quiet)
504 if ((e = E(e)) == null) return 0;
509 var s = e.value.split(':');
513 else if (s.length != 2) {
514 ferror.set(e, 'Invalid IP address or port', quiet);
518 if ((s[0] = fixIP(s[0])) == null) {
519 ferror.set(e, 'Invalid IP address', quiet);
523 if ((s[1] = fixPort(s[1], -1)) == -1) {
524 ferror.set(e, 'Invalid port', quiet);
532 e.value = s.join(':');
544 // ---- this is goofy because << mangles numbers as signed
547 for (i = 0; i < 4; ++i) x += (o[i] * 1).hex(2);
548 return parseInt(x, 16);
553 return ((ip >> 24) & 255) + '.' + ((ip >> 16) & 255) + '.' + ((ip >> 8) & 255) + '.' + (ip & 255);
557 // ---- 1.2.3.4, 1.2.3.4/24, 1.2.3.4/255.255.255.0, 1.2.3.4-1.2.3.5
558 function _v_iptip(e, ip, quiet)
560 var ma, x, y, z, oip;
566 if (ip.match(/^(.*)-(.*)$/)) {
567 a = fixIP(RegExp.$1);
568 b = fixIP(RegExp.$2);
569 if ((a == null) || (b == null)) {
570 ferror.set(e, 'Invalid IP address range - ' + oip, quiet);
575 if (aton(a) > aton(b)) return b + '-' + a;
583 if (ip.match(/^(.*)\/(.*)$/)) {
590 if ((ma == null) || (!_v_netmask(ma))) {
591 ferror.set(e, 'Invalid netmask - ' + oip, quiet);
596 if ((ma < 0) || (ma > 32)) {
597 ferror.set(e, 'Invalid netmask - ' + oip, quiet);
605 ferror.set(e, 'Invalid IP address - ' + oip, quiet);
610 return ip + ((ma != '') ? ('/' + ma) : '');
613 function v_iptip(e, quiet, multi)
617 if ((e = E(e)) == null) return 0;
618 v = e.value.split(',');
620 if (v.length > multi) {
621 ferror.set(e, 'Too many IP addresses', quiet);
627 ferror.set(e, 'Invalid IP address', quiet);
631 for (i = 0; i < v.length; ++i) {
632 if ((v[i] = _v_iptip(e, v[i], quiet)) == null) return 0;
634 e.value = v.join(', ');
638 function _v_domain(e, dom, quiet)
642 s = dom.replace(/\s+/g, ' ').trim();
644 if ((s.search(/^[a-zA-Z0-9][.a-zA-Z0-9_\- ]+$/) == -1) ||
645 (s.search(/\-$/) >= 0)) {
646 ferror.set(e, "Invalid name. Only characters \"A-Z 0-9 . - _\" are allowed.", quiet);
654 function v_domain(e, quiet)
658 if ((e = E(e)) == null) return 0;
659 if ((v = _v_domain(e, e.value, quiet)) == null) return 0;
666 function ExpandIPv6Address(ip)
668 var a, pre, n, i, fill, post;
670 ip = ip.toLowerCase();
671 if (!ip.match(/^(::)?([a-f0-9]{1,4}::?){0,7}([a-f0-9]{1,4})(::)?$/)) return null;
676 if (a[0] == '') return null;
677 pre = a[0].split(':');
678 if (pre.length != 8) return null;
682 pre = a[0].split(':');
683 post = a[1].split(':');
684 n = 8 - pre.length - post.length;
685 for (i=0; i<2; i++) {
688 if (n < 0) return null;
690 while (n-- > 0) fill += ':0';
691 ip = pre.join(':') + fill + ':' + post.join(':');
692 ip = ip.replace(/^:/, '').replace(/:$/, '');
698 ip = ip.replace(/([a-f0-9]{1,4})/ig, '000$1');
699 ip = ip.replace(/0{0,3}([a-f0-9]{4})/ig, '$1');
703 function CompressIPv6Address(ip)
707 ip = ExpandIPv6Address(ip);
708 if (!ip) return null;
710 if (ip.match(/(?:^00)|(?:^fe[8-9a-b])|(?:^ff)/)) return null; // not valid routable unicast address
712 ip = ip.replace(/(^|:)0{1,3}/g, '$1');
713 ip = ip.replace(/(:0)+$/, '::');
714 ip = ip.replace(/(:0){2,}(:[a-f0-9]{1,4})$/, ':$2');
722 ip = ExpandIPv6Address(ip);
727 for (i = 0; i < 8; ++i) x += (('0x' + o[i]) * 1).hex(4);
728 return parseInt(x, 16);
731 function _v_ipv6_addr(e, ip, ipt, quiet)
739 if ((ipt) && ip.match(/^(.*)-(.*)$/)) {
740 a = CompressIPv6Address(RegExp.$1);
741 b = CompressIPv6Address(RegExp.$2);
742 if ((a == null) || (b == null)) {
743 ferror.set(e, 'Invalid IPv6 address range - ' + oip, quiet);
748 if (ipv6ton(a) > ipv6ton(b)) return b + '-' + a;
752 ip = CompressIPv6Address(oip);
754 ferror.set(e, 'Invalid IPv6 address - ' + oip, quiet);
762 function v_ipv6_addr(e, quiet)
764 if ((e = E(e)) == null) return 0;
766 ip = _v_ipv6_addr(e, e.value, false, quiet);
767 if (ip) e.value = ip;
772 function fixPort(p, def)
774 if (def == null) def = -1;
775 if (p == null) return def;
777 if ((isNaN(p) || (p < 1) || (p > 65535) || (('' + p).indexOf('.') != -1))) return def;
781 function _v_portrange(e, quiet, v)
783 if (v.match(/^(.*)[-:](.*)$/)) {
789 if ((x == -1) || (y == -1)) {
790 ferror.set(e, 'Invalid port range: ' + v, quiet);
799 if (x == y) return x;
805 ferror.set(e, 'Invalid port', quiet);
813 function v_portrange(e, quiet)
817 if ((e = E(e)) == null) return 0;
818 v = _v_portrange(e, quiet, e.value);
819 if (v == null) return 0;
824 function v_iptport(e, quiet)
828 if ((e = E(e)) == null) return 0;
830 a = e.value.split(/[,\.]/);
833 ferror.set(e, 'Expecting a list of ports or port range.', quiet);
837 ferror.set(e, 'Only 10 ports/range sets are allowed.', quiet);
842 for (i = 0; i < a.length; ++i) {
843 v = _v_portrange(e, quiet, a[i]);
844 if (v == null) return 0;
848 e.value = q.join(',');
853 function _v_netmask(mask)
855 var v = aton(mask) ^ 0xFFFFFFFF;
856 return (((v + 1) & v) == 0);
859 function v_netmask(e, quiet)
863 if ((e = E(e)) == null) return 0;
872 else if (e.value.match(/^\s*\/\s*(\d+)\s*$/)) {
874 if ((b >= 1) && (b <= 32)) {
875 if (b == 32) n = 0xFFFFFFFF; // js quirk
876 else n = (0xFFFFFFFF >>> b) ^ 0xFFFFFFFF;
877 e.value = (n >>> 24) + '.' + ((n >>> 16) & 0xFF) + '.' + ((n >>> 8) & 0xFF) + '.' + (n & 0xFF);
882 ferror.set(e, 'Invalid netmask', quiet);
890 mac = mac.replace(/\s+/g, '').toUpperCase();
891 if (mac.length == 0) {
894 else if (mac.length == 12) {
895 mac = mac.match(/../g);
898 mac = mac.split(/[:\-]/);
899 if (mac.length != 6) return null;
901 for (i = 0; i < 6; ++i) {
903 if (t.search(/^[0-9A-F]+$/) == -1) return null;
904 if ((t = parseInt(t, 16)) > 255) return null;
907 return mac.join(':');
910 function v_mac(e, quiet)
914 if ((e = E(e)) == null) return 0;
915 mac = fixMAC(e.value);
916 if ((!mac) || (isMAC0(mac))) {
917 ferror.set(e, 'Invalid MAC address', quiet);
925 function v_macz(e, quiet)
929 if ((e = E(e)) == null) return 0;
930 mac = fixMAC(e.value);
932 ferror.set(e, 'Invalid MAC address', quiet);
940 function v_length(e, quiet, min, max)
944 if ((e = E(e)) == null) return 0;
947 if (min == undefined) min = 1;
949 ferror.set(e, 'Invalid length. Please enter at least ' + min + ' character' + (min == 1 ? '.' : 's.'), quiet);
952 max = max || e.maxlength;
954 ferror.set(e, 'Invalid length. Please reduce the length to ' + max + ' characters or less.', quiet);
962 function _v_iptaddr(e, quiet, multi, ipv4, ipv6)
966 if ((e = E(e)) == null) return 0;
967 v = e.value.split(',');
969 if (v.length > multi) {
970 ferror.set(e, 'Too many addresses', quiet);
976 ferror.set(e, 'Invalid domain name or IP address', quiet);
981 for (i = 0; i < v.length; ++i) {
982 if ((t = _v_domain(e, v[i], 1)) == null) {
984 if ((!ipv6) && (!ipv4)) {
985 if (!quiet) ferror.show(e);
988 if ((!ipv6) || ((t = _v_ipv6_addr(e, v[i], 1, 1)) == null)) {
991 if (!quiet) ferror.show(e);
994 if ((t = _v_iptip(e, v[i], 1)) == null) {
995 ferror.set(e, e._error_msg + ', or invalid domain name', quiet);
1005 e.value = v.join(', ');
1010 function v_iptaddr(e, quiet, multi)
1012 return _v_iptaddr(e, quiet, multi, 1, 0);
1015 function v_hostname(e, quiet, multi, delim)
1020 if ((e = E(e)) == null) return 0;
1021 v = (typeof(delim) == 'undefined') ? e.value.split(/\s+/) : e.value.split(delim);
1024 if (v.length > multi) {
1025 ferror.set(e, 'Too many hostnames.', quiet);
1031 ferror.set(e, 'Invalid hostname.', quiet);
1036 for (i = 0; i < v.length; ++i) {
1037 s = v[i].replace(/\s+/g, '_');
1039 if (s.length > 63) {
1040 ferror.set(e, 'Hostname length should not exceed 63 characters.', quiet);
1043 if ((s.search(/^[a-zA-Z0-9][a-zA-Z0-9_\-]+$/) == -1) ||
1044 (s.search(/\-$/) >= 0)) {
1045 ferror.set(e, 'Invalid hostname. Only characters "A-Z 0-9 _" and "-" in the middle are allowed.', quiet);
1051 e.value = v.join(' ');
1057 function v_nodelim(e, quiet, name, checklist)
1059 if ((e = E(e)) == null) return 0;
1061 e.value = e.value.trim();
1062 if (e.value.indexOf('<') != -1 ||
1063 (checklist && e.value.indexOf('>') != -1)) {
1064 ferror.set(e, 'Invalid ' + name + ': \"<\" ' + (checklist ? 'or \">\" are' : 'is') + ' not allowed.', quiet);
1071 function v_path(e, quiet, required)
1073 if ((e = E(e)) == null) return 0;
1074 if (required && !v_length(e, quiet, 1)) return 0;
1076 if (!required && e.value.trim().length == 0) {
1080 if (e.value.substr(0, 1) != '/') {
1081 ferror.set(e, 'Please start at the / root directory.', quiet);
1088 function isMAC0(mac)
1090 return (mac == '00:00:00:00:00:00');
1093 // -----------------------------------------------------------------------------
1095 function cmpIP(a, b)
1097 if ((a = fixIP(a)) == null) a = '255.255.255.255';
1098 if ((b = fixIP(b)) == null) b = '255.255.255.255';
1099 return aton(a) - aton(b);
1102 function cmpText(a, b)
1104 if (a == '') a = '\xff';
1105 if (b == '') b = '\xff';
1106 return (a < b) ? -1 : ((a > b) ? 1 : 0);
1109 function cmpInt(a, b)
1111 a = parseInt(a, 10);
1112 b = parseInt(b, 10);
1113 return ((isNaN(a)) ? -0x7FFFFFFF : a) - ((isNaN(b)) ? -0x7FFFFFFF : b);
1116 function cmpFloat(a, b)
1120 return ((isNaN(a)) ? -Number.MAX_VALUE : a) - ((isNaN(b)) ? -Number.MAX_VALUE : b);
1123 function cmpDate(a, b)
1125 return b.getTime() - a.getTime();
1128 // -----------------------------------------------------------------------------
1130 // ---- todo: cleanup this mess
1134 return elem.parentElem(e, 'TABLE').gridObj;
1137 function tgHideIcons()
1140 while ((e = document.getElementById('tg-row-panel')) != null) e.parentNode.removeChild(e);
1143 // ---- options = sort, move, delete
1144 function TomatoGrid(tb, options, maxAdd, editorFields)
1146 this.init(tb, options, maxAdd, editorFields);
1150 TomatoGrid.prototype = {
1151 init: function(tb, options, maxAdd, editorFields) {
1154 this.tb.gridObj = this;
1159 if (!options) options = '';
1163 this.canSort = options.indexOf('sort') != -1;
1164 this.canMove = options.indexOf('move') != -1;
1165 this.maxAdd = maxAdd || 140;
1166 this.canEdit = (editorFields != null);
1167 this.canDelete = this.canEdit || (options.indexOf('delete') != -1);
1168 this.editorFields = editorFields;
1169 this.sortColumn = -1;
1170 this.sortAscending = true;
1173 _insert: function(at, cells, escCells) {
1177 tr = this.tb.insertRow(at);
1178 for (i = 0; i < cells.length; ++i) {
1180 if (typeof(c) == 'string') {
1181 td = tr.insertCell(i);
1182 td.className = 'co' + (i + 1);
1183 if (escCells) td.appendChild(document.createTextNode(c));
1184 else td.innerHTML = c;
1195 headerClick: function(cell) {
1197 this.sort(cell.cellN);
1201 headerSet: function(cells, escCells) {
1204 elem.remove(this.header);
1205 this.header = e = this._insert(0, cells, escCells);
1206 e.className = 'header';
1208 for (i = 0; i < e.cells.length; ++i) {
1209 e.cells[i].cellN = i; // cellIndex broken in Safari
1210 e.cells[i].onclick = function() { return TGO(this).headerClick(this); };
1217 footerClick: function(cell) {
1220 footerSet: function(cells, escCells) {
1223 elem.remove(this.footer);
1224 this.footer = e = this._insert(-1, cells, escCells);
1225 e.className = 'footer';
1226 for (i = 0; i < e.cells.length; ++i) {
1227 e.cells[i].cellN = i;
1228 e.cells[i].onclick = function() { TGO(this).footerClick(this) };
1239 TGO(e).moving = null;
1240 i = e.previousSibling;
1241 if (i == this.header) return;
1242 e.parentNode.removeChild(e);
1243 i.parentNode.insertBefore(e, i);
1253 TGO(e).moving = null;
1255 if (i == this.footer) return;
1256 e.parentNode.removeChild(e);
1257 i.parentNode.insertBefore(e, i.nextSibling);
1263 rpMo: function(img, e) {
1268 if (me.moving == e) {
1274 img.style.border = "1px dotted red";
1277 rpDel: function(e) {
1279 TGO(e).moving = null;
1280 e.parentNode.removeChild(e);
1285 rpMouIn: function(evt) {
1286 var e, x, ofs, me, s, n;
1288 if ((evt = checkEvent(evt)) == null) return;
1290 me = TGO(evt.target);
1291 if (me.isEditing()) return;
1292 if (me.moving) return;
1295 e = document.createElement('div');
1298 e.setAttribute('id', 'tg-row-panel');
1303 s = '<img src="rpu.gif" onclick="this.parentNode.tgo.rpUp(this.parentNode.ref)" title="Move Up"><img src="rpd.gif" onclick="this.parentNode.tgo.rpDn(this.parentNode.ref)" title="Move Down"><img src="rpm.gif" onclick="this.parentNode.tgo.rpMo(this,this.parentNode.ref)" title="Move">';
1307 s += '<img src="rpx.gif" onclick="this.parentNode.tgo.rpDel(this.parentNode.ref)" title="Delete">';
1311 x = x.cells[x.cells.length - 1];
1312 ofs = elem.getOffset(x);
1314 e.style.left = (ofs.x + x.offsetWidth - n) + 'px';
1315 e.style.top = ofs.y + 'px';
1316 e.style.width = n + 'px';
1319 document.body.appendChild(e);
1322 rpHide: tgHideIcons,
1326 onClick: function(cell) {
1329 var p = this.moving.parentNode;
1331 if (this.moving != q) {
1332 var v = this.moving.rowIndex > q.rowIndex;
1333 p.removeChild(this.moving);
1334 if (v) p.insertBefore(this.moving, q);
1335 else p.insertBefore(this.moving, q.nextSibling);
1346 insert: function(at, data, cells, escCells) {
1349 if ((this.footer) && (at == -1)) at = this.footer.rowIndex;
1350 e = this._insert(at, cells, escCells);
1351 e.className = (e.rowIndex & 1) ? 'even' : 'odd';
1353 for (i = 0; i < e.cells.length; ++i) {
1354 e.cells[i].onclick = function() { return TGO(this).onClick(this); };
1358 e.getRowData = function() { return this._data; }
1359 e.setRowData = function(data) { this._data = data; }
1361 if ((this.canMove) || (this.canEdit) || (this.canDelete)) {
1362 e.onmouseover = this.rpMouIn;
1363 // ---- e.onmouseout = this.rpMouOut;
1364 if (this.canEdit) e.title = 'Click to edit';
1372 insertData: function(at, data) {
1373 return this.insert(at, data, this.dataToView(data), false);
1376 dataToView: function(data) {
1378 for (var i = 0; i < data.length; ++i) {
1379 var s = escapeHTML('' + data[i]);
1380 if (this.editorFields && this.editorFields.length > i) {
1381 var ef = this.editorFields[i].multi;
1382 if (!ef) ef = [this.editorFields[i]];
1383 var f = (ef && ef.length > 0 ? ef[0] : null);
1384 if (f && f.type == 'password') {
1385 if (!f.peekaboo || get_config('web_pb', '1') != '0')
1386 s = s.replace(/./g, '●');
1394 dataToFieldValues: function(data) {
1398 fieldValuesToData: function(row) {
1402 e = fields.getAll(row);
1403 for (i = 0; i < e.length; ++i) data.push(e[i].value);
1409 edit: function(cell) {
1412 if (this.isEditing()) return;
1415 sr.style.display = 'none';
1416 elem.removeClass(sr, 'hover');
1419 er = this.createEditor('edit', sr.rowIndex, sr);
1420 er.className = 'editor';
1423 c = er.cells[cell.cellIndex || 0];
1424 e = c.getElementsByTagName('input');
1425 if ((e) && (e.length > 0)) {
1433 this.controls = this.createControls('edit', sr.rowIndex);
1435 this.disableNewEditor(true);
1437 this.verifyFields(this.editor, true);
1440 createEditor: function(which, rowIndex, source) {
1443 if (which == 'edit') values = this.dataToFieldValues(source.getRowData());
1445 var row = this.tb.insertRow(rowIndex);
1446 row.className = 'editor';
1448 var common = ' onkeypress="return TGO(this).onKey(\'' + which + '\', event)" onchange="TGO(this).onChange(\'' + which + '\', this)"';
1451 for (var i = 0; i < this.editorFields.length; ++i) {
1453 var ef = this.editorFields[i].multi;
1454 if (!ef) ef = [this.editorFields[i]];
1456 for (var j = 0; j < ef.length; ++j) {
1459 if (f.prefix) s += f.prefix;
1460 var attrib = ' class="fi' + (vi + 1) + '" ' + (f.attrib || '');
1461 var id = (this.tb ? ('_' + this.tb + '_' + (vi + 1)) : null);
1462 if (id) attrib += ' id="' + id + '"';
1466 switch (get_config('web_pb', '1')) {
1474 attrib += ' autocomplete="off"';
1475 if (f.peekaboo && id) attrib += ' onfocus=\'peekaboo("' + id + '",1)\'';
1478 s += '<input type="' + f.type + '" maxlength=' + f.maxlen + common + attrib;
1479 if (which == 'edit') s += ' value="' + escapeHTML('' + values[vi]) + '">';
1483 s += '<select' + common + attrib + '>';
1484 for (var k = 0; k < f.options.length; ++k) {
1486 if (which == 'edit') {
1487 s += '<option value="' + a[0] + '"' + ((a[0] == values[vi]) ? ' selected>' : '>') + a[1] + '</option>';
1490 s += '<option value="' + a[0] + '">' + a[1] + '</option>';
1496 s += '<input type="checkbox"' + common + attrib;
1497 if ((which == 'edit') && (values[vi])) s += ' checked';
1501 s += f.custom.replace(/\$which\$/g, which);
1503 if (f.suffix) s += f.suffix;
1507 var c = row.insertCell(i);
1509 if (this.editorFields[i].vtop) c.vAlign = 'top';
1515 createControls: function(which, rowIndex) {
1518 r = this.tb.insertRow(rowIndex);
1519 r.className = 'controls';
1521 c = r.insertCell(0);
1522 c.colSpan = this.header.cells.length;
1523 if (which == 'edit') {
1525 '<input type=button value="Delete" onclick="TGO(this).onDelete()"> ' +
1526 '<input type=button value="OK" onclick="TGO(this).onOK()"> ' +
1527 '<input type=button value="Cancel" onclick="TGO(this).onCancel()">';
1531 '<input type=button value="Add" onclick="TGO(this).onAdd()">';
1536 removeEditor: function() {
1539 elem.remove(this.editor);
1542 if (this.controls) {
1543 elem.remove(this.controls);
1544 this.controls = null;
1548 showSource: function() {
1550 this.source.style.display = '';
1555 onChange: function(which, cell) {
1556 return this.verifyFields((which == 'new') ? this.newEditor : this.editor, true);
1559 onKey: function(which, ev) {
1560 switch (ev.keyCode) {
1562 if (which == 'edit') this.onCancel();
1565 if (((ev.srcElement) && (ev.srcElement.tagName == 'SELECT')) ||
1566 ((ev.target) && (ev.target.tagName == 'SELECT'))) return true;
1567 if (which == 'edit') this.onOK();
1574 onDelete: function() {
1575 this.removeEditor();
1576 elem.remove(this.source);
1578 this.disableNewEditor(false);
1581 onCancel: function() {
1582 this.removeEditor();
1584 this.disableNewEditor(false);
1590 if (!this.verifyFields(this.editor, false)) return;
1592 data = this.fieldValuesToData(this.editor);
1593 view = this.dataToView(data);
1595 this.source.setRowData(data);
1596 for (i = 0; i < this.source.cells.length; ++i) {
1597 this.source.cells[i].innerHTML = view[i];
1600 this.removeEditor();
1602 this.disableNewEditor(false);
1611 if (!this.verifyFields(this.newEditor, false)) return;
1613 data = this.fieldValuesToData(this.newEditor);
1614 this.insertData(-1, data);
1616 this.disableNewEditor(false);
1617 this.resetNewEditor();
1620 verifyFields: function(row, quiet) {
1624 showNewEditor: function() {
1627 r = this.createEditor('new', -1, null);
1628 this.footer = this.newEditor = r;
1630 r = this.createControls('new', -1);
1631 this.newControls = r;
1633 this.disableNewEditor(false);
1636 disableNewEditor: function(disable) {
1637 if (this.getDataCount() >= this.maxAdd) disable = true;
1638 if (this.newEditor) fields.disableAll(this.newEditor, disable);
1639 if (this.newControls) fields.disableAll(this.newControls, disable);
1642 resetNewEditor: function() {
1645 e = fields.getAll(this.newEditor);
1647 for (i = 0; i < e.length; ++i) {
1649 if (f.selectedIndex) f.selectedIndex = 0;
1652 try { if (e.length) e[0].focus(); } catch (er) { }
1655 getDataCount: function() {
1657 n = this.tb.rows.length;
1658 if (this.footer) n = this.footer.rowIndex;
1659 if (this.header) n -= this.header.rowIndex + 1;
1663 sortCompare: function(a, b) {
1665 var col = obj.sortColumn;
1666 var r = cmpText(a.cells[col].innerHTML, b.cells[col].innerHTML);
1667 return obj.sortAscending ? r : -r;
1670 sort: function(column) {
1671 if (this.editor) return;
1673 if (this.sortColumn >= 0) {
1674 elem.removeClass(this.header.cells[this.sortColumn], 'sortasc', 'sortdes');
1676 if (column == this.sortColumn) {
1677 this.sortAscending = !this.sortAscending;
1680 this.sortAscending = true;
1681 this.sortColumn = column;
1683 elem.addClass(this.header.cells[column], this.sortAscending ? 'sortasc' : 'sortdes');
1688 resort: function() {
1689 if ((this.sortColumn < 0) || (this.getDataCount() == 0) || (this.editor)) return;
1691 var p = this.header.parentNode;
1693 var i, j, max, e, p;
1698 top = this.header ? this.header.rowIndex + 1 : 0;
1699 max = this.footer ? this.footer.rowIndex : this.tb.rows.length;
1700 for (i = top; i < max; ++i) a.push(p.rows[i]);
1701 a.sort(THIS(this, this.sortCompare));
1702 this.removeAllData();
1704 for (i = 0; i < a.length; ++i) {
1705 e = p.insertBefore(a[i], this.footer);
1706 e.className = (j & 1) ? 'even' : 'odd';
1711 recolor: function() {
1714 i = this.header ? this.header.rowIndex + 1 : 0;
1715 e = this.footer ? this.footer.rowIndex : this.tb.rows.length;
1716 for (; i < e; ++i) {
1717 o = this.tb.rows[i];
1718 o.className = (o.rowIndex & 1) ? 'even' : 'odd';
1722 removeAllData: function() {
1725 i = this.header ? this.header.rowIndex + 1 : 0;
1726 count = (this.footer ? this.footer.rowIndex : this.tb.rows.length) - i;
1727 while (count-- > 0) elem.remove(this.tb.rows[i]);
1730 getAllData: function() {
1731 var i, max, data, r;
1734 max = this.footer ? this.footer.rowIndex : this.tb.rows.length;
1735 for (i = this.header ? this.header.rowIndex + 1 : 0; i < max; ++i) {
1736 r = this.tb.rows[i];
1737 if ((r.style.display != 'none') && (r._data)) data.push(r._data);
1742 isEditing: function() {
1743 return (this.editor != null);
1748 // -----------------------------------------------------------------------------
1751 function xmlHttpObj()
1755 ob = new XMLHttpRequest();
1760 ob = new ActiveXObject('Microsoft.XMLHTTP');
1768 var _holdAjax = null;
1772 if (_useAjax == -1) _useAjax = ((_holdAjax = xmlHttpObj()) != null);
1778 if ((!useAjax()) || ((this.xob = xmlHttpObj()) == null)) return null;
1782 XmlHttp.prototype = {
1783 addId: function(vars) {
1784 if (vars) vars += '&';
1786 vars += '_http_id=' + escapeCGI(nvram.http_id);
1790 get: function(url, vars) {
1792 vars = this.addId(vars);
1795 this.xob.onreadystatechange = THIS(this, this.onReadyStateChange);
1796 this.xob.open('GET', url, true);
1797 this.xob.send(null);
1804 post: function(url, vars) {
1806 vars = this.addId(vars);
1808 this.xob.onreadystatechange = THIS(this, this.onReadyStateChange);
1809 this.xob.open('POST', url, true);
1810 this.xob.send(vars);
1819 this.xob.onreadystatechange = function () { }
1826 onReadyStateChange: function() {
1828 if (typeof(E) == 'undefined') return; // oddly late? testing for bug...
1830 if (this.xob.readyState == 4) {
1831 if (this.xob.status == 200) {
1832 this.onCompleted(this.xob.responseText, this.xob.responseXML);
1835 this.onError('' + (this.xob.status || 'unknown'));
1844 onCompleted: function(text, xml) { },
1845 onError: function(ex) { }
1849 // -----------------------------------------------------------------------------
1852 function TomatoTimer(func, ms)
1855 this.onTimer = func;
1856 if (ms) this.start(ms);
1860 TomatoTimer.prototype = {
1861 start: function(ms) {
1863 this.tid = setTimeout(THIS(this, this._onTimer), ms);
1867 clearTimeout(this.tid);
1872 isRunning: function() {
1873 return (this.tid != null);
1876 _onTimer: function() {
1881 onTimer: function() {
1886 // -----------------------------------------------------------------------------
1889 function TomatoRefresh(actionURL, postData, refreshTime, cookieTag)
1891 this.setup(actionURL, postData, refreshTime, cookieTag);
1892 this.timer = new TomatoTimer(THIS(this, this.start));
1895 TomatoRefresh.prototype = {
1898 setup: function(actionURL, postData, refreshTime, cookieTag) {
1901 this.actionURL = actionURL;
1902 this.postData = postData;
1903 this.refreshTime = refreshTime * 1000;
1904 this.cookieTag = cookieTag;
1910 if ((e = E('refresh-time')) != null) {
1911 if (this.cookieTag) cookie.set(this.cookieTag, e.value);
1912 this.refreshTime = e.value * 1000;
1916 this.updateUI('start');
1919 if ((this.http = new XmlHttp()) == null) {
1924 this.http.parent = this;
1926 this.http.onCompleted = function(text, xml) {
1927 var p = this.parent;
1929 if (p.cookieTag) cookie.unset(p.cookieTag + '-error');
1937 if ((p.refreshTime > 0) && (!p.once)) {
1939 p.timer.start(Math.round(p.refreshTime));
1948 this.http.onError = function(ex) {
1949 var p = this.parent;
1950 if ((!p) || (!p.running)) return;
1954 if (++p.errors <= 3) {
1956 p.timer.start(3000);
1961 var e = cookie.get(p.cookieTag + '-error') * 1;
1962 if (isNaN(e)) e = 0;
1964 cookie.unset(p.cookieTag);
1965 cookie.set(p.cookieTag + '-error', e, 1);
1967 alert('XMLHTTP: ' + ex);
1972 setTimeout(reloadPage, 2000);
1976 this.http.post(this.actionURL, this.postData);
1980 if (this.cookieTag) cookie.set(this.cookieTag, -(this.refreshTime / 1000));
1982 this.updateUI('stop');
1985 this.once = undefined;
1988 toggle: function(delay) {
1989 if (this.running) this.stop();
1990 else this.start(delay);
1993 updateUI: function(mode) {
1996 if (typeof(E) == 'undefined') return; // for a bizzare bug...
1998 b = (mode != 'stop') && (this.refreshTime > 0);
1999 if ((e = E('refresh-button')) != null) {
2000 e.value = b ? 'Stop' : 'Refresh';
2001 e.disabled = ((mode == 'start') && (!b));
2003 if ((e = E('refresh-time')) != null) e.disabled = b;
2004 if ((e = E('refresh-spinner')) != null) e.style.visibility = b ? 'visible' : 'hidden';
2007 initPage: function(delay, def) {
2010 e = E('refresh-time');
2011 if (((this.cookieTag) && (e != null)) &&
2012 ((v = cookie.get(this.cookieTag)) != null) && (!isNaN(v *= 1))) {
2013 e.value = Math.abs(v);
2014 if (v > 0) v = (v * 1000) + (delay || 0);
2018 if (e) e.value = def;
2029 this.refreshTime = v;
2030 this.timer.start(v);
2031 this.updateUI('wait');
2036 function genStdTimeList(id, zero, min)
2039 var t = [3,4,5,10,15,30,60,120,180,240,300,10*60,15*60,20*60,30*60];
2043 b.push('<select id="' + id + '"><option value=0>' + zero);
2044 for (i = 0; i < t.length; ++i) {
2046 if (v < min) continue;
2047 b.push('<option value=' + v + '>');
2048 if (v == 60) b.push('1 minute');
2049 else if (v > 60) b.push((v / 60) + ' minutes');
2050 else b.push(v + ' seconds');
2052 b.push('</select> ');
2054 document.write(b.join(''));
2057 function genStdRefresh(spin, min, exec)
2059 W('<div style="text-align:right">');
2060 if (spin) W('<img src="spin.gif" id="refresh-spinner"> ');
2061 genStdTimeList('refresh-time', 'Auto Refresh', min);
2062 W('<input type="button" value="Refresh" onclick="' + (exec ? exec : 'refreshClick()') + '" id="refresh-button"></div>');
2066 // -----------------------------------------------------------------------------
2069 function _tabCreate(tabs)
2072 buf.push('<ul id="tabs">');
2073 for (var i = 0; i < arguments.length; ++i)
2074 buf.push('<li><a href="javascript:tabSelect(\'' + arguments[i][0] + '\')" id="' + arguments[i][0] + '">' + arguments[i][1] + '</a>');
2075 buf.push('</ul><div id="tabs-bottom"></div>');
2076 return buf.join('');
2079 function tabCreate(tabs)
2081 document.write(_tabCreate.apply(this, arguments));
2084 function tabHigh(id)
2086 var a = E('tabs').getElementsByTagName('A');
2087 for (var i = 0; i < a.length; ++i) {
2088 if (id != a[i].id) elem.removeClass(a[i], 'active');
2090 elem.addClass(id, 'active');
2093 // -----------------------------------------------------------------------------
2096 set: function(key, value, days) {
2097 document.cookie = 'tomato_' + key + '=' + value + '; expires=' +
2098 (new Date(new Date().getTime() + ((days ? days : 14) * 86400000))).toUTCString() + '; path=/';
2101 get: function(key) {
2102 var r = ('; ' + document.cookie + ';').match('; tomato_' + key + '=(.*?);');
2103 return r ? r[1] : null;
2106 unset: function(key) {
2107 document.cookie = 'tomato_' + key + '=; expires=' +
2108 (new Date(1)).toUTCString() + '; path=/';
2112 // -----------------------------------------------------------------------------
2114 function checkEvent(evt)
2116 if (typeof(evt) == 'undefined') {
2119 evt.target = evt.srcElement;
2120 evt.relatedTarget = evt.toElement;
2132 return (typeof(e) == 'string') ? document.getElementById(e) : e;
2137 return elem.parentElem(e, 'TR');
2140 function THIS(obj, func)
2142 return function() { return func.apply(obj, arguments); }
2147 return (typeof(v) == 'undefined') ? '' : '' + v;
2150 function escapeHTML(s)
2153 return '&#' + c.charCodeAt(0) + ';';
2155 return s.replace(/[&"'<>\r\n]/g, esc);
2158 function escapeCGI(s)
2160 return escape(s).replace(/\+/g, '%2B'); // escape() doesn't handle +
2166 return '%' + c.charCodeAt(0).hex(2);
2168 return s.replace(/[<>|%]/g, esc);
2171 function ellipsis(s, max) {
2172 return (s.length <= max) ? s : s.substr(0, max - 3) + '...';
2177 return a < b ? a : b;
2182 return a > b ? a : b;
2185 function fixInt(n, min, max, def)
2187 if (n === null) return def;
2189 if (isNaN(n)) return def;
2190 if (n < min) return min;
2191 if (n > max) return max;
2199 while ((n = n.replace(/(\d+)(\d{3})/g, '$1,$2')) != p) p = n;
2203 function doScaleSize(n, sm)
2205 if (isNaN(n *= 1)) return '-';
2206 if (n <= 9999) return '' + n;
2211 } while ((n > 9999) && (s < 2));
2212 return comma(n.toFixed(2)) + (sm ? '<small> ' : ' ') + (['KB', 'MB', 'GB'])[s] + (sm ? '</small>' : '');
2215 function scaleSize(n)
2217 return doScaleSize(n, 1);
2220 function timeString(mins)
2222 var h = Math.floor(mins / 60);
2223 if ((new Date(2000, 0, 1, 23, 0, 0, 0)).toLocaleString().indexOf('23') != -1)
2224 return h + ':' + (mins % 60).pad(2);
2225 return ((h == 0) ? 12 : ((h > 12) ? h - 12 : h)) + ':' + (mins % 60).pad(2) + ((h >= 12) ? ' PM' : ' AM');
2228 function features(s)
2230 var features = ['ses','brau','aoss','wham','hpamp','!nve','11n','1000et'];
2233 for (i = features.length - 1; i >= 0; --i) {
2234 if (features[i] == s) return (parseInt(nvram.t_features) & (1 << i)) != 0;
2239 function get_config(name, def)
2241 return ((typeof(nvram) != 'undefined') && (typeof(nvram[name]) != 'undefined')) ? nvram[name] : def;
2248 // -----------------------------------------------------------------------------
2250 function show_notice1(s)
2252 // ---- !!TB - USB Support: multi-line notices
2253 if (s.length) document.write('<div id="notice1">' + s.replace(/\n/g, '<br>') + '</div><br style="clear:both">');
2256 // -----------------------------------------------------------------------------
2262 name = document.location.pathname;
2263 name = name.replace(/\\/g, '/'); // IE local testing
2264 if ((i = name.lastIndexOf('/')) != -1) name = name.substring(i + 1, name.length);
2265 if (name == '') name = 'status-overview.asp';
2272 ['Status', 'status', 0, [
2273 ['Overview', 'overview.asp'],
2274 ['Device List', 'devices.asp'],
2275 ['Web Usage', 'webmon.asp'],
2276 ['Logs', 'log.asp'] ] ],
2277 ['Bandwidth', 'bwm', 0, [
2278 ['Real-Time', 'realtime.asp'],
2279 ['Last 24 Hours', '24.asp'],
2280 ['Daily', 'daily.asp'],
2281 ['Weekly', 'weekly.asp'],
2282 ['Monthly', 'monthly.asp'] ] ],
2284 ['IP Traffic', 'ipt', 0, [
2285 ['Real-Time', 'realtime.asp'],
2286 ['Last 24 Hours', '24.asp'],
2287 ['Transfer Rates', 'details.asp'],
2288 ['Daily', 'daily.asp'],
2289 ['Monthly', 'monthly.asp'] ] ],
2290 ['Tools', 'tools', 0, [
2291 ['Ping', 'ping.asp'],
2292 ['Trace', 'trace.asp'],
2293 ['System', 'shell.asp'],
2294 ['Wireless Survey', 'survey.asp'],
2295 ['WOL', 'wol.asp'] ] ],
2297 ['Basic', 'basic', 0, [
2298 ['Network', 'network.asp'],
2300 ['IPv6', 'ipv6.asp'],
2302 ['Identification', 'ident.asp'],
2303 ['Time', 'time.asp'],
2304 ['DDNS', 'ddns.asp'],
2305 ['Static DHCP/ARP', 'static.asp'],
2306 ['Wireless Filter', 'wfilter.asp'] ] ],
2307 ['Advanced', 'advanced', 0, [
2308 ['Conntrack/Netfilter', 'ctnf.asp'],
2309 ['DHCP/DNS', 'dhcpdns.asp'],
2310 ['Firewall', 'firewall.asp'],
2311 ['MAC Address', 'mac.asp'],
2312 ['Miscellaneous', 'misc.asp'],
2313 ['Routing', 'routing.asp'],
2314 ['VLAN', 'vlan.asp'],
2315 ['LAN Access', 'access.asp'],
2316 ['Wireless', 'wireless.asp'] ] ],
2317 ['Port Forwarding', 'forward', 0, [
2318 ['Basic', 'basic.asp'],
2320 ['Basic IPv6', 'basic-ipv6.asp'],
2323 ['Triggered', 'triggered.asp'],
2324 ['UPnP/NAT-PMP', 'upnp.asp'] ] ],
2326 ['Basic Settings', 'settings.asp'],
2327 ['Classification', 'classify.asp'],
2328 ['View Graphs', 'graphs.asp'],
2329 ['View Details', 'detailed.asp'],
2330 ['Transfer Rates', 'ctrate.asp']
2332 ['Access Restriction', 'restrict.asp'],
2334 ['Scripts', 'sc', 0, [
2335 ['Startup', 'startup.asp'],
2336 ['Shutdown', 'shutdown.asp'],
2337 ['Firewall', 'firewall.asp'],
2338 ['WAN Up', 'wanup.asp']
2342 // ---- !!TB - USB, FTP, Samba, Media Server
2343 ['USB and NAS', 'nas', 0, [
2344 ['USB Support', 'usb.asp']
2346 ,['FTP Server', 'ftp.asp']
2349 ,['File Sharing', 'samba.asp']
2351 /* MEDIA-SRV-BEGIN */
2352 ,['Media Server', 'media.asp']
2357 ['VPN Tunneling', 'vpn', 0, [
2358 ['Server', 'server.asp'],
2359 ['Client', 'client.asp'] ] ],
2362 ['Administration', 'admin', 0, [
2363 ['Admin Access', 'access.asp'],
2364 ['Bandwidth Monitoring','bwm.asp'],
2365 ['IP Traffic Monitoring','iptraffic.asp'],
2366 ['Buttons/LED', 'buttons.asp'],
2368 ['CIFS Client', 'cifs.asp'],
2370 ['Configuration', 'config.asp'],
2371 ['Debugging', 'debug.asp'],
2373 ['JFFS', 'jffs2.asp'],
2375 ['Logging', 'log.asp'],
2376 ['Scheduler', 'sched.asp'],
2377 ['Scripts', 'scripts.asp'],
2379 ['SNMP', 'snmp.asp'],
2381 ['Upgrade', 'upgrade.asp'] ] ],
2383 ['About', 'about.asp'],
2384 ['Reboot...', 'javascript:reboot()'],
2385 ['Shutdown...', 'javascript:shutdown()'],
2386 ['Logout', 'javascript:logout()']
2394 var cexp = get_config('web_mx', '').toLowerCase();
2397 if (name == 'restrict-edit.asp') name = 'restrict.asp';
2398 if ((i = name.indexOf('-')) != -1) {
2399 base = name.substring(0, i);
2400 name = name.substring(i + 1, name.length);
2404 for (i = 0; i < menu.length; ++i) {
2410 if (m.length == 2) {
2411 buf.push('<a href="' + m[1] + '" class="indent1' + (((base == '') && (name == m[1])) ? ' active' : '') + '">' + m[0] + '</a>');
2418 a = cookie.get('menu_' + m[1]);
2420 for (j = 0; j < m[3].length; ++j) {
2421 if (m[3][j][1] == a) {
2428 if (a == 'status-overview.asp') a = '/';
2429 on1 = (base == m[1]);
2430 buf.push('<a href="' + a + '" class="indent1' + (on1 ? ' active' : '') + '">' + m[0] + '</a>');
2431 if ((!on1) && (m[2] == 0) && (cexp.indexOf(m[1]) == -1)) continue;
2433 for (j = 0; j < m[3].length; ++j) {
2435 a = m[1] + '-' + sm[1];
2436 if (a == 'status-overview.asp') a = '/';
2437 buf.push('<a href="' + a + '" class="indent2' + (((on1) && (name == sm[1])) ? ' active' : '') + '">' + sm[0] + '</a>');
2441 document.write(buf.join(''));
2444 if ((base == 'qos') && (name == 'detailed.asp')) name = 'view.asp';
2445 cookie.set('menu_' + base, name);
2449 function createFieldTable(flags, desc)
2463 if ((flags.indexOf('noopen') == -1)) buf.push('<table class="fields">');
2464 for (desci = 0; desci < desc.length; ++desci) {
2465 var v = desc[desci];
2468 buf.push('<tr><td colspan=2 class="spacer"> </td></tr>');
2472 if (v.ignore) continue;
2475 if (v.rid) buf.push(' id="' + v.rid + '"');
2476 if (v.hidden) buf.push(' style="display:none"');
2481 buf.push('<td class="title indent' + (v.indent || 1) + '">' + v.title + '</td><td class="content">' + v.text + '</td></tr>');
2484 buf.push('<td colspan=2>' + v.text + '</td></tr>');
2491 buf2.push('<td class="content">');
2493 if (v.multi) fields = v.multi;
2496 for (n = 0; n < fields.length; ++n) {
2498 if (f.prefix) buf2.push(f.prefix);
2500 if ((f.type == 'radio') && (!f.id)) id = '_' + f.name + '_' + i;
2501 else id = (f.id ? f.id : ('_' + f.name));
2503 if (id1 == '') id1 = id;
2505 common = ' onchange="verifyFields(this, 1)" id="' + id + '"';
2506 if (f.attrib) common += ' ' + f.attrib;
2507 name = f.name ? (' name="' + f.name + '"') : '';
2511 buf2.push('<input type="checkbox"' + name + (f.value ? ' checked' : '') + ' onclick="verifyFields(this, 1)"' + common + '>');
2514 buf2.push('<input type="radio"' + name + (f.value ? ' checked' : '') + ' onclick="verifyFields(this, 1)"' + common + '>');
2518 switch (get_config('web_pb', '1')) {
2526 if (f.type == 'password') {
2527 common += ' autocomplete="off"';
2528 if (f.peekaboo) common += ' onfocus=\'peekaboo("' + id + '",1)\'';
2532 buf2.push('<input type="' + f.type + '"' + name + ' value="' + escapeHTML(UT(f.value)) + '" maxlength=' + f.maxlen + (f.size ? (' size=' + f.size) : '') + common + '>');
2535 buf2.push('<select' + name + common + '>');
2536 for (i = 0; i < f.options.length; ++i) {
2538 if (a.length == 1) a.push(a[0]);
2539 buf2.push('<option value="' + a[0] + '"' + ((a[0] == f.value) ? ' selected' : '') + '>' + a[1] + '</option>');
2541 buf2.push('</select>');
2544 buf2.push('<textarea' + name + common + (f.wrap ? (' wrap=' + f.wrap) : '') + '>' + escapeHTML(UT(f.value)) + '</textarea>');
2547 if (f.custom) buf2.push(f.custom);
2550 if (f.suffix) buf2.push(f.suffix);
2554 buf.push('<td class="title indent' + (v.indent ? v.indent : 1) + '">');
2555 if (id1 != '') buf.push('<label for="' + id + '">' + v.title + '</label></td>');
2556 else buf.push(+ v.title + '</td>');
2558 buf.push(buf2.join(''));
2561 if ((!flags) || (flags.indexOf('noclose') == -1)) buf.push('</table>');
2562 document.write(buf.join(''));
2565 function peekaboo(id, show)
2568 var o = document.createElement('INPUT');
2571 o.type = show ? 'text' : 'password';
2574 o.maxLength = e.maxLength;
2575 o.autocomplete = e.autocomplete;
2577 o.disabled = e.disabled;
2578 o.onchange = e.onchange;
2579 e.parentNode.replaceChild(o, e);
2585 o.onblur = function(ev) { setTimeout('peekaboo("' + this.id + '", 0)', 0) };
2586 setTimeout('try { E("' + id + '").focus() } catch (ex) { }', 0)
2589 o.onfocus = function(ev) { peekaboo(this.id, 1); };
2598 - e.type= doesn't work in IE, ok in FF
2599 - may mess keyboard tabing (bad: IE; ok: FF, Opera)... setTimeout() delay seems to help a little.
2603 // -----------------------------------------------------------------------------
2605 function reloadPage()
2607 document.location.reload(1);
2612 if (confirm("Reboot?")) form.submitHidden('tomato.cgi', { _reboot: 1, _commit: 0, _nvset: 0 });
2617 if (confirm("Shutdown?")) form.submitHidden('shutdown.cgi', { });
2622 form.submitHidden('logout.asp', { });
2625 // -----------------------------------------------------------------------------
2633 return location.href.search('file://') == 0;