Webhooks feature is done #669

This commit is contained in:
Donald Zou
2025-08-28 16:11:01 +08:00
parent c3c7e50f08
commit 85fa427134
8 changed files with 86 additions and 59 deletions

View File

@@ -270,11 +270,11 @@ def API_addWireguardConfiguration():
) )
WireguardConfigurations[data['ConfigurationName']] = ( WireguardConfigurations[data['ConfigurationName']] = (
WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data=data, name=data['ConfigurationName'])) if protocol == 'wg' else ( WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data=data, name=data['ConfigurationName'])) if protocol == 'wg' else (
AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data=data, name=data['ConfigurationName'])) AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data, name=data['ConfigurationName']))
else: else:
WireguardConfigurations[data['ConfigurationName']] = ( WireguardConfigurations[data['ConfigurationName']] = (
WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data=data)) if data.get('Protocol') == 'wg' else ( WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data)) if data.get('Protocol') == 'wg' else (
AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data=data)) AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, DashboardWebHooks, data=data))
return ResponseObject() return ResponseObject()
@app.get(f'{APP_PREFIX}/api/toggleWireguardConfiguration') @app.get(f'{APP_PREFIX}/api/toggleWireguardConfiguration')
@@ -371,7 +371,7 @@ def API_renameWireguardConfiguration():
status, message = rc.renameConfiguration(data.get("NewConfigurationName")) status, message = rc.renameConfiguration(data.get("NewConfigurationName"))
if status: if status:
WireguardConfigurations[data.get("NewConfigurationName")] = (WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data.get("NewConfigurationName")) if rc.Protocol == 'wg' else AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data.get("NewConfigurationName"))) WireguardConfigurations[data.get("NewConfigurationName")] = (WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data.get("NewConfigurationName")) if rc.Protocol == 'wg' else AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data.get("NewConfigurationName")))
else: else:
WireguardConfigurations[data.get("ConfigurationName")] = rc WireguardConfigurations[data.get("ConfigurationName")] = rc
return ResponseObject(status, message) return ResponseObject(status, message)
@@ -552,9 +552,13 @@ def API_updatePeerSettings(configName):
if wireguardConfig.Protocol == 'wg': if wireguardConfig.Protocol == 'wg':
status, msg = peer.updatePeer(name, private_key, preshared_key, dns_addresses, status, msg = peer.updatePeer(name, private_key, preshared_key, dns_addresses,
allowed_ip, endpoint_allowed_ip, mtu, keepalive) allowed_ip, endpoint_allowed_ip, mtu, keepalive)
return ResponseObject(status, msg) else:
status, msg = peer.updatePeer(name, private_key, preshared_key, dns_addresses, status, msg = peer.updatePeer(name, private_key, preshared_key, dns_addresses,
allowed_ip, endpoint_allowed_ip, mtu, keepalive, "off") allowed_ip, endpoint_allowed_ip, mtu, keepalive, "off")
DashboardWebHooks.RunWebHook('peer_updated', {
"configuration": wireguardConfig.Name,
"peers": [id]
})
return ResponseObject(status, msg) return ResponseObject(status, msg)
return ResponseObject(False, "Peer does not exist") return ResponseObject(False, "Peer does not exist")
@@ -749,10 +753,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', { # DashboardWebHooks.RunWebHook('peer_created', {
"configuration": config.Name, # "configuration": config.Name,
"peers": list(map(lambda p : p['id'], keyPairs)) # "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:
@@ -817,10 +821,10 @@ def API_addPeers(configName):
"advanced_security": "off" "advanced_security": "off"
}] }]
) )
DashboardWebHooks.RunWebHook('peer_created', { # DashboardWebHooks.RunWebHook('peer_created', {
"configuration": config.Name, # "configuration": config.Name,
"peers": [{"id": public_key}] # "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)
@@ -1473,9 +1477,9 @@ def InitWireguardConfigurationsList(startup: bool = False):
try: try:
if i in WireguardConfigurations.keys(): if i in WireguardConfigurations.keys():
if WireguardConfigurations[i].configurationFileChanged(): if WireguardConfigurations[i].configurationFileChanged():
WireguardConfigurations[i] = WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, i) WireguardConfigurations[i] = WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, i)
else: else:
WireguardConfigurations[i] = WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, i, startup=startup) WireguardConfigurations[i] = WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, i, startup=startup)
except WireguardConfiguration.InvalidConfigurationFileException as e: except WireguardConfiguration.InvalidConfigurationFileException as e:
print(f"{i} have an invalid configuration file.") print(f"{i} have an invalid configuration file.")
@@ -1488,9 +1492,9 @@ def InitWireguardConfigurationsList(startup: bool = False):
try: try:
if i in WireguardConfigurations.keys(): if i in WireguardConfigurations.keys():
if WireguardConfigurations[i].configurationFileChanged(): if WireguardConfigurations[i].configurationFileChanged():
WireguardConfigurations[i] = AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, i) WireguardConfigurations[i] = AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, i)
else: else:
WireguardConfigurations[i] = AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, i, startup=startup) WireguardConfigurations[i] = AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, i, startup=startup)
except WireguardConfiguration.InvalidConfigurationFileException as e: except WireguardConfiguration.InvalidConfigurationFileException as e:
print(f"{i} have an invalid configuration file.") print(f"{i} have an invalid configuration file.")

