mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-15 00:22:28 +00:00
Expose per-resource alert delay controls (#1497)
This commit is contained in:
@@ -1401,6 +1401,15 @@ coverage, duplicate and delayed reports, wall-clock changes, suppression
|
||||
reset, migration/restart continuity, backup hard-cap behavior, preview
|
||||
immutability, and first-match lifecycle identity.
|
||||
|
||||
The thresholds resource rows expose that same policy owner directly through a
|
||||
per-resource alert-delay action. The action carries the canonical resource id
|
||||
and the first supported CPU, memory, or disk signal into the versioned
|
||||
intent-policy editor, expands it, and leaves persistence and inheritance with
|
||||
`/api/alerts/intent-policies`. Resources without a supported metric open on
|
||||
`state.offline`. Threshold rows must not create a parallel delay field in the
|
||||
legacy threshold override document or save intent changes through the
|
||||
threshold-config endpoint.
|
||||
|
||||
### Provider-observed storage and workload incident lifecycle
|
||||
|
||||
Provider observations use one stable alert identity composed from provider,
|
||||
|
||||
@@ -4758,6 +4758,14 @@ rendering remains in
|
||||
`frontend-modern/src/components/Alerts/AlertResourceTableRow.tsx`, and shared
|
||||
metric normalization remains in
|
||||
`frontend-modern/src/components/Alerts/alertResourceTableModel.ts`.
|
||||
Desktop rows and mobile resource cards also share one alert-delay handoff:
|
||||
their timer action emits the canonical resource id plus the first supported
|
||||
CPU, memory, or disk signal, while `ThresholdsTab.tsx` owns selection state and
|
||||
`AlertIntentPolicyPanel.tsx` owns expansion, scrolling, field inheritance, and
|
||||
API persistence. The action remains available for non-threshold-editable rows
|
||||
and falls back to `state.offline` when no supported metric exists. New
|
||||
platform threshold sections must forward this shared handoff rather than add a
|
||||
platform-local delay editor or a second intent-policy transport.
|
||||
`frontend-modern/src/features/alerts/useAlertDestinationsTabState.ts` now owns
|
||||
destination test actions, retry orchestration, and delivery-health loading while
|
||||
`frontend-modern/src/features/alerts/tabs/DestinationsTab.tsx` stays the
|
||||
|
||||
@@ -519,6 +519,7 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps)
|
||||
onSetOfflineState={props.table.onSetOfflineState}
|
||||
onToggleBackup={props.table.onToggleBackup}
|
||||
onToggleSnapshot={props.table.onToggleSnapshot}
|
||||
onConfigureResourceIntent={props.table.onConfigureResourceIntent}
|
||||
globalDisableFlag={props.table.globalDisableFlag}
|
||||
globalDisableOfflineFlag={props.table.globalDisableOfflineFlag}
|
||||
editingNote={props.table.editingNote}
|
||||
@@ -560,6 +561,7 @@ export function AlertResourceTableDesktop(props: AlertResourceTableDesktopProps)
|
||||
onSetOfflineState={props.table.onSetOfflineState}
|
||||
onToggleBackup={props.table.onToggleBackup}
|
||||
onToggleSnapshot={props.table.onToggleSnapshot}
|
||||
onConfigureResourceIntent={props.table.onConfigureResourceIntent}
|
||||
globalDisableFlag={props.table.globalDisableFlag}
|
||||
globalDisableOfflineFlag={props.table.globalDisableOfflineFlag}
|
||||
editingNote={props.table.editingNote}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { For, Show } from 'solid-js';
|
||||
import Check from 'lucide-solid/icons/check';
|
||||
import Pencil from 'lucide-solid/icons/pencil';
|
||||
import RotateCcw from 'lucide-solid/icons/rotate-ccw';
|
||||
import Timer from 'lucide-solid/icons/timer';
|
||||
import X from 'lucide-solid/icons/x';
|
||||
|
||||
import { ActionIconButton } from '@/components/shared/Button';
|
||||
@@ -296,6 +297,28 @@ export function AlertResourceTableMobile(props: AlertResourceTableMobileProps) {
|
||||
<Pencil class="w-4 h-4" aria-hidden="true" />
|
||||
</ActionIconButton>
|
||||
</Show>
|
||||
<Show when={!isEditing() && props.table.onConfigureResourceIntent}>
|
||||
<ActionIconButton
|
||||
onClick={() => {
|
||||
const preferredMetric = ['cpu', 'memory', 'disk'].find(
|
||||
(metric) =>
|
||||
props.table.columns.some(
|
||||
(column) => normalizeAlertResourceMetricKey(column) === metric,
|
||||
) && alertResourceSupportsMetric(resource.type, metric),
|
||||
);
|
||||
props.table.onConfigureResourceIntent?.(
|
||||
resource.id,
|
||||
preferredMetric ? `metric.${preferredMetric}` : 'state.offline',
|
||||
);
|
||||
}}
|
||||
label={`Configure alert delay for ${getAlertResourceLabel(resource)}`}
|
||||
title="Configure individual alert delay"
|
||||
tone="neutral"
|
||||
size="sm"
|
||||
>
|
||||
<Timer class="w-4 h-4" aria-hidden="true" />
|
||||
</ActionIconButton>
|
||||
</Show>
|
||||
<Show when={isEditing()}>
|
||||
<ActionIconButton
|
||||
onClick={cancelEditing}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { For, Show } from 'solid-js';
|
||||
import Pencil from 'lucide-solid/icons/pencil';
|
||||
import RotateCcw from 'lucide-solid/icons/rotate-ccw';
|
||||
import Timer from 'lucide-solid/icons/timer';
|
||||
import X from 'lucide-solid/icons/x';
|
||||
|
||||
import { ActionIconButton } from '@/components/shared/Button';
|
||||
@@ -38,6 +39,7 @@ import {
|
||||
type AlertResourceThresholdMap,
|
||||
} from './alertResourceTableModel';
|
||||
import type { Alert } from '@/types/api';
|
||||
import type { AlertIntentSignal } from '@/api/alertIntentPolicies';
|
||||
import type { Resource } from '@/features/alerts/thresholds/tableTypes';
|
||||
|
||||
type OfflineState = 'off' | 'warning' | 'critical';
|
||||
@@ -67,6 +69,7 @@ interface AlertResourceTableRowProps {
|
||||
onSetOfflineState?: (resourceId: string, state: OfflineState) => void;
|
||||
onToggleBackup?: (resourceId: string, forceState?: boolean) => void;
|
||||
onToggleSnapshot?: (resourceId: string, forceState?: boolean) => void;
|
||||
onConfigureResourceIntent?: (resourceId: string, signal: AlertIntentSignal) => void;
|
||||
globalDisableFlag?: () => boolean;
|
||||
globalDisableOfflineFlag?: () => boolean;
|
||||
editingNote: () => string;
|
||||
@@ -91,6 +94,17 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
isEditing(),
|
||||
);
|
||||
const isOverridden = (metric: string) => isAlertResourceMetricOverridden(props.resource, metric);
|
||||
const preferredIntentSignal = (): AlertIntentSignal => {
|
||||
for (const metric of ['cpu', 'memory', 'disk'] as const) {
|
||||
if (
|
||||
props.columns.some((column) => normalizeAlertResourceMetricKey(column) === metric) &&
|
||||
alertResourceSupportsMetric(props.resource.type, metric)
|
||||
) {
|
||||
return `metric.${metric}`;
|
||||
}
|
||||
}
|
||||
return 'state.offline';
|
||||
};
|
||||
|
||||
const getThresholds = (): AlertResourceThresholdMap => thresholds();
|
||||
|
||||
@@ -595,10 +609,11 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
props.resource.hasOverride ||
|
||||
((props.resource.type === 'agent' || props.resource.type === 'dockerHost') &&
|
||||
props.resource.disableConnectivity);
|
||||
const showIntent = typeof props.onConfigureResourceIntent === 'function';
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={showEdit || showRevert}
|
||||
when={showEdit || showRevert || showIntent}
|
||||
fallback={
|
||||
<span class="text-xs text-muted" aria-hidden="true">
|
||||
—
|
||||
@@ -616,6 +631,22 @@ export function AlertResourceTableRow(props: AlertResourceTableRowProps) {
|
||||
<Pencil class="w-4 h-4" aria-hidden="true" />
|
||||
</ActionIconButton>
|
||||
</Show>
|
||||
<Show when={showIntent}>
|
||||
<ActionIconButton
|
||||
onClick={() =>
|
||||
props.onConfigureResourceIntent?.(
|
||||
props.resource.id,
|
||||
preferredIntentSignal(),
|
||||
)
|
||||
}
|
||||
label={`Configure alert delay for ${resourceLabel()}`}
|
||||
title="Configure individual alert delay"
|
||||
tone="neutral"
|
||||
size="xs"
|
||||
>
|
||||
<Timer class="w-4 h-4" aria-hidden="true" />
|
||||
</ActionIconButton>
|
||||
</Show>
|
||||
<Show when={showRevert}>
|
||||
<ActionIconButton
|
||||
onClick={() => props.onRemoveOverride(props.resource.id)}
|
||||
|
||||
@@ -1472,6 +1472,34 @@ describe('ResourceTable', () => {
|
||||
});
|
||||
|
||||
describe('delay settings', () => {
|
||||
it('opens the canonical per-resource delay editor from the resource row', () => {
|
||||
const onConfigureResourceIntent = vi.fn();
|
||||
const props = makeProps({
|
||||
resources: [makeResource({ id: 'vm-1', name: 'Database VM' })],
|
||||
columns: ['CPU %', 'Memory %'],
|
||||
onConfigureResourceIntent,
|
||||
});
|
||||
render(() => <ResourceTable {...props} />);
|
||||
|
||||
fireEvent.click(screen.getByLabelText('Configure alert delay for Database VM'));
|
||||
|
||||
expect(onConfigureResourceIntent).toHaveBeenCalledWith('vm-1', 'metric.cpu');
|
||||
});
|
||||
|
||||
it('falls back to the offline signal when the row has no supported metric', () => {
|
||||
const onConfigureResourceIntent = vi.fn();
|
||||
const props = makeProps({
|
||||
resources: [makeResource({ id: 'docker-host-1', name: 'Docker Host', type: 'dockerHost' })],
|
||||
columns: [],
|
||||
onConfigureResourceIntent,
|
||||
});
|
||||
render(() => <ResourceTable {...props} />);
|
||||
|
||||
fireEvent.click(screen.getByLabelText('Configure alert delay for Docker Host'));
|
||||
|
||||
expect(onConfigureResourceIntent).toHaveBeenCalledWith('docker-host-1', 'state.offline');
|
||||
});
|
||||
|
||||
it('shows delay toggle button when showDelayColumn and onMetricDelayChange are provided', () => {
|
||||
const props = makeProps({
|
||||
showDelayColumn: true,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Show } from 'solid-js';
|
||||
import X from 'lucide-solid/icons/x';
|
||||
import { useBreakpoint } from '@/hooks/useBreakpoint';
|
||||
import type { Alert } from '@/types/api';
|
||||
import type { AlertIntentSignal } from '@/api/alertIntentPolicies';
|
||||
import {
|
||||
ALERT_BULK_EDIT_CLEAR_LABEL,
|
||||
getAlertBulkEditOpenLabel,
|
||||
@@ -16,6 +17,7 @@ export type { GroupHeaderMeta, Resource } from '@/features/alerts/thresholds/tab
|
||||
|
||||
export interface ResourceTableProps {
|
||||
title: string;
|
||||
onConfigureResourceIntent?: (resourceId: string, signal: AlertIntentSignal) => void;
|
||||
resources?: Resource[];
|
||||
groupedResources?: Record<string, Resource[]>;
|
||||
columns: string[];
|
||||
|
||||
@@ -24,6 +24,7 @@ export function ThresholdsTableAgentDisksSection(props: ThresholdsTableSectionPr
|
||||
<div ref={state.registerSection('agentDisks')} class="scroll-mt-24">
|
||||
<ResourceTable
|
||||
title=""
|
||||
onConfigureResourceIntent={tableProps.onConfigureResourceIntent}
|
||||
groupedResources={state.agentDisksGroupedByAgent()}
|
||||
groupHeaderMeta={state.agentGroupHeaderMeta()}
|
||||
columns={['Disk %']}
|
||||
|
||||
@@ -19,6 +19,7 @@ export function ThresholdsTableAgentsResourcesSection(props: ThresholdsTableSect
|
||||
<div ref={state.registerSection('agents')} class="scroll-mt-24">
|
||||
<ResourceTable
|
||||
title={state.sectionTitles.agents}
|
||||
onConfigureResourceIntent={tableProps.onConfigureResourceIntent}
|
||||
resources={state.agentsWithOverrides()}
|
||||
columns={['CPU %', 'Memory %', 'Disk %', 'Disk Temp °C']}
|
||||
activeAlerts={tableProps.activeAlerts}
|
||||
|
||||
@@ -12,6 +12,7 @@ export function ThresholdsTableDockerContainersSection(props: ThresholdsTableSec
|
||||
<div ref={state.registerSection('dockerContainers')} class="scroll-mt-24">
|
||||
<ResourceTable
|
||||
title={state.sectionTitles.dockerContainers}
|
||||
onConfigureResourceIntent={tableProps.onConfigureResourceIntent}
|
||||
groupedResources={state.dockerContainersGroupedByHost()}
|
||||
groupHeaderMeta={state.dockerHostGroupMeta()}
|
||||
columns={[
|
||||
|
||||
@@ -12,6 +12,7 @@ export function ThresholdsTableDockerHostsSection(props: ThresholdsTableSectionP
|
||||
<div ref={state.registerSection('dockerHosts')} class="scroll-mt-24">
|
||||
<ResourceTable
|
||||
title={state.sectionTitles.dockerHosts}
|
||||
onConfigureResourceIntent={tableProps.onConfigureResourceIntent}
|
||||
resources={state.dockerHostsWithOverrides()}
|
||||
columns={[]}
|
||||
activeAlerts={tableProps.activeAlerts}
|
||||
|
||||
@@ -50,6 +50,7 @@ function KubernetesResourceSection(
|
||||
<div ref={state.registerSection(props.id)} class="scroll-mt-24">
|
||||
<ResourceTable
|
||||
title=""
|
||||
onConfigureResourceIntent={tableProps.onConfigureResourceIntent}
|
||||
resources={props.resources()}
|
||||
columns={props.columns}
|
||||
activeAlerts={tableProps.activeAlerts}
|
||||
|
||||
@@ -24,6 +24,7 @@ export function ThresholdsTableProxmoxGuestsSection(props: ThresholdsTableSectio
|
||||
<div ref={state.registerSection('guests')} class="scroll-mt-24">
|
||||
<ResourceTable
|
||||
title=""
|
||||
onConfigureResourceIntent={tableProps.onConfigureResourceIntent}
|
||||
groupedResources={state.guestsGroupedByNode()}
|
||||
groupHeaderMeta={state.guestGroupHeaderMeta()}
|
||||
columns={[
|
||||
|
||||
@@ -24,6 +24,7 @@ export function ThresholdsTableProxmoxNodesSection(props: ThresholdsTableSection
|
||||
<div ref={state.registerSection('nodes')} class="scroll-mt-24">
|
||||
<ResourceTable
|
||||
title=""
|
||||
onConfigureResourceIntent={tableProps.onConfigureResourceIntent}
|
||||
resources={state.nodesWithOverrides()}
|
||||
columns={['CPU %', 'Memory %', 'Disk %', 'Temp °C']}
|
||||
activeAlerts={tableProps.activeAlerts}
|
||||
|
||||
@@ -24,6 +24,7 @@ export function ThresholdsTableProxmoxPBSSection(props: ThresholdsTableSectionPr
|
||||
<div id="threshold-section-pbs" ref={state.registerSection('pbs')} class="scroll-mt-24">
|
||||
<ResourceTable
|
||||
title=""
|
||||
onConfigureResourceIntent={tableProps.onConfigureResourceIntent}
|
||||
resources={state.pbsServersWithOverrides()}
|
||||
columns={['CPU %', 'Memory %']}
|
||||
activeAlerts={tableProps.activeAlerts}
|
||||
|
||||
@@ -24,6 +24,7 @@ export function ThresholdsTableProxmoxPMGSection(props: ThresholdsTableSectionPr
|
||||
<div id="threshold-section-pmg" ref={state.registerSection('pmg')} class="scroll-mt-24">
|
||||
<ResourceTable
|
||||
title=""
|
||||
onConfigureResourceIntent={tableProps.onConfigureResourceIntent}
|
||||
resources={state.pmgServersWithOverrides()}
|
||||
columns={[
|
||||
'Queue Warn',
|
||||
|
||||
@@ -24,6 +24,7 @@ export function ThresholdsTableProxmoxStorageSection(props: ThresholdsTableSecti
|
||||
<div ref={state.registerSection('storage')} class="scroll-mt-24">
|
||||
<ResourceTable
|
||||
title=""
|
||||
onConfigureResourceIntent={tableProps.onConfigureResourceIntent}
|
||||
groupedResources={state.storageGroupedByNode()}
|
||||
groupHeaderMeta={state.guestGroupHeaderMeta()}
|
||||
columns={['Usage %']}
|
||||
|
||||
@@ -57,6 +57,7 @@ function TrueNASResourceSection(
|
||||
<div ref={state.registerSection(props.id)} class="scroll-mt-24">
|
||||
<ResourceTable
|
||||
title=""
|
||||
onConfigureResourceIntent={tableProps.onConfigureResourceIntent}
|
||||
resources={props.resources()}
|
||||
columns={props.columns}
|
||||
activeAlerts={tableProps.activeAlerts}
|
||||
|
||||
@@ -64,6 +64,7 @@ function VMwareResourceSection(
|
||||
<div ref={state.registerSection(props.id)} class="scroll-mt-24">
|
||||
<ResourceTable
|
||||
title=""
|
||||
onConfigureResourceIntent={tableProps.onConfigureResourceIntent}
|
||||
resources={props.resources()}
|
||||
columns={props.columns}
|
||||
activeAlerts={tableProps.activeAlerts}
|
||||
|
||||
@@ -60,7 +60,16 @@ const ruleFor = (
|
||||
signal: AlertIntentSignal,
|
||||
): AlertIntentRule => document?.resources?.[resourceId]?.[signal] ?? {};
|
||||
|
||||
export function AlertIntentPolicyPanel(props: { resources: readonly Resource[] }) {
|
||||
export interface AlertIntentPolicySelectionTarget {
|
||||
resourceId: string;
|
||||
signal: AlertIntentSignal;
|
||||
requestId: number;
|
||||
}
|
||||
|
||||
export function AlertIntentPolicyPanel(props: {
|
||||
resources: readonly Resource[];
|
||||
selectionTarget?: AlertIntentPolicySelectionTarget | null;
|
||||
}) {
|
||||
const [expanded, setExpanded] = createSignal(false);
|
||||
const [document, setDocument] = createSignal<AlertIntentPolicyDocument | null>(null);
|
||||
const [loading, setLoading] = createSignal(false);
|
||||
@@ -98,6 +107,7 @@ export function AlertIntentPolicyPanel(props: { resources: readonly Resource[] }
|
||||
const [previewBackupActive, setPreviewBackupActive] = createSignal(false);
|
||||
const [previewing, setPreviewing] = createSignal(false);
|
||||
const [preview, setPreview] = createSignal<AlertIntentPolicyPreview | null>(null);
|
||||
let panelElement: HTMLDivElement | undefined;
|
||||
|
||||
const selectedResource = createMemo(() =>
|
||||
sortedResources().find((resource) => resource.id === resourceId()),
|
||||
@@ -144,6 +154,19 @@ export function AlertIntentPolicyPanel(props: { resources: readonly Resource[] }
|
||||
}
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
const target = props.selectionTarget;
|
||||
if (!target) return;
|
||||
const requestId = target.requestId;
|
||||
setResourceId(target.resourceId);
|
||||
setSignal(target.signal);
|
||||
setExpanded(true);
|
||||
queueMicrotask(() => {
|
||||
if (props.selectionTarget?.requestId !== requestId) return;
|
||||
panelElement?.scrollIntoView?.({ behavior: 'smooth', block: 'start' });
|
||||
});
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
const current = ruleFor(document(), resourceId(), signal());
|
||||
setOverrideGrace(current.graceSeconds === undefined ? '' : String(current.graceSeconds));
|
||||
@@ -340,7 +363,12 @@ export function AlertIntentPolicyPanel(props: { resources: readonly Resource[] }
|
||||
);
|
||||
|
||||
return (
|
||||
<Card padding="md" class="space-y-4">
|
||||
<Card
|
||||
id="alert-intent-policy-panel"
|
||||
ref={panelElement}
|
||||
padding="md"
|
||||
class="space-y-4 scroll-mt-24"
|
||||
>
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<h2 class="text-base font-semibold text-base-content">Alert intent & grace</h2>
|
||||
|
||||
@@ -79,6 +79,23 @@ describe('AlertIntentPolicyPanel', () => {
|
||||
expect(await screen.findByLabelText('Resource')).toHaveValue(resource.id);
|
||||
});
|
||||
|
||||
it('opens directly on a row-selected resource and signal', async () => {
|
||||
render(() => (
|
||||
<AlertIntentPolicyPanel
|
||||
resources={[resource]}
|
||||
selectionTarget={{
|
||||
resourceId: resource.id,
|
||||
signal: 'metric.memory',
|
||||
requestId: 1,
|
||||
}}
|
||||
/>
|
||||
));
|
||||
|
||||
expect(await screen.findByRole('button', { name: 'Hide policies' })).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('Resource')).toHaveValue(resource.id);
|
||||
expect(screen.getByLabelText('Signal')).toHaveValue('metric.memory');
|
||||
});
|
||||
|
||||
it('saves field-level resource overrides without disabling inherited policy', async () => {
|
||||
render(() => <AlertIntentPolicyPanel resources={[resource]} />);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { render, cleanup } from '@solidjs/testing-library';
|
||||
import { render, cleanup, waitFor } from '@solidjs/testing-library';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { ThresholdsTab } from '../tabs/ThresholdsTab';
|
||||
@@ -271,6 +271,7 @@ const buildProps = (): ThresholdsTabProps =>
|
||||
describe('ThresholdsTab', () => {
|
||||
beforeEach(() => {
|
||||
captureThresholdsTableProps.mockReset();
|
||||
captureIntentPolicyProps.mockReset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -290,4 +291,28 @@ describe('ThresholdsTab', () => {
|
||||
expect(typeof props.guestDefaults).toBe('object');
|
||||
expect(typeof props.dockerDefaults).toBe('object');
|
||||
});
|
||||
|
||||
it('routes a resource-row delay action into the canonical intent policy panel', async () => {
|
||||
render(() => <ThresholdsTab {...buildProps()} />);
|
||||
|
||||
const tableProps = captureThresholdsTableProps.mock.calls[0][0] as {
|
||||
onConfigureResourceIntent: (resourceId: string, signal: string) => void;
|
||||
};
|
||||
tableProps.onConfigureResourceIntent('vm-canonical', 'metric.memory');
|
||||
|
||||
await waitFor(() => {
|
||||
const panelProps = captureIntentPolicyProps.mock.calls.at(-1)?.[0] as {
|
||||
selectionTarget?: {
|
||||
resourceId: string;
|
||||
signal: string;
|
||||
requestId: number;
|
||||
};
|
||||
};
|
||||
expect(panelProps.selectionTarget).toEqual({
|
||||
resourceId: 'vm-canonical',
|
||||
signal: 'metric.memory',
|
||||
requestId: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,35 @@
|
||||
import { createSignal } from 'solid-js';
|
||||
import { ThresholdsTable } from '@/components/Alerts/ThresholdsTable';
|
||||
import { AlertIntentPolicyPanel } from '../AlertIntentPolicyPanel';
|
||||
import {
|
||||
AlertIntentPolicyPanel,
|
||||
type AlertIntentPolicySelectionTarget,
|
||||
} from '../AlertIntentPolicyPanel';
|
||||
import type { AlertIntentSignal } from '@/api/alertIntentPolicies';
|
||||
|
||||
import type { ThresholdsTabProps } from '../thresholds/thresholdsTabModel';
|
||||
|
||||
export function ThresholdsTab(props: ThresholdsTabProps) {
|
||||
const [intentSelectionTarget, setIntentSelectionTarget] =
|
||||
createSignal<AlertIntentPolicySelectionTarget | null>(null);
|
||||
let intentSelectionRequest = 0;
|
||||
|
||||
const configureResourceIntent = (resourceId: string, signal: AlertIntentSignal) => {
|
||||
intentSelectionRequest += 1;
|
||||
setIntentSelectionTarget({
|
||||
resourceId,
|
||||
signal,
|
||||
requestId: intentSelectionRequest,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="space-y-4">
|
||||
<AlertIntentPolicyPanel resources={props.allResources} />
|
||||
<AlertIntentPolicyPanel
|
||||
resources={props.allResources}
|
||||
selectionTarget={intentSelectionTarget()}
|
||||
/>
|
||||
<ThresholdsTable
|
||||
onConfigureResourceIntent={configureResourceIntent}
|
||||
overrides={props.overrides}
|
||||
setOverrides={props.setOverrides}
|
||||
rawOverridesConfig={props.rawOverridesConfig}
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
BackupAlertConfig,
|
||||
} from '@/types/alerts';
|
||||
import type { Resource } from '@/types/resource';
|
||||
import type { AlertIntentSignal } from '@/api/alertIntentPolicies';
|
||||
|
||||
export type OverrideType =
|
||||
| 'guest'
|
||||
@@ -73,6 +74,7 @@ export interface SimpleThresholds {
|
||||
}
|
||||
|
||||
export interface ThresholdsTableProps {
|
||||
onConfigureResourceIntent?: (resourceId: string, signal: AlertIntentSignal) => void;
|
||||
overrides: () => Override[];
|
||||
setOverrides: (overrides: Override[]) => void;
|
||||
rawOverridesConfig: () => Record<string, RawOverrideConfig>;
|
||||
|
||||
Reference in New Issue
Block a user