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

View File

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

View File

@@ -168,19 +168,23 @@ class DashboardWebHooks:
return False, str(e)
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)
data['action'] = action
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
def RunWebHook(self, action: str, data):
try:
if action not in WebHookActions:
return False
self.__getWebHooks()
subscribedWebHooks = filter(lambda webhook: action in webhook.SubscribedActions, self.WebHooks)
data['action'] = action
for i in subscribedWebHooks:
try:
ws = WebHookSession(i, data)
t = threading.Thread(target=ws.Execute, daemon=True)
t.start()
print("Spinning threads...")
except Exception as e:
print(e)
except Exception as e:
print(e)
return True
class WebHookSession:
@@ -197,7 +201,6 @@ class WebHookSession:
data['webhook_session'] = self.sessionID
self.data = data
self.Prepare()
self.Execute(data)
def Prepare(self):
with self.engine.begin() as conn:
@@ -235,7 +238,7 @@ class WebHookSession:
)
)
def Execute(self, data: dict[str, str]):
def Execute(self):
success = False
for i in range(5):
@@ -247,15 +250,15 @@ class WebHookSession:
headerDictionary[header['key']] = header['value']
if self.webHook.ContentType == "application/json":
reqData = json.dumps(data)
reqData = json.dumps(self.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)
for (key, val) in self.data.items():
if type(self.data[key]) not in [str, int]:
self.data[key] = json.dumps(self.data[key])
reqData = urllib.parse.urlencode(self.data)
try:
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()
success = True

View File

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

View File

@@ -4,8 +4,6 @@ import { fetchGet } from "@/utilities/fetch.js"
import {onMounted, ref} from "vue";
import AddWebHook from "@/components/settingsComponent/dashboardWebHooksComponents/addWebHook.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 webHooksLoaded = ref(false)
@@ -34,14 +32,14 @@ const view = ref("edit")
<i class="bi bi-plug-fill me-2"></i>
<LocaleText t="Webhooks"></LocaleText>
</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"
v-if="!addWebHook"
>
<i class="bi bi-plus-circle-fill me-2"></i>
<LocaleText t="Webhook"></LocaleText>
</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"
v-else
>
@@ -105,7 +103,6 @@ const view = ref("edit")
:webHook="selectedWebHook" @refresh="getWebHooks()" ></AddWebHook>
<Suspense v-else-if="view === 'sessions'">
<WebHookSessions
:key="selectedWebHook"
:webHook="selectedWebHook"></WebHookSessions>
<template #fallback>

View File

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

View File

@@ -1,8 +1,11 @@
<script setup lang="ts">
import LocaleText from "@/components/text/localeText.vue";
import {computed} from "vue";
const props = defineProps(['session'])
const formattedBody = computed(() => {
return JSON.stringify(props.session.Data, null, 4)
})
</script>
<template>
@@ -94,7 +97,7 @@ const props = defineProps(['session'])
<LocaleText t="Data"></LocaleText>
</h6>
<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>

View File

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