Testing webhooks

This commit is contained in:
Donald Zou
2025-08-25 16:06:41 +08:00
parent f6e625c5f8
commit f360ef5d2f
5 changed files with 183 additions and 12 deletions

View File

@@ -749,6 +749,10 @@ def API_addPeers(configName):
if len(keyPairs) == 0 or (bulkAdd and len(keyPairs) != bulkAddAmount): if len(keyPairs) == 0 or (bulkAdd and len(keyPairs) != bulkAddAmount):
return ResponseObject(False, "Generating key pairs by bulk failed") return ResponseObject(False, "Generating key pairs by bulk failed")
status, result = config.addPeers(keyPairs) status, result = config.addPeers(keyPairs)
DashboardWebHooks.RunWebHook('peer_created', {
"configuration": config.Name,
"peers": list(map(lambda p : p.id, keyPairs))
})
return ResponseObject(status=status, message=result['message'], data=result['peers']) return ResponseObject(status=status, message=result['message'], data=result['peers'])
else: else:
@@ -813,6 +817,10 @@ def API_addPeers(configName):
"advanced_security": "off" "advanced_security": "off"
}] }]
) )
DashboardWebHooks.RunWebHook('peer_created', {
"configuration": config.Name,
"peers": [{"id": public_key}]
})
return ResponseObject(status=status, message=result['message'], data=result['peers']) return ResponseObject(status=status, message=result['message'], data=result['peers'])
except Exception as e: except Exception as e:
app.logger.error("Add peers failed", data, exc_info=e) app.logger.error("Add peers failed", data, exc_info=e)
@@ -1386,7 +1394,14 @@ def API_WebHooks_UpdateWebHook():
data = request.get_json() data = request.get_json()
status, msg = DashboardWebHooks.UpdateWebHook(data) status, msg = DashboardWebHooks.UpdateWebHook(data)
return ResponseObject(status, msg) return ResponseObject(status, msg)
@app.post(f'{APP_PREFIX}/api/webHooks/deleteWebHook')
def API_WebHooks_DeleteWebHook():
data = request.get_json()
status, msg = DashboardWebHooks.DeleteWebHook(data)
return ResponseObject(status, msg)
''' '''
Index Page Index Page
''' '''

View File

