mirror of
https://github.com/rustfs/console.git
synced 2026-08-28 19:47:21 +08:00
fix: normalize performance status responses
This commit is contained in:
@@ -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))}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
Reference in New Issue
Block a user