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)),
];
};