diff --git a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md index 5d26056b6e..9ac7408aa9 100644 --- a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md +++ b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md @@ -2600,26 +2600,29 @@ across provider-backed history. Row hover and focus plus top-card isolation on workload surfaces must resolve against the same canonical workload ID even when the backing history is stored under a provider metrics target, so provider-backed VM rows do not silently drop out of summary emphasis. -The shared infrastructure table hot path now also treats operator-facing -resource identity as a protected boundary: sorting, searching, summary-series -matching, and row titles on the infrastructure page must use the canonical -local instance identity rather than governed AI-summary text, so performance -work cannot “optimize” the table into ambiguous labels that collapse multiple -resources into the same visible name. -The same protected table path treats the visible system column as -identity-first presentation over canonical merged-source data. Sort derivation -for that column must use the same displayed system identity as the render path, -while the render path may keep full merged-source detail in tooltips. When a -row contains both `agent` and a provider/API platform such as Proxmox, the table -must render the provider platform as the compact visible badge rather than -adding extra Agent badge width or sorting primarily by the telemetry method. -When a row is only known through an agent or container runtime, the table must -prefer reported OS/appliance identity before falling back to Docker/runtime -capability labels. -When the displayed system badge already includes the platform version, row title -metadata must not spend extra badge/title budget repeating that same platform as -an unversioned source; it may keep non-duplicate collection context such as -Pulse Agent. +Platform-table hot paths treat operator-facing resource identity as a +protected boundary: sorting, searching, summary-series matching, and row +titles must use the canonical local instance identity (the resource's own name or +`getPreferredInfrastructureDisplayName(...)`) rather than governed AI-summary +text, so performance work cannot “optimize” a table into ambiguous labels that +collapse multiple resources into the same visible name. +A visible system column is identity-first presentation over canonical +merged-source data. The Docker hosts table's System column is the live +instance: `frontend-modern/src/features/docker/DockerHostsTable.tsx` sorts and +renders the same `getDockerHostSystemBadge(...)` result, and the render path +may keep full merged-source detail in tooltips. When a row contains both +`agent` and a provider/API platform such as Proxmox, the system badge must show +the provider platform rather than adding extra Agent badge width or sorting +primarily by the telemetry method. When a row is only known through an agent or +container runtime, the shared resolver prefers reported OS/appliance identity +before falling back to Docker/runtime capability labels; the Docker hosts +column drops that runtime-only fallback, since every row there is a Docker +host, and shows `—` instead. +A surface that shows source badges beside a versioned system badge must not +spend extra badge/title budget repeating that same platform as an unversioned +source; it may keep non-duplicate collection context such as Pulse Agent. The +resource drawer header avoids the repeat by showing system identity badges in +place of source badges. That derived workload owner now also routes grouped row windowing through `frontend-modern/src/components/Workloads/useGroupedTableWindowing.ts`, which owns row-window thresholds, overscan behavior, reveal-index clamping, and @@ -2996,17 +2999,17 @@ the timeline/history path can resolve through the canonical catalog without addi detail-only lookup branch to the hot-row path. The same detail drawer also uses that resolver for correlation dependency and dependent chips, so the investigation path shows catalog labels in the drawer -and keeps the raw ID only when the catalog cannot resolve one, while the AI -page keeps its broader no-catalog fallback. -That same workloads-link path and the workload projection now also -share the canonical cluster-name helpers in the shared agent-resource layer, -so route labels, pod grouping, and cluster-name fetch keys keep using the -same source of truth instead of rebuilding the `clusterName`/`context`/ -`clusterId` prefix locally. -The infrastructure host-table hot path now also suppresses the default -`Internal` + `Cloud Summary` policy pair in row chrome. That baseline posture -still belongs to the canonical policy contract, but repeating it on every host -burns row-density budget without adding operator-grade signal. +and keeps the raw ID only when the catalog cannot resolve one. +The workload projection and the resource drawer share the canonical +cluster-name helpers in `frontend-modern/src/utils/agentResources.ts`, so pod +grouping and the drawer's cluster-name fetch keys keep using the same source +of truth instead of rebuilding the `clusterName`/`context`/`clusterId` prefix +locally. +A platform table that adds policy chips to row chrome must suppress the +default `Internal` + `Cloud Summary` policy pair. That baseline posture still +belongs to the canonical policy contract, but repeating it on every row burns +row-density budget without adding operator-grade signal. No platform table +renders policy chips today. The shared node adapter also uses that same cluster-name helper for the infrastructure summary surface, so Proxmox node projections stay aligned with the same canonical cluster label instead of carrying a raw adapter-local diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index 6f82da2f6d..c191173944 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -3398,10 +3398,14 @@ this health-state projection: it must derive counts only from the resources accessor already available to the summary state, not from a separate API call or a re-projection of websocket state outside the shared summary pipeline. -`resourceBadgePresentation.ts` now owns the Infrastructure table system -identity resolver. That resolver must prefer provider/API platform identity, -then reported host OS or appliance identity, before falling back to Docker or -other runtime capability labels. +`resourceBadgePresentation.ts` owns the canonical system identity resolver, +`getInfrastructureSystemIdentityBadges(...)`, which feeds the resource drawer's +header badges and, through `getDockerHostSystemBadge(...)` in +`frontend-modern/src/features/docker/dockerPageModel.ts` (which drops +runtime-only Docker/Podman labels), the Docker hosts table's System column. +That resolver must prefer provider/API platform +identity, then reported host OS or appliance identity, before falling back to +Docker or other runtime capability labels. This subsystem now sits under the dedicated core monitoring runtime lane so canonical resource identity, discovery normalization, and platform-runtime @@ -4040,16 +4044,23 @@ across backend clone/merge and frontend transport. The owning resource remains the canonical `app-container`; the TrueNAS facet explains the platform-native app shape, and Docker metadata remains secondary runtime compatibility rather than the source of truth for the TrueNAS overview. -That same infrastructure focus boundary now also owns deliberate drawer reveal. -When an infrastructure row opens inline detail on the same route, the page must -tag that detail with the canonical active resource ID. Direct row toggles that -already have the row in view must capture the current `.app-scroll-shell` -position through `frontend-modern/src/utils/appShellScrollRestoration.ts`, so -the remounted root shell in `frontend-modern/src/App.tsx` can stay anchored, -and then still hand off to the shared summary-table/contextual-focus helpers -when the opened drawer would otherwise fall below the fold. That reveal must -scroll only enough of the infrastructure table to keep the row header plus the -start of the detail visible, not leave the drawer clipped and not hard-center +Deliberate drawer reveal after a same-route selection write is owned by the +Workloads table +(`frontend-modern/src/components/Workloads/useWorkloadSelectionState.ts`, +governed in `performance-and-scalability`). Platform resource tables open their +inline drawer through local component state, most through +`createPlatformResourceDetailState(...)` in +`frontend-modern/src/features/platformPage/PlatformResourceDetailTableRow.tsx` +while the Docker hosts and Proxmox nodes tables keep their own selection +signal, and write no route state. An infrastructure table that moves drawer selection +into route state must tag the opened detail with the canonical active resource +ID; direct toggles of a row already in view must capture the current +`.app-scroll-shell` position through +`frontend-modern/src/utils/appShellScrollRestoration.ts`, so the remounted root +shell in `frontend-modern/src/App.tsx` stays anchored, and then hand off to the +shared summary-table/contextual-focus helpers when the opened drawer would fall +below the fold. That reveal scrolls only enough to keep the row header plus the +start of the detail visible, never leaving the drawer clipped or hard-centering the selected row. That same unified-resource boundary now also owns stored metrics-target continuity for provider-backed resources. When registry rebuild cannot derive a @@ -4684,11 +4695,10 @@ The frontend now also consumes those facet reads through drawer, which keeps the presentation surface aligned with the governed API contract instead of rebuilding the relationship and timeline inline. The canonical routing owner now also lives in -`frontend-modern/src/routing/resourceLinks.ts`, including the -workload-to-infrastructure href builder used by Workloads row and drawer -consumers. Future workload-to-resource navigation changes must extend through -that shared routing contract instead of reintroducing workload-local path -builders. +`frontend-modern/src/routing/resourceLinks.ts`. Its workload-to-infrastructure +href builder was retired with the legacy `/infrastructure` route, so future +workload-to-resource navigation must extend that shared routing contract +instead of reintroducing workload-local path builders. That same shared routing boundary now also owns the canonical Patrol destination used by cross-surface findings and drill-down links. Shared dashboard, alert, and settings referrals may target `/patrol` through @@ -4706,9 +4716,10 @@ resolve the deep-link source from the canonical merged source set before falling back to raw `platformType`, so TrueNAS-backed hybrid systems do not lose their storage context just because agent telemetry is also present. That same routing contract now also owns workload platform scoping without -restoring the retired `/workloads` top-level route. Shared workloads links, -dashboard URL-sync state, and infrastructure drill-down helpers must preserve -`platform=` for API-backed workloads such as TrueNAS +restoring the retired `/workloads` top-level route. Workloads URL-sync state, +and any workload link built through the shared `buildWorkloadsRouteSearch(...)` +helper, must preserve `platform=` for API-backed workloads +such as TrueNAS app-containers instead of collapsing those routes back to generic agent or Docker-only semantics when host telemetry is also present. Runtime-local filters like `agent` or cluster context may still be added as secondary scope, @@ -5106,25 +5117,20 @@ block entirely. The shared frontend resource identity helper now owns that policy-aware display contract, so infrastructure surfaces that ask for the preferred resource label no longer need to re-encode the governed summary boundary by -hand. Settings quick-picks, infrastructure selectors, and the connected- -infrastructure / monitored-system projections now all stay on that same -preferred-label helper instead of carrying a separate raw-name fallback fork. +hand. Provider-bound frontend labels, such as Assistant chat resource labels +and organization sharing, stay on that same preferred-label helper instead of +carrying a separate raw-name fallback fork. Operator-local pickers such as the +Settings `ResourcePicker` use `getPreferredInfrastructureDisplayName(...)` on +purpose, under the operator-local resource search obligation above. That same helper also owns platform-id redundancy suppression for infrastructure drawers, so surfaces only render platform IDs when they add identity context beyond the canonical display name or hostname instead of repeating the same identifier chrome in both runtime and identity sections. -The shared workloads-link helper now also uses that preferred-label helper -for Kubernetes-cluster navigation fallbacks, so drawer/table navigation -context stays inside the same governed resource-label boundary instead of -repeating a raw display-name fork. -That same shared workloads-link path now also owns top-level TrueNAS system -handoffs to the canonical app-container workloads route, so infrastructure -drawers and related-link surfaces do not strand canonical `truenas` resources -on infrastructure-only navigation while API-backed apps already exist in the -shared workloads model. That same helper must also honor merged -`agent`+`truenas` source sets for top-level systems, so hybrid TrueNAS -surfaces keep the same workload handoff even when the resource shape presents -through the canonical host path instead of a raw `type='truenas'` row. +The shared workloads-link helper, which labelled Kubernetes-cluster +navigation fallbacks and handed top-level TrueNAS systems to the workloads +route, was retired with the drawer's related-links injection described above. +New drawer or table links follow the local operator identity rule for +infrastructure links and land inside the owning platform page. The resource drawer's Kubernetes namespace and deployment tabs use the canonical cluster-name helper for backend fetch keys, keeping lookup identity separate from the governed display-label contract. @@ -5162,10 +5168,12 @@ cluster name, namespace, or surface-local aliases. The drawer's discovery mapper also reuses that helper for pod fallback agent IDs, so the resource-detail path and the Workloads path stay aligned on the same cluster-name source of truth. -The workload projection and workloads-link route helpers also share -the same Kubernetes context prefix helper in the shared agent-resource -layer, so pod grouping and cluster navigation keep the same cluster-context -prefix before any surface-specific display fallback is applied. +The workload projection and the resource drawer also share the Kubernetes +context helper `getPreferredResourceKubernetesContext(...)` in +`frontend-modern/src/utils/agentResources.ts`, directly in `useWorkloads` and +through `getPreferredResourceClusterName(...)` in the drawer, so pod grouping +and drawer cluster context keep the same cluster-context prefix before any +surface-specific display fallback is applied. The unified-resource projection also reuses that same prefix helper for projected Kubernetes `clusterId`, so the shared resource store stays aligned with the Workloads and detail surfaces on the same canonical cluster-context @@ -5428,14 +5436,14 @@ owners instead of rebuilding ad hoc platform-data parsing or action-target fallback logic inside alerts, settings, recovery, AI, or infrastructure-local state owners. Those runtime adapters must preserve the same operator-facing local identity -boundary as the infrastructure tables and selectors: node, PBS, and PMG view +boundary as the platform tables: node, PBS, and PMG view models keep canonical local instance labels for summary rows, drawers, and settings selectors, while governed summaries remain available for policy/detail surfaces rather than replacing per-instance operator identity. `ResourceFacetSummary` now consumes the shared `frontend-modern/src/utils/resourceChangePresentation.ts` label helper for canonical change kinds, source types, and adapter provenance, so the chip -wording stays aligned across table, drawer, and intelligence surfaces. +wording stays aligned with the drawer's recent-change cards and timeline. Timeline cards in that drawer surface change metadata when it is present, so the history view preserves the richer provenance already carried by the unified-resource model instead of flattening those fields away. @@ -5445,14 +5453,17 @@ and `frontend-modern/src/components/Infrastructure/ResourceCorrelationSummary.ts cards from the Patrol page, resource drawer, and problem-resource dashboard panels, so canonical resource-filter path construction stays owned by the shared summary cards rather than being duplicated per surface. -The unified resource table now also supplies a canonical resource-label -resolver into the resource drawer, so related-resource timeline chips can use -the same governed display labels as the table without adding a new -detail-local lookup path. +Platform tables supply the resource-label resolver to the resource drawer +through `PlatformResourceDetailTableRow`. `createPlatformResourceLabelResolver(...)` +in `frontend-modern/src/features/platformPage/PlatformResourceDetailTableRow.tsx` +resolves an ID against the page's own resources through +`getPreferredInfrastructureDisplayName(...)`, so related-resource timeline +chips show the operator-local display name rather than a raw ID, without adding +a detail-local lookup path. A table that passes no resolver leaves those chips +on the raw resource ID. The resource drawer now also passes that same resolver into the shared -correlation summary, so dependency and dependent chips stay on governed -labels in the investigation path while the AI summary page keeps its broader -raw-ID fallback. +correlation summary, its only consumer, so dependency and dependent chips stay +on those labels in the investigation path. The same timeline and facet-bundle reads now also accept governed `kind` and `sourceType` filters, plus a governed `sourceAdapter` filter for adapter-level provenance drill-down, so history can narrow by canonical change class and @@ -5701,12 +5712,12 @@ because the legacy backend resource projection leaves `platformType` empty on several canonical resource types; the helper falls back to the resource's `sources` array via the existing source-platform normalization so mock fixtures and live backends produce the same client-side platform grouping. -That same boundary also owns the infrastructure table's operator-facing system -identity vocabulary. `frontend-modern/src/utils/resourceBadgePresentation.ts`, -`frontend-modern/src/components/Infrastructure/resourceBadges.ts`, and the -unified resource table sections may preserve full merged-source detail in -tooltips and accessibility metadata, but visible table headers, sort keys, and -row badges must answer what system the operator is looking at. Provider/API +That same boundary also owns the operator-facing system identity vocabulary. +`frontend-modern/src/utils/resourceBadgePresentation.ts` and its consumers, the +Docker hosts table's System column through `getDockerHostSystemBadge(...)` and +the resource drawer header, may preserve full merged-source detail in tooltips +and accessibility metadata, but visible headers, sort keys, and badges must +answer what system the operator is looking at. Provider/API platforms such as Proxmox, TrueNAS, VMware, and Kubernetes outrank collection methods; reported agent OS or appliance identity such as Unraid or Ubuntu outranks a generic container-runtime capability; and the shared generic source @@ -6028,8 +6039,8 @@ hydration and unsupported filtered queries, but canonical live freshness for supported resource snapshots must flow from websocket `state.resources` instead of layering confirmatory REST refresh loops on top of already-owned resource updates. -Shared infrastructure consumers such as the unified resource table and detail -drawer must present that owned metadata through shared helpers instead of +Shared infrastructure consumers such as platform tables and the resource +detail drawer must present that owned metadata through shared helpers instead of reconstructing privacy posture from display names, source types, or other incidental runtime hints. diff --git a/frontend-modern/browser-verification/6c79cba8f0c6e7b0.json b/frontend-modern/browser-verification/6c79cba8f0c6e7b0.json new file mode 100644 index 0000000000..35984127a5 --- /dev/null +++ b/frontend-modern/browser-verification/6c79cba8f0c6e7b0.json @@ -0,0 +1,40 @@ +{ + "version": 1, + "base_sha": "dcd409f9da0a7a7f35a2a4ad5a4354e0b1d3ee44", + "verified_at": "2026-10-07T10:52:19Z", + "result": "passed", + "changed_paths": [ + "frontend-modern/src/utils/resourceBadgePresentation.ts" + ], + "content_sha256": { + "frontend-modern/src/utils/resourceBadgePresentation.ts": "ff0409fed88a69acf8acba49ab66a7fd46147d3ba65e8433f0f758fc50a405ca" + }, + "routes": [ + "/docker", + "/standalone/machines" + ], + "viewports": [ + { + "width": 1440, + "height": 900 + }, + { + "width": 390, + "height": 844 + } + ], + "states": [ + "Embedded production build of this tree on a private demo-mode mock stack (port 7697, fresh data dir)", + "Docker hosts table System column shows getInfrastructureSystemIdentityBadges-derived labels: Ubuntu, Alpine, Debian, and LXC with the \"Docker running inside Proxmox LXC \" tooltip", + "Docker host inline drawer opens with its Overview/History/Manage tabs", + "Machines inline drawer header shows the type badge plus the system identity badge (Agent, Ubuntu 24.04 LTS)", + "Phone width: Docker hosts table hides the System column, Machines drawer header keeps both badges", + "No page errors; only console errors are the pre-existing inline-style CSP reports that PR 2642 fixes on main, which this base predates" + ], + "interactions": [ + "Clicked the System column header: aria-sort became ascending and rows ordered Alpine, Debian, LXC, LXC, Ubuntu x3, matching the rendered badges", + "Expanded the first Docker host row through its disclosure button at both widths", + "Expanded the first Machines row through its disclosure button at both widths", + "Production bundle-size check passed with the same chunk set" + ] +} diff --git a/frontend-modern/src/utils/__tests__/resourceBadgePresentation.branchcov.test.ts b/frontend-modern/src/utils/__tests__/resourceBadgePresentation.branchcov.test.ts index c0f9fe0b7f..7531635455 100644 --- a/frontend-modern/src/utils/__tests__/resourceBadgePresentation.branchcov.test.ts +++ b/frontend-modern/src/utils/__tests__/resourceBadgePresentation.branchcov.test.ts @@ -1,12 +1,11 @@ import { describe, expect, it } from 'vitest'; import { getInfrastructureSystemIdentityBadges, - getInfrastructureSystemIdentitySortLabel, getSourceBadge, getTypeBadge, getUnifiedSourceBadges, } from '@/utils/resourceBadgePresentation'; -import type { PlatformType, Resource, ResourceType, SourceType } from '@/types/resource'; +import type { Resource, ResourceType, SourceType } from '@/types/resource'; const makeResource = (overrides: Partial): Resource => ({ id: 'resource-1', @@ -485,38 +484,3 @@ describe('getDockerHostOsIdentityBadge (via getInfrastructureSystemIdentityBadge ]); }); }); - -describe('getInfrastructureSystemIdentitySortLabel — branch coverage', () => { - it('returns the first badge label when identity badges exist', () => { - const resource = makeResource({ - type: 'k8s-node', - platformType: 'kubernetes', - sourceType: 'api', - sources: ['kubernetes'], - platformData: { - sources: ['kubernetes'], - kubernetes: { version: '1.28.4' }, - }, - }); - expect(getInfrastructureSystemIdentitySortLabel(resource)).toBe('K8s 1.28.4'); - }); - - it('returns empty string when no badges and platformType is falsy', () => { - const resource = makeResource({ - type: 'storage', - platformType: '' as unknown as PlatformType, - sourceType: 'agent', - }); - expect(getInfrastructureSystemIdentitySortLabel(resource)).toBe(''); - }); - - it('returns the platform badge label when no system identity is found', () => { - const resource = makeResource({ - type: 'agent', - platformType: 'truenas', - sourceType: 'agent', - platformData: { sources: ['agent'] }, - }); - expect(getInfrastructureSystemIdentitySortLabel(resource)).toBe('TrueNAS'); - }); -}); diff --git a/frontend-modern/src/utils/__tests__/resourceBadgePresentation.branchcov0717.test.ts b/frontend-modern/src/utils/__tests__/resourceBadgePresentation.branchcov0717.test.ts index 8c4f433ee7..b89e59303c 100644 --- a/frontend-modern/src/utils/__tests__/resourceBadgePresentation.branchcov0717.test.ts +++ b/frontend-modern/src/utils/__tests__/resourceBadgePresentation.branchcov0717.test.ts @@ -5,8 +5,6 @@ import { getContainerRuntimeBadgeForRuntime, getInfrastructurePlatformBadges, getInfrastructureSystemIdentityBadges, - getInfrastructureSystemIdentitySortLabel, - getInfrastructureSystemTitleBadges, getPlatformBadge, getSourceBadge, getTypeBadge, @@ -19,8 +17,8 @@ import { // `getHostIdentityAgentProfile`, `getHostIdentityPlatform`, `getAgentSystemIdentityBadge`, // `getDockerHostOsIdentityBadge`, `getAvailabilitySystemIdentityBadge`, // `getStorageSystemIdentityBadge`, `getStoragePlatformSource`, `getSystemSourceVersion`, -// `getVersionedSourceBadge`, `proxmoxLxcDockerBadge`, `proxmoxLxcDockerVmid`, -// `badgeIdentityLabels`) are exercised transitively through the exported entry points +// `getVersionedSourceBadge`, `proxmoxLxcDockerBadge`, `proxmoxLxcDockerVmid`) are +// exercised transitively through the exported entry points // below, asserting on their observable outputs. const makeResource = (overrides: Partial = {}): Resource => @@ -211,28 +209,6 @@ describe('dedupeResourceBadges — branch coverage', () => { }); }); -describe('badgeIdentityLabels via getInfrastructureSystemTitleBadges — branch coverage', () => { - it('treats a system badge with no title as its label-only identity (value ? ... : "" arm)', () => { - // badgeIdentityLabels maps [badge.label, badge.title]; when title is undefined - // the `(value ? normalizeBadgeIdentityLabel(value) : '')` ternary takes the "" - // arm and the entry is dropped by filter(Boolean). The label identity survives, - // so a source badge sharing it is deduped away. - const result = getInfrastructureSystemTitleBadges( - [{ label: 'PVE', classes: 'type-cls' }], - [{ label: 'pve', classes: 'type-cls', title: 'pve' }], - ); - expect(result).toEqual([{ label: 'PVE', classes: 'type-cls' }]); - }); - - it('retains a source badge whose identity is absent from the system identity set', () => { - const result = getInfrastructureSystemTitleBadges( - [{ label: 'PVE', classes: 'type-cls' }], - [{ label: 'K8s', classes: 'type-cls', title: 'K8s' }], - ); - expect(result.map((b) => b.label)).toEqual(['PVE', 'K8s']); - }); -}); - describe('withBadgeVersion via getInfrastructureSystemIdentityBadges — branch coverage', () => { it('strips the Proxmox pve-manager wrapper format down to the dotted version', () => { const badges = getInfrastructureSystemIdentityBadges( @@ -923,36 +899,3 @@ describe('getInfrastructureSystemIdentityBadges — end-to-end branch coverage', expect(getInfrastructureSystemIdentityBadges(resource)).toEqual([]); }); }); - -describe('getInfrastructureSystemIdentitySortLabel — branch coverage', () => { - it('returns the first identity badge label when one resolves', () => { - const resource = makeResource({ - type: 'k8s-node', - platformType: 'kubernetes', - sourceType: 'api', - sources: ['kubernetes'], - platformData: { sources: ['kubernetes'], kubernetes: { version: '1.28.4' } }, - }); - expect(getInfrastructureSystemIdentitySortLabel(resource)).toBe('K8s 1.28.4'); - }); - - it('returns the platform badge label when no system identity is found', () => { - const resource = makeResource({ - type: 'agent', - platformType: 'truenas', - sourceType: 'agent', - platformData: { sources: ['agent'] }, - }); - expect(getInfrastructureSystemIdentitySortLabel(resource)).toBe('TrueNAS'); - }); - - it('returns "" when no identity, no platform badge, and no platformType resolve', () => { - const resource = makeResource({ - type: 'storage', - platformType: '' as Resource['platformType'], - sourceType: 'agent', - sources: [], - }); - expect(getInfrastructureSystemIdentitySortLabel(resource)).toBe(''); - }); -}); diff --git a/frontend-modern/src/utils/__tests__/resourceBadgePresentation.branchcov0725am.test.ts b/frontend-modern/src/utils/__tests__/resourceBadgePresentation.branchcov0725am.test.ts index ad27a89d1d..3ca067dacd 100644 --- a/frontend-modern/src/utils/__tests__/resourceBadgePresentation.branchcov0725am.test.ts +++ b/frontend-modern/src/utils/__tests__/resourceBadgePresentation.branchcov0725am.test.ts @@ -5,7 +5,6 @@ import { getContainerRuntimeBadgeForRuntime, getInfrastructurePlatformBadges, getInfrastructureSystemIdentityBadges, - getInfrastructureSystemIdentitySortLabel, getPlatformBadge, getTypeBadge, getUnifiedSourceBadges, @@ -131,16 +130,8 @@ describe('resourceBadgePresentation — dead-arm reachability characterization ( expect(badges.map((b) => b.label)).toEqual(['Docker / Podman']); }); - it('getInfrastructureSystemIdentitySortLabel derives a platform label for an unknown platformType, so the L701 truthy-stop of the `||` chain cannot fire', () => { - // Reaching the L701 operand requires the identity badge list to be empty - // (badges[0]?.label falsy). But the list is only empty when no identity at - // all resolves — which requires platformType to be absent/falsy (a non-empty - // platformType always yields a platform badge at L690). With a falsy - // platformType, getPlatformBadge returns null, so L701's `?.label` is - // always falsy when reached and never stops the chain. A non-empty (even - // unknown) platformType instead produces a non-empty identity list, so the - // sort label resolves at L700 and L701 is never reached. - const label = getInfrastructureSystemIdentitySortLabel( + it('getInfrastructureSystemIdentityBadges derives a platform badge for an unknown platformType', () => { + const badges = getInfrastructureSystemIdentityBadges( makeResource({ type: 'vm', platformType: 'fictional-hypervisor-xyz' as Resource['platformType'], @@ -148,7 +139,7 @@ describe('resourceBadgePresentation — dead-arm reachability characterization ( sources: [], }), ); - expect(label).toBe('Fictional Hypervisor Xyz'); + expect(badges.map((b) => b.label)).toEqual(['Fictional Hypervisor Xyz']); }); it('treats a whitespace-only agent.platform as absent, so the `if (!normalized) return null` guards at L271/L287 in the host-identity helpers cannot fire', () => { diff --git a/frontend-modern/src/utils/__tests__/resourceBadgePresentation.branchcov2.test.ts b/frontend-modern/src/utils/__tests__/resourceBadgePresentation.branchcov2.test.ts index fdfe224b36..c1404d1feb 100644 --- a/frontend-modern/src/utils/__tests__/resourceBadgePresentation.branchcov2.test.ts +++ b/frontend-modern/src/utils/__tests__/resourceBadgePresentation.branchcov2.test.ts @@ -3,7 +3,6 @@ import { getContainerRuntimeBadgeForRuntime, getInfrastructurePlatformBadges, getInfrastructureSystemIdentityBadges, - getInfrastructureSystemIdentitySortLabel, getPlatformBadge, getTypeBadge, getUnifiedSourceBadges, @@ -498,7 +497,7 @@ describe('getVersionedSourceBadge via getInfrastructureSystemIdentityBadges (bra }); }); -describe('getInfrastructureSystemIdentityBadges and getInfrastructureSystemIdentitySortLabel (branch coverage)', () => { +describe('getInfrastructureSystemIdentityBadges fallbacks (branch coverage)', () => { it('falls back to the platform badge for a bare agent resource with no other identity', () => { const resource = makeResource({ type: 'agent', @@ -507,10 +506,9 @@ describe('getInfrastructureSystemIdentityBadges and getInfrastructureSystemIdent sources: [], }); expect(getInfrastructureSystemIdentityBadges(resource).map((b) => b.label)).toEqual(['Agent']); - expect(getInfrastructureSystemIdentitySortLabel(resource)).toBe('Agent'); }); - it('returns an empty badge set and empty sort label when nothing resolves', () => { + it('returns an empty badge set when nothing resolves', () => { const resource = makeResource({ id: 'lonely', name: 'lonely', @@ -520,6 +518,5 @@ describe('getInfrastructureSystemIdentityBadges and getInfrastructureSystemIdent sources: [], }); expect(getInfrastructureSystemIdentityBadges(resource)).toEqual([]); - expect(getInfrastructureSystemIdentitySortLabel(resource)).toBe(''); }); }); diff --git a/frontend-modern/src/utils/__tests__/resourceBadgePresentation.test.ts b/frontend-modern/src/utils/__tests__/resourceBadgePresentation.test.ts index 13a3131199..c785a823cd 100644 --- a/frontend-modern/src/utils/__tests__/resourceBadgePresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/resourceBadgePresentation.test.ts @@ -4,8 +4,6 @@ import { getContainerRuntimeBadgeForRuntime, getInfrastructurePlatformBadges, getInfrastructureSystemIdentityBadges, - getInfrastructureSystemIdentitySortLabel, - getInfrastructureSystemTitleBadges, getPlatformBadge, getSourceBadge, getTypeBadge, @@ -158,7 +156,6 @@ describe('resourceBadgePresentation', () => { expect( getInfrastructureSystemIdentityBadges(unraidStorage).map((badge) => badge.label), ).toEqual(['Unraid']); - expect(getInfrastructureSystemIdentitySortLabel(unraidStorage)).toBe('Unraid'); const pbsDatastore = makeResource({ type: 'storage', @@ -278,7 +275,6 @@ describe('resourceBadgePresentation', () => { expect(getInfrastructureSystemIdentityBadges(resource).map((badge) => badge.label)).toEqual([ 'PVE', ]); - expect(getInfrastructureSystemIdentitySortLabel(resource)).toBe('PVE'); }); it('shows platform versions when the reported version belongs to the platform identity', () => { @@ -319,7 +315,7 @@ describe('resourceBadgePresentation', () => { ); }); - it('does not repeat the unversioned platform source after a versioned system identity', () => { + it('takes the platform version from the agent report when the platform facet has none', () => { const agentDiscoveredPve = makeResource({ type: 'agent', platformType: 'proxmox-pve', @@ -343,9 +339,6 @@ describe('resourceBadgePresentation', () => { expect(systemBadges.map((badge) => badge.label)).toEqual(['PVE 8.3.3']); expect(sourceBadges.map((badge) => badge.label)).toEqual(['PVE', 'Agent']); - expect( - getInfrastructureSystemTitleBadges(systemBadges, sourceBadges).map((badge) => badge.label), - ).toEqual(['PVE 8.3.3', 'Agent']); }); it('prefers platform-facet version over agent OS version on hybrid-source resources', () => { @@ -391,7 +384,6 @@ describe('resourceBadgePresentation', () => { expect(getInfrastructureSystemIdentityBadges(resource).map((badge) => badge.label)).toEqual([ 'PVE', ]); - expect(getInfrastructureSystemIdentitySortLabel(resource)).toBe('PVE'); }); it('renders Proxmox LXC Docker hosts with a canonical LXC badge and surfaces the VMID in the tooltip', () => { @@ -449,7 +441,6 @@ describe('resourceBadgePresentation', () => { expect(getInfrastructureSystemIdentityBadges(resource)[0]?.title).toBe( 'TCP availability probe power-meter-01.lab.local:1883', ); - expect(getInfrastructureSystemIdentitySortLabel(resource)).toBe('TCP'); }); it('uses ICMP identity for ping-only availability endpoints', () => { diff --git a/frontend-modern/src/utils/resourceBadgePresentation.ts b/frontend-modern/src/utils/resourceBadgePresentation.ts index 0709aeeaba..98ac949604 100644 --- a/frontend-modern/src/utils/resourceBadgePresentation.ts +++ b/frontend-modern/src/utils/resourceBadgePresentation.ts @@ -159,20 +159,6 @@ const withBadgeVersion = (badge: ResourceBadge, version: string): ResourceBadge }; }; -const VERSIONED_BADGE_SUFFIX_PATTERN = /\s+v?\d+(?:[._-]\d+)*(?:[-+~:][0-9a-z][0-9a-z._:+~-]*)?$/i; - -const normalizeBadgeIdentityLabel = (value: string): string => - value.trim().toLowerCase().replace(VERSIONED_BADGE_SUFFIX_PATTERN, '').trim(); - -const badgeIdentityLabels = (badge: ResourceBadge): string[] => - Array.from( - new Set( - [badge.label, badge.title] - .map((value) => (value ? normalizeBadgeIdentityLabel(value) : '')) - .filter(Boolean), - ), - ); - const normalizeUnifiedSourceKeys = (sources?: string[] | null): KnownSourcePlatform[] => { if (!sources || sources.length === 0) return []; const normalized = sources @@ -695,15 +681,6 @@ export function getInfrastructureSystemIdentityBadges(resource: Resource): Resou return getInfrastructurePlatformBadges(rawSources); } -export function getInfrastructureSystemIdentitySortLabel(resource: Resource): string { - return ( - getInfrastructureSystemIdentityBadges(resource)[0]?.label || - getPlatformBadge(resource.platformType)?.label || - resource.platformType || - '' - ); -} - export function dedupeResourceBadges( badges: Array, ): ResourceBadge[] { @@ -717,19 +694,6 @@ export function dedupeResourceBadges( }); } -export function getInfrastructureSystemTitleBadges( - systemBadges: ResourceBadge[], - sourceBadges: ResourceBadge[], -): ResourceBadge[] { - const systemIdentities = new Set(systemBadges.flatMap(badgeIdentityLabels)); - return dedupeResourceBadges([ - ...systemBadges, - ...sourceBadges.filter((badge) => - badgeIdentityLabels(badge).every((identity) => !systemIdentities.has(identity)), - ), - ]); -} - const getContainerRuntimeLabel = (runtime?: string | null): string => { const raw = (runtime || '').trim(); const normalized = raw.toLowerCase();