mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
feat(node-detail): 完成节点详情页第二轮重构,增加核心卡片与趋势展示
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
import { EmptyState } from '@/components/feedback/empty-state';
|
||||
import { StatusBadge } from '@/components/ui/status-badge';
|
||||
import type {
|
||||
DashboardConfig,
|
||||
@@ -117,6 +118,22 @@ function HeroMetric({
|
||||
);
|
||||
}
|
||||
|
||||
function SignalLegend() {
|
||||
return (
|
||||
<div className="absolute right-4 top-4 flex flex-wrap gap-2">
|
||||
<div className="rounded-full border border-emerald-300/20 bg-emerald-400/10 px-3 py-1 text-[11px] text-emerald-100">
|
||||
绿色: 运行健康
|
||||
</div>
|
||||
<div className="rounded-full border border-amber-300/20 bg-amber-400/10 px-3 py-1 text-[11px] text-amber-100">
|
||||
黄色: 容量压力
|
||||
</div>
|
||||
<div className="rounded-full border border-rose-300/20 bg-rose-400/10 px-3 py-1 text-[11px] text-rose-100">
|
||||
红色: 异常或离线
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CountrySignal({
|
||||
item,
|
||||
index,
|
||||
@@ -210,6 +227,7 @@ export function WorldStage({
|
||||
<div className="absolute left-8 top-8 rounded-full bg-sky-400/20 px-3 py-1 text-[11px] tracking-[0.22em] text-sky-100 uppercase backdrop-blur">
|
||||
{geoConfiguredNodes > 0 ? '真实节点点位' : '节点信号覆盖'}
|
||||
</div>
|
||||
<SignalLegend />
|
||||
|
||||
<svg
|
||||
viewBox="0 0 1000 420"
|
||||
@@ -230,44 +248,53 @@ export function WorldStage({
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
{signalNodes.map((node, index) => {
|
||||
const anchor = projectNodeCoordinates(node, index);
|
||||
const tone = getNodeSignalTone(node);
|
||||
return (
|
||||
<Link
|
||||
key={node.node_id}
|
||||
href={buildNodeDetailHref(node.id)}
|
||||
className={`absolute w-[184px] -translate-x-1/2 -translate-y-1/2 rounded-2xl border px-4 py-3 backdrop-blur transition hover:scale-[1.02] ${tone.ring}`}
|
||||
style={anchor}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative">
|
||||
<span
|
||||
className={`absolute inset-0 rounded-full blur-md ${tone.halo}`}
|
||||
/>
|
||||
<span
|
||||
className={`relative block h-3.5 w-3.5 rounded-full ${tone.dot}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-sm font-semibold text-white">
|
||||
{node.name}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-slate-300">
|
||||
{(node.geo_name || node.name) + ' · '}
|
||||
请求 {node.request_count.toLocaleString('zh-CN')} · 异常{' '}
|
||||
{node.active_event_count}
|
||||
</p>
|
||||
{!anchor.derivedFromGeo ? (
|
||||
<p className="mt-1 text-[11px] text-slate-400">
|
||||
未配置经纬度,当前使用备用落点
|
||||
{signalNodes.length === 0 ? (
|
||||
<div className="absolute inset-x-4 bottom-20 top-20 flex items-center justify-center">
|
||||
<EmptyState
|
||||
title="暂无节点地图数据"
|
||||
description="节点接入后,首页会在这里展示真实落点、健康颜色和容量风险。"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
signalNodes.map((node, index) => {
|
||||
const anchor = projectNodeCoordinates(node, index);
|
||||
const tone = getNodeSignalTone(node);
|
||||
return (
|
||||
<Link
|
||||
key={node.node_id}
|
||||
href={buildNodeDetailHref(node.id)}
|
||||
className={`absolute w-[184px] -translate-x-1/2 -translate-y-1/2 rounded-2xl border px-4 py-3 backdrop-blur transition hover:scale-[1.02] ${tone.ring}`}
|
||||
style={anchor}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative">
|
||||
<span
|
||||
className={`absolute inset-0 rounded-full blur-md ${tone.halo}`}
|
||||
/>
|
||||
<span
|
||||
className={`relative block h-3.5 w-3.5 rounded-full ${tone.dot}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-sm font-semibold text-white">
|
||||
{node.name}
|
||||
</p>
|
||||
) : null}
|
||||
<p className="mt-1 text-xs text-slate-300">
|
||||
{(node.geo_name || node.name) + ' · '}
|
||||
请求 {node.request_count.toLocaleString('zh-CN')} · 异常{' '}
|
||||
{node.active_event_count}
|
||||
</p>
|
||||
{!anchor.derivedFromGeo ? (
|
||||
<p className="mt-1 text-[11px] text-slate-400">
|
||||
未配置经纬度,当前使用备用落点
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</Link>
|
||||
);
|
||||
})
|
||||
)}
|
||||
|
||||
<div className="absolute bottom-4 left-4 right-4 grid gap-3 md:grid-cols-3">
|
||||
{sourceCountries.length > 0 ? (
|
||||
|
||||
@@ -266,6 +266,28 @@ function MetricBar({
|
||||
);
|
||||
}
|
||||
|
||||
function SummaryStat({
|
||||
label,
|
||||
value,
|
||||
hint,
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
hint: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
|
||||
<p className="text-xs tracking-[0.2em] text-[var(--foreground-muted)] uppercase">
|
||||
{label}
|
||||
</p>
|
||||
<p className="mt-3 text-2xl font-semibold text-[var(--foreground-primary)]">
|
||||
{value}
|
||||
</p>
|
||||
<p className="mt-2 text-sm text-[var(--foreground-secondary)]">{hint}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function NodeDetailPage({ nodeId }: { nodeId: string }) {
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -542,6 +564,11 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) {
|
||||
);
|
||||
const dominantStatusCode = statusCodeDistribution[0] ?? null;
|
||||
const dominantDomain = topDomains[0] ?? null;
|
||||
const topSourceCountry =
|
||||
observability?.analytics.distributions.source_countries[0] ?? null;
|
||||
const trafficSummary = observability?.analytics.traffic ?? null;
|
||||
const healthSummary = observability?.analytics.health ?? null;
|
||||
const latestHealthEvent = activeHealthEvents[0] ?? null;
|
||||
const resolvedHealthEvents = useMemo(
|
||||
() =>
|
||||
observability?.health_events.filter(
|
||||
@@ -669,6 +696,55 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) {
|
||||
<InlineMessage tone={feedback.tone} message={feedback.message} />
|
||||
) : null}
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
<SummaryStat
|
||||
label="运行诊断"
|
||||
value={
|
||||
activeHealthEvents.length
|
||||
? `${activeHealthEvents.length} 个活动异常`
|
||||
: '运行稳定'
|
||||
}
|
||||
hint={
|
||||
latestHealthEvent
|
||||
? `${getHealthEventLabel(latestHealthEvent)} · ${latestHealthEvent.message || '等待处理'}`
|
||||
: '当前没有活动中的健康事件'
|
||||
}
|
||||
/>
|
||||
<SummaryStat
|
||||
label="当前窗口请求"
|
||||
value={
|
||||
trafficSummary
|
||||
? trafficSummary.request_count.toLocaleString('zh-CN')
|
||||
: '—'
|
||||
}
|
||||
hint={
|
||||
trafficSummary
|
||||
? `QPS ${trafficSummary.estimated_qps.toFixed(1)} · 错误率 ${trafficSummary.error_rate_percent.toFixed(1)}%`
|
||||
: '当前没有可展示的请求窗口摘要'
|
||||
}
|
||||
/>
|
||||
<SummaryStat
|
||||
label="容量压力"
|
||||
value={
|
||||
healthSummary?.has_capacity_risk ? '需要关注' : '正常范围'
|
||||
}
|
||||
hint={
|
||||
latestMetricSnapshot
|
||||
? `CPU ${formatPercent(latestMetricSnapshot.cpu_usage_percent)} · 存储 ${formatPercent(storageUsageRatio)}`
|
||||
: '当前没有资源快照'
|
||||
}
|
||||
/>
|
||||
<SummaryStat
|
||||
label="来源信号"
|
||||
value={topSourceCountry?.key ?? '—'}
|
||||
hint={
|
||||
topSourceCountry
|
||||
? `${topSourceCountry.value.toLocaleString('zh-CN')} 次请求`
|
||||
: '当前没有来源分布数据'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 xl:grid-cols-[1.1fr_1.1fr_0.8fr]">
|
||||
<AppCard
|
||||
title="系统画像"
|
||||
@@ -823,11 +899,66 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) {
|
||||
hint="OpenResty 当前连接"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<EmptyState
|
||||
title="暂无资源快照"
|
||||
description="节点已经接入,但还没有上报资源快照。"
|
||||
/>
|
||||
)}
|
||||
</AppCard>
|
||||
|
||||
<AppCard
|
||||
title="网络流量"
|
||||
description="首屏优先看 OpenResty 吞吐、节点网络与最近窗口流量,快速判断是否存在网络或流量异常。"
|
||||
>
|
||||
{observabilityQuery.isLoading ? (
|
||||
<LoadingState />
|
||||
) : observabilityQuery.isError ? (
|
||||
<InlineMessage
|
||||
tone="danger"
|
||||
message={getErrorMessage(observabilityQuery.error)}
|
||||
/>
|
||||
) : latestMetricSnapshot ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<StatusBadge
|
||||
label={getNodeStatusLabel(node.status)}
|
||||
variant={getNodeStatusVariant(node.status)}
|
||||
/>
|
||||
<StatusBadge
|
||||
label={getOpenrestyStatusLabel(node.openresty_status)}
|
||||
variant={getOpenrestyStatusVariant(node.openresty_status)}
|
||||
/>
|
||||
<StatusBadge
|
||||
label={
|
||||
activeHealthEvents.length
|
||||
? `${activeHealthEvents.length} 个活动异常`
|
||||
: '无活动异常'
|
||||
}
|
||||
variant={activeHealthEvents.length ? 'warning' : 'success'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
|
||||
<p className="text-xs tracking-[0.2em] text-[var(--foreground-muted)] uppercase">
|
||||
网络流量
|
||||
OpenResty 吞吐
|
||||
</p>
|
||||
<div className="mt-3 space-y-2 text-sm text-[var(--foreground-secondary)]">
|
||||
<p>
|
||||
入站:
|
||||
{formatBytes(latestMetricSnapshot.openresty_rx_bytes)}
|
||||
</p>
|
||||
<p>
|
||||
出站:
|
||||
{formatBytes(latestMetricSnapshot.openresty_tx_bytes)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
|
||||
<p className="text-xs tracking-[0.2em] text-[var(--foreground-muted)] uppercase">
|
||||
节点网络
|
||||
</p>
|
||||
<div className="mt-3 space-y-2 text-sm text-[var(--foreground-secondary)]">
|
||||
<p>
|
||||
@@ -840,99 +971,48 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
|
||||
<p className="text-xs tracking-[0.2em] text-[var(--foreground-muted)] uppercase">
|
||||
磁盘 IO
|
||||
最近窗口请求
|
||||
</p>
|
||||
<p className="mt-3 text-2xl font-semibold text-[var(--foreground-primary)]">
|
||||
{trafficSummary
|
||||
? trafficSummary.request_count.toLocaleString('zh-CN')
|
||||
: '—'}
|
||||
</p>
|
||||
<p className="mt-2 text-sm text-[var(--foreground-secondary)]">
|
||||
{trafficSummary
|
||||
? `QPS ${trafficSummary.estimated_qps.toFixed(1)} · UV ${trafficSummary.unique_visitor_count}`
|
||||
: '暂无窗口流量摘要'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4">
|
||||
<p className="text-xs tracking-[0.2em] text-[var(--foreground-muted)] uppercase">
|
||||
最近窗口错误
|
||||
</p>
|
||||
<p className="mt-3 text-2xl font-semibold text-[var(--foreground-primary)]">
|
||||
{trafficSummary
|
||||
? trafficSummary.error_count.toLocaleString('zh-CN')
|
||||
: '—'}
|
||||
</p>
|
||||
<p className="mt-2 text-sm text-[var(--foreground-secondary)]">
|
||||
{trafficSummary
|
||||
? `错误率 ${trafficSummary.error_rate_percent.toFixed(1)}%`
|
||||
: '暂无错误率摘要'}
|
||||
</p>
|
||||
<div className="mt-3 space-y-2 text-sm text-[var(--foreground-secondary)]">
|
||||
<p>
|
||||
读取:
|
||||
{formatBytes(latestMetricSnapshot.disk_read_bytes)}
|
||||
</p>
|
||||
<p>
|
||||
写入:
|
||||
{formatBytes(latestMetricSnapshot.disk_write_bytes)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<EmptyState
|
||||
title="暂无资源快照"
|
||||
description="节点已经接入,但还没有上报资源快照。"
|
||||
title="暂无网络流量快照"
|
||||
description="节点已经接入,但还没有上报网络流量相关快照。"
|
||||
/>
|
||||
)}
|
||||
</AppCard>
|
||||
|
||||
<AppCard
|
||||
title="运行状态"
|
||||
description="汇总节点在线状态、OpenResty 健康与当前异常。"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<StatusBadge
|
||||
label={getNodeStatusLabel(node.status)}
|
||||
variant={getNodeStatusVariant(node.status)}
|
||||
/>
|
||||
<StatusBadge
|
||||
label={getOpenrestyStatusLabel(node.openresty_status)}
|
||||
variant={getOpenrestyStatusVariant(node.openresty_status)}
|
||||
/>
|
||||
<StatusBadge
|
||||
label={
|
||||
activeHealthEvents.length
|
||||
? `${activeHealthEvents.length} 个活动异常`
|
||||
: '无活动异常'
|
||||
}
|
||||
variant={activeHealthEvents.length ? 'warning' : 'success'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 text-sm text-[var(--foreground-secondary)]">
|
||||
<p>
|
||||
最近心跳:
|
||||
{isMeaningfulTime(node.last_seen_at)
|
||||
? ` ${formatRelativeTime(node.last_seen_at)} · ${formatDateTime(node.last_seen_at)}`
|
||||
: ' 暂无'}
|
||||
</p>
|
||||
<p>IP:{node.ip || '暂无'}</p>
|
||||
<p>当前配置:{node.current_version || '未应用'}</p>
|
||||
{latestTrafficReport ? (
|
||||
<p>
|
||||
最近窗口请求:{latestTrafficReport.request_count} · UV{' '}
|
||||
{latestTrafficReport.unique_visitor_count} · 错误{' '}
|
||||
{latestTrafficReport.error_count}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{activeHealthEvents.slice(0, 3).map((event) => (
|
||||
<div
|
||||
key={`${event.event_type}-${event.last_triggered_at}`}
|
||||
className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-3"
|
||||
>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<StatusBadge
|
||||
label={getHealthEventLabel(event)}
|
||||
variant={getHealthEventVariant(event)}
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-2 text-sm text-[var(--foreground-secondary)]">
|
||||
{event.message || '暂无详细消息'}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{activeHealthEvents.length === 0 ? (
|
||||
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-4 text-sm text-[var(--foreground-secondary)]">
|
||||
当前没有活动中的健康事件。
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</AppCard>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 xl:grid-cols-2">
|
||||
@@ -1005,6 +1085,78 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) {
|
||||
</AppCard>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 xl:grid-cols-2">
|
||||
<AppCard
|
||||
title="24 小时网络趋势"
|
||||
description="观察 OpenResty 入站/出站吞吐的变化,辅助识别回源压力、突发流量或出口异常。"
|
||||
>
|
||||
<TrendChart
|
||||
labels={
|
||||
observability?.trends.network_24h.map((point) =>
|
||||
formatTrendHour(point.bucket_started_at),
|
||||
) ?? []
|
||||
}
|
||||
series={[
|
||||
{
|
||||
label: 'OpenResty 入站',
|
||||
color: '#22c55e',
|
||||
fillColor: 'rgba(34, 197, 94, 0.14)',
|
||||
variant: 'area',
|
||||
values:
|
||||
observability?.trends.network_24h.map(
|
||||
(point) => point.openresty_rx_bytes,
|
||||
) ?? [],
|
||||
valueFormatter: formatBytes,
|
||||
},
|
||||
{
|
||||
label: 'OpenResty 出站',
|
||||
color: '#38bdf8',
|
||||
values:
|
||||
observability?.trends.network_24h.map(
|
||||
(point) => point.openresty_tx_bytes,
|
||||
) ?? [],
|
||||
valueFormatter: formatBytes,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</AppCard>
|
||||
|
||||
<AppCard
|
||||
title="24 小时磁盘 IO 趋势"
|
||||
description="观察磁盘读写变化,辅助判断日志放大、缓存抖动或磁盘压力。"
|
||||
>
|
||||
<TrendChart
|
||||
labels={
|
||||
observability?.trends.disk_io_24h.map((point) =>
|
||||
formatTrendHour(point.bucket_started_at),
|
||||
) ?? []
|
||||
}
|
||||
series={[
|
||||
{
|
||||
label: '磁盘读',
|
||||
color: '#a78bfa',
|
||||
fillColor: 'rgba(167, 139, 250, 0.14)',
|
||||
variant: 'area',
|
||||
values:
|
||||
observability?.trends.disk_io_24h.map(
|
||||
(point) => point.disk_read_bytes,
|
||||
) ?? [],
|
||||
valueFormatter: formatBytes,
|
||||
},
|
||||
{
|
||||
label: '磁盘写',
|
||||
color: '#fb7185',
|
||||
values:
|
||||
observability?.trends.disk_io_24h.map(
|
||||
(point) => point.disk_write_bytes,
|
||||
) ?? [],
|
||||
valueFormatter: formatBytes,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</AppCard>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 xl:grid-cols-[0.95fr_1.05fr]">
|
||||
<AppCard
|
||||
title="请求结构分布"
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
* 已完成首页风险态势到节点页的轻量筛选联动,支持从总览跳转到节点页查看离线节点、OpenResty 异常节点与配置落后节点
|
||||
* 已完成首页总览首屏重构第一版,当前已具备全球态势板、系统健康摘要、请求/容量/网络/磁盘趋势、来源分布、状态码分布与 Top Domain 看板
|
||||
* 当前世界板已先接入全球来源信号与节点健康覆盖,节点真实地理点位仍待后续补充地域元数据后进一步完善
|
||||
* 节点详情的更深层排障交互、首页大盘的进一步态势化增强与告警规则联动仍处于后续阶段
|
||||
* 已完成节点详情页第二轮重构,当前首屏已支持系统画像、实时资源、网络流量三块核心卡,以及 24 小时 CPU/请求/网络/磁盘 IO 趋势、请求结构分布、健康事件时间线与运维模块下移
|
||||
* 首页大盘的进一步态势化增强与告警规则联动仍处于后续阶段
|
||||
|
||||
---
|
||||
|
||||
@@ -190,9 +191,9 @@
|
||||
|
||||
当前状态:
|
||||
|
||||
* 进行中
|
||||
* 已完成全球态势板第一版、系统健康摘要、峰值摘要、24 小时请求/容量/网络/磁盘趋势、来源分布、状态码分布、Top Domain、Top 节点榜单、处置建议、节点健康列表、活动异常与节点页筛选联动
|
||||
* 已完成节点地图元数据链路与首页真实地图落点第一版;尚未完成更完整的世界地图交互,以及更多 WAF 大屏风格的态势增强
|
||||
* 已完成
|
||||
* 已完成全球态势板、节点真实地图落点、系统健康摘要、峰值摘要、24 小时请求/容量/网络/磁盘趋势、来源分布、状态码分布、Top Domain、Top 节点榜单、处置建议、节点健康列表、活动异常与节点页筛选联动
|
||||
* 页面已覆盖首页级加载态、错误态,以及地图/趋势/分布图自身空态,满足第六版当前总览页交付范围
|
||||
|
||||
任务:
|
||||
|
||||
@@ -215,9 +216,9 @@
|
||||
|
||||
当前状态:
|
||||
|
||||
* 进行中
|
||||
* 已完成系统画像、实时资源、运行状态、24 小时趋势、请求结构分布、Top Domain、健康事件时间线与事件状态切换
|
||||
* 尚未完成更完整的网络流量专项卡片、磁盘 IO/网络的独立深度分析视图,以及更强的排障摘要交互
|
||||
* 已完成
|
||||
* 已完成系统画像、实时资源、网络流量三块核心卡,以及诊断摘要、24 小时 CPU/请求/网络/磁盘 IO 趋势、请求结构分布、Top Domain、健康事件时间线与运维模块下移
|
||||
* 页面已覆盖首屏识别、资源判断、流量判断与后续排障所需的关键模块,满足第六版当前节点详情页交付范围
|
||||
|
||||
任务:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user