【调整】SSH地址支持域名形式

【新增】支持自定义监控端口
【新增】通知类型-企业微信
【新增】申请证书(Buypass)、自定义ACME服务器地址
【新增】授权API管理(namesilo、Bunny、Gcore、name.com、京东云)
This commit is contained in:
chudong
2025-06-07 17:37:42 +08:00
parent 87ae1c9570
commit f0d83d23c6
106 changed files with 1570 additions and 223 deletions

View File

@@ -8,6 +8,8 @@ import type {
GetReportListResponse,
GetSettingParams,
GetSettingResponse,
GetVersionParams,
GetVersionResponse,
SaveSettingParams,
TestReportParams,
UpdateReportParams,
@@ -69,5 +71,15 @@ export const testReport = (params?: TestReportParams): useAxiosReturn<AxiosRespo
* @param {GetReportListParams} [params] 请求参数
* @returns {useAxiosReturn<GetReportListResponse, GetReportListParams>} 获取告警类型列表的组合式 API 调用封装。包含响应数据、加载状态及执行函数。
*/
export const getReportList = (params?: GetReportListParams): useAxiosReturn<GetReportListResponse, GetReportListParams> =>
export const getReportList = (
params?: GetReportListParams,
): useAxiosReturn<GetReportListResponse, GetReportListParams> =>
useApi<GetReportListResponse, GetReportListParams>('/v1/report/get_list', params)
/**
* @description 获取版本信息
* @param {GetVersionParams} [params] 请求参数
* @returns {useAxiosReturn<GetVersionResponse, GetVersionParams>} 获取版本信息的组合式 API 调用封装。包含响应数据、加载状态及执行函数。
*/
export const getVersion = (params?: GetVersionParams): useAxiosReturn<GetVersionResponse, GetVersionParams> =>
useApi<GetVersionResponse, GetVersionParams>('/v1/setting/get_version', params)

View File

@@ -6,6 +6,7 @@ import type {
DeleteWorkflowParams,
EnableWorkflowParams,
ExecuteWorkflowParams,
StopWorkflowParams,
UpdateWorkflowExecTypeParams,
UpdateWorkflowParams,
WorkflowHistoryDetailParams,
@@ -23,7 +24,9 @@ import { useApi } from '@api/index'
* @param {WorkflowListParams} [params] 请求参数
* @returns {useAxiosReturn<WorkflowListResponse, WorkflowListParams>} 获取工作流列表的组合式 API 调用封装。包含响应数据、加载状态及执行函数。
*/
export const getWorkflowList = (params?: WorkflowListParams): useAxiosReturn<WorkflowListResponse, WorkflowListParams> =>
export const getWorkflowList = (
params?: WorkflowListParams,
): useAxiosReturn<WorkflowListResponse, WorkflowListParams> =>
useApi<WorkflowListResponse, WorkflowListParams>('/v1/workflow/get_list', params)
/**
@@ -39,7 +42,9 @@ export const addWorkflow = (params?: AddWorkflowParams): useAxiosReturn<AxiosRes
* @param {UpdateWorkflowParams} [params] 请求参数
* @returns {useAxiosReturn<AxiosResponseData, UpdateWorkflowParams>} 修改工作流的组合式 API 调用封装。包含响应数据、加载状态及执行函数。
*/
export const updateWorkflow = (params?: UpdateWorkflowParams): useAxiosReturn<AxiosResponseData, UpdateWorkflowParams> =>
export const updateWorkflow = (
params?: UpdateWorkflowParams,
): useAxiosReturn<AxiosResponseData, UpdateWorkflowParams> =>
useApi<AxiosResponseData, UpdateWorkflowParams>('/v1/workflow/upd_workflow', params)
/**
@@ -47,7 +52,9 @@ export const updateWorkflow = (params?: UpdateWorkflowParams): useAxiosReturn<Ax
* @param {DeleteWorkflowParams} [params] 请求参数
* @returns {useAxiosReturn<AxiosResponseData, DeleteWorkflowParams>} 删除工作流的组合式 API 调用封装。包含响应数据、加载状态及执行函数。
*/
export const deleteWorkflow = (params?: DeleteWorkflowParams): useAxiosReturn<AxiosResponseData, DeleteWorkflowParams> =>
export const deleteWorkflow = (
params?: DeleteWorkflowParams,
): useAxiosReturn<AxiosResponseData, DeleteWorkflowParams> =>
useApi<AxiosResponseData, DeleteWorkflowParams>('/v1/workflow/del_workflow', params)
/**
@@ -75,7 +82,9 @@ export const getWorkflowHistoryDetail = (
* @param {ExecuteWorkflowParams} [params] 请求参数
* @returns {useAxiosReturn<AxiosResponseData, ExecuteWorkflowParams>} 手动执行工作流的组合式 API 调用封装。包含响应数据、加载状态及执行函数。
*/
export const executeWorkflow = (params?: ExecuteWorkflowParams): useAxiosReturn<AxiosResponseData, ExecuteWorkflowParams> =>
export const executeWorkflow = (
params?: ExecuteWorkflowParams,
): useAxiosReturn<AxiosResponseData, ExecuteWorkflowParams> =>
useApi<AxiosResponseData, ExecuteWorkflowParams>('/v1/workflow/execute_workflow', params)
/**
@@ -93,5 +102,15 @@ export const updateWorkflowExecType = (
* @param {EnableWorkflowParams} [params] 请求参数
* @returns {useAxiosReturn<AxiosResponseData, EnableWorkflowParams>} 启用或禁用工作流的组合式 API 调用封装。包含响应数据、加载状态及执行函数。
*/
export const enableWorkflow = (params?: EnableWorkflowParams): useAxiosReturn<AxiosResponseData, EnableWorkflowParams> =>
export const enableWorkflow = (
params?: EnableWorkflowParams,
): useAxiosReturn<AxiosResponseData, EnableWorkflowParams> =>
useApi<AxiosResponseData, EnableWorkflowParams>('/v1/workflow/active', params)
/**
* @description 停止工作流执行
* @param {StopWorkflowParams} [params] 请求参数
* @returns {useAxiosReturn<AxiosResponseData, StopWorkflowParams>} 停止工作流执行的组合式 API 调用封装。包含响应数据、加载状态及执行函数。
*/
export const stopWorkflow = (params?: StopWorkflowParams): useAxiosReturn<AxiosResponseData, StopWorkflowParams> =>
useApi<AxiosResponseData, StopWorkflowParams>('/v1/workflow/stop', params)

View File

@@ -0,0 +1,79 @@
<svg width="64.000000pt" height="64.000000pt" viewBox="0 0 64.000000 64.000000" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(0.000000,64.000000) scale(0.100000,-0.100000)" fill="#fefefe" stroke="none">
<path d="M0 320 l0 -320 320 0 320 0 0 320 0 320 -320 0 -320 0 0 -320z m205
184 c-11 -9 -27 -21 -35 -27 -9 -7 -26 -25 -38 -41 l-22 -29 0 -55 0 -55 34
-31 c39 -37 94 -56 162 -56 l49 0 42 19 c64 30 82 82 42 122 l-22 22 -74 -7
-75 -6 -29 29 -29 29 9 24 c4 13 26 33 47 45 l39 23 47 0 47 0 56 -21 56 -21
34 -33 c18 -19 38 -49 45 -69 l11 -35 -11 -43 -12 -43 -48 -46 -47 -47 -54
-17 c-108 -36 -230 -16 -308 51 -45 39 -81 100 -81 136 1 35 34 100 66 129 25
23 100 68 114 68 3 0 -4 -7 -15 -15z" />
</g>
<g transform="translate(0.000000,64.000000) scale(0.100000,-0.100000)" fill="#2470f0" stroke="none">
<path d="M267 498 c-42 -21 -63 -55 -54 -90 10 -38 95 -75 111 -49 5 7 50 8
98 2 4 -1 15 -12 23 -25 l16 -25 -11 -23 c-17 -32 -51 -58 -77 -58 -13 0 -23
-4 -23 -10 l0 -10 -44 0 c-25 0 -48 5 -51 10 -3 6 -15 10 -26 10 -29 0 -87 41
-98 70 -12 29 -14 44 -12 82 l1 27 30 34 c17 19 30 40 30 46 0 40 -99 -37
-130 -101 l-18 -38 5 -42 6 -43 26 -35 c27 -36 71 -79 82 -80 4 0 24 -7 45
-16 l39 -17 88 -1 c48 -1 87 2 87 7 0 4 8 7 18 7 27 0 117 56 135 84 9 14 23
44 32 68 l14 43 -11 35 c-7 19 -23 50 -36 69 l-25 33 -46 22 c-25 11 -50 21
-56 22 -5 1 -12 3 -15 4 -3 1 -30 3 -60 4 l-55 2 -38 -18z" />
</g>
<g transform="translate(0.000000,64.000000) scale(0.100000,-0.100000)" fill="#2573f7" stroke="none">
<path d="M159 505 c-25 -13 -60 -40 -77 -59 -30 -34 -52 -90 -52 -131 1 -38
37 -101 81 -138 l43 -37 50 -15 c64 -19 169 -19 233 0 l50 15 45 40 c48 41 73
86 65 117 l-4 18 -7 -20 c-4 -11 -15 -35 -24 -54 l-17 -35 -45 -28 c-25 -16
-57 -32 -72 -35 -16 -3 -28 -8 -28 -12 0 -3 -35 -5 -78 -4 l-77 1 -45 18 c-25
10 -46 17 -47 16 -6 -5 -103 105 -98 110 2 2 0 19 -4 38 l-7 35 14 30 c17 35
69 92 97 107 l20 11 -26 -29 c-35 -38 -52 -72 -43 -85 l7 -12 13 29 c17 38 31
54 77 88 20 16 37 32 37 37 0 16 -33 9 -81 -16z" />
<path d="M277 506 c-49 -18 -67 -34 -67 -60 l0 -21 23 27 c12 15 36 33 52 39
33 12 101 15 127 4 9 -4 22 -4 28 0 5 3 10 2 10 -2 0 -8 41 -27 63 -31 14 -1
66 -76 67 -94 0 -9 5 -20 10 -23 l10 -6 0 23 c0 13 -11 40 -25 60 l-25 36 -53
26 -53 26 -64 5 -65 5 -38 -14z" />
<path d="M210 409 c0 -23 32 -48 65 -51 53 -3 95 2 95 12 0 13 -47 13 -54 1
-4 -5 -12 -8 -19 -7 -20 3 -61 28 -74 45 l-12 16 -1 -16z" />
<path d="M380 371 c0 -5 11 -12 25 -15 13 -3 30 -16 36 -27 l11 -22 -11 -23
c-5 -13 -28 -32 -50 -44 l-40 -20 -51 0 -50 0 -45 21 c-48 22 -85 61 -85 87
l0 16 -13 -13 -12 -13 13 -5 c6 -3 10 -8 7 -13 -5 -9 51 -67 60 -62 3 1 13 -4
22 -13 8 -9 22 -14 29 -11 7 3 16 1 19 -4 4 -6 31 -10 61 -10 l54 0 0 11 c0 5
5 7 10 4 16 -10 75 31 88 60 17 38 15 53 -13 80 -23 24 -65 34 -65 16z" />
</g>
<g transform="translate(0.000000,64.000000) scale(0.100000,-0.100000)" fill="#2a74f0" stroke="none">
<path d="M164 504 l-19 -15 24 7 24 6 -7 -11 c-4 -6 -3 -11 3 -11 5 0 13 5 16
10 3 6 1 10 -5 10 -6 0 -8 5 -5 10 9 15 -9 12 -31 -6z" />
<path d="M290 510 c0 -5 4 -10 9 -10 6 0 13 5 16 10 l6 10 -15 0 c-9 0 -16 -4
-16 -10z" />
<path d="M395 510 c3 -5 10 -10 16 -10 5 0 9 5 9 10 0 6 -7 10 -16 10 l-15 0
6 -10z" />
<path d="M474 485 c11 -8 25 -15 30 -15 6 0 2 7 -8 15 -11 8 -25 15 -30 15 -6
0 -2 -7 8 -15z" />
<path d="M70 428 c-16 -22 -30 -46 -29 -52 0 -6 6 -2 12 8 7 11 25 34 41 53
15 18 23 33 17 33 -6 0 -24 -19 -41 -42z" />
<path d="M205 437 c-4 -10 -5 -21 -1 -24 10 -10 18 4 13 24 l-4 18 -8 -18z" />
<path d="M120 420 c-6 -12 -9 -24 -7 -27 3 -2 11 5 17 17 6 12 9 24 7 27 -3 2
-11 -5 -17 -17z" />
<path d="M260 360 c0 -5 7 -10 16 -10 l15 0 -6 10 c-3 6 -10 10 -16 10 -5 0
-9 -4 -9 -10z" />
<path d="M100 351 c0 -6 5 -13 10 -16 l10 -6 0 15 c0 9 -4 16 -10 16 -5 0 -10
-4 -10 -9z" />
<path d="M592 330 c1 -16 5 -30 10 -30 4 0 8 14 8 30 0 17 -4 30 -9 30 -5 0
-9 -13 -9 -30z" />
<path d="M30 296 c0 -9 5 -16 10 -16 6 0 10 4 10 9 0 6 -4 13 -10 16 l-10 6 0
-15z" />
<path d="M130 282 c0 -12 19 -26 26 -19 2 2 -2 10 -11 17 l-15 12 0 -10z" />
<path d="M425 260 c-10 -11 -13 -20 -8 -20 13 0 38 29 31 35 -3 3 -13 -4 -23
-15z" />
<path d="M566 234 c-10 -14 -16 -28 -13 -30 2 -3 12 7 21 22 10 14 16 28 13
30 -2 3 -12 -7 -21 -22z" />
<path d="M70 221 c0 -5 5 -13 10 -16 6 -3 10 -2 10 4 0 5 -4 13 -10 16 -5 3
-10 2 -10 -4z" />
<path d="M105 180 c3 -5 11 -10 16 -10 6 0 7 5 4 10 -3 6 -11 10 -16 10 -6 0
-7 -4 -4 -10z" />
<path d="M515 180 c-3 -5 -2 -10 4 -10 5 0 13 5 16 10 3 6 2 10 -4 10 -5 0
-13 -4 -16 -10z" />
<path d="M195 130 c3 -5 11 -10 16 -10 6 0 7 5 4 10 -3 6 -11 10 -16 10 -6 0
-7 -4 -4 -10z" />
<path d="M425 130 c-3 -5 -2 -10 4 -10 5 0 13 5 16 10 3 6 2 10 -4 10 -5 0
-13 -4 -16 -10z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -0,0 +1 @@
<svg class="icon" width="30" height="30" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M803.6 859.2c0 26.6-20.4 49.2-48.4 51.8-21.2 2-48.6-11-54.8-39.8-5.6-26-13.2-50.8-29-72.4-6-8.2-12.8-16-20-23.4-7.6-8-9.4-14-4.4-19.8 5-5.8 12.8-5 20.8 3.2 20.8 21 45.4 35.2 73.6 43.6 7.2 2.2 14.8 3.4 22.2 5.2 24.6 6.2 40 26.2 40 51.6z" fill="#FC6401"/><path d="M698.2 549.8c.2-28.4 20.8-50.2 49.6-52.6 25.6-2.2 50.6 17.6 55 45.2 6 36.2 22.8 66.2 48.4 92 3.2 3.2 5.6 9.2 5.2 13.8-.4 7.2-9.8 10.6-16.2 6.4-3.4-2.2-6.2-5-9-7.8-25.4-24.6-55.6-39.4-90.4-45.4-24.8-4.6-42.6-26.2-42.6-51.6z" fill="#2DBD00"/><path d="M595.4 765.2c-26.6 0-49.2-20.4-51.8-48.4-2-21.2 11-48.6 39.8-54.8 26-5.6 50.8-13.2 72.4-29 8.2-6 16-12.8 23.4-20 8-7.6 14-9.4 19.8-4.4 5.8 5 5 12.8-3.2 20.8-21 20.8-35.2 45.4-43.6 73.6-2.2 7.2-3.4 14.8-5.2 22.2-6.2 24.6-26.2 40-51.6 40z" fill="#FFCD00"/><path d="M898.8 650c28.4.2 50.2 20.8 52.6 49.6 2.2 25.6-17.6 50.6-45.2 55-36.2 6-66.2 22.8-92 48.4-3.2 3.2-9.2 5.6-13.8 5.2-7.2-.4-10.6-9.8-6.4-16.2 2.2-3.4 5-6.2 7.8-9 24.6-25.4 39.4-55.6 45.4-90.4 4.6-25 26.2-42.8 51.6-42.6z" fill="#0084F0"/><path d="M734 208.6c-110.4-108.4-244.8-139.8-392.4-100-260 70.2-340.4 340.6-209 517 6.4 8.6 7.6 24.4 5.2 35.4-7 32.4-17.4 64.2-26 96.2-4.6 17.2-7.4 34.6 8 48.4 16.6 14.8 34.2 11.8 52.2 2.6 29.6-15 59.8-29.2 89-45 19-10.4 36.2-10.8 57.6-4.8 42.8 11.8 87.2 18.4 109.6 23 43.8-.8 83.6-5.2 120.2-13.6-13.8-12-23-29.2-24.8-49-.4-5.4-.2-10.8.6-16.2C466.4 715 405 710 341 688.6c-42.2-14.2-76.8-17.8-113.4 7-3.4 2.2-7.8 2.8-24.6 8.2 33.8-58.4 8.8-95-19.6-136.6-63.4-92-50.4-210.8 24.6-296.4C330.8 131 571 131 693.8 271c52.8 60.2 73.6 135.2 61.8 206.2 28 1.6 52.8 20.6 63 47.2 32-108.8 4-228.8-84.6-315.8z" fill="#0083EF"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -114,9 +114,11 @@ export default defineComponent<CAProviderSelectProps>({
class="flex-1 w-full"
options={caProviderRef.value}
renderLabel={renderLabel}
renderTag={({ option }: { option: any }) => renderSingleSelectTag({ option: option as CAProviderOption })}
renderTag={({ option }: { option: unknown }) =>
renderSingleSelectTag({ option: option as CAProviderOption })
}
filterable
filter={(pattern: string, option: any) => handleFilter(pattern, option as CAProviderOption)}
filter={(pattern: string, option: unknown) => handleFilter(pattern, option as CAProviderOption)}
placeholder={$t('t_0_1747990228780')}
value={param.value.value} // 使用 controller 中的 param.value.value
onUpdateValue={handleUpdateValue}

View File

@@ -31,7 +31,7 @@ export interface CAProviderSelectProps {
ca: string
/**
* @property email
* @description 邮箱地址,当 value 不为空时会被赋值
* @description 邮箱地址,编辑时从工作流content中传入新建时为空
*/
email: string
/**

View File

@@ -44,26 +44,36 @@ export function useCAProviderSelectController(props: CAProviderSelectProps, emit
const selectedProvider = caProviderRef.value.find((item) => item.value === param.value.value)
if (selectedProvider) {
// 对于 Let's Encrypt 和 Buypass使用传入的工作流邮件
let email = selectedProvider.email
if (selectedProvider.ca === 'letsencrypt' || selectedProvider.ca === 'buypass') {
email = props.email || selectedProvider.email
}
param.value = {
label: selectedProvider.label,
value: selectedProvider.value,
ca: selectedProvider.ca,
email: selectedProvider.email,
email: email,
}
} else if (caProviderRef.value.length > 0 && param.value.value === '') {
// 如果 param.value 为空(例如初始状态或清空后),且 caProviderRef 列表不为空,则默认选中第一个
const firstProvider = caProviderRef.value[0]
let email = firstProvider?.email || ''
if (firstProvider && (firstProvider.ca === 'letsencrypt' || firstProvider.ca === 'buypass')) {
email = props.email || firstProvider.email
}
param.value = {
label: caProviderRef.value[0]?.label || '',
value: caProviderRef.value[0]?.value || '',
ca: caProviderRef.value[0]?.ca || '',
email: caProviderRef.value[0]?.email || '',
label: firstProvider?.label || '',
value: firstProvider?.value || '',
ca: firstProvider?.ca || '',
email: email,
}
}
// 当 value 不为空时,将其赋值给 email 字段
if (param.value.value !== '') {
emit('update:email', param.value.email)
}
// 始终触发邮件更新事件,确保邮件字段能正确渲染
emit('update:email', param.value.email)
emit('update:value', { value: param.value.value, ca: param.value.ca, email: param.value.email })
}
@@ -86,15 +96,23 @@ export function useCAProviderSelectController(props: CAProviderSelectProps, emit
isLoading.value = true
errorMessage.value = ''
try {
// 添加Let's Encrypt作为首选项
// 添加Let's Encrypt作为首选项,使用传入的工作流邮件
const letsEncryptOption: CAProviderOption = {
label: "Let's Encrypt",
value: '',
ca: 'letsencrypt',
email: '',
email: props.email || '',
}
// 获取其他CA授权列表
// 添加Buypass作为第二选项使用传入的工作流邮件
const buypassOption: CAProviderOption = {
label: 'Buypass',
value: 'buypass',
ca: 'buypass',
email: props.email || '',
}
// 获取其他CA授权列表使用接口返回的邮件
const { data } = await getAllEabList({ ca: '' }).fetch()
const eabOptions: CAProviderOption[] = (data || []).map((item) => ({
label: item.name,
@@ -103,8 +121,8 @@ export function useCAProviderSelectController(props: CAProviderSelectProps, emit
email: item.mail,
}))
// 合并选项Let's Encrypt在首位
caProviderRef.value = [letsEncryptOption, ...eabOptions]
// 合并选项Let's Encrypt在首位Buypass在第二位
caProviderRef.value = [letsEncryptOption, buypassOption, ...eabOptions]
// 数据加载后,如果 props.value 有值,尝试根据 props.value 初始化 param
if (props.value) {

View File

@@ -0,0 +1,115 @@
// External Libraries
import { defineComponent } from 'vue'
import { NModal, NButton, NSpace, NScrollbar, NIcon } from 'naive-ui'
import { LogoGithub } from '@vicons/ionicons5'
// Type Imports
import type { PropType } from 'vue'
import type { UpdateLogModalProps } from './types'
import type { VersionData } from '@/types/setting'
// Relative Internal Imports - Controller
import { useUpdateLogModalController } from './useController'
/**
* @description 更新日志弹窗组件。显示版本更新信息和更新日志。
* @example
* <UpdateLogModal
* v-model:show="showModal"
* :versionData="versionData"
* />
*/
export default defineComponent({
name: 'UpdateLogModal',
props: {
/**
* 是否显示弹窗
* @default false
*/
show: {
type: Boolean as PropType<UpdateLogModalProps['show']>,
default: false,
},
/**
* 版本数据
* @default null
*/
versionData: {
type: Object as PropType<VersionData | null>,
default: null,
},
},
/**
* @event update:show - 当弹窗显示状态更新时触发
* @param {boolean} show - 弹窗显示状态
*/
emits: {
'update:show': (payload: boolean) => typeof payload === 'boolean',
},
setup(props: UpdateLogModalProps, { emit }) {
const { formattedLog, goToGitHub, handleClose } = useUpdateLogModalController(props, emit)
return () => (
<NModal
show={props.show}
onUpdateShow={(show: boolean) => emit('update:show', show)}
preset="card"
title="发现新版本"
style={{ width: '600px', maxWidth: '90vw' }}
maskClosable={false}
closable={true}
onClose={handleClose}
>
{props.versionData && (
<div class="update-log-content">
{/* 版本信息 */}
<div class="mb-[1.6rem]">
<div class="flex items-center justify-between mb-[.8rem]">
<span class="text-[1.5rem] font-medium">: {props.versionData.version}</span>
<span class="text-[1.5rem] font-medium text-primary">: {props.versionData.new_version}</span>
</div>
<div class="text-[1.4rem] text-gray-500">: {props.versionData.date}</div>
</div>
{/* 更新日志 */}
<div class="mb-[2.4rem]">
<h3 class="text-[1.6rem] font-medium mb-[1.2rem]"></h3>
<NScrollbar style={{ maxHeight: '300px' }}>
<div class="update-log-list">
{formattedLog.value.map((line, index) => (
<div key={index} class="mb-[.8rem]">
{line.startsWith('■') ? (
<div class="text-[1.4rem] font-medium text-primary mb-[.4rem]">{line}</div>
) : line.startsWith('新增:') || line.startsWith('调整:') ? (
<div class="ml-[1.6rem] text-[1.3rem] text-green-600">{line}</div>
) : (
<div class="ml-[1.6rem] text-[1.3rem] text-gray-700">{line}</div>
)}
</div>
))}
</div>
</NScrollbar>
</div>
{/* 操作按钮 */}
<div class="flex justify-end">
<NSpace size="medium">
<NButton size="medium" onClick={handleClose}>
<span class="text-[1.4rem]"></span>
</NButton>
<NButton size="medium" type="primary" onClick={goToGitHub}>
<div class="flex items-center">
<NIcon size="18" class="mr-[.8rem]">
<LogoGithub />
</NIcon>
<span class="text-[1.4rem]">GitHub下载</span>
</div>
</NButton>
</NSpace>
</div>
</div>
)}
</NModal>
)
},
})

View File

@@ -0,0 +1,44 @@
// Type Imports
import type { Ref } from 'vue'
import type { VersionData } from '@/types/setting'
/**
* @description 更新日志弹窗组件的 Props 定义
*/
export interface UpdateLogModalProps {
/**
* 是否显示弹窗
* @default false
*/
show: boolean
/**
* 版本数据
* @default null
*/
versionData: VersionData | null
}
/**
* @description 更新日志弹窗组件的 Emits 定义
*/
export interface UpdateLogModalEmits {
(e: 'update:show', show: boolean): void
}
/**
* @description 更新日志弹窗控制器暴露给视图的数据和方法
*/
export interface UpdateLogModalControllerExposes {
/**
* 格式化后的更新日志行数组
*/
formattedLog: Ref<string[]>
/**
* 跳转到GitHub的方法
*/
goToGitHub: () => void
/**
* 关闭弹窗的方法
*/
handleClose: () => void
}

View File

@@ -0,0 +1,50 @@
// External Libraries
import { computed } from 'vue'
// Type Imports
import type { UpdateLogModalProps, UpdateLogModalEmits, UpdateLogModalControllerExposes } from './types'
/**
* @description 更新日志弹窗组件的控制器逻辑
* @param props - 组件的 props
* @param emit - 组件的 emit 函数
* @returns {UpdateLogModalControllerExposes} 暴露给视图的响应式数据和方法
*/
export function useUpdateLogModalController(
props: UpdateLogModalProps,
emit: (event: 'update:show', payload: boolean) => void,
): UpdateLogModalControllerExposes {
/**
* @description 处理更新日志文本,将 \r\n 和 \n 转换为换行
*/
const formattedLog = computed(() => {
if (!props.versionData?.log) return []
return props.versionData.log
.replace(/\\r\\n/g, '\n')
.replace(/\\n/g, '\n')
.split('\n')
.filter(line => line.trim() !== '')
})
/**
* @description 跳转到GitHub
*/
const goToGitHub = (): void => {
window.open('https://github.com/allinssl/allinssl', '_blank')
emit('update:show', false)
}
/**
* @description 关闭弹窗
*/
const handleClose = (): void => {
emit('update:show', false)
}
return {
formattedLog,
goToGitHub,
handleClose,
}
}

View File

@@ -114,6 +114,9 @@ nodeOptions[APPLY] = () =>
provider_id: '',
algorithm: 'RSA2048',
skip_check: 0,
close_cname: 0,
max_wait: undefined,
ignore_check: 0,
},
childNode: null,
},

View File

@@ -217,6 +217,9 @@ export interface ApplyNodeConfig {
name_server: string // DNS递归服务器
skip_check: number // 跳过检查
algorithm: string // 数字证书算法
close_cname: number // 禁用CNAME支持0关闭1开启默认0
max_wait?: number // 预检查超时时间,单位秒(可选)
ignore_check: number // 忽略预检查结果1继续0停止默认0
// 高级功能
// algorithm: 'RSA2048' | 'RSA3072' | 'RSA4096' | 'RSA8192' | 'EC256' | 'EC384' // 数字证书算法
// dnsServer?: string // 指定DNS解析服务器

View File

@@ -191,6 +191,36 @@ export const ApiProjectConfig: Record<string, ApiProjectType> = {
type: ['dns'],
sort: 20,
},
namesilo: {
name: 'Namesilo',
icon: 'namesilo',
type: ['dns'],
sort: 21,
},
namedotcom: {
name: 'Name.com',
icon: 'namedotcom',
type: ['dns'],
sort: 22,
},
bunny: {
name: 'Bunny',
icon: 'bunny',
type: ['dns'],
sort: 23,
},
gcore: {
name: 'Gcore',
icon: 'gcore',
type: ['dns'],
sort: 24,
},
jdcloud: {
name: '京东云',
icon: 'jdcloud',
type: ['dns'],
sort: 25,
},
}

View File

@@ -633,5 +633,16 @@
"t_3_1748591484673": "Webhook",
"t_4_1748591492587": "جارٍ اختبار إشعار {type}",
"t_5_1748591491370": "اختبار{type}",
"t_0_1748591669194": "إرسال رسالة اختبار إلى، التكوين الحالي {type} إشعار، هل تتابع؟"
"t_0_1748591669194": "إرسال رسالة اختبار إلى، التكوين الحالي {type} إشعار، هل تتابع؟",
"t_0_1749119980577": "خطأ في تنسيق عنوان IP الخادم أو اسم النطاق",
"t_0_1749204565782": "إيقاف التنفيذ",
"t_1_1749204570473": "هل أنت متأكد أنك تريد إيقاف تنفيذ سير العمل الحالي؟",
"t_2_1749204567193": "تعطيل دعم CNAME",
"t_0_1749263105073": "وقت انتهاء الفحص المسبق (ثانية)",
"t_1_1749263104936": "الرجاء إدخال مهلة التحقق المسبق",
"t_2_1749263103765": "تجاهل نتائج الفحص المسبق",
"t_3_1749263104237": "عندما تفشل الفحص المسبق",
"t_4_1749263101853": "استمر",
"t_5_1749263101934": "توقف",
"t_6_1749263103891": "إرسال طلب التحقق إلى CA"
}

