feat: 重构为多类型 checker 通用框架,支持 HTTP 与命令检查
- 引入 typed target 判别联合,支持 http 与 command 两种 checker - expect 重构为有序规则数组,按配置顺序快速失败并生成结构化 failure - 新增 command runner,支持 exec + args 本地命令执行 - 引入全局并发限制 maxConcurrentChecks 和 size 解析 (KB/MB/GB) - HTTP/command 各自独立 expect pipeline,应用领域默认成功语义 - SQLite schema、API、Dashboard 全链路调整为 checker 通用契约 - 补充完整测试覆盖(192 tests),更新 README 与示例配置
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Line, LineChart, ResponsiveContainer } from "recharts";
|
||||
|
||||
interface SparklineChartProps {
|
||||
data: Array<{ latency: number }>;
|
||||
data: Array<{ duration: number }>;
|
||||
}
|
||||
|
||||
export function SparklineChart({ data }: SparklineChartProps) {
|
||||
@@ -12,7 +12,7 @@ export function SparklineChart({ data }: SparklineChartProps) {
|
||||
return (
|
||||
<ResponsiveContainer width={80} height={32}>
|
||||
<LineChart data={data}>
|
||||
<Line type="monotone" dataKey="latency" stroke="#356dd2" strokeWidth={1.5} dot={false} />
|
||||
<Line type="monotone" dataKey="duration" stroke="#356dd2" strokeWidth={1.5} dot={false} />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
|
||||
@@ -17,8 +17,8 @@ export function SummaryCards({ summary, loading }: SummaryCardsProps) {
|
||||
{ label: "正常", value: summary.up, className: "card-up" },
|
||||
{ label: "异常", value: summary.down, className: "card-down" },
|
||||
{
|
||||
label: "平均延迟",
|
||||
value: summary.avgLatencyMs !== null ? `${Math.round(summary.avgLatencyMs)}ms` : "-",
|
||||
label: "平均耗时",
|
||||
value: summary.avgDurationMs !== null ? `${Math.round(summary.avgDurationMs)}ms` : "-",
|
||||
className: "card-latency",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -26,8 +26,9 @@ export function TargetDetail({ target }: TargetDetailProps) {
|
||||
}, [target.id]);
|
||||
|
||||
useEffect(() => {
|
||||
void fetchTrend();
|
||||
void fetchHistory();
|
||||
fetchTrend();
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
fetchHistory();
|
||||
}, [fetchTrend, fetchHistory]);
|
||||
|
||||
const { stats } = target;
|
||||
@@ -49,15 +50,15 @@ export function TargetDetail({ target }: TargetDetailProps) {
|
||||
</span>
|
||||
</div>
|
||||
<div className="detail-stat">
|
||||
<span className="detail-stat-label">平均延迟</span>
|
||||
<span className="detail-stat-label">平均耗时</span>
|
||||
<span className="detail-stat-value">
|
||||
{stats.avgLatencyMs !== null ? `${Math.round(stats.avgLatencyMs)}ms` : "-"}
|
||||
{stats.avgDurationMs !== null ? `${Math.round(stats.avgDurationMs)}ms` : "-"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="detail-stat">
|
||||
<span className="detail-stat-label">P99 延迟</span>
|
||||
<span className="detail-stat-label">P99 耗时</span>
|
||||
<span className="detail-stat-value">
|
||||
{stats.p99LatencyMs !== null ? `${Math.round(stats.p99LatencyMs)}ms` : "-"}
|
||||
{stats.p99DurationMs !== null ? `${Math.round(stats.p99DurationMs)}ms` : "-"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -79,9 +80,11 @@ export function TargetDetail({ target }: TargetDetailProps) {
|
||||
{item.success && item.matched ? "UP" : "DOWN"}
|
||||
</span>
|
||||
<span className="history-time">{new Date(item.timestamp).toLocaleString("zh-CN")}</span>
|
||||
{item.statusCode && <span className="history-code">{item.statusCode}</span>}
|
||||
{item.latencyMs !== null && <span className="history-latency">{Math.round(item.latencyMs)}ms</span>}
|
||||
{item.error && <span className="history-error">{item.error}</span>}
|
||||
{item.statusDetail && <span className="history-code">{item.statusDetail}</span>}
|
||||
{item.durationMs !== null && (
|
||||
<span className="history-latency">{Math.round(item.durationMs)}ms</span>
|
||||
)}
|
||||
{item.failure?.message && <span className="history-error">{item.failure.message}</span>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ interface TargetRowProps {
|
||||
export function TargetRow({ target, expanded, onToggle }: TargetRowProps) {
|
||||
const isUp = target.latestCheck?.success && target.latestCheck?.matched;
|
||||
|
||||
const sparklineData = target.sparkline.map((latency) => ({ latency }));
|
||||
const sparklineData = target.sparkline.map((duration) => ({ duration }));
|
||||
|
||||
return (
|
||||
<tr className={`target-row ${expanded ? "expanded" : ""}`} onClick={onToggle}>
|
||||
@@ -19,11 +19,11 @@ export function TargetRow({ target, expanded, onToggle }: TargetRowProps) {
|
||||
<StatusDot up={!!isUp} />
|
||||
</td>
|
||||
<td className="col-name">{target.name}</td>
|
||||
<td className="col-url">{target.url}</td>
|
||||
<td className="col-method">{target.method}</td>
|
||||
<td className="col-latency">
|
||||
{target.latestCheck?.latencyMs !== null && target.latestCheck?.latencyMs !== undefined
|
||||
? `${Math.round(target.latestCheck.latencyMs)}ms`
|
||||
<td className="col-target">{target.target}</td>
|
||||
<td className="col-type">{target.type === "http" ? "HTTP" : "Command"}</td>
|
||||
<td className="col-duration">
|
||||
{target.latestCheck?.durationMs !== null && target.latestCheck?.durationMs !== undefined
|
||||
? `${Math.round(target.latestCheck.durationMs)}ms`
|
||||
: "-"}
|
||||
</td>
|
||||
<td className="col-sparkline">
|
||||
|
||||
@@ -25,9 +25,9 @@ export function TargetTable({ targets, loading }: TargetTableProps) {
|
||||
<tr>
|
||||
<th className="col-status">状态</th>
|
||||
<th className="col-name">名称</th>
|
||||
<th className="col-url">URL</th>
|
||||
<th className="col-method">方法</th>
|
||||
<th className="col-latency">延迟</th>
|
||||
<th className="col-target">目标</th>
|
||||
<th className="col-type">类型</th>
|
||||
<th className="col-duration">耗时</th>
|
||||
<th className="col-sparkline">趋势</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -27,7 +27,7 @@ export function TrendChart({ data, loading }: TrendChartProps) {
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
|
||||
<XAxis dataKey="hour" tick={{ fontSize: 12 }} stroke="#94a3b8" />
|
||||
<YAxis
|
||||
yAxisId="latency"
|
||||
yAxisId="duration"
|
||||
tick={{ fontSize: 12 }}
|
||||
stroke="#94a3b8"
|
||||
label={{ value: "ms", position: "insideTopRight", fontSize: 11 }}
|
||||
@@ -44,19 +44,19 @@ export function TrendChart({ data, loading }: TrendChartProps) {
|
||||
formatter={(value: unknown, name: unknown) => {
|
||||
const num = Number(value);
|
||||
const nameStr = String(name);
|
||||
if (nameStr === "avgLatencyMs") return [`${Math.round(num)}ms`, "平均延迟"];
|
||||
if (nameStr === "avgDurationMs") return [`${Math.round(num)}ms`, "平均耗时"];
|
||||
if (nameStr === "availability") return [`${num.toFixed(1)}%`, "可用率"];
|
||||
return [String(value), nameStr];
|
||||
}}
|
||||
/>
|
||||
<Line
|
||||
yAxisId="latency"
|
||||
yAxisId="duration"
|
||||
type="monotone"
|
||||
dataKey="avgLatencyMs"
|
||||
dataKey="avgDurationMs"
|
||||
stroke="#356dd2"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
name="avgLatencyMs"
|
||||
name="avgDurationMs"
|
||||
/>
|
||||
<Line
|
||||
yAxisId="availability"
|
||||
|
||||
@@ -29,7 +29,8 @@ export function useSummary(intervalMs = 8000) {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void fetchSummary();
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
fetchSummary();
|
||||
const timer = setInterval(fetchSummary, intervalMs);
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
|
||||
@@ -29,7 +29,8 @@ export function useTargets(intervalMs = 8000) {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void fetchTargets();
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
fetchTargets();
|
||||
const timer = setInterval(fetchTargets, intervalMs);
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
|
||||
@@ -141,7 +141,7 @@ body {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.col-url {
|
||||
.col-target {
|
||||
color: #61728a;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
font-size: 0.82rem;
|
||||
@@ -151,12 +151,12 @@ body {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.col-method {
|
||||
width: 64px;
|
||||
.col-type {
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.col-latency {
|
||||
.col-duration {
|
||||
width: 80px;
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
@@ -317,7 +317,7 @@ body {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.col-method,
|
||||
.col-type,
|
||||
.col-sparkline {
|
||||
display: none;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user