monitor-ai/CLAUDE.md

510 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CLAUDE.md — monitor.tlyq.ai 告警监控中心
## 项目概述
monitor-ai 是独立部署的统一告警监控中心,监控 tlyq.ai 基础设施所有容器Authelia、Redis、LLDAP、OA、assets、issue、Gitea、Nginx、www、cloud、token的健康状态通过企业微信 Webhook 推送告警。部署在腾讯云txjp 服务器),域名 `monitor.tlyq.ai`
---
## 快速参考
| 属性 | 值 |
|------|-----|
| 站点域名 | `monitor.tlyq.ai` |
| 服务器 | txjpIP: 43.133.38.210 |
| 代码路径 | `/root/docker/monitor-ai/` |
| 本地端口 | 6181 |
| 容器名 | `monitor-ai` |
| 数据库 | SQLite`data/monitor.db` |
| 默认账号 | `localadmin` / 首次部署自动生成 |
### 常用命令
```bash
cd /Users/niuniu/programs/docker/monitor-ai
npm run dev # 本地开发http://localhost:6181
npm run build # 生产构建
npm run start # 启动生产服务
```
---
## 技术栈
| 技术 | 版本 | 用途 |
|------|------|------|
| Next.js | 15 App Router | Web 框架standalone 输出) |
| SQLite | 3.x | 数据库(通过 `execFileSync` + `sqlite3` CLI |
| Tailwind CSS | v4 | 样式(`@tailwindcss/postcss` |
| lucide-react | ^1.8.0 | 图标库 |
| ldapts | ^6.0.0 | LDAP 认证客户端 |
| openid-client | ^5.7.1 | OIDC 认证 |
| tsx | ^4.0.0 | Worker TypeScript 执行 |
---
## 目录结构
```
monitor-ai/
├── Dockerfile # 两阶段构建node:22-alpine
├── docker-compose.yml # 容器编排webnet external
├── entrypoint.sh # Docker 入口双进程守护Worker + Server
├── next.config.ts # output: standalone, transpilePackages
├── shared -> ../shared # 共享库符号链接
├── scripts/
│ ├── monitor-worker.ts # Worker 独立入口
│ ├── deploy-monitor.sh # 部署脚本
│ └── monitor-watchdog.sh # 健康检查兜底脚本
├── src/
│ ├── middleware.ts # Edge Runtime 路由守卫
│ ├── app/
│ │ ├── page.tsx # 仪表盘(四层:标题 + 状态条 + KPI + 服务卡片)
│ │ ├── login/page.tsx # 登录页
│ │ ├── services/page.tsx # 服务管理
│ │ ├── settings/page.tsx # 告警设置
│ │ ├── alerts/page.tsx # 告警历史
│ │ ├── status-history/page.tsx # 状态变更历史
│ │ ├── admin/ # 管理页面users/roles/audit-logs
│ │ └── api/ # API 路由
│ ├── components/
│ │ ├── Sidebar.tsx # 侧边栏导航
│ │ ├── ThemeProvider.tsx # 主题上下文
│ │ └── ThemeToggle.tsx # 主题切换
│ └── lib/
│ ├── config.ts # 配置常量
│ ├── db.ts # 数据库操作封装execFileSync + escapeSql
│ ├── auth-config.ts # 认证配置
│ ├── permissions.ts # RBAC 权限定义
│ ├── audit.ts # 审计日志封装
│ └── monitor-worker.ts # MonitorWorker 核心类
└── data/
└── monitor.db # SQLite 数据库
```
---
## 关键文件
| 文件 | 职责 |
|------|------|
| `src/lib/monitor-worker.ts` | MonitorWorker 核心类30s tick健康检查 + 状态管理 + 告警推送) |
| `src/lib/db.ts` | SQLite 操作封装execFileSync + escapeSql无 ORM |
| `src/lib/permissions.ts` | RBAC 权限定义11 个权限点admin/editor/viewer/localadmin |
| `src/lib/audit.ts` | 审计日志封装(调用 `@shared/lib/audit` |
| `src/lib/auth-config.ts` | 认证配置OIDC + LDAP |
| `src/middleware.ts` | Edge Runtime 路由守卫Cookie JWT 验证,公开/管理路径控制) |
| `scripts/monitor-worker.ts` | Worker 独立入口(信号处理、优雅退出) |
| `entrypoint.sh` | Docker 入口双进程守护Worker + Server任一退出则容器退出 |
| `shared/lib/alert/` | 告警引擎AlertManager、HealthChecker、类型定义 |
| `shared/lib/wechat/` | 企业微信推送WeChatPusher、消息格式化 |
---
## 数据库 Schema
### 表概览
| 表名 | 说明 | 来源 |
|------|------|------|
| `services` | 被监控服务列表 | `@shared/lib/db/alert-schema` |
| `alert_channels` | Webhook 告警渠道配置 | `@shared/lib/db/alert-schema` |
| `alert_settings` | 全局设置 + 冷却状态持久化 | `@shared/lib/db/alert-schema` |
| `status_history` | 服务状态变更事件 | `@shared/lib/db/alert-schema` |
| `alert_history` | 告警推送记录 | `@shared/lib/db/alert-schema` |
| `users` | 用户账号 | `src/lib/db.ts`(内联) |
| `permissions` | 权限定义 | `src/lib/db.ts`(内联) |
| `role_permissions` | 角色-权限映射 | `src/lib/db.ts`(内联) |
| `audit_logs` | 审计日志 | `@shared/lib/audit/audit-schema` |
### services 表
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | INTEGER PK | 主键 |
| `name` | TEXT | 服务名称 |
| `category` | TEXT | 分类endpoint / container / custom |
| `alert_level` | TEXT | 告警级别critical / warning / info |
| `checks` | TEXT | 检查配置JSON 数组HealthCheckConfig[] |
| `check_interval` | INTEGER | 检查间隔秒数(默认 30 |
| `check_timeout` | INTEGER | 检查超时秒数(默认 10 |
| `enabled` | INTEGER | 是否启用(默认 1 |
| `current_status` | TEXT | 当前状态normal / abnormal / unknown |
| `status_since` | TEXT | 状态持续起始时间 |
| `display_order` | INTEGER | 显示排序 |
### alert_channels 表
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | INTEGER PK | 主键 |
| `name` | TEXT | 渠道名称 |
| `channel_type` | TEXT | 渠道类型(默认 'wechat' |
| `webhook_url` | TEXT | Webhook 地址 |
| `enabled` | INTEGER | 是否启用 |
| `level_critical` | INTEGER | 是否接收 critical 告警 |
| `level_warning` | INTEGER | 是否接收 warning 告警 |
| `level_info` | INTEGER | 是否接收 info 告警 |
| `quiet_enabled` | INTEGER | 是否启用免打扰 |
| `quiet_start` | TEXT | 免打扰开始时间 |
| `quiet_end` | TEXT | 免打扰结束时间 |
| `quiet_bypass_critical` | INTEGER | critical 是否绕过免打扰 |
| `cooldown_minutes` | INTEGER | 冷却时间(分钟) |
### status_history 表
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | INTEGER PK | 主键 |
| `service_id` | INTEGER FK | 关联服务 |
| `from_status` | TEXT | 原状态 |
| `to_status` | TEXT | 新状态 |
| `started_at` | TEXT | 状态开始时间 |
| `ended_at` | TEXT | 状态结束时间 |
| `duration_seconds` | INTEGER | 持续时长 |
| `error_message` | TEXT | 错误信息 |
| `failed_checks` | TEXT | 失败检查详情JSON |
| `truncated_by_restart` | INTEGER | 是否被重启截断 |
### alert_history 表
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | INTEGER PK | 主键 |
| `channel_id` | INTEGER FK | 告警渠道 |
| `service_id` | INTEGER FK | 关联服务 |
| `status_event_id` | INTEGER FK | 关联状态事件 |
| `alert_type` | TEXT | 告警类型alert / recovery / flapping |
| `level` | TEXT | 告警级别 |
| `title` | TEXT | 标题 |
| `content` | TEXT | 内容 |
| `sent_at` | TEXT | 发送时间 |
| `success` | INTEGER | 是否成功 |
| `response_code` | INTEGER | HTTP 响应码 |
### users 表
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | INTEGER PK | 主键 |
| `username` | TEXT UNIQUE | 用户名 |
| `display_name` | TEXT | 显示名 |
| `email` | TEXT | 邮箱 |
| `role` | TEXT | 角色admin / editor / viewer |
| `password_hash` | TEXT | 密码哈希 |
| `is_active` | INTEGER | 是否启用 |
默认用户:`localadmin`admin 角色BCrypt 密码哈希)
### 预置角色
| 角色 | 权限 | 说明 |
|------|------|------|
| `admin` | 全部 11 个权限 | 管理员 |
| `editor` | dashboard:view, services:view, alerts:view, status_history:view, status_history:stats | 编辑者 |
| `viewer` | dashboard:view, services:view, status_history:view | 只读 |
| `localadmin` | 硬编码绕过hasPermission 始终返回 true | 应急管理员 |
### 权限列表
`dashboard:view`、`services:view`、`services:manage`、`alerts:view`、`alerts:manage`、`status_history:view`、`status_history:stats`、`audit:view`、`users:view`、`users:manage`、`roles:manage`
---
## API 路由
### 公开 API
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/health` | 自身健康检查(返回 `{status:'OK'}` |
### 认证
登录逻辑OIDC SSO 优先 + LDAP 本地登录 + localadmin 应急用户。
登录成功签发 `tlyq_session` cookie共享 JWTdomain=.tlyq.ai
中间件验证 JWT payloadEdge Runtime不解密签名仅解码 payload 检查过期时间)。
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/auth/login/oidc` | OIDC SSO 重定向到 Authelia |
| POST | `/api/auth/login` | LDAP 或 localadmin 登录 |
| GET | `/api/auth/callback` | OIDC 回调处理 |
| POST | `/api/auth/logout` | 登出(清除 cookie重定向 Authelia end_session |
| GET | `/api/auth/me` | 当前用户信息 |
### 服务管理
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | `/api/services` | 登录 | 服务列表(含解析后的 JSON checks |
| POST | `/api/services` | services:manage | 新增服务 |
| PUT | `/api/services/[id]` | services:manage | 更新服务 |
| DELETE | `/api/services/[id]` | services:manage | 删除服务 |
| POST | `/api/services/[id]/check` | services:manage | 手动触发单次健康检查 |
### 告警渠道
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | `/api/alert-channels` | alerts:manage | 渠道列表webhook_url 脱敏) |
| POST | `/api/alert-channels` | alerts:manage | 新增渠道 |
| PUT | `/api/alert-channels/[id]` | alerts:manage | 更新渠道 |
| DELETE | `/api/alert-channels/[id]` | alerts:manage | 删除渠道 |
| POST | `/api/alert-channels/[id]/test` | alerts:manage | 测试推送 |
### 告警历史 / 状态历史
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | `/api/alerts` | alerts:view | 告警历史(分页) |
| GET | `/api/status-history` | 登录 | 状态变更历史(分页,可按 service_id 筛选) |
| GET | `/api/status` | 登录 | 所有服务实时状态汇总 |
### 内部 APIx-internal-key 鉴权)
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/internal/users` | 返回用户列表 |
| POST | `/api/internal/users` | OA 同步用户角色 |
### 管理
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | `/api/admin/worker-status` | dashboard:view | Worker 运行状态 |
---
## Worker 架构
### MonitorWorker 核心类
位置:`src/lib/monitor-worker.ts`
```
MonitorWorker
├── HealthChecker@shared/lib/alert/health-checker
│ ├── HttpChecker — HTTP 状态码 + 响应体匹配
│ └── DockerChecker — docker ps 容器状态检查
├── AlertManager@shared/lib/alert/alert-manager
│ ├── evaluate() — 三阶段告警决策(级别匹配 → 免打扰 → 冷却)
│ └── CooldownProvider — alert_settings 表持久化冷却状态
└── WeChatPusher@shared/lib/wechat/wechat-pusher
└── pushMarkdown() — 企业微信 Webhook 推送
```
### 运行机制
| 阶段 | 间隔 | 说明 |
|------|------|------|
| 健康检查 | 30 秒 tick | 遍历所有 enabled 服务并发执行检查Promise.allSettled |
| 抖动检测 | 10 分钟窗口 | 同一服务 10 分钟内状态切换 >= 5 次 → 触发 flapping 告警,抑制后续告警 |
| 持续异常提醒 | 30 分钟 | 服务持续 abnormal 状态,每 30 分钟发送提醒 |
| 恢复通知 | 即时 | 服务恢复正常时发送 recovery 通知,绕过免打扰和冷却 |
| WAL 检查点 | 1 小时 | 被动 WAL checkpointPASSIVE |
| 数据清理 | 每天 | 删除 180 天前的 status_history 和 alert_history每批 LIMIT 1000 |
### Docker 入口entrypoint.sh
```
entrypoint.sh
├── npx tsx scripts/monitor-worker.ts & # 后台启动 Worker
├── node server.js & # 后台启动 Next.js Server
└── 守护循环:任一进程退出则容器退出 # 保证双进程存活
└── trap SIGTERM/SIGINT → 清理退出
```
---
## 认证机制
- **OIDC SSO**:通过 Authelia 进行 PKCE 授权码流程,登录成功后签发 `tlyq_session` 共享 JWT cookie
- **LDAP 本地登录**:通过 LLDAP 进行 bind 认证,签发 `tlyq_session` cookie
- **localadmin**:纯本地 BCrypt 认证,不依赖 LLDAP/OIDC用于应急登录
- **Edge Runtime 中间件**`src/middleware.ts` 运行在 Edge Runtime使用 `atob` 解码 JWT payload 检查过期时间,不验证签名(签名验证在 route handler 层)
---
## 环境配置
### 本地与云端差异
| 环境变量 | 本地开发 | 云服务器txjp | 说明 |
|---------|---------|----------------|------|
| `DATABASE_PATH` | `./data/monitor.db` | `/app/data/monitor.db` | Docker volume 挂载 |
| `MONITOR_MODE` | `dev` | `local` | 运行模式 |
| `AUTHELIA_URL` | `https://127.0.0.1:6180` | `https://sso.tlyq.ai` | Authelia 地址 |
| `OIDC_CLIENT_ID` | `monitor-oidc` | 同 | OIDC 客户端 ID |
| `OIDC_CLIENT_SECRET` | 本地生成的值 | 服务器生成的值 | OIDC 客户端密钥(每个环境独立) |
| `OIDC_REDIRECT_URI` | `http://localhost:6181/api/auth/callback` | `https://monitor.tlyq.ai/api/auth/callback` | OIDC 回调地址 |
| `JWT_SECRET` | `dev-jwt-secret-change-in-production` | 部署脚本自动生成 | JWT 签名密钥 |
| `COOKIE_DOMAIN` | `.tlyq.ai` | 同 | Cookie 域 |
| `LDAP_URL` | `ldap://ldap-ai:3890` | `ldap://ldap-ai:3890` | LLDAP 地址Docker 内网) |
| `LOCALADMIN_PASSWORD` | `admin123` | 部署脚本自动生成 | 应急管理员密码 |
| `NODE_TLS_REJECT_UNAUTHORIZED` | `0` | `0` | TLS 证书验证Authelia 自签名) |
### `.env.local` 示例
```bash
DATABASE_PATH=./data/monitor.db
MONITOR_MODE=dev
AUTHELIA_URL=https://127.0.0.1:6180
OIDC_CLIENT_ID=monitor-oidc
OIDC_CLIENT_SECRET=<本地生成的值>
OIDC_REDIRECT_URI=http://localhost:6181/api/auth/callback
JWT_SECRET=dev-jwt-secret-change-in-production
COOKIE_DOMAIN=.tlyq.ai
LDAP_URL=ldap://ldap-ai:3890
LOCALADMIN_PASSWORD=admin123
NODE_TLS_REJECT_UNAUTHORIZED=0
```
---
## 共享库引用
| 库 | 用途 | 导入路径 |
|------|------|------|
| `@shared/lib/auth/jwt-v2` | JWT 签名/验证HS256含 iss | `import { signJwtV2, verifyJwtV2 } from '@shared/lib/auth/jwt-v2'` |
| `@shared/lib/auth/jwt` | JWT 签名/验证V1 兼容,无 iss | `import { signJwt, verifyJwt } from '@shared/lib/auth/jwt'` |
| `@shared/lib/auth/middleware-v2` | 路由守卫工厂V2单 cookie 模型) | `import { createMiddlewareV2 } from '@shared/lib/auth/middleware-v2'` |
| `@shared/lib/auth/middleware` | 路由守卫工厂V1已废弃 | `import { createMiddleware } from '@shared/lib/auth/middleware'` |
| `@shared/lib/auth/oidc` | OIDC PKCE 流程 | `import { discoverOidcConfig, buildAuthorizeUrl } from '@shared/lib/auth/oidc'` |
| `@shared/lib/auth/ldap` | LDAP 认证 | `import { ldapAuth } from '@shared/lib/auth/ldap'` |
| `@shared/lib/alert/alert-manager` | 告警决策引擎 | `import { AlertManager } from '@shared/lib/alert/alert-manager'` |
| `@shared/lib/alert/health-checker` | 健康检查引擎 | `import { HealthChecker, HttpChecker, DockerChecker } from '@shared/lib/alert/health-checker'` |
| `@shared/lib/audit/write-audit-log` | 审计日志 | `import { writeAuditLog } from '@shared/lib/audit/write-audit-log'` |
| `@shared/lib/wechat/wechat-pusher` | 企业微信推送 | `import { WeChatPusher } from '@shared/lib/wechat/wechat-pusher'` |
| `@shared/lib/wechat/message-formatter` | 消息格式化 | `import { formatAlertLevel } from '@shared/lib/wechat/message-formatter'` |
| `@shared/lib/db/alert-schema` | 数据库 Schema 常量 | `import { ALL_TABLES_SQL } from '@shared/lib/db/alert-schema'` |
| `@shared/ui` | UI 组件库 | `import { Button, Card, Badge } from '@shared/ui'` |
| `@shared/ui/login-page` | 统一登录页 | `import LoginPage from '@shared/ui/login-page'` |
> **路径映射**`tsconfig.json` 中配置 `"@shared/*": ["./shared/*"]``shared` 是指向 `../shared` 的符号链接。
---
## 关键设计决策
### execFileSync 数组参数
数据库操作使用 `execFileSync('sqlite3', [dbPath, '-json', sql])` 而非 ORM。SQL 通过数组参数传递,避免 shell 注入。所有用户输入通过 `escapeSql()` 函数转义(单引号双写)。
### Edge Runtime 中间件
`src/middleware.ts` 运行在 Edge Runtime不能使用 Node.js `crypto` 模块。JWT 验证仅解码 payload 检查过期时间,签名验证留给 route handler 层处理。
### 双进程守护
`entrypoint.sh` 同时启动 Worker 和 Server使用 bash 守护循环监控。任一进程退出则整个容器退出,由 Docker 重启策略接管。
### 冷却状态持久化
告警冷却状态存储在 `alert_settings` 表中key 格式:`cooldown:{channel_id}:{service_id}:{level}`),容器重启后冷却状态不丢失。
---
## Docker 部署
```
txjp 服务器
├── monitor-ai容器 ← Next.js standalone + Worker监听 6181
├── nginx-ai ← 反向代理 monitor.tlyq.ai → monitor-ai:6181
└── webnetexternal ← 共享网络
```
部署:`bash deploy-monitor.sh`(本地模式)或 `bash deploy-monitor.sh --remote --host <IP>`
源码打包上传 → 服务器 `npm install` + `npm run build``.next` 挂载进容器生效。
### 生产环境变量
```bash
DATABASE_PATH=/app/data/monitor.db
MONITOR_MODE=local
AUTHELIA_URL=https://sso.tlyq.ai
OIDC_CLIENT_ID=monitor-oidc
OIDC_CLIENT_SECRET=<部署脚本自动生成>
OIDC_REDIRECT_URI=https://monitor.tlyq.ai/api/auth/callback
JWT_SECRET=<部署脚本自动生成>
COOKIE_DOMAIN=.tlyq.ai
LDAP_URL=ldap://ldap-ai:3890
LOCALADMIN_PASSWORD=<部署脚本自动生成>
NODE_ENV=production
NODE_TLS_REJECT_UNAUTHORIZED=0
```
---
## 开发规范
- **新增 API**:在 `src/app/api/` 下创建路由 → 顶部调用 `initDatabase()``getCurrentUser()` 验证 → `checkPermission()` 校验
- **新增页面**:在 `src/app/` 下创建 → 布局由 `client-layout.tsx` 提供Sidebar + ThemeProvider
- **权限格式**`resource:action`,如 `checkPermission(user, 'services:manage')`
- **审计日志**:所有写操作 APIPOST/PUT/DELETE必须添加审计日志
```typescript
import { writeAuditLog } from '@/lib/audit'
writeAuditLog({
userId: user.id,
action: 'create' | 'update' | 'delete',
entityType: 'service' | 'alert_channel' | 'user',
entityId: id,
details: { ... },
ipAddress: getClientIP(request)
})
```
- **日期处理(时区规范)**:整个系统统一使用 UTC+8北京时间。必须遵守
1. **JavaScript/TypeScript**:禁止使用 `Date.toISOString()` 格式化本地日期,应使用本地时间方法拼接
2. **SQLite**:所有 `datetime('now')` 必须写成 `datetime('now', '+8 hours')`
3. **INSERT 时间列**:数据库表 DEFAULT 已统一为 `datetime('now', '+8 hours')`INSERT 时可省略
---
## 健康检查兜底
`scripts/monitor-watchdog.sh` 是独立于 monitor-ai 的兜底脚本,通过 crontab 每 5 分钟执行:
```bash
*/5 * * * * /root/docker/monitor-ai/scripts/monitor-watchdog.sh >> /var/log/monitor-watchdog.log 2>&1
```
`/api/health` 连续失败时,直接通过企业微信 Webhook 发送告警,并尝试重启容器。
---
## 故障排查
```bash
# 容器日志
ssh txjp "docker logs monitor-ai"
# Worker 日志(实时)
ssh txjp "docker logs -f monitor-ai 2>&1 | grep -i worker"
# 数据库查看
ssh txjp "docker exec monitor-ai sqlite3 /app/data/monitor.db 'SELECT id, name, current_status FROM services;'"
# 健康检查
ssh txjp "curl -s -k https://monitor.tlyq.ai/api/health"
# 手动触发检查
ssh txjp "docker exec monitor-ai sqlite3 /app/data/monitor.db 'UPDATE services SET current_status=\"unknown\";'"
# 重建镜像(新增依赖后)
ssh txjp "cd /root/docker/monitor-ai && docker compose build --no-cache && docker compose down && docker compose up -d"
```
---
## Git Tag 规范
使用日期版本号 `vYYYY.MM.DD`(如 `v2026.07.01`)。提交后打 tag 再推送:
```bash
git tag v$(date +%Y.%m.%d) && git push origin main && git push origin v$(date +%Y.%m.%d)
```
同一天多次提交只打一个 tag。详见根目录 `CLAUDE.md`