mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-08 05:41:31 +00:00
Extract thresholds recovery defaults owner
This commit is contained in:
@@ -132,6 +132,8 @@ so governed agent, guest, and storage rows do not leak raw names when the
|
||||
threshold editor saves or re-renders them.
|
||||
That threshold editor data shaping now lives under
|
||||
`frontend-modern/src/features/alerts/thresholds/hooks/useThresholdsData.ts`,
|
||||
backup and snapshot default sanitization plus factory-drift policy now live in
|
||||
`frontend-modern/src/features/alerts/thresholds/hooks/useThresholdsRecoveryDefaultsState.ts`,
|
||||
while `frontend-modern/src/features/alerts/thresholds/hooks/useThresholdsTableState.ts`
|
||||
owns threshold-table route sync, section collapse state, search/edit shell
|
||||
state, and bulk-edit dialog control. Override persistence, bulk threshold
|
||||
|
||||
@@ -236,9 +236,12 @@ inline.
|
||||
The thresholds editor now follows that same split more tightly:
|
||||
`frontend-modern/src/features/alerts/thresholds/hooks/useThresholdsTableState.ts`
|
||||
must stay the table-shell owner for route sync and local UI state, while
|
||||
`frontend-modern/src/features/alerts/thresholds/hooks/useThresholdsRecoveryDefaultsState.ts`
|
||||
owns backup/snapshot default sanitization and factory-drift policy, and
|
||||
`frontend-modern/src/features/alerts/thresholds/hooks/useThresholdsOverrideMutations.ts`
|
||||
owns override save/bulk/toggle persistence and alert-removal side effects. The
|
||||
table-shell hook should not re-accumulate raw override mutation logic inline.
|
||||
table-shell hook should not re-accumulate raw override mutation logic or
|
||||
recovery-threshold defaults policy inline.
|
||||
|
||||
The updates settings surface now follows the same presentation-owner rule.
|
||||
`frontend-modern/src/components/Settings/UpdatesSettingsPanel.tsx` stays the
|
||||
@@ -430,6 +433,7 @@ is now explicitly a feature consumer rather than the data or controller owner.
|
||||
Canonical threshold row shaping, override-ID compatibility, grouped resource
|
||||
normalization, and thresholds-table controller state live in
|
||||
`frontend-modern/src/features/alerts/thresholds/hooks/useThresholdsData.ts`,
|
||||
`frontend-modern/src/features/alerts/thresholds/hooks/useThresholdsRecoveryDefaultsState.ts`,
|
||||
`frontend-modern/src/features/alerts/thresholds/hooks/useThresholdsTableState.ts`,
|
||||
so future cleanup should extend those feature hooks instead of rebuilding
|
||||
resource normalization or thresholds-table runtime state inside the table
|
||||
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import { cleanup, render } from '@solidjs/testing-library';
|
||||
|
||||
import type { ThresholdsTableProps } from '@/features/alerts/thresholds/types';
|
||||
import { useThresholdsRecoveryDefaultsState } from '../useThresholdsRecoveryDefaultsState';
|
||||
|
||||
const buildProps = (): ThresholdsTableProps =>
|
||||
({
|
||||
backupDefaults: () => ({
|
||||
alertOrphaned: false,
|
||||
criticalDays: 8,
|
||||
enabled: true,
|
||||
freshHours: 24,
|
||||
ignoreVMIDs: ['100', '200'],
|
||||
staleHours: 72,
|
||||
warningDays: 4,
|
||||
}),
|
||||
backupFactoryDefaults: {
|
||||
alertOrphaned: true,
|
||||
criticalDays: 7,
|
||||
enabled: false,
|
||||
freshHours: 12,
|
||||
ignoreVMIDs: ['100'],
|
||||
staleHours: 48,
|
||||
warningDays: 3,
|
||||
},
|
||||
snapshotDefaults: () => ({
|
||||
criticalDays: 10,
|
||||
criticalSizeGiB: 18,
|
||||
enabled: true,
|
||||
warningDays: 6,
|
||||
warningSizeGiB: 12,
|
||||
}),
|
||||
snapshotFactoryDefaults: {
|
||||
criticalDays: 7,
|
||||
criticalSizeGiB: 16,
|
||||
enabled: false,
|
||||
warningDays: 3,
|
||||
warningSizeGiB: 8,
|
||||
},
|
||||
}) as ThresholdsTableProps;
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
describe('useThresholdsRecoveryDefaultsState', () => {
|
||||
it('owns backup and snapshot default-policy sanitization', () => {
|
||||
let captured: ReturnType<typeof useThresholdsRecoveryDefaultsState> | undefined;
|
||||
|
||||
const Harness = () => {
|
||||
captured = useThresholdsRecoveryDefaultsState(buildProps());
|
||||
return null;
|
||||
};
|
||||
|
||||
render(() => <Harness />);
|
||||
|
||||
expect(captured).toBeDefined();
|
||||
expect(
|
||||
captured!.sanitizeSnapshotConfig({
|
||||
criticalDays: 4,
|
||||
criticalSizeGiB: 1.08,
|
||||
enabled: true,
|
||||
warningDays: 9,
|
||||
warningSizeGiB: 3.16,
|
||||
}),
|
||||
).toEqual({
|
||||
criticalDays: 4,
|
||||
criticalSizeGiB: 1.1,
|
||||
enabled: true,
|
||||
warningDays: 4,
|
||||
warningSizeGiB: 1.1,
|
||||
});
|
||||
|
||||
expect(
|
||||
captured!.sanitizeBackupConfig({
|
||||
alertOrphaned: false,
|
||||
criticalDays: 2,
|
||||
enabled: true,
|
||||
freshHours: 9,
|
||||
ignoreVMIDs: ['101', ' ', '101', '202'],
|
||||
staleHours: 4,
|
||||
warningDays: 7,
|
||||
}),
|
||||
).toEqual({
|
||||
alertOrphaned: false,
|
||||
criticalDays: 2,
|
||||
enabled: true,
|
||||
freshHours: 9,
|
||||
ignoreVMIDs: ['101', '202'],
|
||||
staleHours: 9,
|
||||
warningDays: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('tracks factory drift for backup and snapshot defaults', () => {
|
||||
let captured: ReturnType<typeof useThresholdsRecoveryDefaultsState> | undefined;
|
||||
|
||||
const Harness = () => {
|
||||
captured = useThresholdsRecoveryDefaultsState(buildProps());
|
||||
return null;
|
||||
};
|
||||
|
||||
render(() => <Harness />);
|
||||
|
||||
expect(captured).toBeDefined();
|
||||
expect(captured!.snapshotDefaultsRecord()).toEqual({
|
||||
'critical days': 10,
|
||||
'critical size (gib)': 18,
|
||||
'warning days': 6,
|
||||
'warning size (gib)': 12,
|
||||
});
|
||||
expect(captured!.backupFactoryDefaultsRecord()).toEqual({
|
||||
'critical days': 7,
|
||||
'fresh hours': 12,
|
||||
'stale hours': 48,
|
||||
'warning days': 3,
|
||||
});
|
||||
expect(captured!.snapshotOverridesCount()).toBe(1);
|
||||
expect(captured!.backupOverridesCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
+12
-7
@@ -30,10 +30,6 @@ vi.mock('../useThresholdsData', () => ({
|
||||
agentDisksGroupedByAgent: () => ({}),
|
||||
agentDisksWithOverrides: () => [],
|
||||
agentsWithOverrides: () => [],
|
||||
backupDefaultsRecord: () => ({}),
|
||||
backupFactoryConfig: () => ({ enabled: false }),
|
||||
backupFactoryDefaultsRecord: () => ({}),
|
||||
backupOverridesCount: () => 0,
|
||||
dockerContainersFlat: () => [],
|
||||
dockerContainersGroupedByHost: () => ({}),
|
||||
dockerHostGroupMeta: () => ({}),
|
||||
@@ -45,15 +41,24 @@ vi.mock('../useThresholdsData', () => ({
|
||||
pbsServersWithOverrides: () => [],
|
||||
pmgGlobalDefaults: () => ({}),
|
||||
pmgServersWithOverrides: () => [],
|
||||
storageGroupedByNode: () => ({}),
|
||||
storageWithOverrides: () => [],
|
||||
totalDockerContainers: () => 0,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../useThresholdsRecoveryDefaultsState', () => ({
|
||||
useThresholdsRecoveryDefaultsState: () => ({
|
||||
backupDefaultsRecord: () => ({}),
|
||||
backupFactoryConfig: () => ({ enabled: false }),
|
||||
backupFactoryDefaultsRecord: () => ({}),
|
||||
backupOverridesCount: () => 0,
|
||||
sanitizeBackupConfig: <T,>(value: T) => value,
|
||||
sanitizeSnapshotConfig: <T,>(value: T) => value,
|
||||
snapshotDefaultsRecord: () => ({}),
|
||||
snapshotFactoryConfig: () => ({ enabled: false }),
|
||||
snapshotFactoryDefaultsRecord: () => ({}),
|
||||
snapshotOverridesCount: () => 0,
|
||||
storageGroupedByNode: () => ({}),
|
||||
storageWithOverrides: () => [],
|
||||
totalDockerContainers: () => 0,
|
||||
}),
|
||||
}));
|
||||
|
||||
|
||||
@@ -14,16 +14,8 @@ import {
|
||||
PMG_THRESHOLD_COLUMNS,
|
||||
PMG_KEY_TO_NORMALIZED,
|
||||
PMG_NORMALIZED_TO_KEY,
|
||||
DEFAULT_SNAPSHOT_WARNING,
|
||||
DEFAULT_SNAPSHOT_CRITICAL,
|
||||
DEFAULT_SNAPSHOT_WARNING_SIZE,
|
||||
DEFAULT_SNAPSHOT_CRITICAL_SIZE,
|
||||
DEFAULT_BACKUP_WARNING,
|
||||
DEFAULT_BACKUP_CRITICAL,
|
||||
DEFAULT_BACKUP_FRESH_HOURS,
|
||||
DEFAULT_BACKUP_STALE_HOURS,
|
||||
} from '../constants';
|
||||
import type { PMGThresholdDefaults, SnapshotAlertConfig, BackupAlertConfig } from '@/types/alerts';
|
||||
import type { PMGThresholdDefaults } from '@/types/alerts';
|
||||
import type { ThresholdsTableProps, Override } from '../types';
|
||||
import type { GroupHeaderMeta, Resource as TableResource } from '../tableTypes';
|
||||
|
||||
@@ -773,179 +765,6 @@ export function useThresholdsData(
|
||||
return meta;
|
||||
});
|
||||
|
||||
const snapshotFactoryConfig = () =>
|
||||
props.snapshotFactoryDefaults ?? {
|
||||
enabled: false,
|
||||
warningDays: DEFAULT_SNAPSHOT_WARNING,
|
||||
criticalDays: DEFAULT_SNAPSHOT_CRITICAL,
|
||||
warningSizeGiB: DEFAULT_SNAPSHOT_WARNING_SIZE,
|
||||
criticalSizeGiB: DEFAULT_SNAPSHOT_CRITICAL_SIZE,
|
||||
};
|
||||
|
||||
const sanitizeSnapshotConfig = (config: SnapshotAlertConfig): SnapshotAlertConfig => {
|
||||
let warning = Math.max(0, Math.round(config.warningDays ?? 0));
|
||||
let critical = Math.max(0, Math.round(config.criticalDays ?? 0));
|
||||
|
||||
if (critical > 0 && warning > critical) {
|
||||
warning = critical;
|
||||
}
|
||||
if (critical === 0 && warning > 0) {
|
||||
critical = warning;
|
||||
}
|
||||
|
||||
const rawWarningSize = Number.isFinite(config.warningSizeGiB)
|
||||
? Number(config.warningSizeGiB)
|
||||
: DEFAULT_SNAPSHOT_WARNING_SIZE;
|
||||
const rawCriticalSize = Number.isFinite(config.criticalSizeGiB)
|
||||
? Number(config.criticalSizeGiB)
|
||||
: DEFAULT_SNAPSHOT_CRITICAL_SIZE;
|
||||
|
||||
const roundSize = (value: number) => Math.round(Math.max(0, value) * 10) / 10;
|
||||
|
||||
let warningSize = roundSize(rawWarningSize);
|
||||
let criticalSize = roundSize(rawCriticalSize);
|
||||
|
||||
if (criticalSize > 0 && warningSize > criticalSize) {
|
||||
warningSize = criticalSize;
|
||||
}
|
||||
if (criticalSize === 0 && warningSize > 0) {
|
||||
criticalSize = warningSize;
|
||||
}
|
||||
|
||||
return {
|
||||
enabled: !!config.enabled,
|
||||
warningDays: warning,
|
||||
criticalDays: critical,
|
||||
warningSizeGiB: warningSize,
|
||||
criticalSizeGiB: criticalSize,
|
||||
};
|
||||
};
|
||||
|
||||
const backupFactoryConfig = () =>
|
||||
props.backupFactoryDefaults ?? {
|
||||
enabled: false,
|
||||
warningDays: DEFAULT_BACKUP_WARNING,
|
||||
criticalDays: DEFAULT_BACKUP_CRITICAL,
|
||||
freshHours: DEFAULT_BACKUP_FRESH_HOURS,
|
||||
staleHours: DEFAULT_BACKUP_STALE_HOURS,
|
||||
alertOrphaned: true,
|
||||
ignoreVMIDs: [],
|
||||
};
|
||||
|
||||
const sanitizeBackupConfig = (config: BackupAlertConfig): BackupAlertConfig => {
|
||||
let warning = Math.max(0, Math.round(config.warningDays ?? 0));
|
||||
let critical = Math.max(0, Math.round(config.criticalDays ?? 0));
|
||||
let fresh = Math.max(0, Math.round(config.freshHours ?? DEFAULT_BACKUP_FRESH_HOURS));
|
||||
let stale = Math.max(0, Math.round(config.staleHours ?? DEFAULT_BACKUP_STALE_HOURS));
|
||||
const alertOrphaned = config.alertOrphaned ?? true;
|
||||
const ignoreVMIDs = Array.from(
|
||||
new Set(
|
||||
(config.ignoreVMIDs ?? []).map((value) => value.trim()).filter((value) => value.length > 0),
|
||||
),
|
||||
);
|
||||
|
||||
if (critical > 0 && warning > critical) {
|
||||
warning = critical;
|
||||
}
|
||||
if (critical === 0 && warning > 0) {
|
||||
critical = warning;
|
||||
}
|
||||
|
||||
// Ensure stale is at least fresh
|
||||
if (stale < fresh) {
|
||||
stale = fresh;
|
||||
}
|
||||
|
||||
return {
|
||||
enabled: !!config.enabled,
|
||||
warningDays: warning,
|
||||
criticalDays: critical,
|
||||
freshHours: fresh,
|
||||
staleHours: stale,
|
||||
alertOrphaned,
|
||||
ignoreVMIDs,
|
||||
};
|
||||
};
|
||||
|
||||
const snapshotDefaultsRecord = createMemo(() => {
|
||||
const current = props.snapshotDefaults();
|
||||
return {
|
||||
'warning days': current.warningDays ?? 0,
|
||||
'critical days': current.criticalDays ?? 0,
|
||||
'warning size (gib)': current.warningSizeGiB ?? 0,
|
||||
'critical size (gib)': current.criticalSizeGiB ?? 0,
|
||||
};
|
||||
});
|
||||
|
||||
const snapshotFactoryDefaultsRecord = createMemo(() => {
|
||||
const factory = snapshotFactoryConfig();
|
||||
return {
|
||||
'warning days': factory.warningDays ?? DEFAULT_SNAPSHOT_WARNING,
|
||||
'critical days': factory.criticalDays ?? DEFAULT_SNAPSHOT_CRITICAL,
|
||||
'warning size (gib)': factory.warningSizeGiB ?? DEFAULT_SNAPSHOT_WARNING_SIZE,
|
||||
'critical size (gib)': factory.criticalSizeGiB ?? DEFAULT_SNAPSHOT_CRITICAL_SIZE,
|
||||
};
|
||||
});
|
||||
|
||||
const backupDefaultsRecord = createMemo(() => {
|
||||
const current = props.backupDefaults();
|
||||
return {
|
||||
'fresh hours': current.freshHours ?? DEFAULT_BACKUP_FRESH_HOURS,
|
||||
'stale hours': current.staleHours ?? DEFAULT_BACKUP_STALE_HOURS,
|
||||
'warning days': current.warningDays ?? 0,
|
||||
'critical days': current.criticalDays ?? 0,
|
||||
};
|
||||
});
|
||||
|
||||
const backupFactoryDefaultsRecord = createMemo(() => {
|
||||
const factory = backupFactoryConfig();
|
||||
return {
|
||||
'fresh hours': factory.freshHours ?? DEFAULT_BACKUP_FRESH_HOURS,
|
||||
'stale hours': factory.staleHours ?? DEFAULT_BACKUP_STALE_HOURS,
|
||||
'warning days': factory.warningDays ?? DEFAULT_BACKUP_WARNING,
|
||||
'critical days': factory.criticalDays ?? DEFAULT_BACKUP_CRITICAL,
|
||||
};
|
||||
});
|
||||
|
||||
const snapshotOverridesCount = createMemo(() => {
|
||||
const current = props.snapshotDefaults();
|
||||
const factory = snapshotFactoryConfig();
|
||||
const differs =
|
||||
current.enabled !== factory.enabled ||
|
||||
(current.warningDays ?? DEFAULT_SNAPSHOT_WARNING) !==
|
||||
(factory.warningDays ?? DEFAULT_SNAPSHOT_WARNING) ||
|
||||
(current.criticalDays ?? DEFAULT_SNAPSHOT_CRITICAL) !==
|
||||
(factory.criticalDays ?? DEFAULT_SNAPSHOT_CRITICAL) ||
|
||||
(current.warningSizeGiB ?? DEFAULT_SNAPSHOT_WARNING_SIZE) !==
|
||||
(factory.warningSizeGiB ?? DEFAULT_SNAPSHOT_WARNING_SIZE) ||
|
||||
(current.criticalSizeGiB ?? DEFAULT_SNAPSHOT_CRITICAL_SIZE) !==
|
||||
(factory.criticalSizeGiB ?? DEFAULT_SNAPSHOT_CRITICAL_SIZE);
|
||||
return differs ? 1 : 0;
|
||||
});
|
||||
|
||||
const backupOverridesCount = createMemo(() => {
|
||||
const backupCurrent = props.backupDefaults();
|
||||
const backupFactory = backupFactoryConfig();
|
||||
const currentIgnore = backupCurrent.ignoreVMIDs ?? [];
|
||||
const factoryIgnore = backupFactory.ignoreVMIDs ?? [];
|
||||
const ignoreDiff =
|
||||
currentIgnore.length !== factoryIgnore.length ||
|
||||
currentIgnore.some((value, index) => value !== factoryIgnore[index]);
|
||||
return backupCurrent.enabled !== backupFactory.enabled ||
|
||||
(backupCurrent.warningDays ?? DEFAULT_BACKUP_WARNING) !==
|
||||
(backupFactory.warningDays ?? DEFAULT_BACKUP_WARNING) ||
|
||||
(backupCurrent.criticalDays ?? DEFAULT_BACKUP_CRITICAL) !==
|
||||
(backupFactory.criticalDays ?? DEFAULT_BACKUP_CRITICAL) ||
|
||||
(backupCurrent.freshHours ?? DEFAULT_BACKUP_FRESH_HOURS) !==
|
||||
(backupFactory.freshHours ?? DEFAULT_BACKUP_FRESH_HOURS) ||
|
||||
(backupCurrent.staleHours ?? DEFAULT_BACKUP_STALE_HOURS) !==
|
||||
(backupFactory.staleHours ?? DEFAULT_BACKUP_STALE_HOURS) ||
|
||||
(backupCurrent.alertOrphaned ?? true) !== (backupFactory.alertOrphaned ?? true) ||
|
||||
ignoreDiff
|
||||
? 1
|
||||
: 0;
|
||||
});
|
||||
|
||||
// Process guests with their overrides and group by node
|
||||
const guestsGroupedByNode = createMemo<Record<string, TableResource[]>>((prev = {}) => {
|
||||
// If we're currently editing, return the previous value to avoid re-renders
|
||||
@@ -1294,16 +1113,6 @@ export function useThresholdsData(
|
||||
dockerContainersFlat,
|
||||
totalDockerContainers,
|
||||
dockerHostGroupMeta,
|
||||
snapshotFactoryConfig,
|
||||
sanitizeSnapshotConfig,
|
||||
backupFactoryConfig,
|
||||
sanitizeBackupConfig,
|
||||
snapshotDefaultsRecord,
|
||||
snapshotFactoryDefaultsRecord,
|
||||
backupDefaultsRecord,
|
||||
backupFactoryDefaultsRecord,
|
||||
snapshotOverridesCount,
|
||||
backupOverridesCount,
|
||||
guestsGroupedByNode,
|
||||
guestsFlat,
|
||||
guestGroupHeaderMeta,
|
||||
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
import { createMemo } from 'solid-js';
|
||||
|
||||
import type { BackupAlertConfig, SnapshotAlertConfig } from '@/types/alerts';
|
||||
import type { ThresholdsTableProps } from '@/features/alerts/thresholds/types';
|
||||
import {
|
||||
DEFAULT_BACKUP_CRITICAL,
|
||||
DEFAULT_BACKUP_FRESH_HOURS,
|
||||
DEFAULT_BACKUP_STALE_HOURS,
|
||||
DEFAULT_BACKUP_WARNING,
|
||||
DEFAULT_SNAPSHOT_CRITICAL,
|
||||
DEFAULT_SNAPSHOT_CRITICAL_SIZE,
|
||||
DEFAULT_SNAPSHOT_WARNING,
|
||||
DEFAULT_SNAPSHOT_WARNING_SIZE,
|
||||
} from '../constants';
|
||||
|
||||
export function useThresholdsRecoveryDefaultsState(props: ThresholdsTableProps) {
|
||||
const snapshotFactoryConfig = () =>
|
||||
props.snapshotFactoryDefaults ?? {
|
||||
enabled: false,
|
||||
warningDays: DEFAULT_SNAPSHOT_WARNING,
|
||||
criticalDays: DEFAULT_SNAPSHOT_CRITICAL,
|
||||
warningSizeGiB: DEFAULT_SNAPSHOT_WARNING_SIZE,
|
||||
criticalSizeGiB: DEFAULT_SNAPSHOT_CRITICAL_SIZE,
|
||||
};
|
||||
|
||||
const sanitizeSnapshotConfig = (config: SnapshotAlertConfig): SnapshotAlertConfig => {
|
||||
let warning = Math.max(0, Math.round(config.warningDays ?? 0));
|
||||
let critical = Math.max(0, Math.round(config.criticalDays ?? 0));
|
||||
|
||||
if (critical > 0 && warning > critical) {
|
||||
warning = critical;
|
||||
}
|
||||
if (critical === 0 && warning > 0) {
|
||||
critical = warning;
|
||||
}
|
||||
|
||||
const rawWarningSize = Number.isFinite(config.warningSizeGiB)
|
||||
? Number(config.warningSizeGiB)
|
||||
: DEFAULT_SNAPSHOT_WARNING_SIZE;
|
||||
const rawCriticalSize = Number.isFinite(config.criticalSizeGiB)
|
||||
? Number(config.criticalSizeGiB)
|
||||
: DEFAULT_SNAPSHOT_CRITICAL_SIZE;
|
||||
|
||||
const roundSize = (value: number) => Math.round(Math.max(0, value) * 10) / 10;
|
||||
|
||||
let warningSize = roundSize(rawWarningSize);
|
||||
let criticalSize = roundSize(rawCriticalSize);
|
||||
|
||||
if (criticalSize > 0 && warningSize > criticalSize) {
|
||||
warningSize = criticalSize;
|
||||
}
|
||||
if (criticalSize === 0 && warningSize > 0) {
|
||||
criticalSize = warningSize;
|
||||
}
|
||||
|
||||
return {
|
||||
enabled: !!config.enabled,
|
||||
warningDays: warning,
|
||||
criticalDays: critical,
|
||||
warningSizeGiB: warningSize,
|
||||
criticalSizeGiB: criticalSize,
|
||||
};
|
||||
};
|
||||
|
||||
const backupFactoryConfig = () =>
|
||||
props.backupFactoryDefaults ?? {
|
||||
enabled: false,
|
||||
warningDays: DEFAULT_BACKUP_WARNING,
|
||||
criticalDays: DEFAULT_BACKUP_CRITICAL,
|
||||
freshHours: DEFAULT_BACKUP_FRESH_HOURS,
|
||||
staleHours: DEFAULT_BACKUP_STALE_HOURS,
|
||||
alertOrphaned: true,
|
||||
ignoreVMIDs: [],
|
||||
};
|
||||
|
||||
const sanitizeBackupConfig = (config: BackupAlertConfig): BackupAlertConfig => {
|
||||
let warning = Math.max(0, Math.round(config.warningDays ?? 0));
|
||||
let critical = Math.max(0, Math.round(config.criticalDays ?? 0));
|
||||
let fresh = Math.max(0, Math.round(config.freshHours ?? DEFAULT_BACKUP_FRESH_HOURS));
|
||||
let stale = Math.max(0, Math.round(config.staleHours ?? DEFAULT_BACKUP_STALE_HOURS));
|
||||
const alertOrphaned = config.alertOrphaned ?? true;
|
||||
const ignoreVMIDs = Array.from(
|
||||
new Set(
|
||||
(config.ignoreVMIDs ?? []).map((value) => value.trim()).filter((value) => value.length > 0),
|
||||
),
|
||||
);
|
||||
|
||||
if (critical > 0 && warning > critical) {
|
||||
warning = critical;
|
||||
}
|
||||
if (critical === 0 && warning > 0) {
|
||||
critical = warning;
|
||||
}
|
||||
if (stale < fresh) {
|
||||
stale = fresh;
|
||||
}
|
||||
|
||||
return {
|
||||
enabled: !!config.enabled,
|
||||
warningDays: warning,
|
||||
criticalDays: critical,
|
||||
freshHours: fresh,
|
||||
staleHours: stale,
|
||||
alertOrphaned,
|
||||
ignoreVMIDs,
|
||||
};
|
||||
};
|
||||
|
||||
const snapshotDefaultsRecord = createMemo(() => {
|
||||
const current = props.snapshotDefaults();
|
||||
return {
|
||||
'warning days': current.warningDays ?? 0,
|
||||
'critical days': current.criticalDays ?? 0,
|
||||
'warning size (gib)': current.warningSizeGiB ?? 0,
|
||||
'critical size (gib)': current.criticalSizeGiB ?? 0,
|
||||
};
|
||||
});
|
||||
|
||||
const snapshotFactoryDefaultsRecord = createMemo(() => {
|
||||
const factory = snapshotFactoryConfig();
|
||||
return {
|
||||
'warning days': factory.warningDays ?? DEFAULT_SNAPSHOT_WARNING,
|
||||
'critical days': factory.criticalDays ?? DEFAULT_SNAPSHOT_CRITICAL,
|
||||
'warning size (gib)': factory.warningSizeGiB ?? DEFAULT_SNAPSHOT_WARNING_SIZE,
|
||||
'critical size (gib)': factory.criticalSizeGiB ?? DEFAULT_SNAPSHOT_CRITICAL_SIZE,
|
||||
};
|
||||
});
|
||||
|
||||
const backupDefaultsRecord = createMemo(() => {
|
||||
const current = props.backupDefaults();
|
||||
return {
|
||||
'fresh hours': current.freshHours ?? DEFAULT_BACKUP_FRESH_HOURS,
|
||||
'stale hours': current.staleHours ?? DEFAULT_BACKUP_STALE_HOURS,
|
||||
'warning days': current.warningDays ?? 0,
|
||||
'critical days': current.criticalDays ?? 0,
|
||||
};
|
||||
});
|
||||
|
||||
const backupFactoryDefaultsRecord = createMemo(() => {
|
||||
const factory = backupFactoryConfig();
|
||||
return {
|
||||
'fresh hours': factory.freshHours ?? DEFAULT_BACKUP_FRESH_HOURS,
|
||||
'stale hours': factory.staleHours ?? DEFAULT_BACKUP_STALE_HOURS,
|
||||
'warning days': factory.warningDays ?? DEFAULT_BACKUP_WARNING,
|
||||
'critical days': factory.criticalDays ?? DEFAULT_BACKUP_CRITICAL,
|
||||
};
|
||||
});
|
||||
|
||||
const snapshotOverridesCount = createMemo(() => {
|
||||
const current = props.snapshotDefaults();
|
||||
const factory = snapshotFactoryConfig();
|
||||
const differs =
|
||||
current.enabled !== factory.enabled ||
|
||||
(current.warningDays ?? DEFAULT_SNAPSHOT_WARNING) !==
|
||||
(factory.warningDays ?? DEFAULT_SNAPSHOT_WARNING) ||
|
||||
(current.criticalDays ?? DEFAULT_SNAPSHOT_CRITICAL) !==
|
||||
(factory.criticalDays ?? DEFAULT_SNAPSHOT_CRITICAL) ||
|
||||
(current.warningSizeGiB ?? DEFAULT_SNAPSHOT_WARNING_SIZE) !==
|
||||
(factory.warningSizeGiB ?? DEFAULT_SNAPSHOT_WARNING_SIZE) ||
|
||||
(current.criticalSizeGiB ?? DEFAULT_SNAPSHOT_CRITICAL_SIZE) !==
|
||||
(factory.criticalSizeGiB ?? DEFAULT_SNAPSHOT_CRITICAL_SIZE);
|
||||
return differs ? 1 : 0;
|
||||
});
|
||||
|
||||
const backupOverridesCount = createMemo(() => {
|
||||
const current = props.backupDefaults();
|
||||
const factory = backupFactoryConfig();
|
||||
const currentIgnore = current.ignoreVMIDs ?? [];
|
||||
const factoryIgnore = factory.ignoreVMIDs ?? [];
|
||||
const ignoreDiff =
|
||||
currentIgnore.length !== factoryIgnore.length ||
|
||||
currentIgnore.some((value, index) => value !== factoryIgnore[index]);
|
||||
|
||||
return current.enabled !== factory.enabled ||
|
||||
(current.warningDays ?? DEFAULT_BACKUP_WARNING) !==
|
||||
(factory.warningDays ?? DEFAULT_BACKUP_WARNING) ||
|
||||
(current.criticalDays ?? DEFAULT_BACKUP_CRITICAL) !==
|
||||
(factory.criticalDays ?? DEFAULT_BACKUP_CRITICAL) ||
|
||||
(current.freshHours ?? DEFAULT_BACKUP_FRESH_HOURS) !==
|
||||
(factory.freshHours ?? DEFAULT_BACKUP_FRESH_HOURS) ||
|
||||
(current.staleHours ?? DEFAULT_BACKUP_STALE_HOURS) !==
|
||||
(factory.staleHours ?? DEFAULT_BACKUP_STALE_HOURS) ||
|
||||
(current.alertOrphaned ?? true) !== (factory.alertOrphaned ?? true) ||
|
||||
ignoreDiff
|
||||
? 1
|
||||
: 0;
|
||||
});
|
||||
|
||||
return {
|
||||
backupDefaultsRecord,
|
||||
backupFactoryConfig,
|
||||
backupFactoryDefaultsRecord,
|
||||
backupOverridesCount,
|
||||
sanitizeBackupConfig,
|
||||
sanitizeSnapshotConfig,
|
||||
snapshotDefaultsRecord,
|
||||
snapshotFactoryConfig,
|
||||
snapshotFactoryDefaultsRecord,
|
||||
snapshotOverridesCount,
|
||||
};
|
||||
}
|
||||
@@ -40,6 +40,7 @@ import type {
|
||||
} from '@/features/alerts/thresholds/tableTypes';
|
||||
import { useCollapsedSections } from '@/components/Alerts/Thresholds/hooks/useCollapsedSections';
|
||||
import { useThresholdsData } from './useThresholdsData';
|
||||
import { useThresholdsRecoveryDefaultsState } from './useThresholdsRecoveryDefaultsState';
|
||||
import { useThresholdsOverrideMutations } from './useThresholdsOverrideMutations';
|
||||
|
||||
const HELP_BANNER_KEY = 'pulse-thresholds-help-dismissed';
|
||||
@@ -189,16 +190,6 @@ export function useThresholdsTableState(props: ThresholdsTableProps) {
|
||||
dockerContainersFlat,
|
||||
totalDockerContainers,
|
||||
dockerHostGroupMeta,
|
||||
snapshotFactoryConfig,
|
||||
sanitizeSnapshotConfig,
|
||||
backupFactoryConfig,
|
||||
sanitizeBackupConfig,
|
||||
snapshotDefaultsRecord,
|
||||
snapshotFactoryDefaultsRecord,
|
||||
backupDefaultsRecord,
|
||||
backupFactoryDefaultsRecord,
|
||||
snapshotOverridesCount,
|
||||
backupOverridesCount,
|
||||
guestsGroupedByNode,
|
||||
guestsFlat,
|
||||
guestGroupHeaderMeta,
|
||||
@@ -209,6 +200,19 @@ export function useThresholdsTableState(props: ThresholdsTableProps) {
|
||||
storageGroupedByNode,
|
||||
} = useThresholdsData(props, editingId, searchTerm);
|
||||
|
||||
const {
|
||||
backupDefaultsRecord,
|
||||
backupFactoryConfig,
|
||||
backupFactoryDefaultsRecord,
|
||||
backupOverridesCount,
|
||||
sanitizeBackupConfig,
|
||||
sanitizeSnapshotConfig,
|
||||
snapshotDefaultsRecord,
|
||||
snapshotFactoryConfig,
|
||||
snapshotFactoryDefaultsRecord,
|
||||
snapshotOverridesCount,
|
||||
} = useThresholdsRecoveryDefaultsState(props);
|
||||
|
||||
const countOverrides = (resources: TableResource[] | undefined) =>
|
||||
resources?.filter(
|
||||
(resource) => resource.hasOverride || resource.disabled || resource.disableConnectivity,
|
||||
|
||||
@@ -17,6 +17,7 @@ import alertThresholdsTabSource from '@/features/alerts/tabs/ThresholdsTab.tsx?r
|
||||
import recentAlertsPanelSource from '@/components/Alerts/RecentAlertsPanel.tsx?raw';
|
||||
import thresholdsTableSource from '@/components/Alerts/ThresholdsTable.tsx?raw';
|
||||
import thresholdsDataHookSource from '@/features/alerts/thresholds/hooks/useThresholdsData.ts?raw';
|
||||
import thresholdsRecoveryDefaultsStateHookSource from '@/features/alerts/thresholds/hooks/useThresholdsRecoveryDefaultsState.ts?raw';
|
||||
import thresholdsTableStateHookSource from '@/features/alerts/thresholds/hooks/useThresholdsTableState.ts?raw';
|
||||
import thresholdsOverrideMutationsHookSource from '@/features/alerts/thresholds/hooks/useThresholdsOverrideMutations.ts?raw';
|
||||
|
||||
@@ -276,11 +277,19 @@ describe('tab path helpers', () => {
|
||||
expect(thresholdsDataHookSource).toContain('export function useThresholdsData');
|
||||
expect(thresholdsTableStateHookSource).toContain('export function useThresholdsTableState');
|
||||
expect(thresholdsTableStateHookSource).toContain('useThresholdsData(props, editingId, searchTerm)');
|
||||
expect(thresholdsTableStateHookSource).toContain('useThresholdsRecoveryDefaultsState(props)');
|
||||
expect(thresholdsTableStateHookSource).toContain('useThresholdsOverrideMutations');
|
||||
expect(thresholdsTableStateHookSource).not.toContain('const saveEdit = (resourceId: string) => {');
|
||||
expect(thresholdsTableStateHookSource).not.toContain(
|
||||
'const toggleNodeConnectivity = (resourceId: string, forceState?: boolean) => {',
|
||||
);
|
||||
expect(thresholdsDataHookSource).not.toContain('const sanitizeSnapshotConfig =');
|
||||
expect(thresholdsDataHookSource).not.toContain('const sanitizeBackupConfig =');
|
||||
expect(thresholdsRecoveryDefaultsStateHookSource).toContain(
|
||||
'export function useThresholdsRecoveryDefaultsState',
|
||||
);
|
||||
expect(thresholdsRecoveryDefaultsStateHookSource).toContain('const sanitizeSnapshotConfig =');
|
||||
expect(thresholdsRecoveryDefaultsStateHookSource).toContain('const sanitizeBackupConfig =');
|
||||
expect(thresholdsOverrideMutationsHookSource).toContain(
|
||||
'export function useThresholdsOverrideMutations',
|
||||
);
|
||||
|
||||
@@ -339,6 +339,7 @@ import alertScheduleTabSource from '@/features/alerts/tabs/ScheduleTab.tsx?raw';
|
||||
import alertThresholdsTabSource from '@/features/alerts/tabs/ThresholdsTab.tsx?raw';
|
||||
import thresholdsTableSource from '@/components/Alerts/ThresholdsTable.tsx?raw';
|
||||
import thresholdsDataHookSource from '@/features/alerts/thresholds/hooks/useThresholdsData.ts?raw';
|
||||
import thresholdsRecoveryDefaultsStateHookSource from '@/features/alerts/thresholds/hooks/useThresholdsRecoveryDefaultsState.ts?raw';
|
||||
import thresholdsTableStateHookSource from '@/features/alerts/thresholds/hooks/useThresholdsTableState.ts?raw';
|
||||
import thresholdsOverrideMutationsHookSource from '@/features/alerts/thresholds/hooks/useThresholdsOverrideMutations.ts?raw';
|
||||
import alertIncidentPresentationSource from '@/utils/alertIncidentPresentation.ts?raw';
|
||||
@@ -2888,11 +2889,19 @@ describe('frontend resource type boundaries', () => {
|
||||
expect(thresholdsTableStateHookSource).toContain('export function useThresholdsTableState');
|
||||
expect(thresholdsTableStateHookSource).toContain('useCollapsedSections()');
|
||||
expect(thresholdsTableStateHookSource).toContain('useThresholdsData(props, editingId, searchTerm)');
|
||||
expect(thresholdsTableStateHookSource).toContain('useThresholdsRecoveryDefaultsState(props)');
|
||||
expect(thresholdsTableStateHookSource).toContain('useThresholdsOverrideMutations');
|
||||
expect(thresholdsTableStateHookSource).not.toContain('const saveEdit = (resourceId: string) => {');
|
||||
expect(thresholdsTableStateHookSource).not.toContain(
|
||||
'const toggleNodeConnectivity = (resourceId: string, forceState?: boolean) => {',
|
||||
);
|
||||
expect(thresholdsDataHookSource).not.toContain('const sanitizeSnapshotConfig =');
|
||||
expect(thresholdsDataHookSource).not.toContain('const sanitizeBackupConfig =');
|
||||
expect(thresholdsRecoveryDefaultsStateHookSource).toContain(
|
||||
'export function useThresholdsRecoveryDefaultsState',
|
||||
);
|
||||
expect(thresholdsRecoveryDefaultsStateHookSource).toContain('const sanitizeSnapshotConfig =');
|
||||
expect(thresholdsRecoveryDefaultsStateHookSource).toContain('const sanitizeBackupConfig =');
|
||||
expect(thresholdsOverrideMutationsHookSource).toContain(
|
||||
'export function useThresholdsOverrideMutations',
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user