mirror of
https://github.com/rustfs/console.git
synced 2026-08-28 19:47:21 +08:00
refactor: clarify running status diagnostics (#180)
This commit is contained in:
@@ -2,7 +2,53 @@
|
||||
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Card, CardContent, CardDescription, CardHeader } from "@/components/ui/card"
|
||||
import type { RunningStatusTopology } from "@/lib/performance-data"
|
||||
import type { RunningStatusTopology, StatusDiagnostic } from "@/lib/performance-data"
|
||||
|
||||
export type InfrastructureHealthState = "healthy" | "attention" | "unknown"
|
||||
|
||||
export function getInfrastructureHealthState({
|
||||
onlineServers,
|
||||
offlineServers,
|
||||
degradedServers,
|
||||
initializingServers,
|
||||
unknownServers,
|
||||
onlineDisks,
|
||||
offlineDisks,
|
||||
unknownDisks,
|
||||
topology,
|
||||
peerHealth,
|
||||
storageReadiness,
|
||||
}: {
|
||||
onlineServers?: number
|
||||
offlineServers?: number
|
||||
degradedServers?: number
|
||||
initializingServers?: number
|
||||
unknownServers?: number
|
||||
onlineDisks?: number
|
||||
offlineDisks?: number
|
||||
unknownDisks?: number
|
||||
topology: RunningStatusTopology
|
||||
peerHealth?: StatusDiagnostic
|
||||
storageReadiness?: StatusDiagnostic
|
||||
}): InfrastructureHealthState {
|
||||
const resourceNeedsAttention = Boolean((offlineServers ?? 0) + (degradedServers ?? 0) + (offlineDisks ?? 0))
|
||||
const diagnosticNeedsAttention = [peerHealth, storageReadiness].some((diagnostic) => diagnostic?.state === "degraded")
|
||||
if (resourceNeedsAttention || diagnosticNeedsAttention) return "attention"
|
||||
|
||||
const resourceUncertain = Boolean((initializingServers ?? 0) + (unknownServers ?? 0) + (unknownDisks ?? 0))
|
||||
const diagnosticUncertain = [peerHealth, storageReadiness].some(
|
||||
(diagnostic) => diagnostic?.state === "stale" || diagnostic?.state === "unknown",
|
||||
)
|
||||
const serverCounts = [onlineServers, offlineServers, degradedServers, initializingServers, unknownServers]
|
||||
const diskCounts = [onlineDisks, offlineDisks, unknownDisks]
|
||||
const dataUnavailable =
|
||||
serverCounts.some((value) => value === undefined) ||
|
||||
diskCounts.some((value) => value === undefined) ||
|
||||
serverCounts.reduce<number>((total, value) => total + (value ?? 0), 0) === 0 ||
|
||||
diskCounts.reduce<number>((total, value) => total + (value ?? 0), 0) === 0
|
||||
|
||||
return resourceUncertain || diagnosticUncertain || dataUnavailable || topology.incomplete ? "unknown" : "healthy"
|
||||
}
|
||||
|
||||
function HealthMetric({ label, value, unavailableLabel }: { label: string; value?: number; unavailableLabel: string }) {
|
||||
return (
|
||||
@@ -23,6 +69,8 @@ export function PerformanceInfrastructureCard({
|
||||
offlineDisks,
|
||||
unknownDisks,
|
||||
topology,
|
||||
peerHealth,
|
||||
storageReadiness,
|
||||
t,
|
||||
}: {
|
||||
onlineServers?: number
|
||||
@@ -34,30 +82,34 @@ export function PerformanceInfrastructureCard({
|
||||
offlineDisks?: number
|
||||
unknownDisks?: number
|
||||
topology: RunningStatusTopology
|
||||
peerHealth?: StatusDiagnostic
|
||||
storageReadiness?: StatusDiagnostic
|
||||
t: (key: string) => string
|
||||
}) {
|
||||
const needsAttention = Boolean((offlineServers ?? 0) + (degradedServers ?? 0) + (offlineDisks ?? 0))
|
||||
const hasUnknownState = Boolean((initializingServers ?? 0) + (unknownServers ?? 0) + (unknownDisks ?? 0))
|
||||
const serverCounts = [onlineServers, offlineServers, degradedServers, initializingServers, unknownServers]
|
||||
const diskCounts = [onlineDisks, offlineDisks, unknownDisks]
|
||||
const dataUnavailable =
|
||||
serverCounts.some((value) => value === undefined) ||
|
||||
diskCounts.some((value) => value === undefined) ||
|
||||
serverCounts.reduce<number>((total, value) => total + (value ?? 0), 0) === 0 ||
|
||||
diskCounts.reduce<number>((total, value) => total + (value ?? 0), 0) === 0
|
||||
const hasIncompleteTopology = topology.incomplete
|
||||
const status = needsAttention
|
||||
? t("Needs attention")
|
||||
: hasUnknownState || dataUnavailable || hasIncompleteTopology
|
||||
? t("Unknown")
|
||||
: t("Healthy")
|
||||
const description = needsAttention
|
||||
? t("Offline or degraded resources require attention.")
|
||||
: hasIncompleteTopology
|
||||
? t("The reported health rows do not cover the full cluster topology.")
|
||||
: hasUnknownState || dataUnavailable
|
||||
? t("Some resource health data is unavailable or still initializing.")
|
||||
: t("All reported servers and disks are online.")
|
||||
const healthState = getInfrastructureHealthState({
|
||||
onlineServers,
|
||||
offlineServers,
|
||||
degradedServers,
|
||||
initializingServers,
|
||||
unknownServers,
|
||||
onlineDisks,
|
||||
offlineDisks,
|
||||
unknownDisks,
|
||||
topology,
|
||||
peerHealth,
|
||||
storageReadiness,
|
||||
})
|
||||
const status =
|
||||
healthState === "attention" ? t("Needs attention") : healthState === "unknown" ? t("Unknown") : t("Healthy")
|
||||
const description =
|
||||
healthState === "attention"
|
||||
? t("Offline or degraded resources require attention.")
|
||||
: hasIncompleteTopology
|
||||
? t("The reported health rows do not cover the full cluster topology.")
|
||||
: healthState === "unknown"
|
||||
? t("Some resource health data is unavailable or still initializing.")
|
||||
: t("All reported servers and disks are online.")
|
||||
|
||||
return (
|
||||
<Card className="h-full shadow-none">
|
||||
@@ -70,13 +122,7 @@ export function PerformanceInfrastructureCard({
|
||||
<CardDescription>{description}</CardDescription>
|
||||
</div>
|
||||
<Badge
|
||||
variant={
|
||||
needsAttention
|
||||
? "destructive"
|
||||
: hasUnknownState || dataUnavailable || hasIncompleteTopology
|
||||
? "outline"
|
||||
: "secondary"
|
||||
}
|
||||
variant={healthState === "attention" ? "destructive" : healthState === "unknown" ? "outline" : "secondary"}
|
||||
aria-live="polite"
|
||||
>
|
||||
{status}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { RiArrowDownSLine } from "@remixicon/react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardHeader } from "@/components/ui/card"
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import type { ClusterDiagnostics, OperationalStatus, StatusDiagnostic } from "@/lib/performance-data"
|
||||
|
||||
@@ -62,71 +65,194 @@ function DiagnosticRow({
|
||||
scope?.set ? `${t("Set")}: ${scope.set}` : undefined,
|
||||
scope?.timeout ? `${t("Timeout")}: ${scope.timeout}` : undefined,
|
||||
].filter(Boolean)
|
||||
const hasSupportingDetail = Boolean(
|
||||
diagnostic.lastSuccessfulUpdate ||
|
||||
showLastSuccessfulUpdate ||
|
||||
diagnostic.lastError ||
|
||||
scopeParts.length ||
|
||||
diagnostic.source ||
|
||||
diagnostic.historicalStallTimeouts !== undefined ||
|
||||
diagnostic.hint ||
|
||||
troubleshooting ||
|
||||
troubleshootingHref,
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="grid gap-2 py-4 sm:grid-cols-[minmax(0,12rem)_minmax(0,1fr)] sm:gap-6">
|
||||
<dt className="flex min-w-0 items-center justify-between gap-3 sm:flex-col sm:items-start sm:justify-start">
|
||||
<span className="font-medium text-foreground">{label}</span>
|
||||
<div className="grid gap-2 py-3 sm:grid-cols-[minmax(0,12rem)_minmax(0,7rem)_minmax(0,1fr)] sm:items-start sm:gap-4">
|
||||
<dt className="min-w-0 font-medium text-foreground">{label}</dt>
|
||||
<dd>
|
||||
<Badge variant={getStatusVariant(diagnostic.state)}>{getStatusLabel(diagnostic.state, t)}</Badge>
|
||||
</dt>
|
||||
<dd className="flex min-w-0 flex-col gap-2 text-muted-foreground">
|
||||
</dd>
|
||||
<dd className="flex min-w-0 flex-col gap-1.5 text-muted-foreground">
|
||||
<p className="break-words text-foreground [overflow-wrap:anywhere]">
|
||||
{diagnostic.reason ?? getDefaultDescription(diagnostic.state, t)}
|
||||
</p>
|
||||
{diagnostic.lastSuccessfulUpdate || showLastSuccessfulUpdate ? (
|
||||
<p className="text-xs">
|
||||
{t("Last successful update")}:{" "}
|
||||
{diagnostic.lastSuccessfulUpdate ? formatTimestamp(diagnostic.lastSuccessfulUpdate, locale) : t("Unknown")}
|
||||
</p>
|
||||
) : null}
|
||||
{diagnostic.lastError ? (
|
||||
<p className="break-words text-xs [overflow-wrap:anywhere]">
|
||||
{t("Last error")}: {diagnostic.lastError}
|
||||
</p>
|
||||
) : null}
|
||||
{scopeParts.length ? (
|
||||
<p className="break-words text-xs [overflow-wrap:anywhere]">{scopeParts.join(" · ")}</p>
|
||||
) : null}
|
||||
{diagnostic.source ? (
|
||||
<p className="break-words text-xs [overflow-wrap:anywhere]">
|
||||
{t("Source")}: {diagnostic.source}
|
||||
</p>
|
||||
) : null}
|
||||
{diagnostic.historicalStallTimeouts !== undefined ? (
|
||||
<div className="space-y-1 text-xs">
|
||||
<p className="text-foreground">
|
||||
{t("Historical internode stall timeouts")}: {diagnostic.historicalStallTimeouts}
|
||||
</p>
|
||||
<p>
|
||||
{t("This lifetime counter has no sampling window and does not indicate current degradation by itself.")}
|
||||
</p>
|
||||
{hasSupportingDetail ? (
|
||||
<div className="flex flex-col gap-1 text-xs">
|
||||
{diagnostic.lastSuccessfulUpdate || showLastSuccessfulUpdate ? (
|
||||
<p>
|
||||
{t("Last successful update")}:{" "}
|
||||
{diagnostic.lastSuccessfulUpdate
|
||||
? formatTimestamp(diagnostic.lastSuccessfulUpdate, locale)
|
||||
: t("Unknown")}
|
||||
</p>
|
||||
) : null}
|
||||
{diagnostic.lastError ? (
|
||||
<p className="break-words [overflow-wrap:anywhere]">
|
||||
{t("Last error")}: {diagnostic.lastError}
|
||||
</p>
|
||||
) : null}
|
||||
{scopeParts.length ? (
|
||||
<p className="break-words [overflow-wrap:anywhere]">{scopeParts.join(" · ")}</p>
|
||||
) : null}
|
||||
{diagnostic.source ? (
|
||||
<p className="break-words [overflow-wrap:anywhere]">
|
||||
{t("Source")}: <code>{diagnostic.source}</code>
|
||||
</p>
|
||||
) : null}
|
||||
{diagnostic.historicalStallTimeouts !== undefined ? (
|
||||
<>
|
||||
<p className="text-foreground">
|
||||
{t("Historical internode stall timeouts")}: {diagnostic.historicalStallTimeouts}
|
||||
</p>
|
||||
<p>
|
||||
{t(
|
||||
"This lifetime counter has no sampling window and does not indicate current degradation by itself.",
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
) : null}
|
||||
{diagnostic.hint ? (
|
||||
<p className="break-words [overflow-wrap:anywhere]">
|
||||
{t("Backend guidance")}: {diagnostic.hint}
|
||||
</p>
|
||||
) : null}
|
||||
{troubleshooting ? (
|
||||
<p className="text-foreground">
|
||||
{t("Troubleshooting")}: {troubleshooting}
|
||||
</p>
|
||||
) : null}
|
||||
{troubleshootingHref ? (
|
||||
<a
|
||||
className="w-fit font-medium text-foreground underline underline-offset-4"
|
||||
href={troubleshootingHref}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t("Open the real multi-node metrics verification guide")}
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{diagnostic.hint ? (
|
||||
<p className="break-words text-xs [overflow-wrap:anywhere]">
|
||||
{t("Backend guidance")}: {diagnostic.hint}
|
||||
</p>
|
||||
) : null}
|
||||
{troubleshooting ? (
|
||||
<p className="text-xs text-foreground">
|
||||
{t("Troubleshooting")}: {troubleshooting}
|
||||
</p>
|
||||
) : null}
|
||||
{troubleshootingHref ? (
|
||||
<a
|
||||
className="w-fit text-xs font-medium text-foreground underline underline-offset-4"
|
||||
href={troubleshootingHref}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t("Open the real multi-node metrics verification guide")}
|
||||
</a>
|
||||
) : null}
|
||||
</dd>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function PerformanceStatusSourcesContent({
|
||||
diagnostics,
|
||||
usageFreshness,
|
||||
t,
|
||||
locale,
|
||||
}: {
|
||||
diagnostics: ClusterDiagnostics
|
||||
usageFreshness?: StatusDiagnostic
|
||||
t: Translate
|
||||
locale?: string
|
||||
}) {
|
||||
const rows = [
|
||||
{ label: t("Peer Health"), diagnostic: diagnostics.peerHealth },
|
||||
{ label: t("Storage Readiness"), diagnostic: diagnostics.storageReadiness },
|
||||
{
|
||||
label: t("Usage Freshness"),
|
||||
diagnostic: usageFreshness ?? diagnostics.usageFreshness,
|
||||
showLastSuccessfulUpdate: true,
|
||||
},
|
||||
{
|
||||
label: t("Listing and Metacache"),
|
||||
diagnostic: diagnostics.listingHealth,
|
||||
troubleshooting: t(
|
||||
"Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.",
|
||||
),
|
||||
troubleshootingHref: "https://github.com/rustfs/backlog/issues/1392#issuecomment-5040442761",
|
||||
},
|
||||
{ label: t("Workload Admission"), diagnostic: diagnostics.workloadAdmission },
|
||||
]
|
||||
const hasAttention = rows.some(({ diagnostic }) => ["degraded", "stale", "unknown"].includes(diagnostic.state))
|
||||
const reportedRows = rows.filter(({ diagnostic }) => diagnostic.state !== "not_reported")
|
||||
const attentionCount = rows.filter(({ diagnostic }) =>
|
||||
["degraded", "stale", "unknown"].includes(diagnostic.state),
|
||||
).length
|
||||
const [open, setOpen] = React.useState(hasAttention)
|
||||
const previousHasAttention = React.useRef(hasAttention)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (hasAttention && !previousHasAttention.current) setOpen(true)
|
||||
previousHasAttention.current = hasAttention
|
||||
}, [hasAttention])
|
||||
|
||||
return (
|
||||
<Collapsible open={open} onOpenChange={setOpen} className="group/diagnostics">
|
||||
<Card className="gap-0 py-0 shadow-none">
|
||||
<CardHeader className="grid-cols-[minmax(0,1fr)_auto] gap-4 py-4">
|
||||
<div className="min-w-0">
|
||||
<h2 id="diagnostic-details-title" className="text-base font-semibold">
|
||||
{t("Diagnostic Details")}
|
||||
</h2>
|
||||
<CardDescription>
|
||||
{hasAttention
|
||||
? `${attentionCount} ${t("Diagnostic items need confirmation")}`
|
||||
: reportedRows.length
|
||||
? t("All reported diagnostics are healthy.")
|
||||
: t("No diagnostic sources were reported by the server.")}
|
||||
</CardDescription>
|
||||
</div>
|
||||
<CollapsibleTrigger
|
||||
render={
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="min-h-11 self-start sm:min-h-0"
|
||||
aria-controls="diagnostic-details-content"
|
||||
>
|
||||
<span className="group-data-[state=open]/diagnostics:hidden">{t("Expand")}</span>
|
||||
<span className="hidden group-data-[state=open]/diagnostics:inline">{t("Collapse")}</span>
|
||||
<RiArrowDownSLine
|
||||
data-icon="inline-end"
|
||||
className="transition-transform duration-200 group-data-[state=open]/diagnostics:rotate-180"
|
||||
aria-hidden
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</CardHeader>
|
||||
<CollapsibleContent id="diagnostic-details-content">
|
||||
<Separator />
|
||||
<CardContent className="py-1">
|
||||
<dl aria-labelledby="diagnostic-details-title">
|
||||
{rows.map((row, index) => (
|
||||
<React.Fragment key={row.label}>
|
||||
{index ? <Separator /> : null}
|
||||
<DiagnosticRow
|
||||
label={row.label}
|
||||
diagnostic={row.diagnostic}
|
||||
troubleshooting={row.troubleshooting}
|
||||
troubleshootingHref={row.troubleshootingHref}
|
||||
showLastSuccessfulUpdate={row.showLastSuccessfulUpdate}
|
||||
t={t}
|
||||
locale={locale}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</dl>
|
||||
</CardContent>
|
||||
</CollapsibleContent>
|
||||
</Card>
|
||||
</Collapsible>
|
||||
)
|
||||
}
|
||||
|
||||
export function PerformanceStatusSources({
|
||||
diagnostics,
|
||||
usageFreshness,
|
||||
@@ -138,55 +264,9 @@ export function PerformanceStatusSources({
|
||||
t: Translate
|
||||
locale?: string
|
||||
}) {
|
||||
const notReported: StatusDiagnostic = { state: "not_reported" }
|
||||
const peerHealth = diagnostics?.peerHealth ?? notReported
|
||||
const storageReadiness = diagnostics?.storageReadiness ?? notReported
|
||||
const resolvedUsageFreshness = usageFreshness ?? diagnostics?.usageFreshness ?? notReported
|
||||
const listingHealth = diagnostics?.listingHealth ?? notReported
|
||||
const workloadAdmission = diagnostics?.workloadAdmission ?? notReported
|
||||
const rows = [
|
||||
{ label: t("Peer Health"), diagnostic: peerHealth },
|
||||
{ label: t("Storage Readiness"), diagnostic: storageReadiness },
|
||||
{ label: t("Usage Freshness"), diagnostic: resolvedUsageFreshness, showLastSuccessfulUpdate: true },
|
||||
{
|
||||
label: t("Listing and Metacache"),
|
||||
diagnostic: listingHealth,
|
||||
troubleshooting: t(
|
||||
"Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.",
|
||||
),
|
||||
troubleshootingHref: "https://github.com/rustfs/backlog/issues/1392#issuecomment-5040442761",
|
||||
},
|
||||
{ label: t("Workload Admission"), diagnostic: workloadAdmission },
|
||||
]
|
||||
if (!diagnostics) return null
|
||||
|
||||
return (
|
||||
<Card className="shadow-none">
|
||||
<CardHeader>
|
||||
<h2 id="status-sources-title" className="text-base font-semibold">
|
||||
{t("Status Sources")}
|
||||
</h2>
|
||||
<CardDescription>
|
||||
{t("Review peer, storage, usage, listing, and workload admission health independently.")}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<dl aria-labelledby="status-sources-title">
|
||||
{rows.map((row, index) => (
|
||||
<React.Fragment key={row.label}>
|
||||
{index ? <Separator /> : null}
|
||||
<DiagnosticRow
|
||||
label={row.label}
|
||||
diagnostic={row.diagnostic}
|
||||
troubleshooting={row.troubleshooting}
|
||||
troubleshootingHref={row.troubleshootingHref}
|
||||
showLastSuccessfulUpdate={row.showLastSuccessfulUpdate}
|
||||
t={t}
|
||||
locale={locale}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</dl>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<PerformanceStatusSourcesContent diagnostics={diagnostics} usageFreshness={usageFreshness} t={t} locale={locale} />
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
"use client"
|
||||
|
||||
import { RiCheckboxCircleLine, RiErrorWarningLine, RiQuestionLine } from "@remixicon/react"
|
||||
import type { RunningStatusTopology, StatusDiagnostic } from "@/lib/performance-data"
|
||||
import { getInfrastructureHealthState } from "./performance-infrastructure-card"
|
||||
|
||||
type Translate = (key: string) => string
|
||||
|
||||
export function PerformanceStatusSummary({
|
||||
onlineServers,
|
||||
offlineServers,
|
||||
degradedServers,
|
||||
initializingServers,
|
||||
unknownServers,
|
||||
onlineDisks,
|
||||
offlineDisks,
|
||||
unknownDisks,
|
||||
topology,
|
||||
peerHealth,
|
||||
storageReadiness,
|
||||
t,
|
||||
}: {
|
||||
onlineServers?: number
|
||||
offlineServers?: number
|
||||
degradedServers?: number
|
||||
initializingServers?: number
|
||||
unknownServers?: number
|
||||
onlineDisks?: number
|
||||
offlineDisks?: number
|
||||
unknownDisks?: number
|
||||
topology: RunningStatusTopology
|
||||
peerHealth?: StatusDiagnostic
|
||||
storageReadiness?: StatusDiagnostic
|
||||
t: Translate
|
||||
}) {
|
||||
const healthState = getInfrastructureHealthState({
|
||||
onlineServers,
|
||||
offlineServers,
|
||||
degradedServers,
|
||||
initializingServers,
|
||||
unknownServers,
|
||||
onlineDisks,
|
||||
offlineDisks,
|
||||
unknownDisks,
|
||||
topology,
|
||||
peerHealth,
|
||||
storageReadiness,
|
||||
})
|
||||
const Icon =
|
||||
healthState === "attention" ? RiErrorWarningLine : healthState === "unknown" ? RiQuestionLine : RiCheckboxCircleLine
|
||||
const label =
|
||||
healthState === "attention"
|
||||
? t("Cluster needs attention")
|
||||
: healthState === "unknown"
|
||||
? t("Cluster status is incomplete")
|
||||
: t("Cluster is healthy")
|
||||
|
||||
return (
|
||||
<section
|
||||
className="flex flex-col gap-3 border bg-card p-4 sm:flex-row sm:items-center sm:justify-between"
|
||||
aria-labelledby="running-status-summary-title"
|
||||
>
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<Icon className="size-5 shrink-0" aria-hidden />
|
||||
<h2 id="running-status-summary-title" className="font-semibold">
|
||||
{label}
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground tabular-nums">
|
||||
{t("Servers")}: {onlineServers ?? t("Unknown")} {t("Online")} · {t("Disks")}: {onlineDisks ?? t("Unknown")}{" "}
|
||||
{t("Online")}
|
||||
</p>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
import { RiDatabase2Line } from "@remixicon/react"
|
||||
import { Card, CardContent, CardDescription, CardHeader } from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
import { niceBytes } from "@/lib/functions"
|
||||
import type { StatusDiagnostic } from "@/lib/performance-data"
|
||||
|
||||
export interface UsageStat {
|
||||
label: string
|
||||
@@ -16,6 +18,7 @@ export function PerformanceUsageCard({
|
||||
totalUsedCapacity,
|
||||
usedPercent,
|
||||
usageStats,
|
||||
usageFreshness,
|
||||
t,
|
||||
}: {
|
||||
totalCapacity?: number
|
||||
@@ -23,9 +26,11 @@ export function PerformanceUsageCard({
|
||||
totalUsedCapacity?: number
|
||||
usedPercent?: number
|
||||
usageStats: UsageStat[]
|
||||
usageFreshness?: StatusDiagnostic
|
||||
t: (key: string) => string
|
||||
}) {
|
||||
const formatCapacity = (value?: number) => (value === undefined ? t("Unknown") : niceBytes(String(value)))
|
||||
const freshnessNeedsAttention = usageFreshness?.state === "degraded" || usageFreshness?.state === "stale"
|
||||
|
||||
return (
|
||||
<Card className="h-full shadow-none">
|
||||
@@ -73,6 +78,17 @@ export function PerformanceUsageCard({
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
{freshnessNeedsAttention ? (
|
||||
<div className="flex flex-col gap-2 border-s-2 border-current ps-3 text-muted-foreground">
|
||||
<Badge variant={usageFreshness.state === "degraded" ? "destructive" : "default"}>
|
||||
{usageFreshness.state === "degraded" ? t("Degraded") : t("Stale")}
|
||||
</Badge>
|
||||
<p className="break-words text-xs [overflow-wrap:anywhere]">
|
||||
{usageFreshness.reason ?? t("Previously reported data may be out of date.")}
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<dl className="grid gap-4 sm:grid-cols-3">
|
||||
{usageStats.map((item) => (
|
||||
<div key={item.label} className="min-w-0">
|
||||
|
||||
@@ -24,6 +24,7 @@ import { PerformanceInfrastructureCard } from "../_components/performance-infras
|
||||
import { PerformanceServerList } from "../_components/performance-server-list"
|
||||
import { PerformanceSummaryCards } from "../_components/performance-summary-cards"
|
||||
import { PerformanceStatusSources } from "../_components/performance-status-sources"
|
||||
import { PerformanceStatusSummary } from "../_components/performance-status-summary"
|
||||
import { PerformanceUsageCard } from "../_components/performance-usage-card"
|
||||
|
||||
function formatDuration(seconds: number | undefined, t: (key: string) => string) {
|
||||
@@ -258,11 +259,19 @@ export default function PerformancePage() {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<PerformanceStatusSources
|
||||
diagnostics={diagnosticsInfo}
|
||||
usageFreshness={diagnosticsInfo ? usageFreshness : undefined}
|
||||
<PerformanceStatusSummary
|
||||
onlineServers={serverSummary?.online}
|
||||
offlineServers={serverSummary?.offline}
|
||||
degradedServers={serverSummary?.degraded}
|
||||
initializingServers={serverSummary?.initializing}
|
||||
unknownServers={serverSummary?.unknown}
|
||||
onlineDisks={systemInfo.backend?.onlineDisks}
|
||||
offlineDisks={systemInfo.backend?.offlineDisks}
|
||||
unknownDisks={systemInfo.backend?.unknownDisks}
|
||||
topology={runningStatus.topology}
|
||||
peerHealth={diagnosticsInfo?.peerHealth}
|
||||
storageReadiness={diagnosticsInfo?.storageReadiness}
|
||||
t={t}
|
||||
locale={i18n.resolvedLanguage}
|
||||
/>
|
||||
|
||||
<div className="grid gap-4 xl:grid-cols-2">
|
||||
@@ -277,6 +286,8 @@ export default function PerformancePage() {
|
||||
offlineDisks={systemInfo.backend?.offlineDisks}
|
||||
unknownDisks={systemInfo.backend?.unknownDisks}
|
||||
topology={runningStatus.topology}
|
||||
peerHealth={diagnosticsInfo?.peerHealth}
|
||||
storageReadiness={diagnosticsInfo?.storageReadiness}
|
||||
t={t}
|
||||
/>
|
||||
</div>
|
||||
@@ -288,6 +299,7 @@ export default function PerformancePage() {
|
||||
totalUsedCapacity={totalUsedCapacity}
|
||||
usedPercent={usedPercent}
|
||||
usageStats={usageStats}
|
||||
usageFreshness={diagnosticsInfo ? usageFreshness : undefined}
|
||||
t={t}
|
||||
/>
|
||||
</div>
|
||||
@@ -305,6 +317,13 @@ export default function PerformancePage() {
|
||||
<PerformanceSummaryCards metrics={summaryMetrics} title={t("Inventory")} />
|
||||
|
||||
<PerformanceBackendCard items={backendInfo} t={t} />
|
||||
|
||||
<PerformanceStatusSources
|
||||
diagnostics={diagnosticsInfo}
|
||||
usageFreshness={diagnosticsInfo ? usageFreshness : undefined}
|
||||
t={t}
|
||||
locale={i18n.resolvedLanguage}
|
||||
/>
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
|
||||
+10
-1
@@ -1549,5 +1549,14 @@
|
||||
"Backend guidance": "Backend guidance",
|
||||
"Open the real multi-node metrics verification guide": "Open the real multi-node metrics verification guide",
|
||||
"Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.": "Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.",
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently."
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently.",
|
||||
"Diagnostic Details": "Diagnostic Details",
|
||||
"Diagnostic items need confirmation": "diagnostic items need confirmation",
|
||||
"All reported diagnostics are healthy.": "All reported diagnostics are healthy.",
|
||||
"No diagnostic sources were reported by the server.": "No diagnostic sources were reported by the server.",
|
||||
"Expand": "Expand",
|
||||
"Collapse": "Collapse",
|
||||
"Cluster is healthy": "Cluster is healthy",
|
||||
"Cluster needs attention": "Cluster needs attention",
|
||||
"Cluster status is incomplete": "Cluster status is incomplete"
|
||||
}
|
||||
|
||||
+10
-1
@@ -1549,5 +1549,14 @@
|
||||
"Backend guidance": "Backend guidance",
|
||||
"Open the real multi-node metrics verification guide": "Open the real multi-node metrics verification guide",
|
||||
"Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.": "Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.",
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently."
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently.",
|
||||
"Diagnostic Details": "Diagnostic Details",
|
||||
"Diagnostic items need confirmation": "diagnostic items need confirmation",
|
||||
"All reported diagnostics are healthy.": "All reported diagnostics are healthy.",
|
||||
"No diagnostic sources were reported by the server.": "No diagnostic sources were reported by the server.",
|
||||
"Expand": "Expand",
|
||||
"Collapse": "Collapse",
|
||||
"Cluster is healthy": "Cluster is healthy",
|
||||
"Cluster needs attention": "Cluster needs attention",
|
||||
"Cluster status is incomplete": "Cluster status is incomplete"
|
||||
}
|
||||
|
||||
+10
-1
@@ -1554,5 +1554,14 @@
|
||||
"Backend guidance": "Backend guidance",
|
||||
"Open the real multi-node metrics verification guide": "Open the real multi-node metrics verification guide",
|
||||
"Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.": "Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.",
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently."
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently.",
|
||||
"Diagnostic Details": "Diagnostic Details",
|
||||
"Diagnostic items need confirmation": "diagnostic items need confirmation",
|
||||
"All reported diagnostics are healthy.": "All reported diagnostics are healthy.",
|
||||
"No diagnostic sources were reported by the server.": "No diagnostic sources were reported by the server.",
|
||||
"Expand": "Expand",
|
||||
"Collapse": "Collapse",
|
||||
"Cluster is healthy": "Cluster is healthy",
|
||||
"Cluster needs attention": "Cluster needs attention",
|
||||
"Cluster status is incomplete": "Cluster status is incomplete"
|
||||
}
|
||||
|
||||
+10
-1
@@ -1549,5 +1549,14 @@
|
||||
"Backend guidance": "Backend guidance",
|
||||
"Open the real multi-node metrics verification guide": "Open the real multi-node metrics verification guide",
|
||||
"Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.": "Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.",
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently."
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently.",
|
||||
"Diagnostic Details": "Diagnostic Details",
|
||||
"Diagnostic items need confirmation": "diagnostic items need confirmation",
|
||||
"All reported diagnostics are healthy.": "All reported diagnostics are healthy.",
|
||||
"No diagnostic sources were reported by the server.": "No diagnostic sources were reported by the server.",
|
||||
"Expand": "Expand",
|
||||
"Collapse": "Collapse",
|
||||
"Cluster is healthy": "Cluster is healthy",
|
||||
"Cluster needs attention": "Cluster needs attention",
|
||||
"Cluster status is incomplete": "Cluster status is incomplete"
|
||||
}
|
||||
|
||||
+10
-1
@@ -1549,5 +1549,14 @@
|
||||
"Backend guidance": "Backend guidance",
|
||||
"Open the real multi-node metrics verification guide": "Open the real multi-node metrics verification guide",
|
||||
"Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.": "Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.",
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently."
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently.",
|
||||
"Diagnostic Details": "Diagnostic Details",
|
||||
"Diagnostic items need confirmation": "diagnostic items need confirmation",
|
||||
"All reported diagnostics are healthy.": "All reported diagnostics are healthy.",
|
||||
"No diagnostic sources were reported by the server.": "No diagnostic sources were reported by the server.",
|
||||
"Expand": "Expand",
|
||||
"Collapse": "Collapse",
|
||||
"Cluster is healthy": "Cluster is healthy",
|
||||
"Cluster needs attention": "Cluster needs attention",
|
||||
"Cluster status is incomplete": "Cluster status is incomplete"
|
||||
}
|
||||
|
||||
+10
-1
@@ -1549,5 +1549,14 @@
|
||||
"Backend guidance": "Backend guidance",
|
||||
"Open the real multi-node metrics verification guide": "Open the real multi-node metrics verification guide",
|
||||
"Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.": "Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.",
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently."
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently.",
|
||||
"Diagnostic Details": "Diagnostic Details",
|
||||
"Diagnostic items need confirmation": "diagnostic items need confirmation",
|
||||
"All reported diagnostics are healthy.": "All reported diagnostics are healthy.",
|
||||
"No diagnostic sources were reported by the server.": "No diagnostic sources were reported by the server.",
|
||||
"Expand": "Expand",
|
||||
"Collapse": "Collapse",
|
||||
"Cluster is healthy": "Cluster is healthy",
|
||||
"Cluster needs attention": "Cluster needs attention",
|
||||
"Cluster status is incomplete": "Cluster status is incomplete"
|
||||
}
|
||||
|
||||
+10
-1
@@ -1549,5 +1549,14 @@
|
||||
"Backend guidance": "Backend guidance",
|
||||
"Open the real multi-node metrics verification guide": "Open the real multi-node metrics verification guide",
|
||||
"Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.": "Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.",
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently."
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently.",
|
||||
"Diagnostic Details": "Diagnostic Details",
|
||||
"Diagnostic items need confirmation": "diagnostic items need confirmation",
|
||||
"All reported diagnostics are healthy.": "All reported diagnostics are healthy.",
|
||||
"No diagnostic sources were reported by the server.": "No diagnostic sources were reported by the server.",
|
||||
"Expand": "Expand",
|
||||
"Collapse": "Collapse",
|
||||
"Cluster is healthy": "Cluster is healthy",
|
||||
"Cluster needs attention": "Cluster needs attention",
|
||||
"Cluster status is incomplete": "Cluster status is incomplete"
|
||||
}
|
||||
|
||||
+10
-1
@@ -1549,5 +1549,14 @@
|
||||
"Backend guidance": "Backend guidance",
|
||||
"Open the real multi-node metrics verification guide": "Open the real multi-node metrics verification guide",
|
||||
"Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.": "Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.",
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently."
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently.",
|
||||
"Diagnostic Details": "Diagnostic Details",
|
||||
"Diagnostic items need confirmation": "diagnostic items need confirmation",
|
||||
"All reported diagnostics are healthy.": "All reported diagnostics are healthy.",
|
||||
"No diagnostic sources were reported by the server.": "No diagnostic sources were reported by the server.",
|
||||
"Expand": "Expand",
|
||||
"Collapse": "Collapse",
|
||||
"Cluster is healthy": "Cluster is healthy",
|
||||
"Cluster needs attention": "Cluster needs attention",
|
||||
"Cluster status is incomplete": "Cluster status is incomplete"
|
||||
}
|
||||
|
||||
+10
-1
@@ -1549,5 +1549,14 @@
|
||||
"Backend guidance": "Backend guidance",
|
||||
"Open the real multi-node metrics verification guide": "Open the real multi-node metrics verification guide",
|
||||
"Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.": "Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.",
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently."
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently.",
|
||||
"Diagnostic Details": "Diagnostic Details",
|
||||
"Diagnostic items need confirmation": "diagnostic items need confirmation",
|
||||
"All reported diagnostics are healthy.": "All reported diagnostics are healthy.",
|
||||
"No diagnostic sources were reported by the server.": "No diagnostic sources were reported by the server.",
|
||||
"Expand": "Expand",
|
||||
"Collapse": "Collapse",
|
||||
"Cluster is healthy": "Cluster is healthy",
|
||||
"Cluster needs attention": "Cluster needs attention",
|
||||
"Cluster status is incomplete": "Cluster status is incomplete"
|
||||
}
|
||||
|
||||
+10
-1
@@ -1549,5 +1549,14 @@
|
||||
"Backend guidance": "Backend guidance",
|
||||
"Open the real multi-node metrics verification guide": "Open the real multi-node metrics verification guide",
|
||||
"Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.": "Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.",
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently."
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently.",
|
||||
"Diagnostic Details": "Diagnostic Details",
|
||||
"Diagnostic items need confirmation": "diagnostic items need confirmation",
|
||||
"All reported diagnostics are healthy.": "All reported diagnostics are healthy.",
|
||||
"No diagnostic sources were reported by the server.": "No diagnostic sources were reported by the server.",
|
||||
"Expand": "Expand",
|
||||
"Collapse": "Collapse",
|
||||
"Cluster is healthy": "Cluster is healthy",
|
||||
"Cluster needs attention": "Cluster needs attention",
|
||||
"Cluster status is incomplete": "Cluster status is incomplete"
|
||||
}
|
||||
|
||||
+10
-1
@@ -1549,5 +1549,14 @@
|
||||
"Backend guidance": "Backend guidance",
|
||||
"Open the real multi-node metrics verification guide": "Open the real multi-node metrics verification guide",
|
||||
"Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.": "Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.",
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently."
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently.",
|
||||
"Diagnostic Details": "Diagnostic Details",
|
||||
"Diagnostic items need confirmation": "diagnostic items need confirmation",
|
||||
"All reported diagnostics are healthy.": "All reported diagnostics are healthy.",
|
||||
"No diagnostic sources were reported by the server.": "No diagnostic sources were reported by the server.",
|
||||
"Expand": "Expand",
|
||||
"Collapse": "Collapse",
|
||||
"Cluster is healthy": "Cluster is healthy",
|
||||
"Cluster needs attention": "Cluster needs attention",
|
||||
"Cluster status is incomplete": "Cluster status is incomplete"
|
||||
}
|
||||
|
||||
+10
-1
@@ -1549,5 +1549,14 @@
|
||||
"Backend guidance": "Backend guidance",
|
||||
"Open the real multi-node metrics verification guide": "Open the real multi-node metrics verification guide",
|
||||
"Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.": "Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.",
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently."
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently.",
|
||||
"Diagnostic Details": "Diagnostic Details",
|
||||
"Diagnostic items need confirmation": "diagnostic items need confirmation",
|
||||
"All reported diagnostics are healthy.": "All reported diagnostics are healthy.",
|
||||
"No diagnostic sources were reported by the server.": "No diagnostic sources were reported by the server.",
|
||||
"Expand": "Expand",
|
||||
"Collapse": "Collapse",
|
||||
"Cluster is healthy": "Cluster is healthy",
|
||||
"Cluster needs attention": "Cluster needs attention",
|
||||
"Cluster status is incomplete": "Cluster status is incomplete"
|
||||
}
|
||||
|
||||
+10
-1
@@ -1549,5 +1549,14 @@
|
||||
"Backend guidance": "Backend guidance",
|
||||
"Open the real multi-node metrics verification guide": "Open the real multi-node metrics verification guide",
|
||||
"Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.": "Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.",
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently."
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "Review peer, storage, usage, listing, and workload admission health independently.",
|
||||
"Diagnostic Details": "Diagnostic Details",
|
||||
"Diagnostic items need confirmation": "diagnostic items need confirmation",
|
||||
"All reported diagnostics are healthy.": "All reported diagnostics are healthy.",
|
||||
"No diagnostic sources were reported by the server.": "No diagnostic sources were reported by the server.",
|
||||
"Expand": "Expand",
|
||||
"Collapse": "Collapse",
|
||||
"Cluster is healthy": "Cluster is healthy",
|
||||
"Cluster needs attention": "Cluster needs attention",
|
||||
"Cluster status is incomplete": "Cluster status is incomplete"
|
||||
}
|
||||
|
||||
+10
-1
@@ -1549,5 +1549,14 @@
|
||||
"Backend guidance": "后端提示",
|
||||
"Open the real multi-node metrics verification guide": "打开真实多节点指标确认指南",
|
||||
"Correlate time-windowed walk_dir metrics and metacache logs before treating listing symptoms as a disk failure.": "在将列表症状判断为磁盘故障前,请关联时间窗口内的 walk_dir 指标和元数据缓存日志。",
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "分别检查节点、存储、用量、列表服务和工作负载准入状态。"
|
||||
"Review peer, storage, usage, listing, and workload admission health independently.": "分别检查节点、存储、用量、列表服务和工作负载准入状态。",
|
||||
"Diagnostic Details": "诊断详情",
|
||||
"Diagnostic items need confirmation": "项诊断状态需要确认",
|
||||
"All reported diagnostics are healthy.": "所有已报告的诊断均正常。",
|
||||
"No diagnostic sources were reported by the server.": "服务器未报告诊断来源。",
|
||||
"Expand": "展开",
|
||||
"Collapse": "收起",
|
||||
"Cluster is healthy": "集群运行正常",
|
||||
"Cluster needs attention": "集群需要关注",
|
||||
"Cluster status is incomplete": "集群状态信息不完整"
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ test("performance infrastructure card renders unknown, degraded, and initializin
|
||||
assert.match(source, /initializingServers\?: number/)
|
||||
assert.match(source, /unknownDisks\?: number/)
|
||||
assert.match(source, /topology: RunningStatusTopology/)
|
||||
assert.match(source, /peerHealth\?: StatusDiagnostic/)
|
||||
assert.match(source, /storageReadiness\?: StatusDiagnostic/)
|
||||
assert.match(source, /diagnosticNeedsAttention/)
|
||||
assert.match(source, /diagnosticUncertain/)
|
||||
assert.match(source, /hasIncompleteTopology/)
|
||||
assert.match(source, /t\("Unreported"\)/)
|
||||
assert.match(source, /\{t\("Unknown"\)\}/)
|
||||
@@ -30,6 +34,18 @@ test("performance infrastructure card renders unknown, degraded, and initializin
|
||||
assert.match(source, /value \?\? unavailableLabel/)
|
||||
})
|
||||
|
||||
test("status page routes diagnostics into the relevant operational surfaces", () => {
|
||||
const source = fs.readFileSync("app/(dashboard)/status/page.tsx", "utf8")
|
||||
const usageSource = fs.readFileSync("app/(dashboard)/_components/performance-usage-card.tsx", "utf8")
|
||||
|
||||
assert.match(source, /peerHealth=\{diagnosticsInfo\?\.peerHealth\}/)
|
||||
assert.match(source, /storageReadiness=\{diagnosticsInfo\?\.storageReadiness\}/)
|
||||
assert.match(source, /usageFreshness=\{diagnosticsInfo \? usageFreshness : undefined\}/)
|
||||
assert.match(usageSource, /usageFreshness\?: StatusDiagnostic/)
|
||||
assert.match(usageSource, /usageFreshness\?\.state === "degraded"/)
|
||||
assert.match(usageSource, /usageFreshness\?\.state === "stale"/)
|
||||
})
|
||||
|
||||
test("performance server list treats every health state as a first-class filter", () => {
|
||||
const source = fs.readFileSync("app/(dashboard)/_components/performance-server-list.tsx", "utf8")
|
||||
|
||||
|
||||
@@ -88,6 +88,16 @@ test("status sections expose semantic headings and named progress", () => {
|
||||
assert.match(backendSource, /<h2/)
|
||||
assert.match(backendSource, /Storage Configuration/)
|
||||
assert.match(statusSourcesSource, /<h2/)
|
||||
assert.match(statusSourcesSource, /<Collapsible/)
|
||||
assert.match(statusSourcesSource, /<CollapsibleTrigger/)
|
||||
assert.match(statusSourcesSource, /<CollapsibleContent/)
|
||||
assert.match(statusSourcesSource, /if \(!diagnostics\) return null/)
|
||||
assert.match(statusSourcesSource, /\["degraded", "stale", "unknown"\]\.includes\(diagnostic\.state\)/)
|
||||
assert.match(statusSourcesSource, /open=\{open\}/)
|
||||
assert.match(statusSourcesSource, /onOpenChange=\{setOpen\}/)
|
||||
assert.match(statusSourcesSource, /hasAttention && !previousHasAttention\.current/)
|
||||
assert.match(statusSourcesSource, /Diagnostic Details/)
|
||||
assert.match(statusSourcesSource, /Diagnostic items need confirmation/)
|
||||
assert.match(statusSourcesSource, /Peer Health/)
|
||||
assert.match(statusSourcesSource, /Storage Readiness/)
|
||||
assert.match(statusSourcesSource, /Usage Freshness/)
|
||||
@@ -99,7 +109,8 @@ test("status sections expose semantic headings and named progress", () => {
|
||||
assert.match(statusSourcesSource, /<Badge/)
|
||||
assert.match(statusSourcesSource, /<Separator/)
|
||||
assert.match(statusSourcesSource, /Correlate time-windowed walk_dir metrics and metacache logs/)
|
||||
assert.doesNotMatch(pageSource, /\{diagnosticsInfo \? \(/)
|
||||
assert.doesNotMatch(statusSourcesSource, /const notReported/)
|
||||
assert.ok(pageSource.indexOf("<PerformanceServerList") < pageSource.indexOf("<PerformanceStatusSources"))
|
||||
})
|
||||
|
||||
test("running status uses backend usable free capacity without synthesizing it", () => {
|
||||
|
||||
Reference in New Issue
Block a user