fix(workloads): show RAM usage in guest drawer Memory card

The guest drawer Overview card titled Memory only rendered supplementary
balloon/swap lines, never the primary RAM used/total, so an expanded guest
showed a Memory card containing just a swap figure. It now leads with
Usage/Total/Free (mirroring the node drawer Memory card) and keeps balloon
and swap as trailing rows.

Replaces the string-only getGuestDrawerMemoryExtraLines with a structured
getGuestDrawerMemoryRows and removes the now-dead memoryExtraLines prop from
the guest drawer chain.
This commit is contained in:
rcourtman
2026-05-29 08:54:55 +01:00
parent a631381abb
commit 58e0b6da9d
4 changed files with 49 additions and 21 deletions
@@ -31,7 +31,6 @@ export const GuestDrawer: Component<GuestDrawerProps> = (props) => {
historyTarget,
ipAddresses,
guestOsSummary,
memoryExtraLines,
networkInterfaces,
normalizedTags,
setHistoryRange,
@@ -88,7 +87,6 @@ export const GuestDrawer: Component<GuestDrawerProps> = (props) => {
hasNetworkInterfaces={hasNetworkInterfaces()}
hasOsInfo={hasOsInfo()}
ipAddresses={ipAddresses()}
memoryExtraLines={memoryExtraLines()}
networkInterfaces={networkInterfaces()}
normalizedTags={normalizedTags()}
onCustomUrlChange={props.onCustomUrlChange}
@@ -8,7 +8,7 @@ import { formatBytes, formatUptime } from '@/utils/format';
import type { MetricDisplayThresholds } from '@/utils/metricThresholds';
import { DiskList } from './DiskList';
import { isGuestDrawerVM } from './guestDrawerModel';
import { getGuestDrawerMemoryRows, isGuestDrawerVM } from './guestDrawerModel';
import type { GuestDrawerProps } from './guestDrawerModel';
@@ -23,7 +23,6 @@ interface GuestDrawerOverviewProps {
hasNetworkInterfaces: boolean;
hasOsInfo: boolean;
ipAddresses: string[];
memoryExtraLines?: string[];
networkInterfaces: NonNullable<GuestDrawerProps['guest']['networkInterfaces']>;
normalizedTags: string[];
onCustomUrlChange?: GuestDrawerProps['onCustomUrlChange'];
@@ -255,13 +254,22 @@ export function GuestDrawerOverview(props: GuestDrawerOverviewProps) {
</div>
</Show>
<Show when={props.memoryExtraLines && props.memoryExtraLines.length > 0}>
<Show when={getGuestDrawerMemoryRows(props.guest).length > 0}>
<div class="rounded border border-border bg-surface p-3 shadow-sm">
<h3 class="text-[11px] font-medium uppercase tracking-wide text-base-content mb-2">
Memory
</h3>
<div class="space-y-1 text-[11px] text-muted">
<For each={props.memoryExtraLines}>{(line) => <div>{line}</div>}</For>
<div class="space-y-1.5 text-[11px]">
<For each={getGuestDrawerMemoryRows(props.guest)}>
{(row) => (
<div class="flex items-center justify-between gap-2 min-w-0">
<span class="shrink-0 text-muted">{row.label}</span>
<span class="truncate text-right font-medium text-base-content" title={row.value}>
{row.value}
</span>
</div>
)}
</For>
</div>
</div>
</Show>
@@ -6,7 +6,7 @@ import type {
} from '@/api/charts';
import { formatHistoryChartTooltipValue } from '@/components/shared/historyChartModel';
import { formatBytes } from '@/utils/format';
import { formatBytes, formatPercent } from '@/utils/format';
import { getCanonicalWorkloadId, resolveWorkloadType } from '@/utils/workloads';
type Guest = WorkloadGuest;
@@ -280,19 +280,44 @@ export const getGuestDrawerAgentTitle = (guest: Guest): string => {
return isGuestDrawerVM(guest) ? `QEMU guest agent ${version}` : version;
};
export const getGuestDrawerMemoryExtraLines = (guest: Guest): string[] | undefined => {
if (!guest.memory) return undefined;
export interface GuestDrawerMemoryRow {
label: string;
value: string;
}
const lines: string[] = [];
const total = guest.memory.total ?? 0;
if (guest.memory.balloon && guest.memory.balloon > 0 && guest.memory.balloon !== total) {
lines.push(`Balloon: ${formatBytes(guest.memory.balloon)}`);
// Memory rows for the guest drawer Overview card. Leads with the primary
// RAM usage (Usage / Total / Free) so the "Memory" card lives up to its title
// and matches the node drawer's memory card, then appends balloon/swap when
// present. The collapsed row only shows the RAM gauge; the drawer is where the
// breakdown belongs.
export const getGuestDrawerMemoryRows = (guest: Guest): GuestDrawerMemoryRow[] => {
const memory = guest.memory;
if (!memory) return [];
const rows: GuestDrawerMemoryRow[] = [];
const total = memory.total ?? 0;
const used = memory.used ?? 0;
if (total > 0) {
rows.push({ label: 'Usage', value: `${formatPercent((used / total) * 100)} · ${formatBytes(used)}` });
rows.push({ label: 'Total', value: formatBytes(total) });
if (typeof memory.free === 'number') {
rows.push({ label: 'Free', value: formatBytes(memory.free) });
}
}
if (guest.memory.swapTotal && guest.memory.swapTotal > 0) {
const swapUsed = guest.memory.swapUsed ?? 0;
lines.push(`Swap: ${formatBytes(swapUsed)} / ${formatBytes(guest.memory.swapTotal)}`);
if (memory.balloon && memory.balloon > 0 && memory.balloon !== total) {
rows.push({ label: 'Balloon', value: formatBytes(memory.balloon) });
}
return lines.length > 0 ? lines : undefined;
if (memory.swapTotal && memory.swapTotal > 0) {
rows.push({
label: 'Swap',
value: `${formatBytes(memory.swapUsed ?? 0)} / ${formatBytes(memory.swapTotal)}`,
});
}
return rows;
};
export const hasGuestDrawerFilesystemDetails = (guest: Guest): boolean =>
@@ -29,7 +29,6 @@ import {
getGuestDrawerBackupPresentation,
GUEST_DRAWER_HISTORY_DEFAULT_RANGE,
getGuestDrawerHistoryTarget,
getGuestDrawerMemoryExtraLines,
getGuestDrawerNetworkInterfaces,
hasGuestDrawerFilesystemDetails,
hasGuestDrawerOsInfo,
@@ -78,7 +77,6 @@ export function useGuestDrawerState(props: GuestDrawerProps) {
const agentTitle = createMemo(() => getGuestDrawerAgentTitle(props.guest));
const hasAgentInfo = createMemo(() => agentLabel().length > 0);
const ipAddresses = createMemo(() => props.guest.ipAddresses || []);
const memoryExtraLines = createMemo(() => getGuestDrawerMemoryExtraLines(props.guest));
const hasFilesystemDetails = createMemo(() => hasGuestDrawerFilesystemDetails(props.guest));
const networkInterfaces = createMemo(() => getGuestDrawerNetworkInterfaces(props.guest));
const hasNetworkInterfaces = createMemo(() => networkInterfaces().length > 0);
@@ -158,7 +156,6 @@ export function useGuestDrawerState(props: GuestDrawerProps) {
historyTarget,
historyRange,
ipAddresses,
memoryExtraLines,
networkInterfaces,
normalizedTags,
osName,