Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions app/(dashboard)/status/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import { Button } from "@/components/ui/button"
import { Spinner } from "@/components/ui/spinner"
import { usePerformanceData, type PerformanceDataSource } from "@/hooks/use-performance-data"
import { usePermissions } from "@/hooks/use-permissions"
import { buildRunningStatusView, formatRelativeTime, resolveUsageFreshness } from "@/lib/performance-data"
import {
buildRunningStatusView,
formatRelativeTime,
isScannerCycleActive,
resolveUsageFreshness,
} from "@/lib/performance-data"
import { cn } from "@/lib/utils"
import {
RiArchiveDrawerFill,
Expand Down Expand Up @@ -97,10 +102,9 @@ export default function PerformancePage() {
)

const scannerStartedAt = metricsInfo.aggregated?.scanner?.current_started
const scannerCycle = metricsInfo.aggregated?.scanner?.current_cycle
const scannerCompleteTimes = metricsInfo.aggregated?.scanner?.cycle_complete_times ?? []
const scannerCompletedAt = scannerCompleteTimes.at(-1)
const scannerActive = (scannerCycle ?? 0) > 0
const scannerActive = isScannerCycleActive(metricsInfo)
const scannerStatus = scannerActive ? t("Scanning") : scannerCompletedAt ? t("Idle") : t("Never run")
const scannerDuration = useMemo(() => {
if (!scannerStartedAt) return undefined
Expand Down
12 changes: 11 additions & 1 deletion lib/performance-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export interface MetricsInfo {
aggregated?: {
scanner?: {
current_cycle?: number
current_cycle_active?: boolean
current_started?: string
cycle_complete_times?: string[]
}
Expand Down Expand Up @@ -630,6 +631,7 @@ export function normalizeMetricsInfo(value: unknown): MetricsInfo {
const aggregated = asRecord(source.aggregated ?? source.Aggregated)
const scanner = asRecord(aggregated.scanner ?? aggregated.Scanner)
const currentCycle = asNonNegativeNumber(scanner.current_cycle ?? scanner.currentCycle)
const currentCycleActive = asBoolean(scanner.current_cycle_active ?? scanner.currentCycleActive)
const currentStarted = asTimestamp(scanner.current_started ?? scanner.currentStarted)
const cycleCompleteTimes = asArray<unknown>(scanner.cycle_complete_times ?? scanner.cycleCompleteTimes).flatMap(
(item) => {
Expand All @@ -638,19 +640,27 @@ export function normalizeMetricsInfo(value: unknown): MetricsInfo {
},
)

if (currentCycle === undefined && !currentStarted && !cycleCompleteTimes.length) return {}
if (currentCycle === undefined && currentCycleActive === undefined && !currentStarted && !cycleCompleteTimes.length) {
return {}
}

return {
aggregated: {
scanner: {
...(currentCycle !== undefined ? { current_cycle: currentCycle } : {}),
...(currentCycleActive !== undefined ? { current_cycle_active: currentCycleActive } : {}),
...(currentStarted ? { current_started: currentStarted } : {}),
...(cycleCompleteTimes.length ? { cycle_complete_times: cycleCompleteTimes } : {}),
},
},
}
}

export function isScannerCycleActive(metricsInfo: MetricsInfo): boolean {
const scanner = metricsInfo.aggregated?.scanner
return scanner?.current_cycle_active ?? (scanner?.current_cycle ?? 0) > 0
}

export function summarizeServerStates(
servers: ServerInfo[] | undefined,
diagnostics?: ClusterDiagnostics,
Expand Down
35 changes: 26 additions & 9 deletions tests/lib/performance-data.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import assert from "node:assert/strict"
import {
buildRunningStatusView,
formatRelativeTime,
isScannerCycleActive,
normalizeClusterDiagnostics,
normalizeDataUsageInfo,
normalizeMetricsInfo,
Expand Down Expand Up @@ -246,15 +247,31 @@ test("normalizeMetricsInfo rejects missing and invalid scanner timestamps", () =
)
})

test("normalizeMetricsInfo keeps the scanner cycle state for active and idle displays", () => {
assert.equal(
normalizeMetricsInfo({ aggregated: { scanner: { current_cycle: 3 } } }).aggregated?.scanner?.current_cycle,
3,
)
assert.equal(
normalizeMetricsInfo({ aggregated: { scanner: { current_cycle: 0 } } }).aggregated?.scanner?.current_cycle,
0,
)
test("normalizeMetricsInfo keeps explicit scanner activity and legacy cycle fallback", () => {
const activeFirstCycle = normalizeMetricsInfo({
aggregated: { scanner: { current_cycle: 0, current_cycle_active: true } },
})
assert.equal(activeFirstCycle.aggregated?.scanner?.current_cycle, 0)
assert.equal(activeFirstCycle.aggregated?.scanner?.current_cycle_active, true)
assert.equal(isScannerCycleActive(activeFirstCycle), true)

const explicitIdle = normalizeMetricsInfo({
aggregated: { scanner: { current_cycle: 3, current_cycle_active: false } },
})
assert.equal(explicitIdle.aggregated?.scanner?.current_cycle_active, false)
assert.equal(isScannerCycleActive(explicitIdle), false)

const activityOnly = normalizeMetricsInfo({ aggregated: { scanner: { current_cycle_active: false } } })
assert.equal(activityOnly.aggregated?.scanner?.current_cycle_active, false)

const camelCaseActivity = normalizeMetricsInfo({
aggregated: { scanner: { currentCycle: 0, currentCycleActive: true } },
})
assert.equal(camelCaseActivity.aggregated?.scanner?.current_cycle_active, true)
assert.equal(isScannerCycleActive(camelCaseActivity), true)

assert.equal(isScannerCycleActive(normalizeMetricsInfo({ aggregated: { scanner: { current_cycle: 3 } } })), true)
assert.equal(isScannerCycleActive(normalizeMetricsInfo({ aggregated: { scanner: { current_cycle: 0 } } })), false)
})

test("formatRelativeTime follows the active locale and advances with the clock", () => {
Expand Down
1 change: 1 addition & 0 deletions tests/lib/performance-status-source.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ test("status page passes every normalized admin info state into infrastructure h
assert.match(source, /initializingServers=\{serverSummary\?\.initializing\}/)
assert.match(source, /unknownDisks=\{systemInfo\.backend\?\.unknownDisks\}/)
assert.match(source, /topology=\{runningStatus\.topology\}/)
assert.match(source, /isScannerCycleActive\(metricsInfo\)/)
assert.doesNotMatch(source, /unknownDisks=.*\?\? 0/)
})

Expand Down
2 changes: 1 addition & 1 deletion tests/lib/running-status-safety.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ test("running status distinguishes unknown values and exposes recovery feedback"
assert.match(pageSource, /lastUpdatedAt/)
assert.match(pageSource, /sourceErrors/)
assert.match(pageSource, /order-2 xl:order-3 xl:col-span-2/)
assert.match(pageSource, /scannerCycle/)
assert.match(pageSource, /isScannerCycleActive\(metricsInfo\)/)
assert.match(pageSource, /t\("Scanner Status"\)/)
assert.doesNotMatch(pageSource, /dayjs\(last\)/)
assert.doesNotMatch(pageSource, /t\("Uptime"\)/)
Expand Down