OpenVPN GUI: Selectable LAN interface to bridge with TAP
[tomato.git] / release / src / router / www / vpn-client.asp
blobc6d716a4fc8a4b7f8cb84278e486e49f3f0ec338
1 <!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0//EN'>
2 <!--
3 Tomato GUI
4 Copyright (C) 2006-2008 Jonathan Zarate
5 http://www.polarcloud.com/tomato/
7 Portions Copyright (C) 2008-2010 Keith Moyer, tomatovpn@keithmoyer.com
8 Portions Copyright (C) 2010-2011 Jean-Yves Avenard, jean-yves@avenard.org
10 For use with Tomato Firmware only.
11 No part of this file may be used without permission.
12 -->
13 <html>
14 <head>
15 <meta http-equiv='content-type' content='text/html;charset=utf-8'>
16 <meta name='robots' content='noindex,nofollow'>
17 <title>[<% ident(); %>] OpenVPN: Client</title>
18 <link rel='stylesheet' type='text/css' href='tomato.css'>
19 <link rel='stylesheet' type='text/css' href='color.css'>
20 <script type='text/javascript' src='tomato.js'></script>
21 <script type='text/javascript' src='vpn.js'></script>
22 <script type='text/javascript'>
24 // <% nvram("vpn_client_eas,vpn_client1_poll,vpn_client1_if,vpn_client1_bridge,vpn_client1_nat,vpn_client1_proto,vpn_client1_addr,vpn_client1_port,vpn_client1_retry,vpn_client1_firewall,vpn_client1_crypt,vpn_client1_comp,vpn_client1_cipher,vpn_client1_local,vpn_client1_remote,vpn_client1_nm,vpn_client1_reneg,vpn_client1_hmac,vpn_client1_adns,vpn_client1_rgw,vpn_client1_gw,vpn_client1_custom,vpn_client1_static,vpn_client1_ca,vpn_client1_crt,vpn_client1_key,vpn_client1_userauth,vpn_client1_username,vpn_client1_password,vpn_client1_useronly,vpn_client1_tlsremote,vpn_client1_cn,vpn_client1_br,vpn_client2_poll,vpn_client2_if,vpn_client2_bridge,vpn_client2_nat,vpn_client2_proto,vpn_client2_addr,vpn_client2_port,vpn_client2_retry,vpn_client2_firewall,vpn_client2_crypt,vpn_client2_comp,vpn_client2_cipher,vpn_client2_local,vpn_client2_remote,vpn_client2_nm,vpn_client2_reneg,vpn_client2_hmac,vpn_client2_adns,vpn_client2_rgw,vpn_client2_gw,vpn_client2_custom,vpn_client2_static,vpn_client2_ca,vpn_client2_crt,vpn_client2_key,vpn_client2_userauth,vpn_client2_username,vpn_client2_password,vpn_client2_useronly,vpn_client2_tlsremote,vpn_client2_cn,vpn_client2_br,lan_ifname,lan1_ifname,lan2_ifname,lan3_ifname"); %>
26 tabs = [['client1', 'Client 1'],['client2', 'Client 2']];
27 sections = [['basic', 'Basic'],['advanced', 'Advanced'],['keys','Keys'],['status','Status']];
28 statusUpdaters = [];
29 for (i = 0; i < tabs.length; ++i) statusUpdaters.push(new StatusUpdater());
30 ciphers = [['default','Use Default'],['none','None']];
31 for (i = 0; i < vpnciphers.length; ++i) ciphers.push([vpnciphers[i],vpnciphers[i]]);
33 changed = 0;
34 vpn1up = parseInt('<% psup("vpnclient1"); %>');
35 vpn2up = parseInt('<% psup("vpnclient2"); %>');
37 function updateStatus(num)
39 var xob = new XmlHttp();
40 xob.onCompleted = function(text, xml)
42 statusUpdaters[num].update(text);
43 xob = null;
45 xob.onError = function(ex)
47 statusUpdaters[num].errors.innerHTML += 'ERROR! '+ex+'<br>';
48 xob = null;
51 xob.post('/vpnstatus.cgi', 'client=' + (num+1));
54 function tabSelect(name)
56 tgHideIcons();
58 tabHigh(name);
60 for (var i = 0; i < tabs.length; ++i)
62 var on = (name == tabs[i][0]);
63 elem.display(tabs[i][0] + '-tab', on);
66 cookie.set('vpn_client_tab', name);
69 function sectSelect(tab, section)
71 tgHideIcons();
73 for (var i = 0; i < sections.length; ++i)
75 if (section == sections[i][0])
77 elem.addClass(tabs[tab][0]+'-'+sections[i][0]+'-tab', 'active');
78 elem.display(tabs[tab][0]+'-'+sections[i][0], true);
80 else
82 elem.removeClass(tabs[tab][0]+'-'+sections[i][0]+'-tab', 'active');
83 elem.display(tabs[tab][0]+'-'+sections[i][0], false);
87 cookie.set('vpn_client'+tab+'_section', section);
90 function toggle(service, isup)
92 if (changed && !confirm("Unsaved changes will be lost. Continue anyway?")) return;
94 E('_' + service + '_button').disabled = true;
95 form.submitHidden('service.cgi', {
96 _redirect: 'vpn-client.asp',
97 _sleep: '3',
98 _service: service + (isup ? '-stop' : '-start')
99 });
102 function verifyFields(focused, quiet)
104 tgHideIcons();
106 var ret = 1;
108 // When settings change, make sure we restart the right client
109 if (focused)
111 changed = 1;
113 var clientindex = focused.name.indexOf("client");
114 if (clientindex >= 0)
116 var clientnumber = focused.name.substring(clientindex+6,clientindex+7);
117 var stripped = focused.name.substring(0,clientindex+6)+focused.name.substring(clientindex+7);
119 if (stripped == 'vpn_client_local')
120 E('_f_vpn_client'+clientnumber+'_local').value = focused.value;
121 else if (stripped == 'f_vpn_client_local')
122 E('_vpn_client'+clientnumber+'_local').value = focused.value;
124 var fom = E('_fom');
125 if (eval('vpn'+clientnumber+'up') && fom._service.value.indexOf('client'+clientnumber) < 0)
127 if ( fom._service.value != "" ) fom._service.value += ",";
128 fom._service.value += 'vpnclient'+clientnumber+'-restart';
133 // Element varification
134 for (i = 0; i < tabs.length; ++i)
136 t = tabs[i][0];
138 if (!v_range('_vpn_'+t+'_poll', quiet, 0, 1440)) ret = 0;
139 if (!v_ip('_vpn_'+t+'_addr', true) && !v_domain('_vpn_'+t+'_addr', true)) { ferror.set(E('_vpn_'+t+'_addr'), "Invalid server address.", quiet); ret = 0; }
140 if (!v_port('_vpn_'+t+'_port', quiet)) ret = 0;
141 if (!v_ip('_vpn_'+t+'_local', quiet, 1)) ret = 0;
142 if (!v_ip('_f_vpn_'+t+'_local', true, 1)) ret = 0;
143 if (!v_ip('_vpn_'+t+'_remote', quiet, 1)) ret = 0;
144 if (!v_netmask('_vpn_'+t+'_nm', quiet)) ret = 0;
145 if (!v_range('_vpn_'+t+'_retry', quiet, -1, 32767)) ret = 0;
146 if (!v_range('_vpn_'+t+'_reneg', quiet, -1, 2147483647)) ret = 0;
147 if (E('_vpn_'+t+'_gw').value.length > 0 && !v_ip('_vpn_'+t+'_gw', quiet, 1)) ret = 0;
150 // Visability changes
151 for (i = 0; i < tabs.length; ++i)
153 t = tabs[i][0];
155 fw = E('_vpn_'+t+'_firewall').value;
156 auth = E('_vpn_'+t+'_crypt').value;
157 iface = E('_vpn_'+t+'_if').value;
158 bridge = E('_f_vpn_'+t+'_bridge').checked;
159 nat = E('_f_vpn_'+t+'_nat').checked;
160 hmac = E('_vpn_'+t+'_hmac').value;
161 rgw = E('_f_vpn_'+t+'_rgw').checked;
163 userauth = E('_f_vpn_'+t+'_userauth').checked && auth == "tls";
164 useronly = userauth && E('_f_vpn_'+t+'_useronly').checked;
166 // Page Basic
167 elem.display(PR('_f_vpn_'+t+'_userauth'), auth == "tls");
168 elem.display(PR('_vpn_'+t+'_username'), PR('_vpn_'+t+'_password'), userauth );
169 elem.display(PR('_f_vpn_'+t+'_useronly'), userauth);
170 elem.display(E(t+'_ca_warn_text'), useronly);
171 elem.display(PR('_vpn_'+t+'_hmac'), auth == "tls");
172 elem.display(E(t+'_custom_crypto_text'), auth == "custom");
173 elem.display(PR('_f_vpn_'+t+'_bridge'), iface == "tap");
174 elem.display(PR('_vpn_'+t+'_br'), iface == "tap");
175 elem.display(E(t+'_bridge_warn_text'), !bridge);
176 elem.display(PR('_f_vpn_'+t+'_nat'), fw != "custom" && (iface == "tun" || !bridge));
177 elem.display(E(t+'_nat_warn_text'), fw != "custom" && (!nat || (auth == "secret" && iface == "tun")));
178 elem.display(PR('_vpn_'+t+'_local'), iface == "tun" && auth == "secret");
179 elem.display(PR('_f_vpn_'+t+'_local'), iface == "tap" && !bridge && auth != "custom");
181 // Page Advanced
182 elem.display(PR('_vpn_'+t+'_adns'), PR('_vpn_'+t+'_reneg'), auth == "tls");
183 elem.display(E(t+'_gateway'), iface == "tap" && rgw > 0);
185 // Page Key
186 elem.display(PR('_vpn_'+t+'_static'), auth == "secret" || (auth == "tls" && hmac >= 0));
187 elem.display(PR('_vpn_'+t+'_ca'), auth == "tls");
188 elem.display(PR('_vpn_'+t+'_crt'), PR('_vpn_'+t+'_key'), auth == "tls" && !useronly);
189 elem.display(PR('_f_vpn_'+t+'_tlsremote'), auth == "tls");
190 elem.display(E(t+'_cn'), auth == "tls" && E('_f_vpn_'+t+'_tlsremote').checked);
192 keyHelp = E(t+'-keyhelp');
193 switch (auth)
195 case "tls":
196 keyHelp.href = helpURL['TLSKeys'];
197 break;
198 case "secret":
199 keyHelp.href = helpURL['staticKeys'];
200 break;
201 default:
202 keyHelp.href = helpURL['howto'];
203 break;
207 var bridge1 = E('_vpn_client1_br');
208 if(nvram.lan_ifname.length < 1)
209 bridge1.options[0].disabled=true;
210 if(nvram.lan1_ifname.length < 1)
211 bridge1.options[1].disabled=true;
212 if(nvram.lan2_ifname.length < 1)
213 bridge1.options[2].disabled=true;
214 if(nvram.lan3_ifname.length < 1)
215 bridge1.options[3].disabled=true;
217 var bridge2 = E('_vpn_client2_br');
218 if(nvram.lan_ifname.length < 1)
219 bridge2.options[0].disabled=true;
220 if(nvram.lan1_ifname.length < 1)
221 bridge2.options[1].disabled=true;
222 if(nvram.lan2_ifname.length < 1)
223 bridge2.options[2].disabled=true;
224 if(nvram.lan3_ifname.length < 1)
225 bridge2.options[3].disabled=true;
227 return ret;
230 function save()
232 if (!verifyFields(null, false)) return;
234 var fom = E('_fom');
236 E('vpn_client_eas').value = '';
238 for (i = 0; i < tabs.length; ++i)
240 t = tabs[i][0];
242 if ( E('_f_vpn_'+t+'_eas').checked )
243 E('vpn_client_eas').value += ''+(i+1)+',';
245 E('vpn_'+t+'_bridge').value = E('_f_vpn_'+t+'_bridge').checked ? 1 : 0;
246 E('vpn_'+t+'_nat').value = E('_f_vpn_'+t+'_nat').checked ? 1 : 0;
247 E('vpn_'+t+'_rgw').value = E('_f_vpn_'+t+'_rgw').checked ? 1 : 0;
248 E('vpn_'+t+'_userauth').value = E('_f_vpn_'+t+'_userauth').checked ? 1 : 0;
249 E('vpn_'+t+'_useronly').value = E('_f_vpn_'+t+'_useronly').checked ? 1 : 0;
250 E('vpn_'+t+'_tlsremote').value = E('_f_vpn_'+t+'_tlsremote').checked ? 1 : 0;
253 form.submit(fom, 1);
255 changed = 0;
258 function init()
260 tabSelect(cookie.get('vpn_client_tab') || tabs[0][0]);
262 for (i = 0; i < tabs.length; ++i)
264 sectSelect(i, cookie.get('vpn_client'+i+'_section') || sections[i][0]);
266 t = tabs[i][0];
268 statusUpdaters[i].init(null,null,t+'-status-stats-table',t+'-status-time',t+'-status-content',t+'-no-status',t+'-status-errors');
269 updateStatus(i);
272 verifyFields(null, true);
274 </script>
276 <style type='text/css'>
277 textarea {
278 width: 98%;
279 height: 10em;
281 p.keyhelp
283 font-size: smaller;
284 font-style: italic;
286 div.status-header p
288 font-weight: bold;
289 padding-bottom: 4px;
291 table.status-table
293 width: auto;
294 margin-left: auto;
295 margin-right: auto;
296 text-align: center;
298 </style>
300 </head>
301 <body>
302 <form id='_fom' method='post' action='tomato.cgi'>
303 <table id='container' cellspacing=0>
304 <tr><td colspan=2 id='header'>
305 <div class='title'>Tomato</div>
306 <div class='version'>Version <% version(); %></div>
307 </td></tr>
308 <tr id='body'><td id='navi'><script type='text/javascript'>navi()</script></td>
309 <td id='content'>
310 <div id='ident'><% ident(); %></div>
312 <input type='hidden' name='_nextpage' value='vpn-client.asp'>
313 <input type='hidden' name='_nextwait' value='5'>
314 <input type='hidden' name='_service' value=''>
315 <input type='hidden' name='vpn_client_eas' id='vpn_client_eas' value=''>
317 <div class='section-title'>OpenVPN Client Configuration</div>
318 <div class='section'>
319 <script type='text/javascript'>
320 tabCreate.apply(this, tabs);
322 for (i = 0; i < tabs.length; ++i)
324 t = tabs[i][0];
325 W('<div id=\''+t+'-tab\'>');
326 W('<input type=\'hidden\' id=\'vpn_'+t+'_bridge\' name=\'vpn_'+t+'_bridge\'>');
327 W('<input type=\'hidden\' id=\'vpn_'+t+'_nat\' name=\'vpn_'+t+'_nat\'>');
328 W('<input type=\'hidden\' id=\'vpn_'+t+'_rgw\' name=\'vpn_'+t+'_rgw\'>');
329 W('<input type=\'hidden\' id=\'vpn_'+t+'_userauth\' name=\'vpn_'+t+'_userauth\'>');
330 W('<input type=\'hidden\' id=\'vpn_'+t+'_useronly\' name=\'vpn_'+t+'_useronly\'>');
331 W('<input type=\'hidden\' id=\'vpn_'+t+'_tlsremote\' name=\'vpn_'+t+'_tlsremote\'>');
333 W('<ul class="tabs">');
334 for (j = 0; j < sections.length; j++)
336 W('<li><a href="javascript:sectSelect('+i+', \''+sections[j][0]+'\')" id="'+t+'-'+sections[j][0]+'-tab">'+sections[j][1]+'</a></li>');
338 W('</ul><div class=\'tabs-bottom\'></div>');
340 W('<div id=\''+t+'-basic\'>');
341 createFieldTable('', [
342 { title: 'Start with WAN', name: 'f_vpn_'+t+'_eas', type: 'checkbox', value: nvram.vpn_client_eas.indexOf(''+(i+1)) >= 0 },
343 { title: 'Interface Type', name: 'vpn_'+t+'_if', type: 'select', options: [ ['tap','TAP'], ['tun','TUN'] ], value: eval( 'nvram.vpn_'+t+'_if' ) },
344 { title: 'Bridge TAP with', indent: 2, name: 'vpn_'+t+'_br', type: 'select', options: [
345 ['br0','LAN (br0)*'],
346 ['br1','LAN1 (br1)'],
347 ['br2','LAN2 (br2)'],
348 ['br3','LAN3 (br3)']
349 ], value: eval ( 'nvram.vpn_'+t+'_br' ), suffix: ' <small>* default</small> ' },
350 { title: 'Protocol', name: 'vpn_'+t+'_proto', type: 'select', options: [ ['udp','UDP'], ['tcp-client','TCP'] ], value: eval( 'nvram.vpn_'+t+'_proto' ) },
351 { title: 'Server Address/Port', multi: [
352 { name: 'vpn_'+t+'_addr', type: 'text', size: 17, value: eval( 'nvram.vpn_'+t+'_addr' ) },
353 { name: 'vpn_'+t+'_port', type: 'text', maxlen: 5, size: 7, value: eval( 'nvram.vpn_'+t+'_port' ) } ] },
354 { title: 'Firewall', name: 'vpn_'+t+'_firewall', type: 'select', options: [ ['auto', 'Automatic'], ['custom', 'Custom'] ], value: eval( 'nvram.vpn_'+t+'_firewall' ) },
355 { title: 'Authorization Mode', name: 'vpn_'+t+'_crypt', type: 'select', options: [ ['tls', 'TLS'], ['secret', 'Static Key'], ['custom', 'Custom'] ], value: eval( 'nvram.vpn_'+t+'_crypt' ),
356 suffix: '<span id=\''+t+'_custom_crypto_text\'>&nbsp;<small>(must configure manually...)</small></span>' },
357 { title: 'Username/Password Authentication', name: 'f_vpn_'+t+'_userauth', type: 'checkbox', value: eval( 'nvram.vpn_'+t+'_userauth' ) != 0 },
358 { title: 'Username: ', indent: 2, name: 'vpn_'+t+'_username', type: 'text', maxlen: 50, size: 54, value: eval( 'nvram.vpn_'+t+'_username' ) },
359 { title: 'Password: ', indent: 2, name: 'vpn_'+t+'_password', type: 'password', maxlen: 50, size: 54, value: eval( 'nvram.vpn_'+t+'_password' ) },
360 { title: 'Username Authen. Only', indent: 2, name: 'f_vpn_'+t+'_useronly', type: 'checkbox', value: eval( 'nvram.vpn_'+t+'_useronly' ) != 0,
361 suffix: '<span style="color: red" id=\''+t+'_ca_warn_text\'>&nbsp<small>Warning: Must define Certificate Authority.<small></span>' },
362 { title: 'Extra HMAC authorization (tls-auth)', name: 'vpn_'+t+'_hmac', type: 'select', options: [ [-1, 'Disabled'], [2, 'Bi-directional'], [0, 'Incoming (0)'], [1, 'Outgoing (1)'] ], value: eval( 'nvram.vpn_'+t+'_hmac' ) },
363 { title: 'Server is on the same subnet', name: 'f_vpn_'+t+'_bridge', type: 'checkbox', value: eval( 'nvram.vpn_'+t+'_bridge' ) != 0,
364 suffix: '<span style="color: red" id=\''+t+'_bridge_warn_text\'>&nbsp<small>Warning: Cannot bridge distinct subnets. Defaulting to routed mode.<small></span>' },
365 { title: 'Create NAT on tunnel', name: 'f_vpn_'+t+'_nat', type: 'checkbox', value: eval( 'nvram.vpn_'+t+'_nat' ) != 0,
366 suffix: '<span style="font-style: italic" id=\''+t+'_nat_warn_text\'>&nbsp<small>Routes must be configured manually.<small></span>' },
367 { title: 'Local/remote endpoint addresses', multi: [
368 { name: 'vpn_'+t+'_local', type: 'text', maxlen: 15, size: 17, value: eval( 'nvram.vpn_'+t+'_local' ) },
369 { name: 'vpn_'+t+'_remote', type: 'text', maxlen: 15, size: 17, value: eval( 'nvram.vpn_'+t+'_remote' ) } ] },
370 { title: 'Tunnel address/netmask', multi: [
371 { name: 'f_vpn_'+t+'_local', type: 'text', maxlen: 15, size: 17, value: eval( 'nvram.vpn_'+t+'_local' ) },
372 { name: 'vpn_'+t+'_nm', type: 'text', maxlen: 15, size: 17, value: eval( 'nvram.vpn_'+t+'_nm' ) } ] }
374 W('</div>');
375 W('<div id=\''+t+'-advanced\'>');
376 createFieldTable('', [
377 { title: 'Poll Interval', name: 'vpn_'+t+'_poll', type: 'text', maxlen: 4, size: 5, value: eval( 'nvram.vpn_'+t+'_poll' ), suffix: '&nbsp;<small>(in minutes, 0 to disable)</small>' },
378 { title: 'Redirect Internet traffic', multi: [
379 { name: 'f_vpn_'+t+'_rgw', type: 'checkbox', value: eval( 'nvram.vpn_'+t+'_rgw' ) != 0 },
380 { name: 'vpn_'+t+'_gw', type: 'text', maxlen: 15, size: 17, value: eval( 'nvram.vpn_'+t+'_gw' ), prefix: '<span id=\''+t+'_gateway\'> Gateway:&nbsp', suffix: '</span>'} ] },
381 { title: 'Accept DNS configuration', name: 'vpn_'+t+'_adns', type: 'select', options: [[0, 'Disabled'],[1, 'Relaxed'],[2, 'Strict'],[3, 'Exclusive']], value: eval( 'nvram.vpn_'+t+'_adns' ) },
382 { title: 'Encryption cipher', name: 'vpn_'+t+'_cipher', type: 'select', options: ciphers, value: eval( 'nvram.vpn_'+t+'_cipher' ) },
383 { title: 'Compression', name: 'vpn_'+t+'_comp', type: 'select', options: [ ['-1', 'Disabled'], ['no', 'None'], ['yes', 'Enabled'], ['adaptive', 'Adaptive'] ], value: eval( 'nvram.vpn_'+t+'_comp' ) },
384 { title: 'TLS Renegotiation Time', name: 'vpn_'+t+'_reneg', type: 'text', maxlen: 10, size: 7, value: eval( 'nvram.vpn_'+t+'_reneg' ),
385 suffix: '&nbsp;<small>(in seconds, -1 for default)</small>' },
386 { title: 'Connection retry', name: 'vpn_'+t+'_retry', type: 'text', maxlen: 5, size: 7, value: eval( 'nvram.vpn_'+t+'_retry' ),
387 suffix: '&nbsp;<small>(in seconds; -1 for infinite)</small>' },
388 { title: 'Verify server certificate (tls-remote)', multi: [
389 { name: 'f_vpn_'+t+'_tlsremote', type: 'checkbox', value: eval( 'nvram.vpn_'+t+'_tlsremote' ) != 0 },
390 { name: 'vpn_'+t+'_cn', type: 'text', maxlen: 64, size: 54,
391 value: eval( 'nvram.vpn_'+t+'_cn' ), prefix: '<span id=\''+t+'_cn\'> Common Name:&nbsp', suffix: '</span>'} ] },
392 { title: 'Custom Configuration', name: 'vpn_'+t+'_custom', type: 'textarea', value: eval( 'nvram.vpn_'+t+'_custom' ) }
394 W('</div>');
395 W('<div id=\''+t+'-keys\'>');
396 W('<p class=\'keyhelp\'>For help generating keys, refer to the OpenVPN <a id=\''+t+'-keyhelp\'>HOWTO</a>.</p>');
397 createFieldTable('', [
398 { title: 'Static Key', name: 'vpn_'+t+'_static', type: 'textarea', value: eval( 'nvram.vpn_'+t+'_static' ) },
399 { title: 'Certificate Authority', name: 'vpn_'+t+'_ca', type: 'textarea', value: eval( 'nvram.vpn_'+t+'_ca' ) },
400 { title: 'Client Certificate', name: 'vpn_'+t+'_crt', type: 'textarea', value: eval( 'nvram.vpn_'+t+'_crt' ) },
401 { title: 'Client Key', name: 'vpn_'+t+'_key', type: 'textarea', value: eval( 'nvram.vpn_'+t+'_key' ) },
403 W('</div>');
404 W('<div id=\''+t+'-status\'>');
405 W('<div id=\''+t+'-no-status\'><p>Client is not running or status could not be read.</p></div>');
406 W('<div id=\''+t+'-status-content\' style=\'display:none\' class=\'status-content\'>');
407 W('<div id=\''+t+'-status-header\' class=\'status-header\'><p>Data current as of <span id=\''+t+'-status-time\'></span>.</p></div>');
408 W('<div id=\''+t+'-status-stats\'><div class=\'section-title\'>General Statistics</div><table class=\'tomato-grid status-table\' id=\''+t+'-status-stats-table\'></table><br></div>');
409 W('<div id=\''+t+'-status-errors\' class=\'error\'></div>');
410 W('</div>');
411 W('<div style=\'text-align:right\'><a href=\'javascript:updateStatus('+i+')\'>Refresh Status</a></div>');
412 W('</div>');
413 W('<input type="button" value="' + (eval('vpn'+(i+1)+'up') ? 'Stop' : 'Start') + ' Now" onclick="toggle(\'vpn'+t+'\', vpn'+(i+1)+'up)" id="_vpn'+t+'_button">');
414 W('</div>');
417 </script>
418 </div>
420 </td></tr>
421 <tr><td id='footer' colspan=2>
422 <span id='footer-msg'></span>
423 <input type='button' value='Save' id='save-button' onclick='save()'>
424 <input type='button' value='Cancel' id='cancel-button' onclick='javascript:reloadPage();'>
425 </td></tr>
426 </table>
427 </form>
428 <script type='text/javascript'>init();</script>
429 </body>
430 </html>