Files
OpenFlare/frontend/lib/services/openflare/dashboard.service.ts
T
ryan a4dd5ca9e1 feat(dashboard): 首页请求趋势拆分状态码并合并容量到业务流量
- 24 小时请求趋势拆分展示请求总量与 200/400/500 状态码请求量,独占一行;
  时间桶聚合新增 status_200/400/500_count(CH countIf、PG FILTER),
  请求趋势改为基于原始桶聚合(小时 rollup 无状态码口径)
- 首页移除宿主机磁盘指标,容量趋势(CPU/内存)并入业务流量卡片展示
- 压缩协议 traffic_24h 扩展为 7 元组,前端归一化同步更新
2026-08-13 11:10:37 +08:00

252 lines
7.2 KiB
TypeScript

import { OpenFlareBaseService } from './base.service';
import type {
CompactCapacityTrendPoint,
CompactDashboardNodeHealth,
CompactDiskIOTrendPoint,
CompactDistributionItem,
CompactNetworkTrendPoint,
CompactTrafficTrendPoint,
DashboardCapacity,
DashboardNodeHealth,
DashboardOverview,
DashboardOverviewCompact,
DashboardSummary,
DashboardTraffic,
DistributionItem,
} from './types';
function arrayOrEmpty<T>(value: T[] | null | undefined) {
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),
status_2xx_count: Number(item[4] ?? 0),
status_4xx_count: Number(item[5] ?? 0),
status_5xx_count: Number(item[6] ?? 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] ?? ''),
bytes_received: Number(item[1] ?? 0),
bytes_provided: Number(item[2] ?? 0),
reported_nodes: Number(item[3] ?? 0),
}
: {
...item,
bytes_received: Number(
(item as { bytes_received?: number }).bytes_received ?? 0,
),
bytes_provided: Number(
(item as { bytes_provided?: number }).bytes_provided ?? 0,
),
},
);
}
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(
overview: DashboardOverview | DashboardOverviewCompact | null | undefined,
): DashboardOverview | null {
if (!overview) {
return null;
}
const summary = (overview.summary ?? {}) as DashboardSummary;
const traffic = (overview.traffic ?? {}) as DashboardTraffic;
const capacity = (overview.capacity ?? {}) as DashboardCapacity;
return {
generated_at: String(overview.generated_at ?? ''),
summary,
traffic,
capacity,
nodes: normalizeDashboardNodes(overview.nodes),
distributions: {
source_countries: normalizeDistributionItems(
overview.distributions?.source_countries,
),
status_codes: normalizeDistributionItems(
overview.distributions?.status_codes,
),
top_domains: normalizeDistributionItems(
overview.distributions?.top_domains,
),
},
trends: {
traffic_24h: normalizeTrafficTrendPoints(overview.trends?.traffic_24h),
capacity_24h: normalizeCapacityTrendPoints(overview.trends?.capacity_24h),
network_24h: normalizeNetworkTrendPoints(overview.trends?.network_24h),
disk_io_24h: normalizeDiskIOTrendPoints(overview.trends?.disk_io_24h),
},
};
}
export class DashboardService extends OpenFlareBaseService {
protected static override readonly basePath: string = '/api/v1/d/dashboard';
static async getOverview(): Promise<DashboardOverview | null> {
const overview = await this.get<
DashboardOverview | DashboardOverviewCompact
>('/overview');
return normalizeDashboardOverview(overview);
}
}