diff --git a/frontend-modern/src/features/standalone/AgentsMachinesTable.tsx b/frontend-modern/src/features/standalone/AgentsMachinesTable.tsx index 5f59f0a06d..ed3ca6bc00 100644 --- a/frontend-modern/src/features/standalone/AgentsMachinesTable.tsx +++ b/frontend-modern/src/features/standalone/AgentsMachinesTable.tsx @@ -22,6 +22,7 @@ import { StackedDiskBar } from '@/components/Workloads/StackedDiskBar'; import { StackedMemoryBar } from '@/components/Workloads/StackedMemoryBar'; import { ColumnPicker } from '@/components/shared/ColumnPicker'; import { StatusDot } from '@/components/shared/StatusDot'; +import { TemperatureGauge } from '@/components/shared/TemperatureGauge'; import { Table, TableBody, @@ -33,6 +34,7 @@ import { import { TableCard } from '@/components/shared/TableCard'; import { TableCardHeader } from '@/components/shared/TableCardHeader'; import { TooltipPortal } from '@/components/shared/TooltipPortal'; +import { hostOverrideIdCandidates } from '@/features/alerts/alertOverridesModel'; import { compareAgentVersions, formatAgentVersionDisplay, @@ -63,6 +65,7 @@ import type { Resource, ResourceAvailabilityMeta } from '@/types/resource'; import { getActionableAgentIdFromResource } from '@/utils/agentResources'; import { formatBytes, formatSpeed, normalizeDiskArray } from '@/utils/format'; import { STORAGE_KEYS } from '@/utils/localStorage'; +import { useAlertsActivation } from '@/stores/alertsActivation'; import { notificationStore } from '@/stores/notifications'; import { buildMetricKeyForUnifiedResource } from '@/utils/metricsKeys'; import { @@ -72,7 +75,6 @@ import { } from '@/utils/raidPresentation'; import { getSimpleStatusIndicator } from '@/utils/status'; import { asTrimmedString } from '@/utils/stringUtils'; -import { formatTemperature as formatTemperatureValue } from '@/utils/temperature'; import { RESOURCE_METADATA_CHANGED_EVENT, type ResourceMetadataChangedDetail, @@ -230,7 +232,7 @@ const AgentMachineTemperatureCell: Component<{ maxWidth={320} trigger={ —}> - {(value) => formatTemperatureValue(value())} + {(value) => } } > @@ -332,14 +334,15 @@ const AgentMachineNetworkInterfacesList: Component<{ } >
+ {/* Interface counters are cumulative totals, not rates — format as bytes. */} RX{' '} - {formatSpeed(iface.rxBytes ?? 0)} + {formatBytes(iface.rxBytes ?? 0)} TX{' '} - {formatSpeed(iface.txBytes ?? 0)} + {formatBytes(iface.txBytes ?? 0)} @@ -935,6 +938,9 @@ const memoryPercentOnlyFor = (machine: Resource): number | undefined => { const memoryBalloonFor = (machine: Resource): number | undefined => finiteMetric(machine.agent?.memory?.balloon); +const memoryCacheFor = (machine: Resource): number | undefined => + finiteMetric(machine.agent?.memory?.cache); + const memorySwapUsedFor = (machine: Resource): number | undefined => finiteMetric(machine.agent?.memory?.swapUsed); @@ -1231,6 +1237,7 @@ export const AgentsMachinesTable: Component<{ initialStatus: 'all' as PlatformResourceStatusFilter, filter: filterAgentMachineResources, }); + const alertsActivation = useAlertsActivation(); const [sortKey, setSortKey] = createSignal('name'); const [sortDirection, setSortDirection] = createSignal<'asc' | 'desc'>('asc'); const columnVisibility = useColumnVisibility( @@ -1499,11 +1506,19 @@ export const AgentsMachinesTable: Component<{ ? outdatedAgentTelemetryFallbackFor(machine, props.targetAgentVersion) : undefined; const metricsKey = () => buildMetricKeyForUnifiedResource(machine); + const alertResourceIds = () => hostOverrideIdCandidates(machine); + const cpuThresholds = () => + alertsActivation.getMetricThresholds('agent', 'cpu', alertResourceIds()); + const memoryThresholds = () => + alertsActivation.getMetricThresholds('agent', 'memory', alertResourceIds()); + const diskThresholds = () => + alertsActivation.getMetricThresholds('agent', 'disk', alertResourceIds()); const cpuPercent = () => getAgentMachineCpuPercent(machine); const cpuCores = () => cpuCoresFor(machine); const cpuLoadAverage = () => cpuLoadAverageFor(machine); const memoryUsed = () => memoryUsedFor(machine); const memoryTotal = () => memoryTotalFor(machine); + const memoryCache = () => memoryCacheFor(machine); const memoryBalloon = () => memoryBalloonFor(machine); const memorySwapUsed = () => memorySwapUsedFor(machine); const memorySwapTotal = () => memorySwapTotalFor(machine); @@ -1644,6 +1659,7 @@ export const AgentsMachinesTable: Component<{ usage={cpuPercent() ?? 0} loadAverage={cpuLoadAverage()} cores={cpuCores()} + thresholds={cpuThresholds()} resourceId={metricsKey()} /> @@ -1659,9 +1675,11 @@ export const AgentsMachinesTable: Component<{ used={memoryUsed()} total={memoryTotal()} percentOnly={memoryPercentOnly()} + cache={memoryCache()} balloon={memoryBalloon()} swapUsed={memorySwapUsed()} swapTotal={memorySwapTotal()} + thresholds={memoryThresholds()} resourceId={metricsKey()} /> @@ -1677,6 +1695,7 @@ export const AgentsMachinesTable: Component<{ mode={(disks()?.length ?? 0) > 1 ? 'vertical-bars' : undefined} disks={disks()} aggregateDisk={aggregateDisk()} + thresholds={diskThresholds()} /> diff --git a/frontend-modern/src/features/standalone/agentMachineTableModel.ts b/frontend-modern/src/features/standalone/agentMachineTableModel.ts index 427fef3e67..14d70ca71d 100644 --- a/frontend-modern/src/features/standalone/agentMachineTableModel.ts +++ b/frontend-modern/src/features/standalone/agentMachineTableModel.ts @@ -244,11 +244,33 @@ const section = ( rows: AgentMachineTemperatureDetailRow[], ): AgentMachineTemperatureDetailSection[] => (rows.length > 0 ? [{ heading, rows }] : []); +const TEMPERATURE_SECTION_ROW_CAP = 6; + +// Hover tooltips can't scroll, so each section is capped — but truncation must +// be visible, not silent (readings are sorted worst-first, so the cap drops +// the least alarming ones). +const capTemperatureRows = ( + rows: AgentMachineTemperatureDetailRow[], +): AgentMachineTemperatureDetailRow[] => + rows.length > TEMPERATURE_SECTION_ROW_CAP + ? [ + ...rows.slice(0, TEMPERATURE_SECTION_ROW_CAP), + { + label: `+${rows.length - TEMPERATURE_SECTION_ROW_CAP} more`, + value: '', + muted: true, + }, + ] + : rows; + const flattenTemperatureSections = ( sections: readonly AgentMachineTemperatureDetailSection[], ): string => sections - .flatMap((entry) => [entry.heading, ...entry.rows.map((row) => `${row.label}: ${row.value}`)]) + .flatMap((entry) => [ + entry.heading, + ...entry.rows.map((row) => (row.value ? `${row.label}: ${row.value}` : row.label)), + ]) .join('\n'); const getMetricPercent = (metric: Resource['cpu'] | undefined): number | undefined => @@ -441,14 +463,12 @@ export const getAgentMachineTemperatureDetailSections = ( ): AgentMachineTemperatureDetailSection[] => { const sensorReadings = getSensorTemperatureReadings(machine) .sort(byHighestTemperature) - .slice(0, 6) .map((reading) => ({ label: reading.label, value: formatTemperatureValue(reading), })); const activeSmartReadings = getActiveSmartTemperatureReadings(machine) .sort(byHighestTemperature) - .slice(0, 6) .map((reading) => ({ label: `Disk ${reading.label}`, value: formatTemperatureValue(reading), @@ -456,7 +476,6 @@ export const getAgentMachineTemperatureDetailSections = ( const standbySmartReadings = getSmartTemperatureReadings(machine) .filter((reading) => reading.standby) .sort(byLabel) - .slice(0, 6) .map((reading) => ({ label: `Disk ${reading.label}`, value: 'standby', @@ -464,23 +483,24 @@ export const getAgentMachineTemperatureDetailSections = ( })); const fanReadings = getFanReadings(machine) .sort(byLabel) - .slice(0, 6) .map((reading) => ({ label: reading.label, value: `${Math.round(reading.value)} RPM`, })); const additionalReadings = getAdditionalTemperatureReadings(machine) .sort(byHighestTemperature) - .slice(0, 6) .map((reading) => ({ label: reading.label, value: formatTemperatureValue(reading), })); return [ - ...section('Temperatures', sensorReadings), - ...section('Disk Temperatures', [...activeSmartReadings, ...standbySmartReadings]), - ...section('Fan Speeds', fanReadings), - ...section('Other Sensors', additionalReadings), + ...section('Temperatures', capTemperatureRows(sensorReadings)), + ...section( + 'Disk Temperatures', + capTemperatureRows([...activeSmartReadings, ...standbySmartReadings]), + ), + ...section('Fan Speeds', capTemperatureRows(fanReadings)), + ...section('Other Sensors', capTemperatureRows(additionalReadings)), ]; };