1
0
mirror of https://github.com/donaldzou/WGDashboard.git synced 2024-11-06 16:00:28 +01:00

Peer schedule style is almost done

But I don't feel it quite right..
This commit is contained in:
Donald Zou 2024-06-25 23:02:13 +08:00
parent 6c529a6908
commit 2d838b69fd
9 changed files with 298 additions and 183 deletions

View File

@ -126,6 +126,7 @@ class PeerJobs:
self.__getJobs()
def __getJobs(self):
self.Jobs.clear()
jobs = self.jobdbCursor.execute("SELECT * FROM PeerJobs WHERE ExpireDate IS NULL").fetchall()
for job in jobs:
self.Jobs.append(PeerJob(
@ -158,6 +159,22 @@ class PeerJobs:
def searchJob(self, Configuration: str, Peer: str):
return list(filter(lambda x: x.Configuration == Configuration and x.Peer == Peer, self.Jobs))
def saveJob(self, Job: PeerJob) -> tuple[bool, list] | tuple[bool, str]:
try:
if (len(str(Job.CreationDate))) == 0:
self.jobdbCursor.execute('''
INSERT INTO PeerJobs VALUES (?, ?, ?, ?, ?, ?, strftime('%Y-%m-%d %H:%M:%S','now'), NULL, ?)
''', (Job.JobID, Job.Configuration, Job.Peer, Job.Field, Job.Operator, Job.Value, Job.Action,))
else:
self.jobdbCursor.execute('''
UPDATE PeerJobs SET Field = ?, Operator = ?, Value = ?, Action = ? WHERE JobID = ?
''', (Job.Field, Job.Operator, Job.Value, Job.Action, Job.JobID))
self.jobdb.commit()
self.__getJobs()
return True, list(filter(lambda x: x.Configuration == Job.Configuration and x.Peer == Job.Peer and x.JobID == Job.JobID, self.Jobs))
except Exception as e:
return False, str(e)
class WireguardConfiguration:
class InvalidConfigurationFileException(Exception):
@ -233,7 +250,6 @@ class WireguardConfiguration:
self.__parser.write(configFile)
self.Peers: list[Peer] = []
# Create tables in database
self.__createDatabase()
self.getPeersList()
@ -638,6 +654,7 @@ class Peer:
self.getJobs()
def toJson(self):
self.getJobs()
return self.__dict__
def __repr__(self):
@ -1368,6 +1385,32 @@ def API_getDashboardTheme():
return ResponseObject(data=DashboardConfig.GetConfig("Server", "dashboard_theme")[1])
@app.route('/api/savePeerScheduleJob/', methods=["POST"])
def API_savePeerScheduleJob():
data = request.json
if "Job" not in data.keys() not in WireguardConfigurations.keys():
return ResponseObject(False, "Please specify job")
job: dict = data['Job']
if "Peer" not in job.keys() or "Configuration" not in job.keys():
return ResponseObject(False, "Please specify peer and configuration")
configuration = WireguardConfigurations.get(job['Configuration'])
f, fp = configuration.searchPeer(job['Peer'])
if not f:
return ResponseObject(False, "Peer does not exist in this configuration")
s, p = AllPeerJobs.saveJob(PeerJob(
job['JobID'], job['Configuration'], job['Peer'], job['Field'], job['Operator'], job['Value'],
job['CreationDate'], job['ExpireDate'], job['Action']))
if s:
return ResponseObject(s, data=p)
return ResponseObject(s, message=p)
'''
Tools
'''
@app.route('/api/ping/getAllPeersIpAddress')
def API_ping_getAllPeersIpAddress():
ips = {}
@ -1546,19 +1589,6 @@ def gunicornConfig():
return app_ip, app_port
# def runGunicorn():
# sqldb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard.db'), check_same_thread=False)
# sqldb.row_factory = sqlite3.Row
# cursor = sqldb.cursor()
# _, app_ip = DashboardConfig.GetConfig("Server", "app_ip")
# _, app_port = DashboardConfig.GetConfig("Server", "app_port")
# WireguardConfigurations = _getConfigurationList()
# bgThread = threading.Thread(target=backGroundThread)
# bgThread.daemon = True
# bgThread.start()
# return app
sqldb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard.db'), check_same_thread=False)
sqldb.row_factory = sqlite3.Row
cursor = sqldb.cursor()
@ -1572,4 +1602,3 @@ bgThread.start()
if __name__ == "__main__":
app.run(host=app_ip, debug=True, port=app_port)

View File

@ -3,6 +3,7 @@ import ScheduleDropdown from "@/components/configurationComponents/peerScheduleJ
import SchedulePeerJob from "@/components/configurationComponents/peerScheduleJobsComponents/schedulePeerJob.vue";
export default {
name: "peerJobs",
props:{
selectedPeer: Object
},
@ -67,33 +68,65 @@ export default {
}
]
},
}
}
},
methods:{
deleteJob(j, index){
if (j.CreationDate){
}else{
this.selectedPeer.jobs = this.selectedPeer.jobs.filter(x => x.JobID !== j.JobID)
}
},
addJob(){
this.selectedPeer.jobs.push(JSON.parse(JSON.stringify({
JobID: crypto.randomUUID(),
Configuration: this.selectedPeer.configuration.Name,
Peer: this.selectedPeer.id,
Field: this.dropdowns.Field[0].value,
Operator: this.dropdowns.Operator[0].value,
Value: "",
CreationDate: "",
ExpireDate: "",
Action: this.dropdowns.Action[0].value
}))
)
}
}
}
</script>
<template>
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0">
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll">
<div class="container d-flex h-100 w-100">
<div class="card m-auto rounded-3 shadow" style="width: 700px">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2">
<h4 class="mb-0 fw-normal">Schedule Jobs
<strong></strong>
</h4>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div>
<div class="card-body px-4 pb-4 pt-0">
<!-- <div class="d-flex gap-2 mb-3">-->
<!-- <small>{{selectedPeer.name ? selectedPeer.name : "Untitled Peer"}}</small>-->
<!-- <small class="ms-auto"><samp>{{this.selectedPeer.id}}</samp></small>-->
<!-- </div>-->
<SchedulePeerJob
:dropdowns="this.dropdowns"
:pjob="job" v-for="job in this.selectedPeer.jobs">
</SchedulePeerJob>
<div class="m-auto modal-dialog-centered dashboardModal">
<div class="card rounded-3 shadow" style="width: 700px">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2">
<h4 class="mb-0 fw-normal">Schedule Jobs
<strong></strong>
</h4>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div>
<div class="card-body px-4 pb-4 pt-2">
<div class="d-flex align-items-center mb-3">
<input class="form-control form-control-sm w-auto rounded-3" placeholder="Search Job...">
<button class="btn btn-sm btn-primary rounded-3 ms-auto" @click="this.addJob()">
<i class="bi bi-plus-lg me-2"></i> Job
</button>
</div>
<TransitionGroup name="fade">
<SchedulePeerJob
@refresh="this.$emit('refresh')"
@delete="this.deleteJob(job)"
:dropdowns="this.dropdowns"
:key="job.JobID"
:pjob="job" v-for="(job) in this.selectedPeer.jobs">
</SchedulePeerJob>
</TransitionGroup>
</div>
</div>
</div>
</div>

