mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-04 15:06:37 +08:00
feat: add 24-hour traffic and capacity trend charts to dashboard and node detail pages
- Implemented TrendChart component for visualizing traffic and capacity trends. - Enhanced DashboardOverview and NodeDetailPage components to display 24-hour request and capacity trends. - Updated types to include traffic and capacity trend data structures. - Created observability trends service to aggregate traffic and capacity data. - Added tests for traffic report generation and trend data handling. - Introduced new dependencies for charting (echarts and echarts-for-react).
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import Link from 'next/link';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { TrendChart } from '@/components/data/trend-chart';
|
||||
import { EmptyState } from '@/components/feedback/empty-state';
|
||||
import { ErrorState } from '@/components/feedback/error-state';
|
||||
import { LoadingState } from '@/components/feedback/loading-state';
|
||||
@@ -32,6 +33,14 @@ function getErrorMessage(error: unknown) {
|
||||
return error instanceof Error ? error.message : '请求失败,请稍后重试。';
|
||||
}
|
||||
|
||||
function formatTrendHour(value: string) {
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return '—';
|
||||
}
|
||||
return `${date.getHours().toString().padStart(2, '0')}:00`;
|
||||
}
|
||||
|
||||
function getAlertVariant(
|
||||
alert: DashboardAlert,
|
||||
): 'success' | 'warning' | 'danger' | 'info' {
|
||||
@@ -215,6 +224,68 @@ export function DashboardOverview() {
|
||||
</div>
|
||||
</AppCard>
|
||||
|
||||
<div className="grid gap-6 xl:grid-cols-2">
|
||||
<AppCard
|
||||
title="24 小时请求趋势"
|
||||
description="观察整体请求量和错误量是否出现异常抬升。"
|
||||
>
|
||||
<TrendChart
|
||||
labels={overview.trends.traffic_24h.map((point) =>
|
||||
formatTrendHour(point.bucket_started_at),
|
||||
)}
|
||||
series={[
|
||||
{
|
||||
label: '请求量',
|
||||
color: '#f59e0b',
|
||||
fillColor: 'rgba(245, 158, 11, 0.18)',
|
||||
variant: 'area',
|
||||
values: overview.trends.traffic_24h.map(
|
||||
(point) => point.request_count,
|
||||
),
|
||||
},
|
||||
{
|
||||
label: '错误量',
|
||||
color: '#ef4444',
|
||||
values: overview.trends.traffic_24h.map(
|
||||
(point) => point.error_count,
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</AppCard>
|
||||
|
||||
<AppCard
|
||||
title="24 小时容量趋势"
|
||||
description="按小时聚合 CPU 与内存使用率,判断整体容量是否持续紧张。"
|
||||
>
|
||||
<TrendChart
|
||||
labels={overview.trends.capacity_24h.map((point) =>
|
||||
formatTrendHour(point.bucket_started_at),
|
||||
)}
|
||||
series={[
|
||||
{
|
||||
label: '平均 CPU',
|
||||
color: '#0f766e',
|
||||
fillColor: 'rgba(15, 118, 110, 0.15)',
|
||||
variant: 'area',
|
||||
values: overview.trends.capacity_24h.map(
|
||||
(point) => point.average_cpu_usage_percent,
|
||||
),
|
||||
valueFormatter: formatPercent,
|
||||
},
|
||||
{
|
||||
label: '平均内存',
|
||||
color: '#2563eb',
|
||||
values: overview.trends.capacity_24h.map(
|
||||
(point) => point.average_memory_usage_percent,
|
||||
),
|
||||
valueFormatter: formatPercent,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</AppCard>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 xl:grid-cols-[1.2fr_0.8fr]">
|
||||
<AppCard
|
||||
title="节点健康列表"
|
||||
|
||||
@@ -30,6 +30,25 @@ export interface DashboardConfig {
|
||||
pending_nodes: number;
|
||||
}
|
||||
|
||||
export interface TrafficTrendPoint {
|
||||
bucket_started_at: string;
|
||||
request_count: number;
|
||||
error_count: number;
|
||||
unique_visitor_count: number;
|
||||
}
|
||||
|
||||
export interface CapacityTrendPoint {
|
||||
bucket_started_at: string;
|
||||
average_cpu_usage_percent: number;
|
||||
average_memory_usage_percent: number;
|
||||
reported_nodes: number;
|
||||
}
|
||||
|
||||
export interface DashboardTrends {
|
||||
traffic_24h: TrafficTrendPoint[];
|
||||
capacity_24h: CapacityTrendPoint[];
|
||||
}
|
||||
|
||||
export interface DashboardNodeHealth {
|
||||
id: number;
|
||||
node_id: string;
|
||||
@@ -63,6 +82,7 @@ export interface DashboardOverview {
|
||||
traffic: DashboardTraffic;
|
||||
capacity: DashboardCapacity;
|
||||
config: DashboardConfig;
|
||||
trends: DashboardTrends;
|
||||
nodes: DashboardNodeHealth[];
|
||||
active_alerts: DashboardAlert[];
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useEffect, useMemo, useState } from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { TrendChart } from '@/components/data/trend-chart';
|
||||
import { EmptyState } from '@/components/feedback/empty-state';
|
||||
import { ErrorState } from '@/components/feedback/error-state';
|
||||
import { InlineMessage } from '@/components/feedback/inline-message';
|
||||
@@ -143,6 +144,14 @@ function formatUptime(seconds?: number | null) {
|
||||
return `${minutes} 分钟`;
|
||||
}
|
||||
|
||||
function formatTrendHour(value: string) {
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return '—';
|
||||
}
|
||||
return `${date.getHours().toString().padStart(2, '0')}:00`;
|
||||
}
|
||||
|
||||
function getHealthEventVariant(
|
||||
event: NodeObservability['health_events'][number],
|
||||
): 'success' | 'warning' | 'danger' | 'info' {
|
||||
@@ -829,6 +838,76 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) {
|
||||
</AppCard>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 xl:grid-cols-2">
|
||||
<AppCard
|
||||
title="24 小时请求趋势"
|
||||
description="按小时聚合该节点的请求量和错误量,判断是否存在突发流量或持续异常。"
|
||||
>
|
||||
<TrendChart
|
||||
labels={
|
||||
observability?.trends.traffic_24h.map((point) =>
|
||||
formatTrendHour(point.bucket_started_at),
|
||||
) ?? []
|
||||
}
|
||||
series={[
|
||||
{
|
||||
label: '请求量',
|
||||
color: '#f59e0b',
|
||||
fillColor: 'rgba(245, 158, 11, 0.18)',
|
||||
variant: 'area',
|
||||
values:
|
||||
observability?.trends.traffic_24h.map(
|
||||
(point) => point.request_count,
|
||||
) ?? [],
|
||||
},
|
||||
{
|
||||
label: '错误量',
|
||||
color: '#ef4444',
|
||||
values:
|
||||
observability?.trends.traffic_24h.map(
|
||||
(point) => point.error_count,
|
||||
) ?? [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</AppCard>
|
||||
|
||||
<AppCard
|
||||
title="24 小时容量趋势"
|
||||
description="观察该节点 CPU 与内存使用率在 24 小时内的变化,辅助扩容和排障。"
|
||||
>
|
||||
<TrendChart
|
||||
labels={
|
||||
observability?.trends.capacity_24h.map((point) =>
|
||||
formatTrendHour(point.bucket_started_at),
|
||||
) ?? []
|
||||
}
|
||||
series={[
|
||||
{
|
||||
label: '平均 CPU',
|
||||
color: '#0f766e',
|
||||
fillColor: 'rgba(15, 118, 110, 0.15)',
|
||||
variant: 'area',
|
||||
values:
|
||||
observability?.trends.capacity_24h.map(
|
||||
(point) => point.average_cpu_usage_percent,
|
||||
) ?? [],
|
||||
valueFormatter: formatPercent,
|
||||
},
|
||||
{
|
||||
label: '平均内存',
|
||||
color: '#2563eb',
|
||||
values:
|
||||
observability?.trends.capacity_24h.map(
|
||||
(point) => point.average_memory_usage_percent,
|
||||
) ?? [],
|
||||
valueFormatter: formatPercent,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</AppCard>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 xl:grid-cols-3">
|
||||
<AppCard title="更新模式">
|
||||
<div className="space-y-3">
|
||||
|
||||
@@ -99,6 +99,25 @@ export interface NodeTrafficReport {
|
||||
source_countries_json: string;
|
||||
}
|
||||
|
||||
export interface NodeTrafficTrendPoint {
|
||||
bucket_started_at: string;
|
||||
request_count: number;
|
||||
error_count: number;
|
||||
unique_visitor_count: number;
|
||||
}
|
||||
|
||||
export interface NodeCapacityTrendPoint {
|
||||
bucket_started_at: string;
|
||||
average_cpu_usage_percent: number;
|
||||
average_memory_usage_percent: number;
|
||||
reported_nodes: number;
|
||||
}
|
||||
|
||||
export interface NodeObservabilityTrends {
|
||||
traffic_24h: NodeTrafficTrendPoint[];
|
||||
capacity_24h: NodeCapacityTrendPoint[];
|
||||
}
|
||||
|
||||
export interface NodeHealthEvent {
|
||||
event_type: string;
|
||||
severity: string;
|
||||
@@ -116,4 +135,5 @@ export interface NodeObservability {
|
||||
metric_snapshots: NodeMetricSnapshot[];
|
||||
traffic_reports: NodeTrafficReport[];
|
||||
health_events: NodeHealthEvent[];
|
||||
trends: NodeObservabilityTrends;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user