Webhook UI

This commit is contained in:
Donald Zou
2025-08-22 18:26:31 +08:00
parent e26639cdc4
commit f6e625c5f8
9 changed files with 438 additions and 78 deletions

View File

@@ -22,7 +22,7 @@ fetchGet("/api/locale", {}, (res) => {
</script>
<template>
<div class="h-100">
<div class="h-100 bg-body" :data-bs-theme="store.Configuration?.Server.dashboard_theme">
<div style="z-index: 9999; height: 5px" class="position-absolute loadingBar top-0 start-0"></div>
<nav class="navbar bg-dark sticky-top" data-bs-theme="dark" v-if="!route.meta.hideTopNav">
<div class="container-fluid d-flex text-body align-items-center">
@@ -57,7 +57,8 @@ fetchGet("/api/locale", {}, (res) => {
.app-enter-from,
.app-leave-to{
opacity: 0;
transform: scale(1.1);
transform: scale(1.05);
filter: blur(8px);
}
@media screen and (min-width: 768px) {
.navbar{

View File

@@ -78,60 +78,65 @@ export default {
</script>
<template>
<form class="d-flex flex-column gap-2">
<div class="row g-2">
<div class="col-sm">
<div class="form-group">
<label :for="'currentPassword_' + this.uuid" class="text-muted mb-1">
<strong><small>
<LocaleText t="Current Password"></LocaleText>
</small></strong>
</label>
<input type="password" class="form-control"
autocomplete="current-password"
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
v-model="this.value.currentPassword"
:id="'currentPassword_' + this.uuid">
<div class="invalid-feedback d-block" v-if="showInvalidFeedback">{{this.invalidFeedback}}</div>
</div>
</div>
<div class="col-sm">
<div class="form-group">
<label :for="'newPassword_' + this.uuid" class="text-muted mb-1">
<strong><small>
<LocaleText t="New Password"></LocaleText>
</small></strong>
</label>
<input type="password" class="form-control"
autocomplete="new-password"
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
v-model="this.value.newPassword"
:id="'newPassword_' + this.uuid">
</div>
</div>
<div class="col-sm">
<div class="form-group">
<label :for="'repeatNewPassword_' + this.uuid" class="text-muted mb-1">
<strong><small>
<LocaleText t="Repeat New Password"></LocaleText>
</small></strong>
</label>
<input type="password" class="form-control"
autocomplete="new-password"
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
v-model="this.value.repeatNewPassword"
:id="'repeatNewPassword_' + this.uuid">
</div>
</div>
</div>
<button
:disabled="!this.passwordValid"
class="ms-auto btn bg-success-subtle text-success-emphasis border-1 border-success-subtle rounded-3 shadow-sm" @click="this.useValidation()">
<i class="bi bi-save2-fill me-2"></i>
<div>
<h6>
<LocaleText t="Update Password"></LocaleText>
</button>
</form>
</h6>
<form class="d-flex flex-column gap-2">
<div class="row g-2">
<div class="col-sm">
<div class="form-group">
<label :for="'currentPassword_' + this.uuid" class="text-muted mb-1">
<strong><small>
<LocaleText t="Current Password"></LocaleText>
</small></strong>
</label>
<input type="password" class="form-control"
autocomplete="current-password"
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
v-model="this.value.currentPassword"
:id="'currentPassword_' + this.uuid">
<div class="invalid-feedback d-block" v-if="showInvalidFeedback">{{this.invalidFeedback}}</div>
</div>
</div>
<div class="col-sm">
<div class="form-group">
<label :for="'newPassword_' + this.uuid" class="text-muted mb-1">
<strong><small>
<LocaleText t="New Password"></LocaleText>
</small></strong>
</label>
<input type="password" class="form-control"
autocomplete="new-password"
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
v-model="this.value.newPassword"
:id="'newPassword_' + this.uuid">
</div>
</div>
<div class="col-sm">
<div class="form-group">
<label :for="'repeatNewPassword_' + this.uuid" class="text-muted mb-1">
<strong><small>
<LocaleText t="Repeat New Password"></LocaleText>
</small></strong>
</label>
<input type="password" class="form-control"
autocomplete="new-password"
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
v-model="this.value.repeatNewPassword"
:id="'repeatNewPassword_' + this.uuid">
</div>
</div>
</div>
<button
:disabled="!this.passwordValid"
class="ms-auto btn bg-success-subtle text-success-emphasis border-1 border-success-subtle rounded-3 shadow-sm" @click="this.useValidation()">
<i class="bi bi-save2-fill me-2"></i>
<LocaleText t="Save"></LocaleText>
</button>
</form>
</div>
</template>
<style scoped>

View File

@@ -65,6 +65,7 @@ export default {
<div class="card rounded-3" >
<div class="card-header d-flex align-items-center" :class="{'border-bottom-0 rounded-3': !this.value}">
<h6 class="my-2">
<i class="bi bi-key-fill me-2"></i>
<LocaleText t="API Keys"></LocaleText>
</h6>
<div class="form-check form-switch ms-auto" v-if="!this.store.getActiveCrossServer()" >

View File

@@ -60,6 +60,7 @@ const sendTestEmail = async () => {
<div class="card">
<div class="card-header">
<h6 class="my-2 d-flex">
<i class="bi bi-envelope-fill me-2"></i>
<LocaleText t="Email Account"></LocaleText>
<span class="text-success ms-auto" v-if="emailIsReady">
<i class="bi bi-check-circle-fill me-2"></i>

View File

@@ -1,11 +1,100 @@
<script setup lang="ts">
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";
const webHooks = ref([])
const webHooksLoaded = ref(false)
onMounted(async () => {
await getWebHooks()
webHooksLoaded.value = true
})
const getWebHooks = async () => {
await fetchGet("/api/webHooks/getWebHooks", {}, (res) => {
webHooks.value = res.data
})
}
const addWebHook = ref(false)
const selectedWebHook = ref(undefined)
</script>
<template>
<div class="card rounded-3">
<div class="card-header d-flex align-items-center">
<h6 class="my-2">
<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"
@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"
@click="addWebHook = false"
v-else
>
<i class="bi bi-chevron-left me-2"></i>
<LocaleText t="Back"></LocaleText>
</button>
</div>
<div class="card-body p-0">
<div style="height: 600px" class="overflow-scroll">
<div class="row g-0 h-100" v-if="!addWebHook">
<div class="col-sm-4 border-end h-100" style="overflow-y: scroll">
<div class="list-group d-flex flex-column d-flex h-100">
<a role="button"
@click="selectedWebHook = webHook"
:class="{active: selectedWebHook?.WebHookID === webHook.WebHookID}"
v-if="webHooks.length > 0"
v-for="webHook in webHooks"
class="list-group-item list-group-item-action " aria-current="true">
<p class="mb-0 fw-bold text-body url" >
{{ webHook.PayloadURL }}
</p>
<small>
<LocaleText t="Subscribed Actions"></LocaleText>:
{{ webHook.SubscribedActions.join(", ")}}
</small>
</a>
<div class="flex-grow-1 d-flex text-muted" v-else>
<LocaleText t="No Webhooks" class="m-auto"></LocaleText>
</div>
</div>
</div>
<div class="col-sm-8 overflow-scroll h-100" v-if="selectedWebHook">
<AddWebHook :webHook="selectedWebHook" @refresh="getWebHooks()" :key="selectedWebHook.WebHookID"></AddWebHook>
</div>
</div>
<suspense v-else>
<AddWebHook @refresh="selectedWebHook = undefined; addWebHook = false; getWebHooks(); "></AddWebHook>
</suspense>
</div>
</div>
</div>
</template>
<style scoped>
.list-group-item{
border-radius: 0 !important;
border-left: 0 !important;
border-right: 0 !important;
}
.list-group-item:first-child{
border-top: 0 !important;
}
.url{
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
font-size: 0.9rem;
}
</style>

View File

@@ -0,0 +1,225 @@
<script setup lang="ts">
import {ref} from "vue";
import {fetchGet, fetchPost} from "@/utilities/fetch.js"
import LocaleText from "@/components/text/localeText.vue";
import { v4 } from "uuid";
const newWebHook = ref({
ContentType: String,
Headers: Object,
IsActive: Boolean,
Notes: String,
PayloadURL: String,
SubscribedActions: Array,
VerifySSL: Boolean,
WebHookID: String
})
const props = defineProps(['webHook'])
if (!props.webHook){
await fetchGet("/api/webHooks/createWebHook", {}, (res) => {
newWebHook.value = res.data
})
}else{
newWebHook.value = {...props.webHook}
}
const Actions = ref({
'peer_created': "Peer Created",
'peer_deleted': "Peer Deleted",
'peer_updated': "Peer Updated"
})
const emits = defineEmits(['refresh'])
const alert = ref(false)
const alertMsg = ref("")
const submitting = ref(false)
const submitWebHook = async (e) => {
if (e) e.preventDefault()
submitting.value = true
await fetchPost("/api/webHooks/updateWebHook", newWebHook.value, (res) => {
if (res.status){
emits('refresh')
}else{
alert.value = true
alertMsg.value = res.message
}
submitting.value = false
})
}
</script>
<template>
<div class="p-3">
<div v-if="!webHook">
<h6>
<LocaleText t="Add Webhook"></LocaleText>
</h6>
<p>
<LocaleText t="WGDashboard will sent a POST Request to the URL below with details of any subscribed events."></LocaleText>
</p>
</div>
<form
@submit="(e) => submitWebHook(e)"
class="d-flex flex-column gap-2">
<div>
<label for="PayloadURL" class="form-label fw-bold text-muted">
<small>
<LocaleText t="Payload URL"></LocaleText>*
</small>
</label>
<input
required
:disabled="submitting"
id="PayloadURL" v-model="newWebHook.PayloadURL"
class="form-control rounded-3" type="url">
</div>
<div>
<label for="ContentType" class="form-label fw-bold text-muted">
<small>
<LocaleText t="Content Type"></LocaleText>*
</small>
</label>
<select
:disabled="submitting"
id="ContentType" v-model="newWebHook.ContentType"
class="form-select rounded-3" required>
<option value="application/json">
application/json
</option>
<option value="application/x-www-form-urlencoded">
application/x-www-form-urlencoded
</option>
</select>
</div>
<div>
<label class="form-label fw-bold text-muted">
<small>
<LocaleText t="Verify SSL"></LocaleText>
</small>
</label>
<div>
<div class="form-check form-switch mb-2">
<input
:disabled="submitting"
v-model="newWebHook.VerifySSL"
class="form-check-input" type="checkbox" role="switch" id="VerifySSL" >
<label class="form-check-label" for="VerifySSL">
<LocaleText :t="newWebHook.VerifySSL ? 'Enabled':'Disabled'"></LocaleText>
</label>
</div>
<div class="alert-danger alert rounded-3" v-if="!newWebHook.VerifySSL">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<LocaleText t="We highly suggest to enable SSL verification"></LocaleText>
</div>
</div>
</div>
<div>
<label class="form-label fw-bold text-muted">
<small>
<LocaleText t="Custom Headers"></LocaleText>
</small>
</label>
<div class="card rounded-3">
<div class="card-body d-flex gap-2 flex-column">
<div class="d-flex gap-2" v-for="(header, headerKey) in newWebHook.Headers">
<div class="flex-grow-1">
<input class="form-control rounded-3 form-control-sm"
:disabled="submitting"
v-model="header.key"
placeholder="Key">
</div>
<div class="flex-grow-1">
<input class="form-control rounded-3 form-control-sm"
:disabled="submitting"
v-model="header.value"
placeholder="Value">
</div>
<button
:class="{disabled: submitting}"
type="button"
@click="delete newWebHook.Headers[headerKey]"
class="btn btn-sm bg-danger-subtle text-danger-emphasis border-danger-subtle rounded-3">
<i class="bi bi-trash-fill"></i>
</button>
</div>
<button
type="button"
:class="{disabled: submitting}"
@click="newWebHook.Headers[v4().toString()] = {key: '', value: ''}"
class="btn btn-sm bg-primary-subtle text-primary-emphasis border-primary-subtle rounded-3">
<i class="bi bi-plus-lg me-2"></i><LocaleText t="Header"></LocaleText>
</button>
</div>
</div>
</div>
<hr>
<div>
<label class="form-label fw-bold text-muted">
<small>
<LocaleText t="Subscribed Actions"></LocaleText>
</small>
</label>
<div>
<div v-for="(action, key) in Actions" class="form-check form-check-inline">
<input class="form-check-input"
:disabled="newWebHook.SubscribedActions.length === 1 && newWebHook.SubscribedActions.includes(key) || submitting"
type="checkbox" :id="key"
:value="key"
v-model="newWebHook.SubscribedActions">
<label class="form-check-label" :for="key">{{ action }}</label>
</div>
</div>
</div>
<hr>
<div>
<label class="form-label fw-bold text-muted">
<small>
<LocaleText t="Enable Webhook"></LocaleText>
</small>
</label>
<div>
<div class="form-check form-switch mb-2">
<input
:disabled="submitting"
v-model="newWebHook.IsActive"
class="form-check-input" type="checkbox" role="switch" id="IsActive" >
<label class="form-check-label" for="IsActive">
<LocaleText :t="newWebHook.IsActive ? 'Yes':'No'"></LocaleText>
</label>
</div>
</div>
</div>
<div class="alert alert-danger rounded-3" v-if="alert">
{{ alertMsg }}
</div>
<div class="d-flex gap-2">
<button
type="submit"
:class="{disabled: submitting}"
class="ms-auto btn bg-success-subtle text-success-emphasis border-success-subtle rounded-3">
<LocaleText t="Save"></LocaleText>
</button>
</div>
<template v-if="webHook">
<hr>
<div class="d-flex align-items-center">
<h6 class="mb-0">
<LocaleText t="Danger Zone"></LocaleText></h6>
<button
@click="confirmDelete = true"
type="button"
:class="{disabled: submitting}"
class="btn bg-danger-subtle text-danger-emphasis border-danger-subtle rounded-3 ms-auto">
<LocaleText t="Delete"></LocaleText>
</button>
</div>
</template>
</form>
</div>
</template>
<style scoped>
</style>

View File

@@ -21,6 +21,7 @@ const dashboardConfigurationStore = DashboardConfigurationStore()
<div class="card rounded-3">
<div class="card-header">
<h6 class="my-2">
<i class="bi bi-magic me-2"></i>
<LocaleText t="Appearance"></LocaleText>
</h6>
</div>
@@ -35,9 +36,10 @@ const dashboardConfigurationStore = DashboardConfigurationStore()
</div>
</div>
</div>
<div class="card">
<div class="card rounded-3">
<div class="card-header">
<h6 class="my-2">
<i class="bi bi-ethernet me-2"></i>
<LocaleText t="Dashboard IP Address & Listen Port"></LocaleText>
</h6>
</div>
@@ -45,9 +47,10 @@ const dashboardConfigurationStore = DashboardConfigurationStore()
<DashboardIPPortInput></DashboardIPPortInput>
</div>
</div>
<div class="card">
<div class="card rounded-3">
<div class="card-header">
<h6 class="my-2">
<i class="bi bi-people-fill me-2"></i>
<LocaleText t="Account Settings"></LocaleText>
</h6>
</div>
@@ -63,18 +66,16 @@ const dashboardConfigurationStore = DashboardConfigurationStore()
targetData="password">
</AccountSettingsInputPassword>
</div>
<hr>
<div>
<h6 >
<LocaleText t="Multi-Factor Authentication (MFA)"></LocaleText>
</h6>
<AccountSettingsMFA v-if="!dashboardConfigurationStore.getActiveCrossServer()"></AccountSettingsMFA>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h6 class="my-2">
<LocaleText t="Multi-Factor Authentication (MFA)"></LocaleText>
</h6>
</div>
<div class="card-body">
<AccountSettingsMFA v-if="!dashboardConfigurationStore.getActiveCrossServer()"></AccountSettingsMFA>
</div>
</div>
<DashboardAPIKeys></DashboardAPIKeys>
<DashboardEmailSettings></DashboardEmailSettings>
<DashboardWebHooks></DashboardWebHooks>