From e2765dc2fbbe53e750b7db95b889c280683f2320 Mon Sep 17 00:00:00 2001 From: Pulse Monitor Date: Fri, 8 Aug 2025 21:41:20 +0000 Subject: [PATCH] feat: remove Discover button and show discovered nodes inline - Remove DiscoveryModal component and Discover button - Show discovered nodes directly in PVE/PBS tabs (greyed out) - Discovered nodes are clickable to configure - Intelligently filter out nodes that are part of configured clusters - Prevent duplicate entries for cluster members in discovery results - Maintain glassmorphic styling throughout --- .../src/components/Settings/Settings.tsx | 308 ++++++++++++------ 1 file changed, 208 insertions(+), 100 deletions(-) diff --git a/frontend-modern/src/components/Settings/Settings.tsx b/frontend-modern/src/components/Settings/Settings.tsx index e7f26f3bf..fb6d26223 100644 --- a/frontend-modern/src/components/Settings/Settings.tsx +++ b/frontend-modern/src/components/Settings/Settings.tsx @@ -2,7 +2,6 @@ import { Component, createSignal, onMount, For, Show, createEffect, onCleanup } import { useWebSocket } from '@/App'; import { showSuccess, showError } from '@/utils/toast'; import { NodeModal } from './NodeModal'; -import { DiscoveryModal } from './DiscoveryModal'; import { SettingsAPI } from '@/api/settings'; import { NodesAPI } from '@/api/nodes'; import { UpdatesAPI } from '@/api/updates'; @@ -24,8 +23,8 @@ const Settings: Component = () => { const [activeTab, setActiveTab] = createSignal('pve'); const [hasUnsavedChanges, setHasUnsavedChanges] = createSignal(false); const [nodes, setNodes] = createSignal([]); + const [discoveredNodes, setDiscoveredNodes] = createSignal([]); const [showNodeModal, setShowNodeModal] = createSignal(false); - const [showDiscoveryModal, setShowDiscoveryModal] = createSignal(false); const [editingNode, setEditingNode] = createSignal(null); const [currentNodeType, setCurrentNodeType] = createSignal<'pve' | 'pbs'>('pve'); @@ -116,6 +115,61 @@ const Settings: Component = () => { console.error('Failed to load nodes:', error); } }; + + // Function to load discovered nodes + const loadDiscoveredNodes = async () => { + try { + const response = await fetch('/api/discover'); + if (response.ok) { + const data = await response.json(); + if (data.servers && Array.isArray(data.servers)) { + // Get all configured hosts and cluster member IPs + const configuredHosts = new Set(); + const clusterMemberIPs = new Set(); + + nodes().forEach(n => { + // Add the main host + const host = n.host.replace(/^https?:\/\//, '').replace(/:\d+$/, ''); + configuredHosts.add(host.toLowerCase()); + + // If it's a cluster, add all member IPs + if (n.type === 'pve' && 'isCluster' in n && n.isCluster && 'clusterEndpoints' in n && n.clusterEndpoints) { + n.clusterEndpoints.forEach((endpoint: any) => { + if (endpoint.IP) { + clusterMemberIPs.add(endpoint.IP.toLowerCase()); + } + if (endpoint.Host) { + clusterMemberIPs.add(endpoint.Host.toLowerCase()); + } + }); + } + }); + + // Filter out nodes that are already configured or part of a cluster + const filtered = data.servers.filter((server: any) => { + const serverIP = server.ip?.toLowerCase(); + const serverHostname = server.hostname?.toLowerCase(); + + // Check if this server is already configured directly + if (configuredHosts.has(serverIP) || configuredHosts.has(serverHostname)) { + return false; + } + + // Check if this server is part of a configured cluster + if (clusterMemberIPs.has(serverIP) || clusterMemberIPs.has(serverHostname)) { + return false; + } + + return true; + }); + + setDiscoveredNodes(filtered); + } + } + } catch (error) { + console.error('Failed to load discovered nodes:', error); + } + }; // Load nodes and system settings on mount onMount(async () => { @@ -124,10 +178,10 @@ const Settings: Component = () => { console.log('[Settings] Node auto-registered, closing modal and refreshing nodes'); // Close any open modals setShowNodeModal(false); - setShowDiscoveryModal(false); setEditingNode(null); // Reload nodes loadNodes(); + loadDiscoveredNodes(); }); const unsubscribeRefresh = eventBus.on('refresh_nodes', () => { @@ -138,11 +192,12 @@ const Settings: Component = () => { // Poll for node updates when modal is open let pollInterval: ReturnType | undefined; createEffect(() => { - if (showNodeModal() || showDiscoveryModal()) { + if (showNodeModal()) { // Start polling every 3 seconds when modal is open pollInterval = setInterval(() => { console.log('[Settings] Polling for node updates...'); loadNodes(); + loadDiscoveredNodes(); }, 3000); } else { // Stop polling when modal is closed @@ -153,6 +208,11 @@ const Settings: Component = () => { } }); + // Poll for discovered nodes every 30 seconds + const discoveryInterval = setInterval(() => { + loadDiscoveredNodes(); + }, 30000); + // Clean up on unmount onCleanup(() => { unsubscribeAutoRegister(); @@ -160,6 +220,7 @@ const Settings: Component = () => { if (pollInterval) { clearInterval(pollInterval); } + clearInterval(discoveryInterval); }); try { @@ -177,6 +238,9 @@ const Settings: Component = () => { // Load nodes await loadNodes(); + // Load discovered nodes + await loadDiscoveredNodes(); + // Load system settings try { const systemResponse = await fetch('/api/config/system'); @@ -481,35 +545,20 @@ const Settings: Component = () => {

