tinc: integration and gui
[tomato.git] / release / src / router / www / vpn-tinc.asp
blobd5af845eb2e9e4beda52958cb00d6767a4a55ae4
1 <!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0//EN'>
2 <!--
3 Tomato GUI
4 Copyright (C) 2006-2010 Jonathan Zarate
5 http://www.polarcloud.com/tomato/
7 Tinc Web GUI
8 Copyright (C) 2014 Lance Fredrickson
9 lancethepants@gmail.com
11 For use with Tomato Firmware only.
12 No part of this file may be used without permission.
13 -->
14 <html>
15 <head>
16 <meta http-equiv='content-type' content='text/html;charset=utf-8'>
17 <meta name='robots' content='noindex,nofollow'>
18 <title>[<% ident(); %>] Tinc Mesh VPN</title>
19 <link rel='stylesheet' type='text/css' href='tomato.css'>
20 <link rel='stylesheet' type='text/css' href='color.css'>
21 <script type='text/javascript' src='tomato.js'></script>
23 <!-- / / / -->
25 <style type='text/css'>
27 #th-grid .co1 {
28 width: 10%;
29 text-align: center;
31 #th-grid .co2 {
32 width: 17%;
34 #th-grid .co3 {
35 width: 29%;
37 #th-grid .co4 {
38 width: 10%;
40 #th-grid .co5 {
41 width: 14%;
43 #th-grid .co6 {
44 width: 20%;
47 textarea
49 width: 98%;
50 height: 10em;
52 </style>
55 <script type='text/javascript' src='debug.js'></script>
57 <script type='text/javascript'>
59 // <% nvram("tinc_wanup,tinc_name,tinc_devicetype,tinc_mode,tinc_vpn_netmask,tinc_private_rsa,tinc_private_ecdsa,tinc_custom,tinc_hosts,tinc_manual_firewall,tinc_manual_tinc_up,tinc_tinc_up,tinc_tinc_down,tinc_host_up,tinc_host_down,tinc_subnet_up,tinc_subnet_down"); %>
61 var tinc_compression = [['0','0 - None'],['1','1 - Fast zlib'],['2','2'],['3','3'],['4','4'],['5','5'],['6','6'],['7','7'],['8','8'],['9','9 - Best zlib'],['10','10 - Fast lzo'],['11','11 - Best lzo']];
62 var th = new TomatoGrid();
63 var cmd = null;
64 var cmdresult = '';
66 tabs = [['config', 'Config'],['hosts', 'Hosts'],['scripts', 'Scripts'],['keys', 'Generate Keys'],['status', 'Status']];
67 changed = 0;
68 tincup = parseInt ('<% psup("tincd"); %>');
70 th.setup = function() {
71 this.init('th-grid', '', 50, [
72 { type: 'checkbox' },
73 { type: 'text', maxlen: 30 },
74 { type: 'text', maxlen: 100 },
75 { type: 'text', maxlen: 5 },
76 { type: 'select', options: tinc_compression },
77 { type: 'text', maxlen: 20 },
78 { type: 'textarea', proxy: "_host_rsa_key" },
79 { type: 'textarea', proxy: "_host_ecdsa_key" },
80 { type: 'textarea', proxy: "_host_custom" }
81 ]);
82 this.headerSet(['ConnectTo', 'Name', 'Address', 'Port', 'Compression', 'Subnet']);
83 var nv = nvram.tinc_hosts.split('>');
84 for (var i = 0; i < nv.length; ++i) {
85 var t = nv[i].split('<');
86 if (t.length == 9){
87 t[0] *= 1;
88 this.insertData(-1, t);
91 th.showNewEditor();
94 th.dataToView = function(data) {
95 return [(data[0] != '0') ? 'On' : '', data[1], data[2], data[3], data[4] ,data[5] ];
98 th.fieldValuesToData = function(row) {
99 var f = fields.getAll(row);
100 return [f[0].checked ? 1 : 0, f[1].value, f[2].value, f[3].value, f[4].value, f[5].value, _host_rsa_key.value, _host_ecdsa_key.value, _host_custom.value ];
104 th.resetNewEditor = function() {
105 var f = fields.getAll(this.newEditor);
106 f[0].checked = 0;
107 f[1].value = '';
108 f[2].value = '';
109 f[3].value = '';
110 f[4].selectedIndex = 0;
111 f[5].value = '';
112 _host_rsa_key.value = '';
113 _host_ecdsa_key.value = '';
114 _host_custom.value = '';
115 ferror.clearAll(fields.getAll(this.newEditor));
116 ferror.clear(_host_rsa_key);
117 ferror.clear(_host_ecdsa_key);
120 th.verifyFields = function(row, quiet) {
122 var f = fields.getAll(row);
124 if (f[1].value == "") { ferror.set(f[1], "Host Name is required.", quiet); return 0 ; }
125 if (f[0].checked && f[2].value == "") { ferror.set(f[2], "Address must be supplied when ConnectTo is checked.", quiet); return 0 ; }
126 if (!f[3].value == "" ) {
127 if (!v_port(f[3], quiet)) return 0 ;
130 if(_tinc_devicetype.value == 'tun'){
131 if ((!v_subnet(f[5], 1)) && (!v_ip(f[5], 1))) { ferror.set(f[5], "Invalid Subnet or IP address.", quiet); return 0 ; }
133 else if (_tinc_devicetype.value == 'tap'){
134 if (f[5].value != '') { ferror.set(f[5], "Subnet is left blank when using the TAP Interface Type.", quiet); return 0 ; }
137 if (_host_rsa_key.value == "") { ferror.set(_host_rsa_key, "RSA Public Key is required.", quiet); return 0 ; }
138 if (_host_ecdsa_key.value == "") { ferror.set(_host_ecdsa_key, "ECDSA Public Key is required.", quiet); return 0 ; }
140 return 1;
143 function verifyFields(focused, quiet)
145 if (focused)
147 changed = 1;
150 // Visibility Changes
151 var vis = {
152 _tinc_mode: 1,
153 _tinc_vpn_netmask: 1,
156 switch (E('_tinc_devicetype').value) {
157 case 'tun':
158 vis._tinc_mode = 0;
159 vis._tinc_vpn_netmask = 1 ;
160 break;
161 case 'tap':
162 vis._tinc_mode = 1;
163 vis._tinc_vpn_netmask = 0 ;
164 break;
167 switch(E('_tinc_manual_tinc_up').value) {
168 case '0' :
169 _tinc_tinc_up.disabled = 1 ;
170 break;
171 case '1' :
172 _tinc_tinc_up.disabled = 0 ;
173 break;
176 for (a in vis) {
177 b = E(a);
178 c = vis[a];
179 b.disabled = (c != 1);
180 PR(b).style.display = c ? '' : 'none';
183 edges.disabled = !tincup;
184 connections.disabled = !tincup;
185 subnets.disabled = !tincup;
186 nodes.disabled = !tincup;
187 info.disabled = !tincup;
188 hostselect.disabled = !tincup;
190 // Element Verification
191 if (_tinc_name.value == "") { ferror.set(_tinc_name, "Host Name is required.", quiet); return 0 ; }
192 if (_tinc_private_rsa.value == "") { ferror.set(_tinc_private_rsa, "RSA Private Key is required.", quiet); return 0 ; }
193 if (_tinc_private_ecdsa.value == "") { ferror.set(_tinc_private_ecdsa, "ECDSA Private Key is required.", quiet); return 0 ; }
194 if (!v_netmask('_tinc_vpn_netmask', quiet)) return 0;
196 var hostdefined = false;
197 var hosts = th.getAllData();
198 for (var i = 0; i < hosts.length; ++i) {
199 if(hosts[i][1] == _tinc_name.value){
200 hostdefined = true;
201 break;
204 if (!hostdefined) { ferror.set(_tinc_name, "Host Name \"" + _tinc_name.value + "\" is not defined in hosts area.", quiet); return 0 ; }
206 return 1;
209 function escapeText(s)
211 function esc(c) {
212 return '&#' + c.charCodeAt(0) + ';';
214 return s.replace(/[&"'<>]/g, esc).replace(/\n/g, ' <br>').replace(/ /g, '&nbsp;');
217 function spin(x,which)
219 E(which).style.visibility = x ? 'visible' : 'hidden';
220 if (!x) cmd = null;
223 // Borrowed from http://snipplr.com/view/14074/
224 String.prototype.between = function(prefix, suffix) {
225 s = this;
226 var i = s.indexOf(prefix);
227 if (i >= 0) {
228 s = s.substring(i + prefix.length);
230 else {
231 return '';
233 if (suffix) {
234 i = s.indexOf(suffix);
235 if (i >= 0) {
236 s = s.substring(0, i);
238 else {
239 return '';
242 return s;
245 function displayKeys()
247 _rsa_private_key.value = "-----BEGIN RSA PRIVATE KEY-----\n" + cmdresult. between('-----BEGIN RSA PRIVATE KEY-----\n','\n-----END RSA PRIVATE KEY-----') + "\n-----END RSA PRIVATE KEY-----";
248 _rsa_public_key.value = "-----BEGIN RSA PUBLIC KEY-----\n" + cmdresult. between('-----BEGIN RSA PUBLIC KEY-----\n','\n-----END RSA PUBLIC KEY-----') + "\n-----END RSA PUBLIC KEY-----";
249 _ecdsa_private_key.value = "-----BEGIN EC PRIVATE KEY-----\n" + cmdresult. between('-----BEGIN EC PRIVATE KEY-----\n','\n-----END EC PRIVATE KEY-----') + "\n-----END EC PRIVATE KEY-----";
250 _ecdsa_public_key.value = cmdresult. between('-----END EC PRIVATE KEY-----\n','\n');
252 cmdresult = '';
253 spin(0,'generateWait');
254 E('execb').disabled = 0;
257 function generateKeys()
259 E('execb').disabled = 1;
260 spin(1,'generateWait');
262 _rsa_private_key.value = "";
263 _rsa_public_key.value = "";
264 _ecdsa_private_key.value = "";
265 _ecdsa_public_key.value = "";
267 cmd = new XmlHttp();
268 cmd.onCompleted = function(text, xml) {
269 eval(text);
270 displayKeys();
272 cmd.onError = function(x) {
273 cmdresult = 'ERROR: ' + x;
274 displayKeys();
277 var commands = "/bin/rm -rf /etc/keys \n\
278 /bin/mkdir /etc/keys \n\
279 /bin/echo -e '\n\n\n\n' | /usr/sbin/tinc -c /etc/keys generate-keys \n\
280 /bin/cat /etc/keys/rsa_key.priv \n\
281 /bin/cat /etc/keys/rsa_key.pub \n\
282 /bin/cat /etc/keys/ecdsa_key.priv \n\
283 /bin/cat /etc/keys/ecdsa_key.pub";
285 cmd.post('shell.cgi', 'action=execute&command=' + escapeCGI(commands.replace(/\r/g, '')));
289 function displayStatus()
291 E('result').innerHTML = '<tt>' + escapeText(cmdresult) + '</tt>';
292 cmdresult = '';
293 spin(0,'statusWait');
296 function updateStatus(type)
298 E('result').innerHTML = '';
299 spin(1,'statusWait');
301 cmd = new XmlHttp();
302 cmd.onCompleted = function(text, xml) {
303 eval(text);
304 displayStatus();
306 cmd.onError = function(x) {
307 cmdresult = 'ERROR: ' + x;
308 displayStatus();
311 if(type != "info"){
312 var commands = "/usr/sbin/tinc dump " + type + "\n";
314 else
316 var selects = document.getElementById("hostselect");
317 var commands = "/usr/sbin/tinc " + type + " " + selects.options[selects.selectedIndex].text + "\n";
320 cmd.post('shell.cgi', 'action=execute&command=' + escapeCGI(commands.replace(/\r/g, '')));
321 updateNodes();
324 function displayNodes()
327 var hostselect=document.getElementById("hostselect")
328 var selected = hostselect.value;
330 while(hostselect.firstChild){
331 hostselect.removeChild(hostselect.firstChild);
334 var hosts = cmdresult.split("\n");
336 for (var i = 0; i < hosts.length; ++i)
338 if (hosts[i] != ''){
339 hostselect.options[hostselect.options.length]=new Option(hosts[i],hosts[i]);
340 if(hosts[i] == selected){
341 hostselect.value = selected;
346 cmdresult = '';
349 function updateNodes()
352 if (tincup)
354 cmd = new XmlHttp();
355 cmd.onCompleted = function(text, xml) {
356 eval(text);
357 displayNodes();
359 cmd.onError = function(x) {
360 cmdresult = 'ERROR: ' + x;
361 displayNodes();
364 var commands = "/usr/sbin/tinc dump nodes | /bin/busybox awk '{print $1}'";
365 cmd.post('shell.cgi', 'action=execute&command=' + escapeCGI(commands.replace(/\r/g, '')));
369 function displayVersion()
371 E('version').innerHTML = "<small>Tinc " + escapeText(cmdresult) + "</small>";
372 cmdresult = '';
375 function getVersion()
377 cmd = new XmlHttp();
378 cmd.onCompleted = function(text, xml) {
379 eval(text);
380 displayVersion();
382 cmd.onError = function(x) {
383 cmdresult = 'ERROR: ' + x;
384 displayVersion();
387 var commands = "/usr/sbin/tinc --version | /bin/busybox awk 'NR==1 {print $3}'";
388 cmd.post('shell.cgi', 'action=execute&command=' + escapeCGI(commands.replace(/\r/g, '')));
391 function tabSelect(name)
393 tgHideIcons();
394 cookie.set('vpn_tinc_tab', name);
395 tabHigh(name);
397 for (var i = 0; i < tabs.length; ++i)
399 var on = (name == tabs[i][0]);
400 elem.display(tabs[i][0] + '-tab', on);
405 function toggle(service, isup)
408 var data = th.getAllData();
409 var s = '';
410 for (var i = 0; i < data.length; ++i) {
411 s += data[i].join('<') + '>';
414 if (nvram.tinc_hosts != s)
415 changed = 1;
417 if (changed) {
418 if (!confirm("Unsaved changes will be lost. Continue anyway?")) return;
421 E('_' + service + '_button1').disabled = true;
422 E('_' + service + '_button2').disabled = true;
423 E('_' + service + '_button3').disabled = true;
424 E('_' + service + '_button4').disabled = true;
425 form.submitHidden('/service.cgi', {
426 _redirect: 'vpn-tinc.asp',
427 _sleep: ((service == 'tinc') && (!isup)) ? '3' : '3',
428 _service: service + (isup ? '-stop' : '-start')
432 function save()
434 if (!verifyFields(null, false)) return;
435 if (th.isEditing()) return;
437 var data = th.getAllData();
438 var s = '';
439 for (var i = 0; i < data.length; ++i) {
440 s += data[i].join('<') + '>';
442 var fom = E('_fom');
443 fom.tinc_hosts.value = s;
444 fom.tinc_wanup.value = fom.f_tinc_wanup.checked ? 1 : 0;
446 if ( tincup )
448 fom._service.value = 'tinc-restart';
451 changed = 0;
453 form.submit(fom, 1);
456 function init()
458 verifyFields(null, true);
459 th.recolor();
460 th.resetNewEditor();
461 var c;
462 if (((c = cookie.get('vpn_tinc_hosts_vis')) != null) && (c == '1')) toggleVisibility("hosts");
463 getVersion();
464 updateNodes();
467 function earlyInit()
469 tabSelect(cookie.get('vpn_tinc_tab') || 'config');
472 function toggleVisibility(whichone) {
473 if (E('sesdiv_' + whichone).style.display == '') {
474 E('sesdiv_' + whichone).style.display = 'none';
475 E('sesdiv_' + whichone + '_showhide').innerHTML = '(Click here to show)';
476 cookie.set('vpn_tinc_' + whichone + '_vis', 0);
477 } else {
478 E('sesdiv_' + whichone).style.display='';
479 E('sesdiv_' + whichone + '_showhide').innerHTML = '(Click here to hide)';
480 cookie.set('vpn_tinc_' + whichone + '_vis', 1);
484 </script>
485 </head>
487 <body onload='init()'>
488 <form id='_fom' method='post' action='tomato.cgi'>
490 <table id='container' cellspacing=0>
491 <tr><td colspan=2 id='header'>
492 <div class='title'>Tomato</div>
493 <div class='version'>Version <% version(); %></div>
494 </td></tr>
495 <tr id='body'><td id='navi'><script type='text/javascript'>navi()</script></td>
496 <td id='content'>
497 <div id='ident'><% ident(); %></div>
499 <!-- / / / -->
501 <input type='hidden' name='_nextpage' value='vpn-tinc.asp'>
502 <input type='hidden' name='_service' value=''>
504 <div class='section-title' style='float:right' id='version'></div>
505 <div class='section-title'>Tinc Configuration</div>
508 <script type='text/javascript'>
510 // -------- BEGIN CONFIG TAB -----------
511 tabCreate.apply(this, tabs);
513 t = "config";
514 W('<div id=\''+t+'-tab\'>');
515 W('<br>');
516 W('<input type=\'hidden\' name=\'tinc_wanup\'>');
517 W('<div class=\'section\'>');
519 createFieldTable('', [
520 { title: 'Start With WAN ', name: 'f_tinc_wanup', type: 'checkbox', value: (nvram.tinc_wanup == 1) },
521 { title: 'Interface Type', name: 'tinc_devicetype', type: 'select', options: [['tun','TUN'],['tap','TAP']], value: nvram.tinc_devicetype },
522 { title: 'Mode', name: 'tinc_mode', type: 'select', options: [['switch','Switch'],['hub','Hub']], value: nvram.tinc_mode },
523 { title: 'VPN Netmask', name: 'tinc_vpn_netmask', type: 'text', maxlen: 15, size: 25, value: nvram.tinc_vpn_netmask, suffix: ' <small>The netmask for the entire VPN network.</small>' },
524 { title: 'Host Name', name: 'tinc_name', type: 'text', maxlen: 30, size: 25, value: nvram.tinc_name, suffix: ' <small>Must also be defined in the \'Hosts\' area.</small>' },
525 { title: 'Firewall', name: 'tinc_manual_firewall', type: 'select', options: [['0','Automatic'],['1','Manual']], value: nvram.tinc_manual_firewall },
526 { title: 'RSA Private Key', name: 'tinc_private_rsa', type: 'textarea', value: nvram.tinc_private_rsa },
527 { title: 'ECDSA Private Key', name: 'tinc_private_ecdsa', type: 'textarea', value: nvram.tinc_private_ecdsa },
528 { title: 'Custom', name: 'tinc_custom', type: 'textarea', value: nvram.tinc_custom }
531 W('</div>');
532 W('<input type="button" value="' + (tincup ? 'Stop' : 'Start') + ' Now" onclick="toggle(\'tinc\', tincup)" id="_tinc_button1">');
533 W('</div>');
534 // -------- END CONFIG TAB -----------
537 // -------- BEGIN HOSTS TAB -----------
538 t = "hosts";
539 W('<div id=\''+t+'-tab\'>');
540 W('<br>');
541 W('<div class=\'section\'>');
542 W('<input type=\'hidden\' name=\'tinc_hosts\'>');
543 W('<table class=\'tomato-grid\' cellspacing=1 id=\'th-grid\'></table>');
545 th.setup();
547 createFieldTable('', [
548 { title: 'RSA Public Key', name: 'host_rsa_key', type: 'textarea' },
549 { title: 'ECDSA Public Key', name: 'host_ecdsa_key', type: 'textarea' },
550 { title: 'Custom', name: 'host_custom', type: 'textarea' }
553 W('</div>');
554 W('<input type="button" value="' + (tincup ? 'Stop' : 'Start') + ' Now" onclick="toggle(\'tinc\', tincup)" id="_tinc_button2">');
556 W('<br>');
557 W('<br>');
559 W('<div class=\'section-title\'>Notes <small><i><a href=\'javascript:toggleVisibility(\"hosts\");\'><span id=\'sesdiv_hosts_showhide\'>(Click here to show)</span></a></i></small></div>');
560 W('<div class=\'section\' id=\'sesdiv_hosts\' style=\'display:none\'>');
561 W('<ul>');
562 W('<li><b>ConnectTo</b> - Tinc will try to establish a meta-connection to the host. Requires the Address field');
563 W('<li><b>Name</b> - Name of the host. There must be an entry for this host.');
564 W('<li><b>Address</b> <i>(optional)</i> - Must resolve to the external IP address where the host can be reached.');
565 W('<li><b>Port</b> <i>(optional)</i> - The port the host listens on. If empty the default value (655) is used.');
566 W('<li><b>Compression</b> - The level of compression used for UDP packets. Possible values are ');
567 W('0 (off), 1 (fast zlib) and any integer up to 9 (best zlib), 10 (fast lzo) and 11 (best lzo).');
568 W('<li><b>Subnet</b> - The subnet which the host will serve.');
569 W('</ul>');
570 W('</div>');
572 W('</div>');
574 // ---------- END HOSTS TAB ------------
577 // -------- BEGIN SCRIPTS TAB -----------
578 t = "scripts";
579 W('<div id=\''+t+'-tab\'>');
580 W('<br>');
581 W('<div class=\'section\'>');
583 createFieldTable('', [
584 { title: 'tinc-up creation', name: 'tinc_manual_tinc_up', type: 'select', options: [['0','Automatic'],['1','Manual']], value: nvram.tinc_manual_tinc_up },
585 { title: 'tinc-up', name: 'tinc_tinc_up', type: 'textarea', value: nvram.tinc_tinc_up },
586 { title: 'tinc-down', name: 'tinc_tinc_down', type: 'textarea', value: nvram.tinc_tinc_down },
587 { title: 'host-up', name: 'tinc_host_up', type: 'textarea', value: nvram.tinc_host_up },
588 { title: 'host-down', name: 'tinc_host_down', type: 'textarea', value: nvram.tinc_host_down },
589 { title: 'subnet-up', name: 'tinc_subnet_up', type: 'textarea', value: nvram.tinc_subnet_up },
590 { title: 'subnet-down', name: 'tinc_subnet_down', type: 'textarea', value: nvram.tinc_subnet_down }
593 W('</div>');
594 W('<input type="button" value="' + (tincup ? 'Stop' : 'Start') + ' Now" onclick="toggle(\'tinc\', tincup)" id="_tinc_button3">');
595 W('</div>');
596 // -------- END SCRIPTS TAB -----------
598 // -------- BEGIN KEYS TAB -----------
599 t = "keys";
600 W('<div id=\''+t+'-tab\'>');
601 W('<br>');
602 W('<div class=\'section\'>');
604 createFieldTable('', [
605 { title: 'RSA Private Key', name: 'rsa_private_key', type: 'textarea', value: "" },
606 { title: 'RSA Public Key', name: 'rsa_public_key', type: 'textarea', value: "" },
607 { title: 'ECDSA Private Key', name: 'ecdsa_private_key', type: 'textarea', value: "" },
608 { title: 'ECDSA Public Key', name: 'ecdsa_public_key', type: 'textarea', value: "" }
611 W('</div>');
612 W('<div style=\'float:left\'><input type=\'button\' value=\'Generate Keys\' onclick=\'generateKeys()\' id=\'execb\'></div>');
613 W('<div style=\"visibility:hidden;text-align:right\" id=\"generateWait\">Please wait... <img src=\'spin.gif\' style=\"vertical-align:top\"></div>');
614 W('</div>');
616 // -------- END KEY TAB -----------
618 // -------- BEGIN STATUS TAB -----------
619 t = "status";
621 W('<div id=\''+t+'-tab\'>');
622 W('<br>');
624 W('<div class=\'section\'>');
625 W('Tinc is currently '+(!tincup ? 'stopped.' : 'running.')+' ');
626 W('<input type="button" value="' + (tincup ? 'Stop' : 'Start') + ' Now" onclick="toggle(\'tinc\', tincup)" id="_tinc_button4">');
627 W('</div>');
630 W('<div class=\'section\'>');
632 W('<div style=\'float:left\'><input type=\'button\' value=\'Edges\' onclick=\'updateStatus(\"edges\")\' id=\'edges\' style=\"width:85px\"></div>');
633 W('<div style=\'float:left\'><input type=\'button\' value=\'Subnets\' onclick=\'updateStatus(\"subnets\")\' id=\'subnets\' style=\"width:85px\"></div>');
634 W('<div style=\'float:left\'><input type=\'button\' value=\'Connections\' onclick=\'updateStatus(\"connections\")\' id=\'connections\' style=\"width:85px\"></div>');
635 W('<div style=\'float:left\'><input type=\'button\' value=\'Nodes\' onclick=\'updateStatus(\"nodes\")\' id=\'nodes\' style=\"width:85px\"></div>');
636 W('<div style=\"visibility:hidden;text-align:right\" id=\"statusWait\">Please wait... <img src=\'spin.gif\' style=\"vertical-align:top\"></div>');
638 W('</div>');
640 W('<div class=\'section\'>');
641 W('<input type=\'button\' value=\'Info\' onclick=\'updateStatus(\"info\")\' id=\'info\' style=\"width:85px\">');
642 W('<select id=\'hostselect\' style=\"width:170px\"></select>');
643 W('</div>');
645 W('<pre id=\'result\'></pre>');
647 W('</div>');
648 // -------- END KEY TAB -----------
650 </script>
652 <!-- / / / -->
654 </td></tr>
655 <tr><td id='footer' colspan=2>
656 <span id='footer-msg'></span>
657 <input type='button' value='Save' id='save-button' onclick='save()'>
658 <input type='button' value='Cancel' id='cancel-button' onclick='reloadPage();'>
659 </td></tr>
660 </table>
661 </form>
662 <script type='text/javascript'>
663 earlyInit();
664 verifyFields(null,true);
665 </script>
666 </body>
667 </html>