diff --git a/src/dashboard.py b/src/dashboard.py index c72e1807..b59dc86b 100644 --- a/src/dashboard.py +++ b/src/dashboard.py @@ -749,6 +749,10 @@ def API_addPeers(configName): if len(keyPairs) == 0 or (bulkAdd and len(keyPairs) != bulkAddAmount): return ResponseObject(False, "Generating key pairs by bulk failed") 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']) else: @@ -813,6 +817,10 @@ def API_addPeers(configName): "advanced_security": "off" }] ) + DashboardWebHooks.RunWebHook('peer_created', { + "configuration": config.Name, + "peers": [{"id": public_key}] + }) return ResponseObject(status=status, message=result['message'], data=result['peers']) except Exception as e: app.logger.error("Add peers failed", data, exc_info=e) @@ -1386,7 +1394,14 @@ def API_WebHooks_UpdateWebHook(): data = request.get_json() status, msg = DashboardWebHooks.UpdateWebHook(data) 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 ''' diff --git a/src/modules/DashboardWebHooks.py b/src/modules/DashboardWebHooks.py index 06e53a87..45205fde 100644 --- a/src/modules/DashboardWebHooks.py +++ b/src/modules/DashboardWebHooks.py @@ -1,7 +1,12 @@ +import json +import threading +import time +import urllib.parse import uuid from datetime import datetime -from pydantic import BaseModel +import requests +from pydantic import BaseModel, field_serializer import sqlalchemy as db from .ConnectionString import ConnectionString @@ -17,6 +22,21 @@ class WebHook(BaseModel): CreationDate: datetime = '' 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: def __init__(self, DashboardConfig): self.engine = db.create_engine(ConnectionString("wgdashboard")) @@ -37,6 +57,22 @@ class DashboardWebHooks: db.Column('Notes', db.Text), 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.WebHooks: list[WebHook] = [] self.__getWebHooks() @@ -72,7 +108,9 @@ class DashboardWebHooks: if len(webHook.PayloadURL) == 0: 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" @@ -99,13 +137,115 @@ class DashboardWebHooks: def DeleteWebHook(self, webHook) -> tuple[bool, str] | tuple[bool, None]: try: - webHook = WebHook.model_validate(webHook) + webHook = WebHook(**webHook) with self.engine.begin() as conn: conn.execute( self.webHooksTable.delete().where( self.webHooksTable.c.WebHookID == webHook.WebHookID ) ) + self.__getWebHooks() except Exception as e: return False, str(e) - return True, None \ No newline at end of file + 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) + + \ No newline at end of file diff --git a/src/modules/WireguardConfiguration.py b/src/modules/WireguardConfiguration.py index 4917922e..10949b56 100644 --- a/src/modules/WireguardConfiguration.py +++ b/src/modules/WireguardConfiguration.py @@ -444,7 +444,6 @@ class WireguardConfiguration: existingPeers = conn.execute(self.peersTable.select()).mappings().fetchall() for i in existingPeers: tmpList.append(Peer(i, self)) - self.Peers = [] self.Peers = tmpList def addPeers(self, peers: list) -> tuple[bool, dict]: diff --git a/src/static/app/src/components/settingsComponent/dashboardWebHooks.vue b/src/static/app/src/components/settingsComponent/dashboardWebHooks.vue index e44a6c2e..95c84fc6 100644 --- a/src/static/app/src/components/settingsComponent/dashboardWebHooks.vue +++ b/src/static/app/src/components/settingsComponent/dashboardWebHooks.vue @@ -57,18 +57,22 @@ const selectedWebHook = ref(undefined)

{{ webHook.PayloadURL }}

- +

: {{ webHook.SubscribedActions.join(", ")}} - +

-
- +
+
diff --git a/src/static/app/src/components/settingsComponent/dashboardWebHooksComponents/addWebHook.vue b/src/static/app/src/components/settingsComponent/dashboardWebHooksComponents/addWebHook.vue index b562de51..f9321225 100644 --- a/src/static/app/src/components/settingsComponent/dashboardWebHooksComponents/addWebHook.vue +++ b/src/static/app/src/components/settingsComponent/dashboardWebHooksComponents/addWebHook.vue @@ -30,7 +30,7 @@ const Actions = ref({ 'peer_deleted': "Peer Deleted", 'peer_updated': "Peer Updated" }) -const emits = defineEmits(['refresh']) +const emits = defineEmits(['refresh', 'delete']) const alert = ref(false) const alertMsg = ref("") @@ -48,6 +48,19 @@ const submitWebHook = async (e) => { 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 + }) +}