mirror of
https://github.com/rustfs/console.git
synced 2026-08-28 19:47:21 +08:00
fix(rebalance): show cleanup warnings and progress state (#132)
This commit is contained in:
@@ -49,6 +49,14 @@ function isFailedRebalancePool(pool: PoolSummary) {
|
||||
return ["failed", "error"].includes(pool.status.trim().toLowerCase())
|
||||
}
|
||||
|
||||
function formatCleanupWarnings(pool: PoolSummary) {
|
||||
const warnings = pool.cleanupWarnings
|
||||
if (!warnings.count) return "--"
|
||||
|
||||
const context = [warnings.lastBucket, warnings.lastObject].filter(Boolean).join("/")
|
||||
return context ? `${warnings.count} (${context})` : String(warnings.count)
|
||||
}
|
||||
|
||||
export default function RebalancePage() {
|
||||
const { t } = useTranslation()
|
||||
const dialog = useDialog()
|
||||
@@ -178,6 +186,7 @@ export default function RebalancePage() {
|
||||
...pool,
|
||||
status: statusPool.status || pool.status,
|
||||
progress: statusPool.progress,
|
||||
cleanupWarnings: statusPool.cleanupWarnings,
|
||||
}
|
||||
})
|
||||
}, [overview.pools, status?.pools])
|
||||
@@ -279,24 +288,37 @@ export default function RebalancePage() {
|
||||
<TableHead>{t("Bytes Moved")}</TableHead>
|
||||
<TableHead>{t("Objects")}</TableHead>
|
||||
<TableHead>{t("Versions")}</TableHead>
|
||||
<TableHead>{t("Cleanup Warnings")}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{pools.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="text-center text-muted-foreground">
|
||||
<TableCell colSpan={7} className="text-center text-muted-foreground">
|
||||
{t("No Data")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
pools.map((pool) => (
|
||||
<TableRow key={pool.id} className={cn(isFailedRebalancePool(pool) && "bg-destructive/10")}>
|
||||
<TableRow
|
||||
key={pool.id}
|
||||
className={cn(isFailedRebalancePool(pool) && "bg-destructive/15 hover:bg-destructive/20")}
|
||||
>
|
||||
<TableCell>{pool.name}</TableCell>
|
||||
<TableCell>{pool.status || "--"}</TableCell>
|
||||
<TableCell>{formatBytesValue(pool.used)}</TableCell>
|
||||
<TableCell>{formatBytesValue(pool.progress.bytes)}</TableCell>
|
||||
<TableCell>{formatNumberValue(pool.progress.objects)}</TableCell>
|
||||
<TableCell>{formatNumberValue(pool.progress.versions)}</TableCell>
|
||||
<TableCell>
|
||||
{pool.cleanupWarnings.count > 0 ? (
|
||||
<Badge variant="secondary" className="bg-amber-100 text-amber-900 hover:bg-amber-100">
|
||||
{formatCleanupWarnings(pool)}
|
||||
</Badge>
|
||||
) : (
|
||||
"--"
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
|
||||
+37
-9
@@ -29,6 +29,14 @@ export interface PoolUsageProgress {
|
||||
elapsed: number
|
||||
}
|
||||
|
||||
export interface RebalanceCleanupWarnings {
|
||||
count: number
|
||||
lastMessage?: string
|
||||
lastBucket?: string
|
||||
lastObject?: string
|
||||
lastAt?: string
|
||||
}
|
||||
|
||||
export interface PoolDecommissionSummary {
|
||||
startTime?: string
|
||||
startSize: number
|
||||
@@ -54,6 +62,7 @@ export interface PoolSummary {
|
||||
lastUpdate?: string
|
||||
status: string
|
||||
progress: PoolUsageProgress
|
||||
cleanupWarnings: RebalanceCleanupWarnings
|
||||
decommission: PoolDecommissionSummary
|
||||
}
|
||||
|
||||
@@ -138,6 +147,17 @@ function normalizeProgress(value: unknown): PoolUsageProgress {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeCleanupWarnings(value: unknown): RebalanceCleanupWarnings {
|
||||
const record = asRecord(value)
|
||||
return {
|
||||
count: asNumber(record.count || record.Count),
|
||||
lastMessage: asString(record.lastMsg || record.LastMsg || record.lastMessage || record.LastMessage) || undefined,
|
||||
lastBucket: asString(record.lastBucket || record.LastBucket) || undefined,
|
||||
lastObject: asString(record.lastObject || record.LastObject) || undefined,
|
||||
lastAt: asString(record.lastAt || record.LastAt) || undefined,
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeDecommissionSummary(value: unknown): PoolDecommissionSummary {
|
||||
const record = asRecord(value)
|
||||
return {
|
||||
@@ -185,12 +205,12 @@ function hasExplicitProgressPercent(record: JsonRecord): boolean {
|
||||
|
||||
function deriveRebalanceProgressPercent(status: string, pools: PoolSummary[]): number {
|
||||
const state = normalizeState(status)
|
||||
if (["completed", "complete", "success", "finished"].includes(state)) return 100
|
||||
if (["completed", "complete", "success", "finished"].includes(state) && pools.every(isCompletedRebalancePool)) {
|
||||
return 100
|
||||
}
|
||||
if (pools.length === 0) return 0
|
||||
|
||||
const finishedPools = pools.filter((pool) =>
|
||||
["completed", "complete", "success", "finished"].includes(normalizeState(pool.status)),
|
||||
).length
|
||||
const finishedPools = pools.filter(isCompletedRebalancePool).length
|
||||
return Math.round((finishedPools / pools.length) * 100)
|
||||
}
|
||||
|
||||
@@ -224,6 +244,7 @@ function normalizePool(value: unknown, index: number): PoolSummary {
|
||||
rawUsagePercent > 0 && rawUsagePercent <= 1 ? rawUsagePercent * 100 : rawUsagePercent,
|
||||
)
|
||||
const progress = normalizeProgress(record.progress || record.Progress)
|
||||
const cleanupWarnings = normalizeCleanupWarnings(record.cleanupWarnings || record.CleanupWarnings)
|
||||
const rawId =
|
||||
record.id ?? record.poolId ?? record.pool ?? record.index ?? record.ID ?? record.PoolID ?? record.poolIndex
|
||||
|
||||
@@ -238,6 +259,7 @@ function normalizePool(value: unknown, index: number): PoolSummary {
|
||||
lastUpdate: asString(record.lastUpdate || record.LastUpdate || record.updatedAt || record.UpdatedAt) || undefined,
|
||||
status: pickStatus(record),
|
||||
progress,
|
||||
cleanupWarnings,
|
||||
decommission,
|
||||
}
|
||||
}
|
||||
@@ -327,6 +349,14 @@ function normalizeState(value: string): string {
|
||||
return value.trim().toLowerCase()
|
||||
}
|
||||
|
||||
function isIdleRebalancePool(pool: PoolSummary): boolean {
|
||||
return ["", "none", "not_started", "not-started", "idle"].includes(normalizeState(pool.status))
|
||||
}
|
||||
|
||||
function isCompletedRebalancePool(pool: PoolSummary): boolean {
|
||||
return ["completed", "complete", "success", "finished"].includes(normalizeState(pool.status))
|
||||
}
|
||||
|
||||
function deriveStatusFromPools(pools: PoolSummary[]): string {
|
||||
if (pools.length === 0) return ""
|
||||
|
||||
@@ -340,13 +370,11 @@ function deriveStatusFromPools(pools: PoolSummary[]): string {
|
||||
) {
|
||||
return "running"
|
||||
}
|
||||
if (
|
||||
states.every((state) =>
|
||||
["completed", "complete", "success", "finished", "none", "not_started", "not-started"].includes(state),
|
||||
)
|
||||
) {
|
||||
if (pools.every(isCompletedRebalancePool)) {
|
||||
return "completed"
|
||||
}
|
||||
if (pools.some(isCompletedRebalancePool) && pools.some(isIdleRebalancePool)) return "running"
|
||||
if (pools.every(isIdleRebalancePool)) return ""
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
@@ -115,15 +115,31 @@ test("normalizeRebalanceStatus reads progress and pool details", () => {
|
||||
id: "reb-1",
|
||||
status: "running",
|
||||
progress: { bytes: "1000", objects: "10", versions: "3", eta: "12", elapsed: "6" },
|
||||
pools: [{ id: 1, used: "20", progress: { bytes: "400" } }],
|
||||
pools: [
|
||||
{
|
||||
id: 1,
|
||||
used: "20",
|
||||
progress: { bytes: "400" },
|
||||
cleanupWarnings: {
|
||||
count: 1,
|
||||
lastMsg: "cleanup warning",
|
||||
lastBucket: "test-bucket",
|
||||
lastObject: "object-a",
|
||||
lastAt: "2026-06-12T00:00:00Z",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
assert.equal(status.id, "reb-1")
|
||||
assert.equal(status.progressPercent, 40)
|
||||
assert.equal(status.pools[0]?.progress.bytes, 400)
|
||||
assert.equal(status.pools[0]?.cleanupWarnings.count, 1)
|
||||
assert.equal(status.pools[0]?.cleanupWarnings.lastMessage, "cleanup warning")
|
||||
assert.equal(status.pools[0]?.cleanupWarnings.lastBucket, "test-bucket")
|
||||
})
|
||||
|
||||
test("normalizeRebalanceStatus derives status from pool-only response", () => {
|
||||
test("normalizeRebalanceStatus does not complete mixed completed and idle pools", () => {
|
||||
const status = normalizeRebalanceStatus({
|
||||
id: "3be0831f-4315-4adb-9904-3bb0609b3bfc",
|
||||
pools: [
|
||||
@@ -154,11 +170,11 @@ test("normalizeRebalanceStatus derives status from pool-only response", () => {
|
||||
stoppedAt: null,
|
||||
})
|
||||
|
||||
assert.equal(status.status, "completed")
|
||||
assert.equal(status.progressPercent, 100)
|
||||
assert.equal(status.status, "running")
|
||||
assert.equal(status.progressPercent, 50)
|
||||
assert.equal(status.pools[0]?.status, "Completed")
|
||||
assert.equal(status.pools[1]?.status, "None")
|
||||
assert.equal(deriveRebalanceDisplayState(status, "supported"), "completed")
|
||||
assert.equal(deriveRebalanceDisplayState(status, "supported"), "running")
|
||||
})
|
||||
|
||||
test("normalizeRebalanceStatus aggregates pool progress when totals are missing", () => {
|
||||
|
||||
@@ -16,7 +16,7 @@ test("rebalance page highlights failed pool rows", () => {
|
||||
|
||||
assert.match(source, /function isFailedRebalancePool\(pool: PoolSummary\)/)
|
||||
assert.match(source, /\["failed", "error"\]\.includes\(pool\.status\.trim\(\)\.toLowerCase\(\)\)/)
|
||||
assert.match(source, /className=\{cn\(isFailedRebalancePool\(pool\) && "bg-destructive\/10"\)\}/)
|
||||
assert.match(source, /bg-destructive\/15 hover:bg-destructive\/20/)
|
||||
})
|
||||
|
||||
test("decommission page keeps decommission detail columns", () => {
|
||||
|
||||
Reference in New Issue
Block a user