import {RefreshCw, Trash2} from 'lucide-react' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' import {useEffect, useState} from 'react' import {getLoadBalancingDependencies, listFlatItems, putFlatItem, deleteFlatItem} from '@/api/cloudProxyClient' import {useAccountId} from '@/lib/accountStore' import {TargetHealthIcon} from '@/components/LoadBalancingProgress' import type {CloudResource} from '@/types/resource' type Props = { resource?: CloudResource runtimeReachable: boolean } type TargetInstance = {id: string; name: string; privateIp: string | null} type PendingTarget = {id: string; name: string; port: number} export function TargetGroupPanel({resource, runtimeReachable}: Props) { const accountId = useAccountId() const queryClient = useQueryClient() const [instanceId, setInstanceId] = useState('') const [port, setPort] = useState('') const [pendingTargets, setPendingTargets] = useState([]) const dependenciesQuery = useQuery({ queryKey: ['target-group-instances', accountId], queryFn: ({signal}) => getTargetGroupInstances(signal), enabled: Boolean(resource) && runtimeReachable, refetchInterval: pendingTargets.length ? 500 : false, }) const healthQuery = useQuery({ queryKey: ['target-group-health', accountId, resource?.id], queryFn: ({signal}) => listFlatItems('aws', 'targetgroups', resource?.id ?? '', undefined, signal), enabled: Boolean(resource) && runtimeReachable, }) const refetchHealth = healthQuery.refetch useEffect(() => { setInstanceId('') setPort(typeof resource?.metadata.port === 'number' ? String(resource.metadata.port) : '') }, [resource?.id, resource?.metadata.port]) useEffect(() => { const targets = healthQuery.data?.items ?? [] setPendingTargets((current) => current.filter((pending) => { const state = stringValue(targets.find((target) => stringValue(target.body.id) === pending.id && Number(target.body.port) === pending.port)?.body.state).toLowerCase() return state !== 'healthy' && state !== 'unhealthy' })) }, [healthQuery.data]) useEffect(() => { if (!pendingTargets.length) { return } const interval = window.setInterval(() => { void refetchHealth() }, 1_000) return () => window.clearInterval(interval) }, [pendingTargets.length, refetchHealth]) const refresh = () => { void Promise.all([dependenciesQuery.refetch(), healthQuery.refetch()]) } const registerMutation = useMutation({ mutationFn: (target: PendingTarget) => putFlatItem('aws', 'targetgroups', resource?.id ?? '', {id: target.id, port: target.port}), onSuccess: (_, target) => { setPendingTargets((current) => [...current, target]) void queryClient.invalidateQueries({queryKey: ['target-group-health', accountId, resource?.id]}) }, }) const deregisterMutation = useMutation({ mutationFn: (targetKey: string) => deleteFlatItem('aws', 'targetgroups', resource?.id ?? '', targetKey), onSuccess: () => queryClient.invalidateQueries({queryKey: ['target-group-health', accountId, resource?.id]}), }) if (!resource) { return

Select a target group

Choose a target group to register running instances and inspect target health.

} const instances = dependenciesQuery.data ?? [] const targets = healthQuery.data?.items ?? [] const validPort = /^\d+$/.test(port) && Number(port) >= 1 && Number(port) <= 65535 const error = healthQuery.error ?? dependenciesQuery.error ?? registerMutation.error ?? deregisterMutation.error const pendingRows = pendingTargets.filter((pending) => !targets.some((target) => stringValue(target.body.id) === pending.id && Number(target.body.port) === pending.port)) return

Target health

{targets.length} registered targets in {resource.name}
{ event.preventDefault(); const instance = instances.find((candidate) => candidate.id === instanceId); if (instance && validPort) registerMutation.mutate({id: instance.id, name: instance.name, port: Number(port)}) }}>
{instances.length === 0 && dependenciesQuery.isSuccess &&

No running instances are available in this account.

}
{error instanceof Error &&

{error.message}

} {healthQuery.isLoading &&
Loading target health
} {healthQuery.isSuccess && targets.length === 0 && pendingRows.length === 0 &&

No registered targets

Register a running instance to see its ELB health state.

} {(targets.length > 0 || pendingRows.length > 0) &&
{pendingRows.map((target) => )}{targets.map((target) => { const state = stringValue(target.body.state); return })}
InstancePortStateReasonDescription
{target.id}{target.port}Checking-Waiting for target health.
{stringValue(target.body.id)}{stringValue(target.body.port)}{state || '-'}{stringValue(target.body.reason) || '-'}{stringValue(target.body.description) || '-'}
}
} async function getTargetGroupInstances(signal?: AbortSignal): Promise { const dependencies = await getLoadBalancingDependencies('targetgroups', signal) return Array.isArray(dependencies.instances) ? dependencies.instances : [] } function stringValue(value: unknown): string { return typeof value === 'string' || typeof value === 'number' ? String(value) : '' }