Proxmox VE Nodes

-
- - -
+
@@ -644,12 +693,67 @@ const Settings: Component = () => { )} - {nodes().filter(n => n.type === 'pve').length === 0 && ( + {nodes().filter(n => n.type === 'pve').length === 0 && discoveredNodes().filter(n => n.type === 'pve').length === 0 && (

No PVE nodes configured

Add a node to start monitoring

)} + + {/* Discovered PVE nodes */} + n.type === 'pve')}> + {(server) => ( +
{ + // Pre-fill the modal with discovered server info + setEditingNode({ + id: '', + type: 'pve', + name: server.hostname || `pve-${server.ip}`, + host: `https://${server.ip}:${server.port}`, + tokenName: '', + tokenValue: '', + verifySSL: false, + monitorVMs: true, + monitorContainers: true, + monitorStorage: true, + monitorBackups: true, + status: 'disconnected' + } as NodeConfigWithStatus); + setCurrentNodeType('pve'); + setShowNodeModal(true); + }} + > +
+
+
+
+
+
+

+ {server.hostname || `Proxmox VE at ${server.ip}`} +

+

+ {server.ip}:{server.port} +

+
+ + Discovered + + + Click to configure + +
+
+
+ + + +
+
+ )} +
@@ -659,35 +763,20 @@ const Settings: Component = () => {

Proxmox Backup Server Nodes

-
- - -
+
@@ -763,12 +852,67 @@ const Settings: Component = () => { )} - {nodes().filter(n => n.type === 'pbs').length === 0 && ( + {nodes().filter(n => n.type === 'pbs').length === 0 && discoveredNodes().filter(n => n.type === 'pbs').length === 0 && (

No PBS nodes configured

Add a node to start monitoring

)} + + {/* Discovered PBS nodes */} + n.type === 'pbs')}> + {(server) => ( +
{ + // Pre-fill the modal with discovered server info + setEditingNode({ + id: '', + type: 'pbs', + name: server.hostname || `pbs-${server.ip}`, + host: `https://${server.ip}:${server.port}`, + tokenName: '', + tokenValue: '', + verifySSL: false, + monitorDatastores: true, + monitorSyncJobs: true, + monitorVerifyJobs: true, + monitorPruneJobs: true, + status: 'disconnected' + } as NodeConfigWithStatus); + setCurrentNodeType('pbs'); + setShowNodeModal(true); + }} + > +
+
+
+
+
+
+

+ {server.hostname || `Backup Server at ${server.ip}`} +

+

+ {server.ip}:{server.port} +

+
+ + Discovered + + + Click to configure + +
+
+
+ + + +
+
+ )} +
@@ -1461,13 +1605,6 @@ docker run -d \ }} nodeType={currentNodeType()} editingNode={editingNode() ?? undefined} - showBackToDiscovery={!showDiscoveryModal() && editingNode()?.id === ''} - onBackToDiscovery={() => { - setShowNodeModal(false); - setEditingNode(null); - // Small delay to ensure modal transitions properly - setTimeout(() => setShowDiscoveryModal(true), 100); - }} onSave={async (nodeData) => { try { if (editingNode()) { @@ -1511,35 +1648,6 @@ docker run -d \ } }} /> - - {/* Discovery Modal */} - setShowDiscoveryModal(false)} - onAddServers={(servers) => { - // Single server selected from discovery - if (servers.length > 0) { - const server = servers[0]; - const nodeData: Partial = { - type: server.type, - name: server.hostname || server.ip, - host: `https://${server.ip}:${server.port}`, - verifySSL: false - }; - - setEditingNode(null); - setCurrentNodeType(server.type); - setEditingNode({ - ...nodeData, - id: '', - status: 'disconnected' - } as NodeConfigWithStatus); - setShowNodeModal(true); - // Close discovery modal when server is selected - setShowDiscoveryModal(false); - } - }} - /> {/* Export Dialog */}