From 065cbe46cff31116f29dff2c0dc728cc497122f1 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sat, 13 Dec 2025 07:59:52 +0000 Subject: [PATCH] chore(settings): Clean up unused imports and optimize polling - Removed unused getPulsePort/getPulseWebSocketUrl imports - Optimized polling to only run on proxmox tab (DiagnosticsPanel handles its own fetching) - Remaining lint warnings are for legacy code that may need future refactoring --- .../src/components/Settings/Settings.tsx | 1075 ++++++++--------- 1 file changed, 537 insertions(+), 538 deletions(-) diff --git a/frontend-modern/src/components/Settings/Settings.tsx b/frontend-modern/src/components/Settings/Settings.tsx index 652018c..ed25c10 100644 --- a/frontend-modern/src/components/Settings/Settings.tsx +++ b/frontend-modern/src/components/Settings/Settings.tsx @@ -13,7 +13,6 @@ import { useNavigate, useLocation } from '@solidjs/router'; import { useWebSocket } from '@/App'; import { showSuccess, showError, showWarning } from '@/utils/toast'; import { copyToClipboard } from '@/utils/clipboard'; -import { getPulsePort, getPulseWebSocketUrl } from '@/utils/url'; import { logger } from '@/utils/logger'; import { apiFetch, @@ -1103,7 +1102,7 @@ const Settings: Component = (props) => { if (typeof window === 'undefined') { return; } - const shouldPoll = currentTab() === 'proxmox' || currentTab() === 'diagnostics'; + const shouldPoll = currentTab() === 'proxmox'; if (!shouldPoll) { return; } @@ -5386,568 +5385,568 @@ const Settings: Component = (props) => { + - - + - {/* Delete Node Modal */ } - < Show when = { showDeleteNodeModal() } > -
- - -
-

- Removing this {nodePendingDeleteTypeLabel().toLowerCase()} also scrubs the Pulse - footprint on the host — the proxy service, SSH key, API token, and bind mount are - all cleaned up automatically. -

-
-

What happens next

-
    -
  • Pulse removes the node entry and clears related alerts.
  • -
  • - {nodePendingDeleteHost() ? ( - <> - The host {nodePendingDeleteHost()} loses - the proxy service, SSH key, and API token. - - ) : ( - 'The host loses the proxy service, SSH key, and API token.' - )} -
  • -
  • - If the host comes back later, rerunning the setup script reinstalls everything - with a fresh key. -
  • - -
  • - Backup user tokens on the PBS are removed, so jobs referencing them will no - longer authenticate until the node is re-added. -
  • -
    - -
  • - Mail gateway tokens are removed as part of the cleanup; re-enroll to restore - outbound telemetry. -
  • -
    -
-
-
- -
- - -
-
-
- - - {/* Node Modal - Use separate modals for PVE and PBS to ensure clean state */ } - < Show when = { isNodeModalVisible('pve') } > - { - setShowNodeModal(false); - setEditingNode(null); - // Increment resetKey to force form reset on next open - setModalResetKey((prev) => prev + 1); - }} - nodeType="pve" - editingNode={editingNode()?.type === 'pve' ? (editingNode() ?? undefined) : undefined} - securityStatus={securityStatus() ?? undefined} - temperatureMonitoringEnabled={resolveTemperatureMonitoringEnabled( - editingNode()?.type === 'pve' ? editingNode() : null, - )} - temperatureMonitoringLocked={temperatureMonitoringLocked()} - savingTemperatureSetting={savingTemperatureSetting()} - onToggleTemperatureMonitoring={ - editingNode()?.id - ? (enabled: boolean) => handleNodeTemperatureMonitoringChange(editingNode()!.id, enabled) - : handleTemperatureMonitoringChange - } - 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, - // Update hasPassword/hasToken based on whether credentials were provided - hasPassword: nodeData.password ? true : n.hasPassword, - hasToken: nodeData.tokenValue ? true : n.hasToken, - status: 'pending', - } - : n, - ), - ); - showSuccess('Node updated successfully'); - } else { - // Add new node - await NodesAPI.addNode(nodeData as NodeConfig); - - // Reload nodes to get the new ID - 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 || ('pending' as const), - })); - setNodes(nodesWithStatus); - showSuccess('Node added successfully'); - } - - setShowNodeModal(false); - setEditingNode(null); - } catch (error) { - showError(error instanceof Error ? error.message : 'Operation failed'); - } - }} - /> - - - {/* PBS Node Modal - Separate instance to prevent contamination */ } - < Show when = { isNodeModalVisible('pbs') } > - { - setShowNodeModal(false); - setEditingNode(null); - // Increment resetKey to force form reset on next open - setModalResetKey((prev) => prev + 1); - }} - nodeType="pbs" - editingNode={editingNode()?.type === 'pbs' ? (editingNode() ?? undefined) : undefined} - securityStatus={securityStatus() ?? undefined} - temperatureMonitoringEnabled={resolveTemperatureMonitoringEnabled( - editingNode()?.type === 'pbs' ? editingNode() : null, - )} - temperatureMonitoringLocked={temperatureMonitoringLocked()} - savingTemperatureSetting={savingTemperatureSetting()} - onToggleTemperatureMonitoring={ - editingNode()?.id - ? (enabled: boolean) => handleNodeTemperatureMonitoringChange(editingNode()!.id, enabled) - : handleTemperatureMonitoringChange - } - 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: 'pending', - } - : 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 || ('pending' as const), - })); - setNodes(nodesWithStatus); - showSuccess('Node added successfully'); - } - - setShowNodeModal(false); - setEditingNode(null); - } catch (error) { - showError(error instanceof Error ? error.message : 'Operation failed'); - } - }} - /> - - - {/* PMG Node Modal */ } - < Show when = { isNodeModalVisible('pmg') } > - { - setShowNodeModal(false); - setEditingNode(null); - setModalResetKey((prev) => prev + 1); - }} - nodeType="pmg" - editingNode={editingNode()?.type === 'pmg' ? (editingNode() ?? undefined) : undefined} - securityStatus={securityStatus() ?? undefined} - temperatureMonitoringEnabled={resolveTemperatureMonitoringEnabled( - editingNode()?.type === 'pmg' ? editingNode() : null, - )} - temperatureMonitoringLocked={temperatureMonitoringLocked()} - savingTemperatureSetting={savingTemperatureSetting()} - onToggleTemperatureMonitoring={ - editingNode()?.id - ? (enabled: boolean) => handleNodeTemperatureMonitoringChange(editingNode()!.id, enabled) - : handleTemperatureMonitoringChange - } - onSave={async (nodeData) => { - try { - if (editingNode() && editingNode()!.id) { - await NodesAPI.updateNode(editingNode()!.id, nodeData as NodeConfig); - setNodes( - nodes().map((n) => - n.id === editingNode()!.id - ? { - ...n, - ...nodeData, - hasPassword: nodeData.password ? true : n.hasPassword, - hasToken: nodeData.tokenValue ? true : n.hasToken, - status: 'pending', - } - : n, - ), - ); - showSuccess('Node updated successfully'); - } else { - await NodesAPI.addNode(nodeData as NodeConfig); - const nodesList = await NodesAPI.getNodes(); - const nodesWithStatus = nodesList.map((node) => ({ - ...node, - hasPassword: node.hasPassword ?? !!node.password, - hasToken: node.hasToken ?? !!node.tokenValue, - status: node.status || ('pending' as const), - })); - setNodes(nodesWithStatus); - showSuccess('Node added successfully'); - } - - setShowNodeModal(false); - setEditingNode(null); - } catch (error) { - showError(error instanceof Error ? error.message : 'Operation failed'); - } - }} - /> - - {/* Export Dialog */ } - < Show when = { showExportDialog() } > -
- - - -
- {/* Password Choice Section - Only show if auth is enabled */} - -
-
- - - + {/* Delete Node Modal */} + < Show when={showDeleteNodeModal()} > +
+ + +
+

+ Removing this {nodePendingDeleteTypeLabel().toLowerCase()} also scrubs the Pulse + footprint on the host — the proxy service, SSH key, API token, and bind mount are + all cleaned up automatically. +

+
+

What happens next

+
    +
  • Pulse removes the node entry and clears related alerts.
  • +
  • + {nodePendingDeleteHost() ? ( + <> + The host {nodePendingDeleteHost()} loses + the proxy service, SSH key, and API token. + + ) : ( + 'The host loses the proxy service, SSH key, and API token.' + )} +
  • +
  • + If the host comes back later, rerunning the setup script reinstalls everything + with a fresh key. +
  • + +
  • + Backup user tokens on the PBS are removed, so jobs referencing them will no + longer authenticate until the node is re-added. +
  • +
    + +
  • + Mail gateway tokens are removed as part of the cleanup; re-enroll to restore + outbound telemetry. +
  • +
    +
- - {/* Show password input based on selection */} -
- - setExportPassphrase(e.currentTarget.value)} - placeholder={ - securityStatus()?.hasAuthentication - ? useCustomPassphrase() - ? 'Enter a strong passphrase' - : 'Enter your Pulse login password' - : 'Enter a strong passphrase for encryption' - } - class={controlClass()} - /> - -

- You'll need this passphrase to restore the backup. -

-
- -

- You'll use this same password when restoring the backup -

-
-
- -
-
- +
+ +
+ + + {/* Node Modal - Use separate modals for PVE and PBS to ensure clean state */} + < Show when={isNodeModalVisible('pve')} > + { + setShowNodeModal(false); + setEditingNode(null); + // Increment resetKey to force form reset on next open + setModalResetKey((prev) => prev + 1); + }} + nodeType="pve" + editingNode={editingNode()?.type === 'pve' ? (editingNode() ?? undefined) : undefined} + securityStatus={securityStatus() ?? undefined} + temperatureMonitoringEnabled={resolveTemperatureMonitoringEnabled( + editingNode()?.type === 'pve' ? editingNode() : null, + )} + temperatureMonitoringLocked={temperatureMonitoringLocked()} + savingTemperatureSetting={savingTemperatureSetting()} + onToggleTemperatureMonitoring={ + editingNode()?.id + ? (enabled: boolean) => handleNodeTemperatureMonitoringChange(editingNode()!.id, enabled) + : handleTemperatureMonitoringChange + } + 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, + // Update hasPassword/hasToken based on whether credentials were provided + hasPassword: nodeData.password ? true : n.hasPassword, + hasToken: nodeData.tokenValue ? true : n.hasToken, + status: 'pending', + } + : n, + ), + ); + showSuccess('Node updated successfully'); + } else { + // Add new node + await NodesAPI.addNode(nodeData as NodeConfig); + + // Reload nodes to get the new ID + 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 || ('pending' as const), + })); + setNodes(nodesWithStatus); + showSuccess('Node added successfully'); + } + + setShowNodeModal(false); + setEditingNode(null); + } catch (error) { + showError(error instanceof Error ? error.message : 'Operation failed'); + } + }} + /> + + + {/* PBS Node Modal - Separate instance to prevent contamination */} + < Show when={isNodeModalVisible('pbs')} > + { + setShowNodeModal(false); + setEditingNode(null); + // Increment resetKey to force form reset on next open + setModalResetKey((prev) => prev + 1); + }} + nodeType="pbs" + editingNode={editingNode()?.type === 'pbs' ? (editingNode() ?? undefined) : undefined} + securityStatus={securityStatus() ?? undefined} + temperatureMonitoringEnabled={resolveTemperatureMonitoringEnabled( + editingNode()?.type === 'pbs' ? editingNode() : null, + )} + temperatureMonitoringLocked={temperatureMonitoringLocked()} + savingTemperatureSetting={savingTemperatureSetting()} + onToggleTemperatureMonitoring={ + editingNode()?.id + ? (enabled: boolean) => handleNodeTemperatureMonitoringChange(editingNode()!.id, enabled) + : handleTemperatureMonitoringChange + } + 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: 'pending', + } + : 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 || ('pending' as const), + })); + setNodes(nodesWithStatus); + showSuccess('Node added successfully'); + } + + setShowNodeModal(false); + setEditingNode(null); + } catch (error) { + showError(error instanceof Error ? error.message : 'Operation failed'); + } + }} + /> + + + {/* PMG Node Modal */} + < Show when={isNodeModalVisible('pmg')} > + { + setShowNodeModal(false); + setEditingNode(null); + setModalResetKey((prev) => prev + 1); + }} + nodeType="pmg" + editingNode={editingNode()?.type === 'pmg' ? (editingNode() ?? undefined) : undefined} + securityStatus={securityStatus() ?? undefined} + temperatureMonitoringEnabled={resolveTemperatureMonitoringEnabled( + editingNode()?.type === 'pmg' ? editingNode() : null, + )} + temperatureMonitoringLocked={temperatureMonitoringLocked()} + savingTemperatureSetting={savingTemperatureSetting()} + onToggleTemperatureMonitoring={ + editingNode()?.id + ? (enabled: boolean) => handleNodeTemperatureMonitoringChange(editingNode()!.id, enabled) + : handleTemperatureMonitoringChange + } + onSave={async (nodeData) => { + try { + if (editingNode() && editingNode()!.id) { + await NodesAPI.updateNode(editingNode()!.id, nodeData as NodeConfig); + setNodes( + nodes().map((n) => + n.id === editingNode()!.id + ? { + ...n, + ...nodeData, + hasPassword: nodeData.password ? true : n.hasPassword, + hasToken: nodeData.tokenValue ? true : n.hasToken, + status: 'pending', + } + : n, + ), + ); + showSuccess('Node updated successfully'); + } else { + await NodesAPI.addNode(nodeData as NodeConfig); + const nodesList = await NodesAPI.getNodes(); + const nodesWithStatus = nodesList.map((node) => ({ + ...node, + hasPassword: node.hasPassword ?? !!node.password, + hasToken: node.hasToken ?? !!node.tokenValue, + status: node.status || ('pending' as const), + })); + setNodes(nodesWithStatus); + showSuccess('Node added successfully'); + } + + setShowNodeModal(false); + setEditingNode(null); + } catch (error) { + showError(error instanceof Error ? error.message : 'Operation failed'); + } + }} + /> + + {/* Export Dialog */} + < Show when={showExportDialog()} > +
+ + + +
+ {/* Password Choice Section - Only show if auth is enabled */} + +
+
+ + + +
+
+
+ + {/* Show password input based on selection */} +
+ + setExportPassphrase(e.currentTarget.value)} + placeholder={ + securityStatus()?.hasAuthentication + ? useCustomPassphrase() + ? 'Enter a strong passphrase' + : 'Enter your Pulse login password' + : 'Enter a strong passphrase for encryption' + } + class={controlClass()} /> - -
- Important: The backup contains node credentials but NOT - authentication settings. Each Pulse instance should configure its own login - credentials for security. Remember your{' '} - {useCustomPassphrase() || !securityStatus()?.hasAuthentication - ? 'passphrase' - : 'password'}{' '} - for restoring. + +

+ You'll need this passphrase to restore the backup. +

+
+ +

+ You'll use this same password when restoring the backup +

+
+
+ +
+
+ + + +
+ Important: The backup contains node credentials but NOT + authentication settings. Each Pulse instance should configure its own login + credentials for security. Remember your{' '} + {useCustomPassphrase() || !securityStatus()?.hasAuthentication + ? 'passphrase' + : 'password'}{' '} + for restoring. +
+
+
+ +
+ +
-
- -
- - -
+
-
-
- {/* API Token Modal */ } - < Show when = { showApiTokenModal() } > -
- - + {/* API Token Modal */} + < Show when={showApiTokenModal()} > +
+ + -
-

- This Pulse instance requires an API token for export/import operations. Please enter - the API token configured on the server. -

+
+

+ This Pulse instance requires an API token for export/import operations. Please enter + the API token configured on the server. +

-
- - setApiTokenInput(e.currentTarget.value)} - placeholder="Enter API token" - class={controlClass()} - /> -
+
+ + setApiTokenInput(e.currentTarget.value)} + placeholder="Enter API token" + class={controlClass()} + /> +
-
-

The API token is set as an environment variable:

- API_TOKENS=token-for-export,token-for-automation -
+
+

The API token is set as an environment variable:

+ API_TOKENS=token-for-export,token-for-automation +
+
+ +
+ + +
+
- -
- - -
-
-
- {/* Import Dialog */ } - < Show when = { showImportDialog() } > -
- - + {/* Import Dialog */} + < Show when={showImportDialog()} > +
+ + -
-
- - { - const file = e.currentTarget.files?.[0]; - if (file) setImportFile(file); - }} - class={controlClass('cursor-pointer')} - /> -
+
+
+ + { + const file = e.currentTarget.files?.[0]; + if (file) setImportFile(file); + }} + class={controlClass('cursor-pointer')} + /> +
-
- - setImportPassphrase(e.currentTarget.value)} - placeholder="Enter the password used when creating this backup" - class={controlClass()} - /> -

- This is usually your Pulse login password, unless you used a custom passphrase -

-
+
+ + setImportPassphrase(e.currentTarget.value)} + placeholder="Enter the password used when creating this backup" + class={controlClass()} + /> +

+ This is usually your Pulse login password, unless you used a custom passphrase +

+
-
-

- Warning: Importing will replace all current configuration. This - action cannot be undone. -

-
+
+

+ Warning: Importing will replace all current configuration. This + action cannot be undone. +

+
-
- - -
+
+ + +
+
+
-
-
- { - setShowPasswordModal(false); - // Refresh security status after password change - loadSecurityStatus(); - }} - /> + { + setShowPasswordModal(false); + // Refresh security status after password change + loadSecurityStatus(); + }} + /> ); };