import {RefreshCw} from 'lucide-react' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' import {useEffect, useState} from 'react' import {describeAlarmHistory, getCloudResource, getMetricSeries, setAlarmActionsEnabled, setAlarmState} from '@/api/cloudProxyClient' import {HttpError} from '@/api/HttpClient' import {MetricChart} from '@/components/MetricChart' import {RANGES} from '@/lib/metricChart' import {useAccountId} from '@/lib/accountStore' import {formatDateTime} from '@/lib/format' import type {AlarmStateValue, CloudResource, MetricDimension} from '@/types/resource' type Props = { resource?: CloudResource runtimeReachable: boolean } const STATES: AlarmStateValue[] = ['ALARM', 'OK', 'INSUFFICIENT_DATA'] export function CloudWatchAlarmPanel({resource, runtimeReachable}: Props) { const accountId = useAccountId() const queryClient = useQueryClient() const [rangeMinutes, setRangeMinutes] = useState(180) const [nextState, setNextState] = useState('ALARM') const [reason, setReason] = useState('') const [notice, setNotice] = useState('') const alarmKey = ['cloudwatch-alarm', accountId, resource?.id] const historyKey = ['cloudwatch-alarm-history', accountId, resource?.id] useEffect(() => { setNotice('') setReason('') }, [resource?.id]) const alarmQuery = useQuery({ queryKey: alarmKey, queryFn: ({signal}) => getCloudResource('aws', 'monitoring', resource?.id ?? '', signal), enabled: Boolean(resource) && runtimeReachable, }) const historyQuery = useQuery({ queryKey: historyKey, queryFn: ({signal}) => describeAlarmHistory('aws', resource?.id ?? '', signal), enabled: Boolean(resource) && runtimeReachable, }) const alarm = alarmQuery.data ?? resource const metadata = alarm?.metadata ?? {} const isMetric = metadata.alarmType === 'metric' && typeof metadata.metricName === 'string' const dimensions = Array.isArray(metadata.dimensions) ? metadata.dimensions as MetricDimension[] : [] const seriesQuery = useQuery({ queryKey: ['cloudwatch-alarm-series', accountId, resource?.id, rangeMinutes, metadata.metricName, metadata.statistic], queryFn: ({signal}) => getMetricSeries('aws', { namespace: String(metadata.namespace), metricName: String(metadata.metricName), dimensions, statistic: String(metadata.statistic ?? 'Average'), rangeMinutes, }, signal), enabled: Boolean(resource) && runtimeReachable && isMetric, }) const refresh = () => Promise.all([ queryClient.invalidateQueries({queryKey: alarmKey}), queryClient.invalidateQueries({queryKey: historyKey}), queryClient.invalidateQueries({queryKey: ['cloud-resources', 'aws', 'monitoring']}), ]) const stateMutation = useMutation({ mutationFn: () => setAlarmState('aws', resource?.id ?? '', nextState, reason), onSuccess: () => { setNotice(`Set to ${nextState}. Its actions ran if they are on; CloudWatch puts it back at the next evaluation.`) setReason('') return refresh() }, }) const actionsMutation = useMutation({ mutationFn: (enabled: boolean) => setAlarmActionsEnabled('aws', resource?.id ?? '', enabled), onSuccess: (_result, enabled) => { setNotice(enabled ? 'Actions on: the alarm notifies its targets when its state changes.' : 'Actions off: the alarm still changes state but notifies nobody.') return refresh() }, }) if (!resource || !alarm) { return

Select an alarm

Choose an alarm to see its metric, test its actions and read its history.

} const state = alarm.status ?? 'INSUFFICIENT_DATA' const actionsEnabled = metadata.actionsEnabled !== false const targets = [...new Set([...list(metadata.alarmActions), ...list(metadata.okActions)])] const disabled = !runtimeReachable || stateMutation.isPending || actionsMutation.isPending // TheCloud's runtime does not implement DescribeAlarmHistory yet; say so rather than fail. const historyUnsupported = isNotImplemented(historyQuery.error) const error = alarmQuery.error ?? (historyUnsupported ? null : historyQuery.error) ?? seriesQuery.error ?? stateMutation.error ?? actionsMutation.error const history = historyQuery.data ?? [] return

Alarm

{state} {String(metadata.stateReason ?? '')}
{isMetric ? 'Condition' : 'Rule'}
{String(metadata.condition ?? '—')}
{isMetric && <>
Metric
{String(metadata.namespace)} / {String(metadata.metricName)}{dimensions.map((dimension) => ` · ${dimension.name}=${dimension.value}`).join('')}
}
State changed
{formatDateTime(typeof metadata.stateUpdatedAt === 'string' ? metadata.stateUpdatedAt : null) ?? '—'}
Notifies
{targets.length ? targets.join(', ') : 'No actions'}
{isMetric && <>
{RANGES.map((range) => ( ))}

{String(metadata.metricName)} · {String(metadata.statistic ?? '')}

{seriesQuery.isLoading &&

Loading metric data

} {seriesQuery.data && }
}

Test the alarm

Sets the state now, which runs its actions. CloudWatch evaluates it again at the next period.
{ event.preventDefault(); setNotice(''); stateMutation.mutate() }}>
{notice &&

{notice}

} {error instanceof Error &&

{error.message}

}

History

{history.length > 0 && Newest first.}
{historyUnsupported &&

TheCloud's runtime does not record alarm history yet. State changes still notify the alarm's actions.

} {historyQuery.isSuccess && history.length === 0 &&

No history yet.

} {history.length > 0 &&
{history.map((item, index) => ( ))}
WhenTypeWhat happened
{formatDateTime(item.timestamp) ?? '—'}{item.type}{item.summary}
}
} function isNotImplemented(error: unknown): boolean { return error instanceof HttpError && typeof error.body === 'object' && error.body !== null && (error.body as {code?: unknown}).code === 'operation_not_implemented' } function list(value: unknown): string[] { return Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === 'string') : [] }