mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-25 21:20:00 +00:00
Restore reviewed configuration recovery guidance on current main
Change-source: pulse-maintainer
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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."
|
||||
]
|
||||
}
|
||||
@@ -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<RecoverySettingsPanelProps> = (pro
|
||||
{/* Backup & Restore Section */}
|
||||
<div class="p-4 sm:p-6">
|
||||
<SectionHeader
|
||||
title="Backup & restore"
|
||||
description="Backup your node configurations and credentials or restore from a previous backup."
|
||||
title="Configuration backup & restore"
|
||||
description="Create an encrypted configuration export for the selected workspace, not a full-server backup."
|
||||
size="md"
|
||||
class="mb-4"
|
||||
/>
|
||||
<p class="mb-4 text-sm text-muted">
|
||||
History, TrueNAS/vSphere connections, local login credentials and agent enrolment state
|
||||
are not included. Check the{' '}
|
||||
<ExternalTextLink href={getShippedDocUrl('MIGRATION.md')}>
|
||||
migration guide
|
||||
</ExternalTextLink>{' '}
|
||||
for the full scope and full-state recovery.
|
||||
</p>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* Export Section */}
|
||||
@@ -253,7 +263,7 @@ export const RecoverySettingsPanel: Component<RecoverySettingsPanelProps> = (pro
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span>All node connections & credentials</span>
|
||||
<span>Proxmox connections & credentials</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 text-xs text-muted">
|
||||
<svg
|
||||
@@ -331,7 +341,9 @@ export const RecoverySettingsPanel: Component<RecoverySettingsPanelProps> = (pro
|
||||
<h4 class="text-base font-semibold text-base-content mb-1">
|
||||
Restore Configuration
|
||||
</h4>
|
||||
<p class="text-sm text-muted mb-3">Import from an encrypted backup file</p>
|
||||
<p class="text-sm text-muted mb-3">
|
||||
Import from an encrypted configuration file. Back up the destination first.
|
||||
</p>
|
||||
|
||||
{/* Feature list */}
|
||||
<ul class="space-y-1 mb-4">
|
||||
@@ -343,7 +355,7 @@ export const RecoverySettingsPanel: Component<RecoverySettingsPanelProps> = (pro
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span>Merge or replace existing config</span>
|
||||
<span>Replaces included settings and API-token records</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 text-xs text-muted">
|
||||
<svg class="w-3.5 h-3.5 text-blue-500" fill="currentColor" viewBox="0 0 20 20">
|
||||
|
||||
@@ -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(() => (
|
||||
<RecoverySettingsPanel
|
||||
backupPollingEnabled={enabled}
|
||||
setBackupPollingEnabled={setEnabled}
|
||||
backupPollingInterval={interval}
|
||||
setBackupPollingInterval={setInterval}
|
||||
backupPollingCustomMinutes={minutes}
|
||||
setBackupPollingCustomMinutes={setMinutes}
|
||||
backupPollingUseCustom={custom}
|
||||
setBackupPollingUseCustom={setCustom}
|
||||
backupPollingEnvLocked={() => 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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user