View File

@@ -8,12 +8,14 @@ from .AmneziaWGPeer import AmneziaWGPeer
from .PeerShareLinks import PeerShareLinks from .PeerShareLinks import PeerShareLinks
from .Utilities import RegexMatch from .Utilities import RegexMatch
from .WireguardConfiguration import WireguardConfiguration from .WireguardConfiguration import WireguardConfiguration
from .DashboardWebHooks import DashboardWebHooks
class AmneziaWireguardConfiguration(WireguardConfiguration): class AmneziaWireguardConfiguration(WireguardConfiguration):
def __init__(self, DashboardConfig, def __init__(self, DashboardConfig,
AllPeerJobs: PeerJobs, AllPeerJobs: PeerJobs,
AllPeerShareLinks: PeerShareLinks, AllPeerShareLinks: PeerShareLinks,
DashboardWebHooks: DashboardWebHooks,
name: str = None, data: dict = None, backup: dict = None, startup: bool = False): name: str = None, data: dict = None, backup: dict = None, startup: bool = False):
self.Jc = 0 self.Jc = 0
self.Jmin = 0 self.Jmin = 0
@@ -25,7 +27,7 @@ class AmneziaWireguardConfiguration(WireguardConfiguration):
self.H3 = 3 self.H3 = 3
self.H4 = 4 self.H4 = 4
super().__init__(DashboardConfig, AllPeerJobs, AllPeerShareLinks, name, data, backup, startup, wg=False) super().__init__(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, name, data, backup, startup, wg=False)
def toJson(self): def toJson(self):
self.Status = self.getStatus() self.Status = self.getStatus()
@@ -301,6 +303,10 @@ class AmneziaWireguardConfiguration(WireguardConfiguration):
p = self.searchPeer(p['id']) p = self.searchPeer(p['id'])
if p[0]: if p[0]:
result['peers'].append(p[1]) result['peers'].append(p[1])
self.DashboardWebHooks.RunWebHook("peer_created", {
"configuration": self.Name,
"peers": list(map(lambda k : k['id'], peers))
})
return True, result return True, result
except Exception as e: except Exception as e:
result['message'] = str(e) result['message'] = str(e)

View File

@@ -168,7 +168,8 @@ class DashboardWebHooks:
return False, str(e) return False, str(e)
return True, None return True, None
def RunWebHook(self, action: str, data: dict[str, str]): def RunWebHook(self, action: str, data):
try:
if action not in WebHookActions: if action not in WebHookActions:
return False return False
self.__getWebHooks() self.__getWebHooks()
@@ -176,11 +177,14 @@ class DashboardWebHooks:
data['action'] = action data['action'] = action
for i in subscribedWebHooks: for i in subscribedWebHooks:
try: try:
t = threading.Thread(target=WebHookSession, args=(i,data), daemon=True) ws = WebHookSession(i, data)
t = threading.Thread(target=ws.Execute, daemon=True)
t.start() t.start()
print("Spinning threads...") print("Spinning threads...")
except Exception as e: except Exception as e:
pass print(e)
except Exception as e:
print(e)
return True return True
class WebHookSession: class WebHookSession:
@@ -197,7 +201,6 @@ class WebHookSession:
data['webhook_session'] = self.sessionID data['webhook_session'] = self.sessionID
self.data = data self.data = data
self.Prepare() self.Prepare()
self.Execute(data)
def Prepare(self): def Prepare(self):
with self.engine.begin() as conn: with self.engine.begin() as conn:
@@ -235,7 +238,7 @@ class WebHookSession:
) )
) )
def Execute(self, data: dict[str, str]): def Execute(self):
success = False success = False
for i in range(5): for i in range(5):
@@ -247,15 +250,15 @@ class WebHookSession:
headerDictionary[header['key']] = header['value'] headerDictionary[header['key']] = header['value']
if self.webHook.ContentType == "application/json": if self.webHook.ContentType == "application/json":
reqData = json.dumps(data) reqData = json.dumps(self.data)
else: else:
for (key, val) in data.items(): for (key, val) in self.data.items():
if type(data[key]) not in [str, int]: if type(self.data[key]) not in [str, int]:
data[key] = json.dumps(data[key]) self.data[key] = json.dumps(self.data[key])
reqData = urllib.parse.urlencode(data) reqData = urllib.parse.urlencode(self.data)
try: try:
req = requests.post( req = requests.post(
self.webHook.PayloadURL, headers=headerDictionary, timeout=10, data=reqData self.webHook.PayloadURL, headers=headerDictionary, timeout=10, data=reqData, verify=self.webHook.VerifySSL
) )
req.raise_for_status() req.raise_for_status()
success = True success = True