View File

@@ -633,5 +633,16 @@
"t_3_1748591484673": "Webhook",
"t_4_1748591492587": "Testing {type} notification",
"t_5_1748591491370": "Test{type}",
"t_0_1748591669194": "Send test message to, current configuration {type} notification, continue?"
"t_0_1748591669194": "Send test message to, current configuration {type} notification, continue?",
"t_0_1749119980577": "Server IP address or domain name format error",
"t_0_1749204565782": "Stop execution",
"t_1_1749204570473": "Are you sure you want to stop the execution of the current workflow?",
"t_2_1749204567193": "Disable CNAME support",
"t_0_1749263105073": "Pre-check timeout (seconds)",
"t_1_1749263104936": "Please enter the pre-check timeout",
"t_2_1749263103765": "Ignore pre-check results",
"t_3_1749263104237": "When the pre-check fails",
"t_4_1749263101853": "Continue",
"t_5_1749263101934": "Stop",
"t_6_1749263103891": "Initiate verification request to CA"
}

View File

@@ -633,5 +633,16 @@
"t_3_1748591484673": "Webhook",
"t_4_1748591492587": "Probando notificación {type}",
"t_5_1748591491370": "Prueba{type}",
"t_0_1748591669194": "Enviar mensaje de prueba a, configuración actual {type} notificación, ¿continuar?"
"t_0_1748591669194": "Enviar mensaje de prueba a, configuración actual {type} notificación, ¿continuar?",
"t_0_1749119980577": "Error de formato de dirección IP o nombre de dominio del servidor",
"t_0_1749204565782": "Detener ejecución",
"t_1_1749204570473": "¿Estás seguro de que quieres detener la ejecución del flujo de trabajo actual?",
"t_2_1749204567193": "Deshabilitar soporte CNAME",
"t_0_1749263105073": "Tiempo de espera de la preverificación (segundos)",
"t_1_1749263104936": "Introduce el tiempo de espera de la comprobación previa",
"t_2_1749263103765": "Ignorar los resultados de la verificación previa",
"t_3_1749263104237": "Cuando falla la verificación previa",
"t_4_1749263101853": "Continuar",
"t_5_1749263101934": "Detener",
"t_6_1749263103891": "Enviar solicitud de verificación a CA"
}

