diff --git a/README.md b/README.md index 7f7219f..86d77f1 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,61 @@ # DiAL -基于 Bun + TypeScript 的多类型拨测监控工具。通过 YAML 配置文件定义 HTTP 和命令行拨测目标,后端按配置定时并发拨测,结果持久化到本地 SQLite,前端 Dashboard 展示各目标实时状态、可用率、耗时趋势等,并支持手动、10 秒、30 秒、1 分钟、5 分钟刷新频率切换,以及系统、明亮、黑暗三种主题模式。主题模式选择会保存在当前浏览器本地存储中,同一浏览器再次访问时自动恢复。 +
+ 轻量级多类型拨测监控工具 +
+ ++ 基于 Bun + TypeScript 构建 · YAML 配置驱动 · 内置 Dashboard +
+ +--- + +DiAL 是一个自托管的拨测监控工具,支持 **HTTP**、**命令行** 和 **数据库** 三种拨测类型。通过 YAML 配置文件定义拨测目标,后端定时并发执行拨测并将结果持久化到本地 SQLite,前端 Dashboard 展示各目标的实时状态、可用率和耗时趋势。 + +**功能亮点:** + +- 多种拨测类型:HTTP(GET/POST/PUT 等)、Cmd(命令行执行)、DB(PostgreSQL/MySQL/SQLite) +- 丰富的校验规则:状态码、响应头、JSONPath、CSS 选择器、XPath、正则匹配、数值比较等 +- 响应式 Dashboard:实时状态、可用率统计、耗时趋势图、手动/自动刷新 +- 多主题支持:系统、明亮、黑暗三种主题模式 +- 零外部依赖:数据存储使用 SQLite,无需额外数据库服务 ## 快速开始 +**前置条件:** [Bun](https://bun.sh/) >= 1.0 + ```bash +# 克隆仓库 +git clone https://github.com/your-org/DiAL.git +cd DiAL + +# 安装依赖 bun install + +# 复制示例配置并按需修改 cp probes.example.yaml probes.yaml + +# 启动开发服务器 bun run dev probes.yaml ``` -`bun run dev` 启动双进程开发服务器(Vite :5173 + Bun API :3000),访问 `http://127.0.0.1:5173`。 +`bun run dev` 会同时启动 Vite 开发服务器(`http://127.0.0.1:5173`)和 API 服务器(`http://127.0.0.1:3000`),访问前端地址即可使用 Dashboard。 -## 开发验证 +## 生产部署 ```bash -bun run check # schema:check + typecheck + lint + bun test -bun run verify # check + build +# 构建 +bun run build + +# 运行 +./dist/dial-server ./probes.yaml ``` -`verify` 会基于当前源码重新构建生产 executable。原 smoke test 已移除,executable/E2E 验证后续单独补充。 +构建产物为独立可执行文件,只需一个 YAML 配置文件即可运行。 ## 配置文件 -程序通过 YAML 配置文件定义所有运行参数: +程序通过 YAML 配置文件定义所有运行参数,完整示例参见 [`probes.example.yaml`](probes.example.yaml)。 ```yaml # yaml-language-server: $schema=./probe-config.schema.json @@ -38,7 +70,7 @@ runtime: retention: "7d" defaults: - interval: "5s" + interval: "30s" timeout: "10s" http: method: GET @@ -65,23 +97,10 @@ targets: Content-Type: contains: "application/json" body: - - contains: "slideshow" - json: path: "$.slideshow.title" equals: "Sample Slide Show" - - name: "HTML 页面示例" - type: http - http: - url: "https://httpbin.org/html" - expect: - status: [200] - body: - - contains: "Moby-Dick" - - xpath: - path: "/html/body/h1/text()" - equals: "Herman Melville - Moby-Dick" - - name: "Bun 脚本检查" type: cmd cmd: @@ -106,145 +125,121 @@ targets: ### 配置说明 -- **server**: 服务配置(均可省略,使用默认值) - - `host`: 监听地址,默认 `127.0.0.1` - - `port`: 监听端口,默认 `3000` - - `dataDir`: 数据目录,默认 `./data`,相对路径基于配置文件所在目录解析 -- **runtime**: 运行时配置 - - `maxConcurrentChecks`: 最大并发拨测数,默认 `20` - - `retention`: 历史数据保留时长,默认 `7d`,支持 `ms`/`s`/`m`/`h`/`d` 单位 -- **defaults**: 全局默认值(均可省略) - - `interval`: 拨测间隔,默认 `30s` - - `timeout`: 超时时间,默认 `10s` - - `http`: HTTP 类型默认值 - - `method`: HTTP 方法,默认 `GET`,必须使用大写枚举值,支持 `GET`、`HEAD`、`POST`、`PUT`、`PATCH`、`DELETE`、`OPTIONS` - - `maxBodyBytes`: 响应体最大字节数,默认 `100MB` - - `headers`: 默认请求头(target 中的 headers 会合并覆盖 defaults 中的同名头) - - `cmd`: Cmd 类型默认值 - - `maxOutputBytes`: 输出最大字节数,默认 `100MB` - - `cwd`: 默认工作目录(相对于配置文件所在目录解析,默认 `.`) -- **targets**: 拨测目标列表(必填) - - `name`: 目标名称(必填,唯一) - - `type`: 目标类型,`http`、`cmd` 或 `db`(必填) - - `group`: 分组名称(可选,默认 `"default"`) - - `http`: HTTP 拨测配置(type 为 http 时必填) - - `url`: 目标 URL - - `method`、`headers`、`body`: 请求参数(`headers` 会与 `defaults.http.headers` 合并,target 优先) - - `ignoreSSL`: 是否忽略 HTTPS 证书校验,默认 `false`,用于自签名或私有证书服务 - - `maxRedirects`: 最大重定向跟随次数,默认 `0`(不跟随重定向) - - `cmd`: 命令行拨测配置(type 为 cmd 时必填) - - `exec`: 可执行文件名或路径 - - `args`: 命令行参数列表 - - `env`: 环境变量覆盖(可选,继承进程环境变量并合并覆盖) - - `cwd`: 工作目录(可选,相对于配置文件所在目录解析,默认 `.`) - - `db`: 数据库拨测配置(type 为 db 时必填) - - `url`: 数据库连接字符串,支持 `postgres://`、`mysql://`、`sqlite://` 协议 - - `query`: SQL 查询语句(可选,不配置时仅测试连接) - - `interval`、`timeout`: 覆盖全局默认值 - - `expect`: 期望校验 - - `status`: 可接受的状态码列表(HTTP),支持精确状态码和范围模式(如 `"2xx"`)混合配置;未指定时默认 `[200]` - - `exitCode`: 可接受的退出码列表(Cmd);未指定时不校验退出码 - - `headers`: 响应头校验(HTTP,支持字符串精确匹配或操作符对象) - - `maxDurationMs`: 最大耗时阈值(毫秒) - - HTTP:覆盖完整执行(含重定向、响应体读取和 expect 校验) - - Cmd:覆盖命令执行耗时(含 stdout/stderr 读取) - - `body`: HTTP 响应体校验(数组,可组合使用) - - `contains`: 响应体包含的文本 - - `regex`: 响应体匹配的正则表达式(启动期会拒绝嵌套量词等存在 ReDoS 风险的模式) - - `json`: JSONPath 提取值比较 - - `path`: JSONPath 表达式(必填,如 `$.slideshow.title`) - - 比较操作符(可选,无操作符时仅检查路径对应值是否存在) - - `css`: CSS 选择器提取 HTML 元素比较 - - `selector`: CSS 选择器(必填) - - `attr`: 提取元素属性值而非文本内容(可选,如 `href`、`class`) - - 比较操作符(可选,无操作符时仅检查元素是否存在) - - `xpath`: XPath 提取 XML/HTML 节点比较 - - `path`: XPath 表达式(必填,如 `/html/body/h1/text()`) - - 比较操作符(可选,无操作符时仅检查节点是否存在) - - `stdout` / `stderr`: Cmd 输出校验(数组,每项为一个操作符对象) - - `rowCount`: DB 查询返回行数校验(支持操作符对象) - - `rows`: DB 查询结果逐行校验(数组,每项为列名→操作符映射) - - 比较操作符:`equals`(默认)、`contains`、`match`(正则,启动期会拒绝存在 ReDoS 风险的模式)、`empty`、`exists`、`gte`、`lte`、`gt`、`lt` +#### server — 服务配置(均可省略,使用默认值) -大小说明:`maxBodyBytes` 和 `maxOutputBytes` 支持单位 `KB`、`MB`、`GB`,也可直接使用数字(非负安全整数字节数)。 +| 字段 | 说明 | 默认值 | +| --------- | ------------------------------------------ | ----------- | +| `host` | 监听地址 | `127.0.0.1` | +| `port` | 监听端口 | `3000` | +| `dataDir` | 数据目录,相对路径基于配置文件所在目录解析 | `./data` | -配置校验:系统启动时会先用 TypeBox 生成的 JSON Schema 契约校验字段类型、必填字段、枚举、数组/对象形状和未知字段,再执行语义 validator 校验 target name 唯一性、URL、正则、JSONPath、XPath、size/duration 解析等规则。非法配置会阻止启动并输出中文错误信息。 +#### runtime — 运行时配置 -未知字段:除 `http.headers`、`defaults.http.headers`、`expect.headers`、`cmd.env` 等动态键值表外,未知字段会导致启动失败。配置备注请使用 YAML 注释,不要添加 `note`、`comment` 等未声明字段。 +| 字段 | 说明 | 默认值 | +| --------------------- | ------------------------------------------------ | ------ | +| `maxConcurrentChecks` | 最大并发拨测数 | `20` | +| `retention` | 历史数据保留时长,支持 `ms`/`s`/`m`/`h`/`d` 单位 | `7d` | -JSON Schema:仓库根目录导出 `probe-config.schema.json`,可在 YAML 文件顶部添加 `# yaml-language-server: $schema=./probe-config.schema.json` 获取编辑器提示和静态校验。该 schema 由运行期契约 fragments 生成,提交前可用 `bun run schema:check` 检查同步。 +#### defaults — 全局默认值(均可省略) -时长格式支持:`500ms`、`30s`、`5m`、`2h`、`7d` +| 字段 | 说明 | 默认值 | +| -------------------- | -------------------------------------------------------------------- | ------- | +| `interval` | 拨测间隔 | `30s` | +| `timeout` | 超时时间 | `10s` | +| `http.method` | HTTP 方法,支持 `GET`/`HEAD`/`POST`/`PUT`/`PATCH`/`DELETE`/`OPTIONS` | `GET` | +| `http.maxBodyBytes` | 响应体最大字节数 | `100MB` | +| `http.headers` | 默认请求头(target 中的 headers 会合并覆盖同名头) | — | +| `cmd.maxOutputBytes` | 输出最大字节数 | `100MB` | +| `cmd.cwd` | 默认工作目录(相对于配置文件所在目录) | `.` | -## API 端点 +#### targets — 拨测目标列表(必填) -| 端点 | 说明 | -| ----------------------------------------------------------------- | ------------------------------------------------------------ | -| `GET /health` | 健康检查 | -| `GET /api/meta` | 运行时元信息(checker 类型列表) | -| `GET /api/dashboard?window=24h&recentLimit=30` | Dashboard 首屏聚合数据(summary + targets) | -| `GET /api/targets/:id/metrics?from=ISO&to=ISO&bucket=1h` | 指定目标的统计、可靠性指标和按小时趋势 | -| `GET /api/targets/:id/history?from=ISO&to=ISO&page=1&pageSize=20` | 指定目标的拨测记录(时间范围 + 分页,`pageSize` 最大 `200`) | +每个 target 的通用字段: -### 响应字段 +| 字段 | 说明 | 必填 | +| ---------- | ----------------------------- | -------------------- | +| `name` | 目标名称(全局唯一) | 是 | +| `type` | 目标类型:`http`、`cmd`、`db` | 是 | +| `group` | 分组名称 | 否,默认 `"default"` | +| `interval` | 覆盖全局拨测间隔 | 否 | +| `timeout` | 覆盖全局超时时间 | 否 | -**DashboardResponse**: `summary`、`targets` +**HTTP 类型** (`type: http`) -**DashboardResponse.summary**: `total`、`up`、`down`、`lastCheckTime`、`incidents`、`window` +| 字段 | 说明 | +| ------------------- | --------------------------------------- | +| `http.url` | 目标 URL | +| `http.method` | HTTP 方法(覆盖 defaults) | +| `http.headers` | 请求头(与 defaults.http.headers 合并) | +| `http.body` | 请求体 | +| `http.ignoreSSL` | 忽略 HTTPS 证书校验,默认 `false` | +| `http.maxRedirects` | 最大重定向跟随次数,默认 `0`(不跟随) | -**MetaResponse**: `checkerTypes`(已注册 checker 类型标识符列表) +**Cmd 类型** (`type: cmd`) -**TargetStatus**: `id`、`name`、`type`(checker 类型,如 http/cmd/db)、`target`(URL、命令摘要或数据库连接摘要)、`group`、`interval`、`latestCheck`、`stats`、`currentStreak`、`recentSamples` +| 字段 | 说明 | +| ---------- | -------------------------------------- | +| `cmd.exec` | 可执行文件名或路径 | +| `cmd.args` | 命令行参数列表 | +| `cmd.env` | 环境变量覆盖(继承进程环境变量并合并) | +| `cmd.cwd` | 工作目录(相对于配置文件所在目录) | -**RecentSample**: `timestamp`、`durationMs`、`up` +**DB 类型** (`type: db`) -**CheckResult**: `timestamp`、`matched`、`durationMs`、`statusDetail`、`failure` +| 字段 | 说明 | +| ---------- | ------------------------------------------------------------- | +| `db.url` | 数据库连接字符串,支持 `postgres://`、`mysql://`、`sqlite://` | +| `db.query` | SQL 查询语句(不配置时仅测试连接) | -**CheckFailure**: `kind`(error/mismatch)、`phase`、`path`、`message`、`expected?`(仅 mismatch)、`actual?`(仅 mismatch) +#### expect — 期望校验 -**TargetStats**: `totalChecks`、`upChecks`、`downChecks`、`availability` +| 字段 | 适用类型 | 说明 | +| ------------------- | -------- | ---------------------------------------------------------------- | +| `status` | HTTP | 可接受的状态码列表,支持精确码和范围(如 `"2xx"`);默认 `[200]` | +| `exitCode` | Cmd | 可接受的退出码列表;未指定时不校验 | +| `headers` | HTTP | 响应头校验 | +| `maxDurationMs` | 全部 | 最大耗时阈值(毫秒) | +| `body` | HTTP | 响应体校验(数组,可组合使用,见下方) | +| `stdout` / `stderr` | Cmd | 输出校验(数组,每项一个操作符对象) | +| `rowCount` | DB | 查询返回行数校验(操作符对象) | +| `rows` | DB | 查询结果逐行校验(数组,列名→操作符映射) | -**CurrentStreak**: `up`、`count`、`capped?` +**body 校验项**(数组中可混合使用): -**TargetMetricsResponse**: `targetId`、`window`、`stats`、`trend` +- `contains` — 响应体包含指定文本 +- `regex` — 正则匹配(启动期会拒绝存在 ReDoS 风险的模式) +- `json` — JSONPath 提取值比较(`path` 必填,如 `$.slideshow.title`) +- `css` — CSS 选择器提取 HTML 元素(`selector` 必填,`attr` 可选提取属性) +- `xpath` — XPath 提取 XML/HTML 节点(`path` 必填,如 `/html/body/h1/text()`) -**TargetMetricsResponse.stats**: `totalChecks`、`upChecks`、`downChecks`、`availability`、`avgDurationMs`、`p95DurationMs`、`p99DurationMs`、`mttr`、`longestOutage`、`incidentCount`、`currentStreak` +**比较操作符**:`equals`(默认)、`contains`、`match`(正则)、`empty`、`exists`、`gte`、`lte`、`gt`、`lt` -**TrendPoint**: `bucketStart`、`avgDurationMs`、`minDurationMs`、`maxDurationMs`、`availability`、`totalChecks`、`upChecks`、`downChecks` +**大小说明**:`maxBodyBytes` 和 `maxOutputBytes` 支持 `KB`、`MB`、`GB` 单位,也可直接使用数字。 -**HistoryResponse**: `items`(CheckResult[])、`total`、`page`、`pageSize` +**时长格式**:`500ms`、`30s`、`5m`、`2h`、`7d` -### 错误响应 +**JSON Schema**:仓库根目录导出 `probe-config.schema.json`,在 YAML 文件顶部添加 `# yaml-language-server: $schema=./probe-config.schema.json` 即可在编辑器中获得提示和校验。 -API 错误返回 `ApiErrorResponse` 格式: - -```json -{ "error": "描述信息", "status": 400 } -``` - -| 状态码 | 触发场景 | -| ------ | ------------------------------------------------------------------------------------------ | -| 400 | 参数格式错误(无效 ID、from/to 缺失或格式错误、page/pageSize 非正整数、pageSize 超过 200) | -| 404 | 目标不存在、API 路由未匹配、非 GET 方法请求 API 路由 | - -## 运行参数 - -CLI 只接受一个参数:YAML 配置文件路径。 - -```bash -./dist/dial-server ./probes.yaml -``` +> **注意:** 配置校验在启动时执行,非法配置会阻止启动并输出错误信息。除动态键值表(`headers`、`env`)外,未知字段会导致启动失败,请使用 YAML 注释。 ## 目标状态判定 -单层判定模型,适用于 HTTP 和 Cmd 两种类型: +采用单层判定模型: -- **matched**: 是否符合 expect 规则(HTTP 未指定 `expect.status` 时默认检查 `[200]`) -- **UP** = matched -- **DOWN** = NOT matched +- **UP** = 拨测结果符合 expect 规则 +- **DOWN** = 拨测结果不符合 expect 规则 -执行失败(网络错误、超时、进程崩溃)和 expect 不匹配都统一为 `matched=false`,通过 `failure.kind` 区分(`"error"` vs `"mismatch"`)。 +执行失败(网络错误、超时、进程崩溃)和 expect 不匹配都统一为 DOWN,通过 `failure.kind` 区分原因(`"error"` vs `"mismatch"`)。 ---- +## 开发 -> 开发相关文档(项目结构、构建、测试、代码规范等)请参阅 [DEVELOPMENT.md](DEVELOPMENT.md)。 +```bash +bun run check # schema:check + typecheck + lint + test +bun run verify # check + build +``` + +开发相关文档(项目结构、构建、测试、代码规范等)请参阅 [DEVELOPMENT.md](DEVELOPMENT.md)。 + +## License + +MIT diff --git a/probes.example.yaml b/probes.example.yaml index 74f4f72..a082492 100644 --- a/probes.example.yaml +++ b/probes.example.yaml @@ -48,34 +48,8 @@ targets: - json: path: "$.slideshow.slides[0].title" contains: "Wake" - - json: - path: "$.slideshow.slides[0].type" - equals: "all" - regex: '"title"' - - name: "HTML 页面 — CSS 选择器" - type: http - http: - url: "https://httpbin.org/html" - expect: - body: - - css: - selector: "h1" - contains: "Moby-Dick" - - css: - selector: "body" - exists: true - - - name: "HTML 页面 — XPath 提取节点文本" - type: http - http: - url: "https://httpbin.org/html" - expect: - body: - - xpath: - path: "/html/body/h1/text()" - contains: "Melville" - - name: "POST 接口测试" type: http http: @@ -94,61 +68,6 @@ targets: path: "$.json.version" gte: 1 - - name: "请求头验证" - type: http - http: - url: "https://httpbin.org/headers" - headers: - X-Custom-Header: "dial-server" - expect: - status: [200] - body: - - json: - path: "$.headers.X-Custom-Header" - equals: "dial-server" - - - name: "响应头自定义校验" - type: http - http: - url: "https://httpbin.org/response-headers" - headers: - accept: "application/json" - expect: - body: - - json: - path: "$.Content-Type" - equals: "application/json" - - - name: "多状态码允许" - type: http - http: - url: "https://httpbin.org/status/200" - expect: - status: [200, 201, 204] - - - name: "状态码范围匹配" - type: http - http: - url: "https://httpbin.org/status/204" - expect: - status: ["2xx"] - - - name: "自签名证书跳过 SSL" - type: http - http: - url: "https://internal.local/health" - ignoreSSL: true - expect: - status: ["2xx"] - - - name: "跟随重定向" - type: http - http: - url: "https://httpbin.org/redirect/1" - maxRedirects: 5 - expect: - status: [200] - # ========== Cmd targets ========== - name: "Bun 版本输出匹配" @@ -162,55 +81,6 @@ targets: stdout: - match: "^\\d+\\.\\d+\\.\\d+" - - name: "自定义文本输出" - type: cmd - cmd: - exec: "bun" - args: ["-e", "console.log('check ok')"] - expect: - stdout: - - equals: "check ok\n" - maxDurationMs: 3000 - - - name: "脚本执行无 stderr" - type: cmd - cmd: - exec: "bun" - args: ["-e", "process.stdout.write('ok')"] - expect: - exitCode: [0] - stderr: - - empty: true - - - name: "日期脚本输出包含年份" - type: cmd - cmd: - exec: "bun" - args: ["-e", "console.log(new Date().getFullYear())"] - expect: - stdout: - - match: "^20\\d{2}\n?$" - - - name: "环境变量覆盖" - type: cmd - cmd: - exec: "bun" - args: ["-e", "console.log(process.env.LANG ?? '')"] - env: - LANG: "C" - expect: - stdout: - - contains: "C" - - - name: "运行平台非空输出" - type: cmd - cmd: - exec: "bun" - args: ["-e", "console.log(process.platform)"] - expect: - stdout: - - match: ".+" - - name: "多规则 stdout 顺序校验" type: cmd interval: "5m" @@ -243,16 +113,6 @@ targets: expect: maxDurationMs: 1000 - - name: "SQLite 内存数据库查询行数" - type: db - db: - url: "sqlite://:memory:" - query: "SELECT 1 as cnt" - expect: - maxDurationMs: 1000 - rowCount: - gte: 1 - - name: "SQLite 内存数据库多列结果校验" type: db db: