mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-09 18:15:50 +00:00
Converge standalone metrics on platform table helper
This commit is contained in:
@@ -2801,9 +2801,10 @@ Platform table metric fallback rendering is also shared.
|
||||
metric bar cells plus optional caller-owned fallback label/title text, and
|
||||
`getPlatformTableFiniteMetric` owns finite-number normalization for CPU and
|
||||
memory, disk, and capacity values. Docker / Podman, Kubernetes, Proxmox,
|
||||
Standalone, TrueNAS, and vSphere platform tables must compose those helpers
|
||||
instead of declaring local `metricFallback` / `finiteMetric` helpers or
|
||||
inlining centered muted dash fallback markup in metric cells.
|
||||
Standalone, TrueNAS, and vSphere platform tables and their table-model helpers
|
||||
must compose those helpers instead of declaring local `metricFallback` /
|
||||
`finiteMetric` helpers or inlining centered muted dash fallback markup in
|
||||
metric cells.
|
||||
Platform load-failure states are registry-backed as well.
|
||||
`PlatformErrorState` owns the repeated table-card error shell, warning icon,
|
||||
and Refresh action for platform page and table load failures; platform
|
||||
|
||||
@@ -2613,6 +2613,26 @@
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "platform-table-finite-metric-normalization",
|
||||
"category": "platform-table-metric",
|
||||
"summary": "Platform table models must use the shared finite-metric normalization helper instead of carrying model-local Number.isFinite wrappers.",
|
||||
"canonical": {
|
||||
"path": "src/features/platformPage/sharedPlatformPage.tsx",
|
||||
"export": "getPlatformTableFiniteMetric"
|
||||
},
|
||||
"requiredConsumers": [{ "path": "src/features/standalone/agentMachineTableModel.ts" }],
|
||||
"forbiddenPatterns": [
|
||||
{
|
||||
"path": "src/features/standalone/agentMachineTableModel.ts",
|
||||
"patterns": ["const finiteMetric"]
|
||||
}
|
||||
],
|
||||
"proof": [
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "platform-error-state",
|
||||
"category": "platform-error-state",
|
||||
@@ -3983,6 +4003,25 @@
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "platform-table-model-local-finite-metric-helper",
|
||||
"category": "platform-table-metric",
|
||||
"summary": "Platform table models must not recreate local finiteMetric helpers for metric normalization.",
|
||||
"canonical": {
|
||||
"path": "src/features/platformPage/sharedPlatformPage.tsx",
|
||||
"export": "getPlatformTableFiniteMetric"
|
||||
},
|
||||
"scopes": ["src/features/standalone/agentMachineTableModel.ts"],
|
||||
"extensions": [".ts"],
|
||||
"allPatterns": ["const finiteMetric"],
|
||||
"legacyReason": "Retired migration debt. Platform table model-side metric normalization must use getPlatformTableFiniteMetric so table presentation validity remains shared.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": [],
|
||||
"proof": [
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "platform-section-tabs-local-nav",
|
||||
"category": "platform-section-tabs",
|
||||
|
||||
@@ -6097,6 +6097,62 @@ describe('shared primitive guardrails', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps platform table model metric normalization on the shared helper', () => {
|
||||
const registry = JSON.parse(sharedTemplateRegistrySource) as {
|
||||
rules?: Array<{
|
||||
id: string;
|
||||
canonical?: { path?: string; export?: string };
|
||||
requiredConsumers?: Array<{ path?: string }>;
|
||||
forbiddenPatterns?: Array<{ path?: string; patterns?: string[] }>;
|
||||
}>;
|
||||
patternGuards?: Array<{
|
||||
id: string;
|
||||
canonical?: { path?: string; export?: string };
|
||||
allPatterns?: string[];
|
||||
scopes?: string[];
|
||||
extensions?: string[];
|
||||
allowedPaths?: string[];
|
||||
ignoredPaths?: string[];
|
||||
}>;
|
||||
};
|
||||
const registeredRule = registry.rules?.find(
|
||||
(rule) => rule.id === 'platform-table-finite-metric-normalization',
|
||||
);
|
||||
const localHelperGuard = registry.patternGuards?.find(
|
||||
(guard) => guard.id === 'platform-table-model-local-finite-metric-helper',
|
||||
);
|
||||
|
||||
expect(registeredRule?.canonical?.path).toBe(
|
||||
'src/features/platformPage/sharedPlatformPage.tsx',
|
||||
);
|
||||
expect(registeredRule?.canonical?.export).toBe('getPlatformTableFiniteMetric');
|
||||
expect(registeredRule?.requiredConsumers?.map((consumer) => consumer.path)).toEqual([
|
||||
'src/features/standalone/agentMachineTableModel.ts',
|
||||
]);
|
||||
expect(registeredRule?.forbiddenPatterns).toEqual([
|
||||
{
|
||||
path: 'src/features/standalone/agentMachineTableModel.ts',
|
||||
patterns: ['const finiteMetric'],
|
||||
},
|
||||
]);
|
||||
expect(localHelperGuard?.canonical?.path).toBe(
|
||||
'src/features/platformPage/sharedPlatformPage.tsx',
|
||||
);
|
||||
expect(localHelperGuard?.canonical?.export).toBe('getPlatformTableFiniteMetric');
|
||||
expect(localHelperGuard?.allPatterns).toEqual(['const finiteMetric']);
|
||||
expect(localHelperGuard?.scopes).toEqual(['src/features/standalone/agentMachineTableModel.ts']);
|
||||
expect(localHelperGuard?.extensions).toEqual(['.ts']);
|
||||
expect(localHelperGuard?.allowedPaths ?? []).toHaveLength(0);
|
||||
expect(localHelperGuard?.ignoredPaths ?? []).toHaveLength(0);
|
||||
|
||||
expect(sharedPlatformPageSource).toContain('export const getPlatformTableFiniteMetric');
|
||||
expect(agentMachineTableModelSource).toContain('getPlatformTableFiniteMetric');
|
||||
expect(agentMachineTableModelSource).not.toContain('const finiteMetric');
|
||||
expect(agentMachineTableModelSource).not.toContain(
|
||||
"typeof value === 'number' && Number.isFinite(value) ? value : undefined",
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps platform section navigation on the shared tabs template', () => {
|
||||
const registry = JSON.parse(sharedTemplateRegistrySource) as {
|
||||
rules?: Array<{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ColumnDef } from '@/hooks/useColumnVisibility';
|
||||
import type { HostDiskIO, HostRAIDArray, HostRAIDDevice } from '@/types/api';
|
||||
import type { Resource } from '@/types/resource';
|
||||
import { getPlatformTableFiniteMetric } from '@/features/platformPage/sharedPlatformPage';
|
||||
import { normalizeDiskArray } from '@/utils/format';
|
||||
import { asTrimmedString } from '@/utils/stringUtils';
|
||||
|
||||
@@ -114,9 +115,6 @@ const AGENT_MACHINE_SORT_DESC_DEFAULTS = new Set<AgentMachineSortKey>([
|
||||
'lastSeen',
|
||||
]);
|
||||
|
||||
const finiteMetric = (value: number | undefined): number | undefined =>
|
||||
typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
||||
|
||||
type TemperatureReading = {
|
||||
label: string;
|
||||
value: number;
|
||||
@@ -151,7 +149,7 @@ export type AgentMachineDiskIODetail = HostDiskIO;
|
||||
export type AgentMachineRaidArrayDetail = HostRAIDArray;
|
||||
|
||||
const positiveTemperature = (value: number | undefined): number | undefined => {
|
||||
const metric = finiteMetric(value);
|
||||
const metric = getPlatformTableFiniteMetric(value);
|
||||
return metric !== undefined && metric > 0 ? metric : undefined;
|
||||
};
|
||||
|
||||
@@ -163,13 +161,13 @@ const getDiskUsagePercent = (disk: {
|
||||
used?: number;
|
||||
usage?: number;
|
||||
}): number | undefined => {
|
||||
const total = finiteMetric(disk.total);
|
||||
const used = finiteMetric(disk.used);
|
||||
const total = getPlatformTableFiniteMetric(disk.total);
|
||||
const used = getPlatformTableFiniteMetric(disk.used);
|
||||
if (total && total > 0 && typeof used === 'number') {
|
||||
return (used / total) * 100;
|
||||
}
|
||||
|
||||
const usage = finiteMetric(disk.usage);
|
||||
const usage = getPlatformTableFiniteMetric(disk.usage);
|
||||
if (usage === undefined) return undefined;
|
||||
return usage <= 1 ? usage * 100 : usage;
|
||||
};
|
||||
@@ -267,51 +265,54 @@ const flattenTemperatureSections = (
|
||||
.join('\n');
|
||||
|
||||
const getMetricPercent = (metric: Resource['cpu'] | undefined): number | undefined =>
|
||||
finiteMetric(metric?.current);
|
||||
getPlatformTableFiniteMetric(metric?.current);
|
||||
|
||||
export const getAgentMachineCpuPercent = (machine: Resource): number | undefined =>
|
||||
getMetricPercent(machine.cpu);
|
||||
|
||||
export const getAgentMachineMemoryPercent = (machine: Resource): number | undefined => {
|
||||
const total = finiteMetric(machine.memory?.total);
|
||||
const used = finiteMetric(machine.memory?.used);
|
||||
const total = getPlatformTableFiniteMetric(machine.memory?.total);
|
||||
const used = getPlatformTableFiniteMetric(machine.memory?.used);
|
||||
if (total && total > 0 && typeof used === 'number') {
|
||||
return (used / total) * 100;
|
||||
}
|
||||
return finiteMetric(machine.memory?.current) ?? finiteMetric(machine.agent?.memory?.usage);
|
||||
return (
|
||||
getPlatformTableFiniteMetric(machine.memory?.current) ??
|
||||
getPlatformTableFiniteMetric(machine.agent?.memory?.usage)
|
||||
);
|
||||
};
|
||||
|
||||
export const getAgentMachineDiskPercent = (machine: Resource): number | undefined => {
|
||||
const maxDiskPercent = getMaxOperationalDiskPercent(machine);
|
||||
if (maxDiskPercent !== undefined) return maxDiskPercent;
|
||||
|
||||
const total = finiteMetric(machine.disk?.total);
|
||||
const used = finiteMetric(machine.disk?.used);
|
||||
const total = getPlatformTableFiniteMetric(machine.disk?.total);
|
||||
const used = getPlatformTableFiniteMetric(machine.disk?.used);
|
||||
if (total && total > 0 && typeof used === 'number') {
|
||||
return (used / total) * 100;
|
||||
}
|
||||
return finiteMetric(machine.disk?.current);
|
||||
return getPlatformTableFiniteMetric(machine.disk?.current);
|
||||
};
|
||||
|
||||
export const getAgentMachineNetworkTotal = (machine: Resource): number | undefined => {
|
||||
const rx = finiteMetric(machine.network?.rxBytes);
|
||||
const tx = finiteMetric(machine.network?.txBytes);
|
||||
const rx = getPlatformTableFiniteMetric(machine.network?.rxBytes);
|
||||
const tx = getPlatformTableFiniteMetric(machine.network?.txBytes);
|
||||
if (rx === undefined && tx === undefined) return undefined;
|
||||
return (rx ?? 0) + (tx ?? 0);
|
||||
};
|
||||
|
||||
const positiveMetric = (value: number | undefined): number | undefined => {
|
||||
const metric = finiteMetric(value);
|
||||
const metric = getPlatformTableFiniteMetric(value);
|
||||
return metric !== undefined && metric > 0 ? metric : undefined;
|
||||
};
|
||||
|
||||
const nonNegativeMetric = (value: number | undefined): number => {
|
||||
const metric = finiteMetric(value);
|
||||
const metric = getPlatformTableFiniteMetric(value);
|
||||
return metric !== undefined && metric > 0 ? metric : 0;
|
||||
};
|
||||
|
||||
const nonNegativeFiniteMetric = (value: number | undefined): number | undefined => {
|
||||
const metric = finiteMetric(value);
|
||||
const metric = getPlatformTableFiniteMetric(value);
|
||||
return metric !== undefined && metric >= 0 ? metric : undefined;
|
||||
};
|
||||
|
||||
@@ -367,8 +368,8 @@ export const getAgentMachineNetworkInterfaceDetails = (
|
||||
const name = asTrimmedString(iface.name);
|
||||
const mac = asTrimmedString(iface.mac);
|
||||
const addresses = uniqueTrimmedValues(iface.addresses);
|
||||
const rxBytes = finiteMetric(iface.rxBytes);
|
||||
const txBytes = finiteMetric(iface.txBytes);
|
||||
const rxBytes = getPlatformTableFiniteMetric(iface.rxBytes);
|
||||
const txBytes = getPlatformTableFiniteMetric(iface.txBytes);
|
||||
const speedMbps = positiveMetric(iface.speedMbps);
|
||||
|
||||
if (
|
||||
@@ -396,8 +397,8 @@ export const getAgentMachineNetworkInterfaceDetails = (
|
||||
);
|
||||
|
||||
export const getAgentMachineDiskIOTotal = (machine: Resource): number | undefined => {
|
||||
const read = finiteMetric(machine.diskIO?.readRate);
|
||||
const write = finiteMetric(machine.diskIO?.writeRate);
|
||||
const read = getPlatformTableFiniteMetric(machine.diskIO?.readRate);
|
||||
const write = getPlatformTableFiniteMetric(machine.diskIO?.writeRate);
|
||||
if (read === undefined && write === undefined) return undefined;
|
||||
return (read ?? 0) + (write ?? 0);
|
||||
};
|
||||
@@ -532,7 +533,7 @@ const getAgentMachineRaidDeviceDetails = (
|
||||
(devices ?? []).reduce<HostRAIDDevice[]>((details, device, index) => {
|
||||
const deviceName = asTrimmedString(device.device);
|
||||
const state = asTrimmedString(device.state);
|
||||
const slot = finiteMetric(device.slot);
|
||||
const slot = getPlatformTableFiniteMetric(device.slot);
|
||||
|
||||
if (!deviceName && !state && slot === undefined) return details;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user