diff --git a/docs/TRUENAS.md b/docs/TRUENAS.md index 54dac2b6e6..270d15e838 100644 --- a/docs/TRUENAS.md +++ b/docs/TRUENAS.md @@ -9,8 +9,13 @@ Pulse v6 includes first-class monitoring for **TrueNAS SCALE** and **TrueNAS COR are listed under **Platform connections**. 3. Enter the TrueNAS URL (e.g., `https://truenas.local`), the API key, and the username that owns the key. -4. Click **Test Connection** → **Save**. -5. Data appears within one configured polling cycle (60 seconds by default). +4. Click **Test Connection**, then **Save**. The test reads system information + on a separate connection; it does not validate inventory or metric collection. +5. Leave the saved connection enabled and allow at least one configured polling + interval (60 seconds by default). Check that the appliance and expected + inventory appear. If they do not, follow the [polling checks](#stale-truenas-data) + without repeatedly testing or restarting; an elapsed interval is not proof + that a poll completed. ## Creating a TrueNAS API Key @@ -155,9 +160,11 @@ its trusted HTTPS origin without disabling certificate verification. ### No data appearing after adding connection - Allow one configured polling cycle (60 seconds by default), not a fixed 30-second wait. Check that the saved connection is enabled. -- **Test Connection** checks authentication and the selected transport, not - successful collection of every metric. The legacy REST diagnostic is - expected for recognized CORE 13 systems; it is not itself a connection error. +- **Test Connection** reads system information on a separate connection. Pools, + datasets, disks and alerts can still fail during ordinary polling, even when + the test succeeds. A successful test also does not establish live CPU, memory + or History readings. The legacy REST diagnostic is expected for recognized + CORE 13 systems; it is not itself a connection error. - Inspect a bounded local log excerpt for TrueNAS-related errors: ```bash journalctl -u pulse -n 100 --no-pager | grep -i truenas @@ -236,11 +243,13 @@ collection step. | No timestamps advance, or no completed attempt is recorded | Check that the connection and integration are enabled, then preserve the bounded local error above. This is not proof of an invalid key. | | Reconnects or TrueNAS sign-ins increase while `observed.collectedAt` advances | Session turnover alone does not establish failed authentication or stopped polling. Record the timing and any existing close/error message separately from the data freshness. | -**Test Connection** checks connectivity and authentication, not a complete -poll. Testing a saved connection can update `lastAttemptAt` and `lastSuccessAt` -and reset its failure count without refreshing `observed.collectedAt`. Record -any manual test and its time separately; a successful test is not evidence -that missing data or stopped polling has recovered. +**Test Connection** is not a complete poll. Some older builds, including Pulse +6.4.1 and 6.4.5, also let a saved-connection test update `lastAttemptAt` and +`lastSuccessAt` and reset its failure count without refreshing +`observed.collectedAt`. Record any manual test and its time separately, and +use the inventory observation time rather than the test time to check freshness. +A successful test is not evidence that missing data or stopped polling has +recovered. An `app.stats` error such as **Apps are not available** concerns Apps collection, not necessarily the key. Check the existing Apps status in diff --git a/frontend-modern/browser-tests/docs-fragment-navigation.tsx b/frontend-modern/browser-tests/docs-fragment-navigation.tsx index 5c11587d77..2c125f2a3b 100644 --- a/frontend-modern/browser-tests/docs-fragment-navigation.tsx +++ b/frontend-modern/browser-tests/docs-fragment-navigation.tsx @@ -22,6 +22,7 @@ const targets: Record = { malformed: '/docs/API#%invalid', missing: '/docs/API#does-not-exist', troubleshooting: '/docs/TROUBLESHOOTING#recovery-mode', + truenas: '/docs/TRUENAS', }; window.history.replaceState({}, '', targets[scenario] ?? targets.plain); diff --git a/frontend-modern/browser-tests/truenas-connection-help.cjs b/frontend-modern/browser-tests/truenas-connection-help.cjs new file mode 100644 index 0000000000..3a6e38541a --- /dev/null +++ b/frontend-modern/browser-tests/truenas-connection-help.cjs @@ -0,0 +1,81 @@ +// The real Docs page/router/styles and shipped TrueNAS help, without a backend. +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const { chromium, webkit } = require('playwright'); + +(async () => { + const root = '/workspace/frontend-modern'; + const artifacts = path.join(root, 'node_modules', 'truenas-connection-help-browser'); + fs.mkdirSync(artifacts, { recursive: true }); + const { createServer } = await import(path.join(root, 'node_modules/vite/dist/node/index.js')); + const server = await createServer({ + root, + configFile: path.join(root, 'vite.config.ts'), + cacheDir: path.join(root, 'node_modules', '.vite-truenas-connection-help'), + server: { host: '127.0.0.1', port: 5250, strictPort: true }, + optimizeDeps: { noDiscovery: true, include: [] }, + }); + const results = []; + let browser; + try { + await server.listen(); + for (const [engine, width] of [['chromium', 1280], ['webkit', 390]]) { + browser = await { chromium, webkit }[engine].launch(engine === 'chromium' + ? { headless: true, channel: 'chromium', args: ['--no-sandbox'] } + : { headless: true }); + const page = await browser.newPage({ viewport: { width, height: 900 }, + ...(engine === 'webkit' ? { isMobile: true, hasTouch: true } : {}) }); + const errors = []; + page.on('pageerror', (error) => errors.push(error.message)); + page.on('console', (message) => { + if (message.type() === 'error') errors.push(message.text()); + }); + await page.goto('http://127.0.0.1:5250/browser-tests/docs-fragment-navigation.html?scenario=truenas', + { waitUntil: 'domcontentloaded', timeout: 60_000 }); + const quickStart = page.getByRole('heading', { name: 'Quick Start', exact: true }); + await quickStart.waitFor(); + if (engine === 'webkit') { + await page.evaluate(async () => { + document.documentElement.classList.add('dark'); + await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))); + await Promise.all(document.getAnimations().map((animation) => animation.finished.catch(() => {}))); + }); + } + const article = await page.locator('article').innerText(); + for (const text of ['it does not validate inventory or metric collection', + 'an elapsed interval is not proof that a poll completed', + 'Some older builds, including Pulse 6.4.1 and 6.4.5', + 'use the inventory observation time rather than the test time', + 'do not copy a token or cookie', 'Do not post the full connection response']) + assert.ok(article.includes(text), text); + assert.ok(!article.includes('Data appears within one configured polling cycle')); + await quickStart.evaluate((heading) => heading.scrollIntoView({ block: 'start' })); + await page.screenshot({ path: path.join(artifacts, `${engine}-setup.png`) }); + const checks = page.getByRole('link', { name: 'polling checks', exact: true }).first(); + await checks.focus(); + await page.keyboard.press('Enter'); + await page.waitForFunction(() => { + const heading = document.getElementById('stale-truenas-data'); + return heading && heading.getBoundingClientRect().top >= 0 + && heading.getBoundingClientRect().top < innerHeight; + }); + assert.ok(page.url().endsWith('/docs/TRUENAS#stale-truenas-data')); + assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1)); + await page.screenshot({ path: path.join(artifacts, `${engine}-polling.png`) }); + assert.deepEqual(errors, []); + results.push({ engine, version: browser.version(), width, + probeScope: true, noGuaranteedPoll: true, versionQualifiedWarning: true, + keyboardPollingLink: true, noDocumentOverflow: true, errors }); + await browser.close(); + browser = undefined; + } + const result = { result: 'passed', playwright: require('playwright/package.json').version, + scope: 'shipped help rendering/navigation, not native TrueNAS polling', results }; + fs.writeFileSync(path.join(artifacts, 'result.json'), JSON.stringify(result, null, 2) + '\n'); + console.log(JSON.stringify(result)); + } finally { + if (browser) await browser.close(); + await server.close(); + } +})().catch((error) => { console.error(error); process.exitCode = 1; }); diff --git a/frontend-modern/public/docs/TRUENAS.md b/frontend-modern/public/docs/TRUENAS.md index 54dac2b6e6..270d15e838 100644 --- a/frontend-modern/public/docs/TRUENAS.md +++ b/frontend-modern/public/docs/TRUENAS.md @@ -9,8 +9,13 @@ Pulse v6 includes first-class monitoring for **TrueNAS SCALE** and **TrueNAS COR are listed under **Platform connections**. 3. Enter the TrueNAS URL (e.g., `https://truenas.local`), the API key, and the username that owns the key. -4. Click **Test Connection** → **Save**. -5. Data appears within one configured polling cycle (60 seconds by default). +4. Click **Test Connection**, then **Save**. The test reads system information + on a separate connection; it does not validate inventory or metric collection. +5. Leave the saved connection enabled and allow at least one configured polling + interval (60 seconds by default). Check that the appliance and expected + inventory appear. If they do not, follow the [polling checks](#stale-truenas-data) + without repeatedly testing or restarting; an elapsed interval is not proof + that a poll completed. ## Creating a TrueNAS API Key @@ -155,9 +160,11 @@ its trusted HTTPS origin without disabling certificate verification. ### No data appearing after adding connection - Allow one configured polling cycle (60 seconds by default), not a fixed 30-second wait. Check that the saved connection is enabled. -- **Test Connection** checks authentication and the selected transport, not - successful collection of every metric. The legacy REST diagnostic is - expected for recognized CORE 13 systems; it is not itself a connection error. +- **Test Connection** reads system information on a separate connection. Pools, + datasets, disks and alerts can still fail during ordinary polling, even when + the test succeeds. A successful test also does not establish live CPU, memory + or History readings. The legacy REST diagnostic is expected for recognized + CORE 13 systems; it is not itself a connection error. - Inspect a bounded local log excerpt for TrueNAS-related errors: ```bash journalctl -u pulse -n 100 --no-pager | grep -i truenas @@ -236,11 +243,13 @@ collection step. | No timestamps advance, or no completed attempt is recorded | Check that the connection and integration are enabled, then preserve the bounded local error above. This is not proof of an invalid key. | | Reconnects or TrueNAS sign-ins increase while `observed.collectedAt` advances | Session turnover alone does not establish failed authentication or stopped polling. Record the timing and any existing close/error message separately from the data freshness. | -**Test Connection** checks connectivity and authentication, not a complete -poll. Testing a saved connection can update `lastAttemptAt` and `lastSuccessAt` -and reset its failure count without refreshing `observed.collectedAt`. Record -any manual test and its time separately; a successful test is not evidence -that missing data or stopped polling has recovered. +**Test Connection** is not a complete poll. Some older builds, including Pulse +6.4.1 and 6.4.5, also let a saved-connection test update `lastAttemptAt` and +`lastSuccessAt` and reset its failure count without refreshing +`observed.collectedAt`. Record any manual test and its time separately, and +use the inventory observation time rather than the test time to check freshness. +A successful test is not evidence that missing data or stopped polling has +recovered. An `app.stats` error such as **Apps are not available** concerns Apps collection, not necessarily the key. Check the existing Apps status in diff --git a/frontend-modern/src/utils/__tests__/docsLinks.test.ts b/frontend-modern/src/utils/__tests__/docsLinks.test.ts index bfe282cb05..69144e2f20 100644 --- a/frontend-modern/src/utils/__tests__/docsLinks.test.ts +++ b/frontend-modern/src/utils/__tests__/docsLinks.test.ts @@ -61,6 +61,24 @@ function getRuntimeSourceFiles(dir: string): string[] { } describe('docsLinks', () => { + it('keeps TrueNAS setup acceptance separate from a system-information probe', () => { + const truenas = readFileSync(path.join(repoRoot, 'docs', 'TRUENAS.md'), 'utf8'); + const setup = truenas.split('## Quick Start')[1].split('## Creating a TrueNAS API Key')[0]; + expect(setup).toContain('it does not validate inventory or metric collection'); + expect(setup).toContain('an elapsed interval is not proof'); + expect(setup).toContain('[polling checks](#stale-truenas-data)'); + expect(setup).not.toContain('Data appears within one configured polling cycle'); + const noData = truenas + .split('### No data appearing after adding connection')[1] + .split('### Inventory works')[0]; + expect(noData).toContain('Pools,\n datasets, disks and alerts can still fail'); + expect(noData).toContain('does not establish live CPU, memory'); + expect(truenas).toContain('Some older builds, including Pulse\n6.4.1 and 6.4.5'); + expect(truenas).toContain('use the inventory observation time rather than the test time'); + expect(truenas).toContain('do not copy a token or cookie'); + expect(truenas).toContain('Do not post the full connection response'); + }); + it('keeps restored Proxmox node network details in the candidate packet', () => { const releaseNotes = readFileSync( path.join(repoRoot, 'docs', 'releases', 'RELEASE_NOTES_v6.4.0-rc.1.md'),