diff --git a/frontend-modern/browser-verification/b1c21df0e17ba92c.json b/frontend-modern/browser-verification/b1c21df0e17ba92c.json new file mode 100644 index 0000000000..c43dc59eda --- /dev/null +++ b/frontend-modern/browser-verification/b1c21df0e17ba92c.json @@ -0,0 +1,39 @@ +{ + "version": 1, + "base_sha": "25f4131720dad07540378f4d82aa9c33b291e75d", + "verified_at": "2026-10-06T09:55:29Z", + "result": "passed", + "changed_paths": [ + "frontend-modern/src/utils/resourceMonitoringPolicy.ts" + ], + "content_sha256": { + "frontend-modern/src/utils/resourceMonitoringPolicy.ts": "5471b694198b0477b2736b752a20792c03f23dfd177d98000636c115924612a6" + }, + "routes": [ + "/alerts/overview", + "/vmware/overview" + ], + "viewports": [ + { + "width": 1440, + "height": 900 + }, + { + "width": 390, + "height": 844 + } + ], + "states": [ + "Alerts > Overview on a private mock stack built from main, with one vSphere VM alarm (finance-jump-01, metadata resourceType vm, incidentProvider vmware, incidentCode vmware_alarm_state, message 'VM vm-205 has VMware alarm ...') appended to the live websocket activeAlerts snapshots, because the mock demo scenario clears VM alarms", + "Card More > Monitoring menu with the change: header reads 'VMware vSphere remains the inventory owner' and Retire from monitoring reads 'VMware vSphere keeps the inventory record', at 1440 and 390", + "Same menu with main's helper swapped back in under Vite in the same session: both lines read Proxmox at 1440 and 390", + "vSphere overview, real mock VM finance-jump-01 (platformType vmware-vsphere, no injection), drawer Manage tab: retirement help reads 'vSphere owns this inventory record ... does not delete anything from vCenter'; with main's helper it read 'Proxmox owns this inventory record ... without deleting it from Proxmox'", + "Menu at 390 opens as a body portal below the card and stays inside the viewport" + ], + "interactions": [ + "Proxied the real websocket with Playwright routeWebSocket and appended the vSphere VM alarm to each activeAlerts array; also appended it to the /api/alerts/active REST recovery response", + "Clicked More, then Monitoring, on the injected alarm card and read the menu header and the Retire from monitoring description", + "Opened the finance-jump-01 drawer from the vSphere overview and switched to Manage", + "Swapped resourceMonitoringPolicy.ts to main's version under the running Vite for a same-session before, then restored the change and re-ran" + ] +} diff --git a/frontend-modern/src/utils/__tests__/resourceMonitoringPolicy.test.ts b/frontend-modern/src/utils/__tests__/resourceMonitoringPolicy.test.ts new file mode 100644 index 0000000000..a745bb5724 --- /dev/null +++ b/frontend-modern/src/utils/__tests__/resourceMonitoringPolicy.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from 'vitest'; + +import { describeResourceInventoryOwnership } from '@/utils/resourceMonitoringPolicy'; + +const ownerOf = (resourceType?: string, platformType?: string) => + describeResourceInventoryOwnership(resourceType, platformType).ownerLabel; + +describe('describeResourceInventoryOwnership', () => { + it('names vSphere as the owner of a VMware VM instead of guessing Proxmox from the type', () => { + const ownership = describeResourceInventoryOwnership('vm', 'vmware'); + + expect(ownership.ownerLabel).toBe('VMware vSphere'); + expect(ownership.retirementDescription).toContain('vCenter'); + expect(ownership.retirementDescription).not.toContain('Proxmox'); + }); + + it('keeps Proxmox as the owner of a VM when no platform is known', () => { + expect(ownerOf('vm', undefined)).toBe('Proxmox'); + expect(ownerOf('vm', '')).toBe('Proxmox'); + }); + + it('lets an explicit platform outrank the resource-type guess', () => { + expect(ownerOf('vm', 'vmware-vsphere')).toBe('VMware vSphere'); + expect(ownerOf('vm', 'truenas')).toBe('TrueNAS'); + expect(ownerOf('vm', 'kubernetes')).toBe('Kubernetes'); + expect(ownerOf('system-container', 'docker')).toBe('container runtime'); + expect(ownerOf('vm', 'agent')).toBe('Pulse agent'); + }); + + it('accepts canonical platform keys as well as platform families', () => { + expect(ownerOf('node', 'proxmox')).toBe('Proxmox'); + expect(ownerOf('vm', 'proxmox-pve')).toBe('Proxmox'); + expect(ownerOf('pbs', 'proxmox-pbs')).toBe('Proxmox'); + expect(ownerOf('pmg', 'proxmox-pmg')).toBe('Proxmox'); + expect(ownerOf('datastore', 'vmware-vsphere')).toBe('VMware vSphere'); + }); + + it('falls back to the resource type when the platform is unknown or unset', () => { + expect(ownerOf('pod', 'generic')).toBe('Kubernetes'); + expect(ownerOf('k8s-node')).toBe('Kubernetes'); + expect(ownerOf('app-container')).toBe('container runtime'); + expect(ownerOf('docker-host')).toBe('container runtime'); + expect(ownerOf('agent')).toBe('Pulse agent'); + expect(ownerOf('storage', 'unknown')).toBe('source system'); + }); + + it('reports only the Pulse agent as not provider-owned', () => { + expect(describeResourceInventoryOwnership('agent').providerOwned).toBe(false); + expect(describeResourceInventoryOwnership('vm', 'vmware').providerOwned).toBe(true); + }); +}); diff --git a/frontend-modern/src/utils/resourceMonitoringPolicy.ts b/frontend-modern/src/utils/resourceMonitoringPolicy.ts index 55f6557be0..add36dd359 100644 --- a/frontend-modern/src/utils/resourceMonitoringPolicy.ts +++ b/frontend-modern/src/utils/resourceMonitoringPolicy.ts @@ -4,60 +4,86 @@ export interface ResourceInventoryOwnership { retirementDescription: string; } +const PROXMOX_OWNERSHIP: ResourceInventoryOwnership = { + ownerLabel: 'Proxmox', + providerOwned: true, + retirementDescription: + 'Proxmox owns this inventory record. Retiring it stops Pulse attention and automation without deleting it from Proxmox. Restore active monitoring here at any time.', +}; + +const KUBERNETES_OWNERSHIP: ResourceInventoryOwnership = { + ownerLabel: 'Kubernetes', + providerOwned: true, + retirementDescription: + 'Kubernetes owns this object. Retiring it stops Pulse attention and automation without deleting the object from the cluster.', +}; + +const VMWARE_OWNERSHIP: ResourceInventoryOwnership = { + ownerLabel: 'VMware vSphere', + providerOwned: true, + retirementDescription: + 'vSphere owns this inventory record. Retiring it changes Pulse monitoring only and does not delete anything from vCenter.', +}; + +const TRUENAS_OWNERSHIP: ResourceInventoryOwnership = { + ownerLabel: 'TrueNAS', + providerOwned: true, + retirementDescription: + 'TrueNAS owns this inventory record. Retiring it changes Pulse monitoring only and does not delete anything from TrueNAS.', +}; + +const CONTAINER_RUNTIME_OWNERSHIP: ResourceInventoryOwnership = { + ownerLabel: 'container runtime', + providerOwned: true, + retirementDescription: + 'The container runtime owns this inventory record. Retiring it changes Pulse monitoring only and does not remove the runtime object.', +}; + +const AGENT_OWNERSHIP: ResourceInventoryOwnership = { + ownerLabel: 'Pulse agent', + providerOwned: false, + retirementDescription: + 'Retiring this machine stops Pulse attention and automation while keeping its history. Agent removal remains available from Machines.', +}; + +const SOURCE_SYSTEM_OWNERSHIP: ResourceInventoryOwnership = { + ownerLabel: 'source system', + providerOwned: true, + retirementDescription: + 'The source system owns this inventory record. Retiring it changes Pulse monitoring only and preserves the resource history.', +}; + +// Callers pass either a platform family ('proxmox', 'vmware') or a canonical +// platform key ('proxmox-pve', 'vmware-vsphere'); both name the same owner. +function ownershipForPlatform(platform: string): ResourceInventoryOwnership | undefined { + if (platform === 'proxmox' || platform.startsWith('proxmox-')) return PROXMOX_OWNERSHIP; + if (platform === 'vmware' || platform.startsWith('vmware-')) return VMWARE_OWNERSHIP; + if (platform === 'kubernetes') return KUBERNETES_OWNERSHIP; + if (platform === 'docker') return CONTAINER_RUNTIME_OWNERSHIP; + if (platform === 'truenas') return TRUENAS_OWNERSHIP; + if (platform === 'agent') return AGENT_OWNERSHIP; + return undefined; +} + +// Fallback for callers that know only the resource type. Resource types such +// as 'vm' are shared across platforms, so this guess must never outrank an +// explicit platform. +function ownershipForResourceType(type: string): ResourceInventoryOwnership { + if (['vm', 'system-container', 'oci-container', 'pbs', 'pmg'].includes(type)) { + return PROXMOX_OWNERSHIP; + } + if (type.startsWith('k8s-') || type === 'pod') return KUBERNETES_OWNERSHIP; + if (type.startsWith('docker-') || type === 'app-container') return CONTAINER_RUNTIME_OWNERSHIP; + if (type === 'agent') return AGENT_OWNERSHIP; + return SOURCE_SYSTEM_OWNERSHIP; +} + export function describeResourceInventoryOwnership( resourceType?: string, platformType?: string, ): ResourceInventoryOwnership { const type = (resourceType || '').toLowerCase(); - const platform = (platformType || '').toLowerCase(); + const platform = (platformType || '').trim().toLowerCase(); - if ( - platform === 'proxmox' || - ['vm', 'system-container', 'oci-container', 'pbs', 'pmg'].includes(type) - ) { - return { - ownerLabel: 'Proxmox', - providerOwned: true, - retirementDescription: - 'Proxmox owns this inventory record. Retiring it stops Pulse attention and automation without deleting it from Proxmox. Restore active monitoring here at any time.', - }; - } - if (platform === 'kubernetes' || type.startsWith('k8s-') || type === 'pod') { - return { - ownerLabel: 'Kubernetes', - providerOwned: true, - retirementDescription: - 'Kubernetes owns this object. Retiring it stops Pulse attention and automation without deleting the object from the cluster.', - }; - } - if (platform === 'vmware') { - return { - ownerLabel: 'VMware vSphere', - providerOwned: true, - retirementDescription: - 'vSphere owns this inventory record. Retiring it changes Pulse monitoring only and does not delete anything from vCenter.', - }; - } - if (platform === 'docker' || type.startsWith('docker-') || type === 'app-container') { - return { - ownerLabel: 'container runtime', - providerOwned: true, - retirementDescription: - 'The container runtime owns this inventory record. Retiring it changes Pulse monitoring only and does not remove the runtime object.', - }; - } - if (type === 'agent' || platform === 'agent') { - return { - ownerLabel: 'Pulse agent', - providerOwned: false, - retirementDescription: - 'Retiring this machine stops Pulse attention and automation while keeping its history. Agent removal remains available from Machines.', - }; - } - return { - ownerLabel: 'source system', - providerOwned: true, - retirementDescription: - 'The source system owns this inventory record. Retiring it changes Pulse monitoring only and preserves the resource history.', - }; + return ownershipForPlatform(platform) ?? ownershipForResourceType(type); }