diff --git a/frontend-modern/src/components/Workloads/GuestDrawer.tsx b/frontend-modern/src/components/Workloads/GuestDrawer.tsx index 778441a03..c8346ab62 100644 --- a/frontend-modern/src/components/Workloads/GuestDrawer.tsx +++ b/frontend-modern/src/components/Workloads/GuestDrawer.tsx @@ -31,7 +31,6 @@ export const GuestDrawer: Component = (props) => { historyTarget, ipAddresses, guestOsSummary, - memoryExtraLines, networkInterfaces, normalizedTags, setHistoryRange, @@ -88,7 +87,6 @@ export const GuestDrawer: Component = (props) => { hasNetworkInterfaces={hasNetworkInterfaces()} hasOsInfo={hasOsInfo()} ipAddresses={ipAddresses()} - memoryExtraLines={memoryExtraLines()} networkInterfaces={networkInterfaces()} normalizedTags={normalizedTags()} onCustomUrlChange={props.onCustomUrlChange} diff --git a/frontend-modern/src/components/Workloads/GuestDrawerOverview.tsx b/frontend-modern/src/components/Workloads/GuestDrawerOverview.tsx index 3582d99c2..de08361cc 100644 --- a/frontend-modern/src/components/Workloads/GuestDrawerOverview.tsx +++ b/frontend-modern/src/components/Workloads/GuestDrawerOverview.tsx @@ -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; normalizedTags: string[]; onCustomUrlChange?: GuestDrawerProps['onCustomUrlChange']; @@ -255,13 +254,22 @@ export function GuestDrawerOverview(props: GuestDrawerOverviewProps) { - 0}> + 0}>

Memory

-
- {(line) =>
{line}
}
+
+ + {(row) => ( +
+ {row.label} + + {row.value} + +
+ )} +
diff --git a/frontend-modern/src/components/Workloads/guestDrawerModel.ts b/frontend-modern/src/components/Workloads/guestDrawerModel.ts index 344f3e3de..ebb0787a2 100644 --- a/frontend-modern/src/components/Workloads/guestDrawerModel.ts +++ b/frontend-modern/src/components/Workloads/guestDrawerModel.ts @@ -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 => diff --git a/frontend-modern/src/components/Workloads/useGuestDrawerState.ts b/frontend-modern/src/components/Workloads/useGuestDrawerState.ts index 57d645029..6b3e40c00 100644 --- a/frontend-modern/src/components/Workloads/useGuestDrawerState.ts +++ b/frontend-modern/src/components/Workloads/useGuestDrawerState.ts @@ -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,