mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 12:15:42 +00:00
machines(table): restore v5 at-a-glance signals lost in the rebuild
The Machines page v5 parity audit (same lens as #1471 on the disks table) found the rebuilt table dropped every threshold-aware signal v5 Hosts had, and the rebuild mislabeled one datum outright. - Temperature cells render through the canonical TemperatureGauge (muted / amber >=70 / red >=80) like the Proxmox nodes table; v6 had been rendering plain neutral text, so a hot machine never stood out. - CPU, memory, and disk bars receive per-resource alert thresholds via hostOverrideIdCandidates + alertsActivation, mirroring the Infrastructure host table; custom thresholds were ignored here. - Network interface tooltips show cumulative RX/TX counters as bytes; they were formatted as rates (a 16-day total read as 1.46 GB/s). - Temperature tooltip sections cap at six rows with a visible +N more marker instead of silently dropping readings (worst-first sort keeps the alarming ones); the section title flattening skips empty values. - The memory bar passes the agent-reported reclaimable cache through, completing the used | cache | free split for machines.
This commit is contained in:
@@ -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={
|
||||
<Show when={positiveTemperature()} fallback={<span class="text-muted">—</span>}>
|
||||
{(value) => formatTemperatureValue(value())}
|
||||
{(value) => <TemperatureGauge value={value()} />}
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
@@ -332,14 +334,15 @@ const AgentMachineNetworkInterfacesList: Component<{
|
||||
}
|
||||
>
|
||||
<div class="mt-1 flex flex-wrap gap-x-3 gap-y-0.5 text-[9px] text-muted">
|
||||
{/* Interface counters are cumulative totals, not rates — format as bytes. */}
|
||||
<Show when={iface.rxBytes !== undefined || iface.txBytes !== undefined}>
|
||||
<span>
|
||||
<span class="font-mono text-emerald-500">RX</span>{' '}
|
||||
{formatSpeed(iface.rxBytes ?? 0)}
|
||||
{formatBytes(iface.rxBytes ?? 0)}
|
||||
</span>
|
||||
<span>
|
||||
<span class="font-mono text-orange-400">TX</span>{' '}
|
||||
{formatSpeed(iface.txBytes ?? 0)}
|
||||
{formatBytes(iface.txBytes ?? 0)}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={iface.speedMbps !== undefined}>
|
||||
@@ -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<AgentMachineSortKey>('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()}
|
||||
/>
|
||||
</Show>
|
||||
@@ -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()}
|
||||
/>
|
||||
</Show>
|
||||
@@ -1677,6 +1695,7 @@ export const AgentsMachinesTable: Component<{
|
||||
mode={(disks()?.length ?? 0) > 1 ? 'vertical-bars' : undefined}
|
||||
disks={disks()}
|
||||
aggregateDisk={aggregateDisk()}
|
||||
thresholds={diskThresholds()}
|
||||
/>
|
||||
</Show>
|
||||
</TableCell>
|
||||
|
||||
@@ -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)),
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user