Files
supabase/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/HaInstanceNode.tsx
T
Gildas GarciaandAlaister Young 737b8595f2 Update API types (#50234)
## Problem

platform, v1 and v2 have been already completely migrated and introduced
some changes.

Some types have been renamed, some outputs and inputs updated.

## Solution

- Update the API types
- Fix the TS errors

## Update

Taking this over to unblock #50134, which needs the new scoped token
permission ids from the regenerated types.

- Merged `master`.
- Regenerated `api-v2.d.ts` from the production spec. The previous files
came from a local API that exposed a webhook events endpoint production
doesn't have yet. Production has since added standardized 400 error
responses on the v2 organization endpoints. `api-v1.d.ts` and
`platform.d.ts` already matched production.
- Fixed `verify-production-types`. It formatted the regenerated files in
a temp directory outside the repository, so Prettier fell back to its
defaults and the comparison could never match the committed files. It
now passes the repository config explicitly. `pnpm api:verify-types`
passes on this branch.
- Verified locally: `pnpm typecheck`, `pnpm api:verify-types`, Studio
unit tests.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Preserved descriptions when saving, sharing, moving, or unsharing
notebooks, reports, SQL snippets, and saved queries.
* Improved handling of empty or null values across notebook
descriptions, billing usage, pooler settings, and infrastructure fields.
* Improved read-replica connection handling, including read-only
connection strings.
* Updated storage configuration and capability handling to match current
settings.

* **API and Compatibility**
* Updated organization, project, storage, OAuth, billing, and
infrastructure data handling to match current API responses.
  * OAuth app creation and updates now require scopes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
2026-09-11 12:17:49 +08:00

191 lines
7.4 KiB
TypeScript

import { Handle, Node, NodeProps, Position } from '@xyflow/react'
import { Database, DatabaseBackup, HelpCircle, Layers, Loader2, Network } from 'lucide-react'
import { Badge, cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import { ComputeMetricsFooter } from './ComputeMetricsFooter'
import {
HaPoolerNodeData,
HaShardNodeData,
MultigatewayNodeData,
} from './HaInstanceConfiguration.utils'
import { HA_POOLER_STATUS_LABELS, HaPoolerStatus } from './HaTopology.utils'
import { NODE_CARD_WIDTH } from './InstanceConfiguration.constants'
import { useHaPoolerCard } from './useHaPooler'
import { BASE_PATH } from '@/lib/constants'
const STATUS_BADGE_VARIANTS: Record<HaPoolerStatus, 'success' | 'warning' | 'default'> = {
healthy: 'success',
coming_up: 'default',
going_down: 'default',
unhealthy: 'warning',
}
const PoolerStatusBadge = ({ status }: { status: HaPoolerStatus }) => (
<Badge variant={STATUS_BADGE_VARIANTS[status]}>{HA_POOLER_STATUS_LABELS[status]}</Badge>
)
const PoolerCardSubtitle = ({
availabilityZone,
computeSize,
}: {
availabilityZone?: string
computeSize?: string | null
}) => (
<p className="flex items-center gap-x-1 text-sm text-foreground-light">
{availabilityZone !== undefined && <span>{availabilityZone}</span>}
{availabilityZone !== undefined && !!computeSize && (
<span className="text-foreground-lighter">·</span>
)}
{!!computeSize && <span>{computeSize}</span>}
</p>
)
export const MultigatewayNode = ({ data }: NodeProps<Node<MultigatewayNodeData>>) => {
const { numGateways } = data
return (
<>
<div className="flex flex-col rounded-sm bg-surface-100 border border-default">
<div className="flex items-start p-3 gap-x-4" style={{ width: NODE_CARD_WIDTH }}>
<div className="flex gap-x-3">
<div className="min-w-8 h-8 bg-blue-600 border border-blue-800 rounded-md flex items-center justify-center">
<Network aria-hidden="true" size={16} />
</div>
<div className="flex flex-col gap-y-0.5">
<p className="text-sm">Multigateway</p>
<p className="flex items-center gap-x-1 text-sm text-foreground-light">
<span>Load balancer</span>
{numGateways > 1 && (
<>
<span className="text-foreground-lighter">·</span>
<span>{numGateways} instances</span>
</>
)}
</p>
</div>
</div>
</div>
</div>
<Handle type="source" position={Position.Bottom} style={{ background: 'transparent' }} />
</>
)
}
export const HaPrimaryNode = ({ data }: NodeProps<Node<HaPoolerNodeData>>) => {
// [Joshen] Just FYI Handles cannot be conditionally rendered
const { cell, name, hasGateway } = data
const { status, availabilityZone, computeSize, primaryRegion } = useHaPoolerCard({ cell, name })
return (
<>
<Handle
type="target"
position={Position.Top}
className={!hasGateway ? 'opacity-0' : ''}
style={{ background: 'transparent' }}
/>
<div className="flex flex-col rounded-sm bg-surface-100 border border-default">
<div className="flex items-start justify-between p-3" style={{ width: NODE_CARD_WIDTH }}>
<div className="flex gap-x-3">
<div className="w-8 h-8 bg-brand-500 border border-brand-600 rounded-md flex items-center justify-center">
<Database aria-hidden="true" size={16} />
</div>
<div className="flex flex-col gap-y-0.5">
<div className="flex items-center gap-x-2">
<p className="text-sm">Primary Database</p>
{/* Stable live region so polled status changes are announced */}
<span role="status">
{status !== undefined && <PoolerStatusBadge status={status} />}
</span>
</div>
{primaryRegion !== undefined && (
<p className="text-sm text-foreground-light">{primaryRegion.name}</p>
)}
<PoolerCardSubtitle availabilityZone={availabilityZone} computeSize={computeSize} />
</div>
</div>
{primaryRegion !== undefined && (
// Decorative — the visible text next to it already names the region
<img
alt=""
aria-hidden="true"
className="w-8 rounded-xs mt-0.5"
src={`${BASE_PATH}/img/regions/${primaryRegion.region}.svg`}
/>
)}
</div>
{/* Whether connection metrics are meaningful through the multigateway
is unconfirmed, so they're left off for HA projects. */}
<ComputeMetricsFooter showConnections={false} />
</div>
<Handle type="source" position={Position.Bottom} style={{ background: 'transparent' }} />
</>
)
}
export const HaReplicaNode = ({ data }: NodeProps<Node<HaPoolerNodeData>>) => {
const { cell, name } = data
const { status, availabilityZone, computeSize } = useHaPoolerCard({ cell, name })
return (
<>
<Handle type="target" position={Position.Top} style={{ background: 'transparent' }} />
<div
className="flex items-start rounded-sm bg-surface-100 border border-default p-3"
style={{ width: NODE_CARD_WIDTH }}
>
<div className="flex gap-x-3">
<div
className={cn(
'w-8 h-8 border rounded-md flex items-center justify-center',
status === 'healthy'
? 'bg-brand-400 border-brand-500'
: 'bg-surface-100 border-foreground/20'
)}
>
{status === 'coming_up' ? (
<Loader2 aria-hidden="true" className="motion-safe:animate-spin" size={16} />
) : (
<DatabaseBackup aria-hidden="true" size={16} />
)}
</div>
<div className="flex flex-col gap-y-0.5">
<div className="flex items-center gap-x-2">
<p className="text-sm">Read Replica</p>
{/* Stable live region so polled status changes are announced */}
<span role="status">
{status !== undefined && <PoolerStatusBadge status={status} />}
</span>
</div>
<PoolerCardSubtitle availabilityZone={availabilityZone} computeSize={computeSize} />
</div>
</div>
</div>
</>
)
}
export const HaShardNode = ({ data }: NodeProps<Node<HaShardNodeData>>) => {
const { name } = data
return (
<div className="relative h-full w-full rounded-md border border-default bg-surface-100/25">
<div className="pointer-events-auto absolute top-3 left-3 flex items-center gap-x-2 rounded-full border border-default bg-surface-100 pl-3 pr-2 py-1">
<Layers aria-hidden="true" size={14} className="text-foreground-light" />
<p className="text-sm">{name}</p>
<Tooltip>
{/* Renders a button so the explanation is keyboard reachable */}
<TooltipTrigger className="flex items-center gap-x-1 text-sm text-foreground-light">
Automatic failover
<HelpCircle aria-hidden="true" size={14} />
</TooltipTrigger>
<TooltipContent side="bottom" className="max-w-72 text-center">
If the primary database fails, a replica in this shard is automatically promoted to take
its place.
</TooltipContent>
</Tooltip>
</div>
</div>
)
}