349 lines
8.1 KiB
Vue
349 lines
8.1 KiB
Vue
<template>
|
|
<n-modal
|
|
v-model:show="showDialog"
|
|
preset="card"
|
|
:title="$t('Edit Webhook')"
|
|
:style="modalStyle"
|
|
:mask-closable="true"
|
|
:close-on-esc="true"
|
|
class="edit-webhook-modal"
|
|
>
|
|
<template #header>
|
|
<span class="text-lg font-semibold">{{ $t('Edit Webhook') }}</span>
|
|
</template>
|
|
<n-space vertical :size="20">
|
|
<n-form-item :label="$t('Endpoint')">
|
|
<n-input
|
|
v-model:value="endpoint"
|
|
:placeholder="$t('Enter webhook endpoint URL')"
|
|
:size="inputSize"
|
|
class="w-full"
|
|
/>
|
|
</n-form-item>
|
|
<n-alert type="info" class="mb-2">
|
|
{{ $t('If you change the endpoint, make sure to reactivate the webhook.') }}
|
|
</n-alert>
|
|
<div v-if="!updateSecret">
|
|
<p class="block text-xs text-gray-600 mb-2">{{ $t('Secret Key') }}</p>
|
|
<div class="mt-1 flex items-center justify-between text-base text-gray-700">
|
|
<p>{{ $t('Want to change the secret key?') }}</p>
|
|
<n-button size="small" @click="updateSecret = true">
|
|
{{ $t('Edit Secret') }}
|
|
</n-button>
|
|
</div>
|
|
</div>
|
|
<div v-else>
|
|
<n-form-item :label="$t('Secret Key')">
|
|
<n-input
|
|
v-model:value="secret"
|
|
:placeholder="$t('Enter secret key (optional)')"
|
|
:size="inputSize"
|
|
class="w-full"
|
|
>
|
|
<template #suffix>
|
|
<n-icon
|
|
class="cursor-pointer text-gray-500 hover:text-gray-700"
|
|
@click="generateRandomSecret"
|
|
>
|
|
<RefreshIcon />
|
|
</n-icon>
|
|
</template>
|
|
</n-input>
|
|
</n-form-item>
|
|
<n-alert type="info" class="mb-2">
|
|
<template #header>
|
|
<strong>{{ $t('Note') }}:</strong>
|
|
</template>
|
|
{{ $t('The secret key is optional. View') }}
|
|
<a href="https://jingrow.com/docs/webhook-introduction" class="text-primary underline" target="_blank">
|
|
{{ $t('documentation') }}
|
|
</a>
|
|
{{ $t('to learn more') }}
|
|
</n-alert>
|
|
</div>
|
|
<div class="mt-4">
|
|
<p class="text-base font-medium text-gray-900 mb-4">
|
|
{{ $t('Select Webhook Events') }}
|
|
</p>
|
|
<n-spin :show="$resources.events.loading">
|
|
<n-space vertical :size="12" v-if="!$resources.events.loading">
|
|
<div
|
|
v-for="event in $resources.events.data"
|
|
:key="event.name"
|
|
class="flex items-center justify-between p-3 rounded border border-gray-200 hover:bg-gray-50"
|
|
>
|
|
<div class="flex-1">
|
|
<div class="text-sm font-medium text-gray-900">
|
|
{{ event.name }}
|
|
</div>
|
|
<div v-if="event.description" class="text-xs text-gray-500 mt-1">
|
|
{{ event.description }}
|
|
</div>
|
|
</div>
|
|
<n-switch
|
|
:value="isEventSelected(event.name)"
|
|
@update:value="selectEvent(event.name)"
|
|
size="medium"
|
|
/>
|
|
</div>
|
|
</n-space>
|
|
</n-spin>
|
|
</div>
|
|
<n-alert
|
|
v-if="errorMessage || $resources.updateWebhook.error"
|
|
type="error"
|
|
:title="errorMessage || $resources.updateWebhook.error"
|
|
/>
|
|
</n-space>
|
|
<template #action>
|
|
<n-space :vertical="isMobile" :size="isMobile ? 12 : 16" class="w-full">
|
|
<n-button @click="hide" :block="isMobile" :size="buttonSize">
|
|
{{ $t('Cancel') }}
|
|
</n-button>
|
|
<n-button
|
|
type="primary"
|
|
:loading="$resources.updateWebhook.loading"
|
|
@click="updateWebhook"
|
|
:block="isMobile"
|
|
:size="buttonSize"
|
|
>
|
|
{{ $t('Save Changes') }}
|
|
</n-button>
|
|
</n-space>
|
|
</template>
|
|
</n-modal>
|
|
</template>
|
|
|
|
<script>
|
|
import {
|
|
NModal,
|
|
NFormItem,
|
|
NInput,
|
|
NSpace,
|
|
NAlert,
|
|
NButton,
|
|
NSwitch,
|
|
NIcon,
|
|
NSpin,
|
|
} from 'naive-ui';
|
|
import { toast } from 'vue-sonner';
|
|
import { getToastErrorMessage } from '../../utils/toast';
|
|
import RefreshIcon from '~icons/lucide/refresh-ccw';
|
|
|
|
export default {
|
|
components: {
|
|
NModal,
|
|
NFormItem,
|
|
NInput,
|
|
NSpace,
|
|
NAlert,
|
|
NButton,
|
|
NSwitch,
|
|
NIcon,
|
|
NSpin,
|
|
RefreshIcon,
|
|
},
|
|
emits: ['success', 'update:modelValue'],
|
|
props: {
|
|
webhook: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
modelValue: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
endpoint: '',
|
|
secret: '',
|
|
updateSecret: false,
|
|
selectedEvents: [],
|
|
errorMessage: '',
|
|
windowWidth: window.innerWidth,
|
|
};
|
|
},
|
|
computed: {
|
|
showDialog: {
|
|
get() {
|
|
return this.modelValue;
|
|
},
|
|
set(value) {
|
|
this.$emit('update:modelValue', value);
|
|
}
|
|
},
|
|
isMobile() {
|
|
return this.windowWidth <= 768;
|
|
},
|
|
modalStyle() {
|
|
return {
|
|
width: this.isMobile ? '95vw' : '800px',
|
|
maxWidth: this.isMobile ? '95vw' : '90vw',
|
|
};
|
|
},
|
|
inputSize() {
|
|
return this.isMobile ? 'medium' : 'large';
|
|
},
|
|
buttonSize() {
|
|
return this.isMobile ? 'medium' : 'medium';
|
|
},
|
|
},
|
|
mounted() {
|
|
this.handleResize();
|
|
window.addEventListener('resize', this.handleResize);
|
|
if (this.selectedEvents.length) {
|
|
this.selectedEvents = this.selectedEvents.map(event => event.name);
|
|
}
|
|
},
|
|
beforeUnmount() {
|
|
window.removeEventListener('resize', this.handleResize);
|
|
},
|
|
methods: {
|
|
handleResize() {
|
|
this.windowWidth = window.innerWidth;
|
|
},
|
|
generateRandomSecret() {
|
|
this.secret = Array(30)
|
|
.fill(0)
|
|
.map(
|
|
() =>
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'[
|
|
Math.floor(Math.random() * 62)
|
|
]
|
|
)
|
|
.join('');
|
|
},
|
|
selectEvent(event) {
|
|
if (this.selectedEvents.includes(event)) {
|
|
this.selectedEvents = this.selectedEvents.filter(e => e !== event);
|
|
} else {
|
|
this.selectedEvents.push(event);
|
|
}
|
|
},
|
|
isEventSelected(event) {
|
|
return this.selectedEvents.includes(event);
|
|
},
|
|
updateWebhook() {
|
|
if (this.selectedEvents.length === 0) {
|
|
this.errorMessage = this.$t('Please enable at least one event');
|
|
return;
|
|
}
|
|
this.errorMessage = '';
|
|
this.$resources.updateWebhook.submit();
|
|
},
|
|
hide() {
|
|
this.showDialog = false;
|
|
this.updateSecret = false;
|
|
this.errorMessage = '';
|
|
}
|
|
},
|
|
resources: {
|
|
events() {
|
|
return {
|
|
url: 'jcloud.api.webhook.available_events',
|
|
inititalData: [],
|
|
auto: true
|
|
};
|
|
},
|
|
fetchWebhookInfo() {
|
|
return {
|
|
url: 'jcloud.api.client.get',
|
|
params: {
|
|
pagetype: 'Jcloud Webhook',
|
|
name: this.webhook.name
|
|
},
|
|
auto: true,
|
|
onSuccess: pg => {
|
|
this.endpoint = pg.endpoint;
|
|
this.selectedEvents = pg.events.map(event => event.event);
|
|
}
|
|
};
|
|
},
|
|
updateWebhook() {
|
|
return {
|
|
url: 'jcloud.api.webhook.update',
|
|
validate: () => {
|
|
if (!this.selectedEvents || this.selectedEvents.length === 0) {
|
|
return this.$t('Please enable at least one event');
|
|
}
|
|
},
|
|
makeParams: () => {
|
|
return {
|
|
name: this.webhook.name,
|
|
endpoint: this.endpoint,
|
|
secret: this.updateSecret ? this.secret : '',
|
|
events: this.selectedEvents
|
|
};
|
|
},
|
|
onSuccess: () => {
|
|
toast.success(this.$t('Webhook updated successfully'));
|
|
const activationRequired = this.webhook.endpoint !== this.endpoint;
|
|
this.hide();
|
|
this.$emit('success', activationRequired);
|
|
},
|
|
onError: e => {
|
|
toast.error(
|
|
getToastErrorMessage(
|
|
e,
|
|
this.$t('Failed to update webhook, please try again')
|
|
)
|
|
);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
:deep(.edit-webhook-modal .n-card) {
|
|
width: 800px;
|
|
max-width: 90vw;
|
|
}
|
|
|
|
:deep(.edit-webhook-modal .n-card-body) {
|
|
padding: 24px;
|
|
}
|
|
|
|
:deep(.edit-webhook-modal .n-form-item) {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
:deep(.edit-webhook-modal .n-input) {
|
|
width: 100%;
|
|
}
|
|
|
|
:deep(.edit-webhook-modal .n-card__action) {
|
|
padding: 16px 24px;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
:deep(.edit-webhook-modal .n-card) {
|
|
width: 95vw !important;
|
|
max-width: 95vw !important;
|
|
margin: 20px auto;
|
|
border-radius: 12px;
|
|
}
|
|
|
|
:deep(.edit-webhook-modal .n-card-body) {
|
|
padding: 20px 16px;
|
|
}
|
|
|
|
:deep(.edit-webhook-modal .n-card__header) {
|
|
padding: 16px;
|
|
font-size: 18px;
|
|
}
|
|
|
|
:deep(.edit-webhook-modal .n-card__action) {
|
|
padding: 12px 16px;
|
|
}
|
|
|
|
:deep(.edit-webhook-modal .n-button) {
|
|
width: 100%;
|
|
font-size: 16px;
|
|
height: 44px;
|
|
}
|
|
}
|
|
</style>
|