From be1125fbc8fede6f8b4c6908f1d5271e1d4cd49a Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 09:02:33 +0000 Subject: [PATCH] State configuration recovery's limits before transfer Remove the overview's all-connections and merge promises. Disclose the selected-workspace export's exclusions and destination-backup warning before opening either existing dialog, with the shipped migration guide and registered consuming coverage. Desktop and narrow browser proof preserves the existing actions and transfer scope. Change-source: pulse-maintainer --- .../subsystems/frontend-primitives.md | 11 +++ .../v6/internal/subsystems/registry.json | 3 +- .../7f87eb399f410e99.json | 46 ++++++++++ .../Settings/RecoverySettingsPanel.tsx | 22 +++-- .../__tests__/RecoverySettingsPanel.test.tsx | 86 +++++++++++++++++++ 5 files changed, 162 insertions(+), 6 deletions(-) create mode 100644 frontend-modern/browser-verification/7f87eb399f410e99.json create mode 100644 frontend-modern/src/components/Settings/__tests__/RecoverySettingsPanel.test.tsx diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 55f3afc747..c7dcae13c2 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -10003,6 +10003,17 @@ there. ### Configuration transfers retain their confirmed workspace +Recovery's overview identifies the selected-workspace configuration export as +limited rather than a full-server backup before either dialog is opened. It +names Proxmox connections, discloses missing history, TrueNAS/vSphere, local +login credentials and agent enrolment state, and links the shipped migration +guide for complete scope and full-state recovery. Restore warns to back up the +destination and describes replacement of included settings and API-token records, +never a merge option. `RecoverySettingsPanel.test.tsx` covers the visible scope, +existing dialog actions and passphrase defaults; desktop/narrow browser proof +must inspect the overview and follow both actions into the production dialogs. +Archive formats, server application and access authority do not change. + Recovery's export/import dialogs capture and display the effective workspace ID when opened, including explicit `default` scope. Their production flow freezes inputs before asynchronous file reads, binds both POSTs through the API client's diff --git a/docs/release-control/v6/internal/subsystems/registry.json b/docs/release-control/v6/internal/subsystems/registry.json index c760544557..edcda57657 100644 --- a/docs/release-control/v6/internal/subsystems/registry.json +++ b/docs/release-control/v6/internal/subsystems/registry.json @@ -5591,12 +5591,14 @@ "match_prefixes": [], "match_files": [ "frontend-modern/src/components/Settings/BackupTransferDialogs.tsx", + "frontend-modern/src/components/Settings/RecoverySettingsPanel.tsx", "frontend-modern/src/components/Settings/useBackupTransferFlow.ts" ], "allow_same_subsystem_tests": false, "test_prefixes": [], "exact_files": [ "frontend-modern/src/components/Settings/__tests__/BackupTransferDialogs.test.tsx", + "frontend-modern/src/components/Settings/__tests__/RecoverySettingsPanel.test.tsx", "frontend-modern/src/components/Settings/__tests__/useBackupTransferFlow.test.ts" ] }, @@ -5772,7 +5774,6 @@ "frontend-modern/src/components/Settings/networkSettingsModel.ts", "frontend-modern/src/components/Settings/NetworkSettingsPanel.tsx", "frontend-modern/src/components/Settings/OperationsPanel.tsx", - "frontend-modern/src/components/Settings/RecoverySettingsPanel.tsx", "frontend-modern/src/components/Settings/SecurityAuthPanel.tsx", "frontend-modern/src/components/Settings/SecurityOverviewPanel.tsx", "frontend-modern/src/components/Settings/Settings.tsx", diff --git a/frontend-modern/browser-verification/7f87eb399f410e99.json b/frontend-modern/browser-verification/7f87eb399f410e99.json new file mode 100644 index 0000000000..92a0c2609b --- /dev/null +++ b/frontend-modern/browser-verification/7f87eb399f410e99.json @@ -0,0 +1,46 @@ +{ + "version": 1, + "base_sha": "5a0549a65dff152d75b77bb4a35d6a7ebda48493", + "verified_at": "2026-10-25T09:02:28Z", + "result": "passed", + "changed_paths": [ + "frontend-modern/src/components/Settings/RecoverySettingsPanel.tsx" + ], + "content_sha256": { + "frontend-modern/src/components/Settings/RecoverySettingsPanel.tsx": "d958e3255357bed6e0f99481695c1d805b2143076f5678d0b37a97483bd83227" + }, + "routes": [ + "/settings/system/recovery (offline fixture mounting production RecoverySettingsPanel, useBackupTransferFlow, BackupTransferDialogs, API client and CSS; not full-app auth)", + "/docs/MIGRATION (production Docs viewer fetching exact shipped markdown in a separate tab)" + ], + "states": [ + "Configuration-only selected-workspace scope and exclusions visible before opening either dialog. Proxmox-specific connection wording; replacement of included settings/API-token records, no merge promise.", + "Overview wraps at desktop/320px in light and dark themes. Existing export/import dialogues keep captured workspace, masked inputs and scope guidance." + ], + "interactions": [ + "Desktop keyboard and narrow touch open the existing migration guide in a separate tab without losing Recovery.", + "Open existing export through real flow; enter masked synthetic passphrase and explicitly download the synthetic server response with original scoped API headers.", + "Open existing restore, select synthetic file and passphrase, inspect replacement warning, cancel without POST, and return focus to Restore Backup.", + "44 checks pass; all ten captures opened using native view_image. Playwright 1.56.1 and Chromium 141.0.7390.37." + ], + "viewports": [ + { + "width": 1440, + "height": 1000, + "browser": "Chromium", + "browser_version": "141.0.7390.37" + }, + { + "width": 320, + "height": 940, + "touch": true, + "browser": "Chromium", + "browser_version": "141.0.7390.37" + } + ], + "evidence": "Current lane RECEIPTS.md, browser/browser-result.json, PIXEL-REVIEW.md and BROWSER-BINDING.json. Build/fixture/driver and synthetic downloads retained as evidence only.", + "limitations": [ + "Safe synthetic HTTP uses real frontend producer/consumer, not server auth, cryptography, import application/idempotency, full app shell, private/native recovery or release delivery.", + "Full-document narrow guide capture is overview evidence, not exhaustive typography acceptance. No Back/reload or excluded reader/renderer/native scenario was attempted." + ] +} diff --git a/frontend-modern/src/components/Settings/RecoverySettingsPanel.tsx b/frontend-modern/src/components/Settings/RecoverySettingsPanel.tsx index 9841807d48..5421f7d345 100644 --- a/frontend-modern/src/components/Settings/RecoverySettingsPanel.tsx +++ b/frontend-modern/src/components/Settings/RecoverySettingsPanel.tsx @@ -2,6 +2,8 @@ import { Component, Show, For, Accessor, Setter, createUniqueId } from 'solid-js import SettingsPanel from '@/components/shared/SettingsPanel'; import { FormSelect } from '@/components/shared/FormSelect'; import { SectionHeader } from '@/components/shared/SectionHeader'; +import { ExternalTextLink } from '@/components/shared/ExternalTextLink'; +import { getShippedDocUrl } from '@/utils/docsLinks'; import { BACKUP_INTERVAL_MAX_MINUTES, BACKUP_INTERVAL_OPTIONS, @@ -209,11 +211,19 @@ export const RecoverySettingsPanel: Component = (pro {/* Backup & Restore Section */}
+