@@ -1,7 +1,12 @@
import json
import threading
import time
import urllib.parse
import uuid import uuid
from datetime import datetime from datetime import datetime
from pydantic import BaseModel import requests
from pydantic import BaseModel, field_serializer
import sqlalchemy as db import sqlalchemy as db
from .ConnectionString import ConnectionString from .ConnectionString import ConnectionString
@@ -17,6 +22,21 @@ class WebHook(BaseModel):
CreationDate: datetime = '' CreationDate: datetime = ''
Notes: str = '' Notes: str = ''
class WebHookSessionLog(BaseModel):
LogTime: datetime
Status: int
Message: str = ''
@field_serializer('LogTime')
def logTimeSerializer(self, LogTime: datetime):
return LogTime.strftime("%Y-%m-%d %H:%M:%S")
class WebHookSessionLogs(BaseModel):
Logs: list[WebHookSessionLog] = []
def addLog(self, status: int, message: str):
self.Logs.append(WebHookSessionLog(LogTime=datetime.now(), Status=status, Message=message))
class DashboardWebHooks: class DashboardWebHooks:
def __init__(self, DashboardConfig): def __init__(self, DashboardConfig):
self.engine = db.create_engine(ConnectionString("wgdashboard")) self.engine = db.create_engine(ConnectionString("wgdashboard"))
@@ -37,6 +57,22 @@ class DashboardWebHooks:
db.Column('Notes', db.Text), db.Column('Notes', db.Text),
extend_existing=True extend_existing=True
) )
self.webHookSessionsTable = db.Table(
'DashboardWebHookSessions', self.metadata,
db.Column('WebHookSessionID', db.String(255), nullable=False, primary_key=True),
db.Column('WebHookID', db.String(255), nullable=False),
db.Column('StartDate',
(db.DATETIME if DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else db.TIMESTAMP),
server_default=db.func.now(),
nullable=False
),
db.Column('EndDate',
(db.DATETIME if DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else db.TIMESTAMP),
),
db.Column('Status', db.INTEGER),
db.Column('Logs', db.JSON)
)
self.metadata.create_all(self.engine) self.metadata.create_all(self.engine)
self.WebHooks: list[WebHook] = [] self.WebHooks: list[WebHook] = []
self.__getWebHooks() self.__getWebHooks()
@@ -72,7 +108,9 @@ class DashboardWebHooks:
if len(webHook.PayloadURL) == 0: if len(webHook.PayloadURL) == 0:
return False, "Payload URL cannot be empty" return False, "Payload URL cannot be empty"
if len(webHook.ContentType) == 0 or webHook.ContentType not in ['application/json', 'application/x-www-form-urlencoded']: if len(webHook.ContentType) == 0 or webHook.ContentType not in [
'application/json', 'application/x-www-form-urlencoded'
]:
return False, "Content Type is invalid" return False, "Content Type is invalid"
@@ -99,13 +137,115 @@ class DashboardWebHooks:
def DeleteWebHook(self, webHook) -> tuple[bool, str] | tuple[bool, None]: def DeleteWebHook(self, webHook) -> tuple[bool, str] | tuple[bool, None]:
try: try:
webHook = WebHook.model_validate(webHook) webHook = WebHook(**webHook)
with self.engine.begin() as conn: with self.engine.begin() as conn:
conn.execute( conn.execute(
self.webHooksTable.delete().where( self.webHooksTable.delete().where(
self.webHooksTable.c.WebHookID == webHook.WebHookID self.webHooksTable.c.WebHookID == webHook.WebHookID
) )
) )
self.__getWebHooks()
except Exception as e: except Exception as e:
return False, str(e) return False, str(e)
return True, None return True, None
def RunWebHook(self, action: str, data: dict[str, str]):
if action not in WebHookActions:
return False
self.__getWebHooks()
subscribedWebHooks = filter(lambda webhook: action in webhook.SubscribedActions, self.WebHooks)
for i in subscribedWebHooks:
try:
t = threading.Thread(target=WebHookSession, args=(i,data), daemon=True)
t.start()
print("Spinning threads...")
except Exception as e:
pass
return True
class WebHookSession:
def __init__(self, webHook: WebHook, data: dict[str, str]):
self.engine = db.create_engine(ConnectionString("wgdashboard"))
self.metadata = db.MetaData()
self.webHookSessionsTable = db.Table('DashboardWebHookSessions', self.metadata, autoload_with=self.engine)
self.webHook = webHook
self.sessionID = str(uuid.uuid4())
self.webHookSessionLogs: WebHookSessionLogs = WebHookSessionLogs()
data['time'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
data['webhook_id'] = webHook.WebHookID
data['webhook_session'] = self.sessionID
self.Prepare()
self.Execute(data)
def Prepare(self):
with self.engine.begin() as conn:
conn.execute(
self.webHookSessionsTable.insert().values({
"WebHookSessionID": self.sessionID,
"WebHookID": self.webHook.WebHookID,
"Status": -1,
"Logs": self.webHookSessionLogs.model_dump()
})
)
self.UpdateSessionLog(-1, "Preparing webhook session")
def UpdateSessionLog(self, status, message):
self.webHookSessionLogs.addLog(status, message)
with self.engine.begin() as conn:
conn.execute(
self.webHookSessionsTable.update().values({
"Logs": self.webHookSessionLogs.model_dump()
}).where(
self.webHookSessionsTable.c.WebHookSessionID == self.sessionID
)
)
def UpdateStatus(self, status: int):
with self.engine.begin() as conn:
conn.execute(
self.webHookSessionsTable.update().values({
"Status": status,
"EndDate": datetime.now()
}).where(
self.webHookSessionsTable.c.WebHookSessionID == self.sessionID
)
)
def Execute(self, data: dict[str, str]):
success = False
for i in range(5):
headerDictionary = {
'Content-Type': self.webHook.ContentType
}
for header in self.webHook.Headers.values():
if header['key'] not in ['Content-Type']:
headerDictionary[header['key']] = header['value']
if self.webHook.ContentType == "application/json":
reqData = json.dumps(data)
else:
for (key, val) in data.items():
if type(data[key]) not in [str, int]:
data[key] = json.dumps(data[key])
reqData = urllib.parse.urlencode(data)
try:
req = requests.post(
self.webHook.PayloadURL, headers=headerDictionary, timeout=10, data=reqData
)
req.raise_for_status()
success = True
self.UpdateSessionLog(0, "Webhook request finished")
self.UpdateSessionLog(0, json.dumps({"returned_data": req.text}))
self.UpdateStatus(0)
break
except requests.exceptions.RequestException as e:
self.UpdateSessionLog(1, f"Attempt #{i + 1}/5. Request errored. Reason: " + str(e))
time.sleep(5)
if not success:
self.UpdateSessionLog(1, "Webhook request failed & terminated.")
self.UpdateStatus(1)

View File

@@ -444,7 +444,6 @@ class WireguardConfiguration:
existingPeers = conn.execute(self.peersTable.select()).mappings().fetchall() existingPeers = conn.execute(self.peersTable.select()).mappings().fetchall()
for i in existingPeers: for i in existingPeers:
tmpList.append(Peer(i, self)) tmpList.append(Peer(i, self))
self.Peers = []
self.Peers = tmpList self.Peers = tmpList
def addPeers(self, peers: list) -> tuple[bool, dict]: def addPeers(self, peers: list) -> tuple[bool, dict]:

View File

@@ -57,18 +57,22 @@ const selectedWebHook = ref(undefined)
<p class="mb-0 fw-bold text-body url" > <p class="mb-0 fw-bold text-body url" >
{{ webHook.PayloadURL }} {{ webHook.PayloadURL }}
</p> </p>
<small> <p class="url mb-0">
<LocaleText t="Subscribed Actions"></LocaleText>: <LocaleText t="Subscribed Actions"></LocaleText>:
{{ webHook.SubscribedActions.join(", ")}} {{ webHook.SubscribedActions.join(", ")}}
</small> </p>
</a> </a>
<div class="flex-grow-1 d-flex text-muted" v-else> <div class="flex-grow-1 d-flex text-muted" v-else>
<LocaleText t="No Webhooks" class="m-auto"></LocaleText> <LocaleText t="No Webhooks" class="m-auto"></LocaleText>
</div> </div>
</div> </div>
</div> </div>
<div class="col-sm-8 overflow-scroll h-100" v-if="selectedWebHook"> <div class="col-sm-8 overflow-scroll h-100" >
<AddWebHook :webHook="selectedWebHook" @refresh="getWebHooks()" :key="selectedWebHook.WebHookID"></AddWebHook> <AddWebHook
:key="selectedWebHook"
v-if="selectedWebHook"
@delete="getWebHooks(); selectedWebHook = undefined;"
:webHook="selectedWebHook" @refresh="getWebHooks()" ></AddWebHook>
</div> </div>
</div> </div>
<suspense v-else> <suspense v-else>

View File

@@ -30,7 +30,7 @@ const Actions = ref({
'peer_deleted': "Peer Deleted", 'peer_deleted': "Peer Deleted",
'peer_updated': "Peer Updated" 'peer_updated': "Peer Updated"
}) })
const emits = defineEmits(['refresh']) const emits = defineEmits(['refresh', 'delete'])
const alert = ref(false) const alert = ref(false)
const alertMsg = ref("") const alertMsg = ref("")
@@ -48,6 +48,19 @@ const submitWebHook = async (e) => {
submitting.value = false submitting.value = false
}) })
} }
const deleteWebHook = async () => {
submitting.value = true;
await fetchPost("/api/webHooks/deleteWebHook", newWebHook.value, (res) => {
if (res.status){
emits('delete')
}else{
alert.value = true
alertMsg.value = res.message
}
submitting.value = false
})
}
</script> </script>
<template> <template>
@@ -208,7 +221,7 @@ const submitWebHook = async (e) => {
<h6 class="mb-0"> <h6 class="mb-0">
<LocaleText t="Danger Zone"></LocaleText></h6> <LocaleText t="Danger Zone"></LocaleText></h6>
<button <button
@click="confirmDelete = true" @click="deleteWebHook()"
type="button" type="button"
:class="{disabled: submitting}" :class="{disabled: submitting}"
class="btn bg-danger-subtle text-danger-emphasis border-danger-subtle rounded-3 ms-auto"> class="btn bg-danger-subtle text-danger-emphasis border-danger-subtle rounded-3 ms-auto">