View File

@@ -633,5 +633,16 @@
"t_3_1748591484673": "Webhook",
"t_4_1748591492587": "Test de la notification {type}",
"t_5_1748591491370": "Test{type}",
"t_0_1748591669194": "Envoyer un message test à, configuration actuelle {type} notification, continuer ?"
"t_0_1748591669194": "Envoyer un message test à, configuration actuelle {type} notification, continuer ?",
"t_0_1749119980577": "Erreur de format d'adresse IP ou de nom de domaine du serveur",
"t_0_1749204565782": "Arrêter l'exécution",
"t_1_1749204570473": "Êtes-vous sûr de vouloir arrêter l'exécution du workflow actuel ?",
"t_2_1749204567193": "Désactiver la prise en charge CNAME",
"t_0_1749263105073": "Délai d'expiration de la pré-vérification (secondes)",
"t_1_1749263104936": "Veuillez entrer le délai d'attente de pré-vérification",
"t_2_1749263103765": "Ignorer les résultats de la pré-vérification",
"t_3_1749263104237": "Lorsque la pré-vérification échoue",
"t_4_1749263101853": "Continuer",
"t_5_1749263101934": "Arrêter",
"t_6_1749263103891": "Envoyer une demande de vérification à CA"
}

View File

@@ -633,5 +633,16 @@
"t_3_1748591484673": "Webhook",
"t_4_1748591492587": "{type}通知をテスト中",
"t_5_1748591491370": "テスト{type}",
"t_0_1748591669194": "テストメッセージを送信します、現在の設定 {type} 通知、続行しますか?"
"t_0_1748591669194": "テストメッセージを送信します、現在の設定 {type} 通知、続行しますか?",
"t_0_1749119980577": "サーバーのIPアドレスまたはドメイン名の形式が正しくありません",
"t_0_1749204565782": "実行を停止",
"t_1_1749204570473": "現在のワークフローの実行を停止しますか?",
"t_2_1749204567193": "CNAMEサポートを無効にする",
"t_0_1749263105073": "事前チェックのタイムアウト時間(秒)",
"t_1_1749263104936": "事前チェックのタイムアウト時間を入力してください",
"t_2_1749263103765": "事前チェック結果を無視",
"t_3_1749263104237": "事前チェックが失敗した場合",
"t_4_1749263101853": "続ける",
"t_5_1749263101934": "停止",
"t_6_1749263103891": "CAに検証リクエストを送信する"
}

