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>
