mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-15 08:33:03 +00:00
Label Patrol configuration toggles
This commit is contained in:
@@ -62,4 +62,23 @@ describe('Toggle', () => {
|
||||
expect(button).toHaveAttribute('aria-describedby');
|
||||
expect(button).toHaveAccessibleDescription('Turns it on');
|
||||
});
|
||||
|
||||
it('supports an external visible label when the layout owns label placement', () => {
|
||||
render(() => (
|
||||
<>
|
||||
<span id="external-toggle-label">Alert-triggered patrols</span>
|
||||
<span id="external-toggle-description">Run when alerts fire.</span>
|
||||
<Toggle
|
||||
checked={true}
|
||||
ariaLabelledBy="external-toggle-label"
|
||||
ariaDescribedBy="external-toggle-description"
|
||||
/>
|
||||
</>
|
||||
));
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Alert-triggered patrols' });
|
||||
expect(button).toHaveAttribute('aria-labelledby', 'external-toggle-label');
|
||||
expect(button).toHaveAttribute('aria-describedby', 'external-toggle-description');
|
||||
expect(button).toHaveAccessibleDescription('Run when alerts fire.');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -59,8 +59,8 @@ export function Toggle(props: LabeledToggleProps) {
|
||||
<TogglePrimitive
|
||||
{...props}
|
||||
size={size}
|
||||
ariaLabelledBy={props.label ? labelId : undefined}
|
||||
ariaDescribedBy={props.description ? descriptionId : undefined}
|
||||
ariaLabelledBy={props.label ? labelId : props.ariaLabelledBy}
|
||||
ariaDescribedBy={props.description ? descriptionId : props.ariaDescribedBy}
|
||||
/>
|
||||
{(props.label || props.description) && (
|
||||
<span class={getToggleLabelClass()}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createMemo, For, Show } from 'solid-js';
|
||||
import { createMemo, createUniqueId, For, Show } from 'solid-js';
|
||||
import RefreshCwIcon from 'lucide-solid/icons/refresh-cw';
|
||||
import PlayIcon from 'lucide-solid/icons/play';
|
||||
import CircleHelpIcon from 'lucide-solid/icons/circle-help';
|
||||
@@ -42,6 +42,13 @@ export function getPatrolConfigurationFailureInlineDetails(
|
||||
|
||||
export function PatrolIntelligenceHeader(props: { state: PatrolIntelligenceState }) {
|
||||
const state = props.state;
|
||||
const fieldIdPrefix = `patrol-config-${createUniqueId()}`;
|
||||
const fieldIds = {
|
||||
alertTriggeredAnalysis: `${fieldIdPrefix}-alert-triggered-analysis`,
|
||||
patrolAlertTriggers: `${fieldIdPrefix}-alert-triggered-patrols`,
|
||||
patrolAnomalyTriggers: `${fieldIdPrefix}-anomaly-triggered-patrols`,
|
||||
autonomousCriticalRemediation: `${fieldIdPrefix}-autonomous-critical-remediation`,
|
||||
};
|
||||
const headerMeta = getPatrolPageHeaderMeta();
|
||||
const scheduleOptions = createMemo(() => buildPatrolScheduleOptions(state.patrolInterval()));
|
||||
const selectedScheduleLabel = createMemo(
|
||||
@@ -325,9 +332,12 @@ export function PatrolIntelligenceHeader(props: { state: PatrolIntelligenceState
|
||||
<div class="space-y-4 pt-4 border-t border-border-subtle">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex-1">
|
||||
<label class="text-sm font-medium text-base-content">
|
||||
<span
|
||||
id={fieldIds.alertTriggeredAnalysis}
|
||||
class="text-sm font-medium text-base-content"
|
||||
>
|
||||
Alert-Triggered Analysis
|
||||
</label>
|
||||
</span>
|
||||
<p class="text-[11px] text-muted mt-0.5 leading-tight">
|
||||
Analyze infrastructure automatically when critical alerts fire.
|
||||
</p>
|
||||
@@ -338,6 +348,7 @@ export function PatrolIntelligenceHeader(props: { state: PatrolIntelligenceState
|
||||
state.handleAlertTriggeredAnalysisChange(e.currentTarget.checked)
|
||||
}
|
||||
disabled={state.isUpdatingSettings() || state.alertAnalysisLocked()}
|
||||
ariaLabelledBy={fieldIds.alertTriggeredAnalysis}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -365,9 +376,12 @@ export function PatrolIntelligenceHeader(props: { state: PatrolIntelligenceState
|
||||
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex-1">
|
||||
<label class="text-sm font-medium text-base-content">
|
||||
<span
|
||||
id={fieldIds.patrolAlertTriggers}
|
||||
class="text-sm font-medium text-base-content"
|
||||
>
|
||||
Alert-Triggered Patrols
|
||||
</label>
|
||||
</span>
|
||||
<p class="text-[11px] text-muted mt-0.5 leading-tight">
|
||||
Run scoped Patrol checks when alerts fire or clear.
|
||||
</p>
|
||||
@@ -378,14 +392,18 @@ export function PatrolIntelligenceHeader(props: { state: PatrolIntelligenceState
|
||||
state.handlePatrolAlertTriggersChange(e.currentTarget.checked)
|
||||
}
|
||||
disabled={state.isUpdatingSettings() || !state.patrolEnabledLocal()}
|
||||
ariaLabelledBy={fieldIds.patrolAlertTriggers}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex-1">
|
||||
<label class="text-sm font-medium text-base-content">
|
||||
<span
|
||||
id={fieldIds.patrolAnomalyTriggers}
|
||||
class="text-sm font-medium text-base-content"
|
||||
>
|
||||
Anomaly-Triggered Patrols
|
||||
</label>
|
||||
</span>
|
||||
<p class="text-[11px] text-muted mt-0.5 leading-tight">
|
||||
Run scoped Patrol checks when learned baselines detect high-signal
|
||||
anomalies.
|
||||
@@ -397,14 +415,18 @@ export function PatrolIntelligenceHeader(props: { state: PatrolIntelligenceState
|
||||
state.handlePatrolAnomalyTriggersChange(e.currentTarget.checked)
|
||||
}
|
||||
disabled={state.isUpdatingSettings() || !state.patrolEnabledLocal()}
|
||||
ariaLabelledBy={fieldIds.patrolAnomalyTriggers}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex-1">
|
||||
<label class="text-sm font-medium text-red-600 dark:text-red-400">
|
||||
<span
|
||||
id={fieldIds.autonomousCriticalRemediation}
|
||||
class="text-sm font-medium text-red-600 dark:text-red-400"
|
||||
>
|
||||
Autonomous critical remediation
|
||||
</label>
|
||||
</span>
|
||||
<p class="text-[11px] text-muted mt-0.5 leading-tight">
|
||||
Permit Patrol to execute critical remediation actions without approval.
|
||||
</p>
|
||||
@@ -418,6 +440,7 @@ export function PatrolIntelligenceHeader(props: { state: PatrolIntelligenceState
|
||||
state.autonomyLevel() === 'assisted' || state.autonomyLevel() === 'full'
|
||||
)
|
||||
}
|
||||
ariaLabelledBy={fieldIds.autonomousCriticalRemediation}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -274,10 +274,14 @@ vi.mock('@/components/shared/Toggle', () => ({
|
||||
disabled?: boolean;
|
||||
onToggle?: (value: boolean) => void;
|
||||
ariaLabel?: string;
|
||||
ariaLabelledBy?: string;
|
||||
ariaDescribedBy?: string;
|
||||
}) => (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={props.ariaLabel}
|
||||
aria-labelledby={props.ariaLabel ? undefined : props.ariaLabelledBy}
|
||||
aria-describedby={props.ariaDescribedBy}
|
||||
aria-pressed={props.checked}
|
||||
disabled={props.disabled}
|
||||
onClick={() => props.onToggle?.(!props.checked)}
|
||||
@@ -287,11 +291,17 @@ vi.mock('@/components/shared/Toggle', () => ({
|
||||
checked?: boolean;
|
||||
disabled?: boolean;
|
||||
onChange?: (event: Event & { currentTarget: HTMLInputElement }) => void;
|
||||
ariaLabel?: string;
|
||||
ariaLabelledBy?: string;
|
||||
ariaDescribedBy?: string;
|
||||
}) => (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={props.checked}
|
||||
disabled={props.disabled}
|
||||
aria-label={props.ariaLabel}
|
||||
aria-labelledby={props.ariaLabel ? undefined : props.ariaLabelledBy}
|
||||
aria-describedby={props.ariaDescribedBy}
|
||||
onChange={(event) => props.onChange?.(event as Event & { currentTarget: HTMLInputElement })}
|
||||
/>
|
||||
),
|
||||
@@ -789,6 +799,12 @@ describe('AIIntelligence entitlement gating', () => {
|
||||
|
||||
expect(screen.getByRole('button', { name: 'Investigate' })).not.toBeDisabled();
|
||||
expect(screen.getByRole('button', { name: 'Remediate' })).not.toBeDisabled();
|
||||
expect(screen.getByRole('checkbox', { name: 'Alert-Triggered Analysis' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('checkbox', { name: 'Alert-Triggered Patrols' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('checkbox', { name: 'Anomaly-Triggered Patrols' })).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('checkbox', { name: 'Autonomous critical remediation' }),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByRole('link', { name: 'Upgrade to Pro' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('link', { name: 'Upgrade' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user