diff --git a/frontend-modern/src/components/Alerts/ResourceTable.tsx b/frontend-modern/src/components/Alerts/ResourceTable.tsx index 720ba0ffb..fa68162b0 100644 --- a/frontend-modern/src/components/Alerts/ResourceTable.tsx +++ b/frontend-modern/src/components/Alerts/ResourceTable.tsx @@ -13,7 +13,8 @@ interface ResourceTableProps { onRemoveOverride: (resourceId: string) => void; onToggleDisabled?: (resourceId: string) => void; onToggleNodeConnectivity?: (nodeId: string) => void; - onBatchToggleAlerts?: (resourceIds: string[], enable: boolean) => void; + onBatchToggleNodeConnectivity?: (nodeIds: string[], targetState: boolean) => void; + onBatchToggleDisabled?: (resourceIds: string[], targetState: boolean) => void; editingId: () => string | null; editingThresholds: () => Record; setEditingThresholds: (value: Record) => void; @@ -100,27 +101,20 @@ export function ResourceTable(props: ResourceTableProps) { onClick={() => { const allDisabled = areAllAlertsDisabled(); const resourceIds = getAllResourceIds(); - if (props.title === 'Proxmox Nodes' && props.onToggleNodeConnectivity) { + if (props.title === 'Proxmox Nodes' && props.onBatchToggleNodeConnectivity) { // For nodes, toggle connectivity alerts // If all are disabled, enable all. If any are enabled, disable all. const targetState = !allDisabled; // true = disable alerts, false = enable alerts - resourceIds.forEach(id => { - const resource = props.resources?.find(r => r.id === id); - if (resource && resource.disableConnectivity !== targetState) { - props.onToggleNodeConnectivity!(id); - } - }); - } else if (props.onToggleDisabled) { + + // Use batch toggle to update all at once + props.onBatchToggleNodeConnectivity(resourceIds, targetState); + } else if (props.onBatchToggleDisabled) { // For guests and storage, toggle disabled flag // If all are disabled, enable all. If any are enabled, disable all. const targetState = !allDisabled; // true = disable alerts, false = enable alerts - resourceIds.forEach(id => { - const resources = props.groupedResources ? Object.values(props.groupedResources).flat() : (props.resources || []); - const resource = resources.find(r => r.id === id); - if (resource && resource.disabled !== targetState) { - props.onToggleDisabled!(id); - } - }); + + // Use batch toggle to update all at once + props.onBatchToggleDisabled(resourceIds, targetState); } }} class={`p-0.5 rounded transition-colors ${ diff --git a/frontend-modern/src/components/Alerts/ThresholdsTable.tsx b/frontend-modern/src/components/Alerts/ThresholdsTable.tsx index c5e5db2c3..fb019c9d2 100644 --- a/frontend-modern/src/components/Alerts/ThresholdsTable.tsx +++ b/frontend-modern/src/components/Alerts/ThresholdsTable.tsx @@ -425,6 +425,76 @@ export function ThresholdsTable(props: ThresholdsTableProps) { props.setHasUnsavedChanges(true); }; + const batchToggleDisabled = (resourceIds: string[], targetState: boolean) => { + const currentOverrides = [...props.overrides()]; + const newRawConfig = { ...props.rawOverridesConfig() }; + const allGuests = Object.values(guestsGroupedByNode()).flat(); + const allResources = [...allGuests, ...storageWithOverrides()]; + + resourceIds.forEach(resourceId => { + const resource = allResources.find(r => r.id === resourceId); + if (!resource || (resource.type !== 'guest' && resource.type !== 'storage')) return; + + const existingOverrideIndex = currentOverrides.findIndex(o => o.id === resourceId); + const existingOverride = existingOverrideIndex >= 0 ? currentOverrides[existingOverrideIndex] : undefined; + + // Clean the thresholds + const cleanThresholds: any = { ...(existingOverride?.thresholds || {}) }; + delete cleanThresholds.disabled; + + // If enabling (targetState = false) and no custom thresholds exist, remove the override + if (!targetState && Object.keys(cleanThresholds).length === 0) { + // Remove from overrides array + if (existingOverrideIndex >= 0) { + currentOverrides.splice(existingOverrideIndex, 1); + } + // Remove from raw config + delete newRawConfig[resourceId]; + } else { + // Create or update the override + const override: Override = { + id: resourceId, + name: resource.name, + type: resource.type, + resourceType: resource.resourceType, + vmid: 'vmid' in resource ? resource.vmid : undefined, + node: 'node' in resource ? resource.node : undefined, + instance: 'instance' in resource ? resource.instance : undefined, + disabled: targetState, + thresholds: cleanThresholds + }; + + if (existingOverrideIndex >= 0) { + currentOverrides[existingOverrideIndex] = override; + } else { + currentOverrides.push(override); + } + + // Update raw config + const hysteresisThresholds: Record = {}; + Object.entries(cleanThresholds).forEach(([metric, value]) => { + if (value !== undefined && value !== null) { + hysteresisThresholds[metric] = { + trigger: value, + clear: Math.max(0, (value as number) - 5) + }; + } + }); + + if (targetState) { + hysteresisThresholds.disabled = true; + } + + newRawConfig[resourceId] = hysteresisThresholds; + } + }); + + // Apply all changes at once + props.setOverrides(currentOverrides); + props.setRawOverridesConfig(newRawConfig); + props.setHasUnsavedChanges(true); + }; + const toggleDisabled = (resourceId: string, forceState?: boolean) => { // Flatten grouped guests to find the resource const allGuests = Object.values(guestsGroupedByNode()).flat(); @@ -500,15 +570,82 @@ export function ThresholdsTable(props: ThresholdsTableProps) { props.setHasUnsavedChanges(true); }; + const batchToggleNodeConnectivity = (nodeIds: string[], targetState: boolean) => { + const currentOverrides = [...props.overrides()]; + const newRawConfig = { ...props.rawOverridesConfig() }; + const nodes = nodesWithOverrides(); + + nodeIds.forEach(nodeId => { + const node = nodes.find(r => r.id === nodeId); + if (!node || node.type !== 'node') return; + + const existingOverrideIndex = currentOverrides.findIndex(o => o.id === nodeId); + const existingOverride = existingOverrideIndex >= 0 ? currentOverrides[existingOverrideIndex] : undefined; + + // Clean the thresholds + const cleanThresholds: any = { ...(existingOverride?.thresholds || {}) }; + delete cleanThresholds.disabled; + delete cleanThresholds.disableConnectivity; + + // If enabling connectivity alerts (targetState = false) and no custom thresholds exist, remove the override + if (!targetState && Object.keys(cleanThresholds).length === 0) { + // Remove from overrides array + if (existingOverrideIndex >= 0) { + currentOverrides.splice(existingOverrideIndex, 1); + } + // Remove from raw config + delete newRawConfig[nodeId]; + } else { + // Create or update the override + const override: Override = { + id: nodeId, + name: node.name, + type: node.type, + resourceType: node.resourceType, + disableConnectivity: targetState, + thresholds: cleanThresholds + }; + + if (existingOverrideIndex >= 0) { + currentOverrides[existingOverrideIndex] = override; + } else { + currentOverrides.push(override); + } + + // Update raw config + const hysteresisThresholds: Record = {}; + Object.entries(cleanThresholds).forEach(([metric, value]) => { + if (value !== undefined && value !== null) { + hysteresisThresholds[metric] = { + trigger: value, + clear: Math.max(0, (value as number) - 5) + }; + } + }); + + if (targetState) { + hysteresisThresholds.disableConnectivity = true; + } + + newRawConfig[nodeId] = hysteresisThresholds; + } + }); + + // Apply all changes at once + props.setOverrides(currentOverrides); + props.setRawOverridesConfig(newRawConfig); + props.setHasUnsavedChanges(true); + }; + const toggleNodeConnectivity = (nodeId: string, forceState?: boolean) => { const node = nodesWithOverrides().find(r => r.id === nodeId); if (!node || node.type !== 'node') return; - // Get existing override if it exists + // Get existing override if it exists const existingOverride = props.overrides().find(o => o.id === nodeId); - // Determine the current state - const currentDisableConnectivity = existingOverride?.disableConnectivity || false; + // Determine the current state - use the node's computed state, not just the override + const currentDisableConnectivity = node.disableConnectivity; const newDisableConnectivity = forceState !== undefined ? forceState : !currentDisableConnectivity; // Clean the thresholds to exclude any unwanted fields @@ -894,6 +1031,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) { onCancelEdit={cancelEdit} onRemoveOverride={removeOverride} onToggleNodeConnectivity={toggleNodeConnectivity} + onBatchToggleNodeConnectivity={batchToggleNodeConnectivity} editingId={editingId} editingThresholds={editingThresholds} setEditingThresholds={setEditingThresholds} @@ -933,6 +1071,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) { onCancelEdit={cancelEdit} onRemoveOverride={removeOverride} onToggleDisabled={toggleDisabled} + onBatchToggleDisabled={batchToggleDisabled} editingId={editingId} editingThresholds={editingThresholds} setEditingThresholds={setEditingThresholds} @@ -953,6 +1092,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) { onCancelEdit={cancelEdit} onRemoveOverride={removeOverride} onToggleDisabled={toggleDisabled} + onBatchToggleDisabled={batchToggleDisabled} editingId={editingId} editingThresholds={editingThresholds} setEditingThresholds={setEditingThresholds}