refactor: 项目更名为 DiAL(统一拨测平台)
将 gateway-checker/Gateway Checker 统一替换为 dial-server/DiAL - 包名、可执行文件名、API service 标识改为 dial-server - UI 标题改为 DiAL,副标题改为统一拨测平台 - 同步更新测试断言、构建脚本、示例配置和文档
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# Gateway Checker
|
# DiAL
|
||||||
|
|
||||||
基于 Bun + TypeScript 的多类型拨测监控工具。通过 YAML 配置文件定义 HTTP 和命令行拨测目标,后端按配置定时并发拨测,结果持久化到本地 SQLite,前端 Dashboard 展示各目标实时状态、可用率、耗时趋势等。
|
基于 Bun + TypeScript 的多类型拨测监控工具。通过 YAML 配置文件定义 HTTP 和命令行拨测目标,后端按配置定时并发拨测,结果持久化到本地 SQLite,前端 Dashboard 展示各目标实时状态、可用率、耗时趋势等。
|
||||||
|
|
||||||
@@ -233,12 +233,12 @@ bun run build
|
|||||||
1. 运行 `vite build`,输出前端资源到 `dist/web`
|
1. 运行 `vite build`,输出前端资源到 `dist/web`
|
||||||
2. 生成临时 `.build/static-assets.ts`,嵌入 Vite 产物
|
2. 生成临时 `.build/static-assets.ts`,嵌入 Vite 产物
|
||||||
3. 生成临时 `.build/server-entry.ts`,作为生产入口
|
3. 生成临时 `.build/server-entry.ts`,作为生产入口
|
||||||
4. 运行 `Bun.build({ compile })`,输出 `dist/gateway-checker`
|
4. 运行 `Bun.build({ compile })`,输出 `dist/dial-server`
|
||||||
|
|
||||||
运行 executable:
|
运行 executable:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./dist/gateway-checker probes.yaml
|
./dist/dial-server probes.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
## 运行参数
|
## 运行参数
|
||||||
@@ -246,7 +246,7 @@ bun run build
|
|||||||
CLI 只接受一个参数:YAML 配置文件路径。
|
CLI 只接受一个参数:YAML 配置文件路径。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./dist/gateway-checker ./probes.yaml
|
./dist/dial-server ./probes.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
## 测试
|
## 测试
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
系统 SHALL 通过单一命令行参数接受 YAML 配置文件路径。
|
系统 SHALL 通过单一命令行参数接受 YAML 配置文件路径。
|
||||||
|
|
||||||
#### Scenario: 指定配置文件启动
|
#### Scenario: 指定配置文件启动
|
||||||
- **WHEN** 用户执行 `./gateway-checker ./probes.yaml`
|
- **WHEN** 用户执行 `./dial-server ./probes.yaml`
|
||||||
- **THEN** 系统 SHALL 读取并解析指定路径的 YAML 文件作为配置
|
- **THEN** 系统 SHALL 读取并解析指定路径的 YAML 文件作为配置
|
||||||
|
|
||||||
#### Scenario: 未提供配置文件路径
|
#### Scenario: 未提供配置文件路径
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "gateway-checker",
|
"name": "dial-server",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -99,13 +99,13 @@ targets:
|
|||||||
http:
|
http:
|
||||||
url: "https://httpbin.org/headers"
|
url: "https://httpbin.org/headers"
|
||||||
headers:
|
headers:
|
||||||
X-Custom-Header: "gateway-checker"
|
X-Custom-Header: "dial-server"
|
||||||
expect:
|
expect:
|
||||||
status: [200]
|
status: [200]
|
||||||
body:
|
body:
|
||||||
- json:
|
- json:
|
||||||
path: "$.headers.X-Custom-Header"
|
path: "$.headers.X-Custom-Header"
|
||||||
equals: "gateway-checker"
|
equals: "dial-server"
|
||||||
|
|
||||||
- name: "响应头自定义校验"
|
- name: "响应头自定义校验"
|
||||||
type: http
|
type: http
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { $ } from "bun";
|
|||||||
|
|
||||||
const buildDir = fileURLToPath(new URL("../.build/", import.meta.url));
|
const buildDir = fileURLToPath(new URL("../.build/", import.meta.url));
|
||||||
const webDistDir = fileURLToPath(new URL("../dist/web/", import.meta.url));
|
const webDistDir = fileURLToPath(new URL("../dist/web/", import.meta.url));
|
||||||
const executablePath = fileURLToPath(new URL("../dist/gateway-checker", import.meta.url));
|
const executablePath = fileURLToPath(new URL("../dist/dial-server", import.meta.url));
|
||||||
const generatedAssetsPath = fileURLToPath(new URL("../.build/static-assets.ts", import.meta.url));
|
const generatedAssetsPath = fileURLToPath(new URL("../.build/static-assets.ts", import.meta.url));
|
||||||
const generatedEntryPath = fileURLToPath(new URL("../.build/server-entry.ts", import.meta.url));
|
const generatedEntryPath = fileURLToPath(new URL("../.build/server-entry.ts", import.meta.url));
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import { join } from "node:path";
|
|||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import type { HealthResponse, SummaryResponse } from "../src/shared/api";
|
import type { HealthResponse, SummaryResponse } from "../src/shared/api";
|
||||||
|
|
||||||
const executablePath = process.argv[2] ?? fileURLToPath(new URL("../dist/gateway-checker", import.meta.url));
|
const executablePath = process.argv[2] ?? fileURLToPath(new URL("../dist/dial-server", import.meta.url));
|
||||||
|
|
||||||
await assertExecutableExists(executablePath);
|
await assertExecutableExists(executablePath);
|
||||||
|
|
||||||
const tempDir = mkdtempSync(join(tmpdir(), "gc-smoke-"));
|
const tempDir = mkdtempSync(join(tmpdir(), "dial-smoke-"));
|
||||||
const configPath = join(tempDir, "probes.yaml");
|
const configPath = join(tempDir, "probes.yaml");
|
||||||
|
|
||||||
const port = await getFreePort();
|
const port = await getFreePort();
|
||||||
@@ -61,11 +61,11 @@ try {
|
|||||||
assert(missingTarget.status === 404, "不存在的目标应返回 404");
|
assert(missingTarget.status === 404, "不存在的目标应返回 404");
|
||||||
|
|
||||||
const { body: rootHtml, response: rootResponse } = await expectText(`${baseUrl}/`, 200);
|
const { body: rootHtml, response: rootResponse } = await expectText(`${baseUrl}/`, 200);
|
||||||
assert(rootHtml.includes("Gateway Checker"), "前端根页面缺少标题");
|
assert(rootHtml.includes("DiAL"), "前端根页面缺少标题");
|
||||||
assert(rootResponse.headers.get("cache-control") === "no-cache", "前端根页面应使用 no-cache");
|
assert(rootResponse.headers.get("cache-control") === "no-cache", "前端根页面应使用 no-cache");
|
||||||
|
|
||||||
const { body: fallbackHtml } = await expectText(`${baseUrl}/dashboard`, 200);
|
const { body: fallbackHtml } = await expectText(`${baseUrl}/dashboard`, 200);
|
||||||
assert(fallbackHtml.includes("Gateway Checker"), "SPA fallback 未返回前端入口页面");
|
assert(fallbackHtml.includes("DiAL"), "SPA fallback 未返回前端入口页面");
|
||||||
|
|
||||||
const assetPath = rootHtml.match(/(?:src|href)="(\/assets\/[^"]+)"/)?.[1];
|
const assetPath = rootHtml.match(/(?:src|href)="(\/assets\/[^"]+)"/)?.[1];
|
||||||
assert(assetPath !== undefined, "前端入口页面未引用 /assets/* 资源");
|
assert(assetPath !== undefined, "前端入口页面未引用 /assets/* 资源");
|
||||||
@@ -74,7 +74,7 @@ try {
|
|||||||
assert(asset.status === 200, `静态资源 ${assetPath} 未返回 200`);
|
assert(asset.status === 200, `静态资源 ${assetPath} 未返回 200`);
|
||||||
|
|
||||||
const missingAsset = await expectText(`${baseUrl}/assets/not-found.js`, 404);
|
const missingAsset = await expectText(`${baseUrl}/assets/not-found.js`, 404);
|
||||||
assert(!missingAsset.body.includes("Gateway Checker"), "未知静态资源不应返回前端入口页面");
|
assert(!missingAsset.body.includes("DiAL"), "未知静态资源不应返回前端入口页面");
|
||||||
|
|
||||||
console.log(`Smoke test passed: ${baseUrl}`);
|
console.log(`Smoke test passed: ${baseUrl}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ function formatDuration(ms: number): string {
|
|||||||
function createHealthResponse(): HealthResponse {
|
function createHealthResponse(): HealthResponse {
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
service: "gateway-checker",
|
service: "dial-server",
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export function readRuntimeConfig(argv: string[] = process.argv.slice(2)): { configPath: string } {
|
export function readRuntimeConfig(argv: string[] = process.argv.slice(2)): { configPath: string } {
|
||||||
if (argv.length === 0) {
|
if (argv.length === 0) {
|
||||||
throw new Error("需要指定 YAML 配置文件路径\n用法: gateway-checker <config.yaml>");
|
throw new Error("需要指定 YAML 配置文件路径\n用法: dial-server <config.yaml>");
|
||||||
}
|
}
|
||||||
|
|
||||||
return { configPath: argv[0]! };
|
return { configPath: argv[0]! };
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export function startServer(options: StartServerOptions) {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Gateway Checker listening on ${server.url}`);
|
console.log(`DiAL listening on ${server.url}`);
|
||||||
|
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ export type RuntimeMode = "development" | "production" | "test";
|
|||||||
|
|
||||||
export interface HealthResponse {
|
export interface HealthResponse {
|
||||||
ok: true;
|
ok: true;
|
||||||
service: "gateway-checker";
|
service: "dial-server";
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ export function App() {
|
|||||||
return (
|
return (
|
||||||
<main className="dashboard">
|
<main className="dashboard">
|
||||||
<header className="dashboard-header">
|
<header className="dashboard-header">
|
||||||
<h1>Gateway Checker</h1>
|
<h1>DiAL</h1>
|
||||||
<p className="dashboard-subtitle">HTTP 拨测监控面板</p>
|
<p className="dashboard-subtitle">统一拨测平台</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{error && <div className="error-banner">请求失败: {error},将在下一次轮询周期自动重试</div>}
|
{error && <div className="error-banner">请求失败: {error},将在下一次轮询周期自动重试</div>}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="description" content="Gateway Checker - HTTP 拨测监控面板" />
|
<meta name="description" content="DiAL - 统一拨测平台" />
|
||||||
<title>Gateway Checker</title>
|
<title>DiAL</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { join } from "node:path";
|
|||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
|
|
||||||
const staticAssets: StaticAssets = {
|
const staticAssets: StaticAssets = {
|
||||||
indexHtml: new Blob(['<!doctype html><title>Gateway Checker</title><div id="root"></div>'], {
|
indexHtml: new Blob(['<!doctype html><title>DiAL</title><div id="root"></div>'], {
|
||||||
type: "text/html",
|
type: "text/html",
|
||||||
}),
|
}),
|
||||||
files: {
|
files: {
|
||||||
@@ -21,7 +21,7 @@ describe("API 路由", () => {
|
|||||||
let fetchHandler: ReturnType<typeof createFetchHandler>;
|
let fetchHandler: ReturnType<typeof createFetchHandler>;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
tempDir = join(tmpdir(), `gc-api-test-${Date.now()}`);
|
tempDir = join(tmpdir(), `dial-api-test-${Date.now()}`);
|
||||||
await mkdir(tempDir, { recursive: true });
|
await mkdir(tempDir, { recursive: true });
|
||||||
store = new ProbeStore(join(tempDir, "test.db"));
|
store = new ProbeStore(join(tempDir, "test.db"));
|
||||||
store.syncTargets([
|
store.syncTargets([
|
||||||
@@ -93,7 +93,7 @@ describe("API 路由", () => {
|
|||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
expect(body.ok).toBe(true);
|
expect(body.ok).toBe(true);
|
||||||
expect(body.service).toBe("gateway-checker");
|
expect(body.service).toBe("dial-server");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("/api/summary 返回总览统计", async () => {
|
test("/api/summary 返回总览统计", async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user