workloads(drawer): seed guest history charts with current values

GuestDrawer.tsx mounted GuestDrawerHistory without a fallbackMetrics
prop, so mergeFallbackHistoryMetrics never fired and any guest with
<2 stored samples in the selected range showed "Collecting history"
indefinitely — even though the row already carries live cpu/memory/
disk/net/diskio values.

Add getGuestDrawerHistoryFallbackMetrics (mirrors the node helper),
project cpu/memory/disk/netin/netout/diskread/diskwrite from the
current Guest snapshot (multiplying the 0-1 cpu fraction up to a
percent so it matches the chart's % scale), and pass it through.
Flat 2-point sparkline now renders immediately and gets replaced by
real history as samples accumulate.
This commit is contained in:
rcourtman
2026-05-16 14:56:57 +01:00
parent 95171ff7bc
commit 5ca05949b8
2 changed files with 42 additions and 3 deletions
@@ -1,6 +1,6 @@
import { Component, Show, Suspense } from 'solid-js';
import { Component, Show, Suspense, createMemo } from 'solid-js';
import { DiscoveryTab } from '../Discovery/DiscoveryTab';
import type { GuestDrawerProps } from './guestDrawerModel';
import { getGuestDrawerHistoryFallbackMetrics, type GuestDrawerProps } from './guestDrawerModel';
import { useGuestDrawerState } from './useGuestDrawerState';
import { GuestDrawerHistory, GuestDrawerHistoryRangeSelect } from './GuestDrawerHistory';
import { GuestDrawerOverview } from './GuestDrawerOverview';
@@ -35,6 +35,9 @@ export const GuestDrawer: Component<GuestDrawerProps> = (props) => {
webInterfaceTargetLabel,
} = useGuestDrawerState(props);
const headingId = () => `guest-drawer-heading-${guestId()}`;
const historyFallbackMetrics = createMemo(() =>
getGuestDrawerHistoryFallbackMetrics(props.guest),
);
return (
<section class="space-y-3" aria-labelledby={headingId()}>
@@ -118,7 +121,11 @@ export const GuestDrawer: Component<GuestDrawerProps> = (props) => {
{hasHistorySupport() && activeTab() === 'history' && (
<div style={{ 'overflow-anchor': 'none' }}>
<GuestDrawerHistory target={historyTarget()} range={historyRange()} />
<GuestDrawerHistory
target={historyTarget()}
range={historyRange()}
fallbackMetrics={historyFallbackMetrics()}
/>
</div>
)}
@@ -52,6 +52,38 @@ export interface GuestDrawerBackupPresentation {
export const isGuestDrawerVM = (guest: Guest): boolean => resolveWorkloadType(guest) === 'vm';
// Fallback current-value metrics for the guest drawer's history charts.
// Mirrors `getNodeDrawerHistoryFallbackMetrics` — supplies a single
// finite value per metric so `mergeFallbackHistoryMetrics` can synthesize
// a flat 2-point line, replacing the "Collecting history" placeholder
// when the metrics-store hasn't yet accumulated 2+ samples for that
// resource within the selected range. Keys must match the `metric`
// strings declared in `GUEST_DRAWER_HISTORY_GROUPS`.
export const getGuestDrawerHistoryFallbackMetrics = (
guest: Guest,
): Record<string, number | undefined> => {
const cpuRaw = typeof guest.cpu === 'number' ? guest.cpu : undefined;
const cpuPercent =
cpuRaw === undefined || !Number.isFinite(cpuRaw)
? undefined
: cpuRaw <= 1.5
? cpuRaw * 100
: cpuRaw;
const memUsage = guest.memory?.usage;
const diskUsage = guest.disk?.usage;
const finite = (value: number | undefined): number | undefined =>
typeof value === 'number' && Number.isFinite(value) ? value : undefined;
return {
cpu: finite(cpuPercent),
memory: finite(memUsage),
disk: finite(diskUsage),
netin: finite(guest.networkIn),
netout: finite(guest.networkOut),
diskread: finite(guest.diskRead),
diskwrite: finite(guest.diskWrite),
};
};
export const GUEST_DRAWER_HISTORY_DEFAULT_RANGE: HistoryTimeRange = '24h';
export const GUEST_DRAWER_HISTORY_GROUPS: GuestDrawerHistoryGroupConfig[] = [