diff --git a/frontend-modern/browser-verification.json b/frontend-modern/browser-verification.json index 6f93fc4c5..7d0d17560 100644 --- a/frontend-modern/browser-verification.json +++ b/frontend-modern/browser-verification.json @@ -1,16 +1,16 @@ { "version": 1, - "base_sha": "550b2d80a175cc66ab4e7ac269b7ec350e101611", - "verified_at": "2026-09-08T11:54:21Z", + "base_sha": "d242055f9877a47d31b08133e3e8588cbff65f40", + "verified_at": "2026-09-08T20:59:34.058403Z", "result": "passed", "changed_paths": [ - "frontend-modern/src/utils/alertDestinationsPresentation.ts" + "frontend-modern/src/utils/proxmoxUpdateEvidence.ts" ], "content_sha256": { - "frontend-modern/src/utils/alertDestinationsPresentation.ts": "67f6f9a1c98345949e162bf06a38760e61457aa1e8a2130913646647a4f29e88" + "frontend-modern/src/utils/proxmoxUpdateEvidence.ts": "b99d8b5411e0e894f294f106d970978c8573915ff7ffe0ed60c451128e22d34a" }, "routes": [ - "/qualification (real health and delivery-log cards, scripted props)" + "/qualification.html (production-built real NodeDrawerOverview with synthetic reactive props)" ], "viewports": [ { @@ -27,11 +27,15 @@ } ], "states": [ - "Retained terminal server_error health diagnosis and failed delivery log entry" + "Unavailable permission denial", + "Retained stale count with permission-denial title", + "Checked zero", + "Checked positive count", + "Not-checked permission-denial reason" ], "interactions": [ - "Load both cards at desktop, tablet and phone widths; assert label, guidance, no page errors or horizontal overflow." + "At each width select unavailable, stale, zero, positive, not_checked, then unavailable again; assert text, stale title, no obsolete Sys.Audit prescription, no page errors or horizontal page overflow." ], - "notes": "Phone screenshot visually inspected; screenshots retained at /tmp/pulse-server-error-copy. Initial fixture omitted alertIds and timed out; corrected fixture passed all three widths. No installed delivery or receipt claim.", - "command": "pulse-heavy-run -- node scripts/check-delivery-server-error-copy.mjs" + "notes": "Built isolated real component with Vite esnext target, served compiled assets to Chromium. Desktop stale and phone unavailable screenshots visually inspected. Phone uses existing ellipsis for long values; full reason retained in title. No installed backend or reporter credential reproduction. Initial fixture build attempts failed on default target and virtual HTML path; corrected fixture build and all checks passed. Screenshots /tmp/pulse-update-access-copy; log /tmp/web-205503/browser-built.log.", + "command": "pulse-heavy-run -- node scripts/check-update-access-copy.mjs" } diff --git a/frontend-modern/src/components/Workloads/__tests__/NodeDrawerOverview.updateEvidence.test.tsx b/frontend-modern/src/components/Workloads/__tests__/NodeDrawerOverview.updateEvidence.test.tsx index 52f83acd3..14136132a 100644 --- a/frontend-modern/src/components/Workloads/__tests__/NodeDrawerOverview.updateEvidence.test.tsx +++ b/frontend-modern/src/components/Workloads/__tests__/NodeDrawerOverview.updateEvidence.test.tsx @@ -52,6 +52,6 @@ describe('NodeDrawerOverview update evidence', () => { /> )); - expect(screen.getByText('Unavailable · Sys.Audit permission required')).toBeInTheDocument(); + expect(screen.getByText('Unavailable · Update check access denied')).toBeInTheDocument(); }); }); diff --git a/frontend-modern/src/utils/__tests__/proxmoxUpdateEvidence.test.ts b/frontend-modern/src/utils/__tests__/proxmoxUpdateEvidence.test.ts index 07a1fefdd..f2dabdf3b 100644 --- a/frontend-modern/src/utils/__tests__/proxmoxUpdateEvidence.test.ts +++ b/frontend-modern/src/utils/__tests__/proxmoxUpdateEvidence.test.ts @@ -51,7 +51,7 @@ describe('Proxmox update evidence presentation', () => { pendingUpdatesReason: 'permission_denied', }), ).toMatchObject({ - value: 'Unavailable · Sys.Audit permission required', + value: 'Unavailable · Update check access denied', current: false, }); }); diff --git a/frontend-modern/src/utils/proxmoxUpdateEvidence.ts b/frontend-modern/src/utils/proxmoxUpdateEvidence.ts index a8c144ae1..4b90f7a1d 100644 --- a/frontend-modern/src/utils/proxmoxUpdateEvidence.ts +++ b/frontend-modern/src/utils/proxmoxUpdateEvidence.ts @@ -16,7 +16,7 @@ type UpdateEvidenceNode = Pick< const reasonLabel = (reason: Node['pendingUpdatesReason']): string => { switch (reason) { case 'permission_denied': - return 'Sys.Audit permission required'; + return 'Update check access denied'; case 'source_unavailable': return 'Proxmox source unavailable'; case 'check_failed': diff --git a/scripts/check-update-access-copy.mjs b/scripts/check-update-access-copy.mjs new file mode 100644 index 000000000..47c0b2a00 --- /dev/null +++ b/scripts/check-update-access-copy.mjs @@ -0,0 +1,81 @@ +// Isolated real-browser component qualification; no installed backend or delivery claim. +import { build, preview } from "../frontend-modern/node_modules/vite/dist/node/index.js"; +import solid from "../frontend-modern/node_modules/vite-plugin-solid/dist/esm/index.mjs"; +import { chromium } from "@playwright/test"; +import { resolve } from "node:path"; +import { mkdirSync } from "node:fs"; +import assert from "node:assert/strict"; +const root = resolve("frontend-modern"); +process.chdir(root); +const fixture = ` +import { render } from 'solid-js/web'; +import { createSignal } from 'solid-js'; +import { NodeDrawerOverview } from '/src/components/Workloads/NodeDrawerOverview'; +import '/src/index.css'; +const base = {id:'lab-pve1',name:'pve1',instance:'lab',status:'online',type:'node',cpu:0,memory:{total:1024,used:256,free:768,usage:25},disk:{total:1024,used:256,free:768,usage:25},uptime:3600,loadAverage:[],kernelVersion:'6.8.12',pveVersion:'9.0.1',cpuInfo:{model:'CPU',cores:4,sockets:1,mhz:'2400'},lastSeen:new Date().toISOString(),connectionHealth:'healthy'}; +const states = { + not_checked:{pendingUpdatesStatus:'not_checked',pendingUpdatesReason:'permission_denied'}, + unavailable:{pendingUpdatesStatus:'unavailable',pendingUpdatesReason:'permission_denied'}, + stale:{pendingUpdates:1,pendingUpdatesStatus:'stale',pendingUpdatesReason:'permission_denied',pendingUpdatesCheckedAt:new Date().toISOString()}, + zero:{pendingUpdates:0,pendingUpdatesStatus:'checked',pendingUpdatesCheckedAt:new Date().toISOString()}, + positive:{pendingUpdates:1,pendingUpdatesStatus:'checked',pendingUpdatesCheckedAt:new Date().toISOString()} +}; +const [state,setState] = createSignal('unavailable'); +render(() =>
, document.getElementById('root')); +`; +const config = { + root, + configFile: false, + optimizeDeps: { + noDiscovery: true, + entries: [], + esbuildOptions: { target: "esnext" }, + }, + esbuild: { target: "esnext" }, + plugins: [ + solid(), + { + name: "update-access-fixture", + resolveId(id) { + if (id === "/update-access-fixture.tsx" || id === resolve(root, "qualification.html")) return id; + }, + load(id) { + if (id === "/update-access-fixture.tsx") return fixture; + if (id === resolve(root, "qualification.html")) return '
'; + }, + }, + ], + resolve: { alias: { "@": resolve(root, "src") } }, + build: { target: "esnext", outDir: "/tmp/pulse-update-access-build", emptyOutDir: true, rollupOptions: { input: resolve(root, "qualification.html") } }, +}; +await build(config); +const server = await preview({root, configFile:false, build:{outDir:"/tmp/pulse-update-access-build"}, preview:{host:"127.0.0.1",port:5198,strictPort:true}}); + +let browser; +try { + + browser = await chromium.launch({ headless: true }); + mkdirSync("/tmp/pulse-update-access-copy", { recursive: true }); + for (const width of [1440, 900, 390]) { + const page = await browser.newPage({ viewport: { width, height: 900 } }); + const errors = []; + page.on('pageerror', error => errors.push(error.message)); + await page.goto("http://127.0.0.1:5198/qualification.html"); + for (const state of ['unavailable', 'stale', 'zero', 'positive', 'not_checked', 'unavailable']) { + await page.getByRole('button', {name:state,exact:true}).click(); + const expected = state === 'not_checked' ? 'Not checked · Update check access denied' : state === 'unavailable' ? 'Unavailable · Update check access denied' : state === 'stale' ? /1 pending · stale/ : state === 'zero' ? /No pending updates · checked/ : /1 pending · checked/; + await page.getByText(expected,{exact:typeof expected === 'string'}).waitFor(); + if (state === 'stale') assert.match(await page.getByText(/1 pending · stale/).getAttribute('title'), /Update check access denied/); + assert.equal(await page.getByText('Sys.Audit permission required',{exact:false}).count(),0); + assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), true); + assert.deepEqual(errors, []); + await page.screenshot({path: `/tmp/pulse-update-access-copy/${width}-${state}.png`, fullPage:true}); + } + await page.close(); + } + console.log(JSON.stringify({result:'passed',viewports:[1440,900,390],scope:'Real NodeDrawerOverview, Chromium, scripted props; no installed delivery claim'})); + +} finally { + await browser?.close(); + await new Promise(resolve => server.httpServer.close(resolve)); +}