mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2025-06-28 09:16:55 +00:00
Updated how available IP is generated
This commit is contained in:
parent
f055241802
commit
6f15389411
132
src/dashboard.py
132
src/dashboard.py
@ -10,6 +10,7 @@ from json import JSONEncoder
|
|||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from icmplib import ping, traceroute
|
from icmplib import ping, traceroute
|
||||||
from flask.json.provider import DefaultJSONProvider
|
from flask.json.provider import DefaultJSONProvider
|
||||||
|
from itertools import islice
|
||||||
from Utilities import (
|
from Utilities import (
|
||||||
RegexMatch, GetRemoteEndpoint, StringToBoolean,
|
RegexMatch, GetRemoteEndpoint, StringToBoolean,
|
||||||
ValidateIPAddressesWithRange, ValidateDNSAddress,
|
ValidateIPAddressesWithRange, ValidateDNSAddress,
|
||||||
@ -1107,41 +1108,69 @@ class WireguardConfiguration:
|
|||||||
return False, str(e)
|
return False, str(e)
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
def getAvailableIP(self, all: bool = False) -> tuple[bool, list[str]] | tuple[bool, None]:
|
def getNumberOfAvailableIP(self):
|
||||||
if len(self.Address) < 0:
|
if len(self.Address) < 0:
|
||||||
return False, None
|
return False, None
|
||||||
address = self.Address.split(',')
|
existedAddress = set()
|
||||||
existedAddress = []
|
availableAddress = {}
|
||||||
availableAddress = []
|
for p in self.Peers + self.getRestrictedPeersList():
|
||||||
for p in self.Peers:
|
peerAllowedIP = p.allowed_ip.split(',')
|
||||||
if len(p.allowed_ip) > 0:
|
for pip in peerAllowedIP:
|
||||||
add = p.allowed_ip.split(',')
|
ppip = pip.strip().split('/')
|
||||||
for i in add:
|
if len(ppip) == 2:
|
||||||
a, c = i.split('/')
|
|
||||||
try:
|
try:
|
||||||
existedAddress.append(ipaddress.ip_address(a.replace(" ", "")))
|
check = ipaddress.ip_network(ppip[0])
|
||||||
except ValueError as error:
|
existedAddress.add(check)
|
||||||
|
except Exception as e:
|
||||||
print(f"[WGDashboard] Error: {self.Name} peer {p.id} have invalid ip")
|
print(f"[WGDashboard] Error: {self.Name} peer {p.id} have invalid ip")
|
||||||
for p in self.getRestrictedPeersList():
|
configurationAddresses = self.Address.split(',')
|
||||||
if len(p.allowed_ip) > 0:
|
for ca in configurationAddresses:
|
||||||
add = p.allowed_ip.split(',')
|
ca = ca.strip()
|
||||||
for i in add:
|
caSplit = ca.split('/')
|
||||||
a, c = i.split('/')
|
try:
|
||||||
existedAddress.append(ipaddress.ip_address(a.replace(" ", "")))
|
if len(caSplit) == 2:
|
||||||
for i in address:
|
network = ipaddress.ip_network(ca, False)
|
||||||
addressSplit, cidr = i.split('/')
|
existedAddress.add(ipaddress.ip_network(caSplit[0]))
|
||||||
existedAddress.append(ipaddress.ip_address(addressSplit.replace(" ", "")))
|
availableAddress[ca] = network.num_addresses
|
||||||
for i in address:
|
for p in existedAddress:
|
||||||
network = ipaddress.ip_network(i.replace(" ", ""), False)
|
if p.subnet_of(network):
|
||||||
count = 0
|
availableAddress[ca] -= 1
|
||||||
for h in network.hosts():
|
|
||||||
if h not in existedAddress:
|
# map(lambda iph : ipaddress.ip_network(iph).compressed, network.hosts())
|
||||||
availableAddress.append(ipaddress.ip_network(h).compressed)
|
|
||||||
count += 1
|
except Exception as e:
|
||||||
if not all:
|
print(e)
|
||||||
if network.version == 6 and count > 255:
|
print(f"[WGDashboard] Error: Failed to parse IP address {ca} from {self.Name}")
|
||||||
break
|
return True, availableAddress
|
||||||
|
|
||||||
|
def getAvailableIP(self, threshold = 255) -> tuple[bool, list[str]] | tuple[bool, None]:
|
||||||
|
if len(self.Address) < 0:
|
||||||
|
return False, None
|
||||||
|
existedAddress = set()
|
||||||
|
availableAddress = {}
|
||||||
|
for p in self.Peers + self.getRestrictedPeersList():
|
||||||
|
peerAllowedIP = p.allowed_ip.split(',')
|
||||||
|
for pip in peerAllowedIP:
|
||||||
|
ppip = pip.strip().split('/')
|
||||||
|
if len(ppip) == 2:
|
||||||
|
try:
|
||||||
|
check = ipaddress.ip_network(ppip[0])
|
||||||
|
existedAddress.add(pip)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[WGDashboard] Error: {self.Name} peer {p.id} have invalid ip")
|
||||||
|
configurationAddresses = self.Address.split(',')
|
||||||
|
for ca in configurationAddresses:
|
||||||
|
ca = ca.strip()
|
||||||
|
caSplit = ca.split('/')
|
||||||
|
try:
|
||||||
|
if len(caSplit) == 2:
|
||||||
|
network = ipaddress.ip_network(ca, False)
|
||||||
|
existedAddress.add(ipaddress.ip_network(caSplit[0]).compressed)
|
||||||
|
availableAddress[ca] = list(islice(filter(lambda ip : ip not in existedAddress,
|
||||||
|
map(lambda iph : ipaddress.ip_network(iph).compressed, network.hosts())), threshold))
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
print(f"[WGDashboard] Error: Failed to parse IP address {ca} from {self.Name}")
|
||||||
return True, availableAddress
|
return True, availableAddress
|
||||||
|
|
||||||
def getRealtimeTrafficUsage(self):
|
def getRealtimeTrafficUsage(self):
|
||||||
@ -2491,7 +2520,7 @@ def API_addPeers(configName):
|
|||||||
|
|
||||||
public_key: str = data.get('public_key', "")
|
public_key: str = data.get('public_key', "")
|
||||||
allowed_ips: list[str] = data.get('allowed_ips', [])
|
allowed_ips: list[str] = data.get('allowed_ips', [])
|
||||||
override_allowed_ips: bool = data.get('override_allowed_ips', False)
|
allowed_ips_validation: bool = data.get('allowed_ips_validation', True)
|
||||||
|
|
||||||
endpoint_allowed_ip: str = data.get('endpoint_allowed_ip', DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[1])
|
endpoint_allowed_ip: str = data.get('endpoint_allowed_ip', DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[1])
|
||||||
dns_addresses: str = data.get('DNS', DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1])
|
dns_addresses: str = data.get('DNS', DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1])
|
||||||
@ -2508,21 +2537,24 @@ def API_addPeers(configName):
|
|||||||
if len(endpoint_allowed_ip) == 0:
|
if len(endpoint_allowed_ip) == 0:
|
||||||
endpoint_allowed_ip = DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[1]
|
endpoint_allowed_ip = DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[1]
|
||||||
config = WireguardConfigurations.get(configName)
|
config = WireguardConfigurations.get(configName)
|
||||||
# if not bulkAdd and (len(public_key) == 0 or len(allowed_ips) == 0):
|
|
||||||
# return ResponseObject(False, "Please provide at least public_key and allowed_ips")
|
|
||||||
if not config.getStatus():
|
if not config.getStatus():
|
||||||
config.toggleConfiguration()
|
config.toggleConfiguration()
|
||||||
availableIps = config.getAvailableIP()
|
ipStatus, availableIps = config.getAvailableIP()
|
||||||
|
defaultIPSubnet = list(availableIps.keys())[0]
|
||||||
if bulkAdd:
|
if bulkAdd:
|
||||||
if type(preshared_key_bulkAdd) is not bool:
|
if type(preshared_key_bulkAdd) is not bool:
|
||||||
preshared_key_bulkAdd = False
|
preshared_key_bulkAdd = False
|
||||||
if type(bulkAddAmount) is not int or bulkAddAmount < 1:
|
if type(bulkAddAmount) is not int or bulkAddAmount < 1:
|
||||||
return ResponseObject(False, "Please specify amount of peers you want to add")
|
return ResponseObject(False, "Please specify amount of peers you want to add")
|
||||||
if not availableIps[0]:
|
if not ipStatus:
|
||||||
return ResponseObject(False, "No more available IP can assign")
|
return ResponseObject(False, "No more available IP can assign")
|
||||||
if bulkAddAmount > len(availableIps[1]):
|
if len(availableIps.keys()) == 0:
|
||||||
|
return ResponseObject(False, "This configuration does not have any IP address available")
|
||||||
|
|
||||||
|
|
||||||
|
if bulkAddAmount > len(availableIps[defaultIPSubnet]):
|
||||||
return ResponseObject(False,
|
return ResponseObject(False,
|
||||||
f"The maximum number of peers can add is {len(availableIps[1])}")
|
f"The maximum number of peers can add is {len(availableIps[defaultIPSubnet])}")
|
||||||
keyPairs = []
|
keyPairs = []
|
||||||
for i in range(bulkAddAmount):
|
for i in range(bulkAddAmount):
|
||||||
newPrivateKey = GenerateWireguardPrivateKey()[1]
|
newPrivateKey = GenerateWireguardPrivateKey()[1]
|
||||||
@ -2530,7 +2562,7 @@ def API_addPeers(configName):
|
|||||||
"private_key": newPrivateKey,
|
"private_key": newPrivateKey,
|
||||||
"id": GenerateWireguardPublicKey(newPrivateKey)[1],
|
"id": GenerateWireguardPublicKey(newPrivateKey)[1],
|
||||||
"preshared_key": (GenerateWireguardPrivateKey()[1] if preshared_key_bulkAdd else ""),
|
"preshared_key": (GenerateWireguardPrivateKey()[1] if preshared_key_bulkAdd else ""),
|
||||||
"allowed_ip": availableIps[1][i],
|
"allowed_ip": availableIps[defaultIPSubnet][i],
|
||||||
"name": f"BulkPeer #{(i + 1)}_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
|
"name": f"BulkPeer #{(i + 1)}_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
|
||||||
"DNS": dns_addresses,
|
"DNS": dns_addresses,
|
||||||
"endpoint_allowed_ip": endpoint_allowed_ip,
|
"endpoint_allowed_ip": endpoint_allowed_ip,
|
||||||
@ -2554,14 +2586,19 @@ def API_addPeers(configName):
|
|||||||
public_key = GenerateWireguardPublicKey(private_key)[1]
|
public_key = GenerateWireguardPublicKey(private_key)[1]
|
||||||
|
|
||||||
if len(allowed_ips) == 0:
|
if len(allowed_ips) == 0:
|
||||||
if availableIps[0]:
|
if ipStatus:
|
||||||
allowed_ips = [availableIps[1][0]]
|
allowed_ips = [availableIps[defaultIPSubnet][0]]
|
||||||
else:
|
else:
|
||||||
return ResponseObject(False, "No more available IP can assign")
|
return ResponseObject(False, "No more available IP can assign")
|
||||||
|
|
||||||
if not override_allowed_ips:
|
if allowed_ips_validation:
|
||||||
for i in allowed_ips:
|
for i in allowed_ips:
|
||||||
if i not in availableIps[1]:
|
found = False
|
||||||
|
for key in availableIps.keys():
|
||||||
|
if i in availableIps[key]:
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
return ResponseObject(False, f"This IP is not available: {i}")
|
return ResponseObject(False, f"This IP is not available: {i}")
|
||||||
|
|
||||||
status, result = config.addPeers([
|
status, result = config.addPeers([
|
||||||
@ -2580,7 +2617,7 @@ def API_addPeers(configName):
|
|||||||
)
|
)
|
||||||
return ResponseObject(status=status, message=result['message'], data=result['peers'])
|
return ResponseObject(status=status, message=result['message'], data=result['peers'])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e, str(e.__traceback__))
|
||||||
return ResponseObject(False, "Add peers failed. Please see data for specific issue")
|
return ResponseObject(False, "Add peers failed. Please see data for specific issue")
|
||||||
|
|
||||||
return ResponseObject(False, "Configuration does not exist")
|
return ResponseObject(False, "Configuration does not exist")
|
||||||
@ -2618,6 +2655,13 @@ def API_getAvailableIPs(configName):
|
|||||||
status, ips = WireguardConfigurations.get(configName).getAvailableIP()
|
status, ips = WireguardConfigurations.get(configName).getAvailableIP()
|
||||||
return ResponseObject(status=status, data=ips)
|
return ResponseObject(status=status, data=ips)
|
||||||
|
|
||||||
|
@app.get(f"{APP_PREFIX}/api/getNumberOfAvailableIPs/<configName>")
|
||||||
|
def API_getNumberOfAvailableIPs(configName):
|
||||||
|
if configName not in WireguardConfigurations.keys():
|
||||||
|
return ResponseObject(False, "Configuration does not exist")
|
||||||
|
status, ips = WireguardConfigurations.get(configName).getNumberOfAvailableIP()
|
||||||
|
return ResponseObject(status=status, data=ips)
|
||||||
|
|
||||||
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurationInfo')
|
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurationInfo')
|
||||||
def API_getConfigurationInfo():
|
def API_getConfigurationInfo():
|
||||||
configurationName = request.args.get("configurationName")
|
configurationName = request.args.get("configurationName")
|
||||||
|
@ -4,6 +4,7 @@ import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStor
|
|||||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||||
import LocaleText from "@/components/text/localeText.vue";
|
import LocaleText from "@/components/text/localeText.vue";
|
||||||
import {GetLocale} from "@/utilities/locale.js";
|
import {GetLocale} from "@/utilities/locale.js";
|
||||||
|
import {ref} from "vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "allowedIPsInput",
|
name: "allowedIPsInput",
|
||||||
@ -22,17 +23,23 @@ export default {
|
|||||||
allowedIpFormatError: false
|
allowedIpFormatError: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(){
|
setup(props){
|
||||||
const store = WireguardConfigurationsStore();
|
const store = WireguardConfigurationsStore();
|
||||||
const dashboardStore = DashboardConfigurationStore();
|
const dashboardStore = DashboardConfigurationStore();
|
||||||
return {store, dashboardStore}
|
const selectedSubnet = ref("")
|
||||||
|
if(Object.keys(props.availableIp).length > 0){
|
||||||
|
selectedSubnet.value = Object.keys(props.availableIp)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {store, dashboardStore, selectedSubnet}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
searchAvailableIps(){
|
searchAvailableIps(){
|
||||||
return this.availableIpSearchString ?
|
return (this.availableIpSearchString ?
|
||||||
this.availableIp.filter(x =>
|
this.availableIp[this.selectedSubnet].filter(x =>
|
||||||
x.includes(this.availableIpSearchString) && !this.data.allowed_ips.includes(x)) :
|
x.includes(this.availableIpSearchString) && !this.data.allowed_ips.includes(x)) :
|
||||||
this.availableIp.filter(x => !this.data.allowed_ips.includes(x))
|
this.availableIp[this.selectedSubnet].filter(x => !this.data.allowed_ips.includes(x)))
|
||||||
},
|
},
|
||||||
inputGetLocale(){
|
inputGetLocale(){
|
||||||
return GetLocale("Enter IP Address/CIDR")
|
return GetLocale("Enter IP Address/CIDR")
|
||||||
@ -59,14 +66,16 @@ export default {
|
|||||||
watch: {
|
watch: {
|
||||||
customAvailableIp(){
|
customAvailableIp(){
|
||||||
this.allowedIpFormatError = false;
|
this.allowedIpFormatError = false;
|
||||||
},
|
|
||||||
availableIp(){
|
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.availableIp !== undefined && this.availableIp.length > 0 && this.data.allowed_ips.length === 0){
|
if (this.availableIp !== undefined &&
|
||||||
this.addAllowedIp(this.availableIp[0])
|
Object.keys(this.availableIp).length > 0 && this.data.allowed_ips.length === 0){
|
||||||
|
for (let subnet in this.availableIp){
|
||||||
|
if (this.availableIp[subnet].length > 0){
|
||||||
|
this.addAllowedIp(this.availableIp[subnet][0])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,19 +83,19 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="{inactiveField: this.bulk}">
|
<div :class="{inactiveField: this.bulk}">
|
||||||
<div class="d-flex">
|
<div class="d-flex flex-column flex-md-row mb-2">
|
||||||
<label for="peer_allowed_ip_textbox" class="form-label">
|
<label for="peer_allowed_ip_textbox" class="form-label mb-0">
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
<LocaleText t="Allowed IPs"></LocaleText> <code><LocaleText t="(Required)"></LocaleText></code>
|
<LocaleText t="Allowed IPs"></LocaleText> <code><LocaleText t="(Required)"></LocaleText></code>
|
||||||
</small>
|
</small>
|
||||||
</label>
|
</label>
|
||||||
<div class="form-check form-switch ms-auto">
|
<div class="form-check form-switch ms-md-auto">
|
||||||
<input class="form-check-input" type="checkbox"
|
<input class="form-check-input" type="checkbox"
|
||||||
v-model="this.data.override_allowed_ips"
|
v-model="this.data.allowed_ips_validation"
|
||||||
role="switch" id="disableIPValidation">
|
role="switch" id="disableIPValidation">
|
||||||
<label class="form-check-label" for="disableIPValidation">
|
<label class="form-check-label" for="disableIPValidation">
|
||||||
<small>
|
<small>
|
||||||
<LocaleText t="Disable Allowed IPs Validation"></LocaleText>
|
<LocaleText t="Allowed IPs Validation"></LocaleText>
|
||||||
</small>
|
</small>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -107,6 +116,7 @@ export default {
|
|||||||
<input type="text" class="form-control form-control-sm rounded-start-3"
|
<input type="text" class="form-control form-control-sm rounded-start-3"
|
||||||
:placeholder="this.inputGetLocale"
|
:placeholder="this.inputGetLocale"
|
||||||
:class="{'is-invalid': this.allowedIpFormatError}"
|
:class="{'is-invalid': this.allowedIpFormatError}"
|
||||||
|
@keyup.enter="this.customAvailableIp ? this.addAllowedIp(this.customAvailableIp) : undefined"
|
||||||
v-model="customAvailableIp"
|
v-model="customAvailableIp"
|
||||||
id="peer_allowed_ip_textbox"
|
id="peer_allowed_ip_textbox"
|
||||||
:disabled="bulk">
|
:disabled="bulk">
|
||||||
@ -129,11 +139,11 @@ export default {
|
|||||||
<i class="bi bi-filter-circle me-2"></i>
|
<i class="bi bi-filter-circle me-2"></i>
|
||||||
<LocaleText t="Pick Available IP"></LocaleText>
|
<LocaleText t="Pick Available IP"></LocaleText>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu mt-2 shadow w-100 dropdown-menu-end rounded-3"
|
<ul class="dropdown-menu mt-2 shadow w-100 dropdown-menu-end rounded-3 pb-0"
|
||||||
v-if="this.availableIp"
|
v-if="this.availableIp"
|
||||||
style="overflow-y: scroll; max-height: 270px; width: 300px !important;">
|
style="width: 300px !important;">
|
||||||
<li>
|
<li>
|
||||||
<div class="px-3 pb-2 pt-1 d-flex gap-3 align-items-center">
|
<div class="px-3 d-flex gap-3 align-items-center">
|
||||||
<label for="availableIpSearchString" class="text-muted">
|
<label for="availableIpSearchString" class="text-muted">
|
||||||
<i class="bi bi-search"></i>
|
<i class="bi bi-search"></i>
|
||||||
</label>
|
</label>
|
||||||
@ -142,16 +152,32 @@ export default {
|
|||||||
class="form-control form-control-sm rounded-3"
|
class="form-control form-control-sm rounded-3"
|
||||||
v-model="this.availableIpSearchString">
|
v-model="this.availableIpSearchString">
|
||||||
</div>
|
</div>
|
||||||
|
<hr class="my-2">
|
||||||
|
<div class="px-3 overflow-x-scroll d-flex overflow-x-scroll overflow-y-hidden align-items-center gap-2">
|
||||||
|
<small class="text-muted">Subnet</small>
|
||||||
|
<button
|
||||||
|
v-for="key in Object.keys(this.availableIp)"
|
||||||
|
@click="this.selectedSubnet = key"
|
||||||
|
:class="{'bg-primary-subtle': this.selectedSubnet === key}"
|
||||||
|
class="btn btn-sm text-primary-emphasis rounded-3">
|
||||||
|
{{key}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<hr class="mt-2 mb-0">
|
||||||
</li>
|
</li>
|
||||||
<li v-for="ip in this.searchAvailableIps" >
|
<li>
|
||||||
<a class="dropdown-item d-flex" role="button" @click="this.addAllowedIp(ip)">
|
<div class="overflow-y-scroll" style="height: 270px;">
|
||||||
<span class="me-auto"><small>{{ip}}</small></span>
|
<div v-for="ip in this.searchAvailableIps" style="">
|
||||||
</a>
|
<a class="dropdown-item d-flex" role="button" @click="this.addAllowedIp(ip)">
|
||||||
</li>
|
<span class="me-auto"><small>{{ip}}</small></span>
|
||||||
<li v-if="this.searchAvailableIps.length === 0">
|
</a>
|
||||||
<small class="px-3 text-muted">
|
</div>
|
||||||
<LocaleText t="No available IP containing"></LocaleText>
|
<div v-if="this.searchAvailableIps.length === 0">
|
||||||
"{{this.availableIpSearchString}}"</small>
|
<small class="px-3 text-muted">
|
||||||
|
<LocaleText t="No available IP containing"></LocaleText>
|
||||||
|
"{{this.availableIpSearchString}}"</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import LocaleText from "@/components/text/localeText.vue";
|
import LocaleText from "@/components/text/localeText.vue";
|
||||||
import {GetLocale} from "@/utilities/locale.js";
|
import {GetLocale} from "@/utilities/locale.js";
|
||||||
|
import {useRoute} from "vue-router";
|
||||||
|
import {fetchGet} from "@/utilities/fetch.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "bulkAdd",
|
name: "bulkAdd",
|
||||||
@ -10,9 +12,37 @@ export default {
|
|||||||
data: Object,
|
data: Object,
|
||||||
availableIp: undefined
|
availableIp: undefined
|
||||||
},
|
},
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
numberOfAvailableIPs: null
|
||||||
|
}
|
||||||
|
},
|
||||||
computed:{
|
computed:{
|
||||||
bulkAddGetLocale(){
|
bulkAddGetLocale(){
|
||||||
return GetLocale("How many peers you want to add?")
|
return GetLocale("How many peers you want to add?")
|
||||||
|
},
|
||||||
|
getNumberOfAvailableIPs(){
|
||||||
|
if (!this.numberOfAvailableIPs){
|
||||||
|
return '...'
|
||||||
|
}else{
|
||||||
|
return Object.values(this.numberOfAvailableIPs).reduce((x, y) => {
|
||||||
|
return x + y
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'data.bulkAdd': {
|
||||||
|
immediate: true,
|
||||||
|
handler(newVal){
|
||||||
|
if (newVal){
|
||||||
|
fetchGet("/api/getNumberOfAvailableIPs/" + this.$route.params.id, {}, (res) => {
|
||||||
|
if (res.status){
|
||||||
|
this.numberOfAvailableIPs = res.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,7 +72,9 @@ export default {
|
|||||||
v-model="this.data.bulkAddAmount"
|
v-model="this.data.bulkAddAmount"
|
||||||
:placeholder="this.bulkAddGetLocale">
|
:placeholder="this.bulkAddGetLocale">
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
<LocaleText :t="`You can add up to ` + this.availableIp.length + ' peers'"></LocaleText>
|
<LocaleText :t="`You can add up to ` +
|
||||||
|
getNumberOfAvailableIPs
|
||||||
|
+ ' peers'"></LocaleText>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -80,10 +80,7 @@ export default {
|
|||||||
<div>
|
<div>
|
||||||
<label for="peer_private_key_textbox" class="form-label">
|
<label for="peer_private_key_textbox" class="form-label">
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
<LocaleText t="Private Key"></LocaleText>
|
<LocaleText t="Private Key"></LocaleText> <code><LocaleText t="(Required for QR Code and Download)"></LocaleText></code></small>
|
||||||
<code>
|
|
||||||
<LocaleText t="(Required for QR Code and Download)"></LocaleText>
|
|
||||||
</code></small>
|
|
||||||
</label>
|
</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control form-control-sm rounded-start-3"
|
<input type="text" class="form-control form-control-sm rounded-start-3"
|
||||||
@ -100,15 +97,14 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="d-flex">
|
<div class="d-flex flex-column flex-md-row mb-2">
|
||||||
<label for="public_key" class="form-label">
|
<label for="public_key" class="form-label mb-0">
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
<LocaleText t="Public Key"></LocaleText>
|
<LocaleText t="Public Key"></LocaleText> <code>
|
||||||
<code>
|
|
||||||
<LocaleText t="(Required)"></LocaleText>
|
<LocaleText t="(Required)"></LocaleText>
|
||||||
</code></small>
|
</code></small>
|
||||||
</label>
|
</label>
|
||||||
<div class="form-check form-switch ms-auto">
|
<div class="form-check form-switch ms-md-auto">
|
||||||
<input class="form-check-input" type="checkbox" role="switch"
|
<input class="form-check-input" type="checkbox" role="switch"
|
||||||
:disabled="this.bulk"
|
:disabled="this.bulk"
|
||||||
id="enablePublicKeyEdit" v-model="this.editKey">
|
id="enablePublicKeyEdit" v-model="this.editKey">
|
||||||
|
@ -32,7 +32,7 @@ const peerData = ref({
|
|||||||
preshared_key: "",
|
preshared_key: "",
|
||||||
preshared_key_bulkAdd: false,
|
preshared_key_bulkAdd: false,
|
||||||
advanced_security: "off",
|
advanced_security: "off",
|
||||||
override_allowed_ips: false,
|
allowed_ips_validation: true,
|
||||||
})
|
})
|
||||||
const availableIp = ref([])
|
const availableIp = ref([])
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
|
@ -0,0 +1,776 @@
|
|||||||
|
<script>
|
||||||
|
import PeerSearch from "@/components/configurationComponents/peerSearch.vue";
|
||||||
|
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||||
|
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
|
||||||
|
import {fetchGet} from "@/utilities/fetch.js";
|
||||||
|
import Peer from "@/components/configurationComponents/peer.vue";
|
||||||
|
import { Line, Bar } from 'vue-chartjs'
|
||||||
|
import Fuse from "fuse.js";
|
||||||
|
import {
|
||||||
|
Chart,
|
||||||
|
LineElement,
|
||||||
|
BarElement,
|
||||||
|
BarController,
|
||||||
|
LineController,
|
||||||
|
LinearScale,
|
||||||
|
Legend,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
CategoryScale,
|
||||||
|
PointElement
|
||||||
|
} from 'chart.js';
|
||||||
|
Chart.register(
|
||||||
|
LineElement,
|
||||||
|
BarElement,
|
||||||
|
BarController,
|
||||||
|
LineController,
|
||||||
|
LinearScale,
|
||||||
|
Legend,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
CategoryScale,
|
||||||
|
PointElement
|
||||||
|
);
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import {defineAsyncComponent, ref} from "vue";
|
||||||
|
import LocaleText from "@/components/text/localeText.vue";
|
||||||
|
import PeerRow from "@/components/configurationComponents/peerRow.vue";
|
||||||
|
import {GetLocale} from "@/utilities/locale.js";
|
||||||
|
import PeerSearchBar from "@/components/configurationComponents/peerSearchBar.vue";
|
||||||
|
import ProtocolBadge from "@/components/protocolBadge.vue";
|
||||||
|
import EditRawConfigurationFile
|
||||||
|
from "@/components/configurationComponents/editConfigurationComponents/editRawConfigurationFile.vue";
|
||||||
|
import PeerIntersectionObserver from "@/components/configurationComponents/peerIntersectionObserver.vue";
|
||||||
|
export default {
|
||||||
|
name: "peerList",
|
||||||
|
components: {
|
||||||
|
PeerIntersectionObserver,
|
||||||
|
EditRawConfigurationFile,
|
||||||
|
ProtocolBadge,
|
||||||
|
PeerSearchBar,
|
||||||
|
PeerRow,
|
||||||
|
DeleteConfiguration:
|
||||||
|
defineAsyncComponent(() => import("@/components/configurationComponents/deleteConfiguration.vue")),
|
||||||
|
ConfigurationBackupRestore:
|
||||||
|
defineAsyncComponent(() => import("@/components/configurationComponents/configurationBackupRestore.vue")),
|
||||||
|
SelectPeers:
|
||||||
|
defineAsyncComponent(() => import("@/components/configurationComponents/selectPeers.vue")),
|
||||||
|
EditConfiguration:
|
||||||
|
defineAsyncComponent(() => import("@/components/configurationComponents/editConfiguration.vue")),
|
||||||
|
LocaleText,
|
||||||
|
PeerShareLinkModal:
|
||||||
|
defineAsyncComponent(() => import("@/components/configurationComponents/peerShareLinkModal.vue")),
|
||||||
|
PeerJobsLogsModal:
|
||||||
|
defineAsyncComponent(() => import("@/components/configurationComponents/peerJobsLogsModal.vue")),
|
||||||
|
PeerJobsAllModal:
|
||||||
|
defineAsyncComponent(() => import("@/components/configurationComponents/peerJobsAllModal.vue")),
|
||||||
|
PeerJobs:
|
||||||
|
defineAsyncComponent(() => import("@/components/configurationComponents/peerJobs.vue")),
|
||||||
|
PeerCreate:
|
||||||
|
defineAsyncComponent(() => import("@/components/configurationComponents/peerCreate.vue")),
|
||||||
|
PeerQRCode:
|
||||||
|
defineAsyncComponent(() => import("@/components/configurationComponents/peerQRCode.vue")),
|
||||||
|
PeerConfigurationFile:
|
||||||
|
defineAsyncComponent(() => import("@/components/configurationComponents/peerConfigurationFile.vue")),
|
||||||
|
PeerSettings:
|
||||||
|
defineAsyncComponent(() => import("@/components/configurationComponents/peerSettings.vue")),
|
||||||
|
PeerSearch,
|
||||||
|
Peer,
|
||||||
|
Line,
|
||||||
|
Bar
|
||||||
|
},
|
||||||
|
async setup(){
|
||||||
|
const dashboardConfigurationStore = DashboardConfigurationStore();
|
||||||
|
const wireguardConfigurationStore = WireguardConfigurationsStore();
|
||||||
|
const interval = ref(undefined)
|
||||||
|
return {dashboardConfigurationStore, wireguardConfigurationStore, interval}
|
||||||
|
},
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
configurationToggling: false,
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
configurationInfo: [],
|
||||||
|
configurationPeers: [],
|
||||||
|
historyDataSentDifference: [],
|
||||||
|
historyDataReceivedDifference: [],
|
||||||
|
historySentData: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Data Sent',
|
||||||
|
data: [],
|
||||||
|
fill: false,
|
||||||
|
borderColor: '#198754',
|
||||||
|
tension: 0
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
historyReceiveData: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Data Received',
|
||||||
|
data: [],
|
||||||
|
fill: false,
|
||||||
|
borderColor: '#0d6efd',
|
||||||
|
tension: 0
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
peerSetting: {
|
||||||
|
modalOpen: false,
|
||||||
|
selectedPeer: undefined
|
||||||
|
},
|
||||||
|
peerScheduleJobs:{
|
||||||
|
modalOpen: false,
|
||||||
|
selectedPeer: undefined
|
||||||
|
},
|
||||||
|
peerQRCode: {
|
||||||
|
modalOpen: false,
|
||||||
|
peerConfigData: undefined
|
||||||
|
},
|
||||||
|
peerConfigurationFile: {
|
||||||
|
modalOpen: false,
|
||||||
|
peerConfigData: undefined
|
||||||
|
},
|
||||||
|
peerCreate: {
|
||||||
|
modalOpen: false
|
||||||
|
},
|
||||||
|
peerScheduleJobsAll: {
|
||||||
|
modalOpen: false
|
||||||
|
},
|
||||||
|
peerScheduleJobsLogs: {
|
||||||
|
modalOpen: false
|
||||||
|
},
|
||||||
|
peerShare:{
|
||||||
|
modalOpen: false,
|
||||||
|
selectedPeer: undefined
|
||||||
|
},
|
||||||
|
editConfiguration: {
|
||||||
|
modalOpen: false
|
||||||
|
},
|
||||||
|
selectPeers: {
|
||||||
|
modalOpen: false
|
||||||
|
},
|
||||||
|
backupRestore: {
|
||||||
|
modalOpen: false
|
||||||
|
},
|
||||||
|
deleteConfiguration: {
|
||||||
|
modalOpen: false
|
||||||
|
},
|
||||||
|
editRawConfigurationFile: {
|
||||||
|
modalOpen: false
|
||||||
|
},
|
||||||
|
peerSearchBarShow: false,
|
||||||
|
searchStringTimeout: undefined,
|
||||||
|
searchString: "",
|
||||||
|
showPeersCount: 10,
|
||||||
|
showPeersThreshold: 10,
|
||||||
|
observer: undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'$route': {
|
||||||
|
immediate: true,
|
||||||
|
handler(){
|
||||||
|
this.showPeersCount = 20;
|
||||||
|
clearInterval(this.dashboardConfigurationStore.Peers.RefreshInterval);
|
||||||
|
this.loading = true;
|
||||||
|
let id = this.$route.params.id;
|
||||||
|
this.configurationInfo = [];
|
||||||
|
this.configurationPeers = [];
|
||||||
|
if (id){
|
||||||
|
this.getPeers(id)
|
||||||
|
this.setPeerInterval();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'dashboardConfigurationStore.Configuration.Server.dashboard_refresh_interval'(){
|
||||||
|
clearInterval(this.dashboardConfigurationStore.Peers.RefreshInterval);
|
||||||
|
this.setPeerInterval();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeRouteLeave(){
|
||||||
|
clearInterval(this.dashboardConfigurationStore.Peers.RefreshInterval);
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
toggle(){
|
||||||
|
this.configurationToggling = true;
|
||||||
|
fetchGet("/api/toggleWireguardConfiguration/", {
|
||||||
|
configurationName: this.configurationInfo.Name
|
||||||
|
}, (res) => {
|
||||||
|
if (res.status){
|
||||||
|
this.dashboardConfigurationStore.newMessage("Server",
|
||||||
|
`${this.configurationInfo.Name} ${res.data ? 'is on':'is off'}`, "success")
|
||||||
|
}else{
|
||||||
|
this.dashboardConfigurationStore.newMessage("Server",
|
||||||
|
res.message, 'danger')
|
||||||
|
}
|
||||||
|
this.wireguardConfigurationStore.Configurations.find(x => x.Name === this.configurationInfo.Name).Status = res.data
|
||||||
|
console.log(this.wireguardConfigurationStore.Configurations.find(x => x.Name === this.configurationInfo.Name).Status)
|
||||||
|
|
||||||
|
this.configurationInfo.Status = res.data
|
||||||
|
this.configurationToggling = false;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getPeers(id = this.$route.params.id){
|
||||||
|
fetchGet("/api/getWireguardConfigurationInfo",
|
||||||
|
{
|
||||||
|
configurationName: id
|
||||||
|
}, (res) => {
|
||||||
|
this.configurationInfo = res.data.configurationInfo;
|
||||||
|
this.configurationPeers = res.data.configurationPeers;
|
||||||
|
this.configurationPeers.forEach(x => {
|
||||||
|
x.restricted = false;
|
||||||
|
})
|
||||||
|
res.data.configurationRestrictedPeers.forEach(x => {
|
||||||
|
x.restricted = true;
|
||||||
|
this.configurationPeers.push(x)
|
||||||
|
})
|
||||||
|
this.loading = false;
|
||||||
|
if (this.configurationPeers.length > 0){
|
||||||
|
const sent = this.configurationPeers.map(x => x.total_sent + x.cumu_sent)
|
||||||
|
.reduce((x,y) => x + y).toFixed(4);
|
||||||
|
const receive = this.configurationPeers.map(x => x.total_receive + x.cumu_receive).reduce((x,y) => x + y).toFixed(4);
|
||||||
|
if (
|
||||||
|
this.historyDataSentDifference[this.historyDataSentDifference.length - 1] !== sent
|
||||||
|
){
|
||||||
|
if (this.historyDataSentDifference.length > 0){
|
||||||
|
this.historySentData = {
|
||||||
|
labels: [...this.historySentData.labels, dayjs().format("HH:mm:ss A")],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Data Sent',
|
||||||
|
data: [...this.historySentData.datasets[0].data,
|
||||||
|
((sent - this.historyDataSentDifference[this.historyDataSentDifference.length - 1])*1000)
|
||||||
|
.toFixed(4)],
|
||||||
|
fill: false,
|
||||||
|
borderColor: '#198754',
|
||||||
|
tension: 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.historyDataSentDifference.push(sent)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
this.historyDataReceivedDifference[this.historyDataReceivedDifference.length - 1] !== receive
|
||||||
|
){
|
||||||
|
if (this.historyDataReceivedDifference.length > 0){
|
||||||
|
this.historyReceiveData = {
|
||||||
|
labels: [...this.historyReceiveData.labels, dayjs().format("HH:mm:ss A")],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Data Received',
|
||||||
|
data: [...this.historyReceiveData.datasets[0].data,
|
||||||
|
((receive - this.historyDataReceivedDifference[this.historyDataReceivedDifference.length - 1])*1000)
|
||||||
|
.toFixed(4)],
|
||||||
|
fill: false,
|
||||||
|
borderColor: '#0d6efd',
|
||||||
|
tension: 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.historyDataReceivedDifference.push(receive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
setPeerInterval(){
|
||||||
|
this.dashboardConfigurationStore.Peers.RefreshInterval = setInterval(() => {
|
||||||
|
this.getPeers()
|
||||||
|
}, parseInt(this.dashboardConfigurationStore.Configuration.Server.dashboard_refresh_interval))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
configurationSummary(){
|
||||||
|
return {
|
||||||
|
connectedPeers: this.configurationPeers.filter(x => x.status === "running").length,
|
||||||
|
totalUsage: this.configurationPeers.length > 0 ?
|
||||||
|
this.configurationPeers.filter(x => !x.restricted)
|
||||||
|
.map(x => x.total_data + x.cumu_data).reduce((a, b) => a + b, 0).toFixed(4) : 0,
|
||||||
|
totalReceive: this.configurationPeers.length > 0 ?
|
||||||
|
this.configurationPeers.filter(x => !x.restricted)
|
||||||
|
.map(x => x.total_receive + x.cumu_receive).reduce((a, b) => a + b, 0).toFixed(4) : 0,
|
||||||
|
totalSent: this.configurationPeers.length > 0 ?
|
||||||
|
this.configurationPeers.filter(x => !x.restricted)
|
||||||
|
.map(x => x.total_sent + x.cumu_sent).reduce((a, b) => a + b, 0).toFixed(4) : 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
receiveData(){
|
||||||
|
return this.historyReceiveData
|
||||||
|
},
|
||||||
|
sentData(){
|
||||||
|
return this.historySentData
|
||||||
|
},
|
||||||
|
individualDataUsage(){
|
||||||
|
return {
|
||||||
|
labels: this.configurationPeers.map(x => {
|
||||||
|
if (x.name) return x.name
|
||||||
|
return `Untitled Peer - ${x.id}`
|
||||||
|
}),
|
||||||
|
datasets: [{
|
||||||
|
label: 'Total Data Usage',
|
||||||
|
data: this.configurationPeers.map(x => x.cumu_data + x.total_data),
|
||||||
|
backgroundColor: this.configurationPeers.map(x => `#0dcaf0`),
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: (tooltipItem) => {
|
||||||
|
return `${tooltipItem.formattedValue} GB`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
individualDataUsageChartOption(){
|
||||||
|
return {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
ticks: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
y:{
|
||||||
|
ticks: {
|
||||||
|
callback: (val, index) => {
|
||||||
|
return `${val} GB`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
chartOptions() {
|
||||||
|
return {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: (tooltipItem) => {
|
||||||
|
return `${tooltipItem.formattedValue} MB/s`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
ticks: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
y:{
|
||||||
|
ticks: {
|
||||||
|
callback: (val, index) => {
|
||||||
|
return `${val} MB/s`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
searchPeers(){
|
||||||
|
const fuse = new Fuse(this.configurationPeers, {
|
||||||
|
keys: ["name", "id", "allowed_ip"]
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = this.wireguardConfigurationStore.searchString ?
|
||||||
|
this.configurationPeers.filter(x => {
|
||||||
|
return x.name.includes(this.wireguardConfigurationStore.searchString) ||
|
||||||
|
x.id.includes(this.wireguardConfigurationStore.searchString) ||
|
||||||
|
x.allowed_ip.includes(this.wireguardConfigurationStore.searchString)
|
||||||
|
}) : this.configurationPeers;
|
||||||
|
|
||||||
|
if (this.dashboardConfigurationStore.Configuration.Server.dashboard_sort === "restricted"){
|
||||||
|
return result.sort((a, b) => {
|
||||||
|
if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]
|
||||||
|
< b[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort] ){
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]
|
||||||
|
> b[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}).slice(0, this.showPeersCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.sort((a, b) => {
|
||||||
|
if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]
|
||||||
|
< b[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort] ){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]
|
||||||
|
> b[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]){
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}).slice(0, this.showPeersCount)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="!this.loading" class="container-md" id="peerList">
|
||||||
|
<div class="d-flex align-items-sm-center flex-column flex-sm-row gap-3">
|
||||||
|
<div>
|
||||||
|
<div class="text-muted d-flex align-items-center gap-2">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<ProtocolBadge :protocol="this.configurationInfo.Protocol"></ProtocolBadge>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center gap-3">
|
||||||
|
<h1 class="mb-0 display-4"><samp>{{this.configurationInfo.Name}}</samp></h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ms-sm-auto d-flex gap-2 flex-column">
|
||||||
|
<div class="card rounded-3 bg-transparent ">
|
||||||
|
<div class="card-body py-2 d-flex align-items-center">
|
||||||
|
<small class="text-muted">
|
||||||
|
<LocaleText t="Status"></LocaleText>
|
||||||
|
</small>
|
||||||
|
<div class="dot ms-2" :class="{active: this.configurationInfo.Status}"></div>
|
||||||
|
<div class="form-check form-switch mb-0 ms-auto pe-0 me-0">
|
||||||
|
<label class="form-check-label" style="cursor: pointer" :for="'switch' + this.configurationInfo.id">
|
||||||
|
<LocaleText t="On" v-if="this.configurationInfo.Status && !this.configurationToggling"></LocaleText>
|
||||||
|
<LocaleText t="Off" v-else-if="!this.configurationInfo.Status && !this.configurationToggling"></LocaleText>
|
||||||
|
<span v-if="this.configurationToggling"
|
||||||
|
class="spinner-border spinner-border-sm ms-2" aria-hidden="true">
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<input class="form-check-input"
|
||||||
|
style="cursor: pointer"
|
||||||
|
:disabled="this.configurationToggling"
|
||||||
|
type="checkbox" role="switch" :id="'switch' + this.configurationInfo.id"
|
||||||
|
@change="this.toggle()"
|
||||||
|
v-model="this.configurationInfo.Status"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<RouterLink
|
||||||
|
to="create"
|
||||||
|
class="titleBtn py-2 text-decoration-none btn text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle ">
|
||||||
|
<i class="bi bi-plus-lg me-2"></i>
|
||||||
|
<LocaleText t="Peer"></LocaleText>
|
||||||
|
</RouterLink>
|
||||||
|
<button class="titleBtn py-2 text-decoration-none btn text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle "
|
||||||
|
@click="editConfiguration.modalOpen = true"
|
||||||
|
type="button" aria-expanded="false">
|
||||||
|
<i class="bi bi-gear-fill me-2"></i>
|
||||||
|
<LocaleText t="Configuration Settings"></LocaleText>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="row mt-3 gy-2 gx-2 mb-2">
|
||||||
|
<div class="col-12 col-lg-3">
|
||||||
|
<div class="card rounded-3 bg-transparent h-100">
|
||||||
|
<div class="card-body py-2 d-flex flex-column justify-content-center">
|
||||||
|
<p class="mb-0 text-muted"><small>
|
||||||
|
<LocaleText t="Address"></LocaleText>
|
||||||
|
</small></p>
|
||||||
|
{{this.configurationInfo.Address}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-3">
|
||||||
|
<div class="card rounded-3 bg-transparent h-100">
|
||||||
|
<div class="card-body py-2 d-flex flex-column justify-content-center">
|
||||||
|
<p class="mb-0 text-muted"><small>
|
||||||
|
<LocaleText t="Listen Port"></LocaleText>
|
||||||
|
</small></p>
|
||||||
|
{{this.configurationInfo.ListenPort}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="word-break: break-all" class="col-12 col-lg-6">
|
||||||
|
<div class="card rounded-3 bg-transparent h-100">
|
||||||
|
<div class="card-body py-2 d-flex flex-column justify-content-center">
|
||||||
|
<p class="mb-0 text-muted"><small>
|
||||||
|
<LocaleText t="Public Key"></LocaleText>
|
||||||
|
</small></p>
|
||||||
|
<samp>{{this.configurationInfo.PublicKey}}</samp>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row gx-2 gy-2 mb-2">
|
||||||
|
<div class="col-12 col-lg-3">
|
||||||
|
<div class="card rounded-3 bg-transparent h-100">
|
||||||
|
<div class="card-body d-flex">
|
||||||
|
<div>
|
||||||
|
<p class="mb-0 text-muted"><small>
|
||||||
|
<LocaleText t="Connected Peers"></LocaleText>
|
||||||
|
</small></p>
|
||||||
|
<strong class="h4">
|
||||||
|
{{configurationSummary.connectedPeers}} / {{configurationPeers.length}}
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
<i class="bi bi-ethernet ms-auto h2 text-muted"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-3">
|
||||||
|
<div class="card rounded-3 bg-transparent h-100">
|
||||||
|
<div class="card-body d-flex">
|
||||||
|
<div>
|
||||||
|
<p class="mb-0 text-muted"><small>
|
||||||
|
<LocaleText t="Total Usage"></LocaleText>
|
||||||
|
</small></p>
|
||||||
|
<strong class="h4">{{configurationSummary.totalUsage}} GB</strong>
|
||||||
|
</div>
|
||||||
|
<i class="bi bi-arrow-down-up ms-auto h2 text-muted"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-3">
|
||||||
|
<div class="card rounded-3 bg-transparent h-100">
|
||||||
|
<div class="card-body d-flex">
|
||||||
|
<div>
|
||||||
|
<p class="mb-0 text-muted"><small>
|
||||||
|
<LocaleText t="Total Received"></LocaleText>
|
||||||
|
</small></p>
|
||||||
|
<strong class="h4 text-primary">{{configurationSummary.totalReceive}} GB</strong>
|
||||||
|
</div>
|
||||||
|
<i class="bi bi-arrow-down ms-auto h2 text-muted"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-3">
|
||||||
|
<div class="card rounded-3 bg-transparent h-100">
|
||||||
|
<div class="card-body d-flex">
|
||||||
|
<div>
|
||||||
|
<p class="mb-0 text-muted"><small>
|
||||||
|
<LocaleText t="Total Sent"></LocaleText>
|
||||||
|
</small></p>
|
||||||
|
<strong class="h4 text-success">{{configurationSummary.totalSent}} GB</strong>
|
||||||
|
</div>
|
||||||
|
<i class="bi bi-arrow-up ms-auto h2 text-muted"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row gx-2 gy-2 mb-3">
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
<div class="card rounded-3 bg-transparent " style="height: 270px">
|
||||||
|
<div class="card-header bg-transparent border-0">
|
||||||
|
<small class="text-muted">
|
||||||
|
<LocaleText t="Peers Data Usage"></LocaleText>
|
||||||
|
</small></div>
|
||||||
|
<div class="card-body pt-1">
|
||||||
|
<Bar
|
||||||
|
:data="individualDataUsage"
|
||||||
|
:options="individualDataUsageChartOption"
|
||||||
|
style="width: 100%; height: 200px; max-height: 200px"></Bar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm col-lg-3">
|
||||||
|
<div class="card rounded-3 bg-transparent " style="height: 270px">
|
||||||
|
<div class="card-header bg-transparent border-0"><small class="text-muted">
|
||||||
|
<LocaleText t="Real Time Received Data Usage"></LocaleText>
|
||||||
|
</small></div>
|
||||||
|
<div class="card-body pt-1">
|
||||||
|
<Line
|
||||||
|
:options="chartOptions"
|
||||||
|
:data="receiveData"
|
||||||
|
style="width: 100%; height: 200px; max-height: 200px"
|
||||||
|
></Line>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm col-lg-3">
|
||||||
|
<div class="card rounded-3 bg-transparent " style="height: 270px">
|
||||||
|
<div class="card-header bg-transparent border-0"><small class="text-muted">
|
||||||
|
<LocaleText t="Real Time Sent Data Usage"></LocaleText>
|
||||||
|
</small></div>
|
||||||
|
<div class="card-body pt-1">
|
||||||
|
<Line
|
||||||
|
:options="chartOptions"
|
||||||
|
:data="sentData"
|
||||||
|
style="width: 100%; height: 200px; max-height: 200px"
|
||||||
|
></Line>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div style="margin-bottom: 80px">
|
||||||
|
<PeerSearch
|
||||||
|
@search="this.peerSearchBarShow = true"
|
||||||
|
@jobsAll="this.peerScheduleJobsAll.modalOpen = true"
|
||||||
|
@jobLogs="this.peerScheduleJobsLogs.modalOpen = true"
|
||||||
|
@editConfiguration="this.editConfiguration.modalOpen = true"
|
||||||
|
@selectPeers="this.selectPeers.modalOpen = true"
|
||||||
|
@backupRestore="this.backupRestore.modalOpen = true"
|
||||||
|
@deleteConfiguration="this.deleteConfiguration.modalOpen = true"
|
||||||
|
:configuration="this.configurationInfo"></PeerSearch>
|
||||||
|
<TransitionGroup name="peerList" tag="div" class="row gx-2 gy-2 z-0 position-relative">
|
||||||
|
<div class="col-12 col-lg-6 col-xl-4"
|
||||||
|
:key="peer.id"
|
||||||
|
v-for="peer in this.searchPeers">
|
||||||
|
<Peer :Peer="peer"
|
||||||
|
@share="this.peerShare.selectedPeer = peer.id; this.peerShare.modalOpen = true;"
|
||||||
|
@refresh="this.getPeers()"
|
||||||
|
@jobs="peerScheduleJobs.modalOpen = true; peerScheduleJobs.selectedPeer = this.configurationPeers.find(x => x.id === peer.id)"
|
||||||
|
@setting="peerSetting.modalOpen = true; peerSetting.selectedPeer = this.configurationPeers.find(x => x.id === peer.id)"
|
||||||
|
@qrcode="(file) => {this.peerQRCode.peerConfigData = file; this.peerQRCode.modalOpen = true;}"
|
||||||
|
@configurationFile="(file) => {this.peerConfigurationFile.peerConfigData = file; this.peerConfigurationFile.modalOpen = true;}"
|
||||||
|
></Peer>
|
||||||
|
</div>
|
||||||
|
</TransitionGroup>
|
||||||
|
</div>
|
||||||
|
<Transition name="slideUp">
|
||||||
|
<PeerSearchBar
|
||||||
|
@close="peerSearchBarShow = false"
|
||||||
|
v-if="this.peerSearchBarShow"></PeerSearchBar>
|
||||||
|
</Transition>
|
||||||
|
|
||||||
|
<Transition name="zoom">
|
||||||
|
<PeerSettings v-if="this.peerSetting.modalOpen"
|
||||||
|
key="settings"
|
||||||
|
:selectedPeer="this.peerSetting.selectedPeer"
|
||||||
|
@refresh="this.getPeers()"
|
||||||
|
@close="this.peerSetting.modalOpen = false">
|
||||||
|
</PeerSettings>
|
||||||
|
</Transition>
|
||||||
|
<Transition name="zoom">
|
||||||
|
<PeerQRCode :peerConfigData="this.peerQRCode.peerConfigData"
|
||||||
|
key="qrcode"
|
||||||
|
@close="this.peerQRCode.modalOpen = false"
|
||||||
|
v-if="peerQRCode.modalOpen"></PeerQRCode>
|
||||||
|
</Transition>
|
||||||
|
<Transition name="zoom">
|
||||||
|
<PeerJobs
|
||||||
|
@refresh="this.getPeers()"
|
||||||
|
v-if="this.peerScheduleJobs.modalOpen"
|
||||||
|
:selectedPeer="this.peerScheduleJobs.selectedPeer"
|
||||||
|
@close="this.peerScheduleJobs.modalOpen = false">
|
||||||
|
</PeerJobs>
|
||||||
|
</Transition>
|
||||||
|
<Transition name="zoom">
|
||||||
|
<PeerJobsAllModal
|
||||||
|
v-if="this.peerScheduleJobsAll.modalOpen"
|
||||||
|
@refresh="this.getPeers()"
|
||||||
|
@allLogs="peerScheduleJobsLogs.modalOpen = true"
|
||||||
|
@close="this.peerScheduleJobsAll.modalOpen = false"
|
||||||
|
:configurationPeers="this.configurationPeers"
|
||||||
|
>
|
||||||
|
</PeerJobsAllModal>
|
||||||
|
</Transition>
|
||||||
|
<Transition name="zoom">
|
||||||
|
<PeerJobsLogsModal v-if="this.peerScheduleJobsLogs.modalOpen"
|
||||||
|
|
||||||
|
@close="this.peerScheduleJobsLogs.modalOpen = false"
|
||||||
|
:configurationInfo="this.configurationInfo"
|
||||||
|
>
|
||||||
|
</PeerJobsLogsModal>
|
||||||
|
</Transition>
|
||||||
|
<Transition name="zoom">
|
||||||
|
<PeerShareLinkModal
|
||||||
|
v-if="this.peerShare.modalOpen"
|
||||||
|
@close="this.peerShare.modalOpen = false; this.peerShare.selectedPeer = undefined;"
|
||||||
|
:peer="this.configurationPeers.find(x => x.id === this.peerShare.selectedPeer)"></PeerShareLinkModal>
|
||||||
|
</Transition>
|
||||||
|
<Transition name="zoom">
|
||||||
|
<EditConfiguration
|
||||||
|
@editRaw="this.editRawConfigurationFile.modalOpen = true"
|
||||||
|
@close="this.editConfiguration.modalOpen = false"
|
||||||
|
@dataChanged="(d) => this.configurationInfo = d"
|
||||||
|
@backupRestore="this.backupRestore.modalOpen = true"
|
||||||
|
@deleteConfiguration="this.deleteConfiguration.modalOpen = true"
|
||||||
|
:configurationInfo="this.configurationInfo"
|
||||||
|
v-if="this.editConfiguration.modalOpen"></EditConfiguration>
|
||||||
|
</Transition>
|
||||||
|
<Transition name="zoom">
|
||||||
|
<SelectPeers
|
||||||
|
@refresh="this.getPeers()"
|
||||||
|
v-if="this.selectPeers.modalOpen"
|
||||||
|
:configurationPeers="this.configurationPeers"
|
||||||
|
@close="this.selectPeers.modalOpen = false"
|
||||||
|
></SelectPeers>
|
||||||
|
</Transition>
|
||||||
|
|
||||||
|
<Transition name="zoom">
|
||||||
|
<DeleteConfiguration
|
||||||
|
@backup="backupRestore.modalOpen = true"
|
||||||
|
@close="deleteConfiguration.modalOpen = false"
|
||||||
|
v-if="deleteConfiguration.modalOpen"></DeleteConfiguration>
|
||||||
|
</Transition>
|
||||||
|
<Transition name="zoom">
|
||||||
|
<EditRawConfigurationFile
|
||||||
|
@close="editRawConfigurationFile.modalOpen = false"
|
||||||
|
v-if="editRawConfigurationFile.modalOpen">
|
||||||
|
</EditRawConfigurationFile>
|
||||||
|
</Transition>
|
||||||
|
<Transition name="zoom">
|
||||||
|
<ConfigurationBackupRestore
|
||||||
|
@close="backupRestore.modalOpen = false"
|
||||||
|
@refreshPeersList="this.getPeers()"
|
||||||
|
v-if="backupRestore.modalOpen"></ConfigurationBackupRestore>
|
||||||
|
</Transition>
|
||||||
|
<Transition name="zoom">
|
||||||
|
<PeerConfigurationFile
|
||||||
|
@close="peerConfigurationFile.modalOpen = false"
|
||||||
|
v-if="peerConfigurationFile.modalOpen"
|
||||||
|
:configurationFile="peerConfigurationFile.peerConfigData"
|
||||||
|
></PeerConfigurationFile>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
<PeerIntersectionObserver
|
||||||
|
:showPeersCount="this.showPeersCount"
|
||||||
|
:peerListLength="this.searchPeers.length"
|
||||||
|
@loadMore="this.showPeersCount += this.showPeersThreshold"></PeerIntersectionObserver>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.peerNav .nav-link{
|
||||||
|
&.active{
|
||||||
|
background-color: #efefef;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td{
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 576px) {
|
||||||
|
.titleBtn{
|
||||||
|
flex-basis: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
File diff suppressed because it is too large
Load Diff
@ -54,7 +54,6 @@ const fetchRealtimeTraffic = async () => {
|
|||||||
configurationName: route.params.id
|
configurationName: route.params.id
|
||||||
}, (res) => {
|
}, (res) => {
|
||||||
let timestamp = dayjs().format("hh:mm:ss A")
|
let timestamp = dayjs().format("hh:mm:ss A")
|
||||||
|
|
||||||
if (res.data.sent !== 0 && res.data.recv !== 0){
|
if (res.data.sent !== 0 && res.data.recv !== 0){
|
||||||
historySentData.value.timestamp.push(timestamp)
|
historySentData.value.timestamp.push(timestamp)
|
||||||
historySentData.value.data.push(res.data.sent)
|
historySentData.value.data.push(res.data.sent)
|
||||||
|
@ -1,440 +0,0 @@
|
|||||||
<script setup async>
|
|
||||||
import {computed, defineAsyncComponent, onBeforeUnmount, ref, watch} from "vue";
|
|
||||||
import {useRoute} from "vue-router";
|
|
||||||
import {fetchGet} from "@/utilities/fetch.js";
|
|
||||||
import ProtocolBadge from "@/components/protocolBadge.vue";
|
|
||||||
import LocaleText from "@/components/text/localeText.vue";
|
|
||||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
|
||||||
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
|
|
||||||
import PeerDataUsageCharts from "@/components/configurationComponents/peerListComponents/peerDataUsageCharts.vue";
|
|
||||||
import PeerSearch from "@/components/configurationComponents/peerSearch.vue";
|
|
||||||
import Peer from "@/components/configurationComponents/peer.vue";
|
|
||||||
import PeerListModals from "@/components/configurationComponents/peerListComponents/peerListModals.vue";
|
|
||||||
import PeerIntersectionObserver from "@/components/configurationComponents/peerIntersectionObserver.vue";
|
|
||||||
|
|
||||||
|
|
||||||
// Async Components
|
|
||||||
const PeerSearchBar = defineAsyncComponent(() => import("@/components/configurationComponents/peerSearchBar.vue"))
|
|
||||||
const PeerJobsAllModal = defineAsyncComponent(() => import("@/components/configurationComponents/peerJobsAllModal.vue"))
|
|
||||||
const PeerJobsLogsModal = defineAsyncComponent(() => import("@/components/configurationComponents/peerJobsLogsModal.vue"))
|
|
||||||
const EditConfigurationModal = defineAsyncComponent(() => import("@/components/configurationComponents/editConfiguration.vue"))
|
|
||||||
const SelectPeersModal = defineAsyncComponent(() => import("@/components/configurationComponents/selectPeers.vue"))
|
|
||||||
const PeerAddModal = defineAsyncComponent(() => import("@/components/configurationComponents/peerAddModal.vue"))
|
|
||||||
|
|
||||||
const dashboardStore = DashboardConfigurationStore()
|
|
||||||
const wireguardConfigurationStore = WireguardConfigurationsStore()
|
|
||||||
const route = useRoute()
|
|
||||||
const configurationInfo = ref({})
|
|
||||||
const configurationPeers = ref([])
|
|
||||||
const configurationToggling = ref(false)
|
|
||||||
const configurationModalSelectedPeer = ref({})
|
|
||||||
const configurationModals = ref({
|
|
||||||
peerNew: {
|
|
||||||
modalOpen: false
|
|
||||||
},
|
|
||||||
peerSetting: {
|
|
||||||
modalOpen: false,
|
|
||||||
},
|
|
||||||
peerScheduleJobs:{
|
|
||||||
modalOpen: false,
|
|
||||||
},
|
|
||||||
peerQRCode: {
|
|
||||||
modalOpen: false,
|
|
||||||
},
|
|
||||||
peerConfigurationFile: {
|
|
||||||
modalOpen: false,
|
|
||||||
},
|
|
||||||
peerCreate: {
|
|
||||||
modalOpen: false
|
|
||||||
},
|
|
||||||
peerScheduleJobsAll: {
|
|
||||||
modalOpen: false
|
|
||||||
},
|
|
||||||
peerScheduleJobsLogs: {
|
|
||||||
modalOpen: false
|
|
||||||
},
|
|
||||||
peerShare:{
|
|
||||||
modalOpen: false,
|
|
||||||
},
|
|
||||||
editConfiguration: {
|
|
||||||
modalOpen: false
|
|
||||||
},
|
|
||||||
selectPeers: {
|
|
||||||
modalOpen: false
|
|
||||||
},
|
|
||||||
backupRestore: {
|
|
||||||
modalOpen: false
|
|
||||||
},
|
|
||||||
deleteConfiguration: {
|
|
||||||
modalOpen: false
|
|
||||||
},
|
|
||||||
editRawConfigurationFile: {
|
|
||||||
modalOpen: false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const peerSearchBar = ref(false)
|
|
||||||
|
|
||||||
// Fetch Peer =====================================
|
|
||||||
const fetchPeerList = async () => {
|
|
||||||
await fetchGet("/api/getWireguardConfigurationInfo", {
|
|
||||||
configurationName: route.params.id
|
|
||||||
}, (res) => {
|
|
||||||
if (res.status){
|
|
||||||
configurationInfo.value = res.data.configurationInfo;
|
|
||||||
configurationPeers.value = res.data.configurationPeers;
|
|
||||||
|
|
||||||
configurationPeers.value.forEach(p => {
|
|
||||||
p.restricted = false
|
|
||||||
})
|
|
||||||
res.data.configurationRestrictedPeers.forEach(x => {
|
|
||||||
x.restricted = true;
|
|
||||||
configurationPeers.value.push(x)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
await fetchPeerList()
|
|
||||||
|
|
||||||
// Fetch Peer Interval =====================================
|
|
||||||
const fetchPeerListInterval = ref(undefined)
|
|
||||||
const setFetchPeerListInterval = () => {
|
|
||||||
clearInterval(fetchPeerListInterval.value)
|
|
||||||
fetchPeerListInterval.value = setInterval(async () => {
|
|
||||||
await fetchPeerList()
|
|
||||||
}, parseInt(dashboardStore.Configuration.Server.dashboard_refresh_interval))
|
|
||||||
}
|
|
||||||
setFetchPeerListInterval()
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
clearInterval(fetchPeerListInterval.value);
|
|
||||||
fetchPeerListInterval.value = undefined;
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(() => {
|
|
||||||
return dashboardStore.Configuration.Server.dashboard_refresh_interval
|
|
||||||
}, () => {
|
|
||||||
setFetchPeerListInterval()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Toggle Configuration Method =====================================
|
|
||||||
const toggleConfiguration = async () => {
|
|
||||||
configurationToggling.value = true;
|
|
||||||
await fetchGet("/api/toggleWireguardConfiguration/", {
|
|
||||||
configurationName: configurationInfo.value.Name
|
|
||||||
}, (res) => {
|
|
||||||
if (res.status){
|
|
||||||
dashboardStore.newMessage("Server",
|
|
||||||
`${configurationInfo.value.Name} ${res.data ? 'is on':'is off'}`, "success")
|
|
||||||
}else{
|
|
||||||
dashboardStore.newMessage("Server", res.message, 'danger')
|
|
||||||
}
|
|
||||||
wireguardConfigurationStore.Configurations
|
|
||||||
.find(x => x.Name === configurationInfo.value.Name).Status = res.data
|
|
||||||
configurationInfo.value.Status = res.data
|
|
||||||
configurationToggling.value = false;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configuration Summary =====================================
|
|
||||||
const configurationSummary = computed(() => {
|
|
||||||
return {
|
|
||||||
connectedPeers: configurationPeers.value.filter(x => x.status === "running").length,
|
|
||||||
totalUsage: configurationPeers.value.length > 0 ?
|
|
||||||
configurationPeers.value.filter(x => !x.restricted)
|
|
||||||
.map(x => x.total_data + x.cumu_data).reduce((a, b) => a + b, 0).toFixed(4) : 0,
|
|
||||||
totalReceive: configurationPeers.value.length > 0 ?
|
|
||||||
configurationPeers.value.filter(x => !x.restricted)
|
|
||||||
.map(x => x.total_receive + x.cumu_receive).reduce((a, b) => a + b, 0).toFixed(4) : 0,
|
|
||||||
totalSent: configurationPeers.value.length > 0 ?
|
|
||||||
configurationPeers.value.filter(x => !x.restricted)
|
|
||||||
.map(x => x.total_sent + x.cumu_sent).reduce((a, b) => a + b, 0).toFixed(4) : 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const showPeersCount = ref(10)
|
|
||||||
const showPeersThreshold = 20;
|
|
||||||
const searchPeers = computed(() => {
|
|
||||||
const result = wireguardConfigurationStore.searchString ?
|
|
||||||
configurationPeers.value.filter(x => {
|
|
||||||
return x.name.includes(wireguardConfigurationStore.searchString) ||
|
|
||||||
x.id.includes(wireguardConfigurationStore.searchString) ||
|
|
||||||
x.allowed_ip.includes(wireguardConfigurationStore.searchString)
|
|
||||||
}) : configurationPeers.value;
|
|
||||||
|
|
||||||
if (dashboardStore.Configuration.Server.dashboard_sort === "restricted"){
|
|
||||||
return result.sort((a, b) => {
|
|
||||||
if ( a[dashboardStore.Configuration.Server.dashboard_sort]
|
|
||||||
< b[dashboardStore.Configuration.Server.dashboard_sort] ){
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if ( a[dashboardStore.Configuration.Server.dashboard_sort]
|
|
||||||
> b[dashboardStore.Configuration.Server.dashboard_sort]){
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}).slice(0, showPeersCount.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.sort((a, b) => {
|
|
||||||
if ( a[dashboardStore.Configuration.Server.dashboard_sort]
|
|
||||||
< b[dashboardStore.Configuration.Server.dashboard_sort] ){
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if ( a[dashboardStore.Configuration.Server.dashboard_sort]
|
|
||||||
> b[dashboardStore.Configuration.Server.dashboard_sort]){
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}).slice(0, showPeersCount.value)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="container-fluid" >
|
|
||||||
<div class="d-flex align-items-sm-center flex-column flex-sm-row gap-3">
|
|
||||||
<div>
|
|
||||||
<div class="text-muted d-flex align-items-center gap-2">
|
|
||||||
<h5 class="mb-0">
|
|
||||||
<ProtocolBadge :protocol="configurationInfo.Protocol"></ProtocolBadge>
|
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex align-items-center gap-3">
|
|
||||||
<h1 class="mb-0 display-4"><samp>{{configurationInfo.Name}}</samp></h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ms-sm-auto d-flex gap-2 flex-column">
|
|
||||||
<div class="card rounded-3 bg-transparent ">
|
|
||||||
<div class="card-body py-2 d-flex align-items-center">
|
|
||||||
<small class="text-muted">
|
|
||||||
<LocaleText t="Status"></LocaleText>
|
|
||||||
</small>
|
|
||||||
<div class="dot ms-2" :class="{active: configurationInfo.Status}"></div>
|
|
||||||
<div class="form-check form-switch mb-0 ms-auto pe-0 me-0">
|
|
||||||
<label class="form-check-label" style="cursor: pointer" :for="'switch' + configurationInfo.id">
|
|
||||||
<LocaleText t="On" v-if="configurationInfo.Status && !configurationToggling"></LocaleText>
|
|
||||||
<LocaleText t="Off" v-else-if="!configurationInfo.Status && !configurationToggling"></LocaleText>
|
|
||||||
<span v-if="configurationToggling"
|
|
||||||
class="spinner-border spinner-border-sm ms-2" aria-hidden="true">
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<input class="form-check-input"
|
|
||||||
style="cursor: pointer"
|
|
||||||
:disabled="configurationToggling"
|
|
||||||
type="checkbox" role="switch" :id="'switch' + configurationInfo.id"
|
|
||||||
@change="toggleConfiguration()"
|
|
||||||
v-model="configurationInfo.Status">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex gap-2">
|
|
||||||
<a
|
|
||||||
role="button"
|
|
||||||
@click="configurationModals.peerNew.modalOpen = true"
|
|
||||||
class="titleBtn py-2 text-decoration-none btn text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle ">
|
|
||||||
<i class="bi bi-plus-circle me-2"></i>
|
|
||||||
<LocaleText t="Peer"></LocaleText>
|
|
||||||
</a>
|
|
||||||
<button class="titleBtn py-2 text-decoration-none btn text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle "
|
|
||||||
@click="configurationModals.editConfiguration.modalOpen = true"
|
|
||||||
type="button" aria-expanded="false">
|
|
||||||
<i class="bi bi-gear-fill me-2"></i>
|
|
||||||
<LocaleText t="Configuration Settings"></LocaleText>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="row mt-3 gy-2 gx-2 mb-2">
|
|
||||||
<div class="col-12 col-lg-3">
|
|
||||||
<div class="card rounded-3 bg-transparent h-100">
|
|
||||||
<div class="card-body py-2 d-flex flex-column justify-content-center">
|
|
||||||
<p class="mb-0 text-muted"><small>
|
|
||||||
<LocaleText t="Address"></LocaleText>
|
|
||||||
</small></p>
|
|
||||||
{{configurationInfo.Address}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-lg-3">
|
|
||||||
<div class="card rounded-3 bg-transparent h-100">
|
|
||||||
<div class="card-body py-2 d-flex flex-column justify-content-center">
|
|
||||||
<p class="mb-0 text-muted"><small>
|
|
||||||
<LocaleText t="Listen Port"></LocaleText>
|
|
||||||
</small></p>
|
|
||||||
{{configurationInfo.ListenPort}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="word-break: break-all" class="col-12 col-lg-6">
|
|
||||||
<div class="card rounded-3 bg-transparent h-100">
|
|
||||||
<div class="card-body py-2 d-flex flex-column justify-content-center">
|
|
||||||
<p class="mb-0 text-muted"><small>
|
|
||||||
<LocaleText t="Public Key"></LocaleText>
|
|
||||||
</small></p>
|
|
||||||
<samp>{{configurationInfo.PublicKey}}</samp>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row gx-2 gy-2 mb-2">
|
|
||||||
<div class="col-12 col-lg-3">
|
|
||||||
<div class="card rounded-3 bg-transparent h-100">
|
|
||||||
<div class="card-body d-flex">
|
|
||||||
<div>
|
|
||||||
<p class="mb-0 text-muted"><small>
|
|
||||||
<LocaleText t="Connected Peers"></LocaleText>
|
|
||||||
</small></p>
|
|
||||||
<strong class="h4">
|
|
||||||
{{configurationSummary.connectedPeers}} / {{configurationPeers.length}}
|
|
||||||
</strong>
|
|
||||||
</div>
|
|
||||||
<i class="bi bi-ethernet ms-auto h2 text-muted"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-lg-3">
|
|
||||||
<div class="card rounded-3 bg-transparent h-100">
|
|
||||||
<div class="card-body d-flex">
|
|
||||||
<div>
|
|
||||||
<p class="mb-0 text-muted"><small>
|
|
||||||
<LocaleText t="Total Usage"></LocaleText>
|
|
||||||
</small></p>
|
|
||||||
<strong class="h4">{{configurationSummary.totalUsage}} GB</strong>
|
|
||||||
</div>
|
|
||||||
<i class="bi bi-arrow-down-up ms-auto h2 text-muted"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-lg-3">
|
|
||||||
<div class="card rounded-3 bg-transparent h-100">
|
|
||||||
<div class="card-body d-flex">
|
|
||||||
<div>
|
|
||||||
<p class="mb-0 text-muted"><small>
|
|
||||||
<LocaleText t="Total Received"></LocaleText>
|
|
||||||
</small></p>
|
|
||||||
<strong class="h4 text-primary">{{configurationSummary.totalReceive}} GB</strong>
|
|
||||||
</div>
|
|
||||||
<i class="bi bi-arrow-down ms-auto h2 text-muted"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-lg-3">
|
|
||||||
<div class="card rounded-3 bg-transparent h-100">
|
|
||||||
<div class="card-body d-flex">
|
|
||||||
<div>
|
|
||||||
<p class="mb-0 text-muted"><small>
|
|
||||||
<LocaleText t="Total Sent"></LocaleText>
|
|
||||||
</small></p>
|
|
||||||
<strong class="h4 text-success">{{configurationSummary.totalSent}} GB</strong>
|
|
||||||
</div>
|
|
||||||
<i class="bi bi-arrow-up ms-auto h2 text-muted"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<PeerDataUsageCharts
|
|
||||||
:configurationPeers="configurationPeers"
|
|
||||||
:configurationInfo="configurationInfo"
|
|
||||||
></PeerDataUsageCharts>
|
|
||||||
<hr>
|
|
||||||
<div style="margin-bottom: 80px">
|
|
||||||
<PeerSearch
|
|
||||||
v-if="configurationPeers.length > 0"
|
|
||||||
@search="peerSearchBar = true"
|
|
||||||
@jobsAll="configurationModals.peerScheduleJobsAll.modalOpen = true"
|
|
||||||
@jobLogs="configurationModals.peerScheduleJobsLogs.modalOpen = true"
|
|
||||||
@editConfiguration="configurationModals.editConfiguration.modalOpen = true"
|
|
||||||
@selectPeers="configurationModals.selectPeers.modalOpen = true"
|
|
||||||
@backupRestore="configurationModals.backupRestore.modalOpen = true"
|
|
||||||
@deleteConfiguration="configurationModals.deleteConfiguration.modalOpen = true"
|
|
||||||
:configuration="configurationInfo">
|
|
||||||
</PeerSearch>
|
|
||||||
<TransitionGroup name="peerList" tag="div" class="row gx-2 gy-2 z-0 position-relative">
|
|
||||||
<div class="col-12 col-lg-6 col-xl-4"
|
|
||||||
:key="peer.id"
|
|
||||||
v-for="peer in searchPeers">
|
|
||||||
<Peer :Peer="peer"
|
|
||||||
@share="configurationModals.peerShare.modalOpen = true; configurationModalSelectedPeer = peer"
|
|
||||||
@refresh="fetchPeerList()"
|
|
||||||
@jobs="configurationModals.peerScheduleJobs.modalOpen = true; configurationModalSelectedPeer = peer"
|
|
||||||
@setting="configurationModals.peerSetting.modalOpen = true; configurationModalSelectedPeer = peer"
|
|
||||||
@qrcode="configurationModalSelectedPeer = peer; configurationModals.peerQRCode.modalOpen = true;"
|
|
||||||
@configurationFile="configurationModalSelectedPeer = peer; configurationModals.peerConfigurationFile.modalOpen = true;"
|
|
||||||
></Peer>
|
|
||||||
</div>
|
|
||||||
</TransitionGroup>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<Transition name="slideUp">
|
|
||||||
<PeerSearchBar @close="peerSearchBar = false" v-if="peerSearchBar"></PeerSearchBar>
|
|
||||||
</Transition>
|
|
||||||
<PeerListModals
|
|
||||||
:configurationModals="configurationModals"
|
|
||||||
:configurationModalSelectedPeer="configurationModalSelectedPeer"
|
|
||||||
@refresh="fetchPeerList()"
|
|
||||||
></PeerListModals>
|
|
||||||
<TransitionGroup name="zoom">
|
|
||||||
<Suspense key="PeerAddModal">
|
|
||||||
<PeerAddModal
|
|
||||||
v-if="configurationModals.peerNew.modalOpen"
|
|
||||||
@close="configurationModals.peerNew.modalOpen = false"
|
|
||||||
@addedPeers="configurationModals.peerNew.modalOpen = false; fetchPeerList()"
|
|
||||||
></PeerAddModal>
|
|
||||||
</Suspense>
|
|
||||||
<PeerJobsAllModal
|
|
||||||
key="PeerJobsAllModal"
|
|
||||||
v-if="configurationModals.peerScheduleJobsAll.modalOpen"
|
|
||||||
@refresh="fetchPeerList()"
|
|
||||||
@allLogs="configurationModals.peerScheduleJobsLogs.modalOpen = true"
|
|
||||||
@close="configurationModals.peerScheduleJobsAll.modalOpen = false"
|
|
||||||
:configurationPeers="configurationPeers"
|
|
||||||
>
|
|
||||||
</PeerJobsAllModal>
|
|
||||||
<PeerJobsLogsModal
|
|
||||||
key="PeerJobsLogsModal"
|
|
||||||
v-if="configurationModals.peerScheduleJobsLogs.modalOpen"
|
|
||||||
@close="configurationModals.peerScheduleJobsLogs.modalOpen = false"
|
|
||||||
:configurationInfo="configurationInfo">
|
|
||||||
</PeerJobsLogsModal>
|
|
||||||
<EditConfigurationModal
|
|
||||||
key="EditConfigurationModal"
|
|
||||||
@editRaw="configurationModals.editRawConfigurationFile.modalOpen = true"
|
|
||||||
@close="configurationModals.editConfiguration.modalOpen = false"
|
|
||||||
@dataChanged="(d) => configurationInfo = d"
|
|
||||||
@refresh="fetchPeerList()"
|
|
||||||
@backupRestore="configurationModals.backupRestore.modalOpen = true"
|
|
||||||
@deleteConfiguration="configurationModals.deleteConfiguration.modalOpen = true"
|
|
||||||
:configurationInfo="configurationInfo"
|
|
||||||
v-if="configurationModals.editConfiguration.modalOpen">
|
|
||||||
</EditConfigurationModal>
|
|
||||||
<SelectPeersModal
|
|
||||||
@refresh="fetchPeerList()"
|
|
||||||
v-if="configurationModals.selectPeers.modalOpen"
|
|
||||||
:configurationPeers="configurationPeers"
|
|
||||||
@close="configurationModals.selectPeers.modalOpen = false"
|
|
||||||
></SelectPeersModal>
|
|
||||||
</TransitionGroup>
|
|
||||||
<PeerIntersectionObserver
|
|
||||||
:showPeersCount="showPeersCount"
|
|
||||||
:peerListLength="searchPeers.length"
|
|
||||||
@loadMore="showPeersCount += showPeersThreshold"></PeerIntersectionObserver>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.peerNav .nav-link{
|
|
||||||
&.active{
|
|
||||||
background-color: #efefef;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
th, td{
|
|
||||||
background-color: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 576px) {
|
|
||||||
.titleBtn{
|
|
||||||
flex-basis: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -91,13 +91,8 @@ const router = createRouter({
|
|||||||
{
|
{
|
||||||
name: "Peers List",
|
name: "Peers List",
|
||||||
path: 'peers',
|
path: 'peers',
|
||||||
component: () => import('@/components/configurationComponents/peerListNew.vue')
|
component: () => import('@/components/configurationComponents/peerList.vue')
|
||||||
},
|
}
|
||||||
{
|
|
||||||
name: "Peers Create",
|
|
||||||
path: 'create',
|
|
||||||
component: () => import('@/components/configurationComponents/peerCreate.vue')
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -28,21 +28,23 @@ export const fetchGet = async (url, params=undefined, callback=undefined) => {
|
|||||||
await fetch(`${getUrl(url)}?${urlSearchParams.toString()}`, {
|
await fetch(`${getUrl(url)}?${urlSearchParams.toString()}`, {
|
||||||
headers: getHeaders()
|
headers: getHeaders()
|
||||||
})
|
})
|
||||||
.then((x) => {
|
.then((x) => {
|
||||||
const store = DashboardConfigurationStore();
|
const store = DashboardConfigurationStore();
|
||||||
if (!x.ok){
|
if (!x.ok){
|
||||||
if (x.status !== 200){
|
if (x.status !== 200){
|
||||||
if (x.status === 401){
|
if (x.status === 401){
|
||||||
store.newMessage("WGDashboard", "Sign in session ended, please sign in again", "warning")
|
store.newMessage("WGDashboard", "Sign in session ended, please sign in again", "warning")
|
||||||
|
}
|
||||||
|
throw new Error(x.statusText)
|
||||||
}
|
}
|
||||||
throw new Error(x.statusText)
|
}else{
|
||||||
|
return x.json()
|
||||||
}
|
}
|
||||||
}else{
|
})
|
||||||
return x.json()
|
.then(x => callback ? callback(x) : undefined).catch(x => {
|
||||||
}
|
console.log("Error:", x)
|
||||||
}).then(x => callback ? callback(x) : undefined).catch(x => {
|
// store.newMessage("WGDashboard", `Error: ${x}`, "danger")
|
||||||
console.log(x)
|
router.push({path: '/signin'})
|
||||||
router.push({path: '/signin'})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +66,8 @@ export const fetchPost = async (url, body, callback) => {
|
|||||||
return x.json()
|
return x.json()
|
||||||
}
|
}
|
||||||
}).then(x => callback ? callback(x) : undefined).catch(x => {
|
}).then(x => callback ? callback(x) : undefined).catch(x => {
|
||||||
console.log(x)
|
console.log("Error:", x)
|
||||||
|
// store.newMessage("WGDashboard", `Error: ${x}`, "danger")
|
||||||
router.push({path: '/signin'})
|
router.push({path: '/signin'})
|
||||||
})
|
})
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user