Files
supabase/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/HaInstanceConfiguration.tsx
T
Joshen Lim 09a1f431bc Mass update for all lucide icon imports to use the component with Icon suffix (#51659)
## Context

This is largely just a mechanical change here:
Opting to use lucide icons with the `Icon` suffix across all icon
imports within `studio`
- so that it’s clearer which components are icons + reduces the
possibility of name collision with our own components
- e.g <User /> vs <UserIcon />

Also added a eslint rule for this:
<img width="1069" height="167" alt="image"
src="https://github.com/user-attachments/assets/7f36f443-5ca2-4bf6-8d22-86e774513bfc"
/>

And updated `AGENTS.md` as such:
```
Icons come from `lucide-react`, always imported with the `Icon` suffix (`import { CheckIcon, Trash2Icon } from 'lucide-react'`) — never the bare names (`Check`) or `Check as CheckIcon` aliases (lint-enforced).
```
2026-10-14 12:56:55 +02:00

150 lines
5.1 KiB
TypeScript

import { useQuery } from '@tanstack/react-query'
import { useParams } from 'common'
import { Loader2Icon } from 'lucide-react'
import { useMemo } from 'react'
import { EmptyStatePresentational } from 'ui-patterns/EmptyStatePresentational'
import { DiagramFlow } from './DiagramFlow'
import { addShardNodes, generateHaNodesAndEdges } from './HaInstanceConfiguration.utils'
import { HaPrimaryNode, HaReplicaNode, HaShardNode, MultigatewayNode } from './HaInstanceNode'
import { buildHaTopology, selectTopologyPoolers } from './HaTopology.utils'
import { HA_RANKSEP } from './InstanceConfiguration.constants'
import { AlertError } from '@/components/ui/AlertError'
import { HighAvailabilityDisabledEmptyState } from '@/components/ui/HighAvailability/HighAvailabilityDisabledEmptyState'
import { haClusterGatewaysQueryOptions } from '@/data/ha-admin/ha-cluster-gateways-query'
import { haClusterPoolersQueryOptions } from '@/data/ha-admin/ha-cluster-poolers-query'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { PROJECT_STATUS } from '@/lib/constants'
const nodeTypes = {
HA_GATEWAY: MultigatewayNode,
HA_PRIMARY: HaPrimaryNode,
HA_REPLICA: HaReplicaNode,
HA_SHARD: HaShardNode,
}
// The gateway and replication edges both use React Flow's built-in smoothstep.
const edgeTypes = {}
const POOLER_STATUS_REFRESH_MS = 30_000
/**
* Cluster topology diagram for High Availability (Multigres) projects:
* gateway tier → shard group → primary + read replicas, driven by the
* read-only multiadmin `/ha-admin` passthrough.
*/
export const HaInstanceConfiguration = () => {
const { ref: projectRef } = useParams()
const { data: project } = useSelectedProjectQuery()
const isProjectBuilding =
project?.status === PROJECT_STATUS.COMING_UP || project?.status === PROJECT_STATUS.UNKNOWN
// Gateways poll on the same interval as poolers so the gateway count and
// gateway→primary edge track cluster changes while the page stays open.
const {
data: gatewaysData,
error: gatewaysError,
isPending: isPendingGateways,
isError: isErrorGateways,
} = useQuery({
...haClusterGatewaysQueryOptions({ projectRef }),
refetchInterval: POOLER_STATUS_REFRESH_MS,
})
const {
data: poolers,
error: poolersError,
isPending: isPendingPoolers,
isError: isErrorPoolers,
} = useQuery({
...haClusterPoolersQueryOptions({ projectRef }),
refetchInterval: POOLER_STATUS_REFRESH_MS,
select: selectTopologyPoolers,
})
const gateways = gatewaysData?.gateways
const { nodes, edges } = useMemo(
() =>
generateHaNodesAndEdges(
buildHaTopology({ gateways: gateways ?? [], poolers: poolers ?? [] })
),
[gateways, poolers]
)
const isPending = isPendingGateways || isPendingPoolers
const isError = isErrorGateways || isErrorPoolers
const error = gatewaysError ?? poolersError
// While the project is provisioning, the /ha-admin endpoints fail or return an
// empty topology as a matter of course — that's a project still booting, not an
// unhealthy cluster.
const isProvisioning = isProjectBuilding && (isError || (poolers ?? []).length === 0)
// The initial load and the provisioning placeholder share one persistent
// role="status" live region so screen readers announce the transition between
// them — a live region only announces updates to content it already contains.
if (isPending || isProvisioning) {
return (
<div role="status" className="h-full w-full flex items-center justify-center p-6">
{isPending ? (
<>
<span className="sr-only">Loading cluster topology...</span>
<Loader2Icon
aria-hidden="true"
className="motion-safe:animate-spin text-foreground-light"
/>
</>
) : (
<EmptyStatePresentational
icon={
<Loader2Icon
size={24}
strokeWidth={1.5}
aria-hidden="true"
className="motion-safe:animate-spin text-foreground-muted"
/>
}
title="Setting up project"
description="Cluster topology will be available once your project is ready. This may take a few minutes."
className="max-w-md"
/>
)}
</div>
)
}
if (isError) {
return (
<div className="h-full w-full flex items-center justify-center px-28">
<AlertError error={error} subject="Failed to retrieve cluster topology" />
</div>
)
}
if ((poolers ?? []).length === 0) {
return (
<div className="h-full w-full flex items-center justify-center p-6">
<HighAvailabilityDisabledEmptyState
title="Cluster topology unavailable"
description="Topology data for this project is not available yet. Contact support if this persists."
/>
</div>
)
}
return (
<div className="nowheel h-full w-full relative">
<DiagramFlow
nodes={nodes}
edges={edges}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
addGroupNodes={addShardNodes}
ranksep={HA_RANKSEP}
/>
</div>
)
}