diff --git a/src/dashboard.py b/src/dashboard.py index e4d4eefb..e04afadc 100644 --- a/src/dashboard.py +++ b/src/dashboard.py @@ -2,6 +2,8 @@ import logging import random, shutil, sqlite3, configparser, hashlib, ipaddress, json, os, secrets, subprocess import time, re, uuid, bcrypt, psutil, pyotp, threading import traceback +from functools import wraps +from urllib.parse import unquote from uuid import uuid4 from zipfile import ZipFile from datetime import datetime, timedelta @@ -68,6 +70,21 @@ def ResponseObject(status=True, message=None, data=None, status_code = 200) -> F response.content_type = "application/json" return response +def require_fields(*fields): + """Decorator that validates required fields in request.json.""" + def decorator(f): + @wraps(f) + def decorated(*args, **kwargs): + data = request.json + if data is None: + return ResponseObject(False, "Request body must be JSON", status_code=400) + missing = [field for field in fields if field not in data] + if missing: + return ResponseObject(False, f"Missing required fields: {', '.join(missing)}", status_code=400) + return f(*args, **kwargs) + return decorated + return decorator + ''' Flask App ''' @@ -1204,52 +1221,75 @@ def API_getDashboardTheme(): def API_getDashboardVersion(): return ResponseObject(data=DashboardConfig.GetConfig("Server", "version")[1]) -@app.post(f'{APP_PREFIX}/api/savePeerScheduleJob') +@app.post(f'{APP_PREFIX}/api/PeerScheduleJob') +@require_fields('Configuration', 'Peer', 'Field', 'Operator', 'Value', 'Action') def API_savePeerScheduleJob(): data = request.json - if "Job" not in data.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']) - if configuration is None: - return ResponseObject(False, "Configuration does not exist") - f, fp = configuration.searchPeer(job['Peer']) - if not f: - return ResponseObject(False, "Peer does not exist") - - - 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) -@app.post(f'{APP_PREFIX}/api/deletePeerScheduleJob') + configuration = WireguardConfigurations.get(data['Configuration']) + if configuration is None: + return ResponseObject(False, "Configuration does not exist", status_code=404) + + peerKey = unquote(data['Peer']) + found, _ = configuration.searchPeer(peerKey) + if not found: + return ResponseObject(False, "Peer does not exist", status_code=404) + + jobID = data.get('JobID', str(uuid4())) + if len(AllPeerJobs.searchJobById(jobID)) > 0: + return ResponseObject(False, "Job already exists", status_code=409) + + success, result = AllPeerJobs.saveJob(PeerJob( + jobID, data['Configuration'], peerKey, data['Field'], data['Operator'], data['Value'], + datetime.now(), data.get('ExpireDate'), data['Action'])) + if success: + return ResponseObject(success, data=result) + return ResponseObject(success, message=result) + +@app.put(f'{APP_PREFIX}/api/PeerScheduleJob') +@require_fields('JobID', 'Configuration', 'Peer', 'Field', 'Operator', 'Value', 'Action') +def API_updatePeerScheduleJob(): + data = request.json + + configuration = WireguardConfigurations.get(data['Configuration']) + if configuration is None: + return ResponseObject(False, "Configuration does not exist", status_code=404) + + peerKey = unquote(data['Peer']) + found, _ = configuration.searchPeer(peerKey) + if not found: + return ResponseObject(False, "Peer does not exist", status_code=404) + + existing = AllPeerJobs.searchJobById(data['JobID']) + if len(existing) == 0: + return ResponseObject(False, "Job does not exist", status_code=404) + + success, result = AllPeerJobs.saveJob(PeerJob( + data['JobID'], data['Configuration'], peerKey, data['Field'], data['Operator'], data['Value'], + datetime.now(), data.get('ExpireDate'), data['Action'])) + if success: + return ResponseObject(success, data=result) + return ResponseObject(success, message=result) + +@app.delete(f'{APP_PREFIX}/api/PeerScheduleJob') +@require_fields('JobID', 'Configuration', 'Peer') def API_deletePeerScheduleJob(): data = request.json - if "Job" not in data.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']) + + configuration = WireguardConfigurations.get(data['Configuration']) if configuration is None: - return ResponseObject(False, "Configuration does not exist") - # f, fp = configuration.searchPeer(job['Peer']) - # if not f: - # return ResponseObject(False, "Peer does not exist") + return ResponseObject(False, "Configuration does not exist", status_code=404) - s, p = AllPeerJobs.deleteJob(PeerJob( - job['JobID'], job['Configuration'], job['Peer'], job['Field'], job['Operator'], job['Value'], - job['CreationDate'], job['ExpireDate'], job['Action'])) - if s: - return ResponseObject(s) - return ResponseObject(s, message=p) + peerKey = unquote(data['Peer']) + success, result = AllPeerJobs.deleteJob(PeerJob( + data['JobID'], data['Configuration'], peerKey, data.get('Field', ''), + data.get('Operator', ''), data.get('Value', ''), + datetime.now(), data.get('ExpireDate'), data.get('Action', ''))) + if success: + return ResponseObject(success, message="Job deleted successfully") + return ResponseObject(success, message=result) -@app.get(f'{APP_PREFIX}/api/getPeerScheduleJobLogs/') +@app.get(f'{APP_PREFIX}/api/PeerScheduleJobLogs/') def API_getPeerScheduleJobLogs(configName): if configName not in WireguardConfigurations.keys(): return ResponseObject(False, "Configuration does not exist") diff --git a/src/static/app/src/components/configurationComponents/peerJobsLogsModal.vue b/src/static/app/src/components/configurationComponents/peerJobsLogsModal.vue index 4f59c1ad..c9f82a68 100644 --- a/src/static/app/src/components/configurationComponents/peerJobsLogsModal.vue +++ b/src/static/app/src/components/configurationComponents/peerJobsLogsModal.vue @@ -26,7 +26,7 @@ export default { methods: { async fetchLog(){ this.dataLoading = true; - await fetchGet(`/api/getPeerScheduleJobLogs/${this.configurationInfo.Name}`, {}, (res) => { + await fetchGet(`/api/PeerScheduleJobLogs/${this.configurationInfo.Name}`, {}, (res) => { this.data = res.data; this.logFetchTime = dayjs().format("YYYY-MM-DD HH:mm:ss") this.dataLoading = false; diff --git a/src/static/app/src/components/configurationComponents/peerScheduleJobsComponents/schedulePeerJob.vue b/src/static/app/src/components/configurationComponents/peerScheduleJobsComponents/schedulePeerJob.vue index 291043cd..e0d577e6 100644 --- a/src/static/app/src/components/configurationComponents/peerScheduleJobsComponents/schedulePeerJob.vue +++ b/src/static/app/src/components/configurationComponents/peerScheduleJobsComponents/schedulePeerJob.vue @@ -2,7 +2,7 @@ import ScheduleDropdown from "@/components/configurationComponents/peerScheduleJobsComponents/scheduleDropdown.vue"; import {ref} from "vue"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; -import {fetchPost} from "@/utilities/fetch.js"; +import {fetchPost, fetchPut, fetchDelete} from "@/utilities/fetch.js"; import { VueDatePicker } from "@vuepic/vue-datepicker"; import dayjs from "dayjs"; import LocaleText from "@/components/text/localeText.vue"; @@ -46,9 +46,8 @@ export default { methods: { save(){ if (this.job.Field && this.job.Operator && this.job.Action && this.job.Value){ - fetchPost(`/api/savePeerScheduleJob`, { - Job: this.job - }, (res) => { + const fn = this.newJob ? fetchPost : fetchPut; + fn(`/api/PeerScheduleJob`, this.job, (res) => { if (res.status){ this.edit = false; this.store.newMessage("Server", "Peer job saved", "success") @@ -84,9 +83,7 @@ export default { }, delete(){ if(this.job.CreationDate){ - fetchPost(`/api/deletePeerScheduleJob`, { - Job: this.job - }, (res) => { + fetchDelete(`/api/PeerScheduleJob`, this.job, (res) => { if (!res.status){ this.store.newMessage("Server", res.message, "danger") this.$emit('delete') diff --git a/src/static/app/src/utilities/fetch.js b/src/static/app/src/utilities/fetch.js index 51f4e947..ce10fd64 100644 --- a/src/static/app/src/utilities/fetch.js +++ b/src/static/app/src/utilities/fetch.js @@ -79,4 +79,50 @@ export const fetchPost = async (url, body, callback) => { console.log("Error:", x) router.push({path: '/signin'}) }) +} + +export const fetchPut = async (url, body, callback) => { + await fetch(`${getUrl(url)}`, { + headers: getHeaders(), + method: "PUT", + body: JSON.stringify(body) + }).then((x) => { + const store = DashboardConfigurationStore(); + if (!x.ok){ + if (x.status !== 200){ + if (x.status === 401){ + store.newMessage("WGDashboard", "Sign in session ended, please sign in again", "warning") + } + throw new Error(x.statusText) + } + }else{ + return x.json() + } + }).then(x => callback ? callback(x) : undefined).catch(x => { + console.log("Error:", x) + router.push({path: '/signin'}) + }) +} + +export const fetchDelete = async (url, body, callback) => { + await fetch(`${getUrl(url)}`, { + headers: getHeaders(), + method: "DELETE", + body: JSON.stringify(body) + }).then((x) => { + const store = DashboardConfigurationStore(); + if (!x.ok){ + if (x.status !== 200){ + if (x.status === 401){ + store.newMessage("WGDashboard", "Sign in session ended, please sign in again", "warning") + } + throw new Error(x.statusText) + } + }else{ + return x.json() + } + }).then(x => callback ? callback(x) : undefined).catch(x => { + console.log("Error:", x) + router.push({path: '/signin'}) + }) } \ No newline at end of file