Fix nvram size for router with more than 32kB of nvram.
[tomato.git] / release / src / router / www / vpn-server.asp
blob8c3e2ac398cad2465c05772ff35225fdabee8348
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
9 For use with Tomato Firmware only.
10 No part of this file may be used without permission.
11 -->
12 <html>
13 <head>
14 <meta http-equiv='content-type' content='text/html;charset=utf-8'>
15 <meta name='robots' content='noindex,nofollow'>
16 <title>[<% ident(); %>] OpenVPN: Server</title>
17 <link rel='stylesheet' type='text/css' href='tomato.css'>
18 <link rel='stylesheet' type='text/css' href='color.css'>
19 <script type='text/javascript' src='tomato.js'></script>
20 <script type='text/javascript' src='vpn.js'></script>
21 <script type='text/javascript'>
23 // <% nvram("vpn_server_eas,vpn_server_dns,vpn_server1_poll,vpn_server1_if,vpn_server1_proto,vpn_server1_port,vpn_server1_firewall,vpn_server1_sn,vpn_server1_nm,vpn_server1_local,vpn_server1_remote,vpn_server1_dhcp,vpn_server1_r1,vpn_server1_r2,vpn_server1_crypt,vpn_server1_comp,vpn_server1_cipher,vpn_server1_reneg,vpn_server1_hmac,vpn_server1_plan,vpn_server1_ccd,vpn_server1_c2c,vpn_server1_ccd_excl,vpn_server1_ccd_val,vpn_server1_pdns,vpn_server1_rgw,vpn_server1_custom,vpn_server1_static,vpn_server1_ca,vpn_server1_crt,vpn_server1_key,vpn_server1_dh,vpn_server2_poll,vpn_server2_if,vpn_server2_proto,vpn_server2_port,vpn_server2_firewall,vpn_server2_sn,vpn_server2_nm,vpn_server2_local,vpn_server2_remote,vpn_server2_dhcp,vpn_server2_r1,vpn_server2_r2,vpn_server2_crypt,vpn_server2_comp,vpn_server2_cipher,vpn_server2_reneg,vpn_server2_hmac,vpn_server2_plan,vpn_server2_ccd,vpn_server2_c2c,vpn_server2_ccd_excl,vpn_server2_ccd_val,vpn_server2_pdns,vpn_server2_rgw,vpn_server2_custom,vpn_server2_static,vpn_server2_ca,vpn_server2_crt,vpn_server2_key,vpn_server2_dh"); %>
25 function CCDGrid() { return this; }
26 CCDGrid.prototype = new TomatoGrid;
28 tabs = [['server1', 'Server 1'],['server2', 'Server 2']];
29 sections = [['basic', 'Basic'],['advanced', 'Advanced'],['keys','Keys'],['status','Status']];
30 ccdTables = [];
31 statusUpdaters = [];
32 for (i = 0; i < tabs.length; ++i)
34 ccdTables.push(new CCDGrid());
35 statusUpdaters.push(new StatusUpdater());
37 ciphers = [['default','Use Default'],['none','None']];
38 for (i = 0; i < vpnciphers.length; ++i) ciphers.push([vpnciphers[i],vpnciphers[i]]);
40 changed = 0;
41 vpn1up = parseInt('<% psup("vpnserver1"); %>');
42 vpn2up = parseInt('<% psup("vpnserver2"); %>');
44 function updateStatus(num)
46 var xob = new XmlHttp();
47 xob.onCompleted = function(text, xml)
49 statusUpdaters[num].update(text);
50 xob = null;
52 xob.onError = function(ex)
54 statusUpdaters[num].errors.innerHTML += 'ERROR! '+ex+'<br>';
55 xob = null;
58 xob.post('/vpnstatus.cgi', 'server=' + (num+1));
61 function tabSelect(name)
63 tgHideIcons();
65 tabHigh(name);
67 for (var i = 0; i < tabs.length; ++i)
69 var on = (name == tabs[i][0]);
70 elem.display(tabs[i][0] + '-tab', on);
73 cookie.set('vpn_server_tab', name);
76 function sectSelect(tab, section)
78 tgHideIcons();
80 for (var i = 0; i < sections.length; ++i)
82 if (section == sections[i][0])
84 elem.addClass(tabs[tab][0]+'-'+sections[i][0]+'-tab', 'active');
85 elem.display(tabs[tab][0]+'-'+sections[i][0], true);
87 else
89 elem.removeClass(tabs[tab][0]+'-'+sections[i][0]+'-tab', 'active');
90 elem.display(tabs[tab][0]+'-'+sections[i][0], false);
94 cookie.set('vpn_server'+tab+'_section', section);
97 function toggle(service, isup)
99 if (changed && !confirm("Unsaved changes will be lost. Continue anyway?")) return;
101 E('_' + service + '_button').disabled = true;
102 form.submitHidden('service.cgi', {
103 _redirect: 'vpn-server.asp',
104 _sleep: '3',
105 _service: service + (isup ? '-stop' : '-start')
109 function verifyFields(focused, quiet)
111 tgHideIcons();
113 var ret = 1;
115 // When settings change, make sure we restart the right services
116 if (focused)
118 changed = 1;
120 var fom = E('_fom');
121 var serverindex = focused.name.indexOf("server");
122 if (serverindex >= 0)
124 var servernumber = focused.name.substring(serverindex+6,serverindex+7);
125 if (eval('vpn'+servernumber+'up') && fom._service.value.indexOf('server'+servernumber) < 0)
127 if ( fom._service.value != "" ) fom._service.value += ",";
128 fom._service.value += 'vpnserver'+servernumber+'-restart';
131 if ((focused.name.indexOf("_dns")>=0 || (focused.name.indexOf("_if")>=0 && E('_f_vpn_server'+servernumber+'_dns').checked)) &&
132 fom._service.value.indexOf('dnsmasq') < 0)
134 if ( fom._service.value != "" ) fom._service.value += ",";
135 fom._service.value += 'dnsmasq-restart';
138 if (focused.name.indexOf("_c2c") >= 0)
139 ccdTables[servernumber-1].reDraw();
143 // Element varification
144 for (i = 0; i < tabs.length; ++i)
146 t = tabs[i][0];
148 if (!v_range('_vpn_'+t+'_poll', quiet, 0, 1440)) ret = 0;
149 if (!v_port('_vpn_'+t+'_port', quiet)) ret = 0;
150 if (!v_ip('_vpn_'+t+'_sn', quiet, 0)) ret = 0;
151 if (!v_netmask('_vpn_'+t+'_nm', quiet)) ret = 0;
152 if (!v_ip('_vpn_'+t+'_r1', quiet, 1)) ret = 0;
153 if (!v_ip('_vpn_'+t+'_r2', quiet, 1)) ret = 0;
154 if (!v_ip('_vpn_'+t+'_local', quiet, 1)) ret = 0;
155 if (!v_ip('_vpn_'+t+'_remote', quiet, 1)) ret = 0;
156 if (!v_range('_vpn_'+t+'_reneg', quiet, -1, 2147483647)) ret = 0;
159 // Visability changes
160 for (i = 0; i < tabs.length; ++i)
162 t = tabs[i][0];
164 auth = E('_vpn_'+t+'_crypt');
165 iface = E('_vpn_'+t+'_if');
166 hmac = E('_vpn_'+t+'_hmac');
167 dhcp = E('_f_vpn_'+t+'_dhcp');
168 ccd = E('_f_vpn_'+t+'_ccd');
169 dns = E('_f_vpn_'+t+'_dns');
171 elem.display(PR('_vpn_'+t+'_ca'), PR('_vpn_'+t+'_crt'), PR('_vpn_'+t+'_dh'), PR('_vpn_'+t+'_key'),
172 PR('_vpn_'+t+'_hmac'), PR('_f_vpn_'+t+'_rgw'), PR('_vpn_'+t+'_reneg'), auth.value == "tls");
173 elem.display(PR('_vpn_'+t+'_static'), auth.value == "secret" || (auth.value == "tls" && hmac.value >= 0));
174 elem.display(E(t+'_custom_crypto_text'), auth.value == "custom");
175 elem.display(PR('_vpn_'+t+'_sn'), PR('_f_vpn_'+t+'_plan'), auth.value == "tls" && iface.value == "tun");
176 elem.display(PR('_f_vpn_'+t+'_dhcp'), auth.value == "tls" && iface.value == "tap");
177 elem.display(E(t+'_range'), !dhcp.checked);
178 elem.display(PR('_vpn_'+t+'_local'), auth.value == "secret" && iface.value == "tun");
179 elem.display(PR('_f_vpn_'+t+'_ccd'), auth.value == "tls");
180 elem.display(PR('_f_vpn_'+t+'_c2c'),PR('_f_vpn_'+t+'_ccd_excl'),PR('table_'+t+'_ccd'), auth.value == "tls" && ccd.checked);
181 elem.display(PR('_f_vpn_'+t+'_pdns'), auth.value == "tls" && dns.checked );
183 keyHelp = E(t+'-keyhelp');
184 switch (auth.value)
186 case "tls":
187 keyHelp.href = helpURL['TLSKeys'];
188 break;
189 case "secret":
190 keyHelp.href = helpURL['staticKeys'];
191 break;
192 default:
193 keyHelp.href = helpURL['howto'];
194 break;
198 return ret;
201 CCDGrid.prototype.verifyFields = function(row, quiet)
203 var ret = 1;
205 // When settings change, make sure we restart the right server
206 var fom = E('_fom');
207 var servernum = 1;
208 for (i = 0; i < tabs.length; ++i)
210 if (ccdTables[i] == this)
212 servernum = i+1;
213 if (eval('vpn'+(i+1)+'up') && fom._service.value.indexOf('server'+(i+1)) < 0)
215 if ( fom._service.value != "" ) fom._service.value += ",";
216 fom._service.value += 'vpnserver'+(i+1)+'-restart';
221 var f = fields.getAll(row);
223 // Verify fields in this row of the table
224 if (f[1].value == "") { ferror.set(f[1], "Common name is mandatory.", quiet); ret = 0; }
225 if (f[1].value.indexOf('>') >= 0 || f[1].value.indexOf('<') >= 0) { ferror.set(f[1], "Common name cannot contain '<' or '>' characters.", quiet); ret = 0; }
226 if (f[2].value != "" && !v_ip(f[2],quiet,0)) ret = 0;
227 if (f[3].value != "" && !v_netmask(f[3],quiet)) ret = 0;
228 if (f[2].value == "" && f[3].value != "" ) { ferror.set(f[2], "Either both or neither subnet and netmask must be provided.", quiet); ret = 0; }
229 if (f[3].value == "" && f[2].value != "" ) { ferror.set(f[3], "Either both or neither subnet and netmask must be provided.", quiet); ret = 0; }
230 if (f[4].checked && (f[2].value == "" || f[3].value == "")) { ferror.set(f[4], "Cannot push routes if they're not given. Please provide subnet/netmask.", quiet); ret = 0; }
232 return ret;
235 CCDGrid.prototype.fieldValuesToData = function(row)
237 var f = fields.getAll(row);
238 return [f[0].checked?1:0, f[1].value, f[2].value, f[3].value, f[4].checked?1:0];
241 CCDGrid.prototype.dataToView = function(data)
243 var c2c = false;
244 for (i = 0; i < tabs.length; ++i)
246 if (ccdTables[i] == this && E('_f_vpn_server'+(i+1)+'_c2c').checked )
247 c2c = true;
250 var temp = ['<input type=\'checkbox\' style="opacity:1" disabled'+(data[0]!=0?' checked':'')+'>',
251 data[1],
252 data[2],
253 data[3],
254 c2c?'<input type=\'checkbox\' style="opacity:1" disabled'+(data[4]!=0?' checked':'')+'>':'N/A'];
256 var v = [];
257 for (var i = 0; i < temp.length; ++i)
258 v.push(i==0||i==4?temp[i]:escapeHTML('' + temp[i]));
260 return v;
263 CCDGrid.prototype.dataToFieldValues = function(data)
265 return [data[0] == 1, data[1], data[2], data[3], data[4] == 1];
268 CCDGrid.prototype.reDraw = function()
270 var i, j, header, data, view;
271 data = this.getAllData();
272 header = this.header ? this.header.rowIndex + 1 : 0;
273 for (i = 0; i < data.length; ++i)
275 view = this.dataToView(data[i]);
276 for (j = 0; j < view.length; ++j)
277 this.tb.rows[i+header].cells[j].innerHTML = view[j];
281 function save()
283 if (!verifyFields(null, false)) return;
285 var fom = E('_fom');
287 E('vpn_server_eas').value = '';
288 E('vpn_server_dns').value = '';
290 for (i = 0; i < tabs.length; ++i)
292 if (ccdTables[i].isEditing()) return;
294 t = tabs[i][0];
296 if ( E('_f_vpn_'+t+'_eas').checked )
297 E('vpn_server_eas').value += ''+(i+1)+',';
299 if ( E('_f_vpn_'+t+'_dns').checked )
300 E('vpn_server_dns').value += ''+(i+1)+',';
302 var data = ccdTables[i].getAllData();
303 var ccd = '';
305 for (j = 0; j < data.length; ++j)
306 ccd += data[j].join('<') + '>';
308 E('vpn_'+t+'_dhcp').value = E('_f_vpn_'+t+'_dhcp').checked ? 1 : 0;
309 E('vpn_'+t+'_plan').value = E('_f_vpn_'+t+'_plan').checked ? 1 : 0;
310 E('vpn_'+t+'_ccd').value = E('_f_vpn_'+t+'_ccd').checked ? 1 : 0;
311 E('vpn_'+t+'_c2c').value = E('_f_vpn_'+t+'_c2c').checked ? 1 : 0;
312 E('vpn_'+t+'_ccd_excl').value = E('_f_vpn_'+t+'_ccd_excl').checked ? 1 : 0;
313 E('vpn_'+t+'_ccd_val').value = ccd;
314 E('vpn_'+t+'_pdns').value = E('_f_vpn_'+t+'_pdns').checked ? 1 : 0;
315 E('vpn_'+t+'_rgw').value = E('_f_vpn_'+t+'_rgw').checked ? 1 : 0;
318 form.submit(fom, 1);
320 changed = 0;
323 function init()
325 tabSelect(cookie.get('vpn_server_tab') || tabs[0][0]);
327 for (i = 0; i < tabs.length; ++i)
329 sectSelect(i, cookie.get('vpn_server'+i+'_section') || sections[0][0]);
331 t = tabs[i][0];
333 ccdTables[i].init('table_'+t+'_ccd', 'sort', 0, [{ type: 'checkbox' }, { type: 'text' }, { type: 'text', maxlen: 15 }, { type: 'text', maxlen: 15 }, { type: 'checkbox' }]);
334 ccdTables[i].headerSet(['Enable', 'Common Name', 'Subnet', 'Netmask', 'Push']);
335 var ccdVal = eval( 'nvram.vpn_'+t+'_ccd_val' );
336 if (ccdVal.length)
338 var s = ccdVal.split('>');
339 for (var j = 0; j < s.length; ++j)
341 if (!s[j].length) continue;
342 var row = s[j].split('<');
343 if (row.length == 5)
344 ccdTables[i].insertData(-1, row);
347 ccdTables[i].showNewEditor();
348 ccdTables[i].resetNewEditor();
350 statusUpdaters[i].init(t+'-status-clients-table',t+'-status-routing-table',t+'-status-stats-table',t+'-status-time',t+'-status-content',t+'-no-status',t+'-status-errors');
351 updateStatus(i);
354 verifyFields(null, true);
356 </script>
358 <style type='text/css'>
359 textarea
361 width: 98%;
362 height: 10em;
364 p.keyhelp
366 font-size: smaller;
367 font-style: italic;
369 div.status-header p
371 font-weight: bold;
372 padding-bottom: 4px;
374 table.status-table
376 width: auto;
377 margin-left: auto;
378 margin-right: auto;
379 text-align: center;
381 </style>
383 </head>
384 <body>
385 <form id='_fom' method='post' action='tomato.cgi'>
386 <table id='container' cellspacing=0>
387 <tr><td colspan=2 id='header'>
388 <div class='title'>Tomato</div>
389 <div class='version'>Version <% version(); %></div>
390 </td></tr>
391 <tr id='body'><td id='navi'><script type='text/javascript'>navi()</script></td>
392 <td id='content'>
393 <div id='ident'><% ident(); %></div>
395 <input type='hidden' name='_nextpage' value='vpn-server.asp'>
396 <input type='hidden' name='_nextwait' value='5'>
397 <input type='hidden' name='_service' value=''>
398 <input type='hidden' name='vpn_server_eas' id='vpn_server_eas' value=''>
399 <input type='hidden' name='vpn_server_dns' id='vpn_server_dns' value=''>
401 <div class='section-title'>OpenVPN Server Configuration</div>
402 <div class='section'>
403 <script type='text/javascript'>
404 tabCreate.apply(this, tabs);
406 for (i = 0; i < tabs.length; ++i)
408 t = tabs[i][0];
409 W('<div id=\''+t+'-tab\'>');
410 W('<input type=\'hidden\' id=\'vpn_'+t+'_dhcp\' name=\'vpn_'+t+'_dhcp\'>');
411 W('<input type=\'hidden\' id=\'vpn_'+t+'_plan\' name=\'vpn_'+t+'_plan\'>');
412 W('<input type=\'hidden\' id=\'vpn_'+t+'_ccd\' name=\'vpn_'+t+'_ccd\'>');
413 W('<input type=\'hidden\' id=\'vpn_'+t+'_c2c\' name=\'vpn_'+t+'_c2c\'>');
414 W('<input type=\'hidden\' id=\'vpn_'+t+'_ccd_excl\' name=\'vpn_'+t+'_ccd_excl\'>');
415 W('<input type=\'hidden\' id=\'vpn_'+t+'_ccd_val\' name=\'vpn_'+t+'_ccd_val\'>');
416 W('<input type=\'hidden\' id=\'vpn_'+t+'_pdns\' name=\'vpn_'+t+'_pdns\'>');
417 W('<input type=\'hidden\' id=\'vpn_'+t+'_rgw\' name=\'vpn_'+t+'_rgw\'>');
419 W('<ul class="tabs">');
420 for (j = 0; j < sections.length; j++)
422 W('<li><a href="javascript:sectSelect('+i+', \''+sections[j][0]+'\')" id="'+t+'-'+sections[j][0]+'-tab">'+sections[j][1]+'</a></li>');
424 W('</ul><div class=\'tabs-bottom\'></div>');
426 W('<div id=\''+t+'-basic\'>');
427 createFieldTable('', [
428 { title: 'Start with WAN', name: 'f_vpn_'+t+'_eas', type: 'checkbox', value: nvram.vpn_server_eas.indexOf(''+(i+1)) >= 0 },
429 { title: 'Interface Type', name: 'vpn_'+t+'_if', type: 'select', options: [ ['tap','TAP'], ['tun','TUN'] ], value: eval( 'nvram.vpn_'+t+'_if' ) },
430 { title: 'Protocol', name: 'vpn_'+t+'_proto', type: 'select', options: [ ['udp','UDP'], ['tcp-server','TCP'] ], value: eval( 'nvram.vpn_'+t+'_proto' ) },
431 { title: 'Port', name: 'vpn_'+t+'_port', type: 'text', value: eval( 'nvram.vpn_'+t+'_port' ) },
432 { title: 'Firewall', name: 'vpn_'+t+'_firewall', type: 'select', options: [ ['auto', 'Automatic'], ['external', 'External Only'], ['custom', 'Custom'] ], value: eval( 'nvram.vpn_'+t+'_firewall' ) },
433 { title: 'Authorization Mode', name: 'vpn_'+t+'_crypt', type: 'select', options: [ ['tls', 'TLS'], ['secret', 'Static Key'], ['custom', 'Custom'] ], value: eval( 'nvram.vpn_'+t+'_crypt' ),
434 suffix: '<span id=\''+t+'_custom_crypto_text\'>&nbsp;<small>(must configure manually...)</small></span>' },
435 { 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' ) },
436 { title: 'VPN subnet/netmask', multi: [
437 { name: 'vpn_'+t+'_sn', type: 'text', maxlen: 15, size: 17, value: eval( 'nvram.vpn_'+t+'_sn' ) },
438 { name: 'vpn_'+t+'_nm', type: 'text', maxlen: 15, size: 17, value: eval( 'nvram.vpn_'+t+'_nm' ) } ] },
439 { title: 'Client address pool', multi: [
440 { name: 'f_vpn_'+t+'_dhcp', type: 'checkbox', value: eval( 'nvram.vpn_'+t+'_dhcp' ) != 0, suffix: ' DHCP ' },
441 { name: 'vpn_'+t+'_r1', type: 'text', maxlen: 15, size: 17, value: eval( 'nvram.vpn_'+t+'_r1' ), prefix: '<span id=\''+t+'_range\'>', suffix: '-' },
442 { name: 'vpn_'+t+'_r2', type: 'text', maxlen: 15, size: 17, value: eval( 'nvram.vpn_'+t+'_r2' ), suffix: '</span>' } ] },
443 { title: 'Local/remote endpoint addresses', multi: [
444 { name: 'vpn_'+t+'_local', type: 'text', maxlen: 15, size: 17, value: eval( 'nvram.vpn_'+t+'_local' ) },
445 { name: 'vpn_'+t+'_remote', type: 'text', maxlen: 15, size: 17, value: eval( 'nvram.vpn_'+t+'_remote' ) } ] }
447 W('</div>');
448 W('<div id=\''+t+'-advanced\'>');
449 createFieldTable('', [
450 { 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>' },
451 { title: 'Push LAN to clients', name: 'f_vpn_'+t+'_plan', type: 'checkbox', value: eval( 'nvram.vpn_'+t+'_plan' ) != 0 },
452 { title: 'Direct clients to<br>redirect Internet traffic', name: 'f_vpn_'+t+'_rgw', type: 'checkbox', value: eval( 'nvram.vpn_'+t+'_rgw' ) != 0 },
453 { title: 'Respond to DNS', name: 'f_vpn_'+t+'_dns', type: 'checkbox', value: nvram.vpn_server_dns.indexOf(''+(i+1)) >= 0 },
454 { title: 'Advertise DNS to clients', name: 'f_vpn_'+t+'_pdns', type: 'checkbox', value: eval( 'nvram.vpn_'+t+'_pdns' ) != 0 },
455 { title: 'Encryption cipher', name: 'vpn_'+t+'_cipher', type: 'select', options: ciphers, value: eval( 'nvram.vpn_'+t+'_cipher' ) },
456 { title: 'Compression', name: 'vpn_'+t+'_comp', type: 'select', options: [ ['-1', 'Disabled'], ['no', 'None'], ['yes', 'Enabled'], ['adaptive', 'Adaptive'] ], value: eval( 'nvram.vpn_'+t+'_comp' ) },
457 { title: 'TLS Renegotiation Time', name: 'vpn_'+t+'_reneg', type: 'text', maxlen: 10, size: 7, value: eval( 'nvram.vpn_'+t+'_reneg' ),
458 suffix: '&nbsp;<small>(in seconds, -1 for default)</small>' },
459 { title: 'Manage Client-Specific Options', name: 'f_vpn_'+t+'_ccd', type: 'checkbox', value: eval( 'nvram.vpn_'+t+'_ccd' ) != 0 },
460 { title: 'Allow Client<->Client', name: 'f_vpn_'+t+'_c2c', type: 'checkbox', value: eval( 'nvram.vpn_'+t+'_c2c' ) != 0 },
461 { title: 'Allow Only These Clients', name: 'f_vpn_'+t+'_ccd_excl', type: 'checkbox', value: eval( 'nvram.vpn_'+t+'_ccd_excl' ) != 0 },
462 { title: '', suffix: '<table class=\'tomato-grid\' id=\'table_'+t+'_ccd\'></table>' },
463 { title: 'Custom Configuration', name: 'vpn_'+t+'_custom', type: 'textarea', value: eval( 'nvram.vpn_'+t+'_custom' ) }
465 W('</div>');
466 W('<div id=\''+t+'-keys\'>');
467 W('<p class=\'keyhelp\'>For help generating keys, refer to the OpenVPN <a id=\''+t+'-keyhelp\'>HOWTO</a>.</p>');
468 createFieldTable('', [
469 { title: 'Static Key', name: 'vpn_'+t+'_static', type: 'textarea', value: eval( 'nvram.vpn_'+t+'_static' ) },
470 { title: 'Certificate Authority', name: 'vpn_'+t+'_ca', type: 'textarea', value: eval( 'nvram.vpn_'+t+'_ca' ) },
471 { title: 'Server Certificate', name: 'vpn_'+t+'_crt', type: 'textarea', value: eval( 'nvram.vpn_'+t+'_crt' ) },
472 { title: 'Server Key', name: 'vpn_'+t+'_key', type: 'textarea', value: eval( 'nvram.vpn_'+t+'_key' ) },
473 { title: 'Diffie Hellman parameters', name: 'vpn_'+t+'_dh', type: 'textarea', value: eval( 'nvram.vpn_'+t+'_dh' ) }
475 W('</div>');
476 W('<div id=\''+t+'-status\'>');
477 W('<div id=\''+t+'-no-status\'><p>Server is not running or status could not be read.</p></div>');
478 W('<div id=\''+t+'-status-content\' style=\'display:none\' class=\'status-content\'>');
479 W('<div id=\''+t+'-status-header\' class=\'status-header\'><p>Data current as of <span id=\''+t+'-status-time\'></span>.</p></div>');
480 W('<div id=\''+t+'-status-clients\'><div class=\'section-title\'>Client List</div><table class=\'tomato-grid status-table\' id=\''+t+'-status-clients-table\'></table><br></div>');
481 W('<div id=\''+t+'-status-routing\'><div class=\'section-title\'>Routing Table</div><table class=\'tomato-grid status-table\' id=\''+t+'-status-routing-table\'></table><br></div>');
482 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>');
483 W('<div id=\''+t+'-status-errors\' class=\'error\'></div>');
484 W('</div>');
485 W('<div style=\'text-align:right\'><a href=\'javascript:updateStatus('+i+')\'>Refresh Status</a></div>');
486 W('</div>');
487 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">');
488 W('</div>');
491 </script>
492 </div>
494 </td></tr>
495 <tr><td id='footer' colspan=2>
496 <span id='footer-msg'></span>
497 <input type='button' value='Save' id='save-button' onclick='save()'>
498 <input type='button' value='Cancel' id='cancel-button' onclick='javascript:reloadPage();'>
499 </td></tr>
500 </table>
501 </form>
502 <script type='text/javascript'>init();</script>
503 </body>
504 </html>