mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-22 11:13:26 +00:00
fix: implement batch toggle operations to prevent state sync issues
addresses #349 - fixed the issue where toggling all Proxmox Node alerts would skip some nodes on subsequent clicks. The problem was that multiple toggle operations in a loop were reading from the same state snapshot. - implemented batchToggleNodeConnectivity and batchToggleDisabled functions - these functions collect all changes and apply them atomically - ensures all resources are properly toggled to the target state - fixes the issue where individual nodes (like 'pi') weren't toggling correctly
This commit is contained in:
@@ -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<string, any>;
|
||||
setEditingThresholds: (value: Record<string, any>) => 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 ${
|
||||
|
||||
@@ -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<string, any> = {};
|
||||
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<string, any> = {};
|
||||
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}
|
||||
|
||||
Reference in New Issue
Block a user