View File

@@ -11,12 +11,14 @@ from itertools import islice
from .ConnectionString import ConnectionString from .ConnectionString import ConnectionString
from .DashboardConfig import DashboardConfig from .DashboardConfig import DashboardConfig
from .DashboardWebHooks import DashboardWebHooks
from .Peer import Peer from .Peer import Peer
from .PeerJobs import PeerJobs from .PeerJobs import PeerJobs
from .PeerShareLinks import PeerShareLinks from .PeerShareLinks import PeerShareLinks
from .Utilities import StringToBoolean, GenerateWireguardPublicKey, RegexMatch, ValidateDNSAddress, \ from .Utilities import StringToBoolean, GenerateWireguardPublicKey, RegexMatch, ValidateDNSAddress, \
ValidateEndpointAllowedIPs ValidateEndpointAllowedIPs
from .WireguardConfigurationInfo import WireguardConfigurationInfo, PeerGroupsClass from .WireguardConfigurationInfo import WireguardConfigurationInfo, PeerGroupsClass
from .DashboardWebHooks import DashboardWebHooks
class WireguardConfiguration: class WireguardConfiguration:
@@ -30,6 +32,7 @@ class WireguardConfiguration:
def __init__(self, DashboardConfig: DashboardConfig, def __init__(self, DashboardConfig: DashboardConfig,
AllPeerJobs: PeerJobs, AllPeerJobs: PeerJobs,
AllPeerShareLinks: PeerShareLinks, AllPeerShareLinks: PeerShareLinks,
DashboardWebHooks: DashboardWebHooks,
name: str = None, name: str = None,
data: dict = None, data: dict = None,
backup: dict = None, backup: dict = None,
@@ -61,6 +64,7 @@ class WireguardConfiguration:
self.AllPeerJobs = AllPeerJobs self.AllPeerJobs = AllPeerJobs
self.DashboardConfig = DashboardConfig self.DashboardConfig = DashboardConfig
self.AllPeerShareLinks = AllPeerShareLinks self.AllPeerShareLinks = AllPeerShareLinks
self.DashboardWebHooks = DashboardWebHooks
self.configPath = os.path.join(self.__getProtocolPath(), f'{self.Name}.conf') self.configPath = os.path.join(self.__getProtocolPath(), f'{self.Name}.conf')
self.engine: sqlalchemy.engine = sqlalchemy.create_engine(ConnectionString("wgdashboard")) self.engine: sqlalchemy.engine = sqlalchemy.create_engine(ConnectionString("wgdashboard"))
self.metadata: sqlalchemy.MetaData = sqlalchemy.MetaData() self.metadata: sqlalchemy.MetaData = sqlalchemy.MetaData()
@@ -497,6 +501,10 @@ class WireguardConfiguration:
p = self.searchPeer(p['id']) p = self.searchPeer(p['id'])
if p[0]: if p[0]:
result['peers'].append(p[1]) result['peers'].append(p[1])
self.DashboardWebHooks.RunWebHook("peer_created", {
"configuration": self.Name,
"peers": list(map(lambda k : k['id'], peers))
})
return True, result return True, result
except Exception as e: except Exception as e:
result['message'] = str(e) result['message'] = str(e)
@@ -598,6 +606,7 @@ class WireguardConfiguration:
def deletePeers(self, listOfPublicKeys, AllPeerJobs: PeerJobs, AllPeerShareLinks: PeerShareLinks) -> tuple[bool, str]: def deletePeers(self, listOfPublicKeys, AllPeerJobs: PeerJobs, AllPeerShareLinks: PeerShareLinks) -> tuple[bool, str]:
numOfDeletedPeers = 0 numOfDeletedPeers = 0
numOfFailedToDeletePeers = 0 numOfFailedToDeletePeers = 0
deleted = []
if not self.getStatus(): if not self.getStatus():
self.toggleConfiguration() self.toggleConfiguration()
with self.engine.begin() as conn: with self.engine.begin() as conn:
@@ -616,6 +625,7 @@ class WireguardConfiguration:
self.peersTable.columns.id == pf.id self.peersTable.columns.id == pf.id
) )
) )
deleted.append(pf.id)
numOfDeletedPeers += 1 numOfDeletedPeers += 1
except Exception as e: except Exception as e:
numOfFailedToDeletePeers += 1 numOfFailedToDeletePeers += 1
@@ -629,7 +639,12 @@ class WireguardConfiguration:
return False, "No peer(s) to delete found" return False, "No peer(s) to delete found"
if numOfDeletedPeers == len(listOfPublicKeys): if numOfDeletedPeers == len(listOfPublicKeys):
self.DashboardWebHooks.RunWebHook("peer_deleted", {
"configuration": self.Name,
"peers": deleted
})
return True, f"Deleted {numOfDeletedPeers} peer(s)" return True, f"Deleted {numOfDeletedPeers} peer(s)"
return False, f"Deleted {numOfDeletedPeers} peer(s) successfully. Failed to delete {numOfFailedToDeletePeers} peer(s)" return False, f"Deleted {numOfDeletedPeers} peer(s) successfully. Failed to delete {numOfFailedToDeletePeers} peer(s)"
def __wgSave(self) -> tuple[bool, str] | tuple[bool, None]: def __wgSave(self) -> tuple[bool, str] | tuple[bool, None]:

