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
130
src/dashboard.py
130
src/dashboard.py
@ -10,6 +10,7 @@ from json import JSONEncoder
|
||||
from flask_cors import CORS
|
||||
from icmplib import ping, traceroute
|
||||
from flask.json.provider import DefaultJSONProvider
|
||||
from itertools import islice
|
||||
from Utilities import (
|
||||
RegexMatch, GetRemoteEndpoint, StringToBoolean,
|
||||
ValidateIPAddressesWithRange, ValidateDNSAddress,
|
||||
@ -1107,41 +1108,69 @@ class WireguardConfiguration:
|
||||
return False, str(e)
|
||||
return True, None
|
||||
|
||||
def getAvailableIP(self, all: bool = False) -> tuple[bool, list[str]] | tuple[bool, None]:
|
||||
def getNumberOfAvailableIP(self):
|
||||
if len(self.Address) < 0:
|
||||
return False, None
|
||||
address = self.Address.split(',')
|
||||
existedAddress = []
|
||||
availableAddress = []
|
||||
for p in self.Peers:
|
||||
if len(p.allowed_ip) > 0:
|
||||
add = p.allowed_ip.split(',')
|
||||
for i in add:
|
||||
a, c = i.split('/')
|
||||
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:
|
||||
existedAddress.append(ipaddress.ip_address(a.replace(" ", "")))
|
||||
except ValueError as error:
|
||||
check = ipaddress.ip_network(ppip[0])
|
||||
existedAddress.add(check)
|
||||
except Exception as e:
|
||||
print(f"[WGDashboard] Error: {self.Name} peer {p.id} have invalid ip")
|
||||
for p in self.getRestrictedPeersList():
|
||||
if len(p.allowed_ip) > 0:
|
||||
add = p.allowed_ip.split(',')
|
||||
for i in add:
|
||||
a, c = i.split('/')
|
||||
existedAddress.append(ipaddress.ip_address(a.replace(" ", "")))
|
||||
for i in address:
|
||||
addressSplit, cidr = i.split('/')
|
||||
existedAddress.append(ipaddress.ip_address(addressSplit.replace(" ", "")))
|
||||
for i in address:
|
||||
network = ipaddress.ip_network(i.replace(" ", ""), False)
|
||||
count = 0
|
||||
for h in network.hosts():
|
||||
if h not in existedAddress:
|
||||
availableAddress.append(ipaddress.ip_network(h).compressed)
|
||||
count += 1
|
||||
if not all:
|
||||
if network.version == 6 and count > 255:
|
||||
break
|
||||
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]))
|
||||
availableAddress[ca] = network.num_addresses
|
||||
for p in existedAddress:
|
||||
if p.subnet_of(network):
|
||||
availableAddress[ca] -= 1
|
||||
|
||||
# map(lambda iph : ipaddress.ip_network(iph).compressed, network.hosts())
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print(f"[WGDashboard] Error: Failed to parse IP address {ca} from {self.Name}")
|
||||
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
|
||||
|
||||
def getRealtimeTrafficUsage(self):
|
||||
@ -2491,7 +2520,7 @@ def API_addPeers(configName):
|
||||
|
||||
public_key: str = data.get('public_key', "")
|
||||
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])
|
||||
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:
|
||||
endpoint_allowed_ip = DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[1]
|
||||
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():
|
||||
config.toggleConfiguration()
|
||||
availableIps = config.getAvailableIP()
|
||||
ipStatus, availableIps = config.getAvailableIP()
|
||||
defaultIPSubnet = list(availableIps.keys())[0]
|
||||
if bulkAdd:
|
||||
if type(preshared_key_bulkAdd) is not bool:
|
||||
preshared_key_bulkAdd = False
|
||||
if type(bulkAddAmount) is not int or bulkAddAmount < 1:
|
||||
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")
|
||||
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,
|
||||
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 = []
|
||||
for i in range(bulkAddAmount):
|
||||
newPrivateKey = GenerateWireguardPrivateKey()[1]
|
||||
@ -2530,7 +2562,7 @@ def API_addPeers(configName):
|
||||
"private_key": newPrivateKey,
|
||||
"id": GenerateWireguardPublicKey(newPrivateKey)[1],
|
||||
"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')}",
|
||||
"DNS": dns_addresses,
|
||||
"endpoint_allowed_ip": endpoint_allowed_ip,
|
||||
@ -2554,14 +2586,19 @@ def API_addPeers(configName):
|
||||
public_key = GenerateWireguardPublicKey(private_key)[1]
|
||||
|
||||
if len(allowed_ips) == 0:
|
||||
if availableIps[0]:
|
||||
allowed_ips = [availableIps[1][0]]
|
||||
if ipStatus:
|
||||
allowed_ips = [availableIps[defaultIPSubnet][0]]
|
||||
else:
|
||||
return ResponseObject(False, "No more available IP can assign")
|
||||
|
||||
if not override_allowed_ips:
|
||||
if allowed_ips_validation:
|
||||
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}")
|
||||
|
||||
status, result = config.addPeers([
|
||||
@ -2580,7 +2617,7 @@ def API_addPeers(configName):
|
||||
)
|
||||
return ResponseObject(status=status, message=result['message'], data=result['peers'])
|
||||
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, "Configuration does not exist")
|
||||
@ -2618,6 +2655,13 @@ def API_getAvailableIPs(configName):
|
||||
status, ips = WireguardConfigurations.get(configName).getAvailableIP()
|
||||
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')
|
||||
def API_getConfigurationInfo():
|
||||
configurationName = request.args.get("configurationName")
|
||||
|
@ -4,6 +4,7 @@ import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStor
|
||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||
import LocaleText from "@/components/text/localeText.vue";
|
||||
import {GetLocale} from "@/utilities/locale.js";
|
||||
import {ref} from "vue";
|
||||
|
||||
export default {
|
||||
name: "allowedIPsInput",
|
||||
@ -22,17 +23,23 @@ export default {
|
||||
allowedIpFormatError: false
|
||||
}
|
||||
},
|
||||
setup(){
|
||||
setup(props){
|
||||
const store = WireguardConfigurationsStore();
|
||||
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: {
|
||||
searchAvailableIps(){
|
||||
return this.availableIpSearchString ?
|
||||
this.availableIp.filter(x =>
|
||||
return (this.availableIpSearchString ?
|
||||
this.availableIp[this.selectedSubnet].filter(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(){
|
||||
return GetLocale("Enter IP Address/CIDR")
|
||||
@ -59,14 +66,16 @@ export default {
|
||||
watch: {
|
||||
customAvailableIp(){
|
||||
this.allowedIpFormatError = false;
|
||||
},
|
||||
availableIp(){
|
||||
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.availableIp !== undefined && this.availableIp.length > 0 && this.data.allowed_ips.length === 0){
|
||||
this.addAllowedIp(this.availableIp[0])
|
||||
if (this.availableIp !== undefined &&
|
||||
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>
|
||||
<div :class="{inactiveField: this.bulk}">
|
||||
<div class="d-flex">
|
||||
<label for="peer_allowed_ip_textbox" class="form-label">
|
||||
<div class="d-flex flex-column flex-md-row mb-2">
|
||||
<label for="peer_allowed_ip_textbox" class="form-label mb-0">
|
||||
<small class="text-muted">
|
||||
<LocaleText t="Allowed IPs"></LocaleText> <code><LocaleText t="(Required)"></LocaleText></code>
|
||||
</small>
|
||||
</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"
|
||||
v-model="this.data.override_allowed_ips"
|
||||
v-model="this.data.allowed_ips_validation"
|
||||
role="switch" id="disableIPValidation">
|
||||
<label class="form-check-label" for="disableIPValidation">
|
||||
<small>
|
||||
<LocaleText t="Disable Allowed IPs Validation"></LocaleText>
|
||||
<LocaleText t="Allowed IPs Validation"></LocaleText>
|
||||
</small>
|
||||
</label>
|
||||
</div>
|
||||
@ -107,6 +116,7 @@ export default {
|
||||
<input type="text" class="form-control form-control-sm rounded-start-3"
|
||||
:placeholder="this.inputGetLocale"
|
||||
:class="{'is-invalid': this.allowedIpFormatError}"
|
||||
@keyup.enter="this.customAvailableIp ? this.addAllowedIp(this.customAvailableIp) : undefined"
|
||||
v-model="customAvailableIp"
|
||||
id="peer_allowed_ip_textbox"
|
||||
:disabled="bulk">
|
||||
@ -129,11 +139,11 @@ export default {
|
||||
<i class="bi bi-filter-circle me-2"></i>
|
||||
<LocaleText t="Pick Available IP"></LocaleText>
|
||||
</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"
|
||||
style="overflow-y: scroll; max-height: 270px; width: 300px !important;">
|
||||
style="width: 300px !important;">
|
||||
<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">
|
||||
<i class="bi bi-search"></i>
|
||||
</label>
|
||||
@ -142,16 +152,32 @@ export default {
|
||||
class="form-control form-control-sm rounded-3"
|
||||
v-model="this.availableIpSearchString">
|
||||
</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 v-for="ip in this.searchAvailableIps" >
|
||||
<li>
|
||||
<div class="overflow-y-scroll" style="height: 270px;">
|
||||
<div v-for="ip in this.searchAvailableIps" style="">
|
||||
<a class="dropdown-item d-flex" role="button" @click="this.addAllowedIp(ip)">
|
||||
<span class="me-auto"><small>{{ip}}</small></span>
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="this.searchAvailableIps.length === 0">
|
||||
</div>
|
||||
<div v-if="this.searchAvailableIps.length === 0">
|
||||
<small class="px-3 text-muted">
|
||||
<LocaleText t="No available IP containing"></LocaleText>
|
||||
"{{this.availableIpSearchString}}"</small>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -1,6 +1,8 @@
|
||||
<script>
|
||||
import LocaleText from "@/components/text/localeText.vue";
|
||||
import {GetLocale} from "@/utilities/locale.js";
|
||||
import {useRoute} from "vue-router";
|
||||
import {fetchGet} from "@/utilities/fetch.js";
|
||||
|
||||
export default {
|
||||
name: "bulkAdd",
|
||||
@ -10,9 +12,37 @@ export default {
|
||||
data: Object,
|
||||
availableIp: undefined
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
numberOfAvailableIPs: null
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
bulkAddGetLocale(){
|
||||
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"
|
||||
:placeholder="this.bulkAddGetLocale">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -80,10 +80,7 @@ export default {
|
||||
<div>
|
||||
<label for="peer_private_key_textbox" class="form-label">
|
||||
<small class="text-muted">
|
||||
<LocaleText t="Private Key"></LocaleText>
|
||||
<code>
|
||||
<LocaleText t="(Required for QR Code and Download)"></LocaleText>
|
||||
</code></small>
|
||||
<LocaleText t="Private Key"></LocaleText> <code><LocaleText t="(Required for QR Code and Download)"></LocaleText></code></small>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control form-control-sm rounded-start-3"
|
||||
@ -100,15 +97,14 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex">
|
||||
<label for="public_key" class="form-label">
|
||||
<div class="d-flex flex-column flex-md-row mb-2">
|
||||
<label for="public_key" class="form-label mb-0">
|
||||
<small class="text-muted">
|
||||
<LocaleText t="Public Key"></LocaleText>
|
||||
<code>
|
||||
<LocaleText t="Public Key"></LocaleText> <code>
|
||||
<LocaleText t="(Required)"></LocaleText>
|
||||
</code></small>
|
||||
</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"
|
||||
:disabled="this.bulk"
|
||||
id="enablePublicKeyEdit" v-model="this.editKey">
|
||||
|
@ -32,7 +32,7 @@ const peerData = ref({
|
||||
preshared_key: "",
|
||||
preshared_key_bulkAdd: false,
|
||||
advanced_security: "off",
|
||||
override_allowed_ips: false,
|
||||
allowed_ips_validation: true,
|
||||
})
|
||||
const availableIp = ref([])
|
||||
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>
|
@ -1,138 +1,48 @@
|
||||
<script>
|
||||
import PeerSearch from "@/components/configurationComponents/peerSearch.vue";
|
||||
<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 {fetchGet} from "@/utilities/fetch.js";
|
||||
import PeerDataUsageCharts from "@/components/configurationComponents/peerListComponents/peerDataUsageCharts.vue";
|
||||
import PeerSearch from "@/components/configurationComponents/peerSearch.vue";
|
||||
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 PeerListModals from "@/components/configurationComponents/peerListComponents/peerListModals.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
|
||||
},
|
||||
],
|
||||
|
||||
|
||||
// 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,
|
||||
selectedPeer: undefined
|
||||
},
|
||||
peerScheduleJobs:{
|
||||
modalOpen: false,
|
||||
selectedPeer: undefined
|
||||
},
|
||||
peerQRCode: {
|
||||
modalOpen: false,
|
||||
peerConfigData: undefined
|
||||
},
|
||||
peerConfigurationFile: {
|
||||
modalOpen: false,
|
||||
peerConfigData: undefined
|
||||
},
|
||||
peerCreate: {
|
||||
modalOpen: false
|
||||
@ -145,7 +55,6 @@ export default {
|
||||
},
|
||||
peerShare:{
|
||||
modalOpen: false,
|
||||
selectedPeer: undefined
|
||||
},
|
||||
editConfiguration: {
|
||||
modalOpen: false
|
||||
@ -161,292 +70,135 @@ export default {
|
||||
},
|
||||
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
|
||||
})
|
||||
const peerSearchBar = ref(false)
|
||||
|
||||
// Fetch Peer =====================================
|
||||
const fetchPeerList = async () => {
|
||||
await fetchGet("/api/getWireguardConfigurationInfo", {
|
||||
configurationName: route.params.id
|
||||
}, (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)
|
||||
configurationInfo.value = res.data.configurationInfo;
|
||||
configurationPeers.value = res.data.configurationPeers;
|
||||
|
||||
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;
|
||||
configurationPeers.value.forEach(p => {
|
||||
p.restricted = false
|
||||
})
|
||||
res.data.configurationRestrictedPeers.forEach(x => {
|
||||
x.restricted = true;
|
||||
this.configurationPeers.push(x)
|
||||
configurationPeers.value.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)
|
||||
}
|
||||
}
|
||||
});
|
||||
await fetchPeerList()
|
||||
|
||||
},
|
||||
setPeerInterval(){
|
||||
this.dashboardConfigurationStore.Peers.RefreshInterval = setInterval(() => {
|
||||
this.getPeers()
|
||||
}, parseInt(this.dashboardConfigurationStore.Configuration.Server.dashboard_refresh_interval))
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
configurationSummary(){
|
||||
// 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: this.configurationPeers.filter(x => x.status === "running").length,
|
||||
totalUsage: this.configurationPeers.length > 0 ?
|
||||
this.configurationPeers.filter(x => !x.restricted)
|
||||
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: this.configurationPeers.length > 0 ?
|
||||
this.configurationPeers.filter(x => !x.restricted)
|
||||
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: this.configurationPeers.length > 0 ?
|
||||
this.configurationPeers.filter(x => !x.restricted)
|
||||
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
|
||||
}
|
||||
},
|
||||
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;
|
||||
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 (this.dashboardConfigurationStore.Configuration.Server.dashboard_sort === "restricted"){
|
||||
if (dashboardStore.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] ){
|
||||
if ( a[dashboardStore.Configuration.Server.dashboard_sort]
|
||||
< b[dashboardStore.Configuration.Server.dashboard_sort] ){
|
||||
return 1;
|
||||
}
|
||||
if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]
|
||||
> b[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]){
|
||||
if ( a[dashboardStore.Configuration.Server.dashboard_sort]
|
||||
> b[dashboardStore.Configuration.Server.dashboard_sort]){
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}).slice(0, this.showPeersCount);
|
||||
}).slice(0, showPeersCount.value);
|
||||
}
|
||||
|
||||
return result.sort((a, b) => {
|
||||
if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]
|
||||
< b[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort] ){
|
||||
if ( a[dashboardStore.Configuration.Server.dashboard_sort]
|
||||
< b[dashboardStore.Configuration.Server.dashboard_sort] ){
|
||||
return -1;
|
||||
}
|
||||
if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]
|
||||
> b[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]){
|
||||
if ( a[dashboardStore.Configuration.Server.dashboard_sort]
|
||||
> b[dashboardStore.Configuration.Server.dashboard_sort]){
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}).slice(0, this.showPeersCount)
|
||||
},
|
||||
}
|
||||
}
|
||||
}).slice(0, showPeersCount.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="!this.loading" class="container-md" id="peerList">
|
||||
<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="this.configurationInfo.Protocol"></ProtocolBadge>
|
||||
<ProtocolBadge :protocol="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>
|
||||
<h1 class="mb-0 display-4"><samp>{{configurationInfo.Name}}</samp></h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ms-sm-auto d-flex gap-2 flex-column">
|
||||
@ -455,35 +207,35 @@ export default {
|
||||
<small class="text-muted">
|
||||
<LocaleText t="Status"></LocaleText>
|
||||
</small>
|
||||
<div class="dot ms-2" :class="{active: this.configurationInfo.Status}"></div>
|
||||
<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' + 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"
|
||||
<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="this.configurationToggling"
|
||||
type="checkbox" role="switch" :id="'switch' + this.configurationInfo.id"
|
||||
@change="this.toggle()"
|
||||
v-model="this.configurationInfo.Status"
|
||||
>
|
||||
: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">
|
||||
<RouterLink
|
||||
to="create"
|
||||
<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-lg me-2"></i>
|
||||
<i class="bi bi-plus-circle me-2"></i>
|
||||
<LocaleText t="Peer"></LocaleText>
|
||||
</RouterLink>
|
||||
</a>
|
||||
<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"
|
||||
@click="configurationModals.editConfiguration.modalOpen = true"
|
||||
type="button" aria-expanded="false">
|
||||
<i class="bi bi-gear-fill me-2"></i>
|
||||
<LocaleText t="Configuration Settings"></LocaleText>
|
||||
@ -499,7 +251,7 @@ export default {
|
||||
<p class="mb-0 text-muted"><small>
|
||||
<LocaleText t="Address"></LocaleText>
|
||||
</small></p>
|
||||
{{this.configurationInfo.Address}}
|
||||
{{configurationInfo.Address}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -509,18 +261,17 @@ export default {
|
||||
<p class="mb-0 text-muted"><small>
|
||||
<LocaleText t="Listen Port"></LocaleText>
|
||||
</small></p>
|
||||
{{this.configurationInfo.ListenPort}}
|
||||
{{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>
|
||||
<samp>{{configurationInfo.PublicKey}}</samp>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -581,177 +332,92 @@ export default {
|
||||
</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>
|
||||
<PeerDataUsageCharts
|
||||
:configurationPeers="configurationPeers"
|
||||
:configurationInfo="configurationInfo"
|
||||
></PeerDataUsageCharts>
|
||||
<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>
|
||||
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 this.searchPeers">
|
||||
v-for="peer in 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;}"
|
||||
@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="peerSearchBarShow = false"
|
||||
v-if="this.peerSearchBarShow"></PeerSearchBar>
|
||||
<PeerSearchBar @close="peerSearchBar = false" v-if="peerSearchBar"></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">
|
||||
<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
|
||||
v-if="this.peerScheduleJobsAll.modalOpen"
|
||||
@refresh="this.getPeers()"
|
||||
@allLogs="peerScheduleJobsLogs.modalOpen = true"
|
||||
@close="this.peerScheduleJobsAll.modalOpen = false"
|
||||
:configurationPeers="this.configurationPeers"
|
||||
key="PeerJobsAllModal"
|
||||
v-if="configurationModals.peerScheduleJobsAll.modalOpen"
|
||||
@refresh="fetchPeerList()"
|
||||
@allLogs="configurationModals.peerScheduleJobsLogs.modalOpen = true"
|
||||
@close="configurationModals.peerScheduleJobsAll.modalOpen = false"
|
||||
:configurationPeers="configurationPeers"
|
||||
>
|
||||
</PeerJobsAllModal>
|
||||
</Transition>
|
||||
<Transition name="zoom">
|
||||
<PeerJobsLogsModal v-if="this.peerScheduleJobsLogs.modalOpen"
|
||||
|
||||
@close="this.peerScheduleJobsLogs.modalOpen = false"
|
||||
:configurationInfo="this.configurationInfo"
|
||||
>
|
||||
<PeerJobsLogsModal
|
||||
key="PeerJobsLogsModal"
|
||||
v-if="configurationModals.peerScheduleJobsLogs.modalOpen"
|
||||
@close="configurationModals.peerScheduleJobsLogs.modalOpen = false"
|
||||
:configurationInfo="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>
|
||||
<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="this.showPeersCount"
|
||||
:peerListLength="this.searchPeers.length"
|
||||
@loadMore="this.showPeersCount += this.showPeersThreshold"></PeerIntersectionObserver>
|
||||
:showPeersCount="showPeersCount"
|
||||
:peerListLength="searchPeers.length"
|
||||
@loadMore="showPeersCount += showPeersThreshold"></PeerIntersectionObserver>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -771,6 +437,4 @@ th, td{
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
@ -54,7 +54,6 @@ const fetchRealtimeTraffic = async () => {
|
||||
configurationName: route.params.id
|
||||
}, (res) => {
|
||||
let timestamp = dayjs().format("hh:mm:ss A")
|
||||
|
||||
if (res.data.sent !== 0 && res.data.recv !== 0){
|
||||
historySentData.value.timestamp.push(timestamp)
|
||||
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",
|
||||
path: 'peers',
|
||||
component: () => import('@/components/configurationComponents/peerListNew.vue')
|
||||
},
|
||||
{
|
||||
name: "Peers Create",
|
||||
path: 'create',
|
||||
component: () => import('@/components/configurationComponents/peerCreate.vue')
|
||||
},
|
||||
component: () => import('@/components/configurationComponents/peerList.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
@ -40,8 +40,10 @@ export const fetchGet = async (url, params=undefined, callback=undefined) => {
|
||||
}else{
|
||||
return x.json()
|
||||
}
|
||||
}).then(x => callback ? callback(x) : undefined).catch(x => {
|
||||
console.log(x)
|
||||
})
|
||||
.then(x => callback ? callback(x) : undefined).catch(x => {
|
||||
console.log("Error:", x)
|
||||
// store.newMessage("WGDashboard", `Error: ${x}`, "danger")
|
||||
router.push({path: '/signin'})
|
||||
})
|
||||
}
|
||||
@ -64,7 +66,8 @@ export const fetchPost = async (url, body, callback) => {
|
||||
return x.json()
|
||||
}
|
||||
}).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'})
|
||||
})
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user