基于naive ui重构设置-开发者页面及弹窗
This commit is contained in:
parent
34577e321d
commit
8b617d833b
@ -1,83 +1,175 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:options="{
|
||||
title: '激活Webhook'
|
||||
}"
|
||||
<n-modal
|
||||
v-model:show="showDialog"
|
||||
preset="card"
|
||||
:title="$t('Activate Webhook')"
|
||||
:style="modalStyle"
|
||||
:mask-closable="true"
|
||||
:close-on-esc="true"
|
||||
class="activate-webhook-modal"
|
||||
>
|
||||
<template #body-content>
|
||||
<div class="space-y-4">
|
||||
<FormControl label="端点" v-model="webhook.endpoint" disabled />
|
||||
<div v-if="request">
|
||||
<p class="text-xs text-gray-600">请求</p>
|
||||
<pre
|
||||
class="mt-2 whitespace-pre-wrap rounded bg-gray-50 px-2 py-1.5 text-sm text-gray-600"
|
||||
>{{ request }}</pre
|
||||
>
|
||||
</div>
|
||||
|
||||
<FormControl
|
||||
v-if="response_status_code"
|
||||
label="响应状态码"
|
||||
v-model="response_status_code"
|
||||
<template #header>
|
||||
<span class="text-lg font-semibold">{{ $t('Activate Webhook') }}</span>
|
||||
</template>
|
||||
<n-space vertical :size="20">
|
||||
<n-form-item :label="$t('Endpoint')">
|
||||
<n-input
|
||||
:value="webhook.endpoint"
|
||||
disabled
|
||||
:size="inputSize"
|
||||
class="w-full"
|
||||
/>
|
||||
<div v-if="response">
|
||||
<p class="text-xs text-gray-600">响应</p>
|
||||
<pre
|
||||
class="mt-2 max-h-52 overflow-y-auto whitespace-pre-wrap rounded bg-gray-50 px-2 py-1.5 text-sm text-gray-600"
|
||||
>{{ response }}</pre
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center" v-if="validated">
|
||||
<ILucideCheck class="h-4 text-green-600" />
|
||||
<div class="ml-2 text-sm font-medium text-gray-700">
|
||||
端点已验证
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ErrorMessage :message="errorMessage" />
|
||||
</n-form-item>
|
||||
<div v-if="request">
|
||||
<p class="text-xs text-gray-600 mb-2">{{ $t('Request') }}</p>
|
||||
<pre class="mt-2 whitespace-pre-wrap rounded bg-gray-50 px-3 py-2 text-sm text-gray-700 border border-gray-200">
|
||||
{{ request }}
|
||||
</pre>
|
||||
</div>
|
||||
<n-form-item v-if="response_status_code" :label="$t('Response Status Code')">
|
||||
<n-input
|
||||
:value="response_status_code"
|
||||
disabled
|
||||
:size="inputSize"
|
||||
class="w-full"
|
||||
/>
|
||||
</n-form-item>
|
||||
<div v-if="response">
|
||||
<p class="text-xs text-gray-600 mb-2">{{ $t('Response') }}</p>
|
||||
<pre class="mt-2 max-h-52 overflow-y-auto whitespace-pre-wrap rounded bg-gray-50 px-3 py-2 text-sm text-gray-700 border border-gray-200">
|
||||
{{ response }}
|
||||
</pre>
|
||||
</div>
|
||||
<div class="flex items-center" v-if="validated">
|
||||
<n-icon class="h-5 w-5 text-green-600 mr-2">
|
||||
<CheckIcon />
|
||||
</n-icon>
|
||||
<div class="text-sm font-medium text-gray-700">
|
||||
{{ $t('Endpoint validated') }}
|
||||
</div>
|
||||
</div>
|
||||
<n-alert v-if="errorMessage" type="error" :title="errorMessage" />
|
||||
</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
|
||||
v-if="!validated"
|
||||
type="primary"
|
||||
:loading="$resources.validateEndpoint.loading"
|
||||
@click="$resources.validateEndpoint.submit()"
|
||||
:block="isMobile"
|
||||
:size="buttonSize"
|
||||
>
|
||||
{{ $t('Validate Webhook') }}
|
||||
</n-button>
|
||||
<n-button
|
||||
v-if="validated"
|
||||
type="primary"
|
||||
:loading="$resources.activateWebhook.loading"
|
||||
@click="$resources.activateWebhook.submit()"
|
||||
:block="isMobile"
|
||||
:size="buttonSize"
|
||||
>
|
||||
{{ $t('Activate Webhook') }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
<template v-slot:actions>
|
||||
<Button
|
||||
class="w-full"
|
||||
theme="gray"
|
||||
variant="solid"
|
||||
@click="$resources.validateEndpoint.submit()"
|
||||
:loading="$resources.validateEndpoint.loading"
|
||||
loadingText="正在验证Webhook"
|
||||
v-if="!validated"
|
||||
>验证Webhook</Button
|
||||
>
|
||||
<Button
|
||||
class="w-full"
|
||||
theme="gray"
|
||||
variant="solid"
|
||||
@click="$resources.activateWebhook.submit()"
|
||||
:loading="$resources.activateWebhook.loading"
|
||||
loadingText="正在激活Webhook"
|
||||
v-if="validated"
|
||||
>激活Webhook</Button
|
||||
>
|
||||
</template>
|
||||
</Dialog>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
NModal,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NSpace,
|
||||
NAlert,
|
||||
NButton,
|
||||
NIcon,
|
||||
} from 'naive-ui';
|
||||
import { toast } from 'vue-sonner';
|
||||
import CheckIcon from '~icons/lucide/check';
|
||||
|
||||
export default {
|
||||
emits: ['success'],
|
||||
props: ['webhook'],
|
||||
components: {
|
||||
NModal,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NSpace,
|
||||
NAlert,
|
||||
NButton,
|
||||
NIcon,
|
||||
CheckIcon,
|
||||
},
|
||||
emits: ['success', 'update:modelValue'],
|
||||
props: {
|
||||
webhook: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
errorMessage: '',
|
||||
validated: false,
|
||||
request: null,
|
||||
response: null,
|
||||
response_status_code: null
|
||||
response_status_code: null,
|
||||
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);
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
},
|
||||
methods: {
|
||||
handleResize() {
|
||||
this.windowWidth = window.innerWidth;
|
||||
},
|
||||
hide() {
|
||||
this.showDialog = false;
|
||||
this.errorMessage = '';
|
||||
this.validated = false;
|
||||
this.request = null;
|
||||
this.response = null;
|
||||
this.response_status_code = null;
|
||||
}
|
||||
},
|
||||
resources: {
|
||||
validateEndpoint() {
|
||||
return {
|
||||
@ -99,9 +191,7 @@ export default {
|
||||
this.validated = true;
|
||||
} else {
|
||||
this.validated = false;
|
||||
|
||||
this.errorMessage =
|
||||
'端点应返回200到300之间的状态\n请检查端点并重试';
|
||||
this.errorMessage = this.$t('Endpoint should return a status code between 200 and 300. Please check the endpoint and try again.');
|
||||
}
|
||||
},
|
||||
onError: e => {
|
||||
@ -121,7 +211,8 @@ export default {
|
||||
};
|
||||
},
|
||||
onSuccess(e) {
|
||||
toast.success('Webhook激活成功');
|
||||
toast.success(this.$t('Webhook activated successfully'));
|
||||
this.hide();
|
||||
this.$emit('success');
|
||||
},
|
||||
onError(e) {
|
||||
@ -132,4 +223,55 @@ export default {
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.activate-webhook-modal .n-card) {
|
||||
width: 800px;
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
:deep(.activate-webhook-modal .n-card-body) {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
:deep(.activate-webhook-modal .n-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.activate-webhook-modal .n-input) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.activate-webhook-modal .n-card__action) {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
:deep(.activate-webhook-modal .n-card) {
|
||||
width: 95vw !important;
|
||||
max-width: 95vw !important;
|
||||
margin: 20px auto;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
:deep(.activate-webhook-modal .n-card-body) {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
:deep(.activate-webhook-modal .n-card__header) {
|
||||
padding: 16px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
:deep(.activate-webhook-modal .n-card__action) {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
:deep(.activate-webhook-modal .n-button) {
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,114 +1,179 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:options="{
|
||||
title: $t('Add New Webhook'),
|
||||
actions: [
|
||||
{
|
||||
label: $t('Add Webhook'),
|
||||
variant: 'solid',
|
||||
onClick: addWebhook,
|
||||
loading: $resources?.addWebhook?.loading
|
||||
}
|
||||
]
|
||||
}"
|
||||
<n-modal
|
||||
v-model:show="showDialog"
|
||||
preset="card"
|
||||
:title="$t('Add New Webhook')"
|
||||
:style="modalStyle"
|
||||
:mask-closable="true"
|
||||
:close-on-esc="true"
|
||||
class="add-webhook-modal"
|
||||
>
|
||||
<template #body-content>
|
||||
<div class="space-y-4">
|
||||
<FormControl :label="$t('Endpoint')" v-model="endpoint" />
|
||||
<div>
|
||||
<FormControl :label="$t('Secret Key')" v-model="secret">
|
||||
<template #suffix>
|
||||
<FeatherIcon
|
||||
class="w-4 cursor-pointer"
|
||||
name="refresh-ccw"
|
||||
@click="generateRandomSecret"
|
||||
/>
|
||||
</template>
|
||||
</FormControl>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
<strong>{{ $t('Note') }}:</strong> {{ $t('The secret key is optional. View') }}
|
||||
<a href="https://jingrow.com/docs/webhook-introduction" class="underline" target="_blank"
|
||||
>{{ $t('documentation') }}</a
|
||||
<template #header>
|
||||
<span class="text-lg font-semibold">{{ $t('Add New Webhook') }}</span>
|
||||
</template>
|
||||
<n-space vertical :size="20">
|
||||
<n-form-item :label="$t('Endpoint')" :required="true">
|
||||
<n-input
|
||||
v-model:value="endpoint"
|
||||
:placeholder="$t('Enter webhook endpoint URL')"
|
||||
:size="inputSize"
|
||||
class="w-full"
|
||||
/>
|
||||
</n-form-item>
|
||||
<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"
|
||||
>
|
||||
{{ $t('to learn more') }}
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-base font-medium text-gray-900">
|
||||
<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 class="mt-4">
|
||||
<p class="text-base font-medium text-gray-900 mb-4">
|
||||
{{ $t('Select Webhook Events') }}
|
||||
</p>
|
||||
<div
|
||||
class="text-center text-sm leading-10 text-gray-500"
|
||||
v-if="$resources.events.loading"
|
||||
>
|
||||
{{ $t('Loading...') }}
|
||||
</div>
|
||||
<div class="mt-6 flex flex-col gap-4" v-else>
|
||||
<Switch
|
||||
v-for="event in localizedEvents"
|
||||
:key="event.name"
|
||||
:label="event.title || event.name"
|
||||
:description="event.description"
|
||||
:modelValue="isEventSelected(event.name)"
|
||||
@update:modelValue="selectEvent(event.name)"
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
<ErrorMessage :message="errorMessage || $resources.addWebhook.error" />
|
||||
<n-spin :show="$resources.events.loading">
|
||||
<n-space vertical :size="12" v-if="!$resources.events.loading">
|
||||
<div
|
||||
v-for="event in localizedEvents"
|
||||
: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.title || 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.addWebhook.error"
|
||||
type="error"
|
||||
:title="errorMessage || $resources.addWebhook.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.addWebhook.loading"
|
||||
@click="addWebhook"
|
||||
:block="isMobile"
|
||||
:size="buttonSize"
|
||||
>
|
||||
{{ $t('Add Webhook') }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</Dialog>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
NModal,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NSpace,
|
||||
NAlert,
|
||||
NButton,
|
||||
NSwitch,
|
||||
NIcon,
|
||||
NSpin,
|
||||
} from 'naive-ui';
|
||||
import { toast } from 'vue-sonner';
|
||||
import RefreshIcon from '~icons/lucide/refresh-ccw';
|
||||
|
||||
export default {
|
||||
emits: ['success'],
|
||||
components: {
|
||||
NModal,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NSpace,
|
||||
NAlert,
|
||||
NButton,
|
||||
NSwitch,
|
||||
NIcon,
|
||||
NSpin,
|
||||
RefreshIcon,
|
||||
},
|
||||
emits: ['success', 'update:modelValue'],
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
endpoint: '',
|
||||
secret: '',
|
||||
selectedEvents: [],
|
||||
errorMessage: ''
|
||||
errorMessage: '',
|
||||
windowWidth: window.innerWidth,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (this.selectedEvents.length) {
|
||||
this.selectedEvents = this.selectedEvents.map(event => event.name);
|
||||
}
|
||||
},
|
||||
resources: {
|
||||
events() {
|
||||
computed: {
|
||||
showDialog: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('update:modelValue', value);
|
||||
}
|
||||
},
|
||||
isMobile() {
|
||||
return this.windowWidth <= 768;
|
||||
},
|
||||
modalStyle() {
|
||||
return {
|
||||
url: 'jcloud.api.webhook.available_events',
|
||||
inititalData: [],
|
||||
auto: true
|
||||
width: this.isMobile ? '95vw' : '800px',
|
||||
maxWidth: this.isMobile ? '95vw' : '90vw',
|
||||
};
|
||||
},
|
||||
addWebhook() {
|
||||
return {
|
||||
url: 'jcloud.api.webhook.add',
|
||||
params: {
|
||||
endpoint: this.endpoint,
|
||||
secret: this.secret,
|
||||
events: this.selectedEvents
|
||||
},
|
||||
onSuccess() {
|
||||
toast.success(this.$t('Webhook added successfully'));
|
||||
this.$emit('success');
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
inputSize() {
|
||||
return this.isMobile ? 'medium' : 'large';
|
||||
},
|
||||
buttonSize() {
|
||||
return this.isMobile ? 'medium' : 'medium';
|
||||
},
|
||||
localizedEvents() {
|
||||
if (!this.$resources.events.data) return [];
|
||||
|
||||
return this.$resources.events.data.map(event => {
|
||||
// 创建新对象,避免直接修改原始数据
|
||||
const localizedEvent = { ...event };
|
||||
|
||||
// 根据事件名称进行本地化翻译
|
||||
if (localizedEvent.name === 'Site Status Update') {
|
||||
localizedEvent.title = this.$t('Site Status Update');
|
||||
localizedEvent.description = this.$t('Pending, Installing, Updating, Active, Inactive, Abnormal, Archived, Paused');
|
||||
@ -116,7 +181,6 @@ export default {
|
||||
localizedEvent.title = this.$t('Site Plan Change');
|
||||
localizedEvent.description = this.$t('Get notifications for site subscription plan changes');
|
||||
} else {
|
||||
// 对于其他事件,如果没有title则使用name,如果没有description则使用空字符串
|
||||
if (!localizedEvent.title) {
|
||||
localizedEvent.title = localizedEvent.name;
|
||||
}
|
||||
@ -129,7 +193,20 @@ export default {
|
||||
});
|
||||
}
|
||||
},
|
||||
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)
|
||||
@ -162,7 +239,89 @@ export default {
|
||||
}
|
||||
this.errorMessage = '';
|
||||
this.$resources.addWebhook.submit();
|
||||
},
|
||||
hide() {
|
||||
this.showDialog = false;
|
||||
this.endpoint = '';
|
||||
this.secret = '';
|
||||
this.selectedEvents = [];
|
||||
this.errorMessage = '';
|
||||
}
|
||||
},
|
||||
resources: {
|
||||
events() {
|
||||
return {
|
||||
url: 'jcloud.api.webhook.available_events',
|
||||
inititalData: [],
|
||||
auto: true
|
||||
};
|
||||
},
|
||||
addWebhook() {
|
||||
return {
|
||||
url: 'jcloud.api.webhook.add',
|
||||
params: {
|
||||
endpoint: this.endpoint,
|
||||
secret: this.secret,
|
||||
events: this.selectedEvents
|
||||
},
|
||||
onSuccess() {
|
||||
toast.success(this.$t('Webhook added successfully'));
|
||||
this.hide();
|
||||
this.$emit('success');
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.add-webhook-modal .n-card) {
|
||||
width: 800px;
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
:deep(.add-webhook-modal .n-card-body) {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
:deep(.add-webhook-modal .n-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.add-webhook-modal .n-input) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.add-webhook-modal .n-card__action) {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
:deep(.add-webhook-modal .n-card) {
|
||||
width: 95vw !important;
|
||||
max-width: 95vw !important;
|
||||
margin: 20px auto;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
:deep(.add-webhook-modal .n-card-body) {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
:deep(.add-webhook-modal .n-card__header) {
|
||||
padding: 16px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
:deep(.add-webhook-modal .n-card__action) {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
:deep(.add-webhook-modal .n-button) {
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,103 +1,220 @@
|
||||
<template>
|
||||
<div class="p-5">
|
||||
<div class="grid grid-cols-1 gap-5">
|
||||
<div
|
||||
class="mx-auto min-w-[48rem] max-w-3xl space-y-6 rounded-md border p-4"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-xl font-semibold">{{ $t('API Access') }}</div>
|
||||
<Button @click="showCreateSecretDialog = true">{{
|
||||
apiKeyButtonLabel
|
||||
}}</Button>
|
||||
</div>
|
||||
<div v-if="$team.pg?.user_info?.api_key">
|
||||
<ClickToCopyField
|
||||
v-if="$team.pg?.user_info?.api_key"
|
||||
:textContent="$team.pg.user_info.api_key"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="pb-2 text-base text-gray-700">
|
||||
{{ $t("You don't have an API key yet. Click the button above to create one.") }}
|
||||
</div>
|
||||
<Dialog
|
||||
v-model="showCreateSecretDialog"
|
||||
:options="apiKeyDialogOptions"
|
||||
>
|
||||
<template #body-content>
|
||||
<div v-if="createSecret.data">
|
||||
<p class="text-base">
|
||||
{{ $t('Please copy the API key immediately. You will not be able to view it again!') }}
|
||||
</p>
|
||||
<label class="block pt-2">
|
||||
<span class="mb-2 block text-sm leading-4 text-gray-700"
|
||||
>API key</span
|
||||
>
|
||||
<ClickToCopyField :textContent="createSecret.data.api_key" />
|
||||
</label>
|
||||
<label class="block pt-2">
|
||||
<span class="mb-2 block text-sm leading-4 text-gray-700"
|
||||
>API secret</span
|
||||
>
|
||||
<ClickToCopyField :textContent="createSecret.data.api_secret" />
|
||||
</label>
|
||||
</div>
|
||||
<div v-else class="text-base text-gray-700">
|
||||
<div class="developer-settings-container">
|
||||
<n-space vertical :size="24">
|
||||
<!-- API Access 卡片 -->
|
||||
<n-card :title="$t('API Access')" class="settings-card">
|
||||
<n-space vertical :size="20">
|
||||
<div class="flex items-center justify-between flex-wrap gap-4">
|
||||
<div class="text-base text-gray-700">
|
||||
{{ $t('API key and API secret can be used to access') }}
|
||||
<a href="/docs/api" class="underline" target="_blank"
|
||||
>{{ $t('Jingrow API') }}</a
|
||||
>.
|
||||
<a href="/docs/api" class="text-primary underline" target="_blank">
|
||||
{{ $t('Jingrow API') }}
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</div>
|
||||
<div
|
||||
class="mx-auto min-w-[48rem] max-w-3xl space-y-6 rounded-md border p-4"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-xl font-semibold">{{ $t('SSH Keys') }}</div>
|
||||
</div>
|
||||
<n-button type="primary" @click="showCreateSecretDialog = true" :size="buttonSize">
|
||||
{{ apiKeyButtonLabel }}
|
||||
</n-button>
|
||||
</div>
|
||||
<div v-if="$team.pg?.user_info?.api_key">
|
||||
<ClickToCopyField :textContent="$team.pg.user_info.api_key" />
|
||||
</div>
|
||||
<div v-else class="text-base text-gray-600">
|
||||
{{ $t("You don't have an API key yet. Click the button above to create one.") }}
|
||||
</div>
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
<!-- SSH Keys 卡片 -->
|
||||
<n-card :title="$t('SSH Keys')" class="settings-card">
|
||||
<ObjectList :options="sshKeyListOptions" />
|
||||
</div>
|
||||
<div
|
||||
</n-card>
|
||||
|
||||
<!-- Webhooks 卡片 -->
|
||||
<n-card
|
||||
v-if="$session.hasWebhookConfigurationAccess"
|
||||
class="mx-auto min-w-[48rem] max-w-3xl space-y-6 rounded-md border p-4"
|
||||
:title="$t('Webhooks')"
|
||||
class="settings-card"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-xl font-semibold">{{ $t('Webhooks') }}</div>
|
||||
</div>
|
||||
<ObjectList :options="webhookListOptions" />
|
||||
<AddNewWebhookDialog
|
||||
v-if="showAddWebhookDialog"
|
||||
v-model="showAddWebhookDialog"
|
||||
@success="onNewWebhookSuccess"
|
||||
/>
|
||||
<ActivateWebhookDialog
|
||||
v-if="showActivateWebhookDialog"
|
||||
v-model="showActivateWebhookDialog"
|
||||
@success="onWebHookActivated"
|
||||
:webhook="selectedWebhook"
|
||||
/>
|
||||
<EditWebhookDialog
|
||||
v-if="showEditWebhookDialog"
|
||||
v-model="showEditWebhookDialog"
|
||||
@success="onWebHookUpdated"
|
||||
:webhook="selectedWebhook"
|
||||
/>
|
||||
<WebhookAttemptsDialog
|
||||
v-if="showWebhookAttempts"
|
||||
v-model="showWebhookAttempts"
|
||||
:name="selectedWebhook.name"
|
||||
/>
|
||||
</n-card>
|
||||
</n-space>
|
||||
|
||||
<!-- API Key 创建/重新生成弹窗 -->
|
||||
<n-modal
|
||||
v-model:show="showCreateSecretDialog"
|
||||
preset="card"
|
||||
:title="$t('API Access')"
|
||||
:style="modalStyle"
|
||||
:mask-closable="true"
|
||||
:close-on-esc="true"
|
||||
class="api-key-modal"
|
||||
>
|
||||
<template #header>
|
||||
<span class="text-lg font-semibold">{{ $t('API Access') }}</span>
|
||||
</template>
|
||||
<n-space vertical :size="20">
|
||||
<div v-if="createSecret.data">
|
||||
<n-alert type="warning" class="mb-4">
|
||||
{{ $t('Please copy the API key immediately. You will not be able to view it again!') }}
|
||||
</n-alert>
|
||||
<n-form-item :label="$t('API Key')">
|
||||
<ClickToCopyField :textContent="createSecret.data.api_key" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="$t('API Secret')">
|
||||
<ClickToCopyField :textContent="createSecret.data.api_secret" />
|
||||
</n-form-item>
|
||||
</div>
|
||||
<div v-else class="text-base text-gray-700">
|
||||
{{ $t('API key and API secret can be used to access') }}
|
||||
<a href="/docs/api" class="text-primary underline" target="_blank">
|
||||
{{ $t('Jingrow API') }}
|
||||
</a>.
|
||||
</div>
|
||||
</n-space>
|
||||
<template #action>
|
||||
<n-space :vertical="isMobile" :size="isMobile ? 12 : 16" class="w-full">
|
||||
<n-button @click="showCreateSecretDialog = false" :block="isMobile" :size="buttonSize">
|
||||
{{ $t('Cancel') }}
|
||||
</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
:loading="createSecret.loading"
|
||||
:disabled="!!createSecret.data"
|
||||
@click="createSecret.submit()"
|
||||
:block="isMobile"
|
||||
:size="buttonSize"
|
||||
>
|
||||
{{ $team.pg.user_info.api_key ? $t('Regenerate API Key') : $t('Create New API Key') }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
|
||||
<!-- SSH Key 添加弹窗 -->
|
||||
<n-modal
|
||||
v-model:show="showAddSSHKeyDialog"
|
||||
preset="card"
|
||||
:title="$t('Add New SSH Key')"
|
||||
:style="modalStyle"
|
||||
:mask-closable="true"
|
||||
:close-on-esc="true"
|
||||
class="ssh-key-modal"
|
||||
>
|
||||
<template #header>
|
||||
<span class="text-lg font-semibold">{{ $t('Add New SSH Key') }}</span>
|
||||
</template>
|
||||
<n-space vertical :size="20">
|
||||
<p class="text-base text-gray-700">{{ $t('Add a new SSH key to your account') }}</p>
|
||||
<n-form-item :label="$t('SSH Key')" :required="true">
|
||||
<n-input
|
||||
v-model:value="sshKeyValue"
|
||||
type="textarea"
|
||||
:placeholder="sshKeyPlaceholder"
|
||||
:rows="6"
|
||||
:size="inputSize"
|
||||
class="w-full"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-alert v-if="sshKeyError" type="error" :title="sshKeyError" />
|
||||
</n-space>
|
||||
<template #action>
|
||||
<n-space :vertical="isMobile" :size="isMobile ? 12 : 16" class="w-full">
|
||||
<n-button @click="showAddSSHKeyDialog = false" :block="isMobile" :size="buttonSize">
|
||||
{{ $t('Cancel') }}
|
||||
</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
:loading="addSSHKey.loading"
|
||||
@click="handleAddSSHKey"
|
||||
:block="isMobile"
|
||||
:size="buttonSize"
|
||||
>
|
||||
{{ $t('Add SSH Key') }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
|
||||
<!-- Webhook 禁用确认弹窗 -->
|
||||
<n-modal
|
||||
v-model:show="showDisableWebhookDialog"
|
||||
preset="dialog"
|
||||
:title="$t('Disable Webhook')"
|
||||
:positive-text="$t('Disable')"
|
||||
:positive-button-props="{ type: 'error' }"
|
||||
:loading="disableWebhook.loading"
|
||||
:mask-closable="true"
|
||||
:close-on-esc="true"
|
||||
@positive-click="handleDisableWebhook"
|
||||
>
|
||||
<div class="py-4">
|
||||
<p class="text-base mb-2">
|
||||
{{ $t('Endpoint') }}: <strong>{{ selectedWebhookForAction?.endpoint }}</strong>
|
||||
</p>
|
||||
<p class="text-base">{{ $t('Are you sure you want to disable this webhook?') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</n-modal>
|
||||
|
||||
<!-- Webhook 删除确认弹窗 -->
|
||||
<n-modal
|
||||
v-model:show="showDeleteWebhookDialog"
|
||||
preset="dialog"
|
||||
:title="$t('Delete Webhook')"
|
||||
:positive-text="$t('Delete')"
|
||||
:positive-button-props="{ type: 'error' }"
|
||||
:loading="deleteWebhook.loading"
|
||||
:mask-closable="true"
|
||||
:close-on-esc="true"
|
||||
@positive-click="handleDeleteWebhook"
|
||||
>
|
||||
<div class="py-4">
|
||||
<p class="text-base mb-2">
|
||||
{{ $t('Endpoint') }}: <strong>{{ selectedWebhookForAction?.endpoint }}</strong>
|
||||
</p>
|
||||
<p class="text-base">{{ $t('Are you sure you want to delete this webhook?') }}</p>
|
||||
</div>
|
||||
</n-modal>
|
||||
|
||||
<!-- Webhook 相关弹窗 -->
|
||||
<AddNewWebhookDialog
|
||||
v-if="showAddWebhookDialog"
|
||||
v-model="showAddWebhookDialog"
|
||||
@success="onNewWebhookSuccess"
|
||||
/>
|
||||
<ActivateWebhookDialog
|
||||
v-if="showActivateWebhookDialog"
|
||||
v-model="showActivateWebhookDialog"
|
||||
@success="onWebHookActivated"
|
||||
:webhook="selectedWebhook"
|
||||
/>
|
||||
<EditWebhookDialog
|
||||
v-if="showEditWebhookDialog"
|
||||
v-model="showEditWebhookDialog"
|
||||
@success="onWebHookUpdated"
|
||||
:webhook="selectedWebhook"
|
||||
/>
|
||||
<WebhookAttemptsDialog
|
||||
v-if="showWebhookAttempts"
|
||||
v-model="showWebhookAttempts"
|
||||
:name="selectedWebhook.name"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
NCard,
|
||||
NSpace,
|
||||
NButton,
|
||||
NModal,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NAlert,
|
||||
} from 'naive-ui';
|
||||
import { Badge, createResource } from 'jingrow-ui';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { computed, h, onMounted, ref, getCurrentInstance } from 'vue';
|
||||
import { confirmDialog, icon } from '../../utils/components';
|
||||
import { icon } from '../../utils/components';
|
||||
import ObjectList from '../ObjectList.vue';
|
||||
import { getTeam } from '../../data/team';
|
||||
import { date } from '../../utils/format';
|
||||
@ -115,11 +232,29 @@ const $t = instance?.appContext.config.globalProperties.$t || ((key) => key);
|
||||
const $team = getTeam();
|
||||
const router = useRouter();
|
||||
let showCreateSecretDialog = ref(false);
|
||||
const showAddSSHKeyDialog = ref(false);
|
||||
const sshKeyValue = ref('');
|
||||
const sshKeyError = ref('');
|
||||
const showAddWebhookDialog = ref(false);
|
||||
const showActivateWebhookDialog = ref(false);
|
||||
const showEditWebhookDialog = ref(false);
|
||||
const showWebhookAttempts = ref(false);
|
||||
const selectedWebhook = ref(null);
|
||||
const selectedWebhookForAction = ref(null);
|
||||
const showDisableWebhookDialog = ref(false);
|
||||
const showDeleteWebhookDialog = ref(false);
|
||||
const windowWidth = ref(window.innerWidth);
|
||||
|
||||
const isMobile = computed(() => windowWidth.value <= 768);
|
||||
const modalStyle = computed(() => ({
|
||||
width: isMobile.value ? '95vw' : '700px',
|
||||
maxWidth: isMobile.value ? '95vw' : '90vw',
|
||||
}));
|
||||
const inputSize = computed(() => (isMobile.value ? 'medium' : 'large'));
|
||||
const buttonSize = computed(() => (isMobile.value ? 'medium' : 'medium'));
|
||||
const sshKeyPlaceholder = computed(() => {
|
||||
return $t("Starts with 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519', 'sk-ecdsa-sha2-nistp256@openssh.com', or 'sk-ssh-ed25519@openssh.com'");
|
||||
});
|
||||
|
||||
const createSecret = createResource({
|
||||
url: 'jcloud.api.account.create_api_secret',
|
||||
@ -129,6 +264,7 @@ const createSecret = createResource({
|
||||
} else {
|
||||
toast.success($t('API key created successfully'));
|
||||
}
|
||||
$team.reload();
|
||||
}
|
||||
});
|
||||
|
||||
@ -136,6 +272,9 @@ const addSSHKey = createResource({
|
||||
url: 'jcloud.api.client.insert',
|
||||
onSuccess() {
|
||||
toast.success($t('SSH key added successfully'));
|
||||
showAddSSHKeyDialog.value = false;
|
||||
sshKeyValue.value = '';
|
||||
sshKeyError.value = '';
|
||||
},
|
||||
onError(err) {
|
||||
toast.error(
|
||||
@ -209,7 +348,10 @@ const sshKeyListOptions = computed(() => ({
|
||||
return {
|
||||
label: $t('Add SSH Key'),
|
||||
slots: { prefix: icon('plus') },
|
||||
onClick: () => renderAddNewKeyDialog(listResource)
|
||||
onClick: () => {
|
||||
showAddSSHKeyDialog.value = true;
|
||||
sshKeyListResource = listResource;
|
||||
}
|
||||
};
|
||||
},
|
||||
rowActions({ row }) {
|
||||
@ -236,42 +378,30 @@ const sshKeyListOptions = computed(() => ({
|
||||
}
|
||||
}));
|
||||
|
||||
function renderAddNewKeyDialog(listResource) {
|
||||
confirmDialog({
|
||||
title: $t('Add New SSH Key'),
|
||||
message: $t('Add a new SSH key to your account'),
|
||||
fields: [
|
||||
{
|
||||
label: $t('SSH Key'),
|
||||
fieldname: 'sshKey',
|
||||
type: 'textarea',
|
||||
placeholder: $t("Starts with 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519', 'sk-ecdsa-sha2-nistp256@openssh.com', or 'sk-ssh-ed25519@openssh.com'"),
|
||||
required: true
|
||||
let sshKeyListResource = null;
|
||||
|
||||
function handleAddSSHKey() {
|
||||
if (!sshKeyValue.value) {
|
||||
sshKeyError.value = $t('SSH key is required');
|
||||
return;
|
||||
}
|
||||
sshKeyError.value = '';
|
||||
addSSHKey
|
||||
.submit({
|
||||
pg: {
|
||||
pagetype: 'User SSH Key',
|
||||
ssh_public_key: sshKeyValue.value,
|
||||
user: $team.pg.user_info.name
|
||||
}
|
||||
],
|
||||
primaryAction: {
|
||||
label: $t('Add SSH Key'),
|
||||
variant: 'solid',
|
||||
onClick({ hide, values }) {
|
||||
if (!values.sshKey) throw new Error($t('SSH key is required'));
|
||||
addSSHKey
|
||||
.submit({
|
||||
pg: {
|
||||
pagetype: 'User SSH Key',
|
||||
ssh_public_key: values.sshKey,
|
||||
user: $team.pg.user_info.name
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
listResource.reload();
|
||||
hide();
|
||||
})
|
||||
.catch(error => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
if (sshKeyListResource) {
|
||||
sshKeyListResource.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
sshKeyError.value = error.message;
|
||||
});
|
||||
}
|
||||
|
||||
const webhookListResource = createResource({
|
||||
@ -289,6 +419,7 @@ const deleteWebhook = createResource({
|
||||
onSuccess() {
|
||||
toast.success($t('Webhook deleted successfully'));
|
||||
webhookListResource.reload();
|
||||
showDeleteWebhookDialog.value = false;
|
||||
},
|
||||
onError(err) {
|
||||
toast.error(
|
||||
@ -341,25 +472,8 @@ const webhookListOptions = computed(() => ({
|
||||
label: $t('Disable'),
|
||||
condition: () => Boolean(row.enabled),
|
||||
onClick: () => {
|
||||
confirmDialog({
|
||||
title: $t('Disable Webhook'),
|
||||
message: $t('Endpoint - {endpoint}<br>Are you sure you want to disable this webhook?<br>', { endpoint: row.endpoint }),
|
||||
primaryAction: {
|
||||
label: $t('Disable'),
|
||||
variant: 'solid',
|
||||
theme: 'red',
|
||||
onClick({ hide }) {
|
||||
disableWebhook
|
||||
.submit({
|
||||
dt: 'Jcloud Webhook',
|
||||
dn: row.name,
|
||||
method: 'disable'
|
||||
})
|
||||
.then(hide);
|
||||
return disableWebhook.promise;
|
||||
}
|
||||
}
|
||||
});
|
||||
selectedWebhookForAction.value = row;
|
||||
showDisableWebhookDialog.value = true;
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -379,24 +493,8 @@ const webhookListOptions = computed(() => ({
|
||||
{
|
||||
label: $t('Delete'),
|
||||
onClick() {
|
||||
confirmDialog({
|
||||
title: $t('Delete Webhook'),
|
||||
message: $t('Endpoint - {endpoint}<br>Are you sure you want to delete this webhook?<br>', { endpoint: row.endpoint }),
|
||||
primaryAction: {
|
||||
label: $t('Delete'),
|
||||
variant: 'solid',
|
||||
theme: 'red',
|
||||
onClick({ hide }) {
|
||||
deleteWebhook
|
||||
.submit({
|
||||
pagetype: 'Jcloud Webhook',
|
||||
name: row.name
|
||||
})
|
||||
.then(hide);
|
||||
return deleteWebhook.promise;
|
||||
}
|
||||
}
|
||||
});
|
||||
selectedWebhookForAction.value = row;
|
||||
showDeleteWebhookDialog.value = true;
|
||||
}
|
||||
}
|
||||
];
|
||||
@ -422,6 +520,7 @@ const disableWebhook = createResource({
|
||||
onSuccess() {
|
||||
toast.success($t('Webhook disabled successfully'));
|
||||
webhookListResource.reload();
|
||||
showDisableWebhookDialog.value = false;
|
||||
},
|
||||
onError(err) {
|
||||
toast.error(
|
||||
@ -432,6 +531,23 @@ const disableWebhook = createResource({
|
||||
}
|
||||
});
|
||||
|
||||
function handleDisableWebhook() {
|
||||
if (!selectedWebhookForAction.value) return;
|
||||
disableWebhook.submit({
|
||||
dt: 'Jcloud Webhook',
|
||||
dn: selectedWebhookForAction.value.name,
|
||||
method: 'disable'
|
||||
});
|
||||
}
|
||||
|
||||
function handleDeleteWebhook() {
|
||||
if (!selectedWebhookForAction.value) return;
|
||||
deleteWebhook.submit({
|
||||
pagetype: 'Jcloud Webhook',
|
||||
name: selectedWebhookForAction.value.name
|
||||
});
|
||||
}
|
||||
|
||||
const onNewWebhookSuccess = () => {
|
||||
webhookListResource.reload();
|
||||
showAddWebhookDialog.value = false;
|
||||
@ -443,7 +559,7 @@ const onWebHookActivated = () => {
|
||||
};
|
||||
|
||||
const onWebHookUpdated = activationRequired => {
|
||||
webhookListResource.reload();
|
||||
webhookListResource.reload();
|
||||
showEditWebhookDialog.value = false;
|
||||
if (activationRequired) {
|
||||
showActivateWebhookDialog.value = true;
|
||||
@ -456,27 +572,104 @@ const apiKeyButtonLabel = computed(() => {
|
||||
: $t('Create New API Key');
|
||||
});
|
||||
|
||||
const apiKeyDialogOptions = computed(() => ({
|
||||
title: $t('API Access'),
|
||||
size: 'xl',
|
||||
actions: [
|
||||
{
|
||||
label: $team.pg.user_info.api_key
|
||||
? $t('Regenerate API Key')
|
||||
: $t('Create New API Key'),
|
||||
variant: 'solid',
|
||||
disabled: !!createSecret.data,
|
||||
loading: createSecret.loading,
|
||||
onClick() {
|
||||
createSecret.submit();
|
||||
}
|
||||
}
|
||||
]
|
||||
}));
|
||||
function handleResize() {
|
||||
windowWidth.value = window.innerWidth;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
if (session.hasWebhookConfigurationAccess) {
|
||||
webhookListResource.fetch();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.developer-settings-container {
|
||||
width: 100%;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.settings-card:hover {
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
:deep(.api-key-modal .n-card),
|
||||
:deep(.ssh-key-modal .n-card) {
|
||||
width: 700px;
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
:deep(.api-key-modal .n-card-body),
|
||||
:deep(.ssh-key-modal .n-card-body) {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
:deep(.api-key-modal .n-form-item),
|
||||
:deep(.ssh-key-modal .n-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.api-key-modal .n-input),
|
||||
:deep(.ssh-key-modal .n-input) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.api-key-modal .n-card__action),
|
||||
:deep(.ssh-key-modal .n-card__action) {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.developer-settings-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
:deep(.api-key-modal .n-card),
|
||||
:deep(.ssh-key-modal .n-card) {
|
||||
width: 95vw !important;
|
||||
max-width: 95vw !important;
|
||||
margin: 20px auto;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
:deep(.api-key-modal .n-card-body),
|
||||
:deep(.ssh-key-modal .n-card-body) {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
:deep(.api-key-modal .n-card__header),
|
||||
:deep(.ssh-key-modal .n-card__header) {
|
||||
padding: 16px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
:deep(.api-key-modal .n-card__action),
|
||||
:deep(.ssh-key-modal .n-card__action) {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
:deep(.api-key-modal .n-space),
|
||||
:deep(.ssh-key-modal .n-space) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.api-key-modal .n-button),
|
||||
:deep(.ssh-key-modal .n-button) {
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,104 +1,243 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:options="{
|
||||
title: '编辑Webhook',
|
||||
actions: [
|
||||
{
|
||||
label: '保存更改',
|
||||
variant: 'solid',
|
||||
onClick: updateWebhook,
|
||||
loading: this.$resources.updateWebhook.loading
|
||||
}
|
||||
]
|
||||
}"
|
||||
<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 #body-content>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<FormControl label="端点" v-model="endpoint" />
|
||||
<p class="mt-1.5 text-sm text-gray-700">
|
||||
如果您更改了端点,请确保重新激活webhook。
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="!updateSecret">
|
||||
<p class="block text-xs text-gray-600">密钥</p>
|
||||
<div
|
||||
class="mt-1 flex items-center justify-between text-base text-gray-700"
|
||||
>
|
||||
<p>想要更改密钥吗?</p>
|
||||
<Button @click="updateSecret = true">编辑密钥</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<FormControl label="密钥" v-model="secret">
|
||||
<template #suffix>
|
||||
<FeatherIcon
|
||||
class="w-4 cursor-pointer"
|
||||
name="refresh-ccw"
|
||||
@click="generateRandomSecret"
|
||||
/>
|
||||
</template>
|
||||
</FormControl>
|
||||
<p class="mt-1.5 text-sm text-gray-700">
|
||||
<secret>注意:</secret> 密钥是可选的。查看
|
||||
<a
|
||||
href="https://jingrow.com/docs/webhook-introduction"
|
||||
class="underline"
|
||||
target="_blank"
|
||||
>文档</a
|
||||
>
|
||||
了解更多
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-base font-medium text-gray-900">
|
||||
选择webhook事件
|
||||
</p>
|
||||
<div
|
||||
class="text-center text-sm leading-10 text-gray-500"
|
||||
v-if="$resources.events.loading"
|
||||
>
|
||||
加载中...
|
||||
</div>
|
||||
<div class="mt-6 flex flex-col gap-3" v-else>
|
||||
<Switch
|
||||
v-for="event in $resources.events.data"
|
||||
:key="event.name"
|
||||
:label="event.name"
|
||||
:description="event.description"
|
||||
:modelValue="isEventSelected(event.name)"
|
||||
@update:modelValue="selectEvent(event.name)"
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
<ErrorMessage
|
||||
:message="errorMessage || $resources.updateWebhook.error"
|
||||
/>
|
||||
</div>
|
||||
<template #header>
|
||||
<span class="text-lg font-semibold">{{ $t('Edit Webhook') }}</span>
|
||||
</template>
|
||||
</Dialog>
|
||||
<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 {
|
||||
emits: ['success'],
|
||||
props: ['webhook'],
|
||||
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: ''
|
||||
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 {
|
||||
@ -125,8 +264,8 @@ export default {
|
||||
return {
|
||||
url: 'jcloud.api.webhook.update',
|
||||
validate: () => {
|
||||
if (!this.selectedEvents) {
|
||||
return '请至少启用一个事件';
|
||||
if (!this.selectedEvents || this.selectedEvents.length === 0) {
|
||||
return this.$t('Please enable at least one event');
|
||||
}
|
||||
},
|
||||
makeParams: () => {
|
||||
@ -138,52 +277,72 @@ export default {
|
||||
};
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success('Webhook更新成功');
|
||||
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,
|
||||
'更新webhook失败,请重试'
|
||||
this.$t('Failed to update webhook, please try again')
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
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 = '请至少选择一个事件来添加';
|
||||
return;
|
||||
}
|
||||
this.errorMessage = '';
|
||||
this.$resources.updateWebhook.submit();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</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>
|
||||
|
||||
@ -1,83 +1,124 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:options="{
|
||||
title: selectedWebhookAttemptId
|
||||
? `Webhook 尝试 - ${selectedWebhookAttemptId}`
|
||||
: 'Webhook 尝试',
|
||||
size: '4xl'
|
||||
}"
|
||||
<n-modal
|
||||
v-model:show="showDialog"
|
||||
preset="card"
|
||||
:title="selectedWebhookAttemptId ? $t('Webhook Attempt - {id}', { id: selectedWebhookAttemptId }) : $t('Webhook Attempts')"
|
||||
:style="modalStyle"
|
||||
:mask-closable="true"
|
||||
:close-on-esc="true"
|
||||
class="webhook-attempts-modal"
|
||||
>
|
||||
<template #body-content>
|
||||
<p class="text-sm mb-2 text-gray-700" v-if="!selectedWebhookAttemptId">
|
||||
<strong>注意:</strong> 您只能查看过去24小时的日志
|
||||
</p>
|
||||
<template #header>
|
||||
<span class="text-lg font-semibold">
|
||||
{{ selectedWebhookAttemptId ? $t('Webhook Attempt - {id}', { id: selectedWebhookAttemptId }) : $t('Webhook Attempts') }}
|
||||
</span>
|
||||
</template>
|
||||
<n-space vertical :size="20">
|
||||
<n-alert type="info" v-if="!selectedWebhookAttemptId">
|
||||
<template #header>
|
||||
<strong>{{ $t('Note') }}:</strong>
|
||||
</template>
|
||||
{{ $t('You can only view logs from the past 24 hours') }}
|
||||
</n-alert>
|
||||
<ObjectList :options="listOptions" v-if="!selectedWebhookAttemptId" />
|
||||
<Button
|
||||
class="mb-2"
|
||||
iconLeft="arrow-left"
|
||||
<n-button
|
||||
v-if="selectedWebhookAttemptId"
|
||||
@click="selectedWebhookAttemptId = null"
|
||||
:size="buttonSize"
|
||||
>
|
||||
返回
|
||||
</Button>
|
||||
<template #icon>
|
||||
<n-icon><ArrowLeftIcon /></n-icon>
|
||||
</template>
|
||||
{{ $t('Back') }}
|
||||
</n-button>
|
||||
<WebhookAttemptDetails
|
||||
:id="selectedWebhookAttemptId"
|
||||
v-if="selectedWebhookAttemptId"
|
||||
/>
|
||||
</template>
|
||||
</Dialog>
|
||||
</n-space>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Breadcrumbs, Badge } from 'jingrow-ui';
|
||||
import Header from '../Header.vue';
|
||||
import ObjectList from '../ObjectList.vue';
|
||||
import {
|
||||
NModal,
|
||||
NSpace,
|
||||
NAlert,
|
||||
NButton,
|
||||
NIcon,
|
||||
} from 'naive-ui';
|
||||
import { Badge } from 'jingrow-ui';
|
||||
import { h } from 'vue';
|
||||
import ObjectList from '../ObjectList.vue';
|
||||
import WebhookAttemptDetails from './WebhookAttemptDetails.vue';
|
||||
import ArrowLeftIcon from '~icons/lucide/arrow-left';
|
||||
|
||||
export default {
|
||||
name: 'WebhookAttempts',
|
||||
props: ['name'],
|
||||
components: {
|
||||
Header,
|
||||
Breadcrumbs,
|
||||
NModal,
|
||||
NSpace,
|
||||
NAlert,
|
||||
NButton,
|
||||
NIcon,
|
||||
ObjectList,
|
||||
WebhookAttemptDetails
|
||||
WebhookAttemptDetails,
|
||||
ArrowLeftIcon,
|
||||
},
|
||||
name: 'WebhookAttempts',
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedWebhookAttemptId: null
|
||||
selectedWebhookAttemptId: null,
|
||||
windowWidth: window.innerWidth,
|
||||
};
|
||||
},
|
||||
resources: {
|
||||
attempts() {
|
||||
return {
|
||||
url: 'jcloud.api.webhook.attempts',
|
||||
params: {
|
||||
webhook: this.$props.name
|
||||
},
|
||||
inititalData: [],
|
||||
auto: true
|
||||
};
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showDialog: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('update:modelValue', value);
|
||||
}
|
||||
},
|
||||
isMobile() {
|
||||
return this.windowWidth <= 768;
|
||||
},
|
||||
modalStyle() {
|
||||
return {
|
||||
width: this.isMobile ? '95vw' : '1200px',
|
||||
maxWidth: this.isMobile ? '95vw' : '90vw',
|
||||
};
|
||||
},
|
||||
buttonSize() {
|
||||
return this.isMobile ? 'medium' : 'medium';
|
||||
},
|
||||
listOptions() {
|
||||
return {
|
||||
data: () => this.$resources?.attempts?.data || [],
|
||||
columns: [
|
||||
{
|
||||
label: '事件',
|
||||
label: this.$t('Event'),
|
||||
fieldname: 'event',
|
||||
width: 0.25
|
||||
},
|
||||
{
|
||||
label: '端点',
|
||||
label: this.$t('Endpoint'),
|
||||
fieldname: 'endpoint',
|
||||
width: 0.5,
|
||||
format: value => value.substring(0, 50)
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
label: this.$t('Status'),
|
||||
fieldname: 'status',
|
||||
width: 0.1,
|
||||
type: 'Component',
|
||||
@ -94,7 +135,7 @@ export default {
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '代码',
|
||||
label: this.$t('Code'),
|
||||
fieldname: 'response_status_code',
|
||||
width: 0.1,
|
||||
format: val => {
|
||||
@ -104,7 +145,7 @@ export default {
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
label: '时间戳',
|
||||
label: this.$t('Timestamp'),
|
||||
fieldname: 'timestamp',
|
||||
width: 0.3,
|
||||
format(value) {
|
||||
@ -117,6 +158,67 @@ export default {
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.handleResize();
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
},
|
||||
methods: {
|
||||
handleResize() {
|
||||
this.windowWidth = window.innerWidth;
|
||||
}
|
||||
},
|
||||
resources: {
|
||||
attempts() {
|
||||
return {
|
||||
url: 'jcloud.api.webhook.attempts',
|
||||
params: {
|
||||
webhook: this.$props.name
|
||||
},
|
||||
inititalData: [],
|
||||
auto: true
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.webhook-attempts-modal .n-card) {
|
||||
width: 1200px;
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
:deep(.webhook-attempts-modal .n-card-body) {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
:deep(.webhook-attempts-modal .n-card__action) {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
:deep(.webhook-attempts-modal .n-card) {
|
||||
width: 95vw !important;
|
||||
max-width: 95vw !important;
|
||||
margin: 20px auto;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
:deep(.webhook-attempts-modal .n-card-body) {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
:deep(.webhook-attempts-modal .n-card__header) {
|
||||
padding: 16px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
:deep(.webhook-attempts-modal .n-card__action) {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user