View File

@@ -4,8 +4,6 @@ import { fetchGet } from "@/utilities/fetch.js"
import {onMounted, ref} from "vue"; import {onMounted, ref} from "vue";
import AddWebHook from "@/components/settingsComponent/dashboardWebHooksComponents/addWebHook.vue"; import AddWebHook from "@/components/settingsComponent/dashboardWebHooksComponents/addWebHook.vue";
import WebHookSessions from "@/components/settingsComponent/dashboardWebHooksComponents/webHookSessions.vue"; import WebHookSessions from "@/components/settingsComponent/dashboardWebHooksComponents/webHookSessions.vue";
import ClientGroup from "@/components/clientComponents/clientGroup.vue";
import ClientSettings from "@/components/clientComponents/clientSettings.vue";
const webHooks = ref([]) const webHooks = ref([])
const webHooksLoaded = ref(false) const webHooksLoaded = ref(false)
@@ -34,14 +32,14 @@ const view = ref("edit")
<i class="bi bi-plug-fill me-2"></i> <i class="bi bi-plug-fill me-2"></i>
<LocaleText t="Webhooks"></LocaleText> <LocaleText t="Webhooks"></LocaleText>
</h6> </h6>
<button class="btn bg-primary-subtle text-primary-emphasis border-1 border-primary-subtle rounded-3 shadow-sm ms-auto" <button class="btn btn-sm bg-primary-subtle text-primary-emphasis border-1 border-primary-subtle rounded-3 shadow-sm ms-auto"
@click="addWebHook = true; selectedWebHook = undefined" @click="addWebHook = true; selectedWebHook = undefined"
v-if="!addWebHook" v-if="!addWebHook"
> >
<i class="bi bi-plus-circle-fill me-2"></i> <i class="bi bi-plus-circle-fill me-2"></i>
<LocaleText t="Webhook"></LocaleText> <LocaleText t="Webhook"></LocaleText>
</button> </button>
<button class="btn bg-secondary-subtle text-secondary-emphasis border-1 border-secondary-subtle rounded-3 shadow-sm ms-auto" <button class="btn btn-sm bg-secondary-subtle text-secondary-emphasis border-1 border-secondary-subtle rounded-3 shadow-sm ms-auto"
@click="addWebHook = false" @click="addWebHook = false"
v-else v-else
> >
@@ -105,7 +103,6 @@ const view = ref("edit")
:webHook="selectedWebHook" @refresh="getWebHooks()" ></AddWebHook> :webHook="selectedWebHook" @refresh="getWebHooks()" ></AddWebHook>
<Suspense v-else-if="view === 'sessions'"> <Suspense v-else-if="view === 'sessions'">
<WebHookSessions <WebHookSessions
:key="selectedWebHook" :key="selectedWebHook"
:webHook="selectedWebHook"></WebHookSessions> :webHook="selectedWebHook"></WebHookSessions>
<template #fallback> <template #fallback>