View File

@@ -633,5 +633,16 @@
"t_3_1748591484673": "Webhook",
"t_4_1748591492587": "{type} 알림 테스트 중",
"t_5_1748591491370": "테스트{type}",
"t_0_1748591669194": "테스트 메시지를 보냅니다, 현재 구성 {type} 알림, 계속하시겠습니까?"
"t_0_1748591669194": "테스트 메시지를 보냅니다, 현재 구성 {type} 알림, 계속하시겠습니까?",
"t_0_1749119980577": "서버 IP 주소 또는 도메인 이름 형식 오류",
"t_0_1749204565782": "실행 중지",
"t_1_1749204570473": "현재 워크플로우 실행을 중지하시겠습니까?",
"t_2_1749204567193": "CNAME 지원 비활성화",
"t_0_1749263105073": "사전 점검 시간 초과 (초)",
"t_1_1749263104936": "사전 검사 시간 초과 시간을 입력하십시오",
"t_2_1749263103765": "사전 점검 결과 무시",
"t_3_1749263104237": "사전 점검이 실패했을 때",
"t_4_1749263101853": "계속하다",
"t_5_1749263101934": "중지",
"t_6_1749263103891": "CA에 검증 요청 시작"
}

View File

@@ -633,5 +633,16 @@
"t_3_1748591484673": "Webhook",
"t_4_1748591492587": "Testando notificação {type}",
"t_5_1748591491370": "Teste{type}",
"t_0_1748591669194": "Enviar mensagem de teste para, configuração atual {type} notificação, continuar?"
"t_0_1748591669194": "Enviar mensagem de teste para, configuração atual {type} notificação, continuar?",
"t_0_1749119980577": "Erro de formato de endereço IP ou nome de domínio do servidor",
"t_0_1749204565782": "Parar execução",
"t_1_1749204570473": "Tem certeza de que deseja interromper a execução do fluxo de trabalho atual?",
"t_2_1749204567193": "Desativar suporte CNAME",
"t_0_1749263105073": "Tempo limite de pré-verificação (segundos)",
"t_1_1749263104936": "Insira o tempo limite de pré-verificação",
"t_2_1749263103765": "Ignorar resultados de pré-verificação",
"t_3_1749263104237": "Quando a pré-verificação falha",
"t_4_1749263101853": "Continuar",
"t_5_1749263101934": "Parar",
"t_6_1749263103891": "Enviar solicitação de verificação para CA"
}

View File

@@ -633,5 +633,16 @@
"t_3_1748591484673": "Webhook",
"t_4_1748591492587": "Тестирование уведомления {type}",
"t_5_1748591491370": "Тест{type}",
"t_0_1748591669194": "Отправить тестовое сообщение, текущая конфигурация {type} уведомления, продолжить?"
"t_0_1748591669194": "Отправить тестовое сообщение, текущая конфигурация {type} уведомления, продолжить?",
"t_0_1749119980577": "Ошибка формата IP-адреса сервера или доменного имени",
"t_0_1749204565782": "Остановить выполнение",
"t_1_1749204570473": "Вы уверены, что хотите остановить выполнение текущего рабочего процесса?",
"t_2_1749204567193": "Отключить поддержку CNAME",
"t_0_1749263105073": "Таймаут предварительной проверки (секунды)",
"t_1_1749263104936": "Введите время ожидания предварительной проверки",
"t_2_1749263103765": "Игнорировать результаты предварительной проверки",
"t_3_1749263104237": "Когда предварительная проверка не удалась",
"t_4_1749263101853": "Продолжить",
"t_5_1749263101934": "Остановить",
"t_6_1749263103891": "Отправить запрос на проверку в CA"
}

View File

