diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/HaTopology.utils.ts b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/HaTopology.utils.ts index 1624caf53b2..8c1867d6105 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/HaTopology.utils.ts +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/HaTopology.utils.ts @@ -54,6 +54,8 @@ export const getPoolerStatus = (pooler: Multipooler): HaPoolerStatus => { // Lifecycle values may arrive with or without the proto enum prefix. const lifecycle = (pooler.lifecycleStatus?.status ?? '').replace(/^LIFECYCLE_/, '') + // The proto's two terminal states: QUARANTINED (gave up recovering, kept alive + // for forensics) and SHUTDOWN (durably down). There is no separate FAILED state. if (lifecycle === 'QUARANTINED' || lifecycle === 'SHUTDOWN') return 'unhealthy' if (lifecycle === 'STARTING') return 'coming_up' if (lifecycle === 'STOPPING') return 'going_down' @@ -66,7 +68,12 @@ export const getPoolerStatus = (pooler: Multipooler): HaPoolerStatus => { return 'healthy' } -// Matches the status vocabulary of the read replica surfaces (getStatusLabel). +// Labels are drawn from the read replica status vocabulary (`getStatusLabel` in +// ReadReplicas.utils.ts) so both surfaces read the same way, but they are only a +// subset of it. The read replica labels also cover 'Failed', 'Restarting', +// 'Resizing' and 'Restoring', which have no one-to-one lifecycle equivalents; +// the closest to 'Failed' is QUARANTINED, surfaced as 'Unhealthy' alongside +// SHUTDOWN. export const HA_POOLER_STATUS_LABELS: Record = { healthy: 'Healthy', coming_up: 'Coming up', diff --git a/apps/studio/data/ha-admin/ha-cluster-cells-query.ts b/apps/studio/data/ha-admin/ha-cluster-cells-query.ts deleted file mode 100644 index d5571f502c3..00000000000 --- a/apps/studio/data/ha-admin/ha-cluster-cells-query.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { queryOptions } from '@tanstack/react-query' -import { z } from 'zod' - -import { getHaAdmin, parseHaAdminResponse } from './get-ha-admin' -import { haAdminKeys } from './keys' -import { IS_PLATFORM } from '@/lib/constants' -import type { ResponseError } from '@/types' - -export type HaClusterCellsVariables = { projectRef?: string } -export type HaClusterCellsError = ResponseError - -// Every field is optional because proto3 JSON omits zero values. -const haClusterCellsResponseSchema = z.object({ names: z.array(z.string()).optional() }) - -async function getHaClusterCells({ projectRef }: HaClusterCellsVariables, signal?: AbortSignal) { - const data = await getHaAdmin(projectRef, 'cells', signal) - return parseHaAdminResponse(haClusterCellsResponseSchema, data) -} - -export type HaClusterCellsData = Awaited> - -export const haClusterCellsQueryOptions = ({ projectRef }: HaClusterCellsVariables) => - queryOptions({ - queryKey: haAdminKeys.cells(projectRef), - queryFn: ({ signal }) => getHaClusterCells({ projectRef }, signal), - enabled: IS_PLATFORM && typeof projectRef !== 'undefined', - }) diff --git a/apps/studio/data/ha-admin/ha-cluster-databases-query.ts b/apps/studio/data/ha-admin/ha-cluster-databases-query.ts deleted file mode 100644 index 15cfd3adfb1..00000000000 --- a/apps/studio/data/ha-admin/ha-cluster-databases-query.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { queryOptions } from '@tanstack/react-query' -import { z } from 'zod' - -import { getHaAdmin, parseHaAdminResponse } from './get-ha-admin' -import { haAdminKeys } from './keys' -import { IS_PLATFORM } from '@/lib/constants' -import type { ResponseError } from '@/types' - -export type HaClusterDatabasesVariables = { projectRef?: string } -export type HaClusterDatabasesError = ResponseError - -// Every field is optional because proto3 JSON omits zero values. -const haClusterDatabasesResponseSchema = z.object({ names: z.array(z.string()).optional() }) - -async function getHaClusterDatabases( - { projectRef }: HaClusterDatabasesVariables, - signal?: AbortSignal -) { - const data = await getHaAdmin(projectRef, 'databases', signal) - return parseHaAdminResponse(haClusterDatabasesResponseSchema, data) -} - -export type HaClusterDatabasesData = Awaited> - -export const haClusterDatabasesQueryOptions = ({ projectRef }: HaClusterDatabasesVariables) => - queryOptions({ - queryKey: haAdminKeys.databases(projectRef), - queryFn: ({ signal }) => getHaClusterDatabases({ projectRef }, signal), - enabled: IS_PLATFORM && typeof projectRef !== 'undefined', - }) diff --git a/apps/studio/data/ha-admin/ha-cluster-poolers-query.ts b/apps/studio/data/ha-admin/ha-cluster-poolers-query.ts index b88b12cbc14..bc6a23c9d82 100644 --- a/apps/studio/data/ha-admin/ha-cluster-poolers-query.ts +++ b/apps/studio/data/ha-admin/ha-cluster-poolers-query.ts @@ -28,6 +28,15 @@ const multipoolerSchema = z.object({ // 'SERVING' | 'DISABLED' | 'DRAINING' servingStatus: z.string().optional(), hostname: z.string().optional(), + // `PoolerLifecycleStatus` (multigres proto/clustermetadata.proto): + // 'STARTING' | 'ACTIVE' | 'STOPPING' | 'SHUTDOWN' | 'QUARANTINED', optionally + // prefixed with 'LIFECYCLE_'. 'LIFECYCLE_UNKNOWN' is the zero value, so it is + // omitted from JSON. There is no dedicated failure member — QUARANTINED is the + // terminal failure state (the pooler gave up recovering, e.g. a failed + // pg_rewind or backup restore, and is kept alive for forensics) and SHUTDOWN is + // "durably down". `getPoolerStatus` maps every member. An unrecognized value + // (a future enum member) falls through to the `servingStatus` check, and since + // SERVING is that enum's zero value it usually renders as Healthy until mapped. lifecycleStatus: z.object({ status: z.string().optional() }).optional(), routingState: z .object({ diff --git a/apps/studio/data/ha-admin/keys.ts b/apps/studio/data/ha-admin/keys.ts index 3193018650a..d07096e90a5 100644 --- a/apps/studio/data/ha-admin/keys.ts +++ b/apps/studio/data/ha-admin/keys.ts @@ -1,7 +1,4 @@ export const haAdminKeys = { - cells: (projectRef: string | undefined) => ['projects', projectRef, 'ha-admin', 'cells'] as const, - databases: (projectRef: string | undefined) => - ['projects', projectRef, 'ha-admin', 'databases'] as const, poolers: (projectRef: string | undefined) => ['projects', projectRef, 'ha-admin', 'poolers'] as const, gateways: (projectRef: string | undefined) =>