- useDashboard hook 改为接受 refetchInterval 动态参数,移除固定 8 秒常量 - Header operations 区域重构为 RadioGroup(手动/10秒/30秒/1分钟/5分钟)+ 倒计时/刷新按钮 - 新增 formatCountdown 工具函数及单元测试 - 新增 .dashboard-refresh-control 和 .dashboard-countdown CSS 类 - 同步更新 DEVELOPMENT.md、README.md、主 specs
10 KiB
DiAL
基于 Bun + TypeScript 的多类型拨测监控工具。通过 YAML 配置文件定义 HTTP 和命令行拨测目标,后端按配置定时并发拨测,结果持久化到本地 SQLite,前端 Dashboard 展示各目标实时状态、可用率、耗时趋势等,并支持手动、10 秒、30 秒、1 分钟、5 分钟刷新频率切换。
快速开始
bun install
cp probes.example.yaml probes.yaml
bun run dev probes.yaml
bun run dev 启动单进程 fullstack 开发服务器(后端 API + 前端 SPA + HMR),访问 http://127.0.0.1:3000。
开发验证
bun run check # schema:check + typecheck + lint + bun test
bun run verify # check + build
verify 会基于当前源码重新构建生产 executable。原 smoke test 已移除,executable/E2E 验证后续单独补充。
配置文件
程序通过 YAML 配置文件定义所有运行参数:
# yaml-language-server: $schema=./probe-config.schema.json
server:
host: "127.0.0.1"
port: 3000
dataDir: "/tmp/probes_data"
runtime:
maxConcurrentChecks: 20
retention: "7d"
defaults:
interval: "5s"
timeout: "10s"
http:
method: GET
maxBodyBytes: "10MB"
cmd:
maxOutputBytes: "1MB"
targets:
- name: "Baidu"
type: http
http:
url: "https://www.baidu.com"
expect:
status: [200]
maxDurationMs: 10000
- name: "JSON API 示例"
type: http
http:
url: "https://httpbin.org/json"
expect:
status: [200]
headers:
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:
exec: "bun"
args: ["-e", "console.log('ok')"]
expect:
exitCode: [0]
stdout:
- contains: "ok"
配置说明
- server: 服务配置(均可省略,使用默认值)
host: 监听地址,默认127.0.0.1port: 监听端口,默认3000dataDir: 数据目录,默认./data,相对路径基于配置文件所在目录解析
- runtime: 运行时配置
maxConcurrentChecks: 最大并发拨测数,默认20retention: 历史数据保留时长,默认7d,支持ms/s/m/h/d单位
- defaults: 全局默认值(均可省略)
interval: 拨测间隔,默认30stimeout: 超时时间,默认10shttp: HTTP 类型默认值method: HTTP 方法,默认GET,必须使用大写枚举值,支持GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONSmaxBodyBytes: 响应体最大字节数,默认100MBheaders: 默认请求头(target 中的 headers 会合并覆盖 defaults 中的同名头)
cmd: Cmd 类型默认值maxOutputBytes: 输出最大字节数,默认100MBcwd: 默认工作目录(相对于配置文件所在目录解析,默认.)
- targets: 拨测目标列表(必填)
name: 目标名称(必填,唯一)type: 目标类型,http或cmd(必填)group: 分组名称(可选,默认"default")http: HTTP 拨测配置(type 为 http 时必填)url: 目标 URLmethod、headers、body: 请求参数(headers会与defaults.http.headers合并,target 优先)ignoreSSL: 是否忽略 HTTPS 证书校验,默认false,用于自签名或私有证书服务maxRedirects: 最大重定向跟随次数,默认0(不跟随重定向)
cmd: 命令行拨测配置(type 为 cmd 时必填)exec: 可执行文件名或路径args: 命令行参数列表env: 环境变量覆盖(可选,继承进程环境变量并合并覆盖)cwd: 工作目录(可选,相对于配置文件所在目录解析,默认.)
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 输出校验(数组,每项为一个操作符对象)- 比较操作符:
equals(默认)、contains、match(正则,启动期会拒绝存在 ReDoS 风险的模式)、empty、exists、gte、lte、gt、lt
大小说明:maxBodyBytes 和 maxOutputBytes 支持单位 KB、MB、GB,也可直接使用数字(非负安全整数字节数)。
配置校验:系统启动时会先用 TypeBox 生成的 JSON Schema 契约校验字段类型、必填字段、枚举、数组/对象形状和未知字段,再执行语义 validator 校验 target name 唯一性、URL、正则、JSONPath、XPath、size/duration 解析等规则。非法配置会阻止启动并输出中文错误信息。
未知字段:除 http.headers、defaults.http.headers、expect.headers、cmd.env 等动态键值表外,未知字段会导致启动失败。配置备注请使用 YAML 注释,不要添加 note、comment 等未声明字段。
JSON Schema:仓库根目录导出 probe-config.schema.json,可在 YAML 文件顶部添加 # yaml-language-server: $schema=./probe-config.schema.json 获取编辑器提示和静态校验。该 schema 由运行期契约 fragments 生成,提交前可用 bun run schema:check 检查同步。
时长格式支持:500ms、30s、5m、2h、7d
API 端点
| 端点 | 说明 |
|---|---|
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) |
响应字段
DashboardResponse: summary、targets
DashboardResponse.summary: total、up、down、lastCheckTime、incidents、window
MetaResponse: checkerTypes(已注册 checker 类型标识符列表)
TargetStatus: id、name、type(checker 类型,如 http/cmd)、target(URL 或命令摘要)、group、interval、latestCheck、stats、currentStreak、recentSamples
RecentSample: timestamp、durationMs、up
CheckResult: timestamp、matched、durationMs、statusDetail、failure
CheckFailure: kind(error/mismatch)、phase、path、message、expected?(仅 mismatch)、actual?(仅 mismatch)
TargetStats: totalChecks、upChecks、downChecks、availability
CurrentStreak: up、count、capped?
TargetMetricsResponse: targetId、window、stats、trend
TargetMetricsResponse.stats: totalChecks、upChecks、downChecks、availability、avgDurationMs、p95DurationMs、p99DurationMs、mttr、longestOutage、incidentCount、currentStreak
TrendPoint: bucketStart、avgDurationMs、minDurationMs、maxDurationMs、availability、totalChecks、upChecks、downChecks
HistoryResponse: items(CheckResult[])、total、page、pageSize
错误响应
API 错误返回 ApiErrorResponse 格式:
{ "error": "描述信息", "status": 400 }
| 状态码 | 触发场景 |
|---|---|
| 400 | 参数格式错误(无效 ID、from/to 缺失或格式错误、page/pageSize 非正整数、pageSize 超过 200) |
| 404 | 目标不存在、API 路由未匹配、非 GET 方法请求 API 路由 |
运行参数
CLI 只接受一个参数:YAML 配置文件路径。
./dist/dial-server ./probes.yaml
目标状态判定
单层判定模型,适用于 HTTP 和 Cmd 两种类型:
- matched: 是否符合 expect 规则(HTTP 未指定
expect.status时默认检查[200]) - UP = matched
- DOWN = NOT matched
执行失败(网络错误、超时、进程崩溃)和 expect 不匹配都统一为 matched=false,通过 failure.kind 区分("error" vs "mismatch")。
开发相关文档(项目结构、构建、测试、代码规范等)请参阅 DEVELOPMENT.md。