@@ -633,5 +633,16 @@
"t_3_1748591484673": "Webhook",
"t_4_1748591492587": "正在测试{type}通知",
"t_5_1748591491370": "测试{type}",
"t_0_1748591669194": "发送测试信息到,当前配置{type}通知,是否继续?"
"t_0_1748591669194": "发送测试信息到,当前配置{type}通知,是否继续?",
"t_0_1749119980577": "服务器IP地址或域名格式错误",
"t_0_1749204565782": "停止执行",
"t_1_1749204570473": "确定要停止当前工作流的执行吗?",
"t_2_1749204567193": "禁用CNAME支持",
"t_0_1749263105073": "预检查超时时间(秒)",
"t_1_1749263104936": "请输入预检查超时时间",
"t_2_1749263103765": "忽略预检查结果",
"t_3_1749263104237": "当预检查失败时",
"t_4_1749263101853": "继续",
"t_5_1749263101934": "停止",
"t_6_1749263103891": "向CA发起验证请求"
}

View File

@@ -632,5 +632,16 @@
"t_3_1748591484673": "Webhook",
"t_4_1748591492587": "正在測試{type}通知",
"t_5_1748591491370": "測試{type}",
"t_0_1748591669194": "發送測試信息到,當前配置{type}通知,是否繼續?"
"t_0_1748591669194": "發送測試信息到,當前配置{type}通知,是否繼續?",
"t_0_1749119980577": "伺服器IP位址或網域名稱格式錯誤",
"t_0_1749204565782": "停止執行",
"t_1_1749204570473": "確定要停止當前工作流的執行嗎?",
"t_2_1749204567193": "禁用CNAME支援",
"t_0_1749263105073": "預檢查超時時間(秒)",
"t_1_1749263104936": "請輸入預檢查超時時間",
"t_2_1749263103765": "忽略預檢查結果",
"t_3_1749263104237": "當預檢查失敗時",
"t_4_1749263101853": "繼續",
"t_5_1749263101934": "停止",
"t_6_1749263103891": "向CA發起驗證請求"
}

View File

@@ -53,7 +53,12 @@ export interface AddAccessParams<
| NS1AccessConfig
| CloudnsAccessConfig
| AwsAccessConfig
| AzureAccessConfig,
| AzureAccessConfig
| NamesiloAccessConfig
| NamedotcomAccessConfig
| BunnyAccessConfig
| GcoreAccessConfig
| JdcloudAccessConfig,
> {
name: string
type: string
@@ -79,7 +84,12 @@ export interface UpdateAccessParams<
| NS1AccessConfig
| CloudnsAccessConfig
| AwsAccessConfig
| AzureAccessConfig,
| AzureAccessConfig
| NamesiloAccessConfig
| NamedotcomAccessConfig
| BunnyAccessConfig
| GcoreAccessConfig
| JdcloudAccessConfig,
> extends AddAccessParams<T> {
id: string
}
@@ -213,6 +223,43 @@ export interface AzureAccessConfig {
environment: string
}
/**
* Namesilo授权配置
*/
export interface NamesiloAccessConfig {
api_key: string
}
/**
* Name.com授权配置
*/
export interface NamedotcomAccessConfig {
username: string
api_token: string
}
/**
* Bunny授权配置
*/
export interface BunnyAccessConfig {
api_key: string
}
/**
* Gcore授权配置
*/
export interface GcoreAccessConfig {
api_token: string
}
/**
* 京东云授权配置
*/
export interface JdcloudAccessConfig {
access_key_id: string
secret_access_key: string
}
/** 删除授权请求参数 */
export interface DeleteAccessParams {
id: string

View File

@@ -87,6 +87,14 @@ export interface ReportWebhook {
ignore_ssl: boolean
}
/** 企业微信通知配置 */
export interface ReportWecom {
name?: string
enabled: string
url: string
data: string
}
/** 添加告警请求参数 */
export interface AddReportParams<T = string> {
name: string
@@ -109,6 +117,25 @@ export interface TestReportParams {
id: number
}
/** 获取版本信息请求参数 */
export interface GetVersionParams {
// 无参数
}
/** 版本信息数据 */
export interface VersionData {
date: string // 版本日期
log: string // 更新日志
new_version: string // 新版本号
update: string // 是否有更新 "1" 表示有更新,"0" 表示无更新
version: string // 当前版本号
}
/** 获取版本信息响应 */
export interface GetVersionResponse extends AxiosResponseData {
data: VersionData
}
/** 消息通知选项 */
export interface NotifyProviderOption {
label: string

View File

@@ -81,7 +81,7 @@ export interface WorkflowHistoryItem {
create_time: number
end_time: string
exec_type: 'auto' | 'manual'
status: 'success' | 'failed' | 'pending'
status: 'success' | 'failed' | 'running'
workflow_id: string
}
@@ -112,3 +112,8 @@ export interface EnableWorkflowParams {
active: string
}
/** 停止工作流请求参数 */
export interface StopWorkflowParams {
id: string
}

View File

@@ -27,7 +27,7 @@ import {
useLoadingMask,
} from '@baota/naive-ui/hooks'
import { useError } from '@baota/hooks/error'
import { isEmail, isIp, isPort, isUrl } from '@baota/utils/business'
import { isEmail, isIp, isPort, isUrl, isDomain } from '@baota/utils/business'
import { $t } from '@locales/index'
import { useStore } from './useStore'
import { ApiProjectConfig } from '@config/data'
@@ -42,6 +42,11 @@ import type {
CloudnsAccessConfig,
AwsAccessConfig,
AzureAccessConfig,
NamesiloAccessConfig,
NamedotcomAccessConfig,
BunnyAccessConfig,
GcoreAccessConfig,
JdcloudAccessConfig,
} from '@/types/access'
import type { VNode, Ref } from 'vue'
import { testAccess } from '@/api/access'
@@ -306,8 +311,8 @@ export const useApiFormController = (props: ApiFormControllerProps): ApiFormCont
required: true,
trigger: 'input',
validator: (rule: FormItemRule, value: string, callback: (error?: Error) => void) => {
if (!isIp(value)) {
return callback(new Error($t('t_0_1745317313835')))
if (!isIp(value) && !isDomain(value)) {
return callback(new Error($t('t_0_1749119980577')))
}
callback()
},
@@ -663,6 +668,27 @@ export const useApiFormController = (props: ApiFormControllerProps): ApiFormCont
useFormInput('Environment', 'config.environment', { allowInput: noSideSpace, placeholder: 'public' }),
)
break
case 'namesilo':
items.push(useFormInput('API Key', 'config.api_key', { allowInput: noSideSpace }))
break
case 'namedotcom':
items.push(
useFormInput('Username', 'config.username', { allowInput: noSideSpace }),
useFormInput('API Token', 'config.api_token', { allowInput: noSideSpace }),
)
break
case 'bunny':
items.push(useFormInput('API Key', 'config.api_key', { allowInput: noSideSpace }))
break
case 'gcore':
items.push(useFormInput('API Token', 'config.api_token', { allowInput: noSideSpace }))
break
case 'jdcloud':
items.push(
useFormInput('Access Key ID', 'config.access_key_id', { allowInput: noSideSpace }),
useFormInput('Secret Access Key', 'config.secret_access_key', { allowInput: noSideSpace }),
)
break
default:
break
}
@@ -760,6 +786,33 @@ export const useApiFormController = (props: ApiFormControllerProps): ApiFormCont
environment: '',
} as AzureAccessConfig
break
case 'namesilo':
param.value.config = {
api_key: '',
} as NamesiloAccessConfig
break
case 'namedotcom':
param.value.config = {
username: '',
api_token: '',
} as NamedotcomAccessConfig
break
case 'bunny':
param.value.config = {
api_key: '',
} as BunnyAccessConfig
break
case 'gcore':
param.value.config = {
api_token: '',
} as GcoreAccessConfig
break
case 'jdcloud':
param.value.config = {
access_key_id: '',
secret_access_key: '',
} as JdcloudAccessConfig
break
}
},
)

View File

