mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-28 19:47:49 +08:00
feat(notification): configure DingTalk robot channel
This commit is contained in:
@@ -5,7 +5,9 @@ import { useI18n } from 'vue-i18n'
|
||||
import { openSharedDialog } from '@/composables/useSharedDialog'
|
||||
import { useCardAccentColor } from '@/composables/useCardAccentColor'
|
||||
|
||||
const NotificationChannelInfoDialog = defineAsyncComponent(() => import('@/components/dialog/NotificationChannelInfoDialog.vue'))
|
||||
const NotificationChannelInfoDialog = defineAsyncComponent(
|
||||
() => import('@/components/dialog/NotificationChannelInfoDialog.vue'),
|
||||
)
|
||||
|
||||
const { t } = useI18n()
|
||||
const { accentRgb, imageRef, updateAccentColor } = useCardAccentColor()
|
||||
@@ -38,6 +40,7 @@ const notificationTypeNames: { [key: string]: string } = {
|
||||
synologychat: t('notification.synologychat.name'),
|
||||
slack: t('notification.slack.name'),
|
||||
discord: t('notification.discord.name'),
|
||||
dingtalk: t('notification.dingtalk.name'),
|
||||
webpush: t('notification.webpush.name'),
|
||||
custom: t('setting.notification.custom'),
|
||||
}
|
||||
@@ -79,6 +82,8 @@ const getIcon = computed(() => {
|
||||
return getLogoUrl('slack')
|
||||
case 'discord':
|
||||
return getLogoUrl('discord')
|
||||
case 'dingtalk':
|
||||
return getLogoUrl('notification')
|
||||
case 'webpush':
|
||||
return getLogoUrl('chrome')
|
||||
default:
|
||||
|
||||
@@ -363,12 +363,7 @@ async function logoutWechatClawBot() {
|
||||
}
|
||||
wechatClawBotActionLoading.value = true
|
||||
try {
|
||||
await manageNotificationChannel(
|
||||
'WechatClawBot',
|
||||
'logout',
|
||||
getWechatClawBotRequestParams(),
|
||||
{ feedback: 'silent' },
|
||||
)
|
||||
await manageNotificationChannel('WechatClawBot', 'logout', getWechatClawBotRequestParams(), { feedback: 'silent' })
|
||||
$toast.success(t('notification.wechatclawbot.logoutSuccess'))
|
||||
await fetchWechatClawBotStatus({
|
||||
autoGenerateQrcode: true,
|
||||
@@ -1008,6 +1003,37 @@ onMounted(() => {
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow v-else-if="notificationInfo.type == 'dingtalk'">
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="notificationInfo.name"
|
||||
:label="t('notification.name')"
|
||||
:placeholder="t('notification.name')"
|
||||
:hint="t('notification.nameHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-label"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
v-model="notificationInfo.config.DINGTALK_WEBHOOK"
|
||||
:label="t('notification.dingtalk.webhook')"
|
||||
:hint="t('notification.dingtalk.webhookHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-webhook"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
v-model="notificationInfo.config.DINGTALK_SECRET"
|
||||
:label="t('notification.dingtalk.secret')"
|
||||
:hint="t('notification.dingtalk.secretHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-shield-key"
|
||||
type="password"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow v-else-if="notificationInfo.type == 'vocechat'">
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
|
||||
@@ -380,6 +380,7 @@ describe('useSetupWizard defaults, selection and validation', () => {
|
||||
['telegram', {}, 'notification.telegram.tokenRequired'],
|
||||
['slack', {}, 'notification.slack.oauthTokenRequired'],
|
||||
['synologychat', {}, 'notification.synologychat.webhookRequired'],
|
||||
['dingtalk', {}, 'notification.dingtalk.webhookRequired'],
|
||||
['vocechat', {}, 'notification.vocechat.hostRequired'],
|
||||
['webpush', {}, 'notification.webpush.usernameRequired'],
|
||||
['qqbot', {}, 'notification.qqbot.appIdRequired'],
|
||||
@@ -399,6 +400,7 @@ describe('useSetupWizard defaults, selection and validation', () => {
|
||||
['telegram', { TELEGRAM_CHAT_ID: 'chat', TELEGRAM_TOKEN: 'token' }],
|
||||
['slack', { SLACK_CHANNEL: 'channel', SLACK_OAUTH_TOKEN: 'token' }],
|
||||
['synologychat', { SYNOLOGYCHAT_WEBHOOK: 'https://chat.example.com' }],
|
||||
['dingtalk', { DINGTALK_WEBHOOK: 'https://oapi.dingtalk.com/robot/send?access_token=test' }],
|
||||
['vocechat', { VOCECHAT_API_KEY: 'key', VOCECHAT_HOST: 'https://voce.example.com' }],
|
||||
['webpush', { WEBPUSH_USERNAME: 'admin' }],
|
||||
['qqbot', { QQ_APP_ID: 'app', QQ_APP_SECRET: 'secret' }],
|
||||
|
||||
@@ -358,6 +358,7 @@ export function useSetupWizard() {
|
||||
'wechat': 'WechatModule',
|
||||
'wechatclawbot': 'WechatClawBotModule',
|
||||
'slack': 'SlackModule',
|
||||
'dingtalk': 'DingTalkModule',
|
||||
'synologychat': 'SynologyChatModule',
|
||||
'qqbot': 'QQBotModule',
|
||||
'vocechat': 'VoceChatModule',
|
||||
@@ -457,6 +458,7 @@ export function useSetupWizard() {
|
||||
wechatclawbot: '微信 ClawBot',
|
||||
telegram: 'Telegram',
|
||||
slack: 'Slack',
|
||||
dingtalk: '钉钉',
|
||||
synologychat: 'SynologyChat',
|
||||
qqbot: 'QQ',
|
||||
vocechat: 'VoceChat',
|
||||
@@ -730,6 +732,12 @@ export function useSetupWizard() {
|
||||
validationErrors.value.notification.SYNOLOGYCHAT_WEBHOOK = true
|
||||
}
|
||||
break
|
||||
case 'dingtalk':
|
||||
if (!config.DINGTALK_WEBHOOK?.trim()) {
|
||||
errors.push(t('notification.dingtalk.webhookRequired'))
|
||||
validationErrors.value.notification.DINGTALK_WEBHOOK = true
|
||||
}
|
||||
break
|
||||
case 'vocechat':
|
||||
if (!config.VOCECHAT_HOST?.trim()) {
|
||||
errors.push(t('notification.vocechat.hostRequired'))
|
||||
|
||||
+11
-1
@@ -545,7 +545,7 @@ export default {
|
||||
notification: {
|
||||
title: 'Notifications',
|
||||
description:
|
||||
'Notification channels (WeChat Work, WeChat ClawBot, Telegram, Slack, SynologyChat, VoceChat, WebPush), message scope',
|
||||
'Notification channels (WeChat Work, WeChat ClawBot, Telegram, Slack, DingTalk, SynologyChat, VoceChat, WebPush), message scope',
|
||||
},
|
||||
about: {
|
||||
title: 'About',
|
||||
@@ -785,6 +785,15 @@ export default {
|
||||
adminsHint: 'User IDs that can use admin menu and commands, separated by commas',
|
||||
adminsPlaceholder: 'User IDs list, separated by commas',
|
||||
},
|
||||
dingtalk: {
|
||||
name: 'DingTalk',
|
||||
webhook: 'Custom Robot Webhook',
|
||||
webhookHint: 'Full webhook URL of the DingTalk group custom robot',
|
||||
webhookRequired: 'Webhook URL cannot be empty',
|
||||
secret: 'Signing Secret',
|
||||
secretHint:
|
||||
'Enter the SEC-prefixed secret when signature security is enabled; leave empty for keyword or IP allowlist security',
|
||||
},
|
||||
synologychat: {
|
||||
name: 'Synology Chat',
|
||||
webhook: 'Webhook URL',
|
||||
@@ -2482,6 +2491,7 @@ export default {
|
||||
other: 'Other',
|
||||
telegram: 'Telegram',
|
||||
slack: 'Slack',
|
||||
dingTalk: 'DingTalk',
|
||||
synologyChat: 'SynologyChat',
|
||||
voceChat: 'VoceChat',
|
||||
webPush: 'WebPush',
|
||||
|
||||
+11
-1
@@ -536,7 +536,8 @@ export default {
|
||||
},
|
||||
notification: {
|
||||
title: '通知',
|
||||
description: '通知渠道(企业微信、微信 ClawBot、Telegram、Slack、SynologyChat、VoceChat、WebPush)、消息发送范围',
|
||||
description:
|
||||
'通知渠道(企业微信、微信 ClawBot、Telegram、Slack、钉钉、SynologyChat、VoceChat、WebPush)、消息发送范围',
|
||||
},
|
||||
about: {
|
||||
title: '关于',
|
||||
@@ -774,6 +775,14 @@ export default {
|
||||
adminsHint: '可使用管理菜单及命令的用户ID列表,多个ID使用,分隔',
|
||||
adminsPlaceholder: '用户ID列表,多个ID使用,分隔',
|
||||
},
|
||||
dingtalk: {
|
||||
name: '钉钉',
|
||||
webhook: '自定义机器人 Webhook',
|
||||
webhookHint: '钉钉群自定义机器人的完整 Webhook 地址',
|
||||
webhookRequired: 'Webhook 地址不能为空',
|
||||
secret: '加签密钥',
|
||||
secretHint: '机器人安全设置启用“加签”时填写,以 SEC 开头;使用关键词或 IP 白名单时可留空',
|
||||
},
|
||||
synologychat: {
|
||||
name: 'Synology Chat',
|
||||
webhook: '机器人传入URL',
|
||||
@@ -2437,6 +2446,7 @@ export default {
|
||||
other: '其它',
|
||||
telegram: 'Telegram',
|
||||
slack: 'Slack',
|
||||
dingTalk: '钉钉',
|
||||
synologyChat: 'SynologyChat',
|
||||
voceChat: 'VoceChat',
|
||||
webPush: 'WebPush',
|
||||
|
||||
+11
-1
@@ -536,7 +536,8 @@ export default {
|
||||
},
|
||||
notification: {
|
||||
title: '通知',
|
||||
description: '通知渠道(企業微信、微信 ClawBot、Telegram、Slack、SynologyChat、VoceChat、WebPush)、消息發送範圍',
|
||||
description:
|
||||
'通知渠道(企業微信、微信 ClawBot、Telegram、Slack、釘釘、SynologyChat、VoceChat、WebPush)、消息發送範圍',
|
||||
},
|
||||
about: {
|
||||
title: '關於',
|
||||
@@ -774,6 +775,14 @@ export default {
|
||||
adminsHint: '可使用管理菜單及命令的用戶ID列表,多個ID使用,分隔',
|
||||
adminsPlaceholder: '用戶ID列表,多個ID使用,分隔',
|
||||
},
|
||||
dingtalk: {
|
||||
name: '釘釘',
|
||||
webhook: '自定義機器人 Webhook',
|
||||
webhookHint: '釘釘群自定義機器人的完整 Webhook 地址',
|
||||
webhookRequired: 'Webhook 地址不能為空',
|
||||
secret: '加簽密鑰',
|
||||
secretHint: '機器人安全設置啟用“加簽”時填寫,以 SEC 開頭;使用關鍵詞或 IP 白名單時可留空',
|
||||
},
|
||||
synologychat: {
|
||||
name: 'Synology Chat',
|
||||
webhook: '機器人傳入URL',
|
||||
@@ -2436,6 +2445,7 @@ export default {
|
||||
other: '其它',
|
||||
telegram: 'Telegram',
|
||||
slack: 'Slack',
|
||||
dingTalk: '釘釘',
|
||||
synologyChat: 'SynologyChat',
|
||||
voceChat: 'VoceChat',
|
||||
webPush: 'WebPush',
|
||||
|
||||
@@ -455,6 +455,9 @@ useSilentSettingRefresh(loadPageData, {
|
||||
<VListItem @click="addNotification('discord')">
|
||||
<VListItemTitle>Discord</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem @click="addNotification('dingtalk')">
|
||||
<VListItemTitle>{{ t('setting.notification.dingTalk') }}</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem @click="addNotification('synologychat')">
|
||||
<VListItemTitle>{{ t('setting.notification.synologyChat') }}</VListItemTitle>
|
||||
</VListItem>
|
||||
|
||||
@@ -180,6 +180,18 @@ describe('AccountSettingNotification', () => {
|
||||
expect(mocks.toastSuccess).toHaveBeenCalledWith('通知设置保存成功')
|
||||
})
|
||||
|
||||
it('offers DingTalk as a native notification channel', async () => {
|
||||
const user = userEvent.setup()
|
||||
await renderNotificationSettings()
|
||||
await screen.findByText('Alpha / wechatclawbot')
|
||||
|
||||
const channelCard = getCard('通知渠道')
|
||||
await user.click(channelCard.getAllByRole('button').at(-1)!)
|
||||
await user.click(await screen.findByText('钉钉', { selector: '.v-list-item-title' }))
|
||||
|
||||
expect(screen.getByText('通知4 / dingtalk')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('compresses chained ClawBot renames and migrates the original source before saving channels', async () => {
|
||||
const user = userEvent.setup()
|
||||
await renderNotificationSettings()
|
||||
|
||||
@@ -118,6 +118,19 @@ const notificationTypes = [
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
<VCol cols="12" md="3">
|
||||
<VCard
|
||||
:color="wizardData.notification.type === 'dingtalk' ? 'primary' : 'default'"
|
||||
:variant="wizardData.notification.type === 'dingtalk' ? 'tonal' : 'outlined'"
|
||||
class="cursor-pointer"
|
||||
@click="selectNotification('dingtalk')"
|
||||
>
|
||||
<VCardText class="text-center">
|
||||
<VIcon icon="mdi-message-processing" size="48" class="mb-2" />
|
||||
<div class="text-h6">钉钉</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
<VCol cols="12" md="3">
|
||||
<VCard
|
||||
:color="wizardData.notification.type === 'qqbot' ? 'primary' : 'default'"
|
||||
@@ -341,7 +354,9 @@ const notificationTypes = [
|
||||
:label="t('notification.feishu.appId')"
|
||||
:hint="t('notification.feishu.appIdHint')"
|
||||
:error="validationErrors.notification.FEISHU_APP_ID"
|
||||
:error-messages="validationErrors.notification.FEISHU_APP_ID ? [t('notification.feishu.appIdRequired')] : []"
|
||||
:error-messages="
|
||||
validationErrors.notification.FEISHU_APP_ID ? [t('notification.feishu.appIdRequired')] : []
|
||||
"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-application"
|
||||
required
|
||||
@@ -353,7 +368,11 @@ const notificationTypes = [
|
||||
:label="t('notification.feishu.appSecret')"
|
||||
:hint="t('notification.feishu.appSecretHint')"
|
||||
:error="validationErrors.notification.FEISHU_APP_SECRET"
|
||||
:error-messages="validationErrors.notification.FEISHU_APP_SECRET ? [t('notification.feishu.appSecretRequired')] : []"
|
||||
:error-messages="
|
||||
validationErrors.notification.FEISHU_APP_SECRET
|
||||
? [t('notification.feishu.appSecretRequired')]
|
||||
: []
|
||||
"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-key"
|
||||
required
|
||||
@@ -517,9 +536,7 @@ const notificationTypes = [
|
||||
:hint="t('notification.qqbot.appSecretHint')"
|
||||
:error="validationErrors.notification.QQ_APP_SECRET"
|
||||
:error-messages="
|
||||
validationErrors.notification.QQ_APP_SECRET
|
||||
? [t('notification.qqbot.appSecretRequired')]
|
||||
: []
|
||||
validationErrors.notification.QQ_APP_SECRET ? [t('notification.qqbot.appSecretRequired')] : []
|
||||
"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-key"
|
||||
@@ -643,6 +660,47 @@ const notificationTypes = [
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow v-else-if="wizardData.notification.type === 'dingtalk'">
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="wizardData.notification.name"
|
||||
:label="t('notification.name')"
|
||||
:placeholder="t('notification.name')"
|
||||
:hint="t('notification.nameHint')"
|
||||
:error="validationErrors.notification.name"
|
||||
:error-messages="validationErrors.notification.name ? [t('notification.nameRequired')] : []"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-label"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="wizardData.notification.config.DINGTALK_WEBHOOK"
|
||||
:label="t('notification.dingtalk.webhook')"
|
||||
:hint="t('notification.dingtalk.webhookHint')"
|
||||
:error="validationErrors.notification.DINGTALK_WEBHOOK"
|
||||
:error-messages="
|
||||
validationErrors.notification.DINGTALK_WEBHOOK
|
||||
? [t('notification.dingtalk.webhookRequired')]
|
||||
: []
|
||||
"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-webhook"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="wizardData.notification.config.DINGTALK_SECRET"
|
||||
:label="t('notification.dingtalk.secret')"
|
||||
:hint="t('notification.dingtalk.secretHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-shield-key"
|
||||
type="password"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow v-else-if="wizardData.notification.type === 'vocechat'">
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
|
||||
Reference in New Issue
Block a user