mirror of
https://github.com/rustfs/console.git
synced 2026-08-31 01:37:52 +08:00
271 lines
9.1 KiB
TypeScript
271 lines
9.1 KiB
TypeScript
export type ServerHealthState = "online" | "offline" | "degraded" | "initializing" | "unknown"
|
|
|
|
export interface ServerInfo {
|
|
endpoint?: string
|
|
state?: ServerHealthState
|
|
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
|
|
unknownDisks?: 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_cycle?: number
|
|
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 asNonNegativeNumber(value: unknown): number | undefined {
|
|
const number = asNumber(value)
|
|
return number !== undefined && number >= 0 ? number : undefined
|
|
}
|
|
|
|
function asString(value: unknown): string | undefined {
|
|
return typeof value === "string" && value.trim() !== "" ? value : undefined
|
|
}
|
|
|
|
function asStringOrNumber(value: unknown): string | number | undefined {
|
|
return typeof value === "string" || (typeof value === "number" && Number.isFinite(value)) ? value : undefined
|
|
}
|
|
|
|
function asTimestamp(value: unknown): string | undefined {
|
|
const timestamp = asString(value)
|
|
return timestamp && !Number.isNaN(Date.parse(timestamp)) ? timestamp : undefined
|
|
}
|
|
|
|
export function normalizeServerHealthState(value: unknown): ServerHealthState {
|
|
const state = asString(value)?.toLowerCase()
|
|
if (state === "online" || state === "offline" || state === "degraded" || state === "initializing") {
|
|
return state
|
|
}
|
|
return "unknown"
|
|
}
|
|
|
|
function normalizeDrive(value: unknown): NonNullable<ServerInfo["drives"]>[number] | undefined {
|
|
const source = asRecord(value)
|
|
if (!Object.keys(source).length) return undefined
|
|
|
|
return {
|
|
uuid: asString(source.uuid ?? source.UUID),
|
|
drive_path: asString(source.drive_path ?? source.drivePath ?? source.DrivePath),
|
|
path: asString(source.path ?? source.Path),
|
|
usedspace: asNonNegativeNumber(source.usedspace ?? source.usedSpace ?? source.UsedSpace),
|
|
totalspace: asNonNegativeNumber(source.totalspace ?? source.totalSpace ?? source.TotalSpace),
|
|
availspace: asNonNegativeNumber(source.availspace ?? source.availableSpace ?? source.AvailableSpace),
|
|
state: asString(source.state ?? source.State)?.toLowerCase(),
|
|
}
|
|
}
|
|
|
|
function normalizeServer(value: unknown): ServerInfo | undefined {
|
|
const source = asRecord(value)
|
|
if (!Object.keys(source).length) return undefined
|
|
|
|
const network = Object.fromEntries(
|
|
Object.entries(asRecord(source.network ?? source.Network)).flatMap(([key, state]) => {
|
|
const normalizedState = asString(state)?.toLowerCase()
|
|
return normalizedState ? [[key, normalizedState]] : []
|
|
}),
|
|
)
|
|
|
|
return {
|
|
endpoint: asString(source.endpoint ?? source.Endpoint),
|
|
state: normalizeServerHealthState(source.state ?? source.State),
|
|
version: asString(source.version ?? source.Version),
|
|
uptime: asNonNegativeNumber(source.uptime ?? source.Uptime),
|
|
drives: asArray<unknown>(source.drives ?? source.Drives).flatMap((drive) => {
|
|
const normalized = normalizeDrive(drive)
|
|
return normalized ? [normalized] : []
|
|
}),
|
|
network,
|
|
}
|
|
}
|
|
|
|
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)
|
|
if (record.error ?? record.Error) return undefined
|
|
const count = asNonNegativeNumber(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)
|
|
const buckets = normalizeCountInfo(source.buckets ?? source.Buckets)
|
|
const objects = normalizeCountInfo(source.objects ?? source.Objects)
|
|
const rawServers = source.servers ?? source.Servers
|
|
const servers = Array.isArray(rawServers)
|
|
? rawServers.flatMap((server) => {
|
|
const normalized = normalizeServer(server)
|
|
return normalized ? [normalized] : []
|
|
})
|
|
: undefined
|
|
|
|
return {
|
|
...(buckets ? { buckets } : {}),
|
|
...(objects ? { objects } : {}),
|
|
...(servers ? { servers } : {}),
|
|
...(Object.keys(backend).length
|
|
? {
|
|
backend: {
|
|
backendType: asString(backend.backendType ?? backend.BackendType),
|
|
onlineDisks: asNonNegativeNumber(backend.onlineDisks ?? backend.OnlineDisks),
|
|
offlineDisks: asNonNegativeNumber(backend.offlineDisks ?? backend.OfflineDisks),
|
|
unknownDisks: asNonNegativeNumber(backend.unknownDisks ?? backend.UnknownDisks),
|
|
},
|
|
}
|
|
: {}),
|
|
}
|
|
}
|
|
|
|
export function normalizeStorageInfo(value: unknown): StorageInfo {
|
|
const source = unwrapInfoRecord(value)
|
|
const backend = asRecord(source.backend ?? source.Backend)
|
|
if (!Object.keys(backend).length) return {}
|
|
|
|
return {
|
|
backend: {
|
|
BackendType: asString(backend.BackendType ?? backend.backendType),
|
|
StandardSCParity: asStringOrNumber(backend.StandardSCParity ?? backend.standardSCParity),
|
|
RRSCParity: asStringOrNumber(backend.RRSCParity ?? backend.rrSCParity),
|
|
},
|
|
}
|
|
}
|
|
|
|
export function normalizeDataUsageInfo(value: unknown): DataUsageInfo {
|
|
const source = unwrapInfoRecord(value)
|
|
return {
|
|
...(asNonNegativeNumber(source.total_capacity ?? source.totalCapacity ?? source.TotalCapacity) !== undefined
|
|
? { total_capacity: asNonNegativeNumber(source.total_capacity ?? source.totalCapacity ?? source.TotalCapacity) }
|
|
: {}),
|
|
...(asNonNegativeNumber(source.total_used_capacity ?? source.totalUsedCapacity ?? source.TotalUsedCapacity) !==
|
|
undefined
|
|
? {
|
|
total_used_capacity: asNonNegativeNumber(
|
|
source.total_used_capacity ?? source.totalUsedCapacity ?? source.TotalUsedCapacity,
|
|
),
|
|
}
|
|
: {}),
|
|
}
|
|
}
|
|
|
|
export function normalizeMetricsInfo(value: unknown): MetricsInfo {
|
|
const source = unwrapInfoRecord(value)
|
|
const aggregated = asRecord(source.aggregated ?? source.Aggregated)
|
|
const scanner = asRecord(aggregated.scanner ?? aggregated.Scanner)
|
|
const currentCycle = asNonNegativeNumber(scanner.current_cycle ?? scanner.currentCycle)
|
|
const currentStarted = asTimestamp(scanner.current_started ?? scanner.currentStarted)
|
|
const cycleCompleteTimes = asArray<unknown>(scanner.cycle_complete_times ?? scanner.cycleCompleteTimes).flatMap(
|
|
(item) => {
|
|
const timestamp = asTimestamp(item)
|
|
return timestamp ? [timestamp] : []
|
|
},
|
|
)
|
|
|
|
if (currentCycle === undefined && !currentStarted && !cycleCompleteTimes.length) return {}
|
|
|
|
return {
|
|
aggregated: {
|
|
scanner: {
|
|
...(currentCycle !== undefined ? { current_cycle: currentCycle } : {}),
|
|
...(currentStarted ? { current_started: currentStarted } : {}),
|
|
...(cycleCompleteTimes.length ? { cycle_complete_times: cycleCompleteTimes } : {}),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
export function summarizeServerStates(servers: ServerInfo[] | undefined): Record<ServerHealthState, number> {
|
|
const summary: Record<ServerHealthState, number> = {
|
|
online: 0,
|
|
offline: 0,
|
|
degraded: 0,
|
|
initializing: 0,
|
|
unknown: 0,
|
|
}
|
|
|
|
for (const server of servers ?? []) {
|
|
summary[normalizeServerHealthState(server.state)] += 1
|
|
}
|
|
|
|
return summary
|
|
}
|
|
|
|
export function formatRelativeTime(timestamp: string | Date, locale: string | undefined, now = Date.now()) {
|
|
const time = timestamp instanceof Date ? timestamp.getTime() : Date.parse(timestamp)
|
|
if (!Number.isFinite(time)) return undefined
|
|
|
|
const difference = time - now
|
|
const units: Array<[Intl.RelativeTimeFormatUnit, number]> = [
|
|
["year", 31_536_000_000],
|
|
["month", 2_592_000_000],
|
|
["day", 86_400_000],
|
|
["hour", 3_600_000],
|
|
["minute", 60_000],
|
|
["second", 1_000],
|
|
]
|
|
const [unit, size] = units.find(([, unitSize]) => Math.abs(difference) >= unitSize) ?? units.at(-1)!
|
|
return new Intl.RelativeTimeFormat(locale, { numeric: "auto" }).format(Math.round(difference / size), unit)
|
|
}
|