Files
webgui/emhttp/plugins/dynamix/WG0.page
2025-08-04 11:31:40 -07:00

1653 lines
77 KiB
Plaintext

Menu="VPNmanager:100"
Title="Tunnel wg0"
Tag="icon-vpn"
Nchan="wg_poller"
---
<?PHP
/* Copyright 2005-2025, Lime Technology
* Copyright 2012-2025, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<?
$etc = '/etc/wireguard';
$tmp = '/tmp/list.tmp';
unset($subnets,$hosts,$subnets6,$hosts6,$vtuns,$filter,$docker);
exec("ip -4 route show scope link | awk '/^[^d].+ dev (eth|br|bond|wlan)[0-9]+(\\.[0-9]+)?/{print \$1}'",$subnets);
exec("ip -6 route show type unicast | awk '\$0 !~ \"expires\" && \$3 !~ \"^shim-\" && /^[^dfm:]/{print \$1}'",$subnets6);
exec("ip -4 -br addr show scope global | awk '/^(br|bond|eth|wlan)[0-9]+(\\.[0-9]+)?/{split(\$3,ip,\"/\");print ip[1]}'",$hosts);
exec("ip -6 -br addr show scope global -temporary -deprecated | awk '/^(br|bond|eth|wlan)[0-9]+(\\.[0-9]+)?/{split(\$3,ip,\"/\");print ip[1]}'",$hosts6);
exec("ls --indicator-style=none $etc/wg*.conf* | grep -Po wg[0-9]+",$vtuns);
exec("docker network ls --filter driver='macvlan' --filter driver='ipvlan' --format='{{.Name}}' 2>/dev/null",$filter);
$nginx = (array)@parse_ini_file('state/nginx.ini');
// add subnets defined in Docker custom networks
if (count($filter)) {
exec("docker network inspect --format='{{range .IPAM.Config}}{{println .Subnet}}{{end}}' ".implode(' ',$filter),$docker);
foreach (array_filter($docker) as $network) {
if (strpos($network,'.')!==false && !in_array($network,$subnets)) $subnets[] = $network;
elseif (strpos($network,':')!==false && !in_array($network,$subnets6)) $subnets6[] = $network;
}
}
$subnets = implode(',',$subnets);
$hosts = implode(',',$hosts);
$subnets6 = implode(',',$subnets6);
$hosts6 = implode(',',$hosts6);
function ifname($eth, $new) {
return str_replace('eth',$new,$eth);
}
function iflink($eth) {
if (lan_port(ifname($eth,'br'))) return ifname($eth,'br');
if (lan_port(ifname($eth,'bond'))) return ifname($eth,'bond');
return $eth;
}
function concat($array) {
return implode(',',array_map(function($v){return "'$v'";},$array));
}
function readConf(&$peer_wg, &$wg, $vtun) {
global $etc,$netbase,$netpool,$netbase6,$netpool6,$validIP4,$validIP6;
$conf = "$etc/$vtun.conf";
$cfg = "$etc/$vtun.cfg";
$file = false;
$vpn = 0;
if (file_exists($conf) && filesize($conf) > 0) {
$entries = array_filter(array_map('trim',preg_split('/\[(Interface|Peer)\]/',file_get_contents($conf))));
foreach ($entries as $key => $entry) {
$i = $key-1;
if ($i) $peer_wg[] = $i;
$rows = explode("\n",$entry);
foreach ($rows as $row) {
[$id,$data] = array_map('trim',my_explode('=',$row));
switch ($id) {
case 'PostUp':
case 'PostDown':
break;
case 'Address':
foreach (array_map('trim',explode(',',$data)) as $ip) {
if (strpos($ip,'.')!==false) $wg["Address:$i"] = $ip;
elseif (strpos($ip,':')!==false) $wg["Address6:$i"] = $ip;
}
break;
case 'Endpoint':
if ($data[0]=='[') {
[$ip,$port] = my_explode(']:',$data);
$ip = substr($ip,1);
} else {
[$ip,$port] = my_explode(':',$data);
}
$wg["Endpoint:$i"] = $ip;
$wg["ListenPort:$i"] = $port;
break;
default:
if ($id[0]=='#') $wg["Name:$i"] = substr($id,1); else $wg["$id:$i"] = $data;
break;
}
}
}
$file = true;
}
if (file_exists($cfg)) {
$more = parse_ini_file($cfg);
foreach ($more as $key => $data) {
[$id,$i] = my_explode(':',$key);
if ($id!='Address') continue;
switch ($more['PROT:0']) {
case '46':
[$ip4,$ip6] = array_map('trim',my_explode(',',$data));
$more[$key] = $ip4;
$more["Address6:$i"] = $ip6;
break;
case '6':
unset($more[$key]);
$more["Address6:$i"] = $data;
break;
}
}
$wg = array_merge($wg,$more);
[$subnet,$mask] = my_explode('/',_var($wg,'Network:0'));
[$subnet6,$mask6] = my_explode('/',_var($wg,'Network6:0'));
$netbase[$vtun] = ip2long($subnet) & (0x100000000-2**(32-$mask));
$netbase6[$vtun] = $subnet6 ?: $netpool6[$vtun];
} else {
$netbase[$vtun] = ip2long($netpool[$vtun]);
$netbase6[$vtun] = $netpool6[$vtun];
}
foreach ($peer_wg as $i) if (_var($wg,"TYPE:$i",0)>=7) {$vpn = $wg["TYPE:$i"]; break;}
return [$conf,$cfg,$file,$vpn];
}
$public = _var($nginx,'NGINX_WANFQDN');
$active = explode(' ',exec('wg show interfaces'));
$autostart = explode(' ',@file_get_contents("$etc/autostart")?:'');
$build = false;
$script = "$docroot/webGui/scripts/upnp_port";
$services = "$docroot/webGui/scripts/update_services";
$template = "$docroot/webGui/WGX.page";
$tower = _var($var,'NAME');
$ethX = 'eth0';
$server = ipaddr($ethX);
$dnsserver = _var($$ethX,'DNS_SERVER1');
$link = iflink($ethX);
$vhost = str_replace(['eth','br','bond'],'vhost',$link);
$wlanUp4 = "iptables -t nat -A POSTROUTING -s <source> -o wlan0 -j MASQUERADE";
$wlanUp6 = "ip6tables -t nat -A POSTROUTING -s <source> -o wlan0 -j MASQUERADE";
$wlanDown4 = "iptables -t nat -D POSTROUTING -s <source> -o wlan0 -j MASQUERADE";
$wlanDown6 = "ip6tables -t nat -D POSTROUTING -s <source> -o wlan0 -j MASQUERADE";
$postUp0 = "$script add $link WireGuard-<wg> $server <port> <port> udp";
$postUp1 = "logger -t wireguard -- 'Tunnel WireGuard-<wg> started'; $services";
$postUp2 = "iptables -t nat -A POSTROUTING -s <source> -o $link -j MASQUERADE; iptables -t nat -A POSTROUTING -s <source> -o $vhost -j MASQUERADE; $wlanUp4";
$postUp3 = "iptables -N WIREGUARD_DROP_<WG>; iptables -A WIREGUARD -o $link -j WIREGUARD_DROP_<WG>";
$postUpX = "iptables -A WIREGUARD_DROP_<WG> -s <source> -d <target> -j DROP";
$postUpZ = "iptables -A WIREGUARD_DROP_<WG> -s <source> -j ACCEPT; iptables -A WIREGUARD_DROP_<WG> -j RETURN";
$postUp26 = "ip6tables -t nat -A POSTROUTING -s <source> -o $link -j MASQUERADE; ip6tables -t nat -A POSTROUTING -s <source> -o $vhost -j MASQUERADE; $wlanUp6";
$postUp36 = "ip6tables -N WIREGUARD_DROP_<WG>; ip6tables -A WIREGUARD -o $link -j WIREGUARD_DROP_<WG>";
$postUpX6 = "ip6tables -A WIREGUARD_DROP_<WG> -s <source> -d <target> -j DROP";
$postUpZ6 = "ip6tables -A WIREGUARD_DROP_<WG> -s <source> -j ACCEPT; ip6tables -A WIREGUARD_DROP_<WG> -j RETURN";
$postDown0 = "$script del $link <port> udp";
$postDown1 = "logger -t wireguard -- 'Tunnel WireGuard-<wg> stopped'; $services";
$postDown2 = "iptables -t nat -D POSTROUTING -s <source> -o $link -j MASQUERADE; iptables -t nat -D POSTROUTING -s <source> -o $vhost -j MASQUERADE; $wlanDown4";
$postDown3 = "iptables -F WIREGUARD_DROP_<WG>; iptables -D WIREGUARD -o $link -j WIREGUARD_DROP_<WG>; iptables -X WIREGUARD_DROP_<WG>";
$postDown26= "ip6tables -t nat -D POSTROUTING -s <source> -o $link -j MASQUERADE; ip6tables -t nat -D POSTROUTING -s <source> -o $vhost -j MASQUERADE; $wlanDown6";
$postDown36= "ip6tables -F WIREGUARD_DROP_<WG>; ip6tables -D WIREGUARD -o $link -j WIREGUARD_DROP_<WG>; ip6tables -X WIREGUARD_DROP_<WG>";
$tld = @file_get_contents("$docroot/webGui/include/tld.key")?:'';
$validDNS = "([0-9a-z]([0-9a-z\-]{0,61}[0-9a-z])?\.)+($tld)";
$validIP4 = "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}";
$validIP6 = "(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(:|([0-9a-fA-F]{1,4}:)+):(([0-9a-fA-F]{1,4}:)*[0-9a-fA-F]{1,4})?)";
$maskIP4 = "([0-9]|[12][0-9]|3[0-2])?";
$maskIP6 = "([0-9]|[1-9][0-9]|1[01][0-9]|12[0-8])?";
$validText = "^($validDNS|$validIP4|$validIP6)$";
$validList = "^(($validIP4/?$maskIP4|$validIP6/?$maskIP6)(, *)?)+$";
$validDNSServerList = "^(($validIP4|$validIP6)(, *)?)+$";
$validname = "^[0-9a-zA-Z \-_]*$";
$netbase = $netpool = $netport = [];
$netbase6 = $netpool6 = [];
$check_wg0 = in_array('wg0',$active) ? 'true' : 'false';
$start_wg0 = in_array('wg0',$autostart);
$peer_wg0 = $wg0 = [];
$netpool['wg0'] = '10.253.0.0';
$netpool6['wg0'] = 'fc00:253:0:0::';
$netport['wg0'] = 51820;
// read current configuration
[$conf_wg0,$cfg_wg0,$this_wg0,$vpn_wg0] = readConf($peer_wg0,$wg0,'wg0');
// gather IPv4 and IPv6 addresses for available interfaces
$endpoints = [];
$eth = 'eth0';
while (isset($$eth)) {
if (!empty($$eth['IPADDR:0'])) $endpoints[] = $$eth['IPADDR:0'];
if (!empty($$eth['IPADDR6:0'])) $endpoints[] = $$eth['IPADDR6:0'];
$eth = 'eth'.(substr($eth,3)+1);
}
// remove obsolete tunnels
foreach (glob("$docroot/webGui/WG[1-9]*.page",GLOB_NOSORT) as $wgX) {
if (!in_array(strtolower(basename($wgX,'.page')),$vtuns)) {
delete_file($wgX);
$build = true;
}
}
// add additional tunnels
foreach ($vtuns as $wgX) {
if ($wgX == 'wg0') continue;
$file = "$docroot/webGui/".strtoupper($wgX).".page";
if (!file_exists($file)) {
$X = filter_var($wgX,FILTER_SANITIZE_NUMBER_INT);
$nnn = 100 + $X;
copy($template,$file);
exec("sed -i 's/parentname:nnn/VPNmanager:$nnn/;s/XXX/$X/g;s/wgX/$wgX/g' $file");
chmod($file,0644);
$build = true;
}
}
?>
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.switchbutton.css')?>">
<script src="<?autov('/webGui/javascript/jquery.switchbutton.js')?>"></script>
<script>
<?if ($build):?>
refresh();
<?endif;?>
Number.prototype.long2ip = function(){return [this>>>24,this>>>16&255,this>>>8&255,this&255].join('.');}
String.prototype.ip2long = function(){var ip=this.split('.');return (ip[0]<<24)+(ip[1]<<16)+(ip[2]<<8)+(ip[3]*1);}
String.prototype.desc = function(){return this.substr(this.lastIndexOf('/'));}
String.prototype.patch = function(s,t){return t?this.replace(/<source>/g,s).replace(/<target>/,t):this.replace(/<source>/g,s);}
Array.prototype.bind = function(w){return (this.join(';')).replace(/<WG>/g,w).replace(/,/g,';');}
var xml = $.cookie('upnp')||'<?=@file_get_contents('/var/tmp/upnp')?>';
var netbase = {}, netpool = {}, netport = {}, netbase6 = {}, netpool6 = {};
netbase['wg0'] = <?=_var($netbase,'wg0')?>;
netpool['wg0'] = "<?=_var($netpool,'wg0')?>";
netport['wg0'] = "<?=_var($netport,'wg0')?>";
netbase6['wg0'] = "<?=_var($netbase6,'wg0')?>";
netpool6['wg0'] = "<?=_var($netpool6,'wg0')?>";
// polyfill to fix reportValidity() for Firefox
if (!HTMLInputElement.prototype.reportValidity || (navigator.userAgent.indexOf("Firefox") !== -1)) {
HTMLInputElement.prototype.reportValidity = function () {
if (this.checkValidity()) {
return true;
} else {
var labelText = "_(Invalid data)_";
try {
labelText = $(this).closest('dl').children().first()[0].textContent;
} catch (err) { }
// the browser generates the validationMessage, we cannot translate it
swal({title:labelText,text:this.validationMessage,animation:'none',html:true,type:'error',confirmButtonText:"_(Ok)_"});
return false;
}
};
}
function ipv4(ip) {
return ip.indexOf('.') > 0;
}
function ipv6(ip) {
return ip.indexOf(':') > 0;
}
function ipset(ip) {
return ipv4(ip) ? ip : '['+ip+']';
}
function nodefault(ip) {
return ip != '0.0.0.0/0';
}
function nodefault6(ip) {
return ip != '::/0';
}
function validateForm(form,vtun) {
// this runs when the Apply button is clicked, before built-in form validation and before the form's onsubmit function
// if any fields do not pass basic validation, ensure they are visible then manually show the validation error
// this prevents validation errors on hidden fields
var isValid = true;
form.find('input').each(function(){
var i = this.name.match(/.*:(\d+)/) ? this.name.match(/.*:(\d+)/)[1] : -1;
if (i > -1 && !this.checkValidity()) {
if ($(this).is(":hidden")) {
var icon = $('#chevron-'+vtun+'-'+i);
openClose(form,icon,'div.zone'+i);
}
this.reportValidity();
isValid = false;
}
});
return isValid;
}
function prepareSettings(form,vtun) {
var subnets = '<?=$subnets?>';
var hosts = '<?=$hosts?>';
var subnets6 = '<?=$subnets6?>';
var hosts6 = '<?=$hosts6?>';
var protocol = form.find('select[name="PROT:0"]').val();
var listen = form.find('input[name="gui:ListenPort:0"]').val()||netport[vtun];
var network = form.find('input[name="gui:Network:0"]').val();
var mypool = netpool[vtun];
var network6 = form.find('input[name="gui:Network6:0"]').val();
var mypool6 = netpool6[vtun];
var vpn = 0;
form.find('input[name="Network:0"]').val((network||mypool)+'/'+(form.find('input[name="gui:Mask:0"]').val()||24));
form.find('input[name="Network6:0"]').val((network6||mypool6)+'/'+(form.find('input[name="gui:Mask6:0"]').val()||64));
form.find('input[name^="Address:"]').each(function(){
var i = $(this).attr('name').split(':')[1];
var address = $(this).val();
var address6 = form.find('input[name="Address6:'+i+'"]').val();
switch (protocol) {
case '46': $(this).val(address+', '+address6); break;
case '6' : $(this).val(address6); break;
default : break;
}
});
switch (protocol) {
case '46':
form.find('input[name="#subnets1"]').val(form.find('input[name="Address:0"]').val()+', '+form.find('input[name="Address6:0"]').val()+', '+subnets+', '+subnets6);
form.find('input[name="#subnets2"]').val(form.find('input[name="Address:0"]').val()+', '+form.find('input[name="Address6:0"]').val()+', '+hosts+', '+hosts6);
form.find('input[name="#shared1"]').val(form.find('input[name="Network:0"]').val()+', '+form.find('input[name="Network6:0"]').val()+', '+subnets+', '+subnets6);
form.find('input[name="#shared2"]').val(form.find('input[name="Network:0"]').val()+', '+form.find('input[name="Network6:0"]').val()+', '+hosts+', '+hosts6);
break;
case '6':
form.find('input[name="#subnets1"]').val(form.find('input[name="Address6:0"]').val()+', '+subnets6);
form.find('input[name="#subnets2"]').val(form.find('input[name="Address6:0"]').val()+', '+hosts6);
form.find('input[name="#shared1"]').val(form.find('input[name="Network6:0"]').val()+', '+subnets6);
form.find('input[name="#shared2"]').val(form.find('input[name="Network6:0"]').val()+', '+hosts6);
break;
default:
form.find('input[name="#subnets1"]').val(form.find('input[name="Address:0"]').val()+', '+subnets);
form.find('input[name="#subnets2"]').val(form.find('input[name="Address:0"]').val()+', '+hosts);
form.find('input[name="#shared1"]').val(form.find('input[name="Network:0"]').val()+', '+subnets);
form.find('input[name="#shared2"]').val(form.find('input[name="Network:0"]').val()+', '+hosts);
break;
}
form.find('select[name^="TYPE:"]').each(function(){if($(this).val()>=7){vpn=$(this).val();return false};});
if (vpn==0) form.find('input[name="ListenPort:0"]').val(listen);
form.find('input[name^="gui:Endpoint:"]').each(function(){
var i = $(this).attr('name').split(':')[2];
var endpoint = form.find('input[name="Endpoint:'+i+'"]');
var port = form.find('input[name="gui:ListenPort:'+i+'"]').val()||listen;
if ($(this).val()) endpoint.val(ipset($(this).val())+':'+port);
});
form.find('input[name^="Address6:"]').each(function(){$(this).prop('disabled',true);});
form.find('input[name^="gui:"]').each(function(){$(this).prop('disabled',true);});
form.find('input[name="#wg"]').val(tstate[vtun]);
form.find('input[name="#internet"]').val(ipset(form.find('input[name="#internet"]').val())+':'+listen);
var upnp = form.find('select[name="UPNP:0"]');
var postup = form.find('input[name="PostUp:0:0"]');
var postdown = form.find('input[name="PostDown:0:0"]');
var source = form.find('input[name="Network:0"]').val();
var source6 = form.find('input[name="Network6:0"]').val();
upnp.prop('disabled',false);
<?if (!is_executable('/usr/bin/upnpc')):?>
upnp.val('no');
<?endif;?>
if (!xml||vpn > 0||upnp.val()=='no') {
postup.prop('disabled',true);
postdown.prop('disabled',true);
} else {
postup.val(postup.val().replace(/<wg>/,vtun).replace(/<port>/g,listen));
postdown.val(postdown.val().replace(/<port>/,listen));
}
postup = form.find('input[name="PostUp:0:1"]');
postup.val(postup.val().replace(/<wg>/,vtun));
postdown = form.find('input[name="PostDown:0:1"]');
postdown.val(postdown.val().replace(/<wg>/,vtun));
postup = form.find('input[name="PostUp:0:2"]');
postdown = form.find('input[name="PostDown:0:2"]');
var drop = form.find('input[name="DROP:0"]').val();
var postup1 = [], postup16 = [];
var postdown1 = [], postdown16 = [];
var ip4 = []; ip6 = [];
if (form.find('select[name="NAT:0"]').val()=='') {
var postup2 = '<?=$postUp2?>';
var postup26 = '<?=$postUp26?>';
var postdown2 = '<?=$postDown2?>';
var postdown26 = '<?=$postDown26?>';
switch (protocol) {
case '46':
postup1.push(postup2.patch(source));
postdown1.push(postdown2.patch(source));
postup16.push(postup26.patch(source6));
postdown16.push(postdown26.patch(source6));
break;
case '6':
postup16.push(postup26.patch(source6));
postdown16.push(postdown26.patch(source6));
break;
default:
postup1.push(postup2.patch(source));
postdown1.push(postdown2.patch(source));
break;
}
}
if (drop) {
var postup3 = '<?=$postUp3?>';
var postupX = '<?=$postUpX?>';
var postupZ = '<?=$postUpZ?>';
var postup36 = '<?=$postUp36?>';
var postupX6 = '<?=$postUpX6?>';
var postupZ6 = '<?=$postUpZ6?>';
var postdown3 = '<?=$postDown3?>';
var postdown36 = '<?=$postDown36?>';
vtun = vtun.toUpperCase();
drop = drop.replace(/ /g,'').split(',');
if (form.find('select[name="RULE:0"]').val()) {
postupX = postupX.replace('-j DROP','-j ACCEPT');
postupZ = postupZ.replace('-j ACCEPT','-j DROP');
postupX6 = postupX6.replace('-j DROP','-j ACCEPT');
postupZ6 = postupZ6.replace('-j ACCEPT','-j DROP');
}
for (var i=0,target; target=drop[i]; i++) {
switch (protocol) {
case '46':
if (ipv4(target)) ip4.push(postupX.patch(source,target));
if (ipv6(target)) ip6.push(postupX6.patch(source6,target));
break;
case '6':
if (ipv6(target)) ip6.push(postupX6.patch(source6,target));
break;
default:
if (ipv4(target)) ip4.push(postupX.patch(source,target));
break;
}
}
}
switch (protocol) {
case '46':
if (ip4.length > 0) {
postup1.push(postup3);
postup1.push(ip4);
postup1.push(postupZ.patch(source));
postdown1.push(postdown3);
}
if (ip6.length > 0) {
postup16.push(postup36);
postup16.push(ip6);
postup16.push(postupZ6.patch(source6));
postdown16.push(postdown36);
}
postup1.push(postup16);
postdown1.push(postdown16);
postup.val(postup1.bind(vtun));
postdown.val(postdown1.bind(vtun));
break;
case '6':
if (ip6.length > 0) {
postup16.push(postup36);
postup16.push(ip6);
postup16.push(postupZ6.patch(source6));
postdown16.push(postdown36);
}
postup.val(postup16.bind(vtun));
postdown.val(postdown16.bind(vtun));
break;
default:
if (ip4.length > 0) {
postup1.push(postup3);
postup1.push(ip4);
postup1.push(postupZ.patch(source));
postdown1.push(postdown3);
}
postup.val(postup1.bind(vtun));
postdown.val(postdown1.bind(vtun));
break;
}
$('div[id^="index-'+vtun+'-"]').each(function(){
var temp = $(this).find('select[name^="TYPE:"]').val();
if (temp >= 7) form.find('input[name="#type"]').val(temp);
});
if (!postup.val()) postup.prop('disabled',true);
if (!postdown.val()) postdown.prop('disabled',true);
}
function keypair(form,i) {
$.post('/webGui/include/update.wireguard.php',{'#cmd':'keypair'},function(keys){
var key = keys.split('\0');
form.find('.private-'+i).val(key[0]).trigger('change');
form.find('.public-'+i).val(key[1]).trigger('change');
});
}
function presharedkey(form,i) {
$.post('/webGui/include/update.wireguard.php',{'#cmd':'presharedkey'},function(key){
form.find('.preshared-'+i).val(key).trigger('change');
});
}
function clearTunnel(vtun) {
$.removeCookie('my-'+vtun);
$.removeCookie('view-'+vtun);
$.removeCookie('block-'+vtun);
refresh();
}
function addTunnel() {
console.log('addTunnel');
$.post('/webGui/include/update.wireguard.php',{'#cmd':'addtunnel','#name':'<?=$tower?>'},function(){refresh();});
}
function delTunnel(vtun) {
swal({title:"_(Delete Tunnel)_ "+vtun,text:"_(This removes any connections running over this tunnel)_",type:'warning',animation:'none',html:true,confirmButtonText:"_(Proceed)_",cancelButtonText:"_(Cancel)_",showCancelButton:true},function(){
$.post('/webGui/include/update.wireguard.php',{'#cmd':'deltunnel','#vtun':vtun,'#name':'<?=$tower?>'},function(ok){
if (ok==0) {
clearTunnel(vtun);
} else {
setTimeout(function(){swal({title:"_(Delete tunnel failed)_",text:"_(Tunnel has running containers attached)_<br>_(Stop corresponding docker containers)_",animation:'none',html:true,type:'error',confirmButtonText:"_(Ok)_"});},250);
}
});
});
}
function addPeer(form,vtun) {
// don't add peer if there are errors that would be propagated to the peer
if (!validateForm(form,vtun)) return false;
var index = [0];
form.find('div[id^="index-'+vtun+'-"]').each(function(){index.push($(this).prop('id').split('-')[2]);});
var i = Math.max(...index)+1;
var template = $($('<div/>').loadTemplate($('#peer-template-'+vtun)).html().replace(/INDEX/g,i));
var peer = i==1 ? $('div#peers-list-'+vtun) : $('[id^="index-'+vtun+'-"]').last();
$(peer).after(template);
if (i==1) form.find('select[name="TYPE:1"] option').each(function(){$(this).prop('disabled',false);});
var address = [], address6 = [];
var protocol = form.find('select[name="PROT:0"]').val();
if (protocol!='6') {
form.find('input[name^="Address:"]').each(function(){if ($(this).val()) address.push($(this).val().ip2long());});
var ip = (Math.max(...address)+1).long2ip();
form.find('input[name="Address:'+i+'"]').val(ip);
}
if (protocol!='') {
form.find('input[name^="Address6:"]').each(function(){if ($(this).val()) address6.push($(this).val().split('::')[1]);});
var ip6 = (form.find('input[name="Network6:0"]').val()||netpool6[vtun])+(address6.sort(function(x,y){return x-y}).pop()*1+1);
form.find('input[name="Address6:'+i+'"]').val(ip6);
}
switch (protocol) {
case '46':
var data = ip+', '+ip6;
form.find('input[name="Address:'+i+'"]').attr('placeholder',"(_(mandatory)_)").prop('required',true);
form.find('input[name="Address6:'+i+'"]').attr('placeholder',"(_(mandatory)_)").prop('required',true);
$('div[class="ipv4 '+vtun+'"]').show();
$('div[class="ipv6 '+vtun+'"]').show();
break;
case '6':
var data = ip6;
form.find('input[name="Address:'+i+'"]').attr('placeholder',"(_(not used)_)").removeAttr('required').val('');
form.find('input[name="Address6:'+i+'"]').attr('placeholder',"(_(mandatory)_)").prop('required',true);
$('div[class="ipv4 '+vtun+'"]').hide();
$('div[class="ipv6 '+vtun+'"]').show();
break;
default:
var data = ip;
form.find('input[name="Address:'+i+'"]').attr('placeholder',"(_(mandatory)_)").prop('required',true);
form.find('input[name="Address6:'+i+'"]').attr('placeholder',"(_(not used)_)").removeAttr('required').val('');
$('div[class="ipv4 '+vtun+'"]').show();
$('div[class="ipv6 '+vtun+'"]').hide();
break;
}
form.find('input[name="AllowedIPs:'+i+'"]').val(data);
form.find('input[name="Address:'+i+'"]').on('input change',function(){form.find('#ping-button1-'+i).prop('disabled',$(this).val()=='');});
form.find('input[name="Address6:'+i+'"]').on('input change',function(){form.find('#ping-button6-'+i).prop('disabled',$(this).val()=='');});
form.find('input[name="gui:Endpoint:'+i+'"]').on('input change',function(){form.find('#ping-button2-'+i).prop('disabled',$(this).val()=='');});
if ($.cookie('view-'+vtun)=='advanced') {
form.find('div.zone'+i).show();
form.find('i.fa-chevron-down').last().removeClass().addClass('fa fa-chevron-up');
}
form.find('input[class$="zone'+i+'"]').show();
form.find('input[name="Name:0"]').trigger('change');
$('blockquote.inline_help').each(function(i) {
$(this).attr('id','helpinfo'+i);
var pin = $(this).prev();
if (!pin.prop('nodeName')) pin = $(this).parent().prev();
while (pin.prop('nodeName') && pin.prop('nodeName').search(/(table|dl)/i) == -1) pin = pin.prev();
pin.find('tr:first,dt:last').each(function() {
var node = $(this);
var name = node.prop('nodeName').toLowerCase();
if (name=='dt') {
while (!node.html() || node.html().search(/(<input|<select|nbsp;)/i) >= 0 || name!='dt') {
if (name=='dt' && node.is(':first-of-type')) break;
node = node.prev();
name = node.prop('nodeName').toLowerCase();
}
node.css('cursor','help').prop('onclick',null).off('click').click(function(){$('#helpinfo'+i).toggle('slow');});
} else {
if (node.html() && (name!='tr' || node.children('td:first').html())) node.css('cursor','help').prop('onclick',null).off('click').click(function(){$('#helpinfo'+i).toggle('slow');});
}
});
});
}
function delPeer(form,peer) {
var deleted = form.find('input[name="#deleted"]');
var comma = deleted.val() ? ',' : '';
deleted.val(deleted.val()+comma+peer.substr(7));
form.find(peer).remove();
form.find('input[name="Name:0"]').trigger('change');
}
function lockEye(form,i) {
form.find('i[class*="eye'+i+'"]').removeClass('key-off').addClass('key-off');
}
function toLC(field) {
field.value=(field.value) ? field.value.toLowerCase() : '';
}
function updatePeer(form,i,n,vtun) {
var unraid = form.find('input[name="#internet"]').val().replace(/^(www\.).+(\.unraid.net)$/,'$1<hash>$2');
var type = form.find('select[name="TYPE:'+i+'"]').val();
var icon = $('#chevron-'+vtun+'-'+i);
var protocol = form.find('select[name="PROT:0"]').val();
switch (n) {
case '6':
var dns = form.find('input[name="DNS:'+i+'"]');
form.find('input[name="Endpoint:0"]').attr('placeholder',unraid);
form.find('input[name="gui:ListenPort:0"]').attr('placeholder',netport[vtun]);
form.find('select[name="NAT:0"]').prop('disabled',false);
form.find('input[name="gui:Endpoint:'+i+'"]').attr('placeholder',"(_(not used)_)").removeAttr('required').val('');
form.find('input[name="gui:ListenPort:'+i+'"]').removeAttr('placeholder').val('');
dns.attr('placeholder',"(_(mandatory)_)").prop('required',true).val(dns.val()||'<?=$dnsserver?>');
if (!dns.val() && icon.hasClass('fa-chevron-down')) openClose(form,icon,'div.zone'+i);
break;
case '7':
form.find('input[name="Endpoint:0"]').attr('placeholder',"(_(not used)_)");
form.find('input[name="gui:ListenPort:0"]').removeAttr('placeholder');
form.find('select[name="NAT:0"]').prop('disabled',true);
form.find('input[name="gui:Endpoint:'+i+'"]').attr('placeholder',"(_(mandatory)_)").prop('required',true);
form.find('input[name="gui:ListenPort:'+i+'"]').attr('placeholder',form.find('input[name="gui:ListenPort:0"]').val()||netport[vtun]);
form.find('input[name="Address:'+i+'"]').attr('placeholder',"(_(not used)_)").removeAttr('required');
form.find('input[name="Address6:'+i+'"]').attr('placeholder',"(_(not used)_)").removeAttr('required');
form.find('input[name="DNS:'+i+'"]').attr('placeholder',"(_(not used)_)").removeAttr('required').val('');
if (icon.hasClass('fa-chevron-down')) openClose(form,icon,'div.zone'+i);
break;
default:
form.find('input[name="Endpoint:0"]').attr('placeholder',unraid);
form.find('input[name="gui:ListenPort:0"]').attr('placeholder',netport[vtun]);
form.find('select[name="NAT:0"]').prop('disabled',false);
if (n=='2'||n=='3') {
form.find('input[name="gui:Endpoint:'+i+'"]').attr('placeholder',"(_(mandatory)_)").prop('required',true);
form.find('input[name="gui:ListenPort:'+i+'"]').attr('placeholder',form.find('input[name="gui:ListenPort:0"]').val()||netport[vtun]);
if (icon.hasClass('fa-chevron-down')) openClose(form,icon,'div.zone'+i);
} else {
form.find('input[name="gui:Endpoint:'+i+'"]').attr('placeholder',"(_(not used)_)").removeAttr('required').val('');
form.find('input[name="gui:ListenPort:'+i+'"]').removeAttr('placeholder').val('');
}
form.find('input[name="DNS:'+i+'"]').attr('placeholder',"(_(optional)_)").removeAttr('required');
break;
}
form.find('#addpeer-'+vtun).prop('disabled',n>=7);
var subnet = form.find('input[name="Address:'+i+'"]').val();
var subnet6 = form.find('input[name="Address6:'+i+'"]').val();
switch (protocol) {
case '46':
setAllow6(form,subnet6,i);
setAllow(form,subnet,i);
break;
case '6':
setAllow6(form,subnet6,i);
break;
default:
setAllow(form,subnet,i);
break;
}
showRemark(form);
showRoute(form,vtun);
showAccess(form,i,n);
}
function setProtocol(form,vtun,update) {
switch (form.find('select[name="PROT:0"]').val()) {
case '46':
$('div[class="ipv4 '+vtun+'"]').show();
$('div[class="ipv6 '+vtun+'"]').show();
form.find('input[name^="Address:"]').each(function(){
var i = $(this).attr('name').split(':')[1];
if (i==0) {
$(this).attr('placeholder',"(_(mandatory)_)").prop('required',true);
} else {
if (form.find('select[name="TYPE:'+i+'"]').val()<7) $(this).attr('placeholder',"(_(mandatory)_)").prop('required',true); else $(this).attr('placeholder',"(_(not used)_)").removeAttr('required');
}
});
form.find('input[name^="Address6:"]').each(function(){
var i = $(this).attr('name').split(':')[1];
if (i==0) {
$(this).attr('placeholder',"(_(mandatory)_)").prop('required',true);
} else {
if (form.find('select[name="TYPE:'+i+'"]').val()<7) $(this).attr('placeholder',"(_(mandatory)_)").prop('required',true); else $(this).attr('placeholder',"(_(not used)_)").removeAttr('required');
}
});
if (update) {
form.find('input[name="gui:Network6:0"]').trigger('change');
form.find('input[name="gui:Network:0"]').trigger('change');
}
break;
case '6':
$('div[class="ipv4 '+vtun+'"]').hide();
$('div[class="ipv6 '+vtun+'"]').show();
form.find('input[name^="Address:"]').each(function(){$(this).attr('placeholder',"(_(not used)_)").removeAttr('required').val('');});
form.find('input[name^="Address6:"]').each(function(){
var i = $(this).attr('name').split(':')[1];
if (i==0) {
$(this).attr('placeholder',"(_(mandatory)_)").prop('required',true);
} else {
if (form.find('select[name="TYPE:'+i+'"]').val()<7) $(this).attr('placeholder',"(_(mandatory)_)").prop('required',true); else $(this).attr('placeholder',"(_(not used)_)").removeAttr('required');
}
});
if (update) {
form.find('input[name="gui:Network6:0"]').trigger('change');
}
break;
default:
$('div[class="ipv4 '+vtun+'"]').show();
$('div[class="ipv6 '+vtun+'"]').hide();
form.find('input[name^="Address6:"]').each(function(){$(this).attr('placeholder',"(_(not used)_)").removeAttr('required').val('');});
form.find('input[name^="Address:"]').each(function(){
var i = $(this).attr('name').split(':')[1];
if (i==0) {
$(this).attr('placeholder',"(_(mandatory)_)").prop('required',true);
} else {
if (form.find('select[name="TYPE:'+i+'"]').val()<7) $(this).attr('placeholder',"(_(mandatory)_)").prop('required',true); else $(this).attr('placeholder',"(_(not used)_)").removeAttr('required');
}
});
if (update) {
form.find('input[name="gui:Network:0"]').trigger('change');
}
break;
}
}
function quickValidate(field) {
try {
var form = $(field).parents('form');
var i = field.name.match(/.*:(\d+)/)[1];
lockEye(form,i);
} catch (err) { }
return field.reportValidity();
}
function setPool(form,vtun) {
// perform standard field validation on Network and Mask before custom validation
field = form.find('input[name="gui:Network:0"]')[0];
field.setCustomValidity('');
if (!quickValidate(field)) return false;
field2 = form.find('input[name="gui:Mask:0"]')[0];
if (!quickValidate(field2)) return false;
// ensure Local tunnel network pool is a pool address
var network = form.find('input[name="gui:Network:0"]').val();
var mask = form.find('select[name="gui:Mask:0"]').val()||24;
var networkSubnet = network.ip2long() & (0x100000000-2**(32-mask));
if (network && network.ip2long() !== networkSubnet) {
network = networkSubnet.long2ip();
form.find('input[name="gui:Network:0"]').val(network);
}
// ensure Local tunnel network pool is not already in use on this server
var subnets = '<?=$subnets?>';
if (network && subnets.includes(network+"/")) {
field.setCustomValidity("_(The Local tunnel network pool cannot be in)_ "+subnets);
field.reportValidity();
return false;
}
// remove existing IPv4 addresses in AllowedIP list
form.find('input[name^="AllowedIPs:"]').each(function(){
var list = $(this).val().replace(/ +/g,'').split(',');
list = list.filter(ipv6);
$(this).val(list.join(', '));
});
// update existing IPv4 addresses with new pool address
netbase[vtun] = (network||netpool[vtun]).ip2long() & (0x100000000-2**(32-mask));
var assign = netbase[vtun];
form.find('input[name^="Address:"]').each(function(){
++assign;
var i = $(this).attr('name').split(':')[1];
$(this).val((assign).long2ip()).trigger('change');
});
}
function setPool6(form,vtun) {
// perform standard field validation on Network6 and Mask6 before custom validation
field = form.find('input[name="gui:Network6:0"]')[0];
field.setCustomValidity('');
if (!quickValidate(field)) return false;
field2 = form.find('input[name="gui:Mask6:0"]')[0];
if (!quickValidate(field2)) return false;
// ensure Local tunnel network pool is a pool address
var network6 = form.find('input[name="gui:Network6:0"]').val();
var mask6 = form.find('select[name="gui:Mask6:0"]').val()||64;
// this logic only handles IPv6 mask of 64. for other masks, assume the user knows what they are doing.
if (network6 && mask6 === 64) {
var network6Subnet = network6.match(/((.*?):){4}/) ? network6.match(/((.*?):){4}/)[0].slice(0,-1) : "";
if (network6Subnet && network6 !== network6Subnet+"::") {
network6 = network6Subnet+"::";
form.find('input[name="gui:Network6:0"]').val(network6);
}
}
// ensure Local tunnel network pool is not already in use on this server
var subnets6 = '<?=$subnets6?>';
if (network6 && subnets6.includes(network6+"/")) {
field.setCustomValidity("_(The Local tunnel network pool IPv6 cannot be in)_ "+subnets6);
field.reportValidity();
return false;
}
// remove existing IPv6 addresses in AllowedIP list
form.find('input[name^="AllowedIPs:"]').each(function(){
var list = $(this).val().replace(/ +/g,'').split(',');
list = list.filter(ipv4);
$(this).val(list.join(', '));
});
// update existing IPv6 addresses with new pool address
netbase6[vtun] = network6||netpool6[vtun];
var assign = 0;
form.find('input[name^="Address6:"]').each(function(){
++assign;
var i = $(this).attr('name').split(':')[1];
$(this).val(netbase6[vtun]+assign).trigger('change');
});
}
function verifyInSubnet(field) {
// perform standard field validation before custom validation
field.setCustomValidity('');
if (!quickValidate(field)) return false;
// ensure this IP address is in the Local tunnel network pool
var form = $(field).parents('form');
// note: if network or mask are invalid, subnet calculation will be unpredictable
// not going to validate them here as the UI would be confusing
var network = form.find('input[name="gui:Network:0"]').val()||form.find('input[name="gui:Network:0"]').prop('placeholder');
var mask = form.find('input[name="gui:Mask:0"]').val()||24;
var addr = $(field).val();
var networkSubnet = network.ip2long() & (0x100000000-2**(32-mask));
var addrSubnet = addr.ip2long() & (0x100000000-2**(32-mask));
if (networkSubnet !== addrSubnet) {
field.setCustomValidity(addr+" _(is not in the)_ "+networkSubnet.long2ip()+"/"+mask+" _(subnet)_");
field.reportValidity();
return false;
}
return true;
}
function verifyInSubnet6(field) {
// perform standard field validation before custom validation
field.setCustomValidity('');
if (!quickValidate(field)) return false;
// ensure this IPv6 address is in the Local tunnel network pool IPv6
var form = $(field).parents('form');
var network6 = form.find('input[name="gui:Network6:0"]').val()||form.find('input[name="gui:Network6:0"]').prop('placeholder');
var mask6 = form.find('input[name="gui:Mask6:0"]').val()||64;
if (mask6 !== 64) {
// this logic only handles IPv6 mask of 64. for other masks, assume the user knows what they are doing.
return true;
}
var network6Subnet = network6.match(/((.*?):){4}/) ? network6.match(/((.*?):){4}/)[0].slice(0,-1) : "";
var addr6 = $(field).val();
var addr6Subnet = addr6.match(/((.*?):){4}/) ? addr6.match(/((.*?):){4}/)[0].slice(0,-1) : "";
if (!network6Subnet || network6Subnet !== addr6Subnet) {
field.setCustomValidity(addr6+" _(is not in the)_ "+network6Subnet+"/"+mask6+" _(subnet)_");
field.reportValidity();
return false;
}
return true;
}
function setAllow(form,subnet,i) {
var input = form.find('input[name="AllowedIPs:'+i+'"]');
var type = form.find('select[name="TYPE:'+i+'"]').val();
var prot = form.find('select[name="PROT:0"]').val();
if (type < 7) {
var list = input.val().replace(/ +/g,'').split(',');
var n = prot=='46' ? 0 : (list.length > 1 ? 2 : 1);
list.splice(0,n);
list.unshift(subnet);
list = [...new Set(list)];
list = list.filter(nodefault);
if (n > 0) list = list.filter(ipv4);
} else {
var list = prot!='46' ? ['0.0.0.0/0'] : ['0.0.0.0/0','::/0'];
}
input.val(list.join(', '));
lockEye(form,i);
}
function setAllow6(form,subnet6,i) {
var input = form.find('input[name="AllowedIPs:'+i+'"]');
var type = form.find('select[name="TYPE:'+i+'"]').val();
var prot = form.find('select[name="PROT:0"]').val();
if (type < 7) {
var list = input.val().replace(/ +/g,'').split(',');
var n = prot=='46' ? 0 : (list.length > 1 ? 2 : 1);
list.splice(0,n);
list.unshift(subnet6);
list = [...new Set(list)];
list = list.filter(nodefault6);
if (n > 0) list = list.filter(ipv6);
} else {
var list = prot!='46' ? ['::/0'] : ['0.0.0.0/0','::/0'];
}
input.val(list.join(', '));
lockEye(form,i);
}
function ping(form,button,field) {
$(button).val('Pinging...');
$.post('/webGui/include/update.wireguard.php',{'#cmd':'ping','#addr':form.find(field).val().replace(/\/.+$/,'')},function(reply){
if (reply) {
$(button).addClass('ok').val("_(Replied)_");
} else {
$(button).addClass('nok').val("_(No Reply)_");
}
setTimeout(function(){$(button).removeClass('ok nok').val("_(Ping)_")},3000);
});
}
function WGconfig(icon,file,path) {
if (!$(icon).hasClass('key-off')) openChanges('wg_config '+file+' '+path, "_(WireGuard Configuration)_");
}
function highlight(form,input,i) {
$(input).css({'color':'#e68a00'});
lockEye(form,i);
}
function showRemark(form) {
var upnp = form.find('select[name="UPNP:0"]').val();
var vpn = 0;
form.find('select[name^="TYPE:"]').each(function(){if ($(this).val()>=7 && vpn==0) vpn = $(this).val();});
if (upnp=='' || vpn > 0) form.find('span.remark').hide(); else form.find('span.remark').show();
if (upnp=='' && vpn < 7) form.find('span.upnp').show(); else form.find('span.upnp').hide();
}
function showRoute(form,vtun,i) {
var nat = form.find('select[name="NAT:0"]').val();
var vpn = 0, lan = false;
form.find('select[name^="TYPE:"]').each(function(){
if ($(this).val()>=7 && vpn==0) vpn = $(this).val();
else lan |= $(this).val()=='6' || $(this).val() % 2;
});
if (nat=='no' && vpn==0 && lan) {
$('span#my-static1-'+vtun).show();
$('span#my-static2-'+vtun).hide();
} else {
$('span#my-static1-'+vtun).hide();
if (lan) $('span#my-static2-'+vtun).show(); else $('span#my-static2-'+vtun).hide();
}
if (i!=null) lockEye(form,i);
}
function showAccess(form,i,n) {
switch (n) {
case '0':
switch (form.find('select[name="PROT:0"]').val()) {
case '6' : var peer_addr = form.find('input[name="Address6:0"]').val()+"</b>"; break;
case '46': var peer_addr = form.find('input[name="Address:0"]').val()+"</b> _(or)_ <b>"+form.find('input[name="Address6:0"]').val()+"</b>"; break;
default : var peer_addr = form.find('input[name="Address:0"]').val()+"</b>"; break;
}
form.find('span#access-type-'+i).html("_(Remark)_: _(connect to this server using IP address)_ <b>"+peer_addr);
break;
case '1':
case '2':
case '3':
case '6': form.find('span#access-type-'+i).html("_(Remark)_: _(ensure the peer network is different from)_ <b><?=exec("ip -4 route show dev $link scope link|awk '{print \$1;exit}'")?></b>"); break;
case '4':
switch (form.find('select[name="PROT:0"]').val()) {
case '6' : var peer_net = form.find('input[name="gui:Network6:0"]').val()+'/'+(form.find('input[name="gui:Mask6:0"]').val()||64)+"</b>"; break;
case '46': var peer_net = form.find('input[name="gui:Network:0"]').val()+'/'+(form.find('input[name="gui:Mask:0"]').val()||24)+"</b> _(or)_ <b>"+form.find('input[name="gui:Network6:0"]').val()+'/'+(form.find('input[name="gui:Mask6:0"]').val()||64)+"</b>"; break;
default : var peer_net = form.find('input[name="gui:Network:0"]').val()+'/'+(form.find('input[name="gui:Mask:0"]').val()||24)+"</b>"; break;
}
form.find('span#access-type-'+i).html("_(Remark)_: _(connect to any device on the VPN tunnel)_ <b>"+peer_net);
break;
case '5': form.find('span#access-type-'+i).html("_(Remark)_: _(ensure the peer networks are different from)_ <b><?=exec("ip -4 route show dev $link scope link|awk '{print \$1;exit}'")?></b>"); break;
case '7': form.find('span#access-type-'+i).html("_(Remark)_: _(this must be the only peer in the tunnel and sole active tunnel when in use)_"); break;
case '8': form.find('span#access-type-'+i).html("_(Remark)_: _(VPN tunnel for docker containers only)_"); break;
}
}
function portRemark(form,vtun,val) {
$('#my-port-'+vtun).text(val);
var port = form.find('input[name="gui:ListenPort:0"]').val()||netport[vtun];
form.find('input[name^="gui:ListenPort:"]').each(function(i) {
if (i>0 && form.find('input[name="gui:Endpoint:'+i+'"]').val()) $(this).attr('placeholder',port);
});
}
function openClose(form,icon,zone) {
if (icon) {
form.find(zone).toggle('slow');
if ($(icon).hasClass('fa-chevron-down')) {
// advanced view
form.find(zone.replace('div','input')).show('slow');
$(icon).removeClass('fa-chevron-down').addClass('fa-chevron-up');
if (!form.find('input[name="PublicKey:'+zone.replace('div.zone','')+'"]').val()) {
form.find(zone.replace('zone','key')).show();
form.find(zone.replace('div','i')).removeClass('key-off').addClass('key-off');
}
} else {
// basic view
$(icon).removeClass('fa-chevron-up').addClass('fa-chevron-down');
if (form.find('input[name="PublicKey:'+zone.replace('div.zone','')+'"]').val()) {
form.find(zone.replace('div','input')).hide('slow');
form.find(zone.replace('zone','key')).hide();
form.find(zone.replace('div','i')).removeClass('key-off');
}
}
} else {
if (!form.find(zone.replace('div.key','i.zone')).hasClass('key-off')) form.find(zone).toggle('slow');
}
}
function blockToggle(vtun) {
$('div#block-'+vtun).toggle('slow');
if ($('i#block-'+vtun).hasClass('fa-chevron-up')) {
$('i#block-'+vtun).removeClass('fa-chevron-up').addClass('fa-chevron-down');
$.cookie('block-'+vtun,'hide',{expires:3650});
} else {
$('i#block-'+vtun).removeClass('fa-chevron-down').addClass('fa-chevron-up');
$.removeCookie('block-'+vtun);
}
}
function importFile(file) {
var reader = new FileReader();
reader.readAsText(file,'UTF-8');
reader.onload = function(e){$.post('/webGui/include/update.wireguard.php',{'#cmd':'import','#name':'<?=$tower?>','#data':e.target.result},function(vtun){clearTunnel(vtun);});};
}
function isPublic(ipv4) {
var rfc1918 = ['10.0.0.0/8','172.16.0.0/12','192.168.0.0/16'];
for (var i=0,rfc; rfc=rfc1918[i]; i++) {
var subnet = rfc.split('/');
if (subnet[0].ip2long() == ipv4.ip2long() & (0x100000000-2**(32-subnet[1]))) return false;
}
return true;
}
function getPublicIP(ip,wg,protocol) {
$.post('/webGui/include/update.wireguard.php',{'#cmd':'public','#ip':ip,'#prot':protocol},function(x){
var endpoints = [<?=concat($endpoints)?>];
var ips = x.split(';');
var wgx_ipv4 = ips[0];
var ext_ipv4 = ips[1];
var wgx_ipv6 = ips[2];
var ext_ipv6 = ips[3];
if ((protocol==''||protocol=='46') && ext_ipv4 && ip) {
if (!wgx_ipv4) {
if (!wgx_ipv6) $('#endpoint4-'+wg).html("<dt>&nbsp;</dt><dd></span> <span class='inline-block'>_(Remark)_: _(The Local endpoint does not resolve to an IPv4 address)_. _(In most cases, this should be your public WAN IPv4)_: <b>"+ext_ipv4+"</b></dd>\n").show();
} else if (wgx_ipv4 != ext_ipv4) {
$('#endpoint4-'+wg).html("<dt>&nbsp;</dt><dd></span> <span class='inline-block'>_(Remark)_: _(The Local endpoint resolves to)_ <b>"+wgx_ipv4+".</b> _(In most cases, this should be your public WAN IPv4 instead)_: <b>"+ext_ipv4+"</b></dd>\n").show();
} else if (endpoints.includes(wgx_ipv4) && isPublic(wgx_ipv4)) {
$('#endpoint4-'+wg).html("<dt>&nbsp;</dt><dd></span> <span class='inline-block'>_(Remark)_: _(The Local endpoint resolves to)_ <b>"+wgx_ipv4+".</b> _(Your Unraid Server is reachable from the internet)_</dd>\n").show();
}
}
if ((protocol=='6'||protocol=='46') && ext_ipv6 && ip) {
if (!wgx_ipv6) {
if (!wgx_ipv4) $('#endpoint6-'+wg).html("<dt>&nbsp;</dt><dd></span> <span class='inline-block'>_(Remark)_: _(The Local endpoint does not resolve to an IPv6 address)_. _(In most cases, this should be your public WAN IPv6)_: <b>"+ext_ipv6+"</b></dd>\n").show();
} else if (wgx_ipv6 != ext_ipv6) {
$('#endpoint6-'+wg).html("<dt>&nbsp;</dt><dd></span> <span class='inline-block'>_(Remark)_: _(The Local endpoint resolves to)_ <b>"+wgx_ipv6+".</b> _(In most cases, this should be your public WAN IPv6 instead)_: <b>"+ext_ipv6+"</b></dd>\n").show();
} else if (endpoints.includes(wgx_ipv6)) {
$('#endpoint6-'+wg).html("<dt>&nbsp;</dt><dd></span> <span class='inline-block'>_(Remark)_: _(The Local endpoint resolves to)_ <b>"+wgx_ipv6+".</b> _(Your Unraid Server is reachable from the internet)_</dd>\n").show();
}
}
<?if (!$public):?>
$('input[name="#internet"]').val(ext_ipv4||ext_ipv6);
$('input[name="Endpoint:0"]').each(function(){if ($(this).attr('placeholder')=='') $(this).attr('placeholder',ext_ipv4||ext_ipv6);});
<?endif;?>
});
}
// keep tunnel states in global variable
var tstate = [];
tstate['wg0'] = "<?=$check_wg0 ? 'active' : 'passive'?>";
var statistics = new NchanSubscriber('/sub/wireguard',{subscriber:'websocket'});
statistics.on('message', function(data) {
var list = [], n = [];
var x = 0; var vtun = '';
// get all existing tunnels
$('div[id^="block-wg"]').each(function(){list.push($(this).prop('id').split('-')[1]);});
// update active tunnels
var rows = JSON.parse(data);
for (var i=0,info; info=rows[i]; i++) {
if (info[0] != vtun) {
vtun = info[0];
// remove tunnel from inactive list
for (var n=0,done; done=list[n]; n++) if (vtun==done) list.splice(n,1);
x = 1;
} else x++;
if (info[1] > 0) {
var hr = parseInt(info[1]/3600);
var mn = parseInt(info[1]/60%60);
var sc = parseInt(info[1]%60);
$('.hs-'+vtun+'-'+x).text(sprintf("_(%s ago)_",plus(hr,"_(hour)_","_(hours)_",false)+plus(mn,"_(minute)_","_(minutes)_",false)+plus(sc,"_(second)_","_(seconds)_",true)));
} else {
$('.hs-'+vtun+'-'+x).text("_(not received)_");
}
$('.rx-'+vtun+'-'+x).text(info[2]);
$('.tx-'+vtun+'-'+x).text(info[3]);
}
// update inactive tunnels
for (var i=0,vtun; vtun=list[i]; i++) {
$('span[class^="hs-'+vtun+'"]').text("_(tunnel is inactive)_");
$('span[class^="tx-'+vtun+'"]').text('---');
$('span[class^="rx-'+vtun+'"]').text('---');
}
});
$(function(){
var form = $(document.wg0);
var ctrl = "<span class='js-wg-ctrl inline-flex flex-row items-center gap-3'>";
ctrl += "<span class='inline-flex flex-row items-center gap-2'><span class='label'>_(Autostart)_</span><span class=''><input type='checkbox' id='start-wg0' style='display:none'<?=$start_wg0?' checked':''?>></span></span>";
ctrl += "<span class='inline-flex flex-row items-center gap-2'><span class=''><input type='checkbox' id='view-wg0' style='display:none'></span></span>";
ctrl += "<span class='inline-flex flex-row items-center gap-2'><span class=''><input type='checkbox' id='toggle-wg0' style='display:none'></span></span>";
ctrl += "<span class='inline-flex flex-row items-center gap-2'><i id='block-wg0' class='fa fa-fw fa-chevron-up' style='cursor:pointer' onclick='blockToggle(&quot;wg0&quot;)'></i></span>";
ctrl += "</span>";
statistics.start();
getPublicIP($('#endpoint-wg0').val(),'wg0',$('#protocol-wg0').val());
$('div.content').prepend('<div class="preset"><small>"WireGuard" and the "WireGuard" logo are registered trademarks of Jason A. Donenfeld</small><div class="inline-flex flex-row items-center gap-2"><input type="button" value="_(Import Tunnel)_" onclick="$(&apos;input#file&apos;).trigger(&apos;click&apos;);"><input type="button" value="_(Add Tunnel)_" onclick="addTunnel()";<?=$this_wg0?"":" disabled"?>></div></div>');
$('div.title').eq(0).find('.right').append(ctrl);
$.post('/webGui/include/update.wireguard.php',{'#cmd':'upnp','#gw':"<?=$$ethX['GATEWAY:0']?>",'#link':"<?=$link?>"},function(url){
if (url) {
$('div.upnp').show();
$.cookie('upnp',url,{expires:3650});
var upnp = 'ON';
} else {
$('div.upnp').hide();
$.removeCookie('upnp');
var upnp = 'OFF';
}
$('span.upnp').each(function(){
var vtun = $(this).attr('class').split(' ')[1];
$.post('/webGui/include/update.wireguard.php',{'#cmd':'upnpc','#xml':xml,'#vtun':vtun,'#link':'<?=$link?>','#ip':'<?=$server?>','#wg':tstate[vtun]},function(data){$('span.upnp.'+vtun).text(data);});
});
if (xml.desc() != url.desc()) {
xml = url;
swal({title:"_(UPnP state changed to)_ "+upnp,text:"_(Please consider re-applying all configurations)_<br>(_(enable advanced settings view)_)",type:'warning',animation:'none',html:true});
}
});
$('#start-wg0').switchButton({labels_placement:'left',off_label:"_(Off)_",on_label:"_(On)_",clear:false});
$('#start-wg0').change(function(){
var start = $('#start-wg0').is(':checked') ? 'on' : 'off';
$.post('/webGui/include/update.wireguard.php',{'#cmd':'autostart','#start':start,'#vtun':'wg0'});
});
$('#toggle-wg0').switchButton({labels_placement:'left',off_label:"_(Inactive)_",on_label:"_(Active)_",checked:<?=$check_wg0?>});
$('#toggle-wg0').change(function(e){
<?if ($this_wg0):?>
e.preventDefault();
e.stopPropagation();
var wg = $('#toggle-wg0').is(':checked') ? 'start' : 'stop';
var type = 0;
$('div[id^="index-wg0-"]').each(function(){
var temp = $(this).find('select[name^="TYPE:"]').val();
if (temp >= 7 && type==0) type = temp;
});
$.post('/webGui/include/update.wireguard.php',{'#cmd':'toggle','#wg':wg,'#vtun':'wg0','#type':type},function(ok){
if (wg=='start') {
if (ok==0) tstate['wg0']='active'; else $('#toggle-wg0').switchButton({checked:false});
} else {
if (ok==0) tstate['wg0']=''; else $('#toggle-wg0').switchButton({checked:true});
}
<?if (empty($wg0['UPNP:0'])):?>
$.post('/webGui/include/update.wireguard.php',{'#cmd':'upnpc','#xml':xml,'#vtun':'wg0','#link':'<?=$link?>','#ip':'<?=$server?>','#wg':tstate['wg0']},function(data){$('span.upnp.wg0').text(data);});
<?endif;?>
});
<?else:?>
$('#toggle-wg0').prop('checked',false).trigger('change');
<?endif;?>
});
if ($.cookie('view-wg0')=='advanced') {
$('.advanced.wg0').show();
form.find('i.fa-chevron-down').removeClass('fa-chevron-down').addClass('fa-chevron-up');
form.find('input[class*="zone"]').show();
form.find('i[class*="zone"]').each(function(i){if (!form.find('input[name="PublicKey:'+i+'"]').val()) {
$('div.wg0.key'+i).show();
$(this).removeClass('key-off').addClass('key-off');
}});
}
$('#view-wg0').switchButton({labels_placement:'left',off_label:"_(Basic)_",on_label:"_(Advanced)_",checked:$.cookie('view-wg0')=='advanced'});
$('#view-wg0').change(function(){
if ($.cookie('view-wg0')==null) {
// advanced view
$.cookie('view-wg0','advanced',{expires:3650});
$('.advanced.wg0').show('slow');
form.find('input[class*="zone"]').show('slow');
form.find('i.fa-chevron-down').removeClass('fa-chevron-down').addClass('fa-chevron-up');
form.find('i[class*="zone"]').each(function(i){if (!form.find('input[name="PublicKey:'+i+'"]').val()) {
$('div.wg0.key'+i).show('slow');
$(this).removeClass('key-off').addClass('key-off');
}});
} else {
// basic view
$.removeCookie('view-wg0');
$('.advanced.wg0').hide('slow');
form.find('i.fa-chevron-up').removeClass('fa-chevron-up').addClass('fa-chevron-down');
form.find('i[class*="zone"]').each(function(i){if (form.find('input[name="PublicKey:'+i+'"]').val()) {
form.find('input[class$="zone'+i+'"]').hide('slow');
$('div.wg0.key'+i).hide('slow');
$(this).removeClass('key-off');
}});
}
});
showRemark(form);
setProtocol(form,'wg0');
form.find('input[name^="Address:"]').each(function(){
var i = $(this).attr('name').split(':')[1];
if (i > 0) $(this).on('input change',function(){form.find('.ping-button1-'+i).prop('disabled',$(this).val()=='');});
});
form.find('input[name^="Address6:"]').each(function(){
var i = $(this).attr('name').split(':')[1];
if (i > 0) $(this).on('input change',function(){form.find('.ping-button6-'+i).prop('disabled',$(this).val()=='');});
});
form.find('input[name^="gui:Endpoint:"]').each(function(){
var i = $(this).attr('name').split(':')[2];
$(this).on('input change',function(){
form.find('.ping-button2-'+i).prop('disabled',$(this).val()=='');
});
});
if (!$.cookie('block-wg0')) $('div#block-wg0').show(); else $('i#block-wg0').removeClass('fa-chevron-up').addClass('fa-chevron-down');
if (xml) {
$('div.upnp.wg0').show();
<?if (empty($wg0['UPNP:0'])):?>
$.post('/webGui/include/update.wireguard.php',{'#cmd':'upnpc','#xml':xml,'#vtun':'wg0','#link':'<?=$link?>','#ip':'<?=$server?>','#wg':$.cookie('my-wg0')||''},function(data){$('span.upnp.wg0').text(data);});
<?endif;?>
} else {
form.find('select[name="UPNP:0"]').val('no');
}
var vpn = 0, lan = false
form.find('select[name^="TYPE:"]').each(function(){
var i = $(this).attr('name').split(':')[1];
if ($(this).val()>=7 && vpn==0) vpn = $(this).val();
lan |= $(this).val()=='6' || $(this).val() % 2;
form.find('input[name="DNS:'+i+'"]').attr('placeholder',$(this).val()>=7 ? "(_(not used)_)" : "(_(optional)_)");
showAccess(form,i,$(this).val());
});
if (vpn > 0) {
form.find('select[name="NAT:0"]').val('no').prop('disabled',true);
form.find('#addpeer-wg0').prop('disabled',true);
}
else if (form.find('select[name="NAT:0"]').val()=='no' && lan) $('span#my-static1-wg0').show();
else if (lan) $('span#my-static2-wg0').show();
<?if (file_exists($tmp)):?>
setTimeout(function(){swal({title:"_(Peer update required)_",text:"<b><u>_(List of peers)_</u></b><br><span style='display:inline-block;text-align:left'><?=file_get_contents($tmp)?></span>",type:'warning',animation:'none',html:true});},200);
<?delete_file($tmp);?>
<?endif;?>
});
</script>
<input type='file' id='file' accept='.conf' onchange='importFile(this.files[0])' style='display:none'>
<div markdown="1" id="block-wg0" style="display:none">
<form markdown="1" name="wg0" autocomplete="off" method="POST" action="/update.php" target="progressFrame" onsubmit="prepareSettings($(this),'wg0')">
<input type="hidden" name="#include" value="/webGui/include/update.wireguard.php">
<input type="hidden" name="#file" value="<?=$conf_wg0?>">
<input type="hidden" name="#cfg" value="<?=$cfg_wg0?>">
<input type="hidden" name="#cmd" value="update">
<input type="hidden" name="#name" value="<?=$tower?>">
<input type="hidden" name="#vtun" value="wg0">
<input type="hidden" name="#type" value="0">
<input type="hidden" name="#wg" value="">
<input type="hidden" name="#internet" value="<?=$public?>">
<input type="hidden" name="#subnets1" value="">
<input type="hidden" name="#subnets2" value="">
<input type="hidden" name="#shared1" value="">
<input type="hidden" name="#shared2" value="">
<input type="hidden" name="#deleted" value="">
<input type="hidden" name="#locale" value="<?=$locale?>">
_(Local name)_:
: <span class="flex flex-row items-center gap-2">
<input type="text" name="Name:0" class="wide" maxlength="99" value="<?=_var($wg0,"Name:0")?>" pattern="<?=$validname?>" title="_(Use only letters A-Z, digits or space,dash,underscore)_" placeholder="(_(optional)_)">
<span class="flex flex-row items-center gap-2">
<i class="fa fa-fw fa-eye eye0<?=$this_wg0?'':' key-off'?>" style="cursor:pointer" onclick="WGconfig(this,'wg0','')" title="_(View Local Config)_"></i>
<i class="fa fa-fw fa-key zone0<?=isset($wg0['PublicKey:0'])?'':' key-off'?>" style="cursor:pointer" onclick="openClose($(document.wg0),null,'div.key0')" title="_(Toggle keys)_"></i>
<i id="chevron-wg0-0" class="fa fa-fw fa-chevron-down" style="cursor:pointer" onclick="openClose($(document.wg0),this,'div.zone0')" title="_(Toggle view)_"></i>
</span>
</span>
:wg_local_name_help:
<div markdown="1" class="keys wg0 key0" style="<?= isset($wg0['PublicKey:0']) ? 'display:none' : '' ?>">
_(Local private key)_:
: <input type="text" name="PrivateKey:0" class="wide private-0" maxlength="64" value="<?=_var($wg0,'PrivateKey:0')?>" onchange="highlight($(document.wg0),this,0)" placeholder="(_(mandatory)_)" required>
<span class="inline-block">
<input type="button" class="form" value="_(Generate Keypair)_" onclick="keypair($(document.wg0),'0')">
</span>
:wg_generate_keypair_help:
_(Local public key)_:
: <input type="text" name="PublicKey:0" class="wide public-0" maxlength="64" value="<?=_var($wg0,'PublicKey:0')?>" onchange="highlight($(document.wg0),this,0)" placeholder="(_(mandatory)_)" required>
<span class="inline-block">
<input type="button" class="form" value="_(Generate Keypair)_" onclick="keypair($(document.wg0),'0')">
</span>
:wg_generate_keypair_help:
</div>
<div markdown="1" class="advanced wg0 zone0" style="display:none">
_(Network protocol)_:
: <span class="input"><select id="protocol-wg0" name="PROT:0" onchange="setProtocol($(document.wg0),'wg0',true)">
<?=mk_option(_var($wg0,'PROT:0'), "", "_(IPv4 only)_")?>
<?=mk_option(_var($wg0,'PROT:0'), "6", "_(IPv6 only)_")?>
<?=mk_option(_var($wg0,'PROT:0'), "46", "_(IPv4 + IPv6)_")?>
</select></span>
<div markdown="1" class="ipv4 wg0" style="display:none">
_(Local tunnel network pool)_:
: <input type="hidden" name="Network:0" value="">
<span class="flex flex-row items-center gap-2">
<input type="text" name="gui:Network:0" maxlength="15" value="<?=explode('/',_var($wg0,'Network:0'))[0]?>" onchange="setPool($(document.wg0),'wg0')" pattern="<?=$validIP4?>" title="_(IPv4 network)_" placeholder="<?=_var($netpool,'wg0')?>">
<span>/</span>
<input type="number" name="gui:Mask:0" class="mask" min="1" max="32" value="<?=my_explode('/',_var($wg0,'Network:0'))[1]?>" onchange="if(quickValidate(this)) {setPool($(document.wg0),'wg0')}" placeholder="24">
</span>
</div>
<div markdown="1" class="ipv6 wg0" style="display:none">
_(Local tunnel network pool IPv6)_:
: <input type="hidden" name="Network6:0" value="">
<span class="flex flex-row items-center gap-2">
<input type="text" name="gui:Network6:0" maxlength="40" value="<?=explode('/',_var($wg0,'Network6:0'))[0]?>" onchange="setPool6($(document.wg0),'wg0')" pattern="<?=$validIP6?>" title="_(IPv6 network)_" placeholder="<?=_var($netpool6,'wg0')?>">
<span>/</span>
<input type="number" name="gui:Mask6:0" class="mask" min="1" max="128" value="<?=my_explode('/',_var($wg0,'Network6:0'))[1]?>" onchange="if(quickValidate(this)) {setPool6($(document.wg0),'wg0')}" placeholder="64">
</span>
</div>
:wg_local_tunnel_network_pool_help:
<div markdown="1" class="ipv4 wg0" style="display:none">
_(Local tunnel address)_:
: <input type="text" name="Address:0" maxlength="15" value="<?=_var($wg0,'Address:0')?:long2ip(_var($netbase,'wg0')+1)?>" onchange="verifyInSubnet(this)" pattern="<?=$validIP4?>" title="_(IPv4 address)_">
:wg_local_tunnel_address_help:
</div>
<div markdown="1" class="ipv6 wg0" style="display:none">
_(Local tunnel address IPv6)_:
: <input type="text" name="Address6:0" maxlength="40" value="<?=_var($wg0,'Address6:0')?:(_var($netbase6,'wg0').'1')?>" onchange="verifyInSubnet6(this)" pattern="<?=$validIP6?>" title="_(IPv6 address)_">
:wg_local_tunnel_address_help:
</div>
</div>
_(Local endpoint)_:
: <span class="flex flex-row items-center gap-2">
<input type="text" class="width:10%;" id="endpoint-wg0" name="Endpoint:0" value="<?=$vpn_wg0?'':_var($wg0,'Endpoint:0')?>" onchange="toLC(this);quickValidate(this);" pattern="<?=$validText?>" title="_(IP address or FQDN)_" placeholder="<?=$vpn_wg0?'(_(not used)_)':preg_replace('/^(.+?\.)[0-9a-zA-Z]+(\.(my)?unraid.net)$/','$1<hash>$2',$public)?>">
<span>:</span>
<input type="number" name="gui:ListenPort:0" class="port" min="1" max="65535" value="<?=$vpn_wg0?'':_var($wg0,'ListenPort:0')?>" onchange="if(quickValidate(this)) {portRemark($(document.wg0),'wg0',this.value)}" placeholder="<?=$vpn_wg0?'':_var($netport,'wg0')?>">
</span>
<span class="remark inline-block" style="display:none">_(Remark)_: _(configure your router with port forwarding of port)_ **<span id="my-port-wg0"><?=_var($wg0,'ListenPort:0')?:_var($netport,'wg0')?></span>/_(UDP)_** _(to)_ **<?=$server?>:<?=_var($wg0,'ListenPort:0')?:_var($netport,'wg0')?>**</span><span class="upnp wg0 inline-block"></span>
<input type="hidden" name="ListenPort:0" value="">
<dl id="endpoint4-wg0" style="display:none"></dl>
<dl id="endpoint6-wg0" style="display:none"></dl>
:wg_local_endpoint_help:
<div markdown="1" class="advanced wg0 zone0" style="display:none">
_(Local server uses NAT)_:
: <select name="NAT:0" onchange="showRoute($(document.wg0),'wg0',0)">
<?=mk_option(_var($wg0,'NAT:0'), "", _("Yes"))?>
<?=mk_option(_var($wg0,'NAT:0'), "no", _("No"))?>
</select>
<span id="my-static1-wg0" class="inline-block" style="display:none">_(Remark)_: _(configure your router with a static route of)_ **<?=_var($wg0,'Network:0')?>** _(to)_ **<?=$server?>**</span>
<span id="my-static2-wg0" class="inline-block" style="display:none">_(Remark)_: _(docker containers on custom networks need static routing)_ **<?=_var($wg0,'Network:0')?>** _(to)_ **<?=$server?>**</span>
:wg_local_server_uses_nat_help:
<div markdown="1" class="upnp wg0" style="display:none">
_(Local gateway uses UPnP)_:
: <select name="UPNP:0" onchange="showRemark($(document.wg0))">
<?=mk_option(_var($wg0,'UPNP:0'), "", _("Yes"))?>
<?=mk_option(_var($wg0,'UPNP:0'), "no", _("No"))?>
</select>
:wg_local_gateway_uses_upnp_help:
</div>
_(Local tunnel firewall)_:
: <span class="flex flex-row items-center gap-2">
<input type="text" name="DROP:0" class="wide" value="<?=_var($wg0,'DROP:0')?>" onchange="quickValidate(this);" pattern="<?=$validList?>" title="_(Comma separated list of IPv4 and IPv6 IP addresses)_, _(CIDR optional)_" placeholder="(_(optional)_)">
<span class="flex flex-row items-center gap-2">
<span>_(Rule)_: </span>
<select name="RULE:0" class="auto">
<?=mk_option(_var($wg0,'RULE:0'), "", _("Deny"))?>
<?=mk_option(_var($wg0,'RULE:0'), "1", _("Allow"))?>
</select>
</span>
</span>
:wg_local_tunnel_firewall_help:
_(MTU size)_:
: <span class="flex flex-row items-center gap-2">
<input type="number" name="MTU:0" class="trim" min="68" max="9198" value="<?=_var($wg0,'MTU:0')?>" onchange="quickValidate(this);" placeholder="(_(auto)_)">
<span>_(bytes)_</span>
</span>
:wg_mtu_size_help:
<!--
_(DNS servers)_:
: <span class="input"><input type="text" name="DNS:0" class="wide" value="" placeholder="(_(optional)_)"></span>
> Not used at the moment. It gives errors when used together with Unraid.
-->
</div>
<input type="hidden" name="PostUp:0:0" value="<?=$postUp0?>">
<input type="hidden" name="PostUp:0:1" value="<?=$postUp1?>">
<input type="hidden" name="PostUp:0:2" value="">
<input type="hidden" name="PostDown:0:0" value="<?=$postDown0?>">
<input type="hidden" name="PostDown:0:1" value="<?=$postDown1?>">
<input type="hidden" name="PostDown:0:2" value="">
:wg_peer_configuration_help:
<div id="peers-list-wg0" style="display:none"></div>
<?foreach ($peer_wg0 as $i):?>
<div markdown="1" id="index-wg0-<?=$i?>" class="shade">
_(Peer name)_:
: <span class="flex flex-row items-center gap-2">
<input type="text" name="Name:<?=$i?>" class="wide" maxlength="99" value="<?=_var($wg0,"Name:$i")?>" onchange="quickValidate(this);" pattern="<?=$validname?>" title="_(Use only letters A-Z, digits or space,dash,underscore)_" placeholder="(_(optional)_)">
<span class="flex flex-row items-center gap-2">
<i class="fa fa-fw fa-eye eye<?=$i?><?=(file_exists("$etc/peers/peer-$tower-wg0-$i.conf")&&isset($wg0["TYPE:$i"])&&$wg0["TYPE:$i"]<=6)?'':' key-off'?>" style="cursor:pointer" onclick="WGconfig(this,'peer-<?=$tower?>-wg0-<?=$i?>','/peers')" title="_(View Peer Config)_"></i>
<i class="fa fa-fw fa-key zone<?=$i?><?=isset($wg0["PublicKey:$i"])?'':' key-off'?>" style="cursor:pointer" onclick="openClose($(document.wg0),null,'div.key<?=$i?>')" title="_(Toggle keys)_"></i>
<i id="chevron-wg0-<?=$i?>" class="fa fa-fw fa-chevron-down" style="cursor:pointer" onclick="openClose($(document.wg0),this,'div.zone<?=$i?>')" title="_(Toggle view)_"></i>
</span>
</span>
<span class="inline-block">
<input type="button" class="form" value="_(Delete Peer)_" onclick="delPeer($(document.wg0),'#index-wg0-<?=$i?>')">
</span>
:wg_peer_name_help:
_(Peer type of access)_:
: <select name="TYPE:<?=$i?>" class="auto" onchange="updatePeer($(document.wg0),<?=$i?>,this.value,'wg0')">
<?=mk_option(_var($wg0,"TYPE:$i"), "0", _("Remote access to server"))?>
<?=mk_option(_var($wg0,"TYPE:$i"), "1", _("Remote access to LAN"))?>
<?=mk_option(_var($wg0,"TYPE:$i"), "2", _("Server to server access"))?>
<?=mk_option(_var($wg0,"TYPE:$i"), "3", _("LAN to LAN access"))?>
<?=mk_option(_var($wg0,"TYPE:$i"), "4", _("Server hub & spoke access"))?>
<?=mk_option(_var($wg0,"TYPE:$i"), "5", _("LAN hub & spoke access"))?>
<?=mk_option(_var($wg0,"TYPE:$i"), "6", _("Remote tunneled access"))?>
<?=mk_option(_var($wg0,"TYPE:$i"), "7", _("VPN tunneled access for system"),count($peer_wg0)==1?'':'disabled')?>
<?=mk_option(_var($wg0,"TYPE:$i"), "8", _("VPN tunneled access for docker"),count($peer_wg0)==1?'':'disabled')?>
</select>
<span id="access-type-<?=$i?>"</span>
<?if ($i==1):?>
> ![](<?=autov('/webGui/images/wireguard-help.png')?>)
<?endif;?>
<div markdown="1" class="keys wg0 key<?=$i?>"<?=isset($wg0["PublicKey:$i"])?' style="display:none">':'>'?>
_(Peer private key)_:
: <input type="text" name="PrivateKey:<?=$i?>" class="wide private-<?=$i?>" maxlength="64" value="<?=_var($wg0,"PrivateKey:$i")?>" onchange="highlight($(document.wg0),this,<?=$i?>)" placeholder="(_(optional)_)">
<span class="inline-block">
<input type="button" class="form" value="_(Generate Keypair)_" onclick="keypair($(document.wg0),'<?=$i?>')">
</span>
:wg_generate_keypair_help:
_(Peer public key)_:
: <input type="text" name="PublicKey:<?=$i?>" class="wide public-<?=$i?>" maxlength="64" value="<?=_var($wg0,"PublicKey:$i")?>" onchange="highlight($(document.wg0),this,<?=$i?>)" placeholder="(_(mandatory)_)" required>
:wg_generate_keypair_help:
_(Peer preshared key)_:
: <input type="text" name="PresharedKey:<?=$i?>" class="wide preshared-<?=$i?>" maxlength="64" value="<?=_var($wg0,"PresharedKey:$i")?>" onchange="highlight($(document.wg0),this,<?=$i?>)" placeholder="(_(optional)_)">
<span class="inline-block">
<input type="button" class="form" value="_(Generate Key)_" onclick="presharedkey($(document.wg0),'<?=$i?>')">
</span>
:wg_peer_preshared_key_help:
</div>
<div markdown="1" class="advanced wg0 zone<?=$i?>" style="display:none">
<div markdown="1" class="ipv4 wg0" style="display:none">
_(Peer tunnel address)_:
: <input type="text" name="Address:<?=$i?>" maxlength="15" value="<?=_var($wg0,"Address:$i")?>" onchange="if(verifyInSubnet(this)){setAllow($(document.wg0),this.value,<?=$i?>)}" pattern="<?=$validIP4?>" title="_(IPv4 address)_">
<span class="inline-block">
<input type="button" class="form ping-button1-<?=$i?>" value="_(Ping)_" onclick="ping($(document.wg0),this,'input[name=&quot;Address:<?=$i?>&quot;]')"<?=isset($wg0["Address:$i"])?'':' disabled'?>>
</span>
</div>
<div markdown="1" class="ipv6 wg0" style="display:none">
_(Peer tunnel address IPv6)_:
: <input type="text" name="Address6:<?=$i?>" maxlength="40" value="<?=_var($wg0,"Address6:$i")?>" onchange="if(verifyInSubnet6(this)){setAllow6($(document.wg0),this.value,<?=$i?>)}" pattern="<?=$validIP6?>" title="_(IPv6 address)_">
<span class="inline-block">
<input type="button" class="form ping-button6-<?=$i?>" value="_(Ping)_" onclick="ping($(document.wg0),this,'input[name=&quot;Address6:<?=$i?>&quot;]')"<?=isset($wg0["Address6:$i"])?'':' disabled'?>>
</span>
</div>
:wg_peer_tunnel_address_help:
_(Peer endpoint)_:
: <input type="hidden" name="Endpoint:<?=$i?>" value="">
<span class="flex flex-row items-center gap-2">
<input type="text" name="gui:Endpoint:<?=$i?>" value="<?=_var($wg0,"Endpoint:$i")?>" pattern="<?=$validText?>" title="_(IP address or FQDN)_" onchange="toLC(this);quickValidate(this);" <?=($vpn_wg0||(int)_var($wg0,"TYPE:$i",0)==2||(int)_var($wg0,"TYPE:$i",0)==3)?'placeholder="(_(mandatory)_)" required':'placeholder="(_(not used)_)"'?>>
<span>:</span>
<input type="number" name="gui:ListenPort:<?=$i?>" class="port" min="1" max="65535" value="<?=_var($wg0,"ListenPort:$i")?>" onchange="quickValidate(this);"<?=isset($wg0["Endpoint:$i"])?" placeholder=\"".(_var($wg0,'ListenPort:0')?:_var($netport,'wg0'))."\"":""?>>
</span>
<span class="inline-block">
<input type="button" class="form ping-button2-<?=$i?>" value="_(Ping)_" onclick="ping($(document.wg0),this,'input[name=&quot;gui:Endpoint:<?=$i?>&quot;]')"<?=isset($wg0["Endpoint:$i"])?'':' disabled'?>>
</span>
:wg_peer_endpoint_help:
_(Peer allowed IPs)_:
: <input type="text" name="AllowedIPs:<?=$i?>" class="wide" value="<?=_var($wg0,"AllowedIPs:$i")?>" onchange="quickValidate(this);" pattern="<?=$validList?>" title="_(Comma separated list of IPv4 and IPv6 IP addresses)_, _(CIDR optional)_" placeholder="(_(mandatory)_)" required>
:wg_peer_allowed_ips_help:
_(Peer DNS server)_:
: <input type="text" name="DNS:<?=$i?>" maxlength="60" value="<?=_var($wg0,"DNS:$i")?>" onchange="quickValidate(this);" pattern="<?=$validDNSServerList?>" title="_(Comma separated list of IPv4 and IPv6 IP addresses)_" <?=(int)_var($wg0,"TYPE:$i",0)!=6?'placeholder="(_(optional)_)"':'placeholder="(_(mandatory)_)" required'?>>
:wg_peer_dns_server_help:
_(Persistent keepalive)_:
: <span class="flex flex-row items-center gap-2">
<input type="number" name="PersistentKeepalive:<?=$i?>" class="trim" min="0" max="600" value="<?=_var($wg0,"PersistentKeepalive:$i")?>" onchange="quickValidate(this);" placeholder="(_(disabled)_)">
<span>_(seconds)_</span>
</span>
:wg_persistent_keepalive_help:
</div>
_(Data received)_:
: <span class="rx-wg0-<?=$i?>">0 B</span>
_(Data sent)_:
: <span class="tx-wg0-<?=$i?>">0 B</span>
_(Last handshake)_:
: <span class="hs-wg0-<?=$i?>">_(unknown)_</span>
</div>
<?endforeach;?>
&nbsp;
: <span class="inline-block">
<input type="submit" value="_(Apply)_" onclick="return(validateForm($(document.wg0),'wg0'))" disabled>
<input type="button" value="_(Done)_" onclick="done()">
<input type="button" id="addpeer-wg0" value="_(Add Peer)_" onclick="addPeer($(document.wg0),'wg0')">
<input type="button" class="advanced wg0" value="_(Delete Tunnel)_" style="display:none" onclick="delTunnel('wg0')"<?=file_exists($conf_wg0)?'':' disabled'?>>
</span>
</form>
<script markdown="1" type="text/html" id="peer-template-wg0">
<div markdown="1" id="index-wg0-INDEX" class="shade">
_(Peer name)_:
: <span class="flex flex-row items-center gap-2">
<input type="text" name="Name:INDEX" class="wide" maxlength="99" value="" onchange="quickValidate(this);" pattern="<?=$validname?>" title="_(Use only letters A-Z, digits or space,dash,underscore)_" placeholder="(_(optional)_)">
<span class="flex flex-row items-center gap-2">
<i class="fa fa-fw fa-eye eyeINDEX key-off" title="_(Show Peer Config)_"></i>
<i class="fa fa-fw fa-key zoneINDEX key-off" title="_(Toggle keys)_"></i>
<i id="chevron-wg0-INDEX" class="fa fa-fw fa-chevron-down" style="cursor:pointer" onclick="openClose($(document.wg0),this,'div.zoneINDEX')" title="_(Toggle view)_"></i>
</span>
</span>
:wg_peer_name_help:
_(Peer type of access)_:
: <select name="TYPE:INDEX" class="auto" onchange="updatePeer($(document.wg0),INDEX,this.value,'wg0')">
<?=mk_option(0, "0", _("Remote access to server"))?>
<?=mk_option(0, "1", _("Remote access to LAN"))?>
<?=mk_option(0, "2", _("Server to server access"))?>
<?=mk_option(0, "3", _("LAN to LAN access"))?>
<?=mk_option(0, "4", _("Server hub & spoke access"))?>
<?=mk_option(0, "5", _("LAN hub & spoke access"))?>
<?=mk_option(0, "6", _("Remote tunneled access"))?>
<?=mk_option(0, "7", _("VPN tunneled access for system"),'disabled')?>
<?=mk_option(0, "8", _("VPN tunneled access for docker"),'disabled')?>
</select>
<span id="access-type-INDEX" class="access-type"></span>
> ![](<?=autov('/webGui/images/wireguard-help.png')?>)
<div markdown="1" class="keys wg0 keyINDEX">
_(Peer private key)_:
: <input type="text" name="PrivateKey:INDEX" class="wide private-INDEX" maxlength="64" value="" onchange="highlight($(document.wg0),this,0)" placeholder="(_(optional)_)">
<span class="inline-block">
<input type="button" class="form" value="_(Generate Keypair)_" onclick="keypair($(document.wg0),'INDEX')">
</span>
:wg_generate_keypair_help:
_(Peer public key)_:
: <input type="text" name="PublicKey:INDEX" class="wide public-INDEX" maxlength="64" onchange="highlight($(document.wg0),this,0)" placeholder="(_(mandatory)_)" value="" required>
:wg_generate_keypair_help:
_(Peer preshared key)_:
: <input type="text" name="PresharedKey:INDEX" class="wide preshared-INDEX" maxlength="64" value="" onchange="highlight($(document.wg0),this,0)" placeholder="(_(optional)_)">
<span class="inline-block">
<input type="button" class="form" value="_(Generate Key)_" onclick="presharedkey($(document.wg0),'INDEX')">
</span>
:wg_peer_preshared_key_help:
</div>
<div markdown="1" class="advanced wg0 zoneINDEX" style="display:none">
<div markdown="1" class="ipv4 wg0" style="display:none">
_(Peer tunnel address)_:
: <input type="text" name="Address:INDEX" maxlength="15" value="" onchange="if(verifyInSubnet(this)){setAllow($(document.wg0),this.value,INDEX)}" pattern="<?=$validIP4?>" title="_(IPv4 address)_">
<span class="inline-block">
<input type="button" class="form ping-button1-INDEX" value="_(Ping)_" onclick="ping($(document.wg0),this,'input[name=&quot;Address:INDEX&quot;]')" disabled>
</span>
</div>
<div markdown="1" class="ipv6 wg0" style="display:none">
_(Peer tunnel address IPv6)_:
: <input type="text" name="Address6:INDEX" maxlength="40" value="" onchange="if(verifyInSubnet6(this)){setAllow6($(document.wg0),this.value,INDEX)}" pattern="<?=$validIP6?>" title="_(IPv6 address)_">
<span class="inline-block">
<input type="button" class="form ping-button6-INDEX" value="_(Ping)_" onclick="ping($(document.wg0),this,'input[name=&quot;Address6:INDEX&quot;]')" disabled>
</span>
</div>
:wg_peer_tunnel_address_help:
_(Peer endpoint)_:
: <input type="hidden" name="Endpoint:INDEX" value="">
<span class="flex flex-row items-center gap-2">
<input type="text" name="gui:Endpoint:INDEX" value="" pattern="<?=$validText?>" title="_(IP address or FQDN)_" onchange="toLC(this);quickValidate(this);" placeholder="(_(not used)_)">
<span>:</span>
<input type="number" name="gui:ListenPort:INDEX" class="port" min="1" max="65535" value="" onchange="quickValidate(this);">
</span>
<span class="inline-block">
<input type="button" class="form ping-button2-INDEX" value="_(Ping)_" onclick="ping($(document.wg0),this,'input[name=&quot;gui:Endpoint:INDEX&quot;]')" disabled>
</span>
:wg_peer_endpoint_help:
_(Peer allowed IPs)_:
: <input type="text" name="AllowedIPs:INDEX" class="wide" value="" onchange="quickValidate(this);" pattern="<?=$validList?>" title="_(Comma separated list of IPv4 and IPv6 IP addresses)_, _(CIDR optional)_" placeholder="(_(mandatory)_)" required>
:wg_peer_allowed_ips_help:
_(Peer DNS server)_:
: <input type="text" name="DNS:INDEX" maxlength="60" value="" onchange="quickValidate(this);" pattern="<?=$validDNSServerList?>" title="_(Comma separated list of IPv4 and IPv6 IP addresses)_" placeholder="(_(optional)_)">
:wg_peer_dns_server_help:
_(Persistent keepalive)_:
: <span class="flex flex-row items-center gap-2">
<input type="number" name="PersistentKeepalive:INDEX" class="trim" min="0" max="600" value="" onchange="quickValidate(this);" placeholder="(_(disabled)_)">
<span>_(seconds)_</span>
</span>
:wg_persistent_keepalive_help:
</div>
</div>
</script>
</div>