View File

@@ -31,7 +31,8 @@ const Actions = ref({
'peer_updated': "Peer Updated" 'peer_updated': "Peer Updated"
}) })
const emits = defineEmits(['refresh', 'delete']) const emits = defineEmits(['refresh', 'delete'])
import { DashboardConfigurationStore } from "@/stores/DashboardConfigurationStore"
const store = DashboardConfigurationStore()
const alert = ref(false) const alert = ref(false)
const alertMsg = ref("") const alertMsg = ref("")
const submitting = ref(false) const submitting = ref(false)
@@ -41,9 +42,11 @@ const submitWebHook = async (e) => {
await fetchPost("/api/webHooks/updateWebHook", newWebHook.value, (res) => { await fetchPost("/api/webHooks/updateWebHook", newWebHook.value, (res) => {
if (res.status){ if (res.status){
emits('refresh') emits('refresh')
store.newMessage("Server", "Webhook saved", "success")
}else{ }else{
alert.value = true alert.value = true
alertMsg.value = res.message alertMsg.value = res.message
store.newMessage("Server", "Webhook failed to save", "danger")
} }
submitting.value = false submitting.value = false
}) })
@@ -54,9 +57,11 @@ const deleteWebHook = async () => {
await fetchPost("/api/webHooks/deleteWebHook", newWebHook.value, (res) => { await fetchPost("/api/webHooks/deleteWebHook", newWebHook.value, (res) => {
if (res.status){ if (res.status){
emits('delete') emits('delete')
store.newMessage("Server", "Webhook deleted", "success")
}else{ }else{
alert.value = true alert.value = true
alertMsg.value = res.message alertMsg.value = res.message
store.newMessage("Server", "Webhook failed to delete", "danger")
} }
submitting.value = false submitting.value = false
}) })

View File

@@ -1,8 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import LocaleText from "@/components/text/localeText.vue"; import LocaleText from "@/components/text/localeText.vue";
import {computed} from "vue";
const props = defineProps(['session']) const props = defineProps(['session'])
const formattedBody = computed(() => {
return JSON.stringify(props.session.Data, null, 4)
})
</script> </script>
<template> <template>
@@ -94,7 +97,7 @@ const props = defineProps(['session'])
<LocaleText t="Data"></LocaleText> <LocaleText t="Data"></LocaleText>
</h6> </h6>
<div class="bg-body-tertiary p-3 rounded-3"> <div class="bg-body-tertiary p-3 rounded-3">
<pre class="mb-0"><code>{{ JSON.stringify(session.Data, null, 4) }}</code></pre> <pre class="mb-0"><code>{{ formattedBody }}</code></pre>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -27,13 +27,6 @@ const latestSession = computed(() => {
return sessions.value[0] return sessions.value[0]
}) })
// watch(() => latestSession.value.Status, () => {
// if (latestSession.value.Status > -1) clearInterval(refreshInterval.value)
// })
// if (latestSession.value.Status === -1){
//
// }
refreshInterval.value = setInterval(() => { refreshInterval.value = setInterval(() => {
getSessions() getSessions()
}, 5000) }, 5000)
@@ -48,7 +41,8 @@ onBeforeUnmount(() => {
<h6 class="mb-3"> <h6 class="mb-3">
<LocaleText t="Latest Session"></LocaleText> <LocaleText t="Latest Session"></LocaleText>
</h6> </h6>
<WebHookSession :session="latestSession" :key="latestSession.WebHookSessionID"></WebHookSession> <WebHookSession :session="latestSession"
:key="latestSession.WebHookID"></WebHookSession>
</div> </div>
<div class="border-top p-3" v-if="sessions.length > 1"> <div class="border-top p-3" v-if="sessions.length > 1">
<h6> <h6>