From 36ab8ec93f5198058d1a15710d5de4f8f838a4af Mon Sep 17 00:00:00 2001 From: engigu Date: Wed, 4 Mar 2026 23:00:45 +0800 Subject: [PATCH] feat: fix message page style --- README.md | 25 ++- internal/middleware/notify_token.go | 6 +- web/src/views/notify/Notify.vue | 49 ++--- web/src/views/notify/components/ApiUsage.vue | 174 ++++++++++++------ .../views/notify/components/ChannelList.vue | 22 ++- 5 files changed, 175 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index 18e5227..459d1fc 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ ### 最近更新 +**2026.03.04** - 新增内置消息推送系统:全新原生支持企业微信、钉钉、飞书、Telegram、Bark、邮件等十余种主流渠道的推送,接入系统级事件通知自动捕获,告别原有必配外部推送服务的繁琐历史 **2026.02.13** - 重构任务执行引擎:深度集成 Mise 运行时管理,支持 Python, Node.js, Go, Rust, PHP 等几乎所有主流语言的动态安装与多版本切换,同步上线跨语言统一依赖管理系统 **2026.02.11** - 增强安全性:首次启动使用随机密码并打印在日志中,登录接口增加防暴力破解,文件系统操作增加路径穿越锁定 **2026.02.10** - 重构任务调度系统,完善并发控制,优化文件树交互体验,支持任务执行实时日志流 @@ -42,6 +43,7 @@ - **任务调度:** 支持标准 Cron 表达式,常用时间规则快捷选择。日志不落文件,没有磁盘频繁io的问题 - **脚本管理:** 在线代码编辑器,支持文件上传、压缩包解压 - **在线终端:** WebSocket 实时终端,命令执行结果实时输出 +- **消息推送:** 内置强大消息推送与通知引擎,无缝兼容主流渠道,支持系统级事件告警 - **环境变量:** 安全存储敏感配置,任务执行时自动注入 - **现代UI:** 响应式设计,深色/浅色主题切换 - **移动端:** 适配移动小屏样式 @@ -79,6 +81,11 @@ - 日志内容压缩存储 - 日志自动清理 +### 消息推送与系统通知 +- 原生内置各大主流平台渠道(钉钉、企业微信、Telegram、Server酱等) +- 支持系统级事件条件触发通知(例如任务失败报警、服务下线提醒) +- 自动生成跨语言调用 API 示例代码供脚本集成 + ### 环境变量 - 安全存储敏感配置 - 变量值脱敏显示 @@ -334,9 +341,13 @@ table_prefix = baihu_
-方式三:一键部署(白虎 + 消息推送)(推荐) +方式三:配合独立中心化消息服务部署(非必需/仅供参考) -白虎面板本身不内置推送功能,但可以与消息聚合服务配合使用。推荐使用 [Message-Push-Nest](https://github.com/engigu/Message-Push-Nest) 作为统一的消息推送中心。 +> 🎉 **好消息**:自白虎面板最新版本起,系统已**原生内置**了完整强大的消息推送功能!您可直接在面板「消息推送」菜单内绑定十余种主流渠道和系统通知事件,原配合外置的 `Message-Push-Nest` 部署方式已不再是使用面板的基础要求。您可随时直接使用上方的第一种简单命令开箱即用体验。 +> +> 以下「白虎 + 消息聚合服务」的联合部署内容被予以保留,专为仍然需要「中心化通知网关」的重度企业解耦用户作为参考: + +白虎面板通过系统集成也可轻松连接独立的消息聚合服务。这里推荐使用 [Message-Push-Nest](https://github.com/engigu/Message-Push-Nest) 作为分布式的统一消息推送中心。 **使用 SQLite** @@ -464,12 +475,12 @@ docker-compose up -d **在任务中使用推送** -Message-Push-Nest 提供了便捷的推送代码生成功能: +如今你无需依赖任何外部服务,可以通过面板自身完成: +1. 进入白虎面板「消息推送」模块,新建你需要通知的渠道。 +2. 可以在「事件绑定」中直接设定**自动化系统通知**(如监控任务失败或脚本异常中止时进行自动提醒)。 +3. 如需在自己的脚本逻辑内动态推送,可以点击「脚本调用」页面,立刻获取一键调用的 Shell / API 代码,内嵌进你的业务逻辑中即可完成推信! -1. 在 Message-Push-Nest 管理界面中配置推送渠道(如企业微信、钉钉、邮件等) -2. 在「消息模板」或「任务」页面,点击「复制推送代码」按钮 -3. 选择对应的编程语言(Python、Bash、Node.js 等) -4. 将生成的代码粘贴到白虎面板的任务脚本中需要推送的位置 +*(如继续使用 `Message-Push-Nest`,你可以通过其管理界面中「消息模板」的「复制推送代码」提取旧有版集成样例。)* ![1768143124572.png](https://f.pz.al/pzal/2026/01/11/1360cd334ff20.png) diff --git a/internal/middleware/notify_token.go b/internal/middleware/notify_token.go index daf06b3..067e6a2 100644 --- a/internal/middleware/notify_token.go +++ b/internal/middleware/notify_token.go @@ -1,6 +1,8 @@ package middleware import ( + "strings" + "github.com/engigu/baihu-panel/internal/constant" "github.com/engigu/baihu-panel/internal/services" "github.com/engigu/baihu-panel/internal/utils" @@ -20,14 +22,14 @@ func NotifyTokenAuth() gin.HandlerFunc { // 从 settings 表读取配置的通知 Token settingsService := services.NewSettingsService() savedToken := settingsService.Get(constant.SectionNotify, constant.KeyNotifyToken) - + if savedToken == "" { utils.Unauthorized(c, "通知 Token 未配置") c.Abort() return } - if token != savedToken { + if strings.ToLower(token) != strings.ToLower(savedToken) { utils.Unauthorized(c, "通知 Token 无效") c.Abort() return diff --git a/web/src/views/notify/Notify.vue b/web/src/views/notify/Notify.vue index 40a31d1..a2351e4 100644 --- a/web/src/views/notify/Notify.vue +++ b/web/src/views/notify/Notify.vue @@ -11,7 +11,7 @@ import { AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog' -import { api, type NotifyChannel, type ChannelType, type EventType, type NotifyBinding } from '@/api' +import { api, type NotifyChannel, type ChannelType, type EventType, type NotifyBinding, type Task } from '@/api' import { toast } from 'vue-sonner' import ChannelList from './components/ChannelList.vue' import EventBinding from './components/EventBinding.vue' @@ -172,13 +172,13 @@ async function saveChannel() { toast.error('请填写渠道名称和类型') return } - + // 确保 enabled 字段有值 const channelData = { ...editingChannel.value, enabled: editingChannel.value.enabled ?? true } - + try { await api.notify.saveChannel(channelData) toast.success('保存成功') @@ -321,52 +321,27 @@ onMounted(() => { - + - + - + - + diff --git a/web/src/views/notify/components/ApiUsage.vue b/web/src/views/notify/components/ApiUsage.vue index 89c8fd0..f3b6c2a 100644 --- a/web/src/views/notify/components/ApiUsage.vue +++ b/web/src/views/notify/components/ApiUsage.vue @@ -65,28 +65,23 @@ const shellExample = computed(() => `curl -s -X POST "http://${host.value}/api/v
- -
+
+ title="复制 Token">
-
-
+

请妥善保管您的 Token,一旦丢失需通过上方按钮重新生成。令牌将作为请求头中的 notify-token 字段发送。

@@ -107,40 +102,63 @@ const shellExample = computed(() => `curl -s -X POST "http://${host.value}/api/v
-
-
+
+
POST - /api/v1/notify/send + /api/v1/notify/send
-
- -
+ +
- Headers -
-

Content-Type: application/json

-

notify-token: <TOKEN>

+ Headers +
+
+ Content-Type: + application/json +
+
+ notify-token: + <TOKEN> +
- +
- Body (JSON) -
-

{

-

"channel_id": "ID", // 渠道唯一标识

-

"title": "标题", // 可选

-

"text": "内容" // 必填

-

}

+ Body + (JSON) +
+

{

+
+
+ "channel_id": + "ID", + // + 渠道唯一标识 +
+
+ "title": + "标题", + // 可选 +
+
+ "text": + "内容" + // 必填 +
+
+

}

@@ -158,12 +176,9 @@ const shellExample = computed(() => `curl -s -X POST "http://${host.value}/api/v
Shell 脚本示例
-
-
+
-

# 使用 CURL 调用推送接口

-

curl -s -X POST "http://{{ host }}/api/v1/notify/send" \

-

-H "Content-Type: application/json" \

-

-H "notify-token: {{ apiToken || 'YOUR_TOKEN' }}" \

-

-d '{"channel_id":"ID","title":"任务完成","text":"脚本执行完毕"}'

+

# 使用 CURL 调用推送接口

+

curl -s -X POST "http://{{ host + }}/api/v1/notify/send" \

+

-H "Content-Type: + application/json" \

+

-H "notify-token: {{ apiToken || + 'YOUR_TOKEN' }}" \ +

+

-d '{"channel_id":"ID","title":"任务完成","text":"脚本执行完毕"}' +

- -
- + +
+ 渠道 ID 快速查找 -
暂无活跃渠道
+
暂无活跃渠道
-
- {{ ch.id.slice(0, 8) }} - {{ ch.name }} +
+ {{ ch.id.slice(0, 8) }} + {{ ch.name }} +
@@ -198,3 +229,38 @@ const shellExample = computed(() => `curl -s -X POST "http://${host.value}/api/v
+ + diff --git a/web/src/views/notify/components/ChannelList.vue b/web/src/views/notify/components/ChannelList.vue index 0621b82..5399211 100644 --- a/web/src/views/notify/components/ChannelList.vue +++ b/web/src/views/notify/components/ChannelList.vue @@ -2,8 +2,10 @@ import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Plus, Trash2, TestTube, Pencil, Bell, Calendar } from 'lucide-vue-next' +import { Plus, Trash2, TestTube, Pencil, Bell, Calendar, Copy, Check } from 'lucide-vue-next' import type { NotifyChannel, ChannelType } from '@/api' +import { ref } from 'vue' +import { toast } from 'vue-sonner' defineProps<{ channels: NotifyChannel[] @@ -21,6 +23,18 @@ function getChannelTypeName(type: string, channelTypes: ChannelType[]): string { const found = channelTypes.find(t => t.type === type) return found ? found.label : type } + +const copiedBlock = ref(null) + +function copyToClipboard(text: string, blockId: string) { + navigator.clipboard.writeText(text).then(() => { + copiedBlock.value = blockId + toast.success('已复制长 ID') + setTimeout(() => { + copiedBlock.value = null + }, 2000) + }) +}