mirror of
https://github.com/rustfs/console.git
synced 2026-10-29 18:44:56 +08:00
fix: support admin info degraded states (#155)
This commit is contained in:
@@ -5,14 +5,20 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
|
||||
export function PerformanceInfrastructureCard({
|
||||
onlineServers,
|
||||
offlineServers,
|
||||
unknownServers,
|
||||
degradedServers,
|
||||
onlineDisks,
|
||||
offlineDisks,
|
||||
unknownDisks,
|
||||
t,
|
||||
}: {
|
||||
onlineServers: number
|
||||
offlineServers: number
|
||||
unknownServers: number
|
||||
degradedServers: number
|
||||
onlineDisks: number
|
||||
offlineDisks: number
|
||||
unknownDisks: number
|
||||
t: (key: string) => string
|
||||
}) {
|
||||
return (
|
||||
@@ -25,7 +31,7 @@ export function PerformanceInfrastructureCard({
|
||||
<div className="grid gap-4 lg:grid-cols-2">
|
||||
<div className="border bg-muted/40 p-4">
|
||||
<p className="text-sm font-medium text-muted-foreground">{t("Servers")}</p>
|
||||
<div className="mt-4 grid gap-3 sm:grid-cols-2">
|
||||
<div className="mt-4 grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<div className="border bg-background p-3">
|
||||
<p className="text-xs text-muted-foreground">{t("Online")}</p>
|
||||
<p className="mt-1 text-xl font-semibold text-foreground">{onlineServers}</p>
|
||||
@@ -34,11 +40,19 @@ export function PerformanceInfrastructureCard({
|
||||
<p className="text-xs text-muted-foreground">{t("Offline")}</p>
|
||||
<p className="mt-1 text-xl font-semibold text-foreground">{offlineServers}</p>
|
||||
</div>
|
||||
<div className="border bg-background p-3">
|
||||
<p className="text-xs text-muted-foreground">{t("Unknown")}</p>
|
||||
<p className="mt-1 text-xl font-semibold text-foreground">{unknownServers}</p>
|
||||
</div>
|
||||
<div className="border bg-background p-3">
|
||||
<p className="text-xs text-muted-foreground">{t("Degraded")}</p>
|
||||
<p className="mt-1 text-xl font-semibold text-foreground">{degradedServers}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border bg-muted/40 p-4">
|
||||
<p className="text-sm font-medium text-muted-foreground">{t("Disks")}</p>
|
||||
<div className="mt-4 grid gap-3 sm:grid-cols-2">
|
||||
<div className="mt-4 grid gap-3 sm:grid-cols-3">
|
||||
<div className="border bg-background p-3">
|
||||
<p className="text-xs text-muted-foreground">{t("Online")}</p>
|
||||
<p className="mt-1 text-xl font-semibold text-foreground">{onlineDisks}</p>
|
||||
@@ -47,6 +61,10 @@ export function PerformanceInfrastructureCard({
|
||||
<p className="text-xs text-muted-foreground">{t("Offline")}</p>
|
||||
<p className="mt-1 text-xl font-semibold text-foreground">{offlineDisks}</p>
|
||||
</div>
|
||||
<div className="border bg-background p-3">
|
||||
<p className="text-xs text-muted-foreground">{t("Unknown")}</p>
|
||||
<p className="mt-1 text-xl font-semibold text-foreground">{unknownDisks}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Progress } from "@/components/ui/progress"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { niceBytes } from "@/lib/functions"
|
||||
import { normalizeServerHealthState, type ServerHealthState } from "@/lib/performance-data"
|
||||
import { cn } from "@/lib/utils"
|
||||
import dayjs from "dayjs"
|
||||
import relativeTime from "dayjs/plugin/relativeTime"
|
||||
@@ -29,13 +30,39 @@ type PerformanceServerSort =
|
||||
| "endpoint-desc"
|
||||
| "status-online"
|
||||
| "status-offline"
|
||||
| "status-unknown"
|
||||
| "status-degraded"
|
||||
| "uptime-desc"
|
||||
| "uptime-asc"
|
||||
|
||||
type PerformanceServerFilter = "all" | "online" | "offline"
|
||||
type PerformanceServerFilter = "all" | ServerHealthState
|
||||
|
||||
function isOnlineServer(server: ServerInfo) {
|
||||
return (server.state ?? "").toLowerCase() === "online"
|
||||
const serverStates: ServerHealthState[] = ["online", "offline", "unknown", "degraded"]
|
||||
|
||||
function getServerState(server: ServerInfo) {
|
||||
return normalizeServerHealthState(server.state)
|
||||
}
|
||||
|
||||
function getServerStateLabel(state: ServerHealthState, t: (key: string) => string) {
|
||||
if (state === "online") return t("Online")
|
||||
if (state === "offline") return t("Offline")
|
||||
if (state === "degraded") return t("Degraded")
|
||||
return t("Unknown")
|
||||
}
|
||||
|
||||
function getServerStateTone(state: ServerHealthState) {
|
||||
if (state === "online") return "bg-primary"
|
||||
if (state === "offline") return "bg-destructive"
|
||||
if (state === "degraded") return "bg-amber-500"
|
||||
return "bg-muted-foreground"
|
||||
}
|
||||
|
||||
function getServerStateButtonClass(state: ServerHealthState, active: boolean) {
|
||||
if (!active) return "shadow-none"
|
||||
if (state === "offline") return "border-destructive/40 text-destructive shadow-none"
|
||||
if (state === "degraded") return "border-amber-500/50 text-amber-700 shadow-none dark:text-amber-300"
|
||||
if (state === "unknown") return "border-muted-foreground/40 text-muted-foreground shadow-none"
|
||||
return "shadow-none"
|
||||
}
|
||||
|
||||
function compareEndpoint(left: ServerInfo, right: ServerInfo, direction: "asc" | "desc") {
|
||||
@@ -68,7 +95,17 @@ function compareUptime(left: ServerInfo, right: ServerInfo, direction: "asc" | "
|
||||
export function PerformanceServerList({ servers, t }: { servers: ServerInfo[]; t: (key: string) => string }) {
|
||||
const [sortBy, setSortBy] = React.useState<PerformanceServerSort>("default")
|
||||
const [filterBy, setFilterBy] = React.useState<PerformanceServerFilter>("all")
|
||||
const onlineCount = servers.filter((server) => isOnlineServer(server)).length
|
||||
const stateCounts = React.useMemo(
|
||||
() =>
|
||||
servers.reduce(
|
||||
(counts, server) => {
|
||||
counts[getServerState(server)] += 1
|
||||
return counts
|
||||
},
|
||||
{ online: 0, offline: 0, unknown: 0, degraded: 0 } satisfies Record<ServerHealthState, number>,
|
||||
),
|
||||
[servers],
|
||||
)
|
||||
|
||||
const visibleServers = React.useMemo(() => {
|
||||
const rows = servers
|
||||
@@ -77,8 +114,7 @@ export function PerformanceServerList({ servers, t }: { servers: ServerInfo[]; t
|
||||
originalIndex,
|
||||
}))
|
||||
.filter(({ server }) => {
|
||||
if (filterBy === "online") return isOnlineServer(server)
|
||||
if (filterBy === "offline") return !isOnlineServer(server)
|
||||
if (filterBy !== "all") return getServerState(server) === filterBy
|
||||
return true
|
||||
})
|
||||
|
||||
@@ -90,13 +126,25 @@ export function PerformanceServerList({ servers, t }: { servers: ServerInfo[]; t
|
||||
return compareEndpoint(left.server, right.server, "desc") || left.originalIndex - right.originalIndex
|
||||
case "status-online":
|
||||
return (
|
||||
Number(isOnlineServer(right.server)) - Number(isOnlineServer(left.server)) ||
|
||||
Number(getServerState(right.server) === "online") - Number(getServerState(left.server) === "online") ||
|
||||
compareEndpoint(left.server, right.server, "asc") ||
|
||||
left.originalIndex - right.originalIndex
|
||||
)
|
||||
case "status-offline":
|
||||
return (
|
||||
Number(isOnlineServer(left.server)) - Number(isOnlineServer(right.server)) ||
|
||||
Number(getServerState(right.server) === "offline") - Number(getServerState(left.server) === "offline") ||
|
||||
compareEndpoint(left.server, right.server, "asc") ||
|
||||
left.originalIndex - right.originalIndex
|
||||
)
|
||||
case "status-unknown":
|
||||
return (
|
||||
Number(getServerState(right.server) === "unknown") - Number(getServerState(left.server) === "unknown") ||
|
||||
compareEndpoint(left.server, right.server, "asc") ||
|
||||
left.originalIndex - right.originalIndex
|
||||
)
|
||||
case "status-degraded":
|
||||
return (
|
||||
Number(getServerState(right.server) === "degraded") - Number(getServerState(left.server) === "degraded") ||
|
||||
compareEndpoint(left.server, right.server, "asc") ||
|
||||
left.originalIndex - right.originalIndex
|
||||
)
|
||||
@@ -125,24 +173,18 @@ export function PerformanceServerList({ servers, t }: { servers: ServerInfo[]; t
|
||||
{t("Total")}: {visibleServers.length}/{servers.length}
|
||||
</span>
|
||||
<div className="flex flex-wrap items-center justify-end gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant={filterBy === "online" ? "secondary" : "outline"}
|
||||
className="shadow-none"
|
||||
onClick={() => setFilterBy((current) => (current === "online" ? "all" : "online"))}
|
||||
>
|
||||
{t("Online")} ({onlineCount})
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant={filterBy === "offline" ? "secondary" : "outline"}
|
||||
className={cn("shadow-none", filterBy === "offline" ? "border-destructive/40 text-destructive" : "")}
|
||||
onClick={() => setFilterBy((current) => (current === "offline" ? "all" : "offline"))}
|
||||
>
|
||||
{t("Offline")} ({servers.length - onlineCount})
|
||||
</Button>
|
||||
{serverStates.map((state) => (
|
||||
<Button
|
||||
key={state}
|
||||
type="button"
|
||||
size="sm"
|
||||
variant={filterBy === state ? "secondary" : "outline"}
|
||||
className={getServerStateButtonClass(state, filterBy === state)}
|
||||
onClick={() => setFilterBy((current) => (current === state ? "all" : state))}
|
||||
>
|
||||
{getServerStateLabel(state, t)} ({stateCounts[state]})
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 self-start sm:self-auto">
|
||||
<span className="text-sm text-muted-foreground">{t("Sort by")}</span>
|
||||
@@ -156,6 +198,8 @@ export function PerformanceServerList({ servers, t }: { servers: ServerInfo[]; t
|
||||
<SelectItem value="endpoint-desc">{`${t("Endpoint")} ↓`}</SelectItem>
|
||||
<SelectItem value="status-online">{`${t("Status")} · ${t("Online")}`}</SelectItem>
|
||||
<SelectItem value="status-offline">{`${t("Status")} · ${t("Offline")}`}</SelectItem>
|
||||
<SelectItem value="status-unknown">{`${t("Status")} · ${t("Unknown")}`}</SelectItem>
|
||||
<SelectItem value="status-degraded">{`${t("Status")} · ${t("Degraded")}`}</SelectItem>
|
||||
<SelectItem value="uptime-desc">{`${t("Uptime")} ↓`}</SelectItem>
|
||||
<SelectItem value="uptime-asc">{`${t("Uptime")} ↑`}</SelectItem>
|
||||
</SelectContent>
|
||||
@@ -174,12 +218,12 @@ export function PerformanceServerList({ servers, t }: { servers: ServerInfo[]; t
|
||||
<div className="flex flex-col gap-2 text-start sm:flex-row sm:items-center sm:gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex h-2 w-2 rounded-full",
|
||||
server.state === "online" ? "bg-primary" : "bg-destructive",
|
||||
)}
|
||||
className={cn("inline-flex h-2 w-2 rounded-full", getServerStateTone(getServerState(server)))}
|
||||
/>
|
||||
<span className="font-semibold">{server.endpoint ?? "--"}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{getServerStateLabel(getServerState(server), t)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-3 text-xs text-muted-foreground">
|
||||
<span>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button"
|
||||
import { Spinner } from "@/components/ui/spinner"
|
||||
import { usePerformanceData } from "@/hooks/use-performance-data"
|
||||
import { niceBytes } from "@/lib/functions"
|
||||
import { normalizeServerHealthState, type ServerHealthState } from "@/lib/performance-data"
|
||||
import {
|
||||
RiArchiveDrawerFill,
|
||||
RiArchiveLine,
|
||||
@@ -115,15 +116,20 @@ export default function PerformancePage() {
|
||||
[t, fromLastStartTime, fromLastScanTime, lastScanTime],
|
||||
)
|
||||
|
||||
const onlineServers = useMemo(
|
||||
() => (systemInfo?.servers || []).filter((s) => s.state === "online").length,
|
||||
[systemInfo],
|
||||
)
|
||||
const serverCounts = useMemo(() => {
|
||||
const counts: Record<ServerHealthState, number> = {
|
||||
online: 0,
|
||||
offline: 0,
|
||||
unknown: 0,
|
||||
degraded: 0,
|
||||
}
|
||||
|
||||
const offlineServers = useMemo(
|
||||
() => (systemInfo?.servers || []).filter((s) => s.state === "offline").length,
|
||||
[systemInfo],
|
||||
)
|
||||
for (const server of systemInfo?.servers ?? []) {
|
||||
counts[normalizeServerHealthState(server.state)] += 1
|
||||
}
|
||||
|
||||
return counts
|
||||
}, [systemInfo])
|
||||
|
||||
const backendInfo = useMemo(
|
||||
() => [
|
||||
@@ -204,10 +210,13 @@ export default function PerformancePage() {
|
||||
/>
|
||||
|
||||
<PerformanceInfrastructureCard
|
||||
onlineServers={onlineServers}
|
||||
offlineServers={offlineServers}
|
||||
onlineServers={serverCounts.online}
|
||||
offlineServers={serverCounts.offline}
|
||||
unknownServers={serverCounts.unknown}
|
||||
degradedServers={serverCounts.degraded}
|
||||
onlineDisks={systemInfo?.backend?.onlineDisks ?? 0}
|
||||
offlineDisks={systemInfo?.backend?.offlineDisks ?? 0}
|
||||
unknownDisks={systemInfo?.backend?.unknownDisks ?? 0}
|
||||
t={t}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1182,6 +1182,7 @@
|
||||
"Understanding Key Types": "فهم أنواع المفاتيح",
|
||||
"Unhealthy": "غير سليم",
|
||||
"Unknown": "غير معروف",
|
||||
"Degraded": "متدهور",
|
||||
"Unknown Folder": "مجلد غير معروف",
|
||||
"Unlimited": "غير محدود",
|
||||
"Unnamed Site": "موقع بدون اسم",
|
||||
|
||||
@@ -1182,6 +1182,7 @@
|
||||
"Understanding Key Types": "Schlüsseltypen verstehen",
|
||||
"Unhealthy": "Fehlerhaft",
|
||||
"Unknown": "Unbekannt",
|
||||
"Degraded": "Beeinträchtigt",
|
||||
"Unknown Folder": "Unbekannter Ordner",
|
||||
"Unlimited": "Unbegrenzt",
|
||||
"Unnamed Site": "Unbenannte Site",
|
||||
|
||||
@@ -1182,6 +1182,7 @@
|
||||
"Understanding Key Types": "Understanding Key Types",
|
||||
"Unhealthy": "Unhealthy",
|
||||
"Unknown": "Unknown",
|
||||
"Degraded": "Degraded",
|
||||
"Unknown Folder": "Unknown Folder",
|
||||
"Unlimited": "Unlimited",
|
||||
"Unnamed Site": "Unnamed Site",
|
||||
|
||||
@@ -1182,6 +1182,7 @@
|
||||
"Understanding Key Types": "Comprensión de Tipos de Clave",
|
||||
"Unhealthy": "Con problemas",
|
||||
"Unknown": "Desconocido",
|
||||
"Degraded": "Degradado",
|
||||
"Unknown Folder": "Carpeta Desconocida",
|
||||
"Unlimited": "Ilimitado",
|
||||
"Unnamed Site": "Sitio sin nombre",
|
||||
|
||||
@@ -1182,6 +1182,7 @@
|
||||
"Understanding Key Types": "Comprendre les types de clés",
|
||||
"Unhealthy": "Défaillant",
|
||||
"Unknown": "Inconnu",
|
||||
"Degraded": "Dégradé",
|
||||
"Unknown Folder": "Dossier inconnu",
|
||||
"Unlimited": "Illimité",
|
||||
"Unnamed Site": "Site sans nom",
|
||||
|
||||
@@ -1182,6 +1182,7 @@
|
||||
"Understanding Key Types": "Memahami Tipe Kunci",
|
||||
"Unhealthy": "Tidak sehat",
|
||||
"Unknown": "Tidak Diketahui",
|
||||
"Degraded": "Terdegradasi",
|
||||
"Unknown Folder": "Folder Tidak Diketahui",
|
||||
"Unlimited": "Tidak Terbatas",
|
||||
"Unnamed Site": "Situs tanpa nama",
|
||||
|
||||
@@ -1182,6 +1182,7 @@
|
||||
"Understanding Key Types": "Comprensione tipi chiave",
|
||||
"Unhealthy": "Non integro",
|
||||
"Unknown": "Sconosciuto",
|
||||
"Degraded": "Degradato",
|
||||
"Unknown Folder": "Cartella sconosciuta",
|
||||
"Unlimited": "Illimitato",
|
||||
"Unnamed Site": "Sito senza nome",
|
||||
|
||||
@@ -1182,6 +1182,7 @@
|
||||
"Understanding Key Types": "キータイプの理解",
|
||||
"Unhealthy": "異常",
|
||||
"Unknown": "不明",
|
||||
"Degraded": "劣化",
|
||||
"Unknown Folder": "不明なフォルダ",
|
||||
"Unlimited": "無制限",
|
||||
"Unnamed Site": "無名のサイト",
|
||||
|
||||
@@ -1182,6 +1182,7 @@
|
||||
"Understanding Key Types": "키 유형 이해",
|
||||
"Unhealthy": "비정상",
|
||||
"Unknown": "알 수 없음",
|
||||
"Degraded": "성능 저하",
|
||||
"Unknown Folder": "알 수 없는 폴더",
|
||||
"Unlimited": "무제한",
|
||||
"Unnamed Site": "이름 없는 사이트",
|
||||
|
||||
@@ -1182,6 +1182,7 @@
|
||||
"Understanding Key Types": "Entendendo Tipos de Chave",
|
||||
"Unhealthy": "Com problemas",
|
||||
"Unknown": "Desconhecido",
|
||||
"Degraded": "Degradado",
|
||||
"Unknown Folder": "Pasta Desconhecida",
|
||||
"Unlimited": "Ilimitado",
|
||||
"Unnamed Site": "Site sem nome",
|
||||
|
||||
@@ -1182,6 +1182,7 @@
|
||||
"Understanding Key Types": "Понимание типов ключей",
|
||||
"Unhealthy": "Неисправен",
|
||||
"Unknown": "Неизвестно",
|
||||
"Degraded": "Деградировано",
|
||||
"Unknown Folder": "Неизвестная папка",
|
||||
"Unlimited": "Неограниченно",
|
||||
"Unnamed Site": "Сайт без названия",
|
||||
|
||||
@@ -1182,6 +1182,7 @@
|
||||
"Understanding Key Types": "Anahtar Türlerini Anlamak",
|
||||
"Unhealthy": "Sağlıksız",
|
||||
"Unknown": "Bilinmeyen",
|
||||
"Degraded": "Bozulmuş",
|
||||
"Unknown Folder": "Bilinmeyen Klasör",
|
||||
"Unlimited": "Sınırsız",
|
||||
"Unnamed Site": "Adsız Site",
|
||||
|
||||
@@ -1182,6 +1182,7 @@
|
||||
"Understanding Key Types": "Tìm hiểu các loại khóa",
|
||||
"Unhealthy": "Không khỏe mạnh",
|
||||
"Unknown": "Không xác định",
|
||||
"Degraded": "Suy giảm",
|
||||
"Unknown Folder": "Thư mục không xác định",
|
||||
"Unlimited": "Không giới hạn",
|
||||
"Unnamed Site": "Site chưa đặt tên",
|
||||
|
||||
@@ -1182,6 +1182,7 @@
|
||||
"Understanding Key Types": "了解密钥类型",
|
||||
"Unhealthy": "异常",
|
||||
"Unknown": "未知",
|
||||
"Degraded": "降级",
|
||||
"Unknown Folder": "未知文件夹",
|
||||
"Unlimited": "无限制",
|
||||
"Unnamed Site": "未命名站点",
|
||||
|
||||
@@ -23,6 +23,7 @@ export interface SystemInfo {
|
||||
backendType?: string
|
||||
onlineDisks?: number
|
||||
offlineDisks?: number
|
||||
unknownDisks?: number
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +51,8 @@ export interface MetricsInfo {
|
||||
|
||||
type JsonRecord = Record<string, unknown>
|
||||
|
||||
export type ServerHealthState = "online" | "offline" | "unknown" | "degraded"
|
||||
|
||||
function asRecord(value: unknown): JsonRecord {
|
||||
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {}
|
||||
}
|
||||
@@ -71,6 +74,14 @@ function asString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim() !== "" ? value : undefined
|
||||
}
|
||||
|
||||
export function normalizeServerHealthState(state?: string | null): ServerHealthState {
|
||||
const normalized = state?.trim().toLowerCase()
|
||||
if (normalized === "online" || normalized === "offline" || normalized === "unknown" || normalized === "degraded") {
|
||||
return normalized
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
function unwrapInfoRecord(value: unknown): JsonRecord {
|
||||
const record = asRecord(value)
|
||||
const wrapped = record.info ?? record.Info
|
||||
@@ -95,6 +106,7 @@ export function normalizeSystemInfo(value: unknown): SystemInfo {
|
||||
backendType: asString(backend.backendType ?? backend.BackendType),
|
||||
onlineDisks: asNumber(backend.onlineDisks ?? backend.OnlineDisks),
|
||||
offlineDisks: asNumber(backend.offlineDisks ?? backend.OfflineDisks),
|
||||
unknownDisks: asNumber(backend.unknownDisks ?? backend.UnknownDisks),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
import test from "node:test"
|
||||
import assert from "node:assert/strict"
|
||||
|
||||
import { normalizeStorageInfo, normalizeSystemInfo } from "../../lib/performance-data.ts"
|
||||
import { normalizeServerHealthState, normalizeStorageInfo, normalizeSystemInfo } from "../../lib/performance-data.ts"
|
||||
|
||||
test("normalizeServerHealthState preserves backend admin info states", () => {
|
||||
assert.equal(normalizeServerHealthState("online"), "online")
|
||||
assert.equal(normalizeServerHealthState("offline"), "offline")
|
||||
assert.equal(normalizeServerHealthState("unknown"), "unknown")
|
||||
assert.equal(normalizeServerHealthState("degraded"), "degraded")
|
||||
assert.equal(normalizeServerHealthState(" Degraded "), "degraded")
|
||||
assert.equal(normalizeServerHealthState("initializing"), "unknown")
|
||||
assert.equal(normalizeServerHealthState(undefined), "unknown")
|
||||
})
|
||||
|
||||
test("normalizeSystemInfo preserves legacy unwrapped info responses", () => {
|
||||
const info = normalizeSystemInfo({
|
||||
@@ -11,8 +21,13 @@ test("normalizeSystemInfo preserves legacy unwrapped info responses", () => {
|
||||
backendType: "Erasure",
|
||||
onlineDisks: 8,
|
||||
offlineDisks: 1,
|
||||
unknownDisks: 1,
|
||||
},
|
||||
servers: [{ endpoint: "node-a", state: "online" }],
|
||||
servers: [
|
||||
{ endpoint: "node-a", state: "online" },
|
||||
{ endpoint: "node-b", state: "unknown" },
|
||||
{ endpoint: "node-c", state: "degraded" },
|
||||
],
|
||||
})
|
||||
|
||||
assert.equal(info.buckets?.count, 2)
|
||||
@@ -20,7 +35,10 @@ test("normalizeSystemInfo preserves legacy unwrapped info responses", () => {
|
||||
assert.equal(info.backend?.backendType, "Erasure")
|
||||
assert.equal(info.backend?.onlineDisks, 8)
|
||||
assert.equal(info.backend?.offlineDisks, 1)
|
||||
assert.equal(info.backend?.unknownDisks, 1)
|
||||
assert.equal(info.servers?.[0]?.endpoint, "node-a")
|
||||
assert.equal(info.servers?.[1]?.state, "unknown")
|
||||
assert.equal(info.servers?.[2]?.state, "degraded")
|
||||
})
|
||||
|
||||
test("normalizeSystemInfo unwraps RustFS admin discovery info responses", () => {
|
||||
@@ -32,10 +50,12 @@ test("normalizeSystemInfo unwraps RustFS admin discovery info responses", () =>
|
||||
backendType: "Erasure",
|
||||
onlineDisks: 12,
|
||||
offlineDisks: 0,
|
||||
unknownDisks: 1,
|
||||
},
|
||||
servers: [
|
||||
{ endpoint: "rustfs-node7", state: "online", drives: [{ state: "ok" }] },
|
||||
{ endpoint: "10.0.0.9:19000", state: "offline", drives: [] },
|
||||
{ endpoint: "rustfs-node8", state: "degraded", drives: [{ state: "ok" }] },
|
||||
],
|
||||
},
|
||||
admin_discovery: {
|
||||
@@ -49,7 +69,9 @@ test("normalizeSystemInfo unwraps RustFS admin discovery info responses", () =>
|
||||
assert.equal(info.objects?.count, 2560)
|
||||
assert.equal(info.backend?.onlineDisks, 12)
|
||||
assert.equal(info.backend?.offlineDisks, 0)
|
||||
assert.equal(info.servers?.length, 2)
|
||||
assert.equal(info.backend?.unknownDisks, 1)
|
||||
assert.equal(info.servers?.length, 3)
|
||||
assert.equal(info.servers?.[2]?.state, "degraded")
|
||||
})
|
||||
|
||||
test("normalizeStorageInfo unwraps RustFS admin discovery storage responses", () => {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import test from "node:test"
|
||||
import assert from "node:assert/strict"
|
||||
import fs from "node:fs"
|
||||
|
||||
test("status page passes unknown and degraded admin info states into infrastructure health", () => {
|
||||
const source = fs.readFileSync("app/(dashboard)/status/page.tsx", "utf8")
|
||||
|
||||
assert.match(source, /normalizeServerHealthState\(server\.state\)/)
|
||||
assert.match(source, /unknownServers=\{serverCounts\.unknown\}/)
|
||||
assert.match(source, /degradedServers=\{serverCounts\.degraded\}/)
|
||||
assert.match(source, /unknownDisks=\{systemInfo\?\.backend\?\.unknownDisks \?\? 0\}/)
|
||||
})
|
||||
|
||||
test("performance infrastructure card renders unknown and degraded buckets", () => {
|
||||
const source = fs.readFileSync("app/(dashboard)/_components/performance-infrastructure-card.tsx", "utf8")
|
||||
|
||||
assert.match(source, /unknownServers: number/)
|
||||
assert.match(source, /degradedServers: number/)
|
||||
assert.match(source, /unknownDisks: number/)
|
||||
assert.match(source, /\{t\("Unknown"\)\}/)
|
||||
assert.match(source, /\{t\("Degraded"\)\}/)
|
||||
})
|
||||
|
||||
test("performance server list treats unknown and degraded as first-class filters", () => {
|
||||
const source = fs.readFileSync("app/(dashboard)/_components/performance-server-list.tsx", "utf8")
|
||||
|
||||
assert.match(source, /const serverStates: ServerHealthState\[\] = \["online", "offline", "unknown", "degraded"\]/)
|
||||
assert.match(source, /getServerState\(server\) === filterBy/)
|
||||
assert.match(source, /status-unknown/)
|
||||
assert.match(source, /status-degraded/)
|
||||
assert.match(source, /getServerStateTone\(getServerState\(server\)\)/)
|
||||
assert.doesNotMatch(source, /!isOnlineServer\(server\)/)
|
||||
})
|
||||
Reference in New Issue
Block a user