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:
Pulse Monitor
2025-09-10 20:20:38 +00:00
parent b2429dbd8f
commit 6cf3c053a4
3 changed files with 99 additions and 44 deletions
@@ -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({});
};
+57 -37
View File
@@ -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;
});