Refactor peer schedule job API to use proper REST verbs
Replace single POST endpoints with POST/PUT/DELETE for peer schedule jobs. Add require_fields decorator for request validation. Add fetchPut and fetchDelete helpers in the frontend. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>pull/1239/head
parent
66b0a2d88a
commit
26f9559363
116
src/dashboard.py
116
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/<configName>')
|
||||
@app.get(f'{APP_PREFIX}/api/PeerScheduleJobLogs/<configName>')
|
||||
def API_getPeerScheduleJobLogs(configName):
|
||||
if configName not in WireguardConfigurations.keys():
|
||||
return ResponseObject(False, "Configuration does not exist")
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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'})
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue