import { For, Show, createMemo, createSignal } from 'solid-js'; import { Portal } from 'solid-js/web'; import { useNavigate } from '@solidjs/router'; import type { JSX } from 'solid-js'; import type { Alert } from '@/types/api'; import type { AlertConfig, AlertThresholds, HysteresisThreshold } from '@/types/alerts'; import { showError, showSuccess } from '@/utils/toast'; import { formatAlertValue, formatAlertThreshold } from '@/utils/alertFormatters'; interface ActivationModalProps { isOpen: boolean; onClose: () => void; onActivated?: () => Promise | void; config: () => AlertConfig | null; activeAlerts: () => Alert[] | undefined; isLoading: () => boolean; activate: () => Promise; refreshActiveAlerts: () => Promise; } interface ThresholdSummary { heading: string; items: Array<{ label: string; value: string }>; } const extractTrigger = ( threshold?: HysteresisThreshold | number, legacy?: number, ): number | undefined => { if (typeof threshold === 'number') { return threshold; } if (threshold && typeof threshold === 'object' && typeof threshold.trigger === 'number') { return threshold.trigger; } if (typeof legacy === 'number') { return legacy; } return undefined; }; const formatThreshold = (value: number | undefined): string => { if (value === undefined || Number.isNaN(value)) { return 'Not configured'; } if (value <= 0) { return 'Disabled'; } return `${value}%`; }; const summarizeThresholds = (config: AlertConfig | null): ThresholdSummary[] => { if (!config) { return []; } const summarize = (thresholds?: AlertThresholds): Array<{ label: string; value: string }> => { if (!thresholds) return []; return [ { label: 'CPU', value: formatThreshold(extractTrigger(thresholds.cpu, thresholds.cpuLegacy)), }, { label: 'Memory', value: formatThreshold(extractTrigger(thresholds.memory, thresholds.memoryLegacy)), }, { label: 'Disk', value: formatThreshold(extractTrigger(thresholds.disk, thresholds.diskLegacy)), }, ]; }; const guestItems = summarize(config.guestDefaults); const nodeItems = summarize(config.nodeDefaults); const storageValue = formatThreshold(extractTrigger(config.storageDefault)); const summaries: ThresholdSummary[] = []; if (guestItems.length > 0) { summaries.push({ heading: 'Guest thresholds', items: guestItems }); } if (nodeItems.length > 0) { const nodeWithTemperature = [ ...nodeItems, { label: 'Temperature', value: formatAlertThreshold(extractTrigger(config.nodeDefaults?.temperature), 'temperature'), }, ]; summaries.push({ heading: 'Node thresholds', items: nodeWithTemperature }); } summaries.push({ heading: 'Storage', items: [ { label: 'Usage', value: storageValue, }, ], }); return summaries; }; const getChannelSummary = (config: AlertConfig | null): { status: 'configured' | 'missing'; message: string } => { if (!config || !config.notifications) { return { status: 'missing', message: 'Notification channels are not configured yet. Configure email or webhook destinations before activation.', }; } const emailConfigured = Boolean(config.notifications.email?.server); const webhookConfigured = Boolean(config.notifications.webhooks?.some((hook) => hook.enabled)); if (!emailConfigured && !webhookConfigured) { return { status: 'missing', message: 'Notification channels are not configured yet. Configure email or webhook destinations before activation.', }; } if (emailConfigured && webhookConfigured) { return { status: 'configured', message: 'Email and webhook destinations are ready. You can fine-tune them under Notification Destinations.', }; } if (emailConfigured) { return { status: 'configured', message: 'Email notifications are configured. Add additional webhook destinations if needed.', }; } return { status: 'configured', message: 'Webhook notifications are configured. Add email fallbacks if needed.', }; }; export function ActivationModal(props: ActivationModalProps): JSX.Element { const navigate = useNavigate(); const [isSubmitting, setIsSubmitting] = createSignal(false); const thresholdSummaries = createMemo(() => summarizeThresholds(props.config())); const violations = createMemo(() => props.activeAlerts() ?? []); const violationCount = createMemo(() => violations().length); const channelSummary = createMemo(() => getChannelSummary(props.config())); const observationHours = createMemo(() => props.config()?.observationWindowHours ?? 24); const handleActivate = async () => { if (isSubmitting()) { return; } setIsSubmitting(true); const success = await props.activate(); if (success) { await props.refreshActiveAlerts(); showSuccess('Notifications activated! You\'ll now receive alerts when issues are detected.'); if (props.onActivated) { await props.onActivated(); } props.onClose(); } else { showError('Unable to activate notifications. Please try again.'); } setIsSubmitting(false); }; const handleNavigateDestinations = () => { props.onClose(); navigate('/alerts/destinations'); }; return (

Ready to activate notifications

Review your alert thresholds and notification channels before turning on alerts.

Current thresholds

Thresholds determine when alerts fire. Adjust them under Alert Thresholds if needed before activating.

{(section) => (

{section.heading}

    {(item) => (
  • {item.label} {item.value}
  • )}
)}

Issues detected

Observation window: {observationHours()}h

{violationCount() > 0 ? 'These alerts are currently active. When you activate, notifications will be sent to your configured channels.' : 'No alerts triggered yet. When you activate, you\'ll be notified immediately if any issues are detected.'}

0} fallback={
All systems healthy — no alerts triggered.
} >
{(alert) => (
{alert.level} {alert.resourceName || alert.resourceId}
{alert.type}

{alert.message}

Threshold {formatAlertValue(alert.threshold, alert.type)} • Current {formatAlertValue(alert.value, alert.type)} • Since{' '} {new Date(alert.startTime).toLocaleString()}

)}

Notification channels

{channelSummary().message}

You can snooze alerts later if you need a quiet period.

); }