mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-11 09:46:37 +08:00
[功能] 优化仪表板概览数据结构,添加压缩和规范化功能
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"server_url": "http://127.0.0.1:3000",
|
"server_url": "http://127.0.0.1:3000",
|
||||||
"agent_token": "89d0efbf7bcc8fa53fe48889dce4d045",
|
"agent_token": "2380de64b00e99093e16590beb91e1a0",
|
||||||
"data_dir": "./data",
|
"data_dir": "./data",
|
||||||
"openresty_container_name": "openflare-openresty",
|
"openresty_container_name": "openflare-openresty",
|
||||||
"openresty_docker_image": "openresty/openresty:alpine",
|
"openresty_docker_image": "openresty/openresty:alpine",
|
||||||
|
|||||||
@@ -6,6 +6,29 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type dashboardOverviewPayload struct {
|
||||||
|
GeneratedAt any `json:"generated_at"`
|
||||||
|
Summary service.DashboardSummary `json:"summary"`
|
||||||
|
Traffic service.DashboardTraffic `json:"traffic"`
|
||||||
|
Capacity service.DashboardCapacity `json:"capacity"`
|
||||||
|
Distributions dashboardDistributionsPayload `json:"distributions"`
|
||||||
|
Trends dashboardTrendsPayload `json:"trends"`
|
||||||
|
Nodes [][]any `json:"nodes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type dashboardDistributionsPayload struct {
|
||||||
|
StatusCodes [][]any `json:"status_codes"`
|
||||||
|
TopDomains [][]any `json:"top_domains"`
|
||||||
|
SourceCountries [][]any `json:"source_countries"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type dashboardTrendsPayload struct {
|
||||||
|
Traffic24h [][]any `json:"traffic_24h"`
|
||||||
|
Capacity24h [][]any `json:"capacity_24h"`
|
||||||
|
Network24h [][]any `json:"network_24h"`
|
||||||
|
DiskIO24h [][]any `json:"disk_io_24h"`
|
||||||
|
}
|
||||||
|
|
||||||
// GetDashboardOverview godoc
|
// GetDashboardOverview godoc
|
||||||
// @Summary Get dashboard overview
|
// @Summary Get dashboard overview
|
||||||
// @Tags Dashboard
|
// @Tags Dashboard
|
||||||
@@ -20,5 +43,130 @@ func GetDashboardOverview(c *gin.Context) {
|
|||||||
respondFailure(c, err.Error())
|
respondFailure(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
respondSuccess(c, view)
|
respondSuccess(c, compressDashboardOverview(view))
|
||||||
|
}
|
||||||
|
|
||||||
|
func compressDashboardOverview(view *service.DashboardOverviewView) *dashboardOverviewPayload {
|
||||||
|
if view == nil {
|
||||||
|
return &dashboardOverviewPayload{
|
||||||
|
Distributions: dashboardDistributionsPayload{
|
||||||
|
StatusCodes: [][]any{},
|
||||||
|
TopDomains: [][]any{},
|
||||||
|
SourceCountries: [][]any{},
|
||||||
|
},
|
||||||
|
Trends: dashboardTrendsPayload{
|
||||||
|
Traffic24h: [][]any{},
|
||||||
|
Capacity24h: [][]any{},
|
||||||
|
Network24h: [][]any{},
|
||||||
|
DiskIO24h: [][]any{},
|
||||||
|
},
|
||||||
|
Nodes: [][]any{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &dashboardOverviewPayload{
|
||||||
|
GeneratedAt: view.GeneratedAt,
|
||||||
|
Summary: view.Summary,
|
||||||
|
Traffic: view.Traffic,
|
||||||
|
Capacity: view.Capacity,
|
||||||
|
Distributions: dashboardDistributionsPayload{
|
||||||
|
StatusCodes: compressDistributionItems(view.Distributions.StatusCodes),
|
||||||
|
TopDomains: compressDistributionItems(view.Distributions.TopDomains),
|
||||||
|
SourceCountries: compressDistributionItems(view.Distributions.SourceCountries),
|
||||||
|
},
|
||||||
|
Trends: dashboardTrendsPayload{
|
||||||
|
Traffic24h: compressTrafficTrendPoints(view.Trends.Traffic24h),
|
||||||
|
Capacity24h: compressCapacityTrendPoints(view.Trends.Capacity24h),
|
||||||
|
Network24h: compressNetworkTrendPoints(view.Trends.Network24h),
|
||||||
|
DiskIO24h: compressDiskIOTrendPoints(view.Trends.DiskIO24h),
|
||||||
|
},
|
||||||
|
Nodes: compressDashboardNodes(view.Nodes),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compressDistributionItems(items []service.DistributionItem) [][]any {
|
||||||
|
rows := make([][]any, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
rows = append(rows, []any{item.Key, item.Value})
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
func compressTrafficTrendPoints(points []service.TrafficTrendPoint) [][]any {
|
||||||
|
rows := make([][]any, 0, len(points))
|
||||||
|
for _, point := range points {
|
||||||
|
rows = append(rows, []any{
|
||||||
|
point.BucketStartedAt,
|
||||||
|
point.RequestCount,
|
||||||
|
point.ErrorCount,
|
||||||
|
point.UniqueVisitorCount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
func compressCapacityTrendPoints(points []service.CapacityTrendPoint) [][]any {
|
||||||
|
rows := make([][]any, 0, len(points))
|
||||||
|
for _, point := range points {
|
||||||
|
rows = append(rows, []any{
|
||||||
|
point.BucketStartedAt,
|
||||||
|
point.AverageCPUUsagePercent,
|
||||||
|
point.AverageMemoryUsagePercent,
|
||||||
|
point.ReportedNodes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
func compressNetworkTrendPoints(points []service.NetworkTrendPoint) [][]any {
|
||||||
|
rows := make([][]any, 0, len(points))
|
||||||
|
for _, point := range points {
|
||||||
|
rows = append(rows, []any{
|
||||||
|
point.BucketStartedAt,
|
||||||
|
point.NetworkRxBytes,
|
||||||
|
point.NetworkTxBytes,
|
||||||
|
point.OpenrestyRxBytes,
|
||||||
|
point.OpenrestyTxBytes,
|
||||||
|
point.ReportedNodes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
func compressDiskIOTrendPoints(points []service.DiskIOTrendPoint) [][]any {
|
||||||
|
rows := make([][]any, 0, len(points))
|
||||||
|
for _, point := range points {
|
||||||
|
rows = append(rows, []any{
|
||||||
|
point.BucketStartedAt,
|
||||||
|
point.DiskReadBytes,
|
||||||
|
point.DiskWriteBytes,
|
||||||
|
point.ReportedNodes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
func compressDashboardNodes(nodes []service.DashboardNodeHealth) [][]any {
|
||||||
|
rows := make([][]any, 0, len(nodes))
|
||||||
|
for _, node := range nodes {
|
||||||
|
rows = append(rows, []any{
|
||||||
|
node.ID,
|
||||||
|
node.NodeID,
|
||||||
|
node.Name,
|
||||||
|
node.GeoName,
|
||||||
|
node.GeoLatitude,
|
||||||
|
node.GeoLongitude,
|
||||||
|
node.Status,
|
||||||
|
node.OpenrestyStatus,
|
||||||
|
node.CurrentVersion,
|
||||||
|
node.LastSeenAt,
|
||||||
|
node.ActiveEventCount,
|
||||||
|
node.CPUUsagePercent,
|
||||||
|
node.MemoryUsagePercent,
|
||||||
|
node.StorageUsagePercent,
|
||||||
|
node.RequestCount,
|
||||||
|
node.ErrorCount,
|
||||||
|
node.UniqueVisitorCount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return rows
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,7 +170,9 @@ func TestPhase2AgentLifecycle(t *testing.T) {
|
|||||||
createRouteAndPublishVersion(t, engine, adminToken)
|
createRouteAndPublishVersion(t, engine, adminToken)
|
||||||
|
|
||||||
dashboardResp := performJSONRequest(t, engine, adminToken, http.MethodGet, "/api/dashboard/overview", nil)
|
dashboardResp := performJSONRequest(t, engine, adminToken, http.MethodGet, "/api/dashboard/overview", nil)
|
||||||
var dashboard service.DashboardOverviewView
|
var dashboard struct {
|
||||||
|
Summary service.DashboardSummary `json:"summary"`
|
||||||
|
}
|
||||||
decodeResponseData(t, dashboardResp, &dashboard)
|
decodeResponseData(t, dashboardResp, &dashboard)
|
||||||
if dashboard.Summary.TotalNodes != 0 {
|
if dashboard.Summary.TotalNodes != 0 {
|
||||||
t.Fatalf("expected empty dashboard node summary before node registration, got %+v", dashboard.Summary)
|
t.Fatalf("expected empty dashboard node summary before node registration, got %+v", dashboard.Summary)
|
||||||
|
|||||||
@@ -1,38 +1,229 @@
|
|||||||
import { apiRequest } from '@/lib/api/client';
|
import { apiRequest } from '@/lib/api/client';
|
||||||
|
|
||||||
import type { DashboardOverview } from '@/features/dashboard/types';
|
import type {
|
||||||
|
CompactCapacityTrendPoint,
|
||||||
|
CompactDashboardNodeHealth,
|
||||||
|
CompactDiskIOTrendPoint,
|
||||||
|
CompactDistributionItem,
|
||||||
|
CompactNetworkTrendPoint,
|
||||||
|
CompactTrafficTrendPoint,
|
||||||
|
DashboardCapacity,
|
||||||
|
DashboardNodeHealth,
|
||||||
|
DashboardOverview,
|
||||||
|
DashboardOverviewCompact,
|
||||||
|
DashboardSummary,
|
||||||
|
DashboardTraffic,
|
||||||
|
DistributionItem,
|
||||||
|
} from '@/features/dashboard/types';
|
||||||
|
|
||||||
function arrayOrEmpty<T>(value: T[] | null | undefined) {
|
function arrayOrEmpty<T>(value: T[] | null | undefined) {
|
||||||
return Array.isArray(value) ? value : [];
|
return Array.isArray(value) ? value : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isCompactDistributionItem(
|
||||||
|
value: DistributionItem | CompactDistributionItem,
|
||||||
|
): value is CompactDistributionItem {
|
||||||
|
return Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCompactTrafficTrendPoint(
|
||||||
|
value: DashboardOverview['trends']['traffic_24h'][number] | CompactTrafficTrendPoint,
|
||||||
|
): value is CompactTrafficTrendPoint {
|
||||||
|
return Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCompactCapacityTrendPoint(
|
||||||
|
value:
|
||||||
|
| DashboardOverview['trends']['capacity_24h'][number]
|
||||||
|
| CompactCapacityTrendPoint,
|
||||||
|
): value is CompactCapacityTrendPoint {
|
||||||
|
return Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCompactNetworkTrendPoint(
|
||||||
|
value:
|
||||||
|
| DashboardOverview['trends']['network_24h'][number]
|
||||||
|
| CompactNetworkTrendPoint,
|
||||||
|
): value is CompactNetworkTrendPoint {
|
||||||
|
return Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCompactDiskIOTrendPoint(
|
||||||
|
value:
|
||||||
|
| DashboardOverview['trends']['disk_io_24h'][number]
|
||||||
|
| CompactDiskIOTrendPoint,
|
||||||
|
): value is CompactDiskIOTrendPoint {
|
||||||
|
return Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCompactDashboardNode(
|
||||||
|
value: DashboardNodeHealth | CompactDashboardNodeHealth,
|
||||||
|
): value is CompactDashboardNodeHealth {
|
||||||
|
return Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDistributionItems(
|
||||||
|
items: Array<DistributionItem | CompactDistributionItem> | null | undefined,
|
||||||
|
): DistributionItem[] {
|
||||||
|
return arrayOrEmpty(items).map((item) =>
|
||||||
|
isCompactDistributionItem(item)
|
||||||
|
? { key: String(item[0] ?? ''), value: Number(item[1] ?? 0) }
|
||||||
|
: item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeTrafficTrendPoints(
|
||||||
|
items:
|
||||||
|
| Array<DashboardOverview['trends']['traffic_24h'][number] | CompactTrafficTrendPoint>
|
||||||
|
| null
|
||||||
|
| undefined,
|
||||||
|
) {
|
||||||
|
return arrayOrEmpty(items).map((item) =>
|
||||||
|
isCompactTrafficTrendPoint(item)
|
||||||
|
? {
|
||||||
|
bucket_started_at: String(item[0] ?? ''),
|
||||||
|
request_count: Number(item[1] ?? 0),
|
||||||
|
error_count: Number(item[2] ?? 0),
|
||||||
|
unique_visitor_count: Number(item[3] ?? 0),
|
||||||
|
}
|
||||||
|
: item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCapacityTrendPoints(
|
||||||
|
items:
|
||||||
|
| Array<
|
||||||
|
| DashboardOverview['trends']['capacity_24h'][number]
|
||||||
|
| CompactCapacityTrendPoint
|
||||||
|
>
|
||||||
|
| null
|
||||||
|
| undefined,
|
||||||
|
) {
|
||||||
|
return arrayOrEmpty(items).map((item) =>
|
||||||
|
isCompactCapacityTrendPoint(item)
|
||||||
|
? {
|
||||||
|
bucket_started_at: String(item[0] ?? ''),
|
||||||
|
average_cpu_usage_percent: Number(item[1] ?? 0),
|
||||||
|
average_memory_usage_percent: Number(item[2] ?? 0),
|
||||||
|
reported_nodes: Number(item[3] ?? 0),
|
||||||
|
}
|
||||||
|
: item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeNetworkTrendPoints(
|
||||||
|
items:
|
||||||
|
| Array<
|
||||||
|
DashboardOverview['trends']['network_24h'][number] | CompactNetworkTrendPoint
|
||||||
|
>
|
||||||
|
| null
|
||||||
|
| undefined,
|
||||||
|
) {
|
||||||
|
return arrayOrEmpty(items).map((item) =>
|
||||||
|
isCompactNetworkTrendPoint(item)
|
||||||
|
? {
|
||||||
|
bucket_started_at: String(item[0] ?? ''),
|
||||||
|
network_rx_bytes: Number(item[1] ?? 0),
|
||||||
|
network_tx_bytes: Number(item[2] ?? 0),
|
||||||
|
openresty_rx_bytes: Number(item[3] ?? 0),
|
||||||
|
openresty_tx_bytes: Number(item[4] ?? 0),
|
||||||
|
reported_nodes: Number(item[5] ?? 0),
|
||||||
|
}
|
||||||
|
: item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDiskIOTrendPoints(
|
||||||
|
items:
|
||||||
|
| Array<
|
||||||
|
DashboardOverview['trends']['disk_io_24h'][number] | CompactDiskIOTrendPoint
|
||||||
|
>
|
||||||
|
| null
|
||||||
|
| undefined,
|
||||||
|
) {
|
||||||
|
return arrayOrEmpty(items).map((item) =>
|
||||||
|
isCompactDiskIOTrendPoint(item)
|
||||||
|
? {
|
||||||
|
bucket_started_at: String(item[0] ?? ''),
|
||||||
|
disk_read_bytes: Number(item[1] ?? 0),
|
||||||
|
disk_write_bytes: Number(item[2] ?? 0),
|
||||||
|
reported_nodes: Number(item[3] ?? 0),
|
||||||
|
}
|
||||||
|
: item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDashboardNodes(
|
||||||
|
items: Array<DashboardNodeHealth | CompactDashboardNodeHealth> | null | undefined,
|
||||||
|
): DashboardNodeHealth[] {
|
||||||
|
return arrayOrEmpty(items).map((item) =>
|
||||||
|
isCompactDashboardNode(item)
|
||||||
|
? {
|
||||||
|
id: Number(item[0] ?? 0),
|
||||||
|
node_id: String(item[1] ?? ''),
|
||||||
|
name: String(item[2] ?? ''),
|
||||||
|
geo_name: String(item[3] ?? ''),
|
||||||
|
geo_latitude:
|
||||||
|
item[4] === null || item[4] === undefined ? null : Number(item[4]),
|
||||||
|
geo_longitude:
|
||||||
|
item[5] === null || item[5] === undefined ? null : Number(item[5]),
|
||||||
|
status: (item[6] ?? 'pending') as DashboardNodeHealth['status'],
|
||||||
|
openresty_status: (item[7] ??
|
||||||
|
'unknown') as DashboardNodeHealth['openresty_status'],
|
||||||
|
current_version: String(item[8] ?? ''),
|
||||||
|
last_seen_at: String(item[9] ?? ''),
|
||||||
|
active_event_count: Number(item[10] ?? 0),
|
||||||
|
cpu_usage_percent: Number(item[11] ?? 0),
|
||||||
|
memory_usage_percent: Number(item[12] ?? 0),
|
||||||
|
storage_usage_percent: Number(item[13] ?? 0),
|
||||||
|
request_count: Number(item[14] ?? 0),
|
||||||
|
error_count: Number(item[15] ?? 0),
|
||||||
|
unique_visitor_count: Number(item[16] ?? 0),
|
||||||
|
}
|
||||||
|
: item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeDashboardOverview(
|
function normalizeDashboardOverview(
|
||||||
overview: DashboardOverview | null | undefined,
|
overview:
|
||||||
|
| DashboardOverview
|
||||||
|
| DashboardOverviewCompact
|
||||||
|
| null
|
||||||
|
| undefined,
|
||||||
): DashboardOverview | null {
|
): DashboardOverview | null {
|
||||||
if (!overview) {
|
if (!overview) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const summary = (overview.summary ?? {}) as DashboardSummary;
|
||||||
|
const traffic = (overview.traffic ?? {}) as DashboardTraffic;
|
||||||
|
const capacity = (overview.capacity ?? {}) as DashboardCapacity;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...overview,
|
generated_at: String(overview.generated_at ?? ''),
|
||||||
nodes: arrayOrEmpty(overview.nodes),
|
summary,
|
||||||
|
traffic,
|
||||||
|
capacity,
|
||||||
|
nodes: normalizeDashboardNodes(overview.nodes),
|
||||||
distributions: {
|
distributions: {
|
||||||
...overview.distributions,
|
source_countries: normalizeDistributionItems(
|
||||||
source_countries: arrayOrEmpty(overview.distributions?.source_countries),
|
overview.distributions?.source_countries,
|
||||||
status_codes: arrayOrEmpty(overview.distributions?.status_codes),
|
),
|
||||||
top_domains: arrayOrEmpty(overview.distributions?.top_domains),
|
status_codes: normalizeDistributionItems(overview.distributions?.status_codes),
|
||||||
|
top_domains: normalizeDistributionItems(overview.distributions?.top_domains),
|
||||||
},
|
},
|
||||||
trends: {
|
trends: {
|
||||||
...overview.trends,
|
traffic_24h: normalizeTrafficTrendPoints(overview.trends?.traffic_24h),
|
||||||
traffic_24h: arrayOrEmpty(overview.trends?.traffic_24h),
|
capacity_24h: normalizeCapacityTrendPoints(overview.trends?.capacity_24h),
|
||||||
capacity_24h: arrayOrEmpty(overview.trends?.capacity_24h),
|
network_24h: normalizeNetworkTrendPoints(overview.trends?.network_24h),
|
||||||
network_24h: arrayOrEmpty(overview.trends?.network_24h),
|
disk_io_24h: normalizeDiskIOTrendPoints(overview.trends?.disk_io_24h),
|
||||||
disk_io_24h: arrayOrEmpty(overview.trends?.disk_io_24h),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDashboardOverview() {
|
export async function getDashboardOverview() {
|
||||||
const overview = await apiRequest<DashboardOverview>('/dashboard/overview');
|
const overview = await apiRequest<DashboardOverview | DashboardOverviewCompact>(
|
||||||
|
'/dashboard/overview',
|
||||||
|
);
|
||||||
return normalizeDashboardOverview(overview);
|
return normalizeDashboardOverview(overview);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ export interface DistributionItem {
|
|||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CompactDistributionItem = [string, number];
|
||||||
|
|
||||||
export interface TrafficTrendPoint {
|
export interface TrafficTrendPoint {
|
||||||
bucket_started_at: string;
|
bucket_started_at: string;
|
||||||
request_count: number;
|
request_count: number;
|
||||||
@@ -99,3 +101,53 @@ export interface DashboardOverview {
|
|||||||
trends: DashboardTrends;
|
trends: DashboardTrends;
|
||||||
nodes: DashboardNodeHealth[];
|
nodes: DashboardNodeHealth[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CompactTrafficTrendPoint = [string, number, number, number];
|
||||||
|
export type CompactCapacityTrendPoint = [string, number, number, number];
|
||||||
|
export type CompactNetworkTrendPoint = [
|
||||||
|
string,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
];
|
||||||
|
export type CompactDiskIOTrendPoint = [string, number, number, number];
|
||||||
|
export type CompactDashboardNodeHealth = [
|
||||||
|
number,
|
||||||
|
string,
|
||||||
|
string,
|
||||||
|
string,
|
||||||
|
number | null,
|
||||||
|
number | null,
|
||||||
|
DashboardNodeHealth['status'],
|
||||||
|
DashboardNodeHealth['openresty_status'],
|
||||||
|
string,
|
||||||
|
string,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface DashboardOverviewCompact {
|
||||||
|
generated_at: string;
|
||||||
|
summary: DashboardSummary;
|
||||||
|
traffic: DashboardTraffic;
|
||||||
|
capacity: DashboardCapacity;
|
||||||
|
distributions: {
|
||||||
|
status_codes: CompactDistributionItem[];
|
||||||
|
top_domains: CompactDistributionItem[];
|
||||||
|
source_countries: CompactDistributionItem[];
|
||||||
|
};
|
||||||
|
trends: {
|
||||||
|
traffic_24h: CompactTrafficTrendPoint[];
|
||||||
|
capacity_24h: CompactCapacityTrendPoint[];
|
||||||
|
network_24h: CompactNetworkTrendPoint[];
|
||||||
|
disk_io_24h: CompactDiskIOTrendPoint[];
|
||||||
|
};
|
||||||
|
nodes: CompactDashboardNodeHealth[];
|
||||||
|
}
|
||||||
|
|||||||
@@ -88,87 +88,87 @@ describe('DashboardOverview', () => {
|
|||||||
},
|
},
|
||||||
distributions: {
|
distributions: {
|
||||||
source_countries: [
|
source_countries: [
|
||||||
{ key: 'CN', value: 440 },
|
['CN', 440],
|
||||||
{ key: 'US', value: 320 },
|
['US', 320],
|
||||||
{ key: 'SG', value: 140 },
|
['SG', 140],
|
||||||
],
|
],
|
||||||
status_codes: [
|
status_codes: [
|
||||||
{ key: '200', value: 820 },
|
['200', 820],
|
||||||
{ key: '502', value: 24 },
|
['502', 24],
|
||||||
{ key: '500', value: 12 },
|
['500', 12],
|
||||||
],
|
],
|
||||||
top_domains: [
|
top_domains: [
|
||||||
{ key: 'app.example.com', value: 580 },
|
['app.example.com', 580],
|
||||||
{ key: 'api.example.com', value: 220 },
|
['api.example.com', 220],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
trends: {
|
trends: {
|
||||||
traffic_24h: Array.from({ length: 24 }, (_, index) => ({
|
traffic_24h: Array.from({ length: 24 }, (_, index) => [
|
||||||
bucket_started_at: `2026-03-13T${String(index).padStart(2, '0')}:00:00Z`,
|
`2026-03-13T${String(index).padStart(2, '0')}:00:00Z`,
|
||||||
request_count: index * 10,
|
index * 10,
|
||||||
error_count: index,
|
index,
|
||||||
unique_visitor_count: index * 3,
|
index * 3,
|
||||||
})),
|
]),
|
||||||
capacity_24h: Array.from({ length: 24 }, (_, index) => ({
|
capacity_24h: Array.from({ length: 24 }, (_, index) => [
|
||||||
bucket_started_at: `2026-03-13T${String(index).padStart(2, '0')}:00:00Z`,
|
`2026-03-13T${String(index).padStart(2, '0')}:00:00Z`,
|
||||||
average_cpu_usage_percent: index,
|
index,
|
||||||
average_memory_usage_percent: index + 10,
|
index + 10,
|
||||||
reported_nodes: 2,
|
2,
|
||||||
})),
|
]),
|
||||||
network_24h: Array.from({ length: 24 }, (_, index) => ({
|
network_24h: Array.from({ length: 24 }, (_, index) => [
|
||||||
bucket_started_at: `2026-03-13T${String(index).padStart(2, '0')}:00:00Z`,
|
`2026-03-13T${String(index).padStart(2, '0')}:00:00Z`,
|
||||||
network_rx_bytes: index * 100,
|
index * 100,
|
||||||
network_tx_bytes: index * 120,
|
index * 120,
|
||||||
openresty_rx_bytes: index * 140,
|
index * 140,
|
||||||
openresty_tx_bytes: index * 160,
|
index * 160,
|
||||||
reported_nodes: 2,
|
2,
|
||||||
})),
|
]),
|
||||||
disk_io_24h: Array.from({ length: 24 }, (_, index) => ({
|
disk_io_24h: Array.from({ length: 24 }, (_, index) => [
|
||||||
bucket_started_at: `2026-03-13T${String(index).padStart(2, '0')}:00:00Z`,
|
`2026-03-13T${String(index).padStart(2, '0')}:00:00Z`,
|
||||||
disk_read_bytes: index * 50,
|
index * 50,
|
||||||
disk_write_bytes: index * 70,
|
index * 70,
|
||||||
reported_nodes: 2,
|
2,
|
||||||
})),
|
]),
|
||||||
},
|
},
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
[
|
||||||
id: 1,
|
1,
|
||||||
node_id: 'node-a',
|
'node-a',
|
||||||
name: 'edge-a',
|
'edge-a',
|
||||||
geo_name: 'Shanghai',
|
'Shanghai',
|
||||||
geo_latitude: 31.2304,
|
31.2304,
|
||||||
geo_longitude: 121.4737,
|
121.4737,
|
||||||
status: 'online',
|
'online',
|
||||||
openresty_status: 'healthy',
|
'healthy',
|
||||||
current_version: '20260314-001',
|
'20260314-001',
|
||||||
last_seen_at: '2026-03-14T08:00:00Z',
|
'2026-03-14T08:00:00Z',
|
||||||
active_event_count: 0,
|
0,
|
||||||
cpu_usage_percent: 45,
|
45,
|
||||||
memory_usage_percent: 50,
|
50,
|
||||||
storage_usage_percent: 60,
|
60,
|
||||||
request_count: 600,
|
600,
|
||||||
error_count: 6,
|
6,
|
||||||
unique_visitor_count: 120,
|
120,
|
||||||
},
|
],
|
||||||
{
|
[
|
||||||
id: 2,
|
2,
|
||||||
node_id: 'node-b',
|
'node-b',
|
||||||
name: 'edge-b',
|
'edge-b',
|
||||||
geo_name: 'San Francisco',
|
'San Francisco',
|
||||||
geo_latitude: 37.7749,
|
37.7749,
|
||||||
geo_longitude: -122.4194,
|
-122.4194,
|
||||||
status: 'online',
|
'online',
|
||||||
openresty_status: 'unhealthy',
|
'unhealthy',
|
||||||
current_version: '20260313-001',
|
'20260313-001',
|
||||||
last_seen_at: '2026-03-14T08:00:00Z',
|
'2026-03-14T08:00:00Z',
|
||||||
active_event_count: 2,
|
2,
|
||||||
cpu_usage_percent: 92,
|
92,
|
||||||
memory_usage_percent: 88,
|
88,
|
||||||
storage_usage_percent: 95,
|
95,
|
||||||
request_count: 300,
|
300,
|
||||||
error_count: 30,
|
30,
|
||||||
unique_visitor_count: 80,
|
80,
|
||||||
},
|
],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -183,7 +183,6 @@ describe('DashboardOverview', () => {
|
|||||||
renderDashboardOverview();
|
renderDashboardOverview();
|
||||||
|
|
||||||
expect(await screen.findByText('全球态势板')).toBeInTheDocument();
|
expect(await screen.findByText('全球态势板')).toBeInTheDocument();
|
||||||
expect(await screen.findByText('系统健康摘要')).toBeInTheDocument();
|
|
||||||
expect(await screen.findByText('24 小时请求趋势')).toBeInTheDocument();
|
expect(await screen.findByText('24 小时请求趋势')).toBeInTheDocument();
|
||||||
expect(await screen.findByText('来源分布')).toBeInTheDocument();
|
expect(await screen.findByText('来源分布')).toBeInTheDocument();
|
||||||
expect(await screen.findByText('Top Domain')).toBeInTheDocument();
|
expect(await screen.findByText('Top Domain')).toBeInTheDocument();
|
||||||
|
|||||||
Reference in New Issue
Block a user