+ History, TrueNAS/vSphere connections, local login credentials and agent enrolment state + are not included. Check the{' '} + + migration guide + {' '} + for the full scope and full-state recovery. +

{/* Export Section */} @@ -253,7 +263,7 @@ export const RecoverySettingsPanel: Component = (pro clip-rule="evenodd" /> - All node connections & credentials + Proxmox connections & credentials
  • = (pro

    Restore Configuration

    -

    Import from an encrypted backup file

    +

    + Import from an encrypted configuration file. Back up the destination first. +

    {/* Feature list */}
      @@ -343,7 +355,7 @@ export const RecoverySettingsPanel: Component = (pro clip-rule="evenodd" /> - Merge or replace existing config + Replaces included settings and API-token records
    • diff --git a/frontend-modern/src/components/Settings/__tests__/RecoverySettingsPanel.test.tsx b/frontend-modern/src/components/Settings/__tests__/RecoverySettingsPanel.test.tsx new file mode 100644 index 0000000000..f17455d8a5 --- /dev/null +++ b/frontend-modern/src/components/Settings/__tests__/RecoverySettingsPanel.test.tsx @@ -0,0 +1,86 @@ +import { cleanup, fireEvent, render, screen } from '@solidjs/testing-library'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { createSignal } from 'solid-js'; +import { RecoverySettingsPanel } from '../RecoverySettingsPanel'; + +function renderRecovery(hasAuthentication = false) { + const [enabled, setEnabled] = createSignal(true); + const [interval, setInterval] = createSignal(300); + const [minutes, setMinutes] = createSignal(5); + const [custom, setCustom] = createSignal(false); + const [, setUnsaved] = createSignal(false); + const setShowExportDialog = vi.fn(); + const setShowImportDialog = vi.fn(); + const setUseCustomPassphrase = vi.fn(); + render(() => ( + false} + backupIntervalSelectValue={() => String(interval())} + backupIntervalSummary={() => 'Every 5 minutes'} + setHasUnsavedChanges={setUnsaved} + showExportDialog={() => false} + setShowExportDialog={setShowExportDialog} + showImportDialog={() => false} + setShowImportDialog={setShowImportDialog} + setUseCustomPassphrase={setUseCustomPassphrase} + securityStatus={() => ({ hasAuthentication })} + /> + )); + return { setShowExportDialog, setShowImportDialog, setUseCustomPassphrase }; +} + +describe('RecoverySettingsPanel', () => { + afterEach(cleanup); + + it('discloses the limited archive scope before either transfer is opened', () => { + renderRecovery(); + const text = document.body.textContent; + expect(text).toContain('selected workspace, not a full-server backup'); + expect(text).toContain('Proxmox connections & credentials'); + expect(text).toContain( + 'History, TrueNAS/vSphere connections, local login credentials and agent enrolment state are not included', + ); + expect(text).not.toContain('All node connections'); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + const guide = screen.getByRole('link', { name: 'migration guide' }); + expect(guide).toHaveAttribute('href', '/docs/MIGRATION'); + expect(guide).toHaveAttribute('target', '_blank'); + expect(guide).toHaveAttribute('rel', 'noopener noreferrer'); + }); + + it('warns that restoring replaces included settings rather than offering a merge', () => { + renderRecovery(true); + const text = document.body.textContent; + expect(text).toContain('Back up the destination first'); + expect(text).toContain('Replaces included settings and API-token records'); + expect(text).not.toContain('Merge or replace'); + expect(screen.getByRole('button', { name: 'Restore Backup' })).toBeEnabled(); + }); + + it.each([false, true])( + 'opens export with the existing passphrase default (authentication: %s)', + (hasAuthentication) => { + const actions = renderRecovery(hasAuthentication); + fireEvent.click(screen.getByRole('button', { name: 'Create Backup' })); + expect(actions.setUseCustomPassphrase).toHaveBeenCalledExactlyOnceWith(!hasAuthentication); + expect(actions.setShowExportDialog).toHaveBeenCalledExactlyOnceWith(true); + expect(actions.setShowImportDialog).not.toHaveBeenCalled(); + }, + ); + + it('opens only the existing import dialog from Restore Backup', () => { + const actions = renderRecovery(); + fireEvent.click(screen.getByRole('button', { name: 'Restore Backup' })); + expect(actions.setShowImportDialog).toHaveBeenCalledExactlyOnceWith(true); + expect(actions.setShowExportDialog).not.toHaveBeenCalled(); + expect(actions.setUseCustomPassphrase).not.toHaveBeenCalled(); + }); +});