diff --git a/frontend-modern/src/components/Settings/NodeModal.tsx b/frontend-modern/src/components/Settings/NodeModal.tsx index 956862771..fd1747d4f 100644 --- a/frontend-modern/src/components/Settings/NodeModal.tsx +++ b/frontend-modern/src/components/Settings/NodeModal.tsx @@ -7,6 +7,7 @@ import { NodesAPI } from '@/api/nodes'; interface NodeModalProps { isOpen: boolean; + resetKey?: number; onClose: () => void; nodeType: 'pve' | 'pbs'; editingNode?: NodeConfig; @@ -19,7 +20,8 @@ export const NodeModal: Component = (props) => { const [testResult, setTestResult] = createSignal<{ status: string; message: string; isCluster?: boolean } | null>(null); const [isTesting, setIsTesting] = createSignal(false); - const [formData, setFormData] = createSignal({ + // Function to get clean form data + const getCleanFormData = () => ({ name: '', host: '', authType: 'token' as 'password' | 'token', @@ -43,34 +45,41 @@ export const NodeModal: Component = (props) => { monitorPruneJobs: true, monitorGarbageJobs: false }); + + const [formData, setFormData] = createSignal(getCleanFormData()); - // Reset form when modal opens for adding new node (prevents contamination) + // Track previous state to detect changes + let previousResetKey: number | undefined = undefined; + let previousNodeType: string | undefined = undefined; + + // Reset form when conditions change createEffect(() => { - if (props.isOpen && !props.editingNode) { - // Clear form completely when opening modal for new node - setFormData({ - name: '', - host: '', - authType: 'token', - setupMode: 'auto', - user: '', - password: '', - tokenName: '', - tokenValue: '', - fingerprint: '', - verifySSL: true, - monitorVMs: true, - monitorContainers: true, - monitorStorage: true, - monitorBackups: true, - enableBackupManagement: true, - monitorDatastores: true, - monitorSyncJobs: true, - monitorVerifyJobs: true, - monitorPruneJobs: true, - monitorGarbageJobs: false - }); - setTestResult(null); // Also clear any test results + const key = props.resetKey; + const nodeType = props.nodeType; + const isOpen = props.isOpen; + const editingNode = props.editingNode; + + // Force reset if resetKey changed + if (key !== undefined && key !== previousResetKey) { + previousResetKey = key; + setFormData(() => getCleanFormData()); + setTestResult(null); + return; + } + + // Force reset if node type changed + if (nodeType !== previousNodeType && previousNodeType !== undefined) { + previousNodeType = nodeType; + setFormData(() => getCleanFormData()); + setTestResult(null); + return; + } + previousNodeType = nodeType; + + // Reset when opening for new node + if (isOpen && !editingNode) { + setFormData(() => getCleanFormData()); + setTestResult(null); } }); diff --git a/frontend-modern/src/components/Settings/Settings.tsx b/frontend-modern/src/components/Settings/Settings.tsx index d384a68d8..dfedebf39 100644 --- a/frontend-modern/src/components/Settings/Settings.tsx +++ b/frontend-modern/src/components/Settings/Settings.tsx @@ -90,6 +90,7 @@ const Settings: Component = () => { const [showNodeModal, setShowNodeModal] = createSignal(false); const [editingNode, setEditingNode] = createSignal(null); const [currentNodeType, setCurrentNodeType] = createSignal<'pve' | 'pbs'>('pve'); + const [modalResetKey, setModalResetKey] = createSignal(0); // System settings const [pollingInterval, setPollingInterval] = createSignal(5); @@ -783,6 +784,7 @@ const Settings: Component = () => { onClick={() => { setEditingNode(null); setCurrentNodeType('pve'); + setModalResetKey(prev => prev + 1); setShowNodeModal(true); }} class="px-4 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2" @@ -998,6 +1000,7 @@ const Settings: Component = () => { onClick={() => { setEditingNode(null); setCurrentNodeType('pbs'); + setModalResetKey(prev => prev + 1); setShowNodeModal(true); }} class="px-4 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2" @@ -1827,16 +1830,20 @@ docker run -d \ - {/* Node Modal */} - { - setShowNodeModal(false); - setEditingNode(null); - }} - nodeType={currentNodeType()} - editingNode={editingNode() ?? undefined} - onSave={async (nodeData) => { + {/* Node Modal - Use separate modals for PVE and PBS to ensure clean state */} + + { + setShowNodeModal(false); + setEditingNode(null); + // Increment resetKey to force form reset on next open + setModalResetKey(prev => prev + 1); + }} + nodeType="pve" + editingNode={editingNode() ?? undefined} + onSave={async (nodeData) => { try { if (editingNode() && editingNode()!.id) { // Update existing node (only if it has a valid ID) @@ -1878,7 +1885,66 @@ docker run -d \ showError(error instanceof Error ? error.message : 'Operation failed'); } }} - /> + /> + + + {/* PBS Node Modal - Separate instance to prevent contamination */} + + { + setShowNodeModal(false); + setEditingNode(null); + // Increment resetKey to force form reset on next open + setModalResetKey(prev => prev + 1); + }} + nodeType="pbs" + editingNode={editingNode() ?? undefined} + onSave={async (nodeData) => { + try { + if (editingNode() && editingNode()!.id) { + // Update existing node (only if it has a valid ID) + await NodesAPI.updateNode(editingNode()!.id, nodeData as NodeConfig); + + // Update local state + setNodes(nodes().map(n => + n.id === editingNode()!.id + ? { + ...n, + ...nodeData, + hasPassword: nodeData.password ? true : n.hasPassword, + hasToken: nodeData.tokenValue ? true : n.hasToken, + status: n.status + } + : n + )); + showSuccess('Node updated successfully'); + } else { + // Add new node + await NodesAPI.addNode(nodeData as NodeConfig); + + // Reload the nodes list to get the latest state + const nodesList = await NodesAPI.getNodes(); + const nodesWithStatus = nodesList.map(node => ({ + ...node, + // Use the hasPassword/hasToken from the API if available, otherwise check local fields + hasPassword: node.hasPassword ?? !!node.password, + hasToken: node.hasToken ?? !!node.tokenValue, + status: node.status || 'disconnected' as const + })); + setNodes(nodesWithStatus); + showSuccess('Node added successfully'); + } + + setShowNodeModal(false); + setEditingNode(null); + } catch (error) { + showError(error instanceof Error ? error.message : 'Operation failed'); + } + }} + /> + {/* Export Dialog */} diff --git a/test-pbs-cli.sh b/test-pbs-cli.sh deleted file mode 100644 index 62b7f0d89..000000000 --- a/test-pbs-cli.sh +++ /dev/null @@ -1,89 +0,0 @@ -#!/bin/bash - -echo "=== PBS Form Contamination Test ===" -echo "" -echo "This test verifies the PBS form fix by:" -echo "1. Opening Pulse UI in Firefox" -echo "2. You manually test according to the steps" -echo "" - -# Open the manual test instructions -echo "Opening test instructions and Pulse UI..." - -# Create HTML test page with instructions -cat > /tmp/pbs-test.html << 'EOF' - - - - PBS Form Test - - - -

PBS Form Contamination Test

- -
-

Test Steps:

-
    -
  1. Open Pulse UI (click here)
  2. -
  3. Navigate to Settings → Nodes tab
  4. -
  5. - Click "Add PVE Node"
    - - Enter name: test-pve
    - - Enter host: https://192.168.1.100:8006
    - - Click Cancel (don't save) -
  6. -
  7. - Click "Add PBS Node"
    - ✓ CHECK: Form should be COMPLETELY EMPTY
    - ✗ FAIL if: Form has any data from PVE -
  8. -
  9. - Fill PBS form and save:
    - - Name: test-pbs
    - - Host: https://192.168.1.200:8007
    - - Token: root@pam!pbstoken
    - - Token value: test-token-12345
    - - Click "Add Node" -
  10. -
  11. - Click Edit icon on the PBS node
    - ✓ CHECK: Form should show PBS data (test-pbs, etc)
    - ✗ FAIL if: Form is empty or shows wrong data -
  12. -
-
- -
-

Expected Results:

-
    -
  • PBS form NEVER shows PVE data ✓
  • -
  • PVE form NEVER shows PBS data ✓
  • -
  • Editing PBS node shows correct PBS data ✓
  • -
  • Editing PVE node shows correct PVE data ✓
  • -
-
- - -EOF - -# Try to open in browser if available -if command -v firefox &> /dev/null; then - firefox /tmp/pbs-test.html 2>/dev/null & -elif command -v chromium &> /dev/null; then - chromium /tmp/pbs-test.html 2>/dev/null & -else - echo "Please open /tmp/pbs-test.html in a browser" -fi - -echo "" -echo "Manual test page created at: /tmp/pbs-test.html" -echo "Please follow the test steps and report results." -echo "" \ No newline at end of file diff --git a/test-pbs-form.js b/test-pbs-form.js deleted file mode 100644 index a281d3a5e..000000000 --- a/test-pbs-form.js +++ /dev/null @@ -1,157 +0,0 @@ -#!/usr/bin/env node - -// Test to verify PBS form doesn't get contaminated with PVE data -// and that editing PBS nodes properly populates the form - -const puppeteer = require('puppeteer'); - -async function test() { - const browser = await puppeteer.launch({ - headless: 'new', - args: ['--no-sandbox', '--disable-setuid-sandbox'] - }); - const page = await browser.newPage(); - - console.log('Testing PBS form contamination fix...\n'); - - try { - // Navigate to Pulse - await page.goto('http://localhost:7655', { waitUntil: 'networkidle0' }); - console.log('✓ Connected to Pulse'); - - // Go to Settings - find the Settings nav item - await page.waitForSelector('nav'); - const settingsLink = await page.$('a:has-text("Settings")') || await page.$('button:has-text("Settings")'); - if (settingsLink) { - await settingsLink.click(); - } else { - // Try clicking by text - await page.evaluate(() => { - const links = Array.from(document.querySelectorAll('a, button')); - const settings = links.find(el => el.textContent?.includes('Settings')); - if (settings) settings.click(); - }); - } - await page.waitForTimeout(1000); - console.log('✓ Navigated to Settings'); - - // Test 1: Add a PVE node first - console.log('\n--- Test 1: Add PVE node ---'); - await page.click('button:has-text("Add PVE Node")'); - await page.waitForSelector('h3:has-text("Add Proxmox VE Node")'); - - // Fill PVE form - await page.type('input[placeholder="pve.example.com or IP"]', 'pve-test.local'); - await page.type('input[placeholder="https://pve.example.com:8006"]', 'https://192.168.1.100:8006'); - - // Close PVE modal - await page.keyboard.press('Escape'); - await page.waitForTimeout(500); - console.log('✓ Filled and closed PVE form'); - - // Test 2: Now add PBS node - should NOT have PVE data - console.log('\n--- Test 2: Add PBS after PVE - check for contamination ---'); - await page.click('button:has-text("Add PBS Node")'); - await page.waitForSelector('h3:has-text("Add Proxmox Backup Server Node")'); - - // Check that PBS form fields are empty (not contaminated with PVE data) - const pbsName = await page.$eval('input[placeholder="pbs.example.com or IP"]', el => el.value); - const pbsHost = await page.$eval('input[placeholder="https://pbs.example.com:8007"]', el => el.value); - - if (pbsName === '' && pbsHost === '') { - console.log('✓ PBS form is clean - no PVE contamination'); - } else { - console.log('✗ FAIL: PBS form contaminated with data:', { pbsName, pbsHost }); - throw new Error('PBS form contaminated with PVE data'); - } - - // Fill PBS form for next test - await page.type('input[placeholder="pbs.example.com or IP"]', 'pbs-test.local'); - await page.type('input[placeholder="https://pbs.example.com:8007"]', 'https://192.168.1.200:8007'); - await page.type('input[placeholder="root@pam!tokenname"]', 'root@pam!test-token'); - await page.type('input[placeholder*="xxxx"]', 'test-token-value-12345'); - - // Save PBS node - await page.click('button:has-text("Add Node")'); - await page.waitForTimeout(1000); - console.log('✓ PBS node added'); - - // Test 3: Edit PBS node - should populate with PBS data - console.log('\n--- Test 3: Edit PBS node - check data population ---'); - - // Find and click edit button for PBS node - const pbsCard = await page.$('div:has(h4:has-text("pbs-test.local"))'); - if (pbsCard) { - const editButton = await pbsCard.$('button[title*="dit"]'); - if (editButton) { - await editButton.click(); - await page.waitForSelector('h3:has-text("Edit Proxmox Backup Server Node")'); - - // Check that form is populated with PBS data - const editName = await page.$eval('input[placeholder="pbs.example.com or IP"]', el => el.value); - const editHost = await page.$eval('input[placeholder="https://pbs.example.com:8007"]', el => el.value); - const editToken = await page.$eval('input[placeholder="root@pam!tokenname"]', el => el.value); - - if (editName === 'pbs-test.local' && - editHost === 'https://192.168.1.200:8007' && - editToken === 'root@pam!test-token') { - console.log('✓ PBS edit form correctly populated with PBS data'); - } else { - console.log('✗ FAIL: PBS edit form not populated correctly:', { editName, editHost, editToken }); - throw new Error('PBS edit form not populated correctly'); - } - } - } - - // Close modal - await page.keyboard.press('Escape'); - await page.waitForTimeout(500); - - // Test 4: Edit PVE after PBS - ensure no cross-contamination - console.log('\n--- Test 4: Edit PVE after PBS - check for contamination ---'); - - // First need to add a real PVE node to edit - await page.click('button:has-text("Add PVE Node")'); - await page.waitForSelector('h3:has-text("Add Proxmox VE Node")'); - await page.type('input[placeholder="pve.example.com or IP"]', 'pve-real.local'); - await page.type('input[placeholder="https://pve.example.com:8006"]', 'https://192.168.1.50:8006'); - await page.type('input[placeholder="root@pam!tokenname"]', 'root@pam!pve-token'); - await page.type('input[placeholder*="xxxx"]', 'pve-token-value-54321'); - await page.click('button:has-text("Add Node")'); - await page.waitForTimeout(1000); - - // Now edit the PVE node - const pveCard = await page.$('div:has(h4:has-text("pve-real.local"))'); - if (pveCard) { - const editButton = await pveCard.$('button[title*="dit"]'); - if (editButton) { - await editButton.click(); - await page.waitForSelector('h3:has-text("Edit Proxmox VE Node")'); - - // Check that form is populated with PVE data, not PBS data - const pveEditName = await page.$eval('input[placeholder="pve.example.com or IP"]', el => el.value); - const pveEditHost = await page.$eval('input[placeholder="https://pve.example.com:8006"]', el => el.value); - - if (pveEditName === 'pve-real.local' && pveEditHost === 'https://192.168.1.50:8006') { - console.log('✓ PVE edit form correctly populated with PVE data (no PBS contamination)'); - } else { - console.log('✗ FAIL: PVE edit form contaminated or incorrect:', { pveEditName, pveEditHost }); - throw new Error('PVE edit form contaminated or incorrect'); - } - } - } - - console.log('\n========================================'); - console.log('✓ ALL TESTS PASSED - PBS form fix verified!'); - console.log('========================================\n'); - - } catch (error) { - console.error('\n✗ TEST FAILED:', error.message); - await browser.close(); - process.exit(1); - } - - await browser.close(); -} - -test().catch(console.error); \ No newline at end of file diff --git a/test-pbs-real.js b/test-pbs-real.js deleted file mode 100644 index dfb1734fb..000000000 --- a/test-pbs-real.js +++ /dev/null @@ -1,45 +0,0 @@ -// Simple test that actually checks if the currentNodeType is being set -// by looking at the compiled JavaScript - -const fs = require('fs'); - -console.log('Checking if PBS form fix is actually in the code...\n'); - -// Read the built frontend JS -const jsFile = '/opt/pulse/frontend-modern/dist/assets/index-D2Xfq6EC.js'; -const content = fs.readFileSync(jsFile, 'utf8'); - -// Check if our fix is in the compiled code -// We're looking for setCurrentNodeType being called with node.type -const hasOldBuggyCode = content.includes('setEditingNode(node),setShowNodeModal(!0)'); -const hasFixedCode = content.includes('setCurrentNodeType(node.type') || - content.includes('setCurrentNodeType(e.type') || - content.includes('CurrentNodeType(n.type') || - content.includes('CurrentNodeType(t.type'); - -console.log('Checking compiled JavaScript for the fix...'); -console.log('Old buggy pattern found:', hasOldBuggyCode); -console.log('Fixed pattern found:', hasFixedCode); - -if (!hasFixedCode) { - console.log('\n❌ FIX NOT FOUND IN COMPILED CODE!'); - console.log('The fix is not in the production build.'); - console.log('Need to rebuild: cd /opt/pulse/frontend-modern && npm run build'); - process.exit(1); -} else { - console.log('\n✅ Fix appears to be in the code'); - console.log('The production build contains the currentNodeType fix'); -} - -// Also check the source to make sure it's there -const sourceFile = '/opt/pulse/frontend-modern/src/components/Settings/Settings.tsx'; -const source = fs.readFileSync(sourceFile, 'utf8'); - -if (source.includes('setCurrentNodeType(node.type')) { - console.log('✅ Fix confirmed in source code'); -} else { - console.log('❌ Fix NOT in source code!'); - process.exit(1); -} - -console.log('\nThe fix is in place, but still needs manual testing to verify it works.'); \ No newline at end of file diff --git a/test-pbs-simple.sh b/test-pbs-simple.sh deleted file mode 100755 index ff3f9cf8b..000000000 --- a/test-pbs-simple.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/bash - -echo "Testing PBS form fix..." -echo "" - -# Start Pulse if not running -sudo systemctl restart pulse-backend - -sleep 3 - -# Test 1: Add a PVE node -echo "1. Adding PVE node..." -curl -X POST http://localhost:7655/api/nodes \ - -H "Content-Type: application/json" \ - -d '{ - "type": "pve", - "name": "pve-test", - "host": "https://192.168.1.100:8006", - "tokenName": "root@pam!test", - "tokenValue": "test-token-123" - }' 2>/dev/null - -if [ $? -eq 0 ]; then - echo "✓ PVE node added" -else - echo "✗ Failed to add PVE node" -fi - -echo "" - -# Test 2: Add a PBS node -echo "2. Adding PBS node..." -curl -X POST http://localhost:7655/api/nodes \ - -H "Content-Type: application/json" \ - -d '{ - "type": "pbs", - "name": "pbs-test", - "host": "https://192.168.1.200:8007", - "tokenName": "root@pam!pbstest", - "tokenValue": "pbs-token-456" - }' 2>/dev/null - -if [ $? -eq 0 ]; then - echo "✓ PBS node added" -else - echo "✗ Failed to add PBS node" -fi - -echo "" - -# Test 3: Get nodes and verify they're separate -echo "3. Verifying nodes are correctly stored..." -NODES=$(curl -s http://localhost:7655/api/nodes) - -# Check if PVE node exists with correct data -if echo "$NODES" | grep -q '"name":"pve-test".*"type":"pve"'; then - echo "✓ PVE node data correct" -else - echo "✗ PVE node data incorrect" -fi - -# Check if PBS node exists with correct data -if echo "$NODES" | grep -q '"name":"pbs-test".*"type":"pbs"'; then - echo "✓ PBS node data correct" -else - echo "✗ PBS node data incorrect" -fi - -echo "" -echo "Test completed. The UI should now:" -echo "1. Show clean forms when adding new nodes" -echo "2. Populate correct data when editing existing nodes" -echo "3. Not mix PVE and PBS data" \ No newline at end of file