Updated how available IP is generated

This commit is contained in:
Donald Zou 2025-02-16 17:42:32 +08:00
parent f055241802
commit 6f15389411
11 changed files with 1382 additions and 1287 deletions

View File

@ -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")

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -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)

View File

@ -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>

View File

@ -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')
}
]
},

View File

@ -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'})
})
}