mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-27 05:57:27 +00:00
Incorporate batch-start grouped workload cleanup
Preserve the exact published upstream history alongside previously reviewed local integrations. This turn does not select or publish a release. Change-source: pulse-maintainer
This commit is contained in:
@@ -3001,9 +3001,9 @@ Agent`), with the plain-language source phrase available through accessible
|
||||
`pveVersion` or a Pulse Agent report whose OS identity resolves to Unraid or
|
||||
Proxmox VE. They must omit the version rather than showing unrelated
|
||||
collector OS versions, such as Debian 12, beside an API-backed PVE badge.
|
||||
Shared row primitives that render Proxmox node identity, including
|
||||
`frontend-modern/src/components/shared/NodeGroupHeader.tsx`, must route raw
|
||||
PVE manager payloads through
|
||||
Surfaces that render Proxmox node versions, including the Proxmox page
|
||||
model at `frontend-modern/src/features/proxmox/proxmoxPageModel.ts`, must
|
||||
route raw PVE manager payloads through
|
||||
`frontend-modern/src/utils/proxmoxVersion.ts` rather than inlining
|
||||
page-local parsing or falling back to unrelated agent OS versions.
|
||||
System title metadata must apply the same identity rule: once the primary
|
||||
@@ -4485,12 +4485,14 @@ The shared table chrome now allows `TableCardHeader` to expose a right-aligned
|
||||
action slot, currently used by the Workloads/Proxmox metric display control.
|
||||
That slot belongs to the table header band and must not reintroduce nested
|
||||
cards or page-local toolbar wrappers inside `TableCard`. Proxmox host grouping
|
||||
also extends the shared `NodeGroupHeader` row pattern: host metrics may align
|
||||
with workload table columns, but the shared primitive owns the header/table
|
||||
shell boundary rather than platform pages copying their own card headers.
|
||||
Compact PVE version text in that header must come from the shared Proxmox
|
||||
version formatter so raw `pve-manager/...` payloads and platform-page host
|
||||
version cells stay consistent.
|
||||
also extends the shared `NodeGroupHeader` row pattern, which owns the
|
||||
header/table shell boundary rather than platform pages copying their own card
|
||||
headers. `frontend-modern/src/components/shared/NodeGroupHeader.tsx` renders
|
||||
node identity only (status dot, linked name, cluster and agent badges); in
|
||||
table-row mode that identity sits in one cell spanning the table. It carries no
|
||||
per-column cell renderer and no inline
|
||||
version, temperature, or uptime facts: host metrics and versions belong to the
|
||||
platform page's host table, so a group row must not duplicate them.
|
||||
Mobile navigation now recognizes `proxmox` as a first-class platform tab in
|
||||
the shared priority model so app-shell ordering remains centralized.
|
||||
|
||||
|
||||
@@ -1994,22 +1994,20 @@ query persistence once per resource, or run on the steady-state alert hot path;
|
||||
ancestor lookup remains bounded by canonical hierarchy depth for each active
|
||||
alert actually evaluated.
|
||||
|
||||
The embedded WorkloadsSurface exposes a `compactGroupHeaders` prop on
|
||||
`frontend-modern/src/components/Workloads/useWorkloadsState.ts` that
|
||||
platform pages owning their own hosts table (Proxmox overview today) set
|
||||
to strip per-host metric cells out of the `NodeGroupHeader` rows in
|
||||
grouped mode. The flag is threaded through
|
||||
`frontend-modern/src/components/Workloads/WorkloadsSurface.tsx`,
|
||||
`frontend-modern/src/components/Workloads/WorkloadsTable.tsx`, and
|
||||
`frontend-modern/src/components/Workloads/WorkloadPanel.tsx`; when set,
|
||||
`WorkloadPanel` calls `NodeGroupHeader` without `columns` /
|
||||
`renderColumnCell` so the existing colspan layout renders just the
|
||||
status dot, linked node name, cluster badge, and agent badge — the
|
||||
duplicate CPU / Memory / Disk / uptime / temperature / version stats
|
||||
the top-of-page hosts table already owns are dropped. New embedded
|
||||
platform-page consumers that render their own hosts summary must set
|
||||
this flag; standalone Workloads surfaces (no top hosts table) keep the
|
||||
original verbose group rows by default.
|
||||
Grouped host rows in the embedded WorkloadsSurface are identity-only
|
||||
dividers. `frontend-modern/src/components/Workloads/WorkloadPanel.tsx`
|
||||
renders each grouped node through `NodeGroupHeader` as one cell spanning
|
||||
the table: status dot, linked node name, cluster badge, and agent badge.
|
||||
Per-host CPU, memory, disk, network and disk I/O, uptime, temperature,
|
||||
and version stats belong to the platform page's own hosts table (the
|
||||
Proxmox nodes table today), so the workloads table must not render them
|
||||
per column or inline in the group row. The former `compactGroupHeaders`
|
||||
option and the per-column node metric cells it switched off were removed
|
||||
because both remaining production mounts (Proxmox and vSphere overview)
|
||||
set it unconditionally, so the non-compact path no longer rendered
|
||||
anywhere. A new embedded consumer that needs host
|
||||
stats beside its workloads must render its own hosts table rather than
|
||||
re-growing metric cells in the shared group row.
|
||||
|
||||
WorkloadsSurface stays monitoring-first. It must not render a persistent
|
||||
aggregate banner just because running VMs or system containers lack an
|
||||
|
||||
@@ -986,6 +986,11 @@ the page supplies it through the Workloads `tableTitle` slot and the shared
|
||||
states. This heading alignment changes neither workload backup scope nor the
|
||||
storage/recovery evidence carried by the adjacent Backup column and Backups
|
||||
tab.
|
||||
The guest table's grouped node rows are identity-only dividers. Node stats and
|
||||
the node drawer stay with the Proxmox nodes table above it, so `ProxmoxPageSurface`
|
||||
passes `groupNodeDrawerMode: 'disabled'` and no host-metric option to the
|
||||
embedded Workloads state. Those dividers carry no backup or recovery evidence;
|
||||
the per-guest Backup column remains the overview's protection signal.
|
||||
The overview's node-row action selects the shared workload node scope and
|
||||
reveals the guest section. The title names the selected node and reports the
|
||||
filtered guest count, including zero. Activating the selected node again or
|
||||
|
||||
@@ -2989,7 +2989,10 @@ canonical WebSocket snapshot, including the public `vmware-vsphere` alias for
|
||||
the raw `vmware` source, rather than degrading a source-scoped page to periodic
|
||||
REST refreshes. VMware Overview passes that same source-scoped snapshot into
|
||||
the embedded Workloads state, so hosts and VMs share one inventory generation
|
||||
and one explicit refresh path.
|
||||
and one explicit refresh path. Beyond disabling the grouped host drawer, it
|
||||
passes no option that adds host metrics to grouped rows: the shared Workloads
|
||||
group row carries host identity only, and per-host stats stay in the page's
|
||||
own hosts table.
|
||||
Its page-owned workload toolbar consumes the complete shared
|
||||
`getWorkloadsMetricFilterProps` binding, so vSphere VMs expose the same Bars,
|
||||
Trends, Details, History, range, and first-use discovery contract as every
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "c3adf0c7b9fa7f4283f7fd832a806659b009b51b",
|
||||
"verified_at": "2026-10-06T21:45:33Z",
|
||||
"result": "passed",
|
||||
"changed_paths": [
|
||||
"frontend-modern/src/components/Workloads/WorkloadPanel.tsx",
|
||||
"frontend-modern/src/components/Workloads/WorkloadsSurface.tsx",
|
||||
"frontend-modern/src/components/Workloads/WorkloadsTable.tsx",
|
||||
"frontend-modern/src/components/Workloads/useWorkloadsState.ts",
|
||||
"frontend-modern/src/components/shared/NodeGroupHeader.tsx",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx",
|
||||
"frontend-modern/src/features/vmware/VmwarePageSurface.tsx"
|
||||
],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/components/Workloads/WorkloadPanel.tsx": "67c2c51ffceb96893f968dc141dbce54f0affe66fb389ffeeba8c8c94988968c",
|
||||
"frontend-modern/src/components/Workloads/WorkloadsSurface.tsx": "e49032445f566b4e6a4329314f4ada41ed6f7652ba52e9e3fc413047a49f7878",
|
||||
"frontend-modern/src/components/Workloads/WorkloadsTable.tsx": "a340532f9e3e0e28d83e9a0fb369c8be973acb0dea7b321fd646345ff924db38",
|
||||
"frontend-modern/src/components/Workloads/useWorkloadsState.ts": "a29034c5149b7323e86bc2fe795d9b18db805b1ad32e32013fb71547256d61f5",
|
||||
"frontend-modern/src/components/shared/NodeGroupHeader.tsx": "e435c1e6ab04b85a022459cf19a67645bc8339f15ef986ffcb5b42ba27ce667f",
|
||||
"frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx": "80c860be3bee37251c16d21044074267e48991b9cb39b87d335cb2155066ec96",
|
||||
"frontend-modern/src/features/vmware/VmwarePageSurface.tsx": "6c74063a298a259ce5f60485174529e9bffbe7293ae0710f53dc95d6f3c11265"
|
||||
},
|
||||
"routes": ["/proxmox/overview", "/vmware"],
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1440,
|
||||
"height": 900
|
||||
},
|
||||
{
|
||||
"width": 390,
|
||||
"height": 844
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
"Production vite build of this tree (rebased on c3adf0c7b9) embedded in a private DEMO_MODE mock backend (50 PVE nodes, 929 guests, 7 ESXi hosts), headless Chromium via Playwright; desktop 1440x900 and phone 390x844.",
|
||||
"Proxmox Overview guest table, Grouped layout: every grouped node row (8 desktop, 2 phone in the rendered window) is one td whose colspan equals the guest table's visible header count (8 desktop, 5 phone), with status dot, linked node name, cluster badge and Agent badge only; no data-workload-col cells, no PVE version, temperature or uptime text.",
|
||||
"vSphere Overview VM table: the 7 host group rows render as one spanning cell (colspan 6 desktop, 5 phone) with host name and cluster badge, unchanged shape.",
|
||||
"Bars and Trends metric modes both inspected: Trends adds four sparklines per guest row (Proxmox 140 to 560 images, vSphere 18 to 72) while each grouped host row keeps exactly its one status-dot image.",
|
||||
"Screenshots of grouped rows in both modes and of open guest drawers inspected at both viewports; no page errors.",
|
||||
"This is the shape both pages already rendered on main, where compactGroupHeaders was always set; it is not a check of host drawers, which both pages disable."
|
||||
],
|
||||
"interactions": [
|
||||
"Opened the View popover (desktop) and the Filters popover (phone) and toggled Bars to Trends and back.",
|
||||
"Clicked a grouped host row on each page: no inline node drawer row appears and nothing else changes.",
|
||||
"Clicked the first guest row under a group on each page: its inline guest drawer opens.",
|
||||
"Scrolled the first grouped host row to the viewport centre before each capture."
|
||||
]
|
||||
}
|
||||
@@ -1,10 +1,6 @@
|
||||
import { createMemo, For, Show } from 'solid-js';
|
||||
|
||||
import { ComponentErrorBoundary } from '@/components/ErrorBoundary';
|
||||
import { EnhancedCPUBar } from '@/components/Workloads/EnhancedCPUBar';
|
||||
import { MetricMiniSparkline } from '@/components/Workloads/MetricMiniSparkline';
|
||||
import { StackedDiskBar } from '@/components/Workloads/StackedDiskBar';
|
||||
import { StackedMemoryBar } from '@/components/Workloads/StackedMemoryBar';
|
||||
import {
|
||||
GROUPED_TABLE_ROW_BADGE_CLASS,
|
||||
getGroupedTableRowCellClass,
|
||||
@@ -16,12 +12,9 @@ import { NodeGroupHeader } from '@/components/shared/NodeGroupHeader';
|
||||
import { createSummaryInteractiveRowPreviewHandlers } from '@/components/shared/summaryInteractionA11y';
|
||||
import { buildSummaryDisclosureControlsId } from '@/components/shared/summaryInteractionA11y';
|
||||
import { TableBody, TableCell, TableRow } from '@/components/shared/Table';
|
||||
import type { Node } from '@/types/api';
|
||||
import { getAlertsForResource, getAlertStyles } from '@/utils/alerts';
|
||||
import { guestOverrideIdCandidates } from '@/features/alerts/guestOverrideIdentity';
|
||||
import { formatSpeed, formatUptime } from '@/utils/format';
|
||||
import { isNodeOnline } from '@/utils/status';
|
||||
import { formatTemperature, getCpuTemperature, getTemperatureTextClass } from '@/utils/temperature';
|
||||
import { getCanonicalWorkloadId, getWorkloadMetadataIdCandidates } from '@/utils/workloads';
|
||||
import {
|
||||
resolveSummaryGroupMemberInteractionState,
|
||||
@@ -34,14 +27,12 @@ import { NodeDrawer } from './NodeDrawer';
|
||||
import { buildWorkloadSummaryGroupScope } from './workloadSelectors';
|
||||
import { getWorkloadGuestMetadataRecord } from './workloadGuestMetadataRecord';
|
||||
import type { WorkloadsState } from './useWorkloadsState';
|
||||
import type { WorkloadTableMetric } from './workloadMetricHistoryModel';
|
||||
|
||||
type WorkloadPanelProps = Pick<
|
||||
WorkloadsState,
|
||||
| 'activeAlerts'
|
||||
| 'alertsEnabled'
|
||||
| 'bottomSpacerHeight'
|
||||
| 'compactGroupHeaders'
|
||||
| 'getGroupLabel'
|
||||
| 'getNodeTemperatureThresholds'
|
||||
| 'groupedGuests'
|
||||
@@ -58,7 +49,6 @@ type WorkloadPanelProps = Pick<
|
||||
| 'focusedSummaryWorkloadGroupScope'
|
||||
| 'focusedSummaryWorkloadGroupId'
|
||||
| 'hoveredSummaryWorkloadGroupScope'
|
||||
| 'isMobile'
|
||||
| 'nestedWorkloadContextByGuestId'
|
||||
| 'nodeByInstance'
|
||||
| 'search'
|
||||
@@ -80,289 +70,9 @@ type WorkloadPanelProps = Pick<
|
||||
| 'workloadTableLayoutMode'
|
||||
| 'workloadTableVisibleColumnIds'
|
||||
| 'workloadColumnWidths'
|
||||
| 'workloadTableVisibleColumns'
|
||||
>;
|
||||
|
||||
const GROUP_NODE_METRIC_CELL_CLASS = 'px-1.5 sm:px-2 py-0.5 align-middle';
|
||||
const GROUP_NODE_NAME_CELL_CLASS = getGroupedTableRowCellClass(
|
||||
'py-1! pl-2! sm:pl-3! pr-1.5! sm:pr-2!',
|
||||
);
|
||||
|
||||
const getGroupNodeColumnCellClass = (_columnId: string, isNameColumn: boolean): string => {
|
||||
if (isNameColumn) return GROUP_NODE_NAME_CELL_CLASS;
|
||||
return GROUP_NODE_METRIC_CELL_CLASS;
|
||||
};
|
||||
|
||||
const normalizeMetricPercent = (value: number | null | undefined): number => {
|
||||
if (typeof value !== 'number' || !Number.isFinite(value)) return 0;
|
||||
return value <= 1 ? Math.max(0, value * 100) : Math.max(0, value);
|
||||
};
|
||||
|
||||
const formatMetricPercent = (value: number | null | undefined): string =>
|
||||
`${Math.round(normalizeMetricPercent(value))}%`;
|
||||
|
||||
const getUsedPercent = (used?: number | null, total?: number | null): number => {
|
||||
if (typeof used !== 'number' || typeof total !== 'number' || total <= 0) return 0;
|
||||
return normalizeMetricPercent((used / total) * 100);
|
||||
};
|
||||
|
||||
const getGroupNodeMetricKey = (node: Node): string => node.linkedAgentId || node.id || node.name;
|
||||
|
||||
const getGroupNodePveVersion = (node: Node): string => {
|
||||
const version = (node.pveVersion || '').trim();
|
||||
if (!version || version.toLowerCase() === 'unknown') return '';
|
||||
return (
|
||||
version.match(/pve-manager\/([^/\s]+)/i)?.[1] || version.match(/\d+(?:\.\d+)+/)?.[0] || version
|
||||
);
|
||||
};
|
||||
|
||||
const renderGroupNodeEmptyCell = () => (
|
||||
<div class="text-center">
|
||||
<span class="text-xs text-slate-400" aria-hidden="true">
|
||||
—
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export function WorkloadPanel(props: WorkloadPanelProps) {
|
||||
const renderGroupNodeSparkline = (
|
||||
node: Node,
|
||||
metric: WorkloadTableMetric,
|
||||
valueLabel: string,
|
||||
title: string,
|
||||
unit = '%',
|
||||
valueLabelMode: 'inline' | 'tooltip' | 'hidden' = 'inline',
|
||||
formatValue?: (value: number) => string,
|
||||
) => (
|
||||
<MetricMiniSparkline
|
||||
series={props.workloadMetricHistory.getNodeMetricSeries(node, metric)}
|
||||
valueLabel={valueLabel}
|
||||
valueLabelMode={valueLabelMode}
|
||||
title={title}
|
||||
unit={unit}
|
||||
formatValue={formatValue}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderGroupNodeRatePair = (
|
||||
leftLabel: string,
|
||||
leftClass: string,
|
||||
leftValue: number | null | undefined,
|
||||
rightLabel: string,
|
||||
rightClass: string,
|
||||
rightValue: number | null | undefined,
|
||||
) => {
|
||||
const left = Math.max(0, leftValue ?? 0);
|
||||
const right = Math.max(0, rightValue ?? 0);
|
||||
if (left <= 0 && right <= 0) return renderGroupNodeEmptyCell();
|
||||
|
||||
return (
|
||||
<div class="grid w-full min-w-0 grid-cols-[0.75rem_minmax(0,1fr)_0.75rem_minmax(0,1fr)] items-center gap-x-1 overflow-hidden text-[11px] tabular-nums">
|
||||
<span class={`inline-flex w-3 justify-center ${leftClass}`}>{leftLabel}</span>
|
||||
<span
|
||||
class="block min-w-0 overflow-hidden text-ellipsis whitespace-nowrap text-muted"
|
||||
title={formatSpeed(left)}
|
||||
>
|
||||
{formatSpeed(left)}
|
||||
</span>
|
||||
<span class={`inline-flex w-3 justify-center ${rightClass}`}>{rightLabel}</span>
|
||||
<span
|
||||
class="block min-w-0 overflow-hidden text-ellipsis whitespace-nowrap text-muted"
|
||||
title={formatSpeed(right)}
|
||||
>
|
||||
{formatSpeed(right)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderGroupNodeInfoCell = (node: Node) => {
|
||||
const version = getGroupNodePveVersion(node);
|
||||
const temperature = getCpuTemperature(node.temperature);
|
||||
if (!version && temperature === null) return renderGroupNodeEmptyCell();
|
||||
|
||||
return (
|
||||
<div class="flex items-center justify-center gap-2 text-[10px] font-medium text-muted">
|
||||
<Show when={version}>
|
||||
<span title="Proxmox VE version">PVE {version}</span>
|
||||
</Show>
|
||||
<Show when={temperature !== null}>
|
||||
<span
|
||||
class={getTemperatureTextClass(temperature, props.getNodeTemperatureThresholds(node))}
|
||||
title="CPU temperature"
|
||||
>
|
||||
{formatTemperature(temperature)}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderGroupNodeColumnCell = (columnId: string, node: Node) => {
|
||||
switch (columnId) {
|
||||
case 'type':
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<span
|
||||
class="inline-flex items-center px-1 py-0.5 text-[10px] font-medium rounded-sm whitespace-nowrap bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300"
|
||||
title="Proxmox VE node"
|
||||
>
|
||||
PVE
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
case 'info':
|
||||
case 'vmid':
|
||||
return renderGroupNodeInfoCell(node);
|
||||
case 'cpu':
|
||||
if (props.workloadMetricDisplayMode() === 'sparklines') {
|
||||
return renderGroupNodeSparkline(
|
||||
node,
|
||||
'cpu',
|
||||
formatMetricPercent(node.cpu),
|
||||
`${node.name} CPU history`,
|
||||
'%',
|
||||
'inline',
|
||||
formatMetricPercent,
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div class="h-4">
|
||||
<EnhancedCPUBar
|
||||
usage={normalizeMetricPercent(node.cpu)}
|
||||
loadAverage={node.loadAverage?.[0]}
|
||||
cores={props.isMobile() ? undefined : node.cpuInfo?.cores}
|
||||
model={node.cpuInfo?.model}
|
||||
resourceId={getGroupNodeMetricKey(node)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case 'memory':
|
||||
if (props.workloadMetricDisplayMode() === 'sparklines') {
|
||||
return renderGroupNodeSparkline(
|
||||
node,
|
||||
'memory',
|
||||
node.memory?.usageUnavailable
|
||||
? 'N/A'
|
||||
: formatMetricPercent(getUsedPercent(node.memory?.used, node.memory?.total)),
|
||||
`${node.name} memory history`,
|
||||
'%',
|
||||
'inline',
|
||||
formatMetricPercent,
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div class="h-4">
|
||||
<StackedMemoryBar
|
||||
used={node.memory?.used || 0}
|
||||
total={node.memory?.total || 0}
|
||||
unavailable={node.memory?.usageUnavailable === true}
|
||||
balloon={node.memory?.balloon || 0}
|
||||
swapUsed={node.memory?.swapUsed || 0}
|
||||
swapTotal={node.memory?.swapTotal || 0}
|
||||
resourceId={getGroupNodeMetricKey(node)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case 'disk':
|
||||
if (props.workloadMetricDisplayMode() === 'sparklines') {
|
||||
return renderGroupNodeSparkline(
|
||||
node,
|
||||
'disk',
|
||||
formatMetricPercent(node.disk?.usage),
|
||||
`${node.name} disk usage history`,
|
||||
'%',
|
||||
'inline',
|
||||
formatMetricPercent,
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div class="h-4">
|
||||
<StackedDiskBar
|
||||
aggregateDisk={{
|
||||
total: node.disk?.total || 0,
|
||||
used: node.disk?.used || 0,
|
||||
free:
|
||||
node.disk?.free ?? Math.max(0, (node.disk?.total || 0) - (node.disk?.used || 0)),
|
||||
usage: node.disk?.usage || 0,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case 'uptime':
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<Show when={(node.uptime ?? 0) > 0} fallback={renderGroupNodeEmptyCell()}>
|
||||
<span
|
||||
class={`text-xs whitespace-nowrap ${
|
||||
node.uptime > 0 && node.uptime < 3600 ? 'text-orange-500' : 'text-muted'
|
||||
}`}
|
||||
>
|
||||
{formatUptime(node.uptime, props.isMobile())}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
case 'node':
|
||||
case 'context':
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<span class="text-xs text-muted truncate max-w-[120px]" title={node.name}>
|
||||
{node.name}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
case 'os':
|
||||
return (
|
||||
<div class="flex justify-center">
|
||||
<span class="text-xs text-muted">PVE</span>
|
||||
</div>
|
||||
);
|
||||
case 'netIo':
|
||||
if (props.workloadMetricDisplayMode() === 'sparklines') {
|
||||
return renderGroupNodeSparkline(
|
||||
node,
|
||||
'netIo',
|
||||
`${formatSpeed(node.networkIn ?? 0)} / ${formatSpeed(node.networkOut ?? 0)}`,
|
||||
`${node.name} network I/O history`,
|
||||
'B/s',
|
||||
'tooltip',
|
||||
formatSpeed,
|
||||
);
|
||||
}
|
||||
return renderGroupNodeRatePair(
|
||||
'↓',
|
||||
'text-emerald-500',
|
||||
node.networkIn,
|
||||
'↑',
|
||||
'text-orange-400',
|
||||
node.networkOut,
|
||||
);
|
||||
case 'diskIo':
|
||||
if (props.workloadMetricDisplayMode() === 'sparklines') {
|
||||
return renderGroupNodeSparkline(
|
||||
node,
|
||||
'diskIo',
|
||||
`${formatSpeed(node.diskRead ?? 0)} / ${formatSpeed(node.diskWrite ?? 0)}`,
|
||||
`${node.name} disk I/O history`,
|
||||
'B/s',
|
||||
'tooltip',
|
||||
formatSpeed,
|
||||
);
|
||||
}
|
||||
return renderGroupNodeRatePair(
|
||||
'R',
|
||||
'font-mono text-blue-500',
|
||||
node.diskRead,
|
||||
'W',
|
||||
'font-mono text-amber-500',
|
||||
node.diskWrite,
|
||||
);
|
||||
default:
|
||||
return renderGroupNodeEmptyCell();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TableBody ref={props.setTableBodyRef} class="divide-y divide-border">
|
||||
<Show when={props.groupedWindowing.isWindowed() && props.topSpacerHeight() > 0}>
|
||||
@@ -485,22 +195,8 @@ export function WorkloadPanel(props: WorkloadPanelProps) {
|
||||
>
|
||||
<NodeGroupHeader
|
||||
node={node()!}
|
||||
temperatureThresholds={props.getNodeTemperatureThresholds(node()!)}
|
||||
renderAs="tr"
|
||||
colspan={props.totalColumns()}
|
||||
columns={
|
||||
props.compactGroupHeaders() ? undefined : props.workloadTableVisibleColumns()
|
||||
}
|
||||
columnCellClass={
|
||||
props.compactGroupHeaders() ? undefined : getGroupNodeColumnCellClass
|
||||
}
|
||||
renderColumnCell={
|
||||
props.compactGroupHeaders() ? undefined : renderGroupNodeColumnCell
|
||||
}
|
||||
showFactsInName={
|
||||
!props.compactGroupHeaders() &&
|
||||
!props.workloadTableVisibleColumnIds().includes('info')
|
||||
}
|
||||
trClass={
|
||||
canOpenNodeDrawer()
|
||||
? 'cursor-pointer select-none duration-150 [&>td>div]:flex-nowrap'
|
||||
|
||||
@@ -192,7 +192,6 @@ export function WorkloadsSurface(props: WorkloadsSurfaceComponentProps) {
|
||||
activeAlerts={state.activeAlerts}
|
||||
alertsEnabled={state.alertsEnabled}
|
||||
bottomSpacerHeight={state.bottomSpacerHeight}
|
||||
compactGroupHeaders={state.compactGroupHeaders}
|
||||
getGroupLabel={state.getGroupLabel}
|
||||
getNodeTemperatureThresholds={state.getNodeTemperatureThresholds}
|
||||
groupedGuests={state.groupedGuests}
|
||||
|
||||
@@ -17,7 +17,6 @@ type WorkloadsTableProps = Pick<
|
||||
| 'activeAlerts'
|
||||
| 'alertsEnabled'
|
||||
| 'bottomSpacerHeight'
|
||||
| 'compactGroupHeaders'
|
||||
| 'getGroupLabel'
|
||||
| 'getNodeTemperatureThresholds'
|
||||
| 'groupedGuests'
|
||||
@@ -133,7 +132,6 @@ export function WorkloadsTable(props: WorkloadsTableProps) {
|
||||
activeAlerts={props.activeAlerts}
|
||||
alertsEnabled={props.alertsEnabled}
|
||||
bottomSpacerHeight={props.bottomSpacerHeight}
|
||||
compactGroupHeaders={props.compactGroupHeaders}
|
||||
getGroupLabel={props.getGroupLabel}
|
||||
getNodeTemperatureThresholds={props.getNodeTemperatureThresholds}
|
||||
groupedGuests={props.groupedGuests}
|
||||
@@ -150,7 +148,6 @@ export function WorkloadsTable(props: WorkloadsTableProps) {
|
||||
focusedSummaryWorkloadGroupScope={props.focusedSummaryWorkloadGroupScope}
|
||||
focusedSummaryWorkloadGroupId={props.focusedSummaryWorkloadGroupId}
|
||||
hoveredSummaryWorkloadGroupScope={props.hoveredSummaryWorkloadGroupScope}
|
||||
isMobile={props.isMobile}
|
||||
nestedWorkloadContextByGuestId={props.nestedWorkloadContextByGuestId}
|
||||
nodeByInstance={props.nodeByInstance}
|
||||
search={props.search}
|
||||
@@ -171,7 +168,6 @@ export function WorkloadsTable(props: WorkloadsTableProps) {
|
||||
workloadMetricHistory={props.workloadMetricHistory}
|
||||
workloadTableLayoutMode={props.workloadTableLayoutMode}
|
||||
workloadTableVisibleColumnIds={props.workloadTableVisibleColumnIds}
|
||||
workloadTableVisibleColumns={props.workloadTableVisibleColumns}
|
||||
workloadColumnWidths={props.workloadColumnWidths}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
-26
@@ -454,32 +454,6 @@ describe('Workloads platform-page embed contract', () => {
|
||||
expect(nodesTableSource).toContain('isSparklineMode');
|
||||
});
|
||||
|
||||
it('exposes compactGroupHeaders so platform pages can strip duplicate host stats from group rows', async () => {
|
||||
const stateSource = (await import('../useWorkloadsState.ts?raw')).default;
|
||||
expect(stateSource).toContain('compactGroupHeaders?: boolean;');
|
||||
expect(stateSource).toContain('compactGroupHeaders: () => props.compactGroupHeaders === true,');
|
||||
|
||||
const tableSource = (await import('../WorkloadsTable.tsx?raw')).default;
|
||||
expect(tableSource).toContain(`| 'compactGroupHeaders'`);
|
||||
expect(tableSource).toContain('compactGroupHeaders={props.compactGroupHeaders}');
|
||||
|
||||
const panelSource = (await import('../WorkloadPanel.tsx?raw')).default;
|
||||
expect(panelSource).toContain(`| 'compactGroupHeaders'`);
|
||||
// When the flag is set the panel falls back to NodeGroupHeader's
|
||||
// colspan layout: no per-column metric cells, no inline node facts.
|
||||
expect(panelSource).toContain(
|
||||
'props.compactGroupHeaders() ? undefined : props.workloadTableVisibleColumns()',
|
||||
);
|
||||
expect(panelSource).toContain(
|
||||
'props.compactGroupHeaders() ? undefined : renderGroupNodeColumnCell',
|
||||
);
|
||||
expect(panelSource).toContain('!props.compactGroupHeaders() &&');
|
||||
|
||||
const proxmoxSource = (await import('../../../features/proxmox/ProxmoxPageSurface.tsx?raw'))
|
||||
.default;
|
||||
expect(proxmoxSource).toContain('compactGroupHeaders');
|
||||
});
|
||||
|
||||
it('lets platform pages move grouped host drawers to a dedicated host table owner', async () => {
|
||||
const stateSource = (await import('../useWorkloadsState.ts?raw')).default;
|
||||
expect(stateSource).toContain("groupNodeDrawerMode?: 'inline' | 'disabled';");
|
||||
|
||||
@@ -105,12 +105,6 @@ export interface WorkloadsSurfaceProps {
|
||||
}>;
|
||||
resourceSnapshotRefetch?: () => Promise<unknown>;
|
||||
statusModeStorageScope?: string;
|
||||
// Platform pages that render their own hosts table above the embedded
|
||||
// workloads surface (e.g. Proxmox overview) own the per-host CPU / Memory
|
||||
// / Disk / Temperature / uptime / version stats. Setting
|
||||
// `compactGroupHeaders` strips those stats from the NodeGroupHeader rows
|
||||
// in grouped mode so the section dividers don't duplicate the info.
|
||||
compactGroupHeaders?: boolean;
|
||||
// Default Workloads behavior owns grouped host row drawers inline. Platform
|
||||
// pages with a dedicated host table can disable that drawer so host details
|
||||
// open from the host-owned table instead of the embedded guest table.
|
||||
@@ -728,7 +722,6 @@ export function useWorkloadsState(props: WorkloadsSurfaceProps) {
|
||||
workloadsNoInventoryState,
|
||||
ws,
|
||||
groupingMode,
|
||||
compactGroupHeaders: () => props.compactGroupHeaders === true,
|
||||
groupNodeDrawerMode: () => props.groupNodeDrawerMode ?? 'inline',
|
||||
groupLabelBadges,
|
||||
} as const;
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { Component, For, Show, type JSX } from 'solid-js';
|
||||
import { Component, Show, type JSX } from 'solid-js';
|
||||
import type { Node } from '@/types/api';
|
||||
import type { MetricDisplayThresholds } from '@/utils/metricThresholds';
|
||||
import { getNodeDisplayName, getNodeExternalUrl, hasAlternateDisplayName } from '@/utils/nodes';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import { getNodeStatusIndicator } from '@/utils/status';
|
||||
import { formatUptime } from '@/utils/format';
|
||||
import { formatTemperature, getCpuTemperature, getTemperatureTextClass } from '@/utils/temperature';
|
||||
import { formatProxmoxVersion } from '@/utils/proxmoxVersion';
|
||||
import {
|
||||
GROUPED_TABLE_ROW_BADGE_CLASS,
|
||||
getGroupedTableRowCellClass,
|
||||
@@ -17,13 +13,8 @@ import { ResourceNameWithWebInterfaceLink } from './WebInterfaceLink';
|
||||
interface NodeGroupHeaderProps {
|
||||
node: Node;
|
||||
colspan?: number;
|
||||
columns?: Array<{ id: string }>;
|
||||
columnCellClass?: (columnId: string, isNameColumn: boolean) => string;
|
||||
leadingAction?: JSX.Element;
|
||||
renderColumnCell?: (columnId: string, node: Node) => JSX.Element;
|
||||
renderAs?: 'tr' | 'div';
|
||||
showFactsInName?: boolean;
|
||||
temperatureThresholds?: MetricDisplayThresholds | null;
|
||||
trClass?: string;
|
||||
trProps?: JSX.HTMLAttributes<HTMLTableRowElement> &
|
||||
Partial<Record<`data-${string}`, string | undefined>>;
|
||||
@@ -35,12 +26,6 @@ export const NodeGroupHeader: Component<NodeGroupHeaderProps> = (props) => {
|
||||
const nodeUrl = () => getNodeExternalUrl(props.node);
|
||||
const displayName = () => getNodeDisplayName(props.node);
|
||||
const showActualName = () => hasAlternateDisplayName(props.node);
|
||||
const pveVersion = () => formatProxmoxVersion(props.node.pveVersion);
|
||||
const cpuTemperature = () => getCpuTemperature(props.node.temperature);
|
||||
const hasNodeFacts = () =>
|
||||
Boolean(pveVersion()) ||
|
||||
cpuTemperature() !== null ||
|
||||
(typeof props.node.uptime === 'number' && props.node.uptime > 0);
|
||||
|
||||
const InnerContent = () => (
|
||||
<div
|
||||
@@ -84,25 +69,6 @@ export const NodeGroupHeader: Component<NodeGroupHeaderProps> = (props) => {
|
||||
Agent
|
||||
</span>
|
||||
</Show>
|
||||
|
||||
<Show when={props.showFactsInName !== false && hasNodeFacts()}>
|
||||
<span class="hidden sm:inline-flex flex-wrap items-center gap-3 text-[10px] font-medium text-muted">
|
||||
<Show when={pveVersion()}>
|
||||
<span title="Proxmox VE version">PVE {pveVersion()}</span>
|
||||
</Show>
|
||||
<Show when={cpuTemperature() !== null}>
|
||||
<span
|
||||
class={getTemperatureTextClass(cpuTemperature(), props.temperatureThresholds)}
|
||||
title="CPU temperature"
|
||||
>
|
||||
{formatTemperature(cpuTemperature())}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={typeof props.node.uptime === 'number' && props.node.uptime > 0}>
|
||||
<span title="Node uptime">{formatUptime(props.node.uptime, true)}</span>
|
||||
</Show>
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -118,38 +84,9 @@ export const NodeGroupHeader: Component<NodeGroupHeaderProps> = (props) => {
|
||||
}
|
||||
>
|
||||
<tr class={getGroupedTableRowClass(props.trClass)} {...props.trProps}>
|
||||
<Show
|
||||
when={(props.columns?.length ?? 0) > 0 && Boolean(props.renderColumnCell)}
|
||||
fallback={
|
||||
<td colspan={props.colspan} class={getGroupedTableRowCellClass()}>
|
||||
<InnerContent />
|
||||
</td>
|
||||
}
|
||||
>
|
||||
<For each={props.columns}>
|
||||
{(column) => {
|
||||
const isNameColumn = () => column.id === 'name';
|
||||
return (
|
||||
<td
|
||||
data-workload-col={column.id}
|
||||
class={
|
||||
props.columnCellClass?.(column.id, isNameColumn()) ??
|
||||
(isNameColumn()
|
||||
? getGroupedTableRowCellClass()
|
||||
: 'px-1.5 sm:px-2 py-0.5 align-middle')
|
||||
}
|
||||
>
|
||||
<Show
|
||||
when={isNameColumn()}
|
||||
fallback={props.renderColumnCell?.(column.id, props.node)}
|
||||
>
|
||||
<InnerContent />
|
||||
</Show>
|
||||
</td>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</Show>
|
||||
<td colspan={props.colspan} class={getGroupedTableRowCellClass()}>
|
||||
<InnerContent />
|
||||
</td>
|
||||
</tr>
|
||||
</Show>
|
||||
);
|
||||
|
||||
@@ -216,6 +216,7 @@ import proxmoxCoverageTableSource from '@/features/proxmox/ProxmoxCoverageTable.
|
||||
import proxmoxMailGatewayTableSource from '@/features/proxmox/ProxmoxMailGatewayTable.tsx?raw';
|
||||
import proxmoxNodesTableSource from '@/features/proxmox/ProxmoxNodesTable.tsx?raw';
|
||||
import proxmoxHostTableModelSource from '@/features/proxmox/proxmoxHostTableModel.ts?raw';
|
||||
import proxmoxPageModelSource from '@/features/proxmox/proxmoxPageModel.ts?raw';
|
||||
import proxmoxRecoverableTableSource from '@/features/proxmox/ProxmoxRecoverableTable.tsx?raw';
|
||||
import proxmoxReplicationTableSource from '@/features/proxmox/ProxmoxReplicationTable.tsx?raw';
|
||||
import vsphereHostsTableSource from '@/features/vmware/VsphereHostsTable.tsx?raw';
|
||||
@@ -1406,9 +1407,9 @@ describe('shared primitive guardrails', () => {
|
||||
});
|
||||
|
||||
it('routes Proxmox node version presentation through the shared formatter', () => {
|
||||
expect(nodeGroupHeaderSource).toContain("from '@/utils/proxmoxVersion'");
|
||||
expect(nodeGroupHeaderSource).toContain('formatProxmoxVersion(props.node.pveVersion)');
|
||||
expect(nodeGroupHeaderSource).not.toContain('pve-manager\\/');
|
||||
expect(proxmoxPageModelSource).toContain("from '@/utils/proxmoxVersion'");
|
||||
expect(proxmoxPageModelSource).toContain('formatProxmoxVersion(resource.proxmox?.pveVersion)');
|
||||
expect(proxmoxPageModelSource).not.toContain('pve-manager\\/');
|
||||
|
||||
expect(proxmoxVersionSource).toContain('formatProxmoxVersion');
|
||||
expect(proxmoxVersionSource).toContain('pve-manager\\/');
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { render, screen } from '@solidjs/testing-library';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { NodeGroupHeader } from '@/components/shared/NodeGroupHeader';
|
||||
import type { Node } from '@/types/api';
|
||||
import { temperatureStore } from '@/utils/temperature';
|
||||
|
||||
const makeNode = (overrides: Partial<Node> = {}): Node => ({
|
||||
id: 'cluster-a-pve1',
|
||||
@@ -34,18 +33,11 @@ const makeNode = (overrides: Partial<Node> = {}): Node => ({
|
||||
});
|
||||
|
||||
describe('NodeGroupHeader', () => {
|
||||
beforeEach(() => {
|
||||
temperatureStore.setUnit('celsius');
|
||||
});
|
||||
|
||||
it('renders compact infrastructure facts in the grouped workload header', () => {
|
||||
it('renders node identity in the grouped workload header', () => {
|
||||
render(() => <NodeGroupHeader node={makeNode()} />);
|
||||
|
||||
expect(screen.getByText('pve1')).toBeInTheDocument();
|
||||
expect(screen.getByText('homelab')).toBeInTheDocument();
|
||||
expect(screen.getByText('PVE 9.1.9')).toBeInTheDocument();
|
||||
expect(screen.getByText('62°C')).toBeInTheDocument();
|
||||
expect(screen.getByText('2h')).toBeInTheDocument();
|
||||
|
||||
const link = screen.getByRole('link', { name: 'Open web interface for pve1' });
|
||||
expect(link).toHaveAttribute('href', 'https://pve1:8006');
|
||||
@@ -53,20 +45,21 @@ describe('NodeGroupHeader', () => {
|
||||
expect(link).toHaveAttribute('rel', 'noopener noreferrer');
|
||||
});
|
||||
|
||||
it('colors grouped header temperatures from configured thresholds', () => {
|
||||
render(() => (
|
||||
<NodeGroupHeader
|
||||
node={makeNode({
|
||||
temperature: {
|
||||
available: true,
|
||||
cpuPackage: 76,
|
||||
lastUpdate: '2026-01-01T00:00:00Z',
|
||||
},
|
||||
})}
|
||||
temperatureThresholds={{ warning: 80, critical: 85 }}
|
||||
/>
|
||||
it('spans the table in one cell and leaves host stats to the platform host table', () => {
|
||||
const { container } = render(() => (
|
||||
<table>
|
||||
<tbody>
|
||||
<NodeGroupHeader node={makeNode()} renderAs="tr" colspan={9} />
|
||||
</tbody>
|
||||
</table>
|
||||
));
|
||||
|
||||
expect(screen.getByText('76°C')).toHaveClass('text-green-600');
|
||||
const cells = container.querySelectorAll('tr > td');
|
||||
expect(cells).toHaveLength(1);
|
||||
expect(cells[0]).toHaveAttribute('colspan', '9');
|
||||
expect(cells[0]).toHaveTextContent('pve1');
|
||||
expect(screen.queryByText('PVE 9.1.9')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/°[CF]/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('2h')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -495,7 +495,6 @@ function ProxmoxOverview(props: ProxmoxOverviewProps) {
|
||||
// surface, so label it with the platform vocabulary instead of 'Info'.
|
||||
columnLabelOverrides: { info: 'ID' },
|
||||
statusModeStorageScope: PROXMOX_WORKLOAD_STATUS_STORAGE_SCOPE,
|
||||
compactGroupHeaders: true,
|
||||
groupNodeDrawerMode: 'disabled',
|
||||
metricDisplayMode: props.metricDisplayMode,
|
||||
onMetricDisplayModeChange: props.setMetricDisplayMode,
|
||||
@@ -638,7 +637,6 @@ function ProxmoxOverview(props: ProxmoxOverviewProps) {
|
||||
forcedPlatform={PROXMOX_PLATFORM_FILTER}
|
||||
excludedWorkloadTypes={PROXMOX_WORKLOAD_EXCLUDED_TYPES}
|
||||
showNestedExcludedWorkloads
|
||||
compactGroupHeaders
|
||||
groupNodeDrawerMode="disabled"
|
||||
suppressFilterToolbar
|
||||
emptyStateTitle="No Proxmox workloads"
|
||||
|
||||
@@ -214,6 +214,16 @@ describe('ProxmoxPageSurface contract', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('leaves host details to the nodes table instead of the embedded guest table', () => {
|
||||
setResources([makeResource({ id: 'vm-1', type: 'vm' })]);
|
||||
|
||||
renderSurface();
|
||||
|
||||
expect(mockWorkloadsOptions).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ groupNodeDrawerMode: 'disabled' }),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.clearAllMocks();
|
||||
|
||||
@@ -474,7 +474,6 @@ function VmwareOverview(props: VmwareOverviewProps) {
|
||||
statusModeStorageScope: VMWARE_WORKLOAD_STATUS_STORAGE_SCOPE,
|
||||
columnVisibilityStorageScope: VMWARE_WORKLOAD_COLUMN_VISIBILITY_SCOPE,
|
||||
additionalDefaultHiddenColumnIds: [...VMWARE_WORKLOAD_DEFAULT_HIDDEN_COLUMN_IDS],
|
||||
compactGroupHeaders: true,
|
||||
inventorySourcesQuery: props.inventorySourcesQuery,
|
||||
groupNodeDrawerMode: 'disabled',
|
||||
metricDisplayMode: props.metricDisplayMode,
|
||||
@@ -545,7 +544,6 @@ function VmwareOverview(props: VmwareOverviewProps) {
|
||||
useWorkloads
|
||||
forcedPlatform={VMWARE_PLATFORM_FILTER}
|
||||
forcedViewMode="vm"
|
||||
compactGroupHeaders
|
||||
groupNodeDrawerMode="disabled"
|
||||
suppressFilterToolbar
|
||||
emptyStateTitle="No vSphere VMs"
|
||||
|
||||
@@ -2,15 +2,15 @@ import { describe, expect, it } from 'vitest';
|
||||
|
||||
import nodeDrawerOverviewSource from '@/components/Workloads/NodeDrawerOverview.tsx?raw';
|
||||
import workloadPanelSource from '@/components/Workloads/WorkloadPanel.tsx?raw';
|
||||
import nodeGroupHeaderSource from '@/components/shared/NodeGroupHeader.tsx?raw';
|
||||
import temperatureGaugeSource from '@/components/shared/TemperatureGauge.tsx?raw';
|
||||
import dockerHostDrawerOverviewSource from '@/features/docker/DockerHostDrawerOverview.tsx?raw';
|
||||
import agentsMachinesTableSource from '@/features/standalone/AgentsMachinesTable.tsx?raw';
|
||||
|
||||
describe('temperature display guardrails', () => {
|
||||
it('keeps colored temperature displays threshold-aware', () => {
|
||||
expect(nodeGroupHeaderSource).toContain('temperatureThresholds');
|
||||
expect(workloadPanelSource).toContain('getNodeTemperatureThresholds(node)');
|
||||
expect(workloadPanelSource).toContain(
|
||||
'temperatureThresholds={props.getNodeTemperatureThresholds(node()!)}',
|
||||
);
|
||||
expect(nodeDrawerOverviewSource).toContain('getTemperatureTextClass(value, thresholds)');
|
||||
expect(dockerHostDrawerOverviewSource).toContain('temperatureThresholds()');
|
||||
expect(agentsMachinesTableSource).toContain('getAgentMachineTemperatureMetric');
|
||||
@@ -19,7 +19,6 @@ describe('temperature display guardrails', () => {
|
||||
});
|
||||
|
||||
it('does not reintroduce the previous local temperature severity calls', () => {
|
||||
expect(nodeGroupHeaderSource).not.toContain('getTemperatureTextClass(cpuTemperature())');
|
||||
expect(workloadPanelSource).not.toContain('getTemperatureTextClass(temperature)}');
|
||||
expect(nodeDrawerOverviewSource).not.toContain('getTemperatureTextClass(value)');
|
||||
expect(dockerHostDrawerOverviewSource).not.toContain('getTemperatureTextClass(temperature)');
|
||||
|
||||
Reference in New Issue
Block a user