mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-24 12:13:28 +00:00
fix: resolve PBS threshold save issues and input focus loss (addresses #440)
- Added 'pbs' type to Override interfaces in both Alerts.tsx and ThresholdsTable.tsx - Fixed createEffect in Alerts.tsx to properly handle PBS server overrides - Prevented memos from recomputing during editing to avoid input focus loss - PBS threshold values now persist correctly when saved
This commit is contained in:
@@ -123,6 +123,9 @@ export function ResourceTable(props: ResourceTableProps) {
|
||||
const val = thresh?.[metric] || defaults[metric];
|
||||
return typeof val === 'string' ? (parseFloat(val) || 0) : (val || 0);
|
||||
}
|
||||
if (resource.type === 'pbs') {
|
||||
console.log(`PBS Display - Resource: ${resource.id}, Metric: ${metric}, Thresholds:`, resource.thresholds, 'Defaults:', defaults);
|
||||
}
|
||||
return resource.thresholds?.[metric] || defaults[metric] || 0;
|
||||
};
|
||||
const isOverridden = (metric: string) => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ResourceTable } from './ResourceTable';
|
||||
interface Override {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'guest' | 'node' | 'storage';
|
||||
type: 'guest' | 'node' | 'storage' | 'pbs';
|
||||
resourceType?: string;
|
||||
vmid?: number;
|
||||
node?: string;
|
||||
@@ -130,7 +130,12 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
};
|
||||
|
||||
// Process nodes with their overrides
|
||||
const nodesWithOverrides = createMemo(() => {
|
||||
const nodesWithOverrides = createMemo((prev) => {
|
||||
// If we're currently editing, return the previous value to avoid re-renders
|
||||
if (editingId()) {
|
||||
return prev || [];
|
||||
}
|
||||
|
||||
const search = searchTerm().toLowerCase();
|
||||
const overridesMap = new Map(props.overrides().map(o => [o.id, o]));
|
||||
|
||||
@@ -166,7 +171,12 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
});
|
||||
|
||||
// Process guests with their overrides and group by node
|
||||
const guestsGroupedByNode = createMemo(() => {
|
||||
const guestsGroupedByNode = createMemo((prev) => {
|
||||
// If we're currently editing, return the previous value to avoid re-renders
|
||||
if (editingId()) {
|
||||
return prev || {};
|
||||
}
|
||||
|
||||
const search = searchTerm().toLowerCase();
|
||||
const overridesMap = new Map(props.overrides().map(o => [o.id, o]));
|
||||
|
||||
@@ -231,9 +241,15 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
});
|
||||
|
||||
// Process PBS servers with their overrides
|
||||
const pbsServersWithOverrides = createMemo(() => {
|
||||
const pbsServersWithOverrides = createMemo((prev) => {
|
||||
// If we're currently editing, return the previous value to avoid re-renders
|
||||
if (editingId()) {
|
||||
return prev || [];
|
||||
}
|
||||
|
||||
const search = searchTerm().toLowerCase();
|
||||
const overridesMap = new Map(props.overrides().map(o => [o.id, o]));
|
||||
console.log('PBS memo recomputing, overrides:', Array.from(overridesMap.entries()));
|
||||
|
||||
// Get PBS instances from props
|
||||
const pbsInstances = props.pbsInstances || [];
|
||||
@@ -285,7 +301,12 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
});
|
||||
|
||||
// Process storage with their overrides
|
||||
const storageWithOverrides = createMemo(() => {
|
||||
const storageWithOverrides = createMemo((prev) => {
|
||||
// If we're currently editing, return the previous value to avoid re-renders
|
||||
if (editingId()) {
|
||||
return prev || [];
|
||||
}
|
||||
|
||||
const search = searchTerm().toLowerCase();
|
||||
const overridesMap = new Map(props.overrides().map(o => [o.id, o]));
|
||||
|
||||
@@ -336,23 +357,33 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
const allGuests = Object.values(guestsGroupedByNode()).flat();
|
||||
const allResources = [...nodesWithOverrides(), ...allGuests, ...storageWithOverrides(), ...pbsServersWithOverrides()];
|
||||
const resource = allResources.find(r => r.id === resourceId);
|
||||
if (!resource) return;
|
||||
if (!resource) {
|
||||
console.log('Resource not found for id:', resourceId);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('SAVING PBS THRESHOLD FOR:', resourceId, 'Type:', resource.type);
|
||||
const editedThresholds = editingThresholds();
|
||||
const defaultThresholds = resource.defaults;
|
||||
console.log('Edited thresholds:', editedThresholds);
|
||||
console.log('Default thresholds:', defaultThresholds);
|
||||
|
||||
// Only include values that differ from defaults
|
||||
const overrideThresholds: Record<string, number> = {};
|
||||
Object.keys(editedThresholds).forEach(key => {
|
||||
const editedValue = editedThresholds[key];
|
||||
const defaultValue = defaultThresholds[key as keyof typeof defaultThresholds];
|
||||
console.log(`Comparing ${key}: edited=${editedValue}, default=${defaultValue}`);
|
||||
if (editedValue !== defaultValue && editedValue !== undefined && editedValue !== '') {
|
||||
overrideThresholds[key] = editedValue;
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Override thresholds to save:', overrideThresholds);
|
||||
|
||||
// If no overrides, just cancel the edit
|
||||
if (Object.keys(overrideThresholds).length === 0) {
|
||||
console.log('No overrides to save, canceling edit');
|
||||
// If there was an existing override, remove it
|
||||
if (resource.hasOverride) {
|
||||
const newOverrides = props.overrides().filter(o => o.id !== resourceId);
|
||||
@@ -372,7 +403,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
const override: Override = {
|
||||
id: resourceId,
|
||||
name: resource.name,
|
||||
type: resource.type as 'guest' | 'node' | 'storage',
|
||||
type: resource.type as 'guest' | 'node' | 'storage' | 'pbs',
|
||||
resourceType: resource.resourceType,
|
||||
vmid: 'vmid' in resource ? resource.vmid : undefined,
|
||||
node: 'node' in resource ? resource.node : undefined,
|
||||
@@ -409,6 +440,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
props.setRawOverridesConfig(newRawConfig);
|
||||
|
||||
props.setHasUnsavedChanges(true);
|
||||
console.log('Save complete, clearing edit state');
|
||||
setEditingId(null);
|
||||
setEditingThresholds({});
|
||||
};
|
||||
|
||||
@@ -45,10 +45,10 @@ interface ScheduleConfig {
|
||||
|
||||
// Override interface for both guests and nodes
|
||||
interface Override {
|
||||
id: string; // Full ID (e.g. "Main-node1-105" for guest, "node-node1" for node)
|
||||
id: string; // Full ID (e.g. "Main-node1-105" for guest, "node-node1" for node, "pbs-name" for PBS)
|
||||
name: string; // Display name
|
||||
type: 'guest' | 'node' | 'storage';
|
||||
resourceType?: string; // VM, CT, Node, or Storage
|
||||
type: 'guest' | 'node' | 'storage' | 'pbs';
|
||||
resourceType?: string; // VM, CT, Node, Storage, or PBS
|
||||
vmid?: number; // Only for guests
|
||||
node?: string; // Node name (for guests and storage), undefined for nodes themselves
|
||||
instance?: string;
|
||||
@@ -179,54 +179,74 @@ export function Alerts() {
|
||||
|
||||
// Process raw overrides config when state changes
|
||||
createEffect(() => {
|
||||
// Skip this effect if there are unsaved changes to prevent losing focus
|
||||
if (hasUnsavedChanges()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rawConfig = rawOverridesConfig();
|
||||
if (Object.keys(rawConfig).length > 0 && state.nodes && state.vms && state.containers && state.storage) {
|
||||
// Convert overrides object to array format
|
||||
const overridesList: Override[] = [];
|
||||
|
||||
Object.entries(rawConfig).forEach(([key, thresholds]) => {
|
||||
// Check if it's a node override by looking for matching node
|
||||
const node = (state.nodes || []).find((n) => n.id === key);
|
||||
if (node) {
|
||||
overridesList.push({
|
||||
id: key,
|
||||
name: node.name,
|
||||
type: 'node',
|
||||
resourceType: 'Node',
|
||||
disableConnectivity: thresholds.disableConnectivity || false,
|
||||
thresholds: extractTriggerValues(thresholds)
|
||||
});
|
||||
} else {
|
||||
// Check if it's a storage device
|
||||
const storage = (state.storage || []).find((s) => s.id === key);
|
||||
if (storage) {
|
||||
// Check if it's a PBS server override (starts with "pbs-")
|
||||
if (key.startsWith('pbs-')) {
|
||||
const pbs = (state.pbs || []).find((p) => p.id === key);
|
||||
if (pbs) {
|
||||
overridesList.push({
|
||||
id: key,
|
||||
name: storage.name,
|
||||
type: 'storage',
|
||||
resourceType: 'Storage',
|
||||
node: storage.node,
|
||||
instance: storage.instance,
|
||||
disabled: thresholds.disabled || false,
|
||||
name: pbs.name,
|
||||
type: 'pbs',
|
||||
resourceType: 'PBS',
|
||||
disableConnectivity: thresholds.disableConnectivity || false,
|
||||
thresholds: extractTriggerValues(thresholds)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Check if it's a node override by looking for matching node
|
||||
const node = (state.nodes || []).find((n) => n.id === key);
|
||||
if (node) {
|
||||
overridesList.push({
|
||||
id: key,
|
||||
name: node.name,
|
||||
type: 'node',
|
||||
resourceType: 'Node',
|
||||
disableConnectivity: thresholds.disableConnectivity || false,
|
||||
thresholds: extractTriggerValues(thresholds)
|
||||
});
|
||||
} else {
|
||||
// Find the guest by matching the full ID
|
||||
const vm = (state.vms || []).find((g) => g.id === key);
|
||||
const container = (state.containers || []).find((g) => g.id === key);
|
||||
const guest = vm || container;
|
||||
if (guest) {
|
||||
// Check if it's a storage device
|
||||
const storage = (state.storage || []).find((s) => s.id === key);
|
||||
if (storage) {
|
||||
overridesList.push({
|
||||
id: key,
|
||||
name: guest.name,
|
||||
type: 'guest',
|
||||
resourceType: guest.type === 'qemu' ? 'VM' : 'CT',
|
||||
vmid: guest.vmid,
|
||||
node: guest.node,
|
||||
instance: guest.instance,
|
||||
name: storage.name,
|
||||
type: 'storage',
|
||||
resourceType: 'Storage',
|
||||
node: storage.node,
|
||||
instance: storage.instance,
|
||||
disabled: thresholds.disabled || false,
|
||||
thresholds: extractTriggerValues(thresholds)
|
||||
});
|
||||
} else {
|
||||
// Find the guest by matching the full ID
|
||||
const vm = (state.vms || []).find((g) => g.id === key);
|
||||
const container = (state.containers || []).find((g) => g.id === key);
|
||||
const guest = vm || container;
|
||||
if (guest) {
|
||||
overridesList.push({
|
||||
id: key,
|
||||
name: guest.name,
|
||||
type: 'guest',
|
||||
resourceType: guest.type === 'qemu' ? 'VM' : 'CT',
|
||||
vmid: guest.vmid,
|
||||
node: guest.node,
|
||||
instance: guest.instance,
|
||||
disabled: thresholds.disabled || false,
|
||||
thresholds: extractTriggerValues(thresholds)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,9 +258,9 @@ export function Alerts() {
|
||||
overridesList.some((newOverride) => {
|
||||
const existing = currentOverrides.find(o => o.id === newOverride.id);
|
||||
if (!existing) return true;
|
||||
// Check both thresholds and disableConnectivity for nodes
|
||||
// Check both thresholds and disableConnectivity for nodes/PBS
|
||||
const thresholdsChanged = JSON.stringify(newOverride.thresholds) !== JSON.stringify(existing.thresholds);
|
||||
const connectivityChanged = newOverride.type === 'node' && newOverride.disableConnectivity !== existing.disableConnectivity;
|
||||
const connectivityChanged = (newOverride.type === 'node' || newOverride.type === 'pbs') && newOverride.disableConnectivity !== existing.disableConnectivity;
|
||||
const disabledChanged = (newOverride.type === 'guest' || newOverride.type === 'storage') && newOverride.disabled !== existing.disabled;
|
||||
return thresholdsChanged || connectivityChanged || disabledChanged;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user