mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-10 00:14:38 +00:00
feat(connections): surface pve/pbs scope toggles in node modal
Proxmox VE and PBS nodes already carry Monitor* booleans on their config structs, but the node modal only surfaced the advanced PVE toggles (SMART, temperature) and none of the PBS surfaces — so save paths hardcoded every VE/PBS collector to true. The PMG side has had a real Data-collection UI for a while; bring PVE and PBS to parity so users can opt out of surfaces without going through the retired "Stop this surface" dialog. - nodeModalPresentation: add monitorVMs/Containers/Storage/Backups and monitorDatastores/SyncJobs/VerifyJobs/PruneJobs/GarbageJobs fields to the form data shape; default all true. - useNodeModalState: populate the new fields from editingNode (matching the existing pmg pattern) so edit-mode reflects stored scope. - buildNodeModalMonitoringPayload: dispatch form values through instead of hardcoded true, so the save body honors the toggles. - NodeModalMonitoringSection: render Data-collection sections for pve and pbs mirroring the pmg section, with short per-surface copy. Tests: extend nodeModalPresentation.test to assert scope toggles round-trip through buildNodeModalMonitoringPayload for pve and pbs.
This commit is contained in:
parent
a7ba4206fe
commit
e769c3be7f
4 changed files with 266 additions and 9 deletions
|
|
@ -59,6 +59,81 @@ export const NodeModalMonitoringSection: Component<NodeModalMonitoringSectionPro
|
|||
</div>
|
||||
|
||||
<Show when={modalProps.nodeType === 'pve'}>
|
||||
<div class="space-y-3">
|
||||
<SectionHeader
|
||||
title="Data collection"
|
||||
size="sm"
|
||||
class="mb-1"
|
||||
titleClass="text-base-content"
|
||||
/>
|
||||
<p class="text-xs text-muted">
|
||||
Control which Proxmox VE resources Pulse ingests. Disable individual collectors to
|
||||
reduce API load or trim dashboard noise.
|
||||
</p>
|
||||
|
||||
<label class="flex items-start gap-2 text-sm text-base-content">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={state.formData().monitorVMs}
|
||||
onChange={(event) => state.updateField('monitorVMs', event.currentTarget.checked)}
|
||||
class={formCheckbox + ' mt-0.5'}
|
||||
/>
|
||||
<div>
|
||||
<div>Virtual machines</div>
|
||||
<p class="text-xs text-muted mt-1">
|
||||
QEMU guest state, resource usage, and per-VM metrics.
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start gap-2 text-sm text-base-content">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={state.formData().monitorContainers}
|
||||
onChange={(event) =>
|
||||
state.updateField('monitorContainers', event.currentTarget.checked)
|
||||
}
|
||||
class={formCheckbox + ' mt-0.5'}
|
||||
/>
|
||||
<div>
|
||||
<div>Containers</div>
|
||||
<p class="text-xs text-muted mt-1">
|
||||
LXC container state and resource usage.
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start gap-2 text-sm text-base-content">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={state.formData().monitorStorage}
|
||||
onChange={(event) => state.updateField('monitorStorage', event.currentTarget.checked)}
|
||||
class={formCheckbox + ' mt-0.5'}
|
||||
/>
|
||||
<div>
|
||||
<div>Storage</div>
|
||||
<p class="text-xs text-muted mt-1">
|
||||
Per-storage capacity, status, and content type breakdown.
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start gap-2 text-sm text-base-content">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={state.formData().monitorBackups}
|
||||
onChange={(event) => state.updateField('monitorBackups', event.currentTarget.checked)}
|
||||
class={formCheckbox + ' mt-0.5'}
|
||||
/>
|
||||
<div>
|
||||
<div>Backups</div>
|
||||
<p class="text-xs text-muted mt-1">
|
||||
vzdump schedules and recent backup activity.
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<SectionHeader
|
||||
title="Advanced monitoring"
|
||||
|
|
@ -150,6 +225,106 @@ export const NodeModalMonitoringSection: Component<NodeModalMonitoringSectionPro
|
|||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={modalProps.nodeType === 'pbs'}>
|
||||
<div class="space-y-3">
|
||||
<SectionHeader
|
||||
title="Data collection"
|
||||
size="sm"
|
||||
class="mb-1"
|
||||
titleClass="text-base-content"
|
||||
/>
|
||||
<p class="text-xs text-muted">
|
||||
Control which Proxmox Backup Server surfaces Pulse ingests. Disable collectors you
|
||||
don't need.
|
||||
</p>
|
||||
|
||||
<label class="flex items-start gap-2 text-sm text-base-content">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={state.formData().monitorDatastores}
|
||||
onChange={(event) =>
|
||||
state.updateField('monitorDatastores', event.currentTarget.checked)
|
||||
}
|
||||
class={formCheckbox + ' mt-0.5'}
|
||||
/>
|
||||
<div>
|
||||
<div>Datastores</div>
|
||||
<p class="text-xs text-muted mt-1">
|
||||
Datastore capacity, usage, and deduplication status.
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start gap-2 text-sm text-base-content">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={state.formData().monitorSyncJobs}
|
||||
onChange={(event) =>
|
||||
state.updateField('monitorSyncJobs', event.currentTarget.checked)
|
||||
}
|
||||
class={formCheckbox + ' mt-0.5'}
|
||||
/>
|
||||
<div>
|
||||
<div>Sync jobs</div>
|
||||
<p class="text-xs text-muted mt-1">
|
||||
Remote sync schedules and their last-run state.
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start gap-2 text-sm text-base-content">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={state.formData().monitorVerifyJobs}
|
||||
onChange={(event) =>
|
||||
state.updateField('monitorVerifyJobs', event.currentTarget.checked)
|
||||
}
|
||||
class={formCheckbox + ' mt-0.5'}
|
||||
/>
|
||||
<div>
|
||||
<div>Verify jobs</div>
|
||||
<p class="text-xs text-muted mt-1">
|
||||
Backup verification schedules and outcomes.
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start gap-2 text-sm text-base-content">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={state.formData().monitorPruneJobs}
|
||||
onChange={(event) =>
|
||||
state.updateField('monitorPruneJobs', event.currentTarget.checked)
|
||||
}
|
||||
class={formCheckbox + ' mt-0.5'}
|
||||
/>
|
||||
<div>
|
||||
<div>Prune jobs</div>
|
||||
<p class="text-xs text-muted mt-1">
|
||||
Retention policy runs and the snapshots they remove.
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start gap-2 text-sm text-base-content">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={state.formData().monitorGarbageJobs}
|
||||
onChange={(event) =>
|
||||
state.updateField('monitorGarbageJobs', event.currentTarget.checked)
|
||||
}
|
||||
class={formCheckbox + ' mt-0.5'}
|
||||
/>
|
||||
<div>
|
||||
<div>Garbage collection</div>
|
||||
<p class="text-xs text-muted mt-1">
|
||||
GC runs that reclaim orphaned chunks from the datastore.
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={modalProps.nodeType === 'pmg'}>
|
||||
<div class="space-y-3">
|
||||
<SectionHeader
|
||||
|
|
|
|||
|
|
@ -276,6 +276,27 @@ export const useNodeModalState = (props: NodeModalProps) => {
|
|||
username = parts[0];
|
||||
}
|
||||
|
||||
const pveConfig =
|
||||
node.type === 'pve'
|
||||
? (node as NodeConfig & {
|
||||
monitorVMs?: boolean;
|
||||
monitorContainers?: boolean;
|
||||
monitorStorage?: boolean;
|
||||
monitorBackups?: boolean;
|
||||
})
|
||||
: undefined;
|
||||
|
||||
const pbsConfig =
|
||||
node.type === 'pbs'
|
||||
? (node as NodeConfig & {
|
||||
monitorDatastores?: boolean;
|
||||
monitorSyncJobs?: boolean;
|
||||
monitorVerifyJobs?: boolean;
|
||||
monitorPruneJobs?: boolean;
|
||||
monitorGarbageJobs?: boolean;
|
||||
})
|
||||
: undefined;
|
||||
|
||||
const pmgConfig =
|
||||
node.type === 'pmg'
|
||||
? (node as NodeConfig & {
|
||||
|
|
@ -298,6 +319,10 @@ export const useNodeModalState = (props: NodeModalProps) => {
|
|||
tokenValue: '',
|
||||
fingerprint: ('fingerprint' in node ? node.fingerprint : '') || '',
|
||||
verifySSL: node.verifySSL ?? true,
|
||||
monitorVMs: pveConfig?.monitorVMs ?? true,
|
||||
monitorContainers: pveConfig?.monitorContainers ?? true,
|
||||
monitorStorage: pveConfig?.monitorStorage ?? true,
|
||||
monitorBackups: pveConfig?.monitorBackups ?? true,
|
||||
monitorPhysicalDisks:
|
||||
node.type === 'pve'
|
||||
? ((node as NodeConfig & { monitorPhysicalDisks?: boolean }).monitorPhysicalDisks ?? true)
|
||||
|
|
@ -307,6 +332,11 @@ export const useNodeModalState = (props: NodeModalProps) => {
|
|||
? ((node as NodeConfig & { physicalDiskPollingMinutes?: number })
|
||||
.physicalDiskPollingMinutes ?? 5)
|
||||
: 5,
|
||||
monitorDatastores: pbsConfig?.monitorDatastores ?? true,
|
||||
monitorSyncJobs: pbsConfig?.monitorSyncJobs ?? true,
|
||||
monitorVerifyJobs: pbsConfig?.monitorVerifyJobs ?? true,
|
||||
monitorPruneJobs: pbsConfig?.monitorPruneJobs ?? true,
|
||||
monitorGarbageJobs: pbsConfig?.monitorGarbageJobs ?? true,
|
||||
monitorMailStats: pmgConfig?.monitorMailStats ?? true,
|
||||
monitorQueues: pmgConfig?.monitorQueues ?? true,
|
||||
monitorQuarantine: pmgConfig?.monitorQuarantine ?? true,
|
||||
|
|
|
|||
|
|
@ -97,6 +97,40 @@ describe('nodeModalPresentation', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('propagates per-surface scope toggles through the pve payload', () => {
|
||||
const form = {
|
||||
...getNodeModalDefaultFormData('pve'),
|
||||
monitorVMs: false,
|
||||
monitorContainers: true,
|
||||
monitorStorage: false,
|
||||
monitorBackups: true,
|
||||
};
|
||||
expect(buildNodeModalMonitoringPayload('pve', form)).toMatchObject({
|
||||
monitorVMs: false,
|
||||
monitorContainers: true,
|
||||
monitorStorage: false,
|
||||
monitorBackups: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('propagates per-surface scope toggles through the pbs payload', () => {
|
||||
const form = {
|
||||
...getNodeModalDefaultFormData('pbs'),
|
||||
monitorDatastores: true,
|
||||
monitorSyncJobs: false,
|
||||
monitorVerifyJobs: true,
|
||||
monitorPruneJobs: false,
|
||||
monitorGarbageJobs: false,
|
||||
};
|
||||
expect(buildNodeModalMonitoringPayload('pbs', form)).toMatchObject({
|
||||
monitorDatastores: true,
|
||||
monitorSyncJobs: false,
|
||||
monitorVerifyJobs: true,
|
||||
monitorPruneJobs: false,
|
||||
monitorGarbageJobs: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns canonical node test result presentation', () => {
|
||||
expect(getNodeModalTestResultPresentation('success')).toMatchObject({
|
||||
icon: 'success',
|
||||
|
|
|
|||
|
|
@ -18,8 +18,17 @@ export interface NodeModalFormData {
|
|||
tokenValue: string;
|
||||
fingerprint: string;
|
||||
verifySSL: boolean;
|
||||
monitorVMs: boolean;
|
||||
monitorContainers: boolean;
|
||||
monitorStorage: boolean;
|
||||
monitorBackups: boolean;
|
||||
monitorPhysicalDisks: boolean;
|
||||
physicalDiskPollingMinutes: number;
|
||||
monitorDatastores: boolean;
|
||||
monitorSyncJobs: boolean;
|
||||
monitorVerifyJobs: boolean;
|
||||
monitorPruneJobs: boolean;
|
||||
monitorGarbageJobs: boolean;
|
||||
monitorMailStats: boolean;
|
||||
monitorQueues: boolean;
|
||||
monitorQuarantine: boolean;
|
||||
|
|
@ -47,8 +56,17 @@ export function getNodeModalDefaultFormData(
|
|||
tokenValue: '',
|
||||
fingerprint: '',
|
||||
verifySSL: true,
|
||||
monitorVMs: true,
|
||||
monitorContainers: true,
|
||||
monitorStorage: true,
|
||||
monitorBackups: true,
|
||||
monitorPhysicalDisks: false,
|
||||
physicalDiskPollingMinutes: 5,
|
||||
monitorDatastores: true,
|
||||
monitorSyncJobs: true,
|
||||
monitorVerifyJobs: true,
|
||||
monitorPruneJobs: true,
|
||||
monitorGarbageJobs: true,
|
||||
monitorMailStats: true,
|
||||
monitorQueues: true,
|
||||
monitorQuarantine: true,
|
||||
|
|
@ -150,20 +168,20 @@ export function buildNodeModalMonitoringPayload(
|
|||
switch (nodeType) {
|
||||
case 'pve':
|
||||
return {
|
||||
monitorVMs: true,
|
||||
monitorContainers: true,
|
||||
monitorStorage: true,
|
||||
monitorBackups: true,
|
||||
monitorVMs: formData.monitorVMs,
|
||||
monitorContainers: formData.monitorContainers,
|
||||
monitorStorage: formData.monitorStorage,
|
||||
monitorBackups: formData.monitorBackups,
|
||||
monitorPhysicalDisks: formData.monitorPhysicalDisks,
|
||||
physicalDiskPollingMinutes: formData.physicalDiskPollingMinutes,
|
||||
};
|
||||
case 'pbs':
|
||||
return {
|
||||
monitorDatastores: true,
|
||||
monitorSyncJobs: true,
|
||||
monitorVerifyJobs: true,
|
||||
monitorPruneJobs: true,
|
||||
monitorGarbageJobs: true,
|
||||
monitorDatastores: formData.monitorDatastores,
|
||||
monitorSyncJobs: formData.monitorSyncJobs,
|
||||
monitorVerifyJobs: formData.monitorVerifyJobs,
|
||||
monitorPruneJobs: formData.monitorPruneJobs,
|
||||
monitorGarbageJobs: formData.monitorGarbageJobs,
|
||||
};
|
||||
case 'pmg':
|
||||
return {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue