fix: normalize performance status responses

This commit is contained in:
cxymds
2026-06-26 11:30:01 +08:00
parent 05a44e9867
commit e6a25b91e1
5 changed files with 201 additions and 52 deletions
@@ -5,7 +5,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
export interface BackendInfoItem {
icon: React.ComponentType<{ className?: string; "aria-hidden"?: boolean | "true" | "false" }>
title: string
value?: string
value?: string | number
}
export function PerformanceBackendCard({ items, t }: { items: BackendInfoItem[]; t: (key: string) => string }) {
@@ -203,10 +203,10 @@ export function PerformanceServerList({ servers, t }: { servers: ServerInfo[]; t
<ScrollArea className="w-full">
<div className="flex gap-4 pb-2">
{(server.drives || []).map((drive) => (
<Card key={drive.uuid ?? drive.drive_path} className="min-w-[260px] shadow-none">
<Card key={drive.uuid ?? drive.drive_path ?? drive.path} className="min-w-[260px] shadow-none">
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
{drive.drive_path ?? "--"}
{drive.drive_path ?? drive.path ?? "--"}
</CardTitle>
<CardDescription className="text-xs">
{niceBytes(String(drive.usedspace ?? 0))} / {niceBytes(String(drive.totalspace ?? 0))}
+11 -49
View File
@@ -4,54 +4,16 @@ import * as React from "react"
import { useCallback, useEffect, useState } from "react"
import { useTranslation } from "react-i18next"
import { useSystem } from "@/hooks/use-system"
import {
normalizeStorageInfo,
normalizeSystemInfo,
type DataUsageInfo,
type MetricsInfo,
type StorageInfo,
type SystemInfo,
} from "@/lib/performance-data"
export interface ServerInfo {
endpoint?: string
state?: string
version?: string
uptime?: number
drives?: Array<{
uuid?: string
drive_path?: string
usedspace?: number
totalspace?: number
availspace?: number
state?: string
}>
network?: Record<string, string>
}
export interface SystemInfo {
buckets?: { count?: number }
objects?: { count?: number }
servers?: ServerInfo[]
backend?: {
backendType?: string
onlineDisks?: number
offlineDisks?: number
}
}
export interface DataUsageInfo {
total_capacity?: number
total_used_capacity?: number
}
export interface StorageInfo {
backend?: {
StandardSCParity?: string
RRSCParity?: string
}
}
export interface MetricsInfo {
aggregated?: {
scanner?: {
current_started?: string
cycle_complete_times?: string[]
}
}
}
export type { DataUsageInfo, MetricsInfo, ServerInfo, StorageInfo, SystemInfo } from "@/lib/performance-data"
export function usePerformanceData() {
const { t } = useTranslation()
@@ -78,9 +40,9 @@ export function usePerformanceData() {
systemApi.getStorageInfo(),
])
if (!mountedRef.current) return
setSystemInfo((sysRes as SystemInfo) ?? {})
setSystemInfo(normalizeSystemInfo(sysRes))
setDatausageinfo((usageRes as DataUsageInfo) ?? {})
setStorageinfo((storageRes as StorageInfo) ?? {})
setStorageinfo(normalizeStorageInfo(storageRes))
} catch (err) {
if (!mountedRef.current) return
console.error("Failed to load performance data:", err)
+113
View File
@@ -0,0 +1,113 @@
export interface ServerInfo {
endpoint?: string
state?: string
version?: string
uptime?: number
drives?: Array<{
uuid?: string
drive_path?: string
path?: string
usedspace?: number
totalspace?: number
availspace?: number
state?: string
}>
network?: Record<string, string>
}
export interface SystemInfo {
buckets?: { count?: number }
objects?: { count?: number }
servers?: ServerInfo[]
backend?: {
backendType?: string
onlineDisks?: number
offlineDisks?: number
}
}
export interface DataUsageInfo {
total_capacity?: number
total_used_capacity?: number
}
export interface StorageInfo {
backend?: {
BackendType?: string
StandardSCParity?: string | number
RRSCParity?: string | number
}
}
export interface MetricsInfo {
aggregated?: {
scanner?: {
current_started?: string
cycle_complete_times?: string[]
}
}
}
type JsonRecord = Record<string, unknown>
function asRecord(value: unknown): JsonRecord {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {}
}
function asArray<T>(value: unknown): T[] {
return Array.isArray(value) ? (value as T[]) : []
}
function asNumber(value: unknown): number | undefined {
if (typeof value === "number" && Number.isFinite(value)) return value
if (typeof value === "string" && value.trim() !== "") {
const parsed = Number(value)
if (Number.isFinite(parsed)) return parsed
}
return undefined
}
function asString(value: unknown): string | undefined {
return typeof value === "string" && value.trim() !== "" ? value : undefined
}
function unwrapInfoRecord(value: unknown): JsonRecord {
const record = asRecord(value)
const wrapped = record.info ?? record.Info
return wrapped && typeof wrapped === "object" && !Array.isArray(wrapped) ? (wrapped as JsonRecord) : record
}
function normalizeCountInfo(value: unknown): { count?: number } | undefined {
const record = asRecord(value)
const count = asNumber(record.count ?? record.Count)
return count === undefined ? undefined : { count }
}
export function normalizeSystemInfo(value: unknown): SystemInfo {
const source = unwrapInfoRecord(value)
const backend = asRecord(source.backend ?? source.Backend)
return {
buckets: normalizeCountInfo(source.buckets ?? source.Buckets),
objects: normalizeCountInfo(source.objects ?? source.Objects),
servers: asArray<ServerInfo>(source.servers ?? source.Servers),
backend: {
backendType: asString(backend.backendType ?? backend.BackendType),
onlineDisks: asNumber(backend.onlineDisks ?? backend.OnlineDisks),
offlineDisks: asNumber(backend.offlineDisks ?? backend.OfflineDisks),
},
}
}
export function normalizeStorageInfo(value: unknown): StorageInfo {
const source = unwrapInfoRecord(value)
const backend = asRecord(source.backend ?? source.Backend)
return {
backend: {
BackendType: asString(backend.BackendType ?? backend.backendType),
StandardSCParity: (backend.StandardSCParity ?? backend.standardSCParity) as string | number | undefined,
RRSCParity: (backend.RRSCParity ?? backend.rrSCParity) as string | number | undefined,
},
}
}
+74
View File
@@ -0,0 +1,74 @@
import test from "node:test"
import assert from "node:assert/strict"
import { normalizeStorageInfo, normalizeSystemInfo } from "../../lib/performance-data.ts"
test("normalizeSystemInfo preserves legacy unwrapped info responses", () => {
const info = normalizeSystemInfo({
buckets: { count: 2 },
objects: { count: 40 },
backend: {
backendType: "Erasure",
onlineDisks: 8,
offlineDisks: 1,
},
servers: [{ endpoint: "node-a", state: "online" }],
})
assert.equal(info.buckets?.count, 2)
assert.equal(info.objects?.count, 40)
assert.equal(info.backend?.backendType, "Erasure")
assert.equal(info.backend?.onlineDisks, 8)
assert.equal(info.backend?.offlineDisks, 1)
assert.equal(info.servers?.[0]?.endpoint, "node-a")
})
test("normalizeSystemInfo unwraps RustFS admin discovery info responses", () => {
const info = normalizeSystemInfo({
info: {
buckets: { count: 1, error: null },
objects: { count: 2560, error: null },
backend: {
backendType: "Erasure",
onlineDisks: 12,
offlineDisks: 0,
},
servers: [
{ endpoint: "rustfs-node7", state: "online", drives: [{ state: "ok" }] },
{ endpoint: "10.0.0.9:19000", state: "offline", drives: [] },
],
},
admin_discovery: {
runtimeCapabilities: "/rustfs/admin/v4/runtime/capabilities",
clusterSnapshot: "/rustfs/admin/v4/cluster/snapshot",
extensionsCatalog: "/rustfs/admin/v4/extensions/catalog",
},
})
assert.equal(info.buckets?.count, 1)
assert.equal(info.objects?.count, 2560)
assert.equal(info.backend?.onlineDisks, 12)
assert.equal(info.backend?.offlineDisks, 0)
assert.equal(info.servers?.length, 2)
})
test("normalizeStorageInfo unwraps RustFS admin discovery storage responses", () => {
const storage = normalizeStorageInfo({
info: {
backend: {
BackendType: "Erasure",
StandardSCParity: 4,
RRSCParity: 1,
},
},
admin_discovery: {
runtimeCapabilities: "/rustfs/admin/v4/runtime/capabilities",
clusterSnapshot: "/rustfs/admin/v4/cluster/snapshot",
extensionsCatalog: "/rustfs/admin/v4/extensions/catalog",
},
})
assert.equal(storage.backend?.BackendType, "Erasure")
assert.equal(storage.backend?.StandardSCParity, 4)
assert.equal(storage.backend?.RRSCParity, 1)
})