v0.2.4: fix ad key-words comma split + deleteAdFiles entry for extensions only
This commit is contained in:
@@ -494,87 +494,122 @@
|
||||
<!-- 📬 消息推送 -->
|
||||
<el-card id="section-sys-notify" v-show="!activeSection || activeSection === 'sys-notify'">
|
||||
<template #header>
|
||||
<span>📬 消息推送</span>
|
||||
<div style="display:flex; align-items:center; justify-content:space-between;">
|
||||
<span>📬 消息推送</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="strategy-section">
|
||||
<el-divider content-position="left">推送通道配置</el-divider>
|
||||
|
||||
<!-- 飞书 -->
|
||||
<el-form-item label="飞书 Webhook">
|
||||
<el-input v-model="configs.feishu_webhook_url" placeholder="https://open.feishu.cn/open-apis/bot/v2/hook/xxx" style="max-width: 500px" />
|
||||
<div class="form-tip">飞书机器人 Webhook URL,配置后发送卡片消息到群聊。</div>
|
||||
<div class="form-tip" style="color: var(--el-color-primary); font-size: 12px; margin-top: 2px;">
|
||||
优先从环境变量 FEISHU_WEBHOOK 读取,其次读取此配置
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- Server酱 -->
|
||||
<el-form-item label="Server酱 (微信)">
|
||||
<el-input v-model="configs.serverchan_key" placeholder="SendKey" style="max-width: 300px" />
|
||||
<div class="form-tip">通过 <a href="https://sct.ftqq.com" target="_blank" rel="noopener" style="color: var(--primary-color)">Server酱</a> 推送到微信,只需填写 SendKey</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- Bark -->
|
||||
<el-form-item label="Bark (iOS)">
|
||||
<el-input v-model="configs.bark_key" placeholder="xxxxxxxxxxxxxxxxxxxxxx" style="max-width: 300px" />
|
||||
<div class="form-tip" style="margin-bottom: 4px;">通过 <a href="https://bark.day.app" target="_blank" rel="noopener" style="color: var(--primary-color)">Bark</a> 推送到 iOS 设备,填写 API Key</div>
|
||||
<div class="field-label-row">
|
||||
<span class="field-label" style="font-size:12px; color:#909399;">自定义服务器</span>
|
||||
<el-input v-model="configs.bark_server" placeholder="https://api.day.app" style="max-width: 280px" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- Telegram -->
|
||||
<el-form-item label="Telegram">
|
||||
<div style="display: flex; gap: 8px; align-items: center; width: 100%;">
|
||||
<el-input v-model="configs.telegram_bot_token" placeholder="123456:ABC-DEF" style="max-width: 300px" />
|
||||
<span style="font-size:12px; color:#909399;">Bot Token</span>
|
||||
<el-input v-model="configs.telegram_chat_id" placeholder="@频道或 -100..." style="max-width: 200px" />
|
||||
<span style="font-size:12px; color:#909399;">Chat ID</span>
|
||||
</div>
|
||||
<div class="form-tip">通过 TG Bot 推送消息,需先创建 Bot 并获取 Token</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 自定义 Webhook -->
|
||||
<el-form-item label="自定义 Webhook">
|
||||
<el-input v-model="configs.webhook_url" placeholder="https://example.com/webhook" style="max-width: 500px" />
|
||||
<div class="form-tip">POST JSON 到指定 URL,格式:{title, content, level, source: "CloudSearch"}</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">推送事件开关</el-divider>
|
||||
|
||||
<div class="strategy-grid" style="grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));">
|
||||
<div class="grid-cell">
|
||||
<div class="field-label-row">
|
||||
<span class="field-label">✅ 转存成功</span>
|
||||
<el-switch v-model="configs.notify_on_save_success" active-value="true" inactive-value="false" />
|
||||
<!-- 全局推送兜底(动态渲染全部通道) -->
|
||||
<el-collapse :model-value="['global']">
|
||||
<el-collapse-item title="全局推送(管理员兜底)" name="global">
|
||||
<div class="strategy-section">
|
||||
<el-form label-width="140px" label-position="left">
|
||||
<div style="display:grid; grid-template-columns:repeat(2,1fr); gap:8px;">
|
||||
<div v-for="(np, nkey) in notifyProviders" :key="nkey" style="border:1px solid var(--el-border-color-light); border-radius:6px; padding:8px 12px;">
|
||||
<div style="display:flex; align-items:center; gap:8px; margin-bottom:6px;">
|
||||
<el-switch v-model="globalNotifyForm.channels[nkey]._enabled" size="small" />
|
||||
<strong>{{ np.label }}</strong>
|
||||
<el-button v-if="globalNotifyForm.channels[nkey]._enabled" size="small" text type="primary" @click="testGlobalChannel(nkey)" :loading="globalNotifyForm.channels[nkey]._testing">测试</el-button>
|
||||
</div>
|
||||
<div v-if="globalNotifyForm.channels[nkey]._enabled">
|
||||
<el-form-item v-for="p in np.params" :key="p.key" :label="p.label" style="margin-bottom:6px;">
|
||||
<el-input v-if="p.type==='password'" v-model="globalNotifyForm.channels[nkey][p.key]" type="password" show-password :placeholder="p.placeholder || ''" style="max-width:360px" />
|
||||
<el-switch v-else-if="p.type==='switch'" v-model="globalNotifyForm.channels[nkey][p.key]" />
|
||||
<el-input-number v-else-if="p.type==='number'" v-model="globalNotifyForm.channels[nkey][p.key]" :min="1" :max="10" style="max-width:160px" />
|
||||
<el-input v-else v-model="globalNotifyForm.channels[nkey][p.key]" :placeholder="p.placeholder || ''" style="max-width:360px" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-desc">转存成功时推送通知</div>
|
||||
</div>
|
||||
<div class="grid-cell">
|
||||
<div class="field-label-row">
|
||||
<span class="field-label">❌ 转存连续失败</span>
|
||||
<el-switch v-model="configs.notify_on_save_fail" active-value="true" inactive-value="false" />
|
||||
</div>
|
||||
<div class="field-desc">连续失败 3 次后推送通知</div>
|
||||
</div>
|
||||
<div class="grid-cell">
|
||||
<div class="field-label-row">
|
||||
<span class="field-label">⚠️ Cookie 过期</span>
|
||||
<el-switch v-model="configs.notify_on_cookie_expire" active-value="true" inactive-value="false" />
|
||||
</div>
|
||||
<div class="field-desc">Cookie 过期时推送提醒</div>
|
||||
</div>
|
||||
<div class="grid-cell">
|
||||
<div class="field-label-row">
|
||||
<span class="field-label">🧹 清理完成</span>
|
||||
<el-switch v-model="configs.notify_on_cleanup" active-value="true" inactive-value="false" />
|
||||
</div>
|
||||
<div class="field-desc">每日自动清理完成时推送</div>
|
||||
<el-divider content-position="left">全局事件开关</el-divider>
|
||||
<div style="display:flex; flex-wrap:wrap; gap:16px;">
|
||||
<el-switch v-model="globalNotifyForm.events.on_save_success" active-text="转存成功" />
|
||||
<el-switch v-model="globalNotifyForm.events.on_save_fail" active-text="转存失败" />
|
||||
<el-switch v-model="globalNotifyForm.events.on_cookie_expire" active-text="Cookie过期" />
|
||||
<el-switch v-model="globalNotifyForm.events.on_cleanup" active-text="清理完成" />
|
||||
</div>
|
||||
<div class="form-tip" style="margin-top:8px;">全局推送作为兜底通道。设置了推送用户的网盘配置走用户推送,未设置的走全局推送。</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse> <el-divider content-position="left">添加推送用户</el-divider>
|
||||
|
||||
<!-- Inline push user add/edit form -->
|
||||
<div style="border:1px solid var(--el-border-color-light); border-radius:6px; padding:12px 16px; margin-bottom:16px;">
|
||||
<div style="display:flex; align-items:center; gap:12px; flex-wrap:wrap;">
|
||||
<el-select v-model="pushUserForm.account" filterable allow-create clearable placeholder="选择推广账户" style="width:200px;">
|
||||
<el-option
|
||||
v-for="acc in pushUserAccountOptions"
|
||||
:key="acc"
|
||||
:label="acc"
|
||||
:value="acc"
|
||||
/>
|
||||
</el-select>
|
||||
|
||||
<el-select v-model="pushUserForm.channels" multiple collapse-tags collapse-tags-tooltip placeholder="选择您所需的消息频道" style="width:260px;">
|
||||
<el-option
|
||||
v-for="(np, nkey) in enabledNotifyProviders"
|
||||
:key="nkey"
|
||||
:label="np.label"
|
||||
:value="nkey"
|
||||
/>
|
||||
</el-select>
|
||||
|
||||
<el-switch v-model="pushUserForm.events.on_save_success" active-text="转存成功" />
|
||||
<el-switch v-model="pushUserForm.events.on_save_fail" active-text="转存失败" />
|
||||
<el-switch v-model="pushUserForm.events.on_cookie_expire" active-text="Cookie过期" />
|
||||
<el-switch v-model="pushUserForm.events.on_cleanup" active-text="清理完成" />
|
||||
|
||||
<el-button type="primary" size="small" :loading="pushUserSaving" @click="savePushUser">{{ pushUserForm.id ? '更新' : '确认添加' }}</el-button>
|
||||
<el-button v-if="pushUserForm.id" size="small" @click="cancelEditPushUser">取消编辑</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider content-position="left">推送用户列表</el-divider>
|
||||
|
||||
<el-table :data="pushUsers" stripe style="width:100%" empty-text="暂无推送用户">
|
||||
<el-table-column prop="account" label="推广账号" min-width="140" />
|
||||
<el-table-column label="转存成功" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="getEventEnabled(row, 'on_save_success')" type="success" size="small">✔</el-tag>
|
||||
<span v-else style="color:#ccc;">—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="转存失败" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="getEventEnabled(row, 'on_save_fail')" type="success" size="small">✔</el-tag>
|
||||
<span v-else style="color:#ccc;">—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Cookie过期" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="getEventEnabled(row, 'on_cookie_expire')" type="success" size="small">✔</el-tag>
|
||||
<span v-else style="color:#ccc;">—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="清理完成" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="getEventEnabled(row, 'on_cleanup')" type="success" size="small">✔</el-tag>
|
||||
<span v-else style="color:#ccc;">—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已启用的通道" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-for="(_, key) in getEnabledChannels(row)" :key="key" size="small" style="margin-right:4px;margin-bottom:2px;">{{ getProviderLabel(key) }}</el-tag>
|
||||
<span v-if="!hasEnabledChannels(row)" style="color:#909399;font-size:12px;">走全局推送</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button text type="primary" size="small" @click="editPushUser(row)">编辑</el-button>
|
||||
<el-popconfirm title="确定删除该推送用户?" @confirm="deletePushUser(row)">
|
||||
<template #reference>
|
||||
<el-button text type="danger" size="small">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 🔄 系统维护 --> <el-card id="section-sys-maintenance" v-show="!activeSection || activeSection === 'sys-maintenance'"> <template #header> <span>🔄 系统维护</span> </template> <el-form label-width="180px" label-position="left"> <el-form-item label="自动更新镜像"> <el-switch v-model="autoUpdateEnabled" active-text="启用" inactive-text="禁用" /> <div class="form-tip">启用后 CloudSearch 将自动检测并更新到最新镜像版本</div> <div class="form-tip" style="color: var(--(--el-color-warning,#e6a23c));"> 当前需手动在服务器执行:docker-compose -f /opt/CloudSearch/docker-compose.yml pull && docker-compose -f /opt/CloudSearch/docker-compose.yml up -d </div> </el-form-item> </el-form> </el-card>
|
||||
@@ -589,11 +624,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from "vue"
|
||||
import { ref, reactive, onMounted, computed, watch } from "vue"
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { ElForm } from 'element-plus'
|
||||
import { getSystemConfigs, updateSystemConfigs, changePassword as changePasswordApi, uploadFallbackImage, uploadLogo, updateSetting, getDbStatus, testRedisConnection, testExternalService } from "../../api"
|
||||
import { getSystemConfigs, updateSystemConfigs, changePassword as changePasswordApi, uploadFallbackImage, uploadLogo, updateSetting, getDbStatus, testRedisConnection, testExternalService, testNotifyChannel, getAllNotifierProviders, getCloudConfigs } from "../../api"
|
||||
import { Upload, Loading } from "@element-plus/icons-vue"
|
||||
|
||||
|
||||
@@ -658,6 +693,253 @@ const autoUpdateEnabled = computed({
|
||||
set: (val: boolean) => { configs.auto_update_enabled = val ? 'true' : 'false' },
|
||||
})
|
||||
|
||||
// ======================== Push User Notifications ========================
|
||||
const pushUsers = ref<any[]>([])
|
||||
const notifyProviders = ref<Record<string, any>>({})
|
||||
// const pushUserDialogVisible = ref(false) // removed - using inline form
|
||||
const pushUserSaving = ref(false)
|
||||
const pushUserAccountOptions = ref<string[]>([])
|
||||
|
||||
async function loadPushUserAccountOptions() {
|
||||
try {
|
||||
const resp = await fetch('/api/admin/cloud-configs', {
|
||||
headers: { 'Authorization': 'Bearer ' + (localStorage.getItem('admin_token') || '') }
|
||||
})
|
||||
if (!resp.ok) return
|
||||
const configs = await resp.json()
|
||||
const options = Array.isArray(configs)
|
||||
? [...new Set(configs.map((c: any) => c.promotion_account || '').filter(Boolean))]
|
||||
: []
|
||||
pushUserAccountOptions.value = options
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const pushUserForm = reactive<any>({
|
||||
id: null,
|
||||
account: '',
|
||||
channels: [],
|
||||
events: {
|
||||
on_save_success: true,
|
||||
on_save_fail: true,
|
||||
on_cookie_expire: true,
|
||||
on_cleanup: false,
|
||||
},
|
||||
})
|
||||
|
||||
// Only show channels that are enabled in global notification settings
|
||||
const enabledNotifyProviders = computed(() => {
|
||||
const result: Record<string, any> = {}
|
||||
for (const [k, np] of Object.entries(notifyProviders.value)) {
|
||||
if (globalNotifyForm.channels[k]?._enabled) {
|
||||
result[k] = np
|
||||
}
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
const globalNotifyForm = reactive<{ channels: Record<string, any>; events: Record<string, boolean> }>({
|
||||
channels: {},
|
||||
events: { on_save_success: true, on_save_fail: true, on_cookie_expire: true, on_cleanup: false },
|
||||
})
|
||||
|
||||
function initPushUserChannelForm() {
|
||||
const channels: Record<string, any> = {}
|
||||
for (const [k, np] of Object.entries(notifyProviders.value)) {
|
||||
channels[k] = { _enabled: false, _testing: false }
|
||||
for (const p of np.params || []) {
|
||||
channels[k][p.key] = p.default || ''
|
||||
}
|
||||
}
|
||||
return channels
|
||||
}
|
||||
|
||||
function editPushUser(row?: any) {
|
||||
if (row) {
|
||||
pushUserForm.id = row.id
|
||||
pushUserForm.account = row.account
|
||||
const nc = row.notify_config || {}
|
||||
pushUserForm.channels = Object.keys(nc.channels || {})
|
||||
pushUserForm.events = {
|
||||
on_save_success: nc.events?.on_save_success !== false,
|
||||
on_save_fail: nc.events?.on_save_fail !== false,
|
||||
on_cookie_expire: nc.events?.on_cookie_expire !== false,
|
||||
on_cleanup: nc.events?.on_cleanup === true,
|
||||
}
|
||||
} else {
|
||||
pushUserForm.id = null
|
||||
pushUserForm.account = ''
|
||||
pushUserForm.channels = []
|
||||
pushUserForm.events = { on_save_success: true, on_save_fail: true, on_cookie_expire: true, on_cleanup: false }
|
||||
}
|
||||
}
|
||||
|
||||
function cancelEditPushUser() {
|
||||
pushUserForm.id = null
|
||||
pushUserForm.account = ''
|
||||
pushUserForm.channels = []
|
||||
pushUserForm.events = { on_save_success: true, on_save_fail: true, on_cookie_expire: true, on_cleanup: false }
|
||||
}
|
||||
|
||||
function getEventEnabled(row: any, eventKey: string): boolean {
|
||||
const nc = row.notify_config || {}
|
||||
const events = nc.events || {}
|
||||
return events[eventKey] === true
|
||||
}
|
||||
|
||||
async function savePushUser() {
|
||||
if (!pushUserForm.account) {
|
||||
ElMessage.warning('请填写推广账号')
|
||||
return
|
||||
}
|
||||
pushUserSaving.value = true
|
||||
try {
|
||||
const payload: any = {
|
||||
account: pushUserForm.account,
|
||||
notify_config: { channels: {}, events: pushUserForm.events },
|
||||
}
|
||||
// Build channels from selected keys (no params — use global config at push time)
|
||||
const ch: Record<string, any> = {}
|
||||
for (const key of pushUserForm.channels) {
|
||||
ch[key] = {}
|
||||
}
|
||||
payload.notify_config.channels = ch
|
||||
if (pushUserForm.id) {
|
||||
await fetch('/api/admin/push-users/' + pushUserForm.id, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + (localStorage.getItem('admin_token') || '') },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
} else {
|
||||
await fetch('/api/admin/push-users', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + (localStorage.getItem('admin_token') || '') },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
}
|
||||
const isUpdate = !!pushUserForm.id
|
||||
pushUserForm.id = null
|
||||
pushUserForm.account = ''
|
||||
pushUserForm.channels = []
|
||||
pushUserForm.events = { on_save_success: true, on_save_fail: true, on_cookie_expire: true, on_cleanup: false }
|
||||
ElMessage.success(isUpdate ? '推送用户已更新' : '推送用户已添加')
|
||||
await loadPushUsers()
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e.message || '保存失败')
|
||||
} finally {
|
||||
pushUserSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPushUsers() {
|
||||
try {
|
||||
const resp = await fetch('/api/admin/push-users', {
|
||||
headers: { 'Authorization': 'Bearer ' + (localStorage.getItem('admin_token') || '') }
|
||||
})
|
||||
if (resp.ok) {
|
||||
pushUsers.value = await resp.json()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load push users', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadNotifyProviders() {
|
||||
try {
|
||||
notifyProviders.value = await getAllNotifierProviders()
|
||||
} catch (e) {
|
||||
console.error('Failed to load providers', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function deletePushUser(row: any) {
|
||||
try {
|
||||
await fetch('/api/admin/push-users/' + row.id, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + (localStorage.getItem('admin_token') || '') } })
|
||||
ElMessage.success('已删除')
|
||||
await loadPushUsers()
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e.message || '删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function getEnabledChannels(row: any): Record<string, any> {
|
||||
const ch = row.notify_config?.channels || {}
|
||||
// Support both old format ({key: {...params}}) and new format ({key: {}})
|
||||
return ch
|
||||
}
|
||||
|
||||
function getProviderLabel(key: string): string {
|
||||
return notifyProviders.value[key]?.label || key
|
||||
}
|
||||
|
||||
function hasEnabledChannels(row: any): boolean {
|
||||
return Object.keys(getEnabledChannels(row)).length > 0
|
||||
}
|
||||
// ==================== End Push User Notifications ====================
|
||||
|
||||
|
||||
// ==================== Global Notify Functions ====================
|
||||
|
||||
function initGlobalNotifyForm() {
|
||||
const channels: Record<string, any> = {}
|
||||
for (const [k, np] of Object.entries(notifyProviders.value)) {
|
||||
channels[k] = { _enabled: false, _testing: false }
|
||||
for (const p of np.params || []) {
|
||||
channels[k][p.key] = p.default || ''
|
||||
}
|
||||
}
|
||||
globalNotifyForm.channels = channels
|
||||
globalNotifyForm.events = { on_save_success: true, on_save_fail: true, on_cookie_expire: true, on_cleanup: false }
|
||||
}
|
||||
|
||||
async function loadGlobalNotifyConfig() {
|
||||
try {
|
||||
const resp = await fetch('/api/admin/system-configs', {
|
||||
headers: { 'Authorization': 'Bearer ' + (localStorage.getItem('admin_token') || '') }
|
||||
})
|
||||
const configs = await resp.json() as any[]
|
||||
const gcfg = configs.find((c: any) => c.key === 'global_notify_config')
|
||||
if (gcfg && gcfg.value) {
|
||||
try {
|
||||
const parsed = JSON.parse(gcfg.value)
|
||||
const nc = parsed.channels || {}
|
||||
for (const [k, v] of Object.entries(nc)) {
|
||||
if (globalNotifyForm.channels[k]) {
|
||||
globalNotifyForm.channels[k]._enabled = true
|
||||
for (const [pk, pv] of Object.entries(v as Record<string, any>)) {
|
||||
globalNotifyForm.channels[k][pk] = pv
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parsed.events) {
|
||||
globalNotifyForm.events.on_save_success = parsed.events.on_save_success !== false
|
||||
globalNotifyForm.events.on_save_fail = parsed.events.on_save_fail !== false
|
||||
globalNotifyForm.events.on_cookie_expire = parsed.events.on_cookie_expire !== false
|
||||
globalNotifyForm.events.on_cleanup = parsed.events.on_cleanup === true
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function testGlobalChannel(channelName: string) {
|
||||
const ch = globalNotifyForm.channels[channelName]
|
||||
if (!ch || !ch._enabled) return
|
||||
ch._testing = true
|
||||
try {
|
||||
const result = await testNotifyChannel(channelName)
|
||||
if (result.success) {
|
||||
ElMessage.success(result.message)
|
||||
} else {
|
||||
ElMessage.error(result.message)
|
||||
}
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e.message || '测试失败')
|
||||
} finally {
|
||||
ch._testing = false
|
||||
}
|
||||
}
|
||||
const passwordForm = reactive({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
@@ -719,6 +1001,23 @@ onMounted(async () => {
|
||||
}
|
||||
// Auto-load PanSou info on page load
|
||||
fetchPansouInfo()
|
||||
await loadNotifyProviders()
|
||||
initGlobalNotifyForm()
|
||||
await loadGlobalNotifyConfig()
|
||||
loadPushUsers()
|
||||
loadPushUserAccountOptions()
|
||||
})
|
||||
|
||||
// Watch for notifyProviders loaded asynchronously — sync global form channels
|
||||
watch(notifyProviders, () => {
|
||||
for (const [k, np] of Object.entries(notifyProviders.value)) {
|
||||
if (!globalNotifyForm.channels[k]) {
|
||||
globalNotifyForm.channels[k] = { _enabled: false, _testing: false }
|
||||
for (const p of np.params || []) {
|
||||
globalNotifyForm.channels[k][p.key] = p.default || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
async function handleTestRedis() {
|
||||
@@ -1006,10 +1305,26 @@ function handleLogout() {
|
||||
async function handleSave() {
|
||||
saving.value = true
|
||||
try {
|
||||
// Build global_notify_config from form
|
||||
const channels: Record<string, any> = {}
|
||||
for (const [k, v] of Object.entries(globalNotifyForm.channels)) {
|
||||
if ((v as any)._enabled) {
|
||||
const params: Record<string, string> = {}
|
||||
for (const [pk, pv] of Object.entries(v as Record<string, any>)) {
|
||||
if (!pk.startsWith('_') && pv !== '') params[pk] = String(pv)
|
||||
}
|
||||
if (Object.keys(params).length > 0) channels[k] = params
|
||||
}
|
||||
}
|
||||
const entries = rawConfigs.value.map(cfg => ({
|
||||
key: cfg.key,
|
||||
value: String(configs[cfg.key] ?? cfg.value),
|
||||
}))
|
||||
// Add global_notify_config as JSON entry
|
||||
entries.push({
|
||||
key: 'global_notify_config',
|
||||
value: JSON.stringify({ channels, events: globalNotifyForm.events }),
|
||||
})
|
||||
await updateSystemConfigs(entries)
|
||||
ElMessage.success('配置已保存')
|
||||
} catch (e: any) {
|
||||
|
||||
Reference in New Issue
Block a user