@@ -1,4 +1,4 @@
import { NFormItem, NInputNumber } from 'naive-ui'
import { NFormItem, NInputNumber, NSwitch } from 'naive-ui'
import { useForm, useFormHooks, useModalHooks } from '@baota/naive-ui/hooks'
import { useStore } from '@components/FlowChart/useStore'
import { $t } from '@locales/index'
@@ -29,6 +29,9 @@ export default defineComponent({
name_server: '',
skip_check: 0,
algorithm: 'RSA2048',
close_cname: 0,
max_wait: undefined,
ignore_check: 0,
},
}),
},
@@ -86,7 +89,8 @@ export default defineComponent({
'onUpdate:value': (val: { value: string; ca: string; email: string }) => {
param.value.eabId = val.value
param.value.ca = val.ca
if (val.value) param.value.email = val.email
// 始终更新邮件,确保 Let's Encrypt 和 Buypass 的邮件能正确显示
param.value.email = val.email
},
}}
/>
@@ -138,15 +142,12 @@ export default defineComponent({
},
{ showRequireMark: false },
),
useFormInput(
$t('t_0_1747106957037'),
'name_server',
useFormSwitch(
$t('t_2_1749204567193'),
'close_cname',
{
placeholder: $t('t_1_1747106961747'),
allowInput: noSideSpace,
onInput: (val: string) => {
param.value.name_server = val.replace(//g, ',').replace(/;/g, ',') // 中文逗号分隔
},
checkedValue: 1,
uncheckedValue: 0,
},
{ showRequireMark: false },
),
@@ -159,6 +160,62 @@ export default defineComponent({
},
{ showRequireMark: false },
),
// 只有在跳过预检查关闭时才显示DNS递归服务器、预检查超时时间和忽略预检查结果
...(param.value.skip_check === 0
? [
useFormInput(
$t('t_0_1747106957037'),
'name_server',
{
placeholder: $t('t_1_1747106961747'),
allowInput: noSideSpace,
onInput: (val: string) => {
param.value.name_server = val.replace(//g, ',').replace(/;/g, ',') // 中文逗号分隔
},
},
{ showRequireMark: false },
),
{
type: 'custom' as const,
render: () => {
return (
<NFormItem label={$t('t_0_1749263105073')} path="max_wait">
<NInputNumber
v-model:value={(param.value as ApplyNodeConfig & { max_wait?: number }).max_wait}
showButton={false}
min={1}
class="w-full"
placeholder={$t('t_1_1749263104936')}
/>
</NFormItem>
)
},
},
{
type: 'custom' as const,
render: () => {
return (
<NFormItem label={$t('t_2_1749263103765')} path="ignore_check">
<div class="flex items-center">
<span class="text-[1.4rem] mr-[1.2rem]">{$t('t_3_1749263104237')}</span>
<NSwitch
v-model:value={param.value.ignore_check}
checkedValue={1}
uncheckedValue={0}
class="mx-[.5rem]"
v-slots={{
checked: () => $t('t_4_1749263101853'),
unchecked: () => $t('t_5_1749263101934'),
}}
/>
<span class="text-[1.4rem] ml-[1.2rem]">{$t('t_6_1749263103891')}</span>
</div>
</NFormItem>
)
},
},
]
: []),
]
: []),
useFormHelp([
@@ -183,6 +240,7 @@ export default defineComponent({
confirm(async (close) => {
try {
await example.value?.validate()
updateNodeConfig(props.node.id, data.value) // 更新节点配置
isRefreshNode.value = props.node.id // 刷新节点
close()

View File

@@ -35,6 +35,7 @@ const {
workflowFormData,
deleteExistingWorkflow,
executeExistingWorkflow,
stopExistingWorkflow,
setWorkflowActive,
setWorkflowExecType,
caFormData,
@@ -220,7 +221,7 @@ export const useController = () => {
useModal({
title: workflow ? `${workflow.name}】 - ${$t('t_9_1745215914666')}` : $t('t_9_1745215914666'),
component: HistoryModal,
area: 800,
area: 850,
componentProps: { id: workflow.id.toString() },
})
}
@@ -422,6 +423,23 @@ export const useHistoryController = (id: string) => {
})
}
/**
* @description 停止工作流执行
* @param {WorkflowHistoryItem} historyItem - 工作流历史记录项
*/
const handleStopWorkflow = async (historyItem: WorkflowHistoryItem) => {
useDialog({
title: $t('t_0_1749204565782'),
content: $t('t_1_1749204570473'),
onPositiveClick: async () => {
await stopExistingWorkflow(historyItem.id)
await fetch() // 刷新历史记录表格
// 触发外部主表格刷新
refreshTable.value = true
},
})
}
/**
* @description 创建历史记录表格列配置
* @returns {DataTableColumn<WorkflowHistoryItem>[]} 返回表格列配置数组
@@ -430,7 +448,7 @@ export const useHistoryController = (id: string) => {
{
title: $t('t_4_1745227838558'),
key: 'create_time',
width: 230,
width: 200,
render: (row: WorkflowHistoryItem) => {
// 处理数字类型的时间戳
return row.create_time ? row.create_time : '-'
@@ -439,7 +457,7 @@ export const useHistoryController = (id: string) => {
{
title: $t('t_5_1745227839906'),
key: 'end_time',
width: 230,
width: 200,
render: (row: WorkflowHistoryItem) => {
// 处理数字类型的时间戳
return row.end_time ? row.end_time : '-'
@@ -448,7 +466,7 @@ export const useHistoryController = (id: string) => {
{
title: $t('t_6_1745227838798'),
key: 'exec_type',
width: 110,
width: 120,
render: (row: WorkflowHistoryItem) => (
<NTag type={row.exec_type === 'auto' ? 'info' : 'default'} size="small" bordered={false}>
{row.exec_type === 'auto' ? $t('t_2_1745215915397') : $t('t_3_1745215914237')}
@@ -461,9 +479,14 @@ export const useHistoryController = (id: string) => {
key: 'actions',
fixed: 'right',
align: 'right',
width: 80,
width: 180,
render: (row: WorkflowHistoryItem) => (
<NSpace justify="end">
<NSpace justify="end" size="small">
{row.status === 'running' && (
<NButton size="tiny" strong secondary type="error" onClick={() => handleStopWorkflow(row)}>
{$t('t_0_1749204565782')}
</NButton>
)}
<NButton
size="tiny"
strong

View File

@@ -5,6 +5,7 @@ import {
executeWorkflow,
updateWorkflowExecType,
enableWorkflow,
stopWorkflow,
} from '@/api/workflow'
import { getEabList, addEab, deleteEab } from '@/api/access'
import { useError } from '@baota/hooks/error'
@@ -17,6 +18,7 @@ import type {
WorkflowItem,
UpdateWorkflowExecTypeParams,
EnableWorkflowParams,
StopWorkflowParams,
} from '@/types/workflow'
import type { EabItem, EabListParams, EabAddParams } from '@/types/access'
import type { TableResponse } from '@baota/naive-ui/types/table'
@@ -148,6 +150,22 @@ export const useWorkflowStore = defineStore('workflow-store', () => {
}
}
/**
* 停止工作流执行
* @description 停止指定工作流的执行
* @param {string} id - 工作流ID
* @returns {Promise<void>} 停止执行结果
*/
const stopExistingWorkflow = async (id: string) => {
try {
const { message, fetch } = stopWorkflow({ id })
message.value = true
await fetch()
} catch (error) {
handleError(error).default($t('t_1_1747895712756'))
}
}
/**
* 获取CA授权列表
* @param {EabListParams} params - 请求参数
@@ -217,6 +235,7 @@ export const useWorkflowStore = defineStore('workflow-store', () => {
fetchWorkflowHistory,
deleteExistingWorkflow,
executeExistingWorkflow,
stopExistingWorkflow,
setWorkflowActive,
setWorkflowExecType,
fetchEabList,
@@ -234,4 +253,4 @@ export const useWorkflowStore = defineStore('workflow-store', () => {
export const useStore = () => {
const store = useWorkflowStore()
return { ...store, ...storeToRefs(store) }
}
}

View File

@@ -1,5 +1,15 @@
// 外部库依赖
import { Transition, type Component as ComponentType, h, defineComponent, ref, onMounted, computed, watch } from 'vue' // 添加 watch
import {
Transition,
type Component as ComponentType,
h,
defineComponent,
ref,
onMounted,
computed,
watch,
onUnmounted,
} from 'vue' // 添加 watch, onUnmounted
import { NBadge, NIcon, NLayout, NLayoutContent, NLayoutHeader, NLayoutSider, NMenu, NTooltip } from 'naive-ui'
import { RouterView } from 'vue-router'
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@vicons/antd'
@@ -12,7 +22,12 @@ import { useController } from './useController'
import { $t } from '@locales/index'
// 内部模块导入 - 样式
import styles from './index.module.css'
// 内部模块导入 - API
import { getVersion } from '@api/setting'
// 内部模块导入 - 组件
import UpdateLogModal from '@/components/UpdateLogModal'
// 内部模块导入 - 类型
import type { VersionData } from '@/types/setting'
/**
* @description 基础布局组件,包含侧边栏导航、头部信息和内容区域。
@@ -35,9 +50,39 @@ export default defineComponent({
'actionColor',
'layoutContentBackgroundColor',
'siderLoginHeight', // 确保这个变量在 Naive UI 主题中存在或已自定义
'contentPadding'
'contentPadding',
])
// 版本检查相关状态
const hasUpdate = ref(false)
const versionData = ref<VersionData | null>(null)
const showUpdateModal = ref(false)
const checkTimer = ref<NodeJS.Timeout | null>(null)
// 版本检查API
const versionApi = getVersion()
// 检查版本更新
const checkVersion = async () => {
try {
await versionApi.fetch()
if (versionApi.data.value && versionApi.data.value.data) {
const data = versionApi.data.value.data
versionData.value = data
hasUpdate.value = data.update === '1'
}
} catch (error) {
console.error('检查版本更新失败:', error)
}
}
// 点击版本号
const handleVersionClick = () => {
if (hasUpdate.value && versionData.value) {
showUpdateModal.value = true
}
}
const siderWidth = ref(200)
const siderCollapsedWidth = ref(60)
@@ -50,13 +95,28 @@ export default defineComponent({
if (isMobile.value || isNarrowScreen.value) {
isCollapsed.value = true
}
// 初始检查版本
checkVersion()
// 设置定时检查版本更新每30分钟检查一次
checkTimer.value = setInterval(checkVersion, 30 * 60 * 1000)
})
// 组件卸载时清理定时器
onUnmounted(() => {
if (checkTimer.value) {
clearInterval(checkTimer.value)
}
})
// 监听屏幕宽度变化,自动折叠/展开菜单
watch(isNarrowScreen, (newValue) => {
if (newValue && !isMobile.value) { // 仅在非移动设备且宽度小于1100px时处理
if (newValue && !isMobile.value) {
// 仅在非移动设备且宽度小于1100px时处理
isCollapsed.value = true
} else if (!newValue && !isMobile.value) { // 宽度大于1100px且非移动设备时
} else if (!newValue && !isMobile.value) {
// 宽度大于1100px且非移动设备时
isCollapsed.value = false
}
})
@@ -109,13 +169,13 @@ export default defineComponent({
class={[styles.sider, siderDynamicClass.value].join(' ')}
bordered
>
<div class={`${styles.logoContainer} ${
// Logo 容器的 'active' 状态 (仅在桌面端且折叠时应用)
// 在移动端,由于 NLayoutSider 自身宽度不变,不应用 active 样式来改变 Logo 区域布局
(isMobile.value ? false : isCollapsed.value)
? styles.logoContainerActive
: ''
}`}>
<div
class={`${styles.logoContainer} ${
// Logo 容器的 'active' 状态 (仅在桌面端且折叠时应用)
// 在移动端,由于 NLayoutSider 自身宽度不变,不应用 active 样式来改变 Logo 区域布局
(isMobile.value ? false : isCollapsed.value) ? styles.logoContainerActive : ''
}`}
>
{/* Logo 显示逻辑 */}
{(isMobile.value ? false : isCollapsed.value) ? (
// 折叠时的 Logo (仅桌面端)
@@ -134,11 +194,11 @@ export default defineComponent({
<NTooltip placement="right" trigger="hover">
{{
trigger: () => (
<div
class={styles.menuToggleButton}
onClick={() => toggleCollapse()}
>
<NIcon size={20}><MenuFoldOutlined /></NIcon> {/* 图标大小调整为 20 */}
<div class={styles.menuToggleButton} onClick={() => toggleCollapse()}>
<NIcon size={20}>
<MenuFoldOutlined />
</NIcon>{' '}
{/* 图标大小调整为 20 */}
</div>
),
default: () => <span>{$t('t_4_1744098802046')}</span>,
@@ -172,9 +232,7 @@ export default defineComponent({
{{
trigger: () => (
<div class={styles.headerMenuToggleButton} onClick={() => toggleCollapse()}>
<NIcon size={20}>
{isCollapsed.value ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
</NIcon>
<NIcon size={20}>{isCollapsed.value ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}</NIcon>
</div>
),
default: () => <span></span>,
@@ -183,8 +241,13 @@ export default defineComponent({
</div>
)}
<div class={styles.systemInfo}>
<NBadge value={1} show={false} dot>
<span class="px-1 sm:px-[.5rem] cursor-pointer">v1.0.4</span>
<NBadge value={1} show={hasUpdate.value} dot>
<span
class="px-[.8rem] sm:px-[.5rem] py-[.4rem] cursor-pointer hover:text-primary transition-colors text-[1.4rem] font-medium"
onClick={handleVersionClick}
>
v1.0.4
</span>
</NBadge>
</div>
</NLayoutHeader>
@@ -199,9 +262,10 @@ export default defineComponent({
</NLayoutContent>
</NLayout>
{/* 移动端菜单展开时的背景遮罩 */}
{showBackdrop.value && (
<div class={styles.mobileMenuBackdrop} onClick={() => toggleCollapse()}></div>
)}
{showBackdrop.value && <div class={styles.mobileMenuBackdrop} onClick={() => toggleCollapse()}></div>}
{/* 更新日志弹窗 */}
<UpdateLogModal v-model:show={showUpdateModal.value} versionData={versionData.value} />
</NLayout>
)
},

View File

@@ -14,7 +14,7 @@ import {
useLoadingMask,
} from '@baota/naive-ui/hooks'
import { useError } from '@baota/hooks/error'
import { isDomain } from '@baota/utils/business'
import { isDomain, isPort, isIp } from '@baota/utils/business'
import { $t } from '@locales/index'
// Store和组件
@@ -64,6 +64,31 @@ const {
// 错误处理
const { handleError } = useError()
/**
* 验证域名或IP+端口格式
* @param value - 要验证的值
* @returns {boolean} 如果是有效的域名、IP地址或它们加端口的格式则返回 true
*/
const isDomainWithPort = (value: string): boolean => {
if (!value) return false
// 检查是否包含端口号
const parts = value.split(':')
if (parts.length === 1) {
// 只有域名或IP验证域名或IP地址
return isDomain(value) || isIp(value)
} else if (parts.length === 2) {
// 域名/IP+端口格式
const [host, port] = parts
if (!host || !port) return false
return (isDomain(host) || isIp(host)) && isPort(port)
}
// 超过一个冒号格式不正确IPv6除外但这里暂不处理IPv6+端口的复杂情况)
return false
}
/**
* 监控管理业务逻辑控制器
* @description 处理监控列表页面的业务逻辑,包括表格展示、添加、编辑、删除等操作
@@ -318,7 +343,7 @@ export const useMonitorFormController = (data: UpdateSiteMonitorParams | null =
*/
const config = computed(() => [
useFormInput('名称', 'name'),
useFormInput('域名', 'domain'),
useFormInput('域名/IP地址', 'domain'),
useFormInputNumber('周期(分钟)', 'cycle', { class: 'w-full' }),
useFormCustom(() => {
return (
@@ -342,11 +367,11 @@ export const useMonitorFormController = (data: UpdateSiteMonitorParams | null =
name: { required: true, message: '请输入名称', trigger: 'input' },
domain: {
required: true,
message: '请输入正确的域名',
message: '请输入正确的域名或IP地址',
trigger: 'input',
validator: (rule: any, value: any, callback: any) => {
if (!isDomain(value)) {
callback(new Error('请输入正确的域名'))
if (!isDomainWithPort(value)) {
callback(new Error('请输入正确的域名或IP地址支持域名:端口或IP:端口格式)'))
} else {
callback()
}

View File

@@ -1,20 +1,65 @@
import { NCard, NSpace, NDescriptions, NDescriptionsItem, NIcon, NButton } from 'naive-ui'
import { NCard, NSpace, NDescriptions, NDescriptionsItem, NIcon, NButton, NBadge, NAlert } from 'naive-ui'
import { $t } from '@locales/index'
import { LogoGithub } from '@vicons/ionicons5'
import { getVersion } from '@api/setting'
import type { VersionData } from '@/types/setting'
/**
* 关于我们标签页组件
*/
export default defineComponent({
name: 'AboutSettings',
setup() {
// 版本检查相关状态
const versionData = ref<VersionData | null>(null)
const hasUpdate = ref(false)
// 版本检查API
const versionApi = getVersion()
// 检查版本更新
const checkVersion = async () => {
try {
await versionApi.fetch()
if (versionApi.data.value && versionApi.data.value.data) {
const data = versionApi.data.value.data
versionData.value = data
hasUpdate.value = data.update === '1'
}
} catch (error) {
console.error('检查版本更新失败:', error)
}
}
// 跳转到GitHub
const goToGitHub = () => {
window.open('https://github.com/allinssl/allinssl', '_blank')
}
// 组件挂载时检查版本
onMounted(() => {
checkVersion()
})
return () => (
<div class="about-settings">
<NCard title={$t('t_4_1745833932780')} class="mb-4">
<NSpace vertical size={24}>
<NDescriptions bordered>
<NDescriptionsItem label={$t('t_5_1745833933241')}>
<div class="flex items-center">
<span class="text-[2rem] font-medium">v1.0.4</span>
<div class="flex items-center space-x-[1.2rem]">
<span class="text-[2.0rem] font-medium">v1.0.4</span>
{hasUpdate.value && versionData.value && (
<div class="relative">
<NBadge value="NEW" type="success" offset={[4, -3]}>
<span
class="text-[1.4rem] text-primary cursor-pointer font-medium inline-block px-[.8rem] py-[.4rem]"
onClick={goToGitHub}
>
{versionData.value.new_version}
</span>
</NBadge>
</div>
)}
</div>
</NDescriptionsItem>
<NDescriptionsItem label={$t('t_29_1746667589773')}>
@@ -22,7 +67,7 @@ export default defineComponent({
<NIcon size="20" class="text-gray-600">
<LogoGithub />
</NIcon>
<NButton text tag="a" href="https://github.com/allinssl/allinssl" target="_blank" type="primary">
<NButton text onClick={goToGitHub} type="primary">
https://github.com/allinssl/allinssl
</NButton>
</div>
@@ -31,6 +76,33 @@ export default defineComponent({
</NSpace>
</NCard>
{/* 新版本信息卡片 */}
{hasUpdate.value && versionData.value && (
<NCard title="发现新版本" class="mb-4">
<NAlert type="info" title={`新版本 ${versionData.value.new_version} 已发布`} class="mb-[1.6rem]">
<div class="text-[1.4rem]">
<div class="mb-[1.2rem] text-[1.4rem]">: {versionData.value.date}</div>
<div class="mb-[1.2rem] text-[1.4rem]">
<strong>:</strong>
</div>
<div class="whitespace-pre-line text-gray-700 text-[1.3rem] leading-relaxed">
{versionData.value.log.replace(/\\r\\n/g, '\n').replace(/\\n/g, '\n')}
</div>
<div class="mt-4">
<NButton size="medium" type="primary" onClick={goToGitHub}>
<div class="flex items-center">
<NIcon size="18" class="mr-2">
<LogoGithub />
</NIcon>
GitHub下载
</div>
</NButton>
</div>
</div>
</NAlert>
</NCard>
)}
<NCard title={$t('t_13_1745833933630')} class="mb-4">
<div class="about-content">
<p class="text-gray-700 leading-relaxed">

View File

@@ -65,6 +65,19 @@ export default defineComponent({
return () => (
<div class="webhook-channel-form">
<WebhookForm labelPlacement="top"></WebhookForm>
{/* 模板变量说明 */}
<div class="mt-4 p-4 bg-gray-50 rounded-md">
<div class="font-medium text-gray-700 mb-3 text-xl"></div>
<div class="text-gray-600 space-y-3 text-lg">
<div>
<code class="px-2 py-1 bg-gray-200 rounded text-lg font-mono">__subject__</code>
</div>
<div>
<code class="px-2 py-1 bg-gray-200 rounded text-lg font-mono">__body__</code>
</div>
</div>
</div>
</div>
)
},

View File

@@ -0,0 +1,94 @@
import { useForm, useModalHooks } from '@baota/naive-ui/hooks'
import { useError } from '@baota/hooks/error'
import { useWecomChannelFormController } from './useController'
import { useStore } from '@settings/useStore'
import type { ReportWecom, ReportType } from '@/types/setting'
/**
* 企业微信通知渠道表单组件
*/
export default defineComponent({
name: 'WecomChannelModel',
props: {
data: {
type: Object as PropType<ReportType<ReportWecom> | null>,
default: () => null,
},
},
setup(props: { data: ReportType<ReportWecom> | null }) {
const { handleError } = useError()
const { confirm } = useModalHooks()
const { fetchNotifyChannels } = useStore()
const { config, rules, wecomChannelForm, submitForm } = useWecomChannelFormController()
if (props.data) {
const { name, config } = props.data
wecomChannelForm.value = {
name,
...config,
}
}
// 使用表单hooks
const {
component: WecomForm,
example,
data,
} = useForm({
config,
defaultValue: wecomChannelForm,
rules,
})
// 关联确认按钮
confirm(async (close) => {
try {
const { name, ...other } = data.value
await example.value?.validate()
const res = await submitForm(
{
type: 'workwx',
name: name || '',
config: other,
},
example,
props.data?.id,
)
fetchNotifyChannels()
if (res) close()
} catch (error) {
handleError(error)
}
})
return () => (
<div class="wecom-channel-form">
<WecomForm labelPlacement="top"></WecomForm>
{/* 模板变量说明 */}
<div class="mt-4 p-4 bg-gray-50 rounded-md">
<div class="font-medium text-gray-700 mb-3 text-xl"></div>
<div class="text-gray-600 space-y-3 text-lg">
<div>
<code class="px-2 py-1 bg-gray-200 rounded text-lg font-mono">__subject__</code>
</div>
<div>
<code class="px-2 py-1 bg-gray-200 rounded text-lg font-mono">__body__</code>
</div>
</div>
<div class="mt-4 pt-3 border-t border-gray-200">
<a
href="https://developer.work.weixin.qq.com/document/path/91770"
target="_blank"
class="hover:opacity-80 text-xl"
style="color: #20a50a"
>
📖
</a>
</div>
</div>
</div>
)
},
})

View File

@@ -3,13 +3,21 @@ import { useFormHooks, useLoadingMask } from '@baota/naive-ui/hooks'
import { useError } from '@baota/hooks/error'
import { $t } from '@locales/index'
import { useStore } from '@settings/useStore'
import type { ReportMail, ReportFeishu, ReportWebhook, ReportDingtalk, AddReportParams } from '@/types/setting'
import type {
ReportMail,
ReportFeishu,
ReportWebhook,
ReportDingtalk,
ReportWecom,
AddReportParams,
} from '@/types/setting'
const {
emailChannelForm,
feishuChannelForm,
webhookChannelForm,
dingtalkChannelForm,
wecomChannelForm,
addReportChannel,
updateReportChannel,
} = useStore()
@@ -359,3 +367,100 @@ export const useDingtalkChannelFormController = () => {
submitForm,
}
}
/**
* 企业微信通知渠道表单控制器
* @function useWecomChannelFormController
* @description 提供企业微信通知渠道表单的配置、规则和提交方法
* @returns {object} 返回表单相关配置、规则和方法
*/
export const useWecomChannelFormController = () => {
const { open: openLoad, close: closeLoad } = useLoadingMask({ text: $t('t_0_1746667592819') })
/**
* 表单验证规则
* @type {FormRules}
*/
const rules: FormRules = {
name: {
required: true,
trigger: ['input', 'blur'],
message: $t('t_25_1746773349596'),
},
url: {
required: true,
trigger: ['input', 'blur'],
message: '请输入企业微信webhook地址',
},
}
/**
* 表单配置
* @type {ComputedRef<FormConfig>}
* @description 生成企业微信通知渠道表单的字段配置
*/
const config = computed(() => [
useFormInput($t('t_2_1745289353944'), 'name'),
useFormInput('企业微信WebHook地址', 'url'),
useFormTextarea(
'推送数据格式',
'data',
{
placeholder: `请输入企业微信推送数据格式,支持模板变量 __subject__ 和 __body__
示例格式:
{
"msgtype": "news",
"news": {
"articles": [
{
"title": "__subject__",
"description": "__body__。",
"url": "https://allinssl.com/",
"picurl": "https://allinssl.com/logo.svg"
}
]
}
}`,
rows: 12,
},
{ showRequireMark: false },
),
])
/**
* 提交表单
* @async
* @function submitForm
* @description 验证并提交企业微信通知渠道表单
* @param {any} params - 表单参数
* @param {Ref<FormInst>} formRef - 表单实例引用
* @returns {Promise<boolean>} 提交成功返回true失败返回false
*/
const submitForm = async (
{ config, ...other }: AddReportParams<ReportWecom>,
formRef: Ref<FormInst | null>,
id?: number,
) => {
try {
openLoad()
if (id) {
await updateReportChannel({ id, config: JSON.stringify(config), ...other })
} else {
await addReportChannel({ config: JSON.stringify(config), ...other })
}
return true
} catch (error) {
handleError(error)
return false
} finally {
closeLoad()
}
}
return {
config,
rules,
wecomChannelForm,
submitForm,
}
}

View File

@@ -16,6 +16,7 @@ export default defineComponent({
openAddFeishuChannelModal,
openAddWebhookChannelModal,
openAddDingtalkChannelModal,
openAddWecomChannelModal,
editChannelConfig,
testChannelConfig,
confirmDeleteChannel,
@@ -64,6 +65,12 @@ export default defineComponent({
{$t('t_1_1746676859550')}
</NButton>
)
} else if (type === 'workwx') {
return (
<NButton strong secondary type="primary" onClick={() => openAddWecomChannelModal(getConfiguredCount(type))}>
{$t('t_1_1746676859550')}
</NButton>
)
}
// 其他渠道暂未支持
return (
@@ -100,7 +107,7 @@ export default defineComponent({
color: '#1677ff',
},
{
type: 'wecom',
type: 'workwx',
name: $t('t_7_1746676857191'),
description: $t('t_8_1746676860457'),
color: '#07c160',

View File

@@ -10,6 +10,7 @@ import EmailChannelModel from './components/channel/EmailChannelModel'
import FeishuChannelModel from './components/channel/FeishuChannelModel'
import WebhookChannelModel from './components/channel/WebhookChannelModel'
import DingtalkChannelModel from './components/channel/DingtalkChannelModel'
import WecomChannelModel from './components/channel/WecomChannelModel'
import type { ReportMail, SaveSettingParams, ReportType } from '@/types/setting'
const {
@@ -178,6 +179,25 @@ export const useController = () => {
})
}
/**
* 打开添加企业微信通知渠道弹窗
* @function openAddWecomChannelModal
* @description 打开添加企业微信通知渠道的模态框,并在关闭后刷新通知渠道列表
* @returns {void} 无返回值
*/
const openAddWecomChannelModal = (limit: number = 1) => {
if (limit >= 1) {
message.warning('企业微信通知渠道已达到上限')
return
}
useModal({
title: '添加企业微信通知',
area: 650,
component: WecomChannelModel,
footer: true,
})
}
// 处理启用状态切换
const handleEnableChange = async (item: ReportType<ReportMail>) => {
useDialog({
@@ -263,6 +283,17 @@ export const useController = () => {
footer: true,
onClose: () => fetchNotifyChannels(),
})
} else if (item.type === 'workwx') {
useModal({
title: '编辑企业微信通知',
area: 650,
component: WecomChannelModel,
componentProps: {
data: item,
},
footer: true,
onClose: () => fetchNotifyChannels(),
})
}
}
@@ -274,7 +305,13 @@ export const useController = () => {
* @returns {void} 无返回值
*/
const testChannelConfig = (item: ReportType<any>) => {
if (item.type !== 'mail' && item.type !== 'feishu' && item.type !== 'webhook' && item.type !== 'dingtalk') {
if (
item.type !== 'mail' &&
item.type !== 'feishu' &&
item.type !== 'webhook' &&
item.type !== 'dingtalk' &&
item.type !== 'workwx'
) {
message.warning($t('t_19_1746773352558'))
return
}
@@ -283,6 +320,7 @@ export const useController = () => {
feishu: $t('t_34_1746773350153'),
webhook: $t('t_3_1748591484673'),
dingtalk: $t('t_32_1746773348993'),
workwx: $t('t_33_1746773350932'),
}
const { open, close } = useLoadingMask({ text: $t('t_4_1748591492587', { type: typeMap[item.type] }) })
useDialog({
@@ -337,6 +375,7 @@ export const useController = () => {
openAddFeishuChannelModal,
openAddWebhookChannelModal,
openAddDingtalkChannelModal,
openAddWecomChannelModal,
handleEnableChange,
editChannelConfig,
testChannelConfig,

View File

@@ -22,6 +22,7 @@ import type {
ReportFeishu,
ReportWebhook,
ReportDingtalk,
ReportWecom,
} from '@/types/setting'
const { handleError } = useError()
@@ -63,7 +64,7 @@ export const useSettingsStore = defineStore('settings-store', () => {
const channelTypes = ref<Record<string, string>>({
mail: $t('t_68_1745289354676'),
dingtalk: $t('t_32_1746773348993'),
wecom: $t('t_33_1746773350932'),
workwx: $t('t_33_1746773350932'),
feishu: $t('t_34_1746773350153'),
webhook: 'WebHook',
})
@@ -107,6 +108,26 @@ export const useSettingsStore = defineStore('settings-store', () => {
secret: '', // 钉钉webhook加密密钥可选
})
// 企业微信通知渠道表单
const wecomChannelForm = ref<ReportWecom>({
name: '',
enabled: '1',
url: '', // 企业微信webhook地址
data: `{
"msgtype": "news",
"news": {
"articles": [
{
"title": "__subject__",
"description": "__body__。",
"url": "https://allinssl.com/",
"picurl": "https://allinssl.com/logo.svg"
}
]
}
}`, // 企业微信推送数据格式
})
// 关于页面数据
const aboutInfo = ref({
version: '1.0.0',
@@ -250,6 +271,7 @@ export const useSettingsStore = defineStore('settings-store', () => {
feishuChannelForm,
webhookChannelForm,
dingtalkChannelForm,
wecomChannelForm,
aboutInfo,
// 方法