1
0
mirror of https://github.com/donaldzou/WGDashboard.git synced 2024-11-22 15:20:09 +01:00

Made some progress ;)

This commit is contained in:
Donald Zou 2024-05-20 22:28:52 +08:00
parent 41e05ddf9c
commit c7ca20b45a
16 changed files with 250 additions and 81 deletions

View File

@ -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
cursor.execute(
"UPDATE %s SET total_receive = ?, total_sent = ?, total_data = ? WHERE id = ?" _, p = self.searchPeer(data_usage[i][0])
% self.Name, (round(total_receive, 4), round(total_sent, 4), if p.total_receive != round(total_receive, 4) or p.total_sent != round(total_sent, 4):
round(total_receive + total_sent, 4), data_usage[i][0],)) cursor.execute(
"UPDATE %s SET total_receive = ?, total_sent = ?, total_data = ? WHERE id = ?"
% self.Name, (round(total_receive, 4), round(total_sent, 4),
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()

View File

@ -4,4 +4,6 @@ psutil
pyotp pyotp
flask flask
icmplib icmplib
sqlalchemy sqlalchemy
flask[async]
aiosqlite

View File

@ -935,4 +935,8 @@ pre.index-alert {
.theme-switch-btn{ .theme-switch-btn{
width: 100%; width: 100%;
}
.dropdown-item.disabled, .dropdown-item:disabled{
opacity: 0.7;
} }

View File

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

View File

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

View File

@ -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> <div class="d-flex align-items-center">
<small CLASS="text-muted">CONFIGURATION</small> <div>
<div class="d-flex align-items-center gap-3"> <small CLASS="text-muted">CONFIGURATION</small>
<h1 class="mb-0"><samp>{{this.configurationInfo.Name}}</samp></h1> <div class="d-flex align-items-center gap-3">
<div class="dot active ms-0"></div> <h1 class="mb-0"><samp>{{this.configurationInfo.Name}}</samp></h1>
</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,17 +500,19 @@ 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">
<div class="col-12 col-lg-6 col-xl-4" <div class="col-12 col-lg-6 col-xl-4"
: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>

View File

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

View File

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

View File

@ -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>
</Transition>
</div> </div>
</div> </div>
</template> </template>
<style scoped> <style scoped>

View File

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

View File

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

View File

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

View File

@ -19,6 +19,7 @@ export const WireguardConfigurationsStore = defineStore('WireguardConfigurations
}, },
checkCIDR(ip){ checkCIDR(ip){
return isCidr(ip) !== 0 return isCidr(ip) !== 0
} },
} }
}); });

View File

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

View File

@ -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">
<Component :is="Component" :key="route.path"></Component> <Suspense>
<Component :is="Component" :key="route.path"></Component>
</Suspense>
</Transition> </Transition>
</RouterView> </RouterView>
</div> </div>

View File

@ -41,7 +41,11 @@ export default {
if (response.message){ if (response.message){
this.$router.push('/welcome') this.$router.push('/welcome')
}else{ }else{
this.$router.push('/') if (this.store.Redirect !== undefined){
this.$router.push(this.store.Redirect)
}else{
this.$router.push('/')
}
} }
}else{ }else{
this.loginError = true; this.loginError = true;
@ -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>