mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2024-11-06 16:00:28 +01:00
Made some progress ;)
This commit is contained in:
parent
41e05ddf9c
commit
c7ca20b45a
@ -35,9 +35,6 @@ from icmplib import ping, traceroute
|
|||||||
# Import other python files
|
# Import other python files
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from sqlalchemy.orm import mapped_column, declarative_base, Session
|
|
||||||
from sqlalchemy import FLOAT, INT, VARCHAR, select, MetaData, DATETIME
|
|
||||||
from sqlalchemy import create_engine, inspect
|
|
||||||
from flask.json.provider import DefaultJSONProvider
|
from flask.json.provider import DefaultJSONProvider
|
||||||
|
|
||||||
DASHBOARD_VERSION = 'v4.0'
|
DASHBOARD_VERSION = 'v4.0'
|
||||||
@ -141,7 +138,6 @@ class WireguardConfiguration:
|
|||||||
|
|
||||||
self.Status = self.getStatus()
|
self.Status = self.getStatus()
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.Name = data["ConfigurationName"]
|
self.Name = data["ConfigurationName"]
|
||||||
for i in dir(self):
|
for i in dir(self):
|
||||||
@ -168,7 +164,7 @@ class WireguardConfiguration:
|
|||||||
# print(self.__parser.sections())
|
# print(self.__parser.sections())
|
||||||
self.__parser.write(configFile)
|
self.__parser.write(configFile)
|
||||||
|
|
||||||
self.Peers = []
|
self.Peers: list[Peer] = []
|
||||||
|
|
||||||
# Create tables in database
|
# Create tables in database
|
||||||
self.__createDatabase()
|
self.__createDatabase()
|
||||||
@ -313,6 +309,30 @@ class WireguardConfiguration:
|
|||||||
return True, i
|
return True, i
|
||||||
return False, None
|
return False, None
|
||||||
|
|
||||||
|
def deletePeers(self, listOfPublicKeys):
|
||||||
|
numOfDeletedPeers = 0
|
||||||
|
numOfFailedToDeletePeers = 0
|
||||||
|
for p in listOfPublicKeys:
|
||||||
|
found, pf = self.searchPeer(p)
|
||||||
|
if found:
|
||||||
|
try:
|
||||||
|
subprocess.check_output(f"wg set {self.Name} peer {pf.id} remove",
|
||||||
|
shell=True, stderr=subprocess.STDOUT)
|
||||||
|
cursor.execute("DELETE FROM %s WHERE id = ?" % self.Name, (pf.id,))
|
||||||
|
numOfDeletedPeers += 1
|
||||||
|
except Exception as e:
|
||||||
|
numOfFailedToDeletePeers += 1
|
||||||
|
|
||||||
|
if not self.__wgSave():
|
||||||
|
return ResponseObject(False, "Failed to save configuration through WireGuard")
|
||||||
|
|
||||||
|
self.__getPeers()
|
||||||
|
|
||||||
|
if numOfDeletedPeers == len(listOfPublicKeys):
|
||||||
|
return ResponseObject(True, f"Deleted {numOfDeletedPeers} peer(s)")
|
||||||
|
return ResponseObject(False,
|
||||||
|
f"Deleted {numOfDeletedPeers} peer(s) successfully. Failed to delete {numOfFailedToDeletePeers} peer(s)")
|
||||||
|
|
||||||
def __savePeers(self):
|
def __savePeers(self):
|
||||||
for i in self.Peers:
|
for i in self.Peers:
|
||||||
d = i.toJson()
|
d = i.toJson()
|
||||||
@ -329,6 +349,13 @@ class WireguardConfiguration:
|
|||||||
)
|
)
|
||||||
sqldb.commit()
|
sqldb.commit()
|
||||||
|
|
||||||
|
def __wgSave(self) -> tuple[bool, str] | tuple[bool, None]:
|
||||||
|
try:
|
||||||
|
subprocess.check_output(f"wg-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT)
|
||||||
|
return True, None
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
return False, str(e)
|
||||||
|
|
||||||
def getPeersLatestHandshake(self):
|
def getPeersLatestHandshake(self):
|
||||||
try:
|
try:
|
||||||
latestHandshake = subprocess.check_output(f"wg show {self.Name} latest-handshakes",
|
latestHandshake = subprocess.check_output(f"wg show {self.Name} latest-handshakes",
|
||||||
@ -383,22 +410,25 @@ class WireguardConfiguration:
|
|||||||
data_usage[i][0],))
|
data_usage[i][0],))
|
||||||
total_sent = 0
|
total_sent = 0
|
||||||
total_receive = 0
|
total_receive = 0
|
||||||
|
|
||||||
|
_, p = self.searchPeer(data_usage[i][0])
|
||||||
|
if p.total_receive != round(total_receive, 4) or p.total_sent != round(total_sent, 4):
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"UPDATE %s SET total_receive = ?, total_sent = ?, total_data = ? WHERE id = ?"
|
"UPDATE %s SET total_receive = ?, total_sent = ?, total_data = ? WHERE id = ?"
|
||||||
% self.Name, (round(total_receive, 4), round(total_sent, 4),
|
% self.Name, (round(total_receive, 4), round(total_sent, 4),
|
||||||
round(total_receive + total_sent, 4), data_usage[i][0],))
|
round(total_receive + total_sent, 4), data_usage[i][0],))
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
now_string = now.strftime("%d/%m/%Y %H:%M:%S")
|
now_string = now.strftime("%d/%m/%Y %H:%M:%S")
|
||||||
cursor.execute(f'''
|
# cursor.execute(f'''
|
||||||
INSERT INTO %s_transfer
|
# INSERT INTO %s_transfer
|
||||||
(id, total_receive, total_sent, total_data,
|
# (id, total_receive, total_sent, total_data,
|
||||||
cumu_receive, cumu_sent, cumu_data, time)
|
# cumu_receive, cumu_sent, cumu_data, time)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
# VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
''' % self.Name, (data_usage[i][0], round(total_receive, 4), round(total_sent, 4),
|
# ''' % self.Name, (data_usage[i][0], round(total_receive, 4), round(total_sent, 4),
|
||||||
round(total_receive + total_sent, 4), round(cumulative_receive, 4),
|
# round(total_receive + total_sent, 4), round(cumulative_receive, 4),
|
||||||
round(cumulative_sent, 4),
|
# round(cumulative_sent, 4),
|
||||||
round(cumulative_sent + cumulative_receive, 4), now_string,))
|
# round(cumulative_sent + cumulative_receive, 4), now_string,))
|
||||||
sqldb.commit()
|
# sqldb.commit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Error" + str(e))
|
print("Error" + str(e))
|
||||||
|
|
||||||
@ -837,9 +867,14 @@ def auth_req():
|
|||||||
and "getDashboardConfiguration" not in request.path and "getDashboardTheme" not in request.path
|
and "getDashboardConfiguration" not in request.path and "getDashboardTheme" not in request.path
|
||||||
and "isTotpEnabled" not in request.path
|
and "isTotpEnabled" not in request.path
|
||||||
):
|
):
|
||||||
resp = Flask.make_response(app, "Not Authorized" + request.path)
|
response = Flask.make_response(app, {
|
||||||
resp.status_code = 401
|
"status": False,
|
||||||
return resp
|
"message": None,
|
||||||
|
"data": None
|
||||||
|
})
|
||||||
|
response.content_type = "application/json"
|
||||||
|
response.status_code = 401
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/validateAuthentication', methods=["GET"])
|
@app.route('/api/validateAuthentication', methods=["GET"])
|
||||||
@ -999,6 +1034,19 @@ def API_updatePeerSettings(configName):
|
|||||||
return ResponseObject(False, "Peer does not exist")
|
return ResponseObject(False, "Peer does not exist")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/deletePeers/<configName>', methods=['POST'])
|
||||||
|
def API_deletePeers(configName: str) -> ResponseObject:
|
||||||
|
data = request.get_json()
|
||||||
|
peers = data['peers']
|
||||||
|
if configName in WireguardConfigurations.keys():
|
||||||
|
if len(peers) == 0:
|
||||||
|
return ResponseObject(False, "Please specify more than one peer")
|
||||||
|
configuration = WireguardConfigurations.get(configName)
|
||||||
|
return configuration.deletePeers(peers)
|
||||||
|
|
||||||
|
return ResponseObject(False, "Configuration does not exist")
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/addPeers/<configName>', methods=['POST'])
|
@app.route('/api/addPeers/<configName>', methods=['POST'])
|
||||||
def API_addPeers(configName):
|
def API_addPeers(configName):
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
@ -1055,8 +1103,6 @@ def API_addPeers(configName):
|
|||||||
|
|
||||||
return ResponseObject()
|
return ResponseObject()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if config.searchPeer(public_key)[0] is True:
|
if config.searchPeer(public_key)[0] is True:
|
||||||
return ResponseObject(False, f"This peer already exist.")
|
return ResponseObject(False, f"This peer already exist.")
|
||||||
@ -1231,7 +1277,7 @@ def backGroundThread():
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
engine = create_engine("sqlite:///" + os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard.db'))
|
# engine = create_engine("sqlite:///" + os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard.db'))
|
||||||
sqldb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard.db'), check_same_thread=False)
|
sqldb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard.db'), check_same_thread=False)
|
||||||
sqldb.row_factory = sqlite3.Row
|
sqldb.row_factory = sqlite3.Row
|
||||||
cursor = sqldb.cursor()
|
cursor = sqldb.cursor()
|
||||||
|
@ -5,3 +5,5 @@ pyotp
|
|||||||
flask
|
flask
|
||||||
icmplib
|
icmplib
|
||||||
sqlalchemy
|
sqlalchemy
|
||||||
|
flask[async]
|
||||||
|
aiosqlite
|
@ -936,3 +936,7 @@ pre.index-alert {
|
|||||||
.theme-switch-btn{
|
.theme-switch-btn{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-item.disabled, .dropdown-item:disabled{
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { onClickOutside } from '@vueuse/core'
|
import { onClickOutside } from '@vueuse/core'
|
||||||
|
import "animate.css"
|
||||||
import PeerSettingsDropdown from "@/components/configurationComponents/peerSettingsDropdown.vue";
|
import PeerSettingsDropdown from "@/components/configurationComponents/peerSettingsDropdown.vue";
|
||||||
export default {
|
export default {
|
||||||
name: "peer",
|
name: "peer",
|
||||||
@ -76,6 +77,7 @@ export default {
|
|||||||
<PeerSettingsDropdown
|
<PeerSettingsDropdown
|
||||||
@qrcode="(file) => this.$emit('qrcode', file)"
|
@qrcode="(file) => this.$emit('qrcode', file)"
|
||||||
@setting="this.$emit('setting')"
|
@setting="this.$emit('setting')"
|
||||||
|
@refresh="this.$emit('refresh')"
|
||||||
:Peer="Peer"
|
:Peer="Peer"
|
||||||
v-if="this.subMenuOpened"
|
v-if="this.subMenuOpened"
|
||||||
ref="target"
|
ref="target"
|
||||||
|
@ -100,13 +100,14 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="mb-4 d-flex align-items-center gap-4">
|
<div class="mb-4">
|
||||||
<RouterLink to="peers">
|
<RouterLink to="peers" is="div" class="d-flex align-items-center gap-4 text-decoration-none">
|
||||||
<h3 class="mb-0 text-body">
|
<h3 class="mb-0 text-body">
|
||||||
<i class="bi bi-chevron-left"></i>
|
<i class="bi bi-chevron-left"></i>
|
||||||
</h3>
|
</h3>
|
||||||
|
<h3 class="text-body mb-0">Add Peers</h3>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<h3 class="text-body mb-0">New Configuration</h3>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-column gap-2">
|
<div class="d-flex flex-column gap-2">
|
||||||
<BulkAdd :saving="saving" :data="this.data" :availableIp="this.availableIp"></BulkAdd>
|
<BulkAdd :saving="saving" :data="this.data" :availableIp="this.availableIp"></BulkAdd>
|
||||||
|
@ -73,6 +73,7 @@ export default {
|
|||||||
},
|
},
|
||||||
data(){
|
data(){
|
||||||
return {
|
return {
|
||||||
|
configurationToggling: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
configurationInfo: [],
|
configurationInfo: [],
|
||||||
@ -151,7 +152,24 @@ export default {
|
|||||||
clearInterval(this.interval)
|
clearInterval(this.interval)
|
||||||
},
|
},
|
||||||
methods:{
|
methods:{
|
||||||
getPeers(id){
|
toggle(){
|
||||||
|
this.configurationToggling = true;
|
||||||
|
fetchGet("/api/toggleWireguardConfiguration/", {
|
||||||
|
configurationName: this.configurationInfo.Name
|
||||||
|
}, (res) => {
|
||||||
|
if (res.status){
|
||||||
|
this.dashboardConfigurationStore.newMessage("Server",
|
||||||
|
`${this.configurationInfo.Name} is
|
||||||
|
${res.data ? 'is on':'is off'}`, "Success")
|
||||||
|
}else{
|
||||||
|
this.dashboardConfigurationStore.newMessage("Server",
|
||||||
|
res.message, 'danger')
|
||||||
|
}
|
||||||
|
this.configurationInfo.Status = res.data
|
||||||
|
this.configurationToggling = false;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getPeers(id = this.$route.params.id){
|
||||||
fetchGet("/api/getWireguardConfigurationInfo",
|
fetchGet("/api/getWireguardConfigurationInfo",
|
||||||
{
|
{
|
||||||
configurationName: id
|
configurationName: id
|
||||||
@ -207,7 +225,7 @@ export default {
|
|||||||
},
|
},
|
||||||
setInterval(){
|
setInterval(){
|
||||||
this.interval = setInterval(() => {
|
this.interval = setInterval(() => {
|
||||||
this.getPeers(this.$route.params.id)
|
this.getPeers()
|
||||||
}, parseInt(this.dashboardConfigurationStore.Configuration.Server.dashboard_refresh_interval))
|
}, parseInt(this.dashboardConfigurationStore.Configuration.Server.dashboard_refresh_interval))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -338,14 +356,39 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="!this.loading">
|
<div v-if="!this.loading">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
<div>
|
<div>
|
||||||
<small CLASS="text-muted">CONFIGURATION</small>
|
<small CLASS="text-muted">CONFIGURATION</small>
|
||||||
<div class="d-flex align-items-center gap-3">
|
<div class="d-flex align-items-center gap-3">
|
||||||
<h1 class="mb-0"><samp>{{this.configurationInfo.Name}}</samp></h1>
|
<h1 class="mb-0"><samp>{{this.configurationInfo.Name}}</samp></h1>
|
||||||
<div class="dot active ms-0"></div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card rounded-3 bg-transparent shadow-sm ms-auto">
|
||||||
|
<div class="card-body py-2 d-flex align-items-center">
|
||||||
|
<div>
|
||||||
|
<p class="mb-0 text-muted"><small>Status</small></p>
|
||||||
|
<div class="form-check form-switch ms-auto">
|
||||||
|
<label class="form-check-label" style="cursor: pointer" :for="'switch' + this.configurationInfo.id">
|
||||||
|
{{this.configurationToggling ? 'Turning ':''}}
|
||||||
|
{{this.configurationInfo.Status ? "On":"Off"}}
|
||||||
|
<span v-if="this.configurationToggling"
|
||||||
|
class="spinner-border spinner-border-sm" 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 class="dot ms-5" :class="{active: this.configurationInfo.Status}"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-3 gy-2 gx-2 mb-2">
|
<div class="row mt-3 gy-2 gx-2 mb-2">
|
||||||
|
|
||||||
<div class="col-6 col-lg-3">
|
<div class="col-6 col-lg-3">
|
||||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
<div class="card rounded-3 bg-transparent shadow-sm">
|
||||||
<div class="card-body py-2">
|
<div class="card-body py-2">
|
||||||
@ -419,36 +462,36 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
<div class="row gx-2 gy-2 mb-5">
|
<div class="row gx-2 gy-2 mb-5">
|
||||||
<div class="col-12 col-lg-6">
|
<div class="col-12 col-lg-6">
|
||||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
<div class="card rounded-3 bg-transparent shadow-sm" style="height: 270px">
|
||||||
<div class="card-header bg-transparent border-0"><small class="text-muted">Peers Total Data Usage</small></div>
|
<div class="card-header bg-transparent border-0"><small class="text-muted">Peers Total Data Usage</small></div>
|
||||||
<div class="card-body pt-1">
|
<div class="card-body pt-1">
|
||||||
<Bar
|
<Bar
|
||||||
:data="individualDataUsage"
|
:data="individualDataUsage"
|
||||||
:options="individualDataUsageChartOption"
|
:options="individualDataUsageChartOption"
|
||||||
style="height: 200px; width: 100%"></Bar>
|
style="width: 100%; height: 200px; max-height: 200px"></Bar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm col-lg-3">
|
<div class="col-sm col-lg-3">
|
||||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
<div class="card rounded-3 bg-transparent shadow-sm" style="height: 270px">
|
||||||
<div class="card-header bg-transparent border-0"><small class="text-muted">Real Time Received Data Usage</small></div>
|
<div class="card-header bg-transparent border-0"><small class="text-muted">Real Time Received Data Usage</small></div>
|
||||||
<div class="card-body pt-1">
|
<div class="card-body pt-1">
|
||||||
<Line
|
<Line
|
||||||
:options="chartOptions"
|
:options="chartOptions"
|
||||||
:data="receiveData"
|
:data="receiveData"
|
||||||
style="width: 100%; height: 200px"
|
style="width: 100%; height: 200px; max-height: 200px"
|
||||||
></Line>
|
></Line>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm col-lg-3">
|
<div class="col-sm col-lg-3">
|
||||||
<div class="card rounded-3 bg-transparent shadow-sm">
|
<div class="card rounded-3 bg-transparent shadow-sm" style="height: 270px">
|
||||||
<div class="card-header bg-transparent border-0"><small class="text-muted">Real Time Sent Data Usage</small></div>
|
<div class="card-header bg-transparent border-0"><small class="text-muted">Real Time Sent Data Usage</small></div>
|
||||||
<div class="card-body pt-1">
|
<div class="card-body pt-1">
|
||||||
<Line
|
<Line
|
||||||
:options="chartOptions"
|
:options="chartOptions"
|
||||||
:data="sentData"
|
:data="sentData"
|
||||||
style="width: 100%; height: 200px"
|
style="width: 100%; height: 200px; max-height: 200px"
|
||||||
></Line>
|
></Line>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -457,10 +500,11 @@ export default {
|
|||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<div class="d-flex align-items-center gap-3 mb-2 ">
|
<div class="d-flex align-items-center gap-3 mb-2 ">
|
||||||
<h3>Peers</h3>
|
<h3>Peers</h3>
|
||||||
|
|
||||||
<RouterLink
|
<RouterLink
|
||||||
to="create"
|
to="create"
|
||||||
class="text-decoration-none ms-auto">
|
class="text-decoration-none ms-auto btn btn-primary rounded-3">
|
||||||
<i class="bi bi-plus-circle-fill me-2"></i>Add Peer</RouterLink>
|
<i class="bi bi-plus-circle-fill me-2"></i>Peers</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
<PeerSearch></PeerSearch>
|
<PeerSearch></PeerSearch>
|
||||||
<TransitionGroup name="list" tag="div" class="row gx-2 gy-2 z-0">
|
<TransitionGroup name="list" tag="div" class="row gx-2 gy-2 z-0">
|
||||||
@ -468,6 +512,7 @@ export default {
|
|||||||
:key="peer.id"
|
:key="peer.id"
|
||||||
v-for="peer in this.searchPeers">
|
v-for="peer in this.searchPeers">
|
||||||
<Peer :Peer="peer"
|
<Peer :Peer="peer"
|
||||||
|
@refresh="this.getPeers()"
|
||||||
@setting="peerSetting.modalOpen = true; peerSetting.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;}"
|
@qrcode="(file) => {this.peerQRCode.peerConfigData = file; this.peerQRCode.modalOpen = true;}"
|
||||||
></Peer>
|
></Peer>
|
||||||
@ -477,7 +522,7 @@ export default {
|
|||||||
<Transition name="fade">
|
<Transition name="fade">
|
||||||
<PeerSettings v-if="this.peerSetting.modalOpen"
|
<PeerSettings v-if="this.peerSetting.modalOpen"
|
||||||
:selectedPeer="this.peerSetting.selectedPeer"
|
:selectedPeer="this.peerSetting.selectedPeer"
|
||||||
@refresh="this.getPeers(this.$route.params.id)"
|
@refresh="this.getPeers()"
|
||||||
@close="this.peerSetting.modalOpen = false">
|
@close="this.peerSetting.modalOpen = false">
|
||||||
</PeerSettings>
|
</PeerSettings>
|
||||||
|
|
||||||
@ -487,10 +532,6 @@ export default {
|
|||||||
@close="this.peerQRCode.modalOpen = false"
|
@close="this.peerQRCode.modalOpen = false"
|
||||||
v-if="peerQRCode.modalOpen"></PeerQRCode>
|
v-if="peerQRCode.modalOpen"></PeerQRCode>
|
||||||
</Transition>
|
</Transition>
|
||||||
<!-- <Transition name="fade">-->
|
|
||||||
<!-- -->
|
|
||||||
<!-- </Transition>-->
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -53,15 +53,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
let fadeIn = "animate__fadeInUp";
|
|
||||||
let fadeOut = "animate__fadeOutDown"
|
|
||||||
|
|
||||||
this.$el.querySelectorAll(".dropdown").forEach(x => {
|
|
||||||
x.addEventListener('show.bs.dropdown', (e) => {
|
|
||||||
console.log(e.target.parentNode.children)
|
|
||||||
console.log(e.target.closest("ul.dropdown-menu"))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -73,34 +65,43 @@ export default {
|
|||||||
<i class="bi bi-filter-circle me-2"></i>
|
<i class="bi bi-filter-circle me-2"></i>
|
||||||
Sort
|
Sort
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu mt-2 shadow">
|
<ul class="dropdown-menu mt-2 shadow rounded-3">
|
||||||
<li v-for="(value, key) in this.sort">
|
<li v-for="(value, key) in this.sort">
|
||||||
<a class="dropdown-item d-flex" role="button" @click="this.updateSort(key)">
|
<a class="dropdown-item d-flex" role="button" @click="this.updateSort(key)">
|
||||||
<span class="me-auto">{{value}}</span>
|
<span class="me-auto">{{value}}</span>
|
||||||
<i class="bi bi-check"
|
<i class="bi bi-check text-primary"
|
||||||
v-if="store.Configuration.Server.dashboard_sort === key"></i>
|
v-if="store.Configuration.Server.dashboard_sort === key"></i>
|
||||||
</a></li>
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button class="btn btn-outline-secondary btn-sm dropdown-toggle rounded-3" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
<button class="btn btn-outline-secondary btn-sm dropdown-toggle rounded-3" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<i class="bi bi-arrow-repeat me-2"></i>Refresh Interval
|
<i class="bi bi-arrow-repeat me-2"></i>Refresh Interval
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu shadow mt-2">
|
<ul class="dropdown-menu shadow mt-2 rounded-3">
|
||||||
<li v-for="(value, key) in this.interval">
|
<li v-for="(value, key) in this.interval">
|
||||||
<a class="dropdown-item d-flex" role="button" @click="updateRefreshInterval(key)">
|
<a class="dropdown-item d-flex" role="button" @click="updateRefreshInterval(key)">
|
||||||
<span class="me-auto">{{value}}</span>
|
<span class="me-auto">{{value}}</span>
|
||||||
<i class="bi bi-check"
|
<i class="bi bi-check text-primary"
|
||||||
v-if="store.Configuration.Server.dashboard_refresh_interval === key"></i>
|
v-if="store.Configuration.Server.dashboard_refresh_interval === key"></i>
|
||||||
</a></li>
|
</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- <button class="btn btn-outline-secondary btn-sm rounded-3" type="button"-->
|
||||||
|
<!-- @click="this.store.Peers.Selecting = !this.store.Peers.Selecting"-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- <i class="bi bi-app-indicator me-2"></i>-->
|
||||||
|
<!-- Select-->
|
||||||
|
<!-- </button>-->
|
||||||
|
|
||||||
<div class="ms-auto d-flex align-items-center">
|
<div class="ms-auto d-flex align-items-center">
|
||||||
<label class="d-flex me-2 text-muted" for="searchPeers"><i class="bi bi-search me-1"></i></label>
|
<label class="d-flex me-2 text-muted" for="searchPeers"><i class="bi bi-search me-1"></i></label>
|
||||||
<input class="form-control form-control-sm rounded-3"
|
<input class="form-control form-control-sm rounded-3"
|
||||||
|
id="searchPeers"
|
||||||
v-model="this.wireguardConfigurationStore.searchString">
|
v-model="this.wireguardConfigurationStore.searchString">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import {fetchGet} from "@/utilities/fetch.js";
|
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
|
||||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -11,6 +11,11 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
Peer: Object
|
Peer: Object
|
||||||
},
|
},
|
||||||
|
data(){
|
||||||
|
return{
|
||||||
|
deleteBtnDisabled: false
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
downloadPeer(){
|
downloadPeer(){
|
||||||
fetchGet("/api/downloadPeer/"+this.$route.params.id, {
|
fetchGet("/api/downloadPeer/"+this.$route.params.id, {
|
||||||
@ -40,6 +45,16 @@ export default {
|
|||||||
this.dashboardStore.newMessage("Server", res.message, "danger")
|
this.dashboardStore.newMessage("Server", res.message, "danger")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
deletePeer(){
|
||||||
|
this.deleteBtnDisabled = true
|
||||||
|
fetchPost(`/api/deletePeers/${this.$route.params.id}`, {
|
||||||
|
peers: [this.Peer.id]
|
||||||
|
}, (res) => {
|
||||||
|
this.dashboardStore.newMessage("Server", res.message, res.status ? "success":"danger")
|
||||||
|
this.$emit("refresh")
|
||||||
|
this.deleteBtnDisabled = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,9 +105,10 @@ export default {
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item d-flex fw-bold text-danger"
|
<a class="dropdown-item d-flex fw-bold text-danger"
|
||||||
|
@click="this.deletePeer()"
|
||||||
|
:class="{disabled: this.deleteBtnDisabled}"
|
||||||
role="button">
|
role="button">
|
||||||
<i class="me-auto bi bi-trash"></i> Delete
|
<i class="me-auto bi bi-trash"></i> {{!this.deleteBtnDisabled ? "Delete":"Deleting..."}}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -102,4 +118,8 @@ export default {
|
|||||||
.dropdown-menu{
|
.dropdown-menu{
|
||||||
right: 1rem;
|
right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-item.disabled, .dropdown-item:disabled{
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
@ -8,13 +8,23 @@ export default {
|
|||||||
components: {ConfigurationCard},
|
components: {ConfigurationCard},
|
||||||
async setup(){
|
async setup(){
|
||||||
const wireguardConfigurationsStore = WireguardConfigurationsStore();
|
const wireguardConfigurationsStore = WireguardConfigurationsStore();
|
||||||
await wireguardConfigurationsStore.getConfigurations();
|
|
||||||
return {wireguardConfigurationsStore}
|
return {wireguardConfigurationsStore}
|
||||||
|
},
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
configurationLoaded: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.wireguardConfigurationsStore.getConfigurations();
|
||||||
|
this.configurationLoaded = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="d-flex mb-4 ">
|
<div class="d-flex mb-4 ">
|
||||||
@ -24,13 +34,21 @@ export default {
|
|||||||
<i class="bi bi-plus-circle-fill ms-2"></i>
|
<i class="bi bi-plus-circle-fill ms-2"></i>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-muted" v-if="this.wireguardConfigurationsStore.Configurations.length === 0">You don't have any WireGuard configurations yet. Please check the configuration folder or change it in "Settings". By default the folder is "/etc/wireguard".</p>
|
<Transition name="fade" mode="out-in">
|
||||||
|
<div v-if="this.configurationLoaded">
|
||||||
|
<p class="text-muted" v-if="this.wireguardConfigurationsStore.Configurations.length === 0">
|
||||||
|
You don't have any WireGuard configurations yet. Please check the configuration folder or change it in "Settings". By default the folder is "/etc/wireguard".
|
||||||
|
</p>
|
||||||
|
|
||||||
<div class="d-flex gap-3 flex-column" v-else>
|
<div class="d-flex gap-3 flex-column" v-else>
|
||||||
<ConfigurationCard v-for="c in this.wireguardConfigurationsStore.Configurations" :key="c.Name" :c="c"></ConfigurationCard>
|
<ConfigurationCard v-for="c in this.wireguardConfigurationsStore.Configurations" :key="c.Name" :c="c"></ConfigurationCard>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Transition>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -13,12 +13,18 @@ export default {
|
|||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
data(){
|
||||||
|
return{
|
||||||
|
configurationToggling: false
|
||||||
|
}
|
||||||
|
},
|
||||||
setup(){
|
setup(){
|
||||||
const dashboardConfigurationStore = DashboardConfigurationStore();
|
const dashboardConfigurationStore = DashboardConfigurationStore();
|
||||||
return {dashboardConfigurationStore}
|
return {dashboardConfigurationStore}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggle(){
|
toggle(){
|
||||||
|
this.configurationToggling = true;
|
||||||
fetchGet("/api/toggleWireguardConfiguration/", {
|
fetchGet("/api/toggleWireguardConfiguration/", {
|
||||||
configurationName: this.c.Name
|
configurationName: this.c.Name
|
||||||
}, (res) => {
|
}, (res) => {
|
||||||
@ -30,6 +36,7 @@ export default {
|
|||||||
res.message, 'danger')
|
res.message, 'danger')
|
||||||
}
|
}
|
||||||
this.c.Status = res.data
|
this.c.Status = res.data
|
||||||
|
this.configurationToggling = false;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,10 +60,15 @@ export default {
|
|||||||
<samp style="line-break: anywhere">{{c.PublicKey}}</samp>
|
<samp style="line-break: anywhere">{{c.PublicKey}}</samp>
|
||||||
</small>
|
</small>
|
||||||
<div class="form-check form-switch ms-auto">
|
<div class="form-check form-switch ms-auto">
|
||||||
<label class="form-check-label" :for="'switch' + c.PrivateKey">
|
<label class="form-check-label" style="cursor: pointer" :for="'switch' + c.PrivateKey">
|
||||||
|
{{this.configurationToggling ? 'Turning ':''}}
|
||||||
{{c.Status ? "On":"Off"}}
|
{{c.Status ? "On":"Off"}}
|
||||||
|
<span v-if="this.configurationToggling"
|
||||||
|
class="spinner-border spinner-border-sm" aria-hidden="true"></span>
|
||||||
</label>
|
</label>
|
||||||
<input class="form-check-input"
|
<input class="form-check-input"
|
||||||
|
style="cursor: pointer"
|
||||||
|
:disabled="this.configurationToggling"
|
||||||
type="checkbox" role="switch" :id="'switch' + c.PrivateKey"
|
type="checkbox" role="switch" :id="'switch' + c.PrivateKey"
|
||||||
@change="this.toggle()"
|
@change="this.toggle()"
|
||||||
v-model="c.Status"
|
v-model="c.Status"
|
||||||
|
@ -117,8 +117,10 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
if (!wireguardConfigurationsStore.Configurations && to.name !== "Configuration List"){
|
if (!wireguardConfigurationsStore.Configurations && to.name !== "Configuration List"){
|
||||||
await wireguardConfigurationsStore.getConfigurations();
|
await wireguardConfigurationsStore.getConfigurations();
|
||||||
}
|
}
|
||||||
|
dashboardConfigurationStore.Redirect = undefined;
|
||||||
next()
|
next()
|
||||||
}else{
|
}else{
|
||||||
|
dashboardConfigurationStore.Redirect = to;
|
||||||
next("/signin")
|
next("/signin")
|
||||||
}
|
}
|
||||||
}else {
|
}else {
|
||||||
|
@ -4,8 +4,12 @@ import {v4} from "uuid";
|
|||||||
|
|
||||||
export const DashboardConfigurationStore = defineStore('DashboardConfigurationStore', {
|
export const DashboardConfigurationStore = defineStore('DashboardConfigurationStore', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
|
Redirect: undefined,
|
||||||
Configuration: undefined,
|
Configuration: undefined,
|
||||||
Messages: []
|
Messages: [],
|
||||||
|
Peers: {
|
||||||
|
Selecting: false
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
async getConfiguration(){
|
async getConfiguration(){
|
||||||
|
@ -19,6 +19,7 @@ export const WireguardConfigurationsStore = defineStore('WireguardConfigurations
|
|||||||
},
|
},
|
||||||
checkCIDR(ip){
|
checkCIDR(ip){
|
||||||
return isCidr(ip) !== 0
|
return isCidr(ip) !== 0
|
||||||
}
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
@ -7,7 +7,12 @@ export const fetchGet = async (url, params=undefined, callback=undefined) => {
|
|||||||
})
|
})
|
||||||
.then(x => x.json())
|
.then(x => x.json())
|
||||||
.then(x => callback ? callback(x) : undefined)
|
.then(x => callback ? callback(x) : undefined)
|
||||||
|
.catch(x => {
|
||||||
|
// let router = useRouter()
|
||||||
|
// if (x.status === 401){
|
||||||
|
// router.push('/signin')
|
||||||
|
// }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchPost = async (url, body, callback) => {
|
export const fetchPost = async (url, body, callback) => {
|
||||||
|
@ -8,7 +8,9 @@ export default {
|
|||||||
<div class="mt-5 text-body">
|
<div class="mt-5 text-body">
|
||||||
<RouterView v-slot="{ Component, route }">
|
<RouterView v-slot="{ Component, route }">
|
||||||
<Transition name="fade2" mode="out-in">
|
<Transition name="fade2" mode="out-in">
|
||||||
|
<Suspense>
|
||||||
<Component :is="Component" :key="route.path"></Component>
|
<Component :is="Component" :key="route.path"></Component>
|
||||||
|
</Suspense>
|
||||||
</Transition>
|
</Transition>
|
||||||
</RouterView>
|
</RouterView>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,9 +40,13 @@ export default {
|
|||||||
this.$refs["signInBtn"].classList.add("signedIn")
|
this.$refs["signInBtn"].classList.add("signedIn")
|
||||||
if (response.message){
|
if (response.message){
|
||||||
this.$router.push('/welcome')
|
this.$router.push('/welcome')
|
||||||
|
}else{
|
||||||
|
if (this.store.Redirect !== undefined){
|
||||||
|
this.$router.push(this.store.Redirect)
|
||||||
}else{
|
}else{
|
||||||
this.$router.push('/')
|
this.$router.push('/')
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
this.loginError = true;
|
this.loginError = true;
|
||||||
this.loginErrorMessage = response.message;
|
this.loginErrorMessage = response.message;
|
||||||
@ -71,7 +75,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container-fluid login-container-fluid d-flex main" :data-bs-theme="this.theme">
|
<div class="container-fluid login-container-fluid d-flex main flex-column" :data-bs-theme="this.theme">
|
||||||
<div class="login-box m-auto" style="width: 500px;">
|
<div class="login-box m-auto" style="width: 500px;">
|
||||||
<h4 class="mb-0 text-body">Welcome to</h4>
|
<h4 class="mb-0 text-body">Welcome to</h4>
|
||||||
<span class="dashboardLogo display-3">WGDashboard</span>
|
<span class="dashboardLogo display-3">WGDashboard</span>
|
||||||
@ -117,6 +121,10 @@ export default {
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<small class="text-muted pb-3 d-block w-100 text-center">
|
||||||
|
WGDashboard v4.0 | Developed with ❤️ by
|
||||||
|
<a href="https://github.com/donaldzou" target="_blank"><strong>Donald Zou</strong></a>
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user