2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 10:17:17 +08:00

feat: 支持 webhook, close #695

This commit is contained in:
2026-01-08 23:04:37 +08:00
parent 978c9b2fc3
commit ab6e0903f5
13 changed files with 813 additions and 86 deletions

View File

@@ -0,0 +1,14 @@
import { http } from '@/utils'
export default {
// 获取 WebHook 列表
list: (page: number, limit: number): any => http.Get('/webhook', { params: { page, limit } }),
// 获取 WebHook 信息
get: (id: number): any => http.Get(`/webhook/${id}`),
// 创建 WebHook
create: (req: any): any => http.Post('/webhook', req),
// 修改 WebHook
update: (id: number, req: any): any => http.Put(`/webhook/${id}`, req),
// 删除 WebHook
delete: (id: number): any => http.Delete(`/webhook/${id}`)
}

View File

@@ -6,6 +6,7 @@ defineOptions({
import BenchmarkView from '@/views/toolbox/BenchmarkView.vue'
import ProcessView from '@/views/toolbox/ProcessView.vue'
import SystemView from '@/views/toolbox/SystemView.vue'
import WebHookView from '@/views/toolbox/WebHookView.vue'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
@@ -18,12 +19,14 @@ const current = ref('process')
<n-tabs v-model:value="current" animated>
<n-tab name="process" :tab="$gettext('Process')" />
<n-tab name="system" :tab="$gettext('System')" />
<n-tab name="webhook" :tab="$gettext('WebHook')" />
<n-tab name="benchmark" :tab="$gettext('Benchmark')" />
</n-tabs>
</template>
<n-flex vertical>
<process-view v-if="current === 'process'" />
<system-view v-if="current === 'system'" />
<web-hook-view v-if="current === 'webhook'" />
<benchmark-view v-if="current === 'benchmark'" />
</n-flex>
</common-page>

View File

@@ -0,0 +1,394 @@
<script setup lang="ts">
import { NButton, NDataTable, NInput, NPopconfirm, NSwitch, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import webhook from '@/api/panel/webhook'
import { formatDateTime } from '@/utils'
const { $gettext } = useGettext()
// 创建弹窗
const createModal = ref(false)
const createModel = ref({
name: '',
script: '#!/bin/bash\n\n',
raw: false,
user: 'root'
})
// 编辑弹窗
const editModal = ref(false)
const editModel = ref({
id: 0,
name: '',
script: '',
raw: false,
user: '',
status: true
})
const columns: any = [
{
title: $gettext('Name'),
key: 'name',
minWidth: 150,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: 'Key',
key: 'key',
minWidth: 200,
resizable: true,
ellipsis: { tooltip: true },
render(row: any) {
return h(
NTag,
{
type: 'info',
bordered: false
},
{
default: () => row.key
}
)
}
},
{
title: $gettext('Run As User'),
key: 'user',
width: 120,
resizable: true,
ellipsis: { tooltip: true },
render(row: any) {
return row.user || 'root'
}
},
{
title: $gettext('Raw Output'),
key: 'raw',
width: 120,
resizable: true,
render(row: any) {
return h(
NTag,
{
type: row.raw ? 'success' : 'default',
size: 'small'
},
{
default: () => (row.raw ? $gettext('Yes') : $gettext('No'))
}
)
}
},
{
title: $gettext('Enabled'),
key: 'status',
width: 100,
resizable: true,
render(row: any) {
return h(NSwitch, {
size: 'small',
rubberBand: false,
value: row.status,
onUpdateValue: () => handleStatusChange(row)
})
}
},
{
title: $gettext('Call Count'),
key: 'call_count',
width: 100,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: $gettext('Last Call'),
key: 'last_call_at',
width: 180,
resizable: true,
ellipsis: { tooltip: true },
render(row: any): string {
if (!row.last_call_at || row.last_call_at === '0001-01-01T00:00:00Z') {
return '-'
}
return formatDateTime(row.last_call_at)
}
},
{
title: $gettext('Creation Time'),
key: 'created_at',
width: 180,
resizable: true,
ellipsis: { tooltip: true },
render(row: any): string {
return formatDateTime(row.created_at)
}
},
{
title: $gettext('Actions'),
key: 'actions',
width: 280,
hideInExcel: true,
render(row: any) {
return [
h(
NButton,
{
size: 'small',
type: 'info',
secondary: true,
onClick: () => handleCopyUrl(row)
},
{
default: () => $gettext('Copy URL')
}
),
h(
NButton,
{
size: 'small',
type: 'primary',
style: 'margin-left: 10px;',
onClick: () => handleEdit(row)
},
{
default: () => $gettext('Edit')
}
),
h(
NPopconfirm,
{
onPositiveClick: () => handleDelete(row.id)
},
{
default: () => {
return $gettext('Are you sure you want to delete this WebHook?')
},
trigger: () => {
return h(
NButton,
{
size: 'small',
type: 'error',
style: 'margin-left: 10px;'
},
{
default: () => $gettext('Delete')
}
)
}
}
)
]
}
}
]
const { loading, data, page, total, pageSize, pageCount, refresh } = usePagination(
(page, pageSize) => webhook.list(page, pageSize),
{
initialData: { total: 0, list: [] },
initialPageSize: 20,
total: (res: any) => res.total,
data: (res: any) => res.items
}
)
const handleStatusChange = (row: any) => {
useRequest(
webhook.update(row.id, {
name: row.name,
script: row.script,
raw: row.raw,
user: row.user,
status: !row.status
})
).onSuccess(() => {
row.status = !row.status
window.$message.success($gettext('Modified successfully'))
})
}
const handleCopyUrl = (row: any) => {
const url = `${window.location.origin}/webhook/${row.key}`
navigator.clipboard.writeText(url).then(() => {
window.$message.success($gettext('URL copied to clipboard'))
})
}
const handleEdit = (row: any) => {
editModel.value = {
id: row.id,
name: row.name,
script: row.script,
raw: row.raw,
user: row.user || 'root',
status: row.status
}
editModal.value = true
}
const handleDelete = (id: number) => {
useRequest(webhook.delete(id)).onSuccess(() => {
window.$message.success($gettext('Deleted successfully'))
refresh()
})
}
const handleCreate = () => {
if (!createModel.value.name) {
window.$message.warning($gettext('Please enter a name'))
return
}
if (!createModel.value.script) {
window.$message.warning($gettext('Please enter a script'))
return
}
useRequest(webhook.create(createModel.value)).onSuccess(() => {
createModal.value = false
createModel.value = {
name: '',
script: '#!/bin/bash\n\n',
raw: false,
user: 'root'
}
window.$message.success($gettext('Created successfully'))
refresh()
})
}
const handleUpdate = () => {
if (!editModel.value.name) {
window.$message.warning($gettext('Please enter a name'))
return
}
if (!editModel.value.script) {
window.$message.warning($gettext('Please enter a script'))
return
}
useRequest(
webhook.update(editModel.value.id, {
name: editModel.value.name,
script: editModel.value.script,
raw: editModel.value.raw,
user: editModel.value.user,
status: editModel.value.status
})
).onSuccess(() => {
editModal.value = false
window.$message.success($gettext('Modified successfully'))
refresh()
})
}
onMounted(() => {
refresh()
})
</script>
<template>
<n-flex vertical>
<n-flex justify="end">
<n-button type="primary" @click="createModal = true">
{{ $gettext('Create WebHook') }}
</n-button>
</n-flex>
<n-data-table
striped
remote
:scroll-x="1400"
:loading="loading"
:columns="columns"
:data="data"
:row-key="(row: any) => row.id"
v-model:page="page"
v-model:pageSize="pageSize"
:pagination="{
page: page,
pageCount: pageCount,
pageSize: pageSize,
itemCount: total,
showQuickJumper: true,
showSizePicker: true,
pageSizes: [20, 50, 100, 200]
}"
/>
</n-flex>
<!-- 创建弹窗 -->
<n-modal
v-model:show="createModal"
preset="card"
:title="$gettext('Create WebHook')"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
>
<n-form :model="createModel">
<n-form-item :label="$gettext('Name')">
<n-input v-model:value="createModel.name" :placeholder="$gettext('Enter WebHook name')" />
</n-form-item>
<n-form-item :label="$gettext('User')">
<n-input
v-model:value="createModel.user"
:placeholder="$gettext('User to run the script (default: root)')"
/>
</n-form-item>
<n-form-item :label="$gettext('Raw Output')">
<n-switch v-model:value="createModel.raw" />
<span ml-10 text-gray>
{{ $gettext('Return script output as raw text instead of JSON') }}
</span>
</n-form-item>
<n-form-item :label="$gettext('Script')">
<common-editor v-model:value="createModel.script" lang="sh" height="40vh" />
</n-form-item>
</n-form>
<n-button type="info" @click="handleCreate" block>
{{ $gettext('Create') }}
</n-button>
</n-modal>
<!-- 编辑弹窗 -->
<n-modal
v-model:show="editModal"
preset="card"
:title="$gettext('Edit WebHook')"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
>
<n-form :model="editModel">
<n-form-item :label="$gettext('Name')">
<n-input v-model:value="editModel.name" :placeholder="$gettext('Enter WebHook name')" />
</n-form-item>
<n-form-item :label="$gettext('User')">
<n-input
v-model:value="editModel.user"
:placeholder="$gettext('User to run the script (default: root)')"
/>
</n-form-item>
<n-form-item :label="$gettext('Raw Output')">
<n-switch v-model:value="editModel.raw" />
<span ml-10 text-gray>
{{ $gettext('Return script output as raw text instead of JSON') }}
</span>
</n-form-item>
<n-form-item :label="$gettext('Enabled')">
<n-switch v-model:value="editModel.status" />
</n-form-item>
<n-form-item :label="$gettext('Script')">
<common-editor v-model:value="editModel.script" lang="sh" height="40vh" />
</n-form-item>
</n-form>
<n-button type="info" @click="handleUpdate" block>
{{ $gettext('Save') }}
</n-button>
</n-modal>
</template>
<style scoped lang="scss"></style>