View File

@ -551,6 +551,7 @@ export default {
</Transition>
<Transition name="fade">
<PeerJobs
@refresh="this.getPeers()"
v-if="this.peerScheduleJobs.modalOpen"
:selectedPeer="this.peerScheduleJobs.selectedPeer"
@close="this.peerScheduleJobs.modalOpen = false">

View File

@ -14,15 +14,17 @@ export default {
</script>
<template>
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0">
<div class="container d-flex h-100 w-100">
<div class="card m-auto rounded-3 shadow">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0">
<h4 class="mb-0">QR Code</h4>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div>
<div class="card-body">
<canvas id="qrcode" class="rounded-3 shadow" ref="qrcode"></canvas>
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll">
<div class="container d-flex h-100 w-100 m-auto">
<div class="modal-dialog-centered dashboardModal">
<div class="card m-auto rounded-3 shadow">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0">
<h4 class="mb-0">QR Code</h4>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div>
<div class="card-body">
<canvas id="qrcode" class="rounded-3 shadow" ref="qrcode"></canvas>
</div>
</div>
</div>
</div>
@ -30,8 +32,4 @@ export default {
</template>
<style scoped>
.peerSettingContainer {
background-color: #00000060;
z-index: 1000;
}
</style>

View File

@ -8,8 +8,10 @@ export default {
data: String,
edit: false
},
mounted() {
console.log(this.options)
setup(props) {
if (props.data === undefined){
this.$emit('update', this.options[0].value)
}
},
computed:{
currentSelection(){
@ -21,7 +23,8 @@ export default {
<template>
<div class="dropdown scheduleDropdown">
<button class="btn btn-sm btn-outline-primary rounded-3" :class="{disabled: !edit}" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<button class="btn btn-sm btn-outline-primary rounded-3"
:class="{'disabled': !edit}" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<samp>{{this.currentSelection.display}}</samp>
</button>
<ul class="dropdown-menu rounded-3 shadow" style="font-size: 0.875rem; width: 200px">
@ -40,4 +43,7 @@ export default {
opacity: 1;
background-color: rgba(13, 110, 253, 0.09);
}
.btn{
//padding: 0.1rem 0.4rem;
}
</style>

View File

@ -1,5 +1,8 @@
<script>
import ScheduleDropdown from "@/components/configurationComponents/peerScheduleJobsComponents/scheduleDropdown.vue";
import {ref} from "vue";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {fetchPost} from "@/utilities/fetch.js";
export default {
name: "schedulePeerJob",
@ -8,22 +11,45 @@ export default {
dropdowns: Array[Object],
pjob: Object
},
setup(props){
const job = ref({})
const edit = ref(false)
const newJob = ref(false)
job.value = JSON.parse(JSON.stringify(props.pjob))
if (!job.value.CreationDate){
edit.value = true
newJob.value = true
}
const store = DashboardConfigurationStore()
return {job, edit, newJob, store}
},
data(){
return {
job: Object,
inputType: undefined,
edit: false
}
},
beforeMount() {
this.job = JSON.parse(JSON.stringify(this.pjob))
watch:{
pjob: {
deep: true,
handler(newValue){
this.job = JSON.parse(JSON.stringify(newValue))
}
}
},
methods: {
save(){
if (this.job.Field && this.job.Operator && this.job.Action && this.job.Value){
if (this.job.Field === 'date'){
this.job.Value = new Date(this.job.Value).getTime();
}
fetchPost(`/api/savePeerScheduleJob/`, {
Job: this.job
}, (res) => {
if (res.status){
this.edit = false;
this.store.newMessage("Server", "Job Saved!", "success")
this.$emit("refresh")
}else{
this.store.newMessage("Server", res.message, "danger")
}
})
}else{
this.alert();
}
@ -40,23 +66,28 @@ export default {
}, 2000)
},
reset(){
this.job = JSON.parse(JSON.stringify(this.pjob));
this.edit = false;
if(this.job.CreationDate){
this.job = JSON.parse(JSON.stringify(this.pjob));
this.edit = false;
}else{
this.$emit('delete')
}
}
},
}
</script>
<template>
<div class="card shadow-sm rounded-3">
<div class="card shadow-sm rounded-3 mb-2" :class="{'border-warning-subtle': this.newJob}">
<div class="card-header bg-transparent text-muted border-0">
<small class="d-flex">
<small class="d-flex" v-if="!this.newJob">
<strong class="me-auto">Job ID</strong>
<samp>{{this.job.JobID}}</samp>
</small>
<small v-else><span class="badge text-bg-warning">Unsaved Job</span></small>
</div>
<div class="card-body pt-1" style="font-family: var(--bs-font-monospace)">
<div class="d-flex gap-3 align-items-center mb-2">
<div class="d-flex gap-2 align-items-center mb-2">
<samp>
if
</samp>
@ -87,18 +118,17 @@ export default {
v-model="this.job.Value"
style="width: auto">
<samp>
{{this.dropdowns.Field.find(x => x.value === this.job.Field).unit}} {
{{this.dropdowns.Field.find(x => x.value === this.job.Field)?.unit}} {
</samp>
</div>
<div class="px-5 d-flex gap-3 align-items-center">
<samp>execute</samp>
<div class="px-5 d-flex gap-2 align-items-center">
<samp>then</samp>
<ScheduleDropdown
:edit="edit"
:options="this.dropdowns.Action"
:data="this.job.Action"
@update="(value) => this.job.Action = value"
></ScheduleDropdown>
<samp>;</samp>
</div>
<div class="d-flex gap-3">
<samp>}</samp>
@ -123,5 +153,11 @@ export default {
</template>
<style scoped>
*{
font-size: 0.875rem;
}
input{
padding: 0.1rem 0.4rem;
}
</style>

View File

@ -55,128 +55,131 @@ export default {
<template>
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0">
<div class="container d-flex h-100 w-100">
<div class="card m-auto rounded-3 shadow" style="width: 700px">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4">
<h4 class="mb-0">Peer Settings</h4>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div>
<div class="card-body px-4 pb-4" v-if="this.data">
<div class="d-flex flex-column gap-2 mb-4">
<div>
<small class="text-muted">Public Key</small><br>
<small><samp>{{this.data.id}}</samp></small>
</div>
<div>
<label for="peer_name_textbox" class="form-label">
<small class="text-muted">Name</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.name"
id="peer_name_textbox" placeholder="">
</div>
<div>
<div class="d-flex position-relative">
<label for="peer_private_key_textbox" class="form-label">
<small class="text-muted">Private Key <code>(Required for QR Code and Download)</code></small>
</label>
<a role="button" class="ms-auto text-decoration-none toggleShowKey"
@click="this.showKey = !this.showKey"
>
<i class="bi" :class="[this.showKey ? 'bi-eye-slash-fill':'bi-eye-fill']"></i>
</a>
<div class="m-auto modal-dialog-centered dashboardModal">
<div class="card rounded-3 shadow flex-grow-1">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4">
<h4 class="mb-0">Peer Settings</h4>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div>
<div class="card-body px-4 pb-4" v-if="this.data">
<div class="d-flex flex-column gap-2 mb-4">
<div>
<small class="text-muted">Public Key</small><br>
<small><samp>{{this.data.id}}</samp></small>
</div>
<input :type="[this.showKey ? 'text':'password']" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.private_key"
id="peer_private_key_textbox"
style="padding-right: 40px">
</div>
<div>
<label for="peer_allowed_ip_textbox" class="form-label">
<small class="text-muted">Allowed IPs <code>(Required)</code></small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.allowed_ip"
id="peer_allowed_ip_textbox">
</div>
<div>
<label for="peer_endpoint_allowed_ips" class="form-label">
<small class="text-muted">Endpoint Allowed IPs <code>(Required)</code></small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.endpoint_allowed_ip"
id="peer_endpoint_allowed_ips">
</div>
<div>
<label for="peer_DNS_textbox" class="form-label">
<small class="text-muted">DNS</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.DNS"
id="peer_DNS_textbox">
</div>
<hr>
<div class="accordion mt-2" id="peerSettingsAccordion">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button rounded-3 collapsed" type="button"
data-bs-toggle="collapse" data-bs-target="#peerSettingsAccordionOptional">
Optional Settings
</button>
</h2>
<div id="peerSettingsAccordionOptional" class="accordion-collapse collapse"
data-bs-parent="#peerSettingsAccordion">
<div class="accordion-body d-flex flex-column gap-2 mb-2">
<div>
<label for="peer_preshared_key_textbox" class="form-label">
<small class="text-muted">Pre-Shared Key</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.preshared_key"
id="peer_preshared_key_textbox">
</div>
<div>
<label for="peer_mtu" class="form-label"><small class="text-muted">MTU</small></label>
<input type="number" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.mtu"
id="peer_mtu">
</div>
<div>
<label for="peer_keep_alive" class="form-label">
<small class="text-muted">Persistent Keepalive</small>
</label>
<input type="number" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.keepalive"
id="peer_keep_alive">
<div>
<label for="peer_name_textbox" class="form-label">
<small class="text-muted">Name</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.name"
id="peer_name_textbox" placeholder="">
</div>
<div>
<div class="d-flex position-relative">
<label for="peer_private_key_textbox" class="form-label">
<small class="text-muted">Private Key <code>(Required for QR Code and Download)</code></small>
</label>
<a role="button" class="ms-auto text-decoration-none toggleShowKey"
@click="this.showKey = !this.showKey"
>
<i class="bi" :class="[this.showKey ? 'bi-eye-slash-fill':'bi-eye-fill']"></i>
</a>
</div>
<input :type="[this.showKey ? 'text':'password']" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.private_key"
id="peer_private_key_textbox"
style="padding-right: 40px">
</div>
<div>
<label for="peer_allowed_ip_textbox" class="form-label">
<small class="text-muted">Allowed IPs <code>(Required)</code></small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.allowed_ip"
id="peer_allowed_ip_textbox">
</div>
<div>
<label for="peer_endpoint_allowed_ips" class="form-label">
<small class="text-muted">Endpoint Allowed IPs <code>(Required)</code></small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.endpoint_allowed_ip"
id="peer_endpoint_allowed_ips">
</div>
<div>
<label for="peer_DNS_textbox" class="form-label">
<small class="text-muted">DNS</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.DNS"
id="peer_DNS_textbox">
</div>
<hr>
<div class="accordion mt-2" id="peerSettingsAccordion">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button rounded-3 collapsed" type="button"
data-bs-toggle="collapse" data-bs-target="#peerSettingsAccordionOptional">
Optional Settings
</button>
</h2>
<div id="peerSettingsAccordionOptional" class="accordion-collapse collapse"
data-bs-parent="#peerSettingsAccordion">
<div class="accordion-body d-flex flex-column gap-2 mb-2">
<div>
<label for="peer_preshared_key_textbox" class="form-label">
<small class="text-muted">Pre-Shared Key</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.preshared_key"
id="peer_preshared_key_textbox">
</div>
<div>
<label for="peer_mtu" class="form-label"><small class="text-muted">MTU</small></label>
<input type="number" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.mtu"
id="peer_mtu">
</div>
<div>
<label for="peer_keep_alive" class="form-label">
<small class="text-muted">Persistent Keepalive</small>
</label>
<input type="number" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.keepalive"
id="peer_keep_alive">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="d-flex align-items-center gap-2">
<button class="btn btn-secondary rounded-3 shadow"
@click="this.reset()"
:disabled="!this.dataChanged || this.saving">
Reset <i class="bi bi-arrow-clockwise ms-2"></i>
</button>
<button class="ms-auto btn btn-dark btn-brand rounded-3 px-3 py-2 shadow"
:disabled="!this.dataChanged || this.saving"
@click="this.savePeer()"
>
Save Peer<i class="bi bi-save-fill ms-2"></i></button>
<div class="d-flex align-items-center gap-2">
<button class="btn btn-secondary rounded-3 shadow"
@click="this.reset()"
:disabled="!this.dataChanged || this.saving">
Reset <i class="bi bi-arrow-clockwise ms-2"></i>
</button>
<button class="ms-auto btn btn-dark btn-brand rounded-3 px-3 py-2 shadow"
:disabled="!this.dataChanged || this.saving"
@click="this.savePeer()"
>
Save Peer<i class="bi bi-save-fill ms-2"></i></button>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -14,8 +14,8 @@ export default {
</script>
<template>
<div class="col-md-3 col-lg-2 d-md-block p-3">
<nav id="sidebarMenu" class=" bg-body-tertiary sidebar border h-100 rounded-3 shadow" >
<div class="col-md-3 col-lg-2 d-md-block p-3" style="height: calc(-50px + 100vh);">
<nav id="sidebarMenu" class=" bg-body-tertiary sidebar border h-100 rounded-3 shadow overflow-y-scroll" >
<div class="sidebar-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">

View File

@ -1119,4 +1119,13 @@ pre.index-alert {
.peerSettingContainer {
background-color: #00000060;
z-index: 9999;
}
.dashboardModal{
min-height: calc(100% - 1.75rem * 2);
width: 700px
}
.dashboardModal > .card{
margin: 1.75rem;
}