feat: add batch toggle for alerts in Thresholds tab

addresses #349 - adds a toggle button in the Alerts column header that allows users to enable/disable all alerts for a resource type with one click. This is especially helpful when managing many VMs, containers, or storage devices.

- added toggle icon in Alerts column header for VMs & Containers, Storage, and Nodes tables
- icon shows current state (eye for enabled, eye-slash for disabled)
- clicking toggles all resources in that table between enabled/disabled
- for nodes, toggles connectivity alerts instead of general disable flag
This commit is contained in:
Pulse Monitor 2025-08-25 14:29:35 +00:00
parent 7592c95021
commit 3ead28bc4f
2 changed files with 71 additions and 5 deletions

View file

@ -13,6 +13,7 @@ interface ResourceTableProps {
onRemoveOverride: (resourceId: string) => void;
onToggleDisabled?: (resourceId: string) => void;
onToggleNodeConnectivity?: (nodeId: string) => void;
onBatchToggleAlerts?: (resourceIds: string[], enable: boolean) => void;
editingId: () => string | null;
editingThresholds: () => Record<string, any>;
setEditingThresholds: (value: Record<string, any>) => void;
@ -21,6 +22,28 @@ interface ResourceTableProps {
}
export function ResourceTable(props: ResourceTableProps) {
// Get all resource IDs for batch operations
const getAllResourceIds = () => {
if (props.groupedResources) {
return Object.values(props.groupedResources).flat().map(r => r.id);
}
return props.resources?.map(r => r.id) || [];
};
// Check if all alerts are disabled
const areAllAlertsDisabled = () => {
const resources = props.groupedResources ? Object.values(props.groupedResources).flat() : (props.resources || []);
if (resources.length === 0) return false;
// For nodes, check disableConnectivity instead of disabled
if (props.title === 'Proxmox Nodes') {
return resources.every(r => r.disableConnectivity === true);
}
// For guests and storage, check disabled flag
return resources.every(r => r.disabled === true);
};
const MetricValueWithHeat = (metricProps: {
resourceId: string;
metric: string;
@ -70,7 +93,50 @@ export function ResourceTable(props: ResourceTableProps) {
)}
</For>
<th class="px-3 py-2 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Alerts
<div class="flex items-center justify-center gap-1">
<span>Alerts</span>
<Show when={(props.title === 'VMs & Containers' || props.title === 'Storage Devices' || props.title === 'Proxmox Nodes') && getAllResourceIds().length > 0}>
<button type="button"
onClick={() => {
const allDisabled = areAllAlertsDisabled();
const resourceIds = getAllResourceIds();
if (props.title === 'Proxmox Nodes' && props.onToggleNodeConnectivity) {
// For nodes, toggle connectivity alerts
resourceIds.forEach(id => {
const resource = props.resources?.find(r => r.id === id);
if (resource && (allDisabled || !resource.disableConnectivity)) {
props.onToggleNodeConnectivity!(id);
}
});
} else if (props.onToggleDisabled) {
// For guests and storage, toggle disabled flag
resourceIds.forEach(id => {
const resources = props.groupedResources ? Object.values(props.groupedResources).flat() : (props.resources || []);
const resource = resources.find(r => r.id === id);
if (resource && (allDisabled || !resource.disabled)) {
props.onToggleDisabled!(id);
}
});
}
}}
class={`p-0.5 rounded transition-colors ${
areAllAlertsDisabled()
? 'text-red-600 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900/50'
: 'text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-900/50'
}`}
title={areAllAlertsDisabled() ? 'Enable all alerts' : 'Disable all alerts'}
>
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<Show when={areAllAlertsDisabled()} fallback={
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
}>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</Show>
</svg>
</button>
</Show>
</div>
</th>
<th class="px-3 py-2 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Actions

View file

@ -425,7 +425,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
props.setHasUnsavedChanges(true);
};
const toggleDisabled = (resourceId: string) => {
const toggleDisabled = (resourceId: string, forceState?: boolean) => {
// Flatten grouped guests to find the resource
const allGuests = Object.values(guestsGroupedByNode()).flat();
const allResources = [...allGuests, ...storageWithOverrides()];
@ -437,7 +437,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
// Determine the current disabled state - check the resource's current state, not the override
const currentDisabledState = resource.disabled;
const newDisabledState = !currentDisabledState;
const newDisabledState = forceState !== undefined ? forceState : !currentDisabledState;
// Clean the thresholds to exclude 'disabled' if it got in there
const cleanThresholds: any = { ...(existingOverride?.thresholds || {}) };
@ -500,7 +500,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
props.setHasUnsavedChanges(true);
};
const toggleNodeConnectivity = (nodeId: string) => {
const toggleNodeConnectivity = (nodeId: string, forceState?: boolean) => {
const node = nodesWithOverrides().find(r => r.id === nodeId);
if (!node || node.type !== 'node') return;
@ -509,7 +509,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
// Determine the current state
const currentDisableConnectivity = existingOverride?.disableConnectivity || false;
const newDisableConnectivity = !currentDisableConnectivity;
const newDisableConnectivity = forceState !== undefined ? forceState : !currentDisableConnectivity;
// Clean the thresholds to exclude any unwanted fields
const cleanThresholds: any = { ...(existingOverride?.thresholds || {}) };