Finished most of the webhook UI

This commit is contained in:
Donald Zou
2025-08-26 00:41:37 +08:00
parent c83a075886
commit f865317600
4 changed files with 217 additions and 9 deletions

View File

@@ -1401,6 +1401,18 @@ def API_WebHooks_DeleteWebHook():
status, msg = DashboardWebHooks.DeleteWebHook(data)
return ResponseObject(status, msg)
@app.get(f'{APP_PREFIX}/api/webHooks/getWebHookSessions')
def API_WebHooks_GetWebHookSessions():
webhookID = request.args.get('WebHookID')
if not webhookID:
return ResponseObject(False, "Please provide WebHookID")
webHook = DashboardWebHooks.SearchWebHookByID(webhookID)
if not webHook:
return ResponseObject(False, "Webhook does not exist")
return ResponseObject(data=DashboardWebHooks.GetWebHookSessions(webHook))
'''
Index Page

View File

@@ -69,6 +69,7 @@ class DashboardWebHooks:
db.Column('EndDate',
(db.DATETIME if DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else db.TIMESTAMP),
),
db.Column('Data', db.JSON),
db.Column('Status', db.INTEGER),
db.Column('Logs', db.JSON)
)
@@ -91,6 +92,17 @@ class DashboardWebHooks:
self.__getWebHooks()
return list(map(lambda x : x.model_dump(), self.WebHooks))
def GetWebHookSessions(self, webHook: WebHook):
with self.engine.connect() as conn:
sessions = conn.execute(
self.webHookSessionsTable.select().where(
self.webHookSessionsTable.c.WebHookID == webHook.WebHookID
).order_by(
db.desc(self.webHookSessionsTable.c.StartDate)
)
).mappings().fetchall()
return sessions
def CreateWebHook(self) -> WebHook:
return WebHook(WebHookID=str(uuid.uuid4()))
@@ -101,6 +113,13 @@ class DashboardWebHooks:
return None
return first
def SearchWebHookByID(self, webHookID: str) -> WebHook | None:
try:
first = next(filter(lambda x : x.WebHookID == webHookID, self.WebHooks))
except StopIteration:
return None
return first
def UpdateWebHook(self, webHook: dict[str, str]) -> tuple[bool, str] | tuple[bool, None]:
try:
webHook = WebHook(**webHook)
@@ -154,6 +173,7 @@ class DashboardWebHooks:
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)
@@ -171,11 +191,11 @@ class WebHookSession:
self.webHook = webHook
self.sessionID = str(uuid.uuid4())
self.webHookSessionLogs: WebHookSessionLogs = WebHookSessionLogs()
data['time'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.time = datetime.now()
data['time'] = self.time.strftime("%Y-%m-%d %H:%M:%S")
data['webhook_id'] = webHook.WebHookID
data['webhook_session'] = self.sessionID
data['content'] = 'hi!'
self.data = data
self.Prepare()
self.Execute(data)
@@ -185,6 +205,8 @@ class WebHookSession:
self.webHookSessionsTable.insert().values({
"WebHookSessionID": self.sessionID,
"WebHookID": self.webHook.WebHookID,
"Data": self.data,
"StartDate": self.time,
"Status": -1,
"Logs": self.webHookSessionLogs.model_dump()
})
@@ -243,10 +265,8 @@ class WebHookSession:
break
except requests.exceptions.RequestException as e:
self.UpdateSessionLog(1, f"Attempt #{i + 1}/5. Request errored. Reason: " + str(e))
time.sleep(5)
time.sleep(10)
if not success:
self.UpdateSessionLog(1, "Webhook request failed & terminated.")
self.UpdateStatus(1)

View File

@@ -3,6 +3,7 @@ import LocaleText from "@/components/text/localeText.vue";
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";
const webHooks = ref([])
const webHooksLoaded = ref(false)
@@ -19,6 +20,7 @@ const getWebHooks = async () => {
const addWebHook = ref(false)
const selectedWebHook = ref(undefined)
const view = ref("edit")
</script>
<template>
@@ -67,12 +69,43 @@ const selectedWebHook = ref(undefined)
</div>
</div>
</div>
<div class="col-sm-8 overflow-scroll h-100" >
<div class="col-sm-8 overflow-scroll h-100" v-if="selectedWebHook">
<nav class="navbar navbar-expand-lg bg-body-tertiary sticky-top">
<div class="container-fluid">
<div>
<ul class="navbar-nav gap-2">
<li class="nav-item">
<a
@click="view = 'edit'"
:class="{active: view === 'edit'}"
class="nav-link rounded-3" role="button">Edit</a>
</li>
<li class="nav-item">
<a
:class="{active: view === 'sessions'}"
@click="view = 'sessions'"
class="nav-link rounded-3" role="button">Sessions</a>
</li>
</ul>
</div>
</div>
</nav>
<AddWebHook
v-if="view === 'edit'"
:key="selectedWebHook"
v-if="selectedWebHook"
@delete="getWebHooks(); selectedWebHook = undefined;"
:webHook="selectedWebHook" @refresh="getWebHooks()" ></AddWebHook>
<Suspense v-else-if="view === 'sessions'">
<WebHookSessions
:key="selectedWebHook"
:webHook="selectedWebHook"></WebHookSessions>
<template #fallback>
<div class="p-3">
<LocaleText t="Loading..."></LocaleText>
</div>
</template>
</Suspense>
</div>
</div>
<suspense v-else>

View File

@@ -0,0 +1,143 @@
<script setup lang="ts">
import { fetchGet } from "@/utilities/fetch.js"
import LocaleText from "@/components/text/localeText.vue";
import {computed, ref, watch} from "vue";
const props = defineProps(['webHook'])
const sessions = ref([])
const refreshInterval = ref(undefined);
const getSessions = async () => {
await fetchGet("/api/webHooks/getWebHookSessions", {
WebHookID: props.webHook.WebHookID
}, (res) => {
sessions.value = res.data
})
}
await getSessions()
const latestSession = computed(() => {
if (!sessions.value){
return undefined
}
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)
}
</script>
<template>
<div class="p-3" v-if="latestSession">
<h6 class="mb-3">
<LocaleText t="Latest Session"></LocaleText>
</h6>
<h3 :class="{'text-success': latestSession.Status === 0, 'text-danger': latestSession.Status === 1}">
<span v-if="latestSession.Status === 0">
<i class="bi bi-check-circle-fill me-2"></i><LocaleText t="Success"></LocaleText>
</span>
<span v-else-if="latestSession.Status === 1">
<i class="bi bi-x-circle-fill me-2"></i><LocaleText t="Failed"></LocaleText>
</span>
<span v-else-if="latestSession.Status === -1">
<i class="spinner-border me-2"></i><LocaleText t="Requesting..."></LocaleText>
</span>
</h3>
<div class="d-flex gap-4 align-items-center">
<div>
<small class="text-muted">
<LocaleText t="Started At"></LocaleText>
</small>
<h6>
{{ latestSession.StartDate }}
</h6>
</div>
<div v-if="latestSession.EndDate">
<i class="bi bi-arrow-right"></i>
</div>
<div v-if="latestSession.EndDate">
<small class="text-muted">
<LocaleText t="Ended At"></LocaleText>
</small>
<h6>
{{ latestSession.EndDate }}
</h6>
</div>
</div>
<hr>
<div>
<h6>
<LocaleText t="Logs"></LocaleText>
</h6>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">
<LocaleText t="Datetime"></LocaleText>
</th>
<th scope="col">
<LocaleText t="Status"></LocaleText>
</th>
<th scope="col">
<LocaleText t="Message"></LocaleText>
</th>
</tr>
</thead>
<tbody>
<tr v-for="log in [...latestSession.Logs.Logs].reverse()">
<td style="white-space: nowrap">
{{ log.LogTime }}
</td>
<td style="white-space: nowrap" :class="{'text-success': log.Status === 0, 'text-danger': log.Status === 1}">
<span v-if="log.Status === 0">
<i class="bi bi-check-circle-fill me-2"></i>
</span>
<span v-else-if="log.Status === 1">
<i class="bi bi-x-circle-fill me-2"></i>
</span>
<span v-else-if="log.Status === -1">
<i class="bi bi-circle me-2"></i>
</span>
</td>
<td>
{{ log.Message }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div>
<h6>
<LocaleText t="Data"></LocaleText>
</h6>
<div class="bg-body-tertiary p-3 rounded-3" style="max-height: 200px; overflow: scroll">
<pre><code>{{ JSON.stringify(latestSession.Data, null, 4) }}</code></pre>
</div>
</div>
</div>
<div v-else class="p-3">
<div class="bg-body-tertiary p-3 w-100 d-flex rounded-3" >
<h6 class="mb-0 m-auto">No Sessions</h6>
</div>
</div>
</template>
<style scoped>
.table > :not(caption) > * > *{
padding-left: 0 !important;
padding-right: 1rem !important;
}
</style>