Files
Stephen MorganandJoshen Lim 9dc5c0faf3 feat: privatelink read replica (#48642)
Frontend changes for new privatelink functionality.
The updates to the API are already live, but will be putting the UI
changes behind a feature flag while we do some full end to end testing.

Changes to Integration page:
<img width="744" height="456" alt="image"
src="https://github.com/user-attachments/assets/02026083-4b61-4f36-927b-1bac42cb73e2"
/>

Changes to create associations page:
<img width="859" height="411" alt="image"
src="https://github.com/user-attachments/assets/303b4ec5-3b5a-4eaf-8d22-45e1e63a0705"
/>

Fixes SEC-919

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

- **New Features**
- AWS PrivateLink connections can target the primary database or a read
replica.
- Connection lists and confirmation dialogs now identify the selected
database clearly.
- Replica details include improved status information and a “Manage
replica” link.

- **Bug Fixes**
- Replica removal errors now provide clearer guidance and link to
integration settings when PrivateLink issues occur.

- **Updates**
- Infrastructure diagrams focus on visualization, with replica
management actions moved to dedicated management views.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-08-04 20:47:48 +12:00

180 lines
6.8 KiB
TypeScript

import { useParams } from 'common'
import { BarChart2 } from 'lucide-react'
import { useMemo } from 'react'
import { AWS_REGIONS } from 'shared-data'
import { Card, CardContent, CardHeader, CardTitle } from 'ui'
import {
Chart,
ChartCard,
ChartContent,
ChartEmptyState,
ChartHeader,
ChartLine,
ChartLoadingState,
ChartMetric,
} from 'ui-patterns/Chart'
import { Input } from 'ui-patterns/DataInputs/Input'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { REPLICA_STATUS } from '../Replication.constants'
import { REPORT_DATERANGE_HELPER_LABELS } from '@/components/interfaces/Reports/Reports.constants'
import { ScaffoldContainer, ScaffoldSection } from '@/components/layouts/Scaffold'
import { useInfraMonitoringAttributesQuery } from '@/data/analytics/infra-monitoring-query'
import { useLoadBalancersQuery } from '@/data/read-replicas/load-balancers-query'
import { useReplicationLagQuery } from '@/data/read-replicas/replica-lag-query'
import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
import { useReadReplicasStatusesQuery } from '@/data/read-replicas/replicas-status-query'
import { useReportDateRange } from '@/hooks/misc/useReportDateRange'
import { BASE_PATH } from '@/lib/constants'
const attribute = 'physical_replication_lag_physical_replication_lag_seconds'
export const ReadReplicaDetails = () => {
const { ref: projectRef, replicaId } = useParams()
const { data = [], isPending: isLoadingDatabases } = useReadReplicasQuery({ projectRef })
const replica = data.find((x) => x.identifier === replicaId)
const { identifier, connectionString, status: baseStatus, restUrl, region, size } = replica ?? {}
const regionLabel = Object.values(AWS_REGIONS).find((x) => x.code === region)?.displayName
const { data: statuses = [] } = useReadReplicasStatusesQuery({ projectRef })
const replicaStatus = statuses.find((x) => x.identifier === identifier)
const status = replicaStatus?.status ?? baseStatus
const { data: loadBalancers = [] } = useLoadBalancersQuery({ projectRef })
const loadBalancer = loadBalancers.find((x) =>
x.databases.some((x) => x.identifier === identifier)
)
const { data: lagDuration, isPending: isLoadingLag } = useReplicationLagQuery(
{
id: identifier ?? '',
projectRef,
connectionString,
},
{ enabled: status === REPLICA_STATUS.ACTIVE_HEALTHY }
)
const { selectedDateRange } = useReportDateRange(REPORT_DATERANGE_HELPER_LABELS.LAST_60_MINUTES)
// [Joshen] This is unused but intentional to scaffold the usage for now, refer to comment below
const { data: infraMonitoringData, isPending: isFetchingInfraMonitoring } =
useInfraMonitoringAttributesQuery(
{
projectRef,
attributes: [attribute],
databaseIdentifier: identifier,
startDate: selectedDateRange.period_start.date,
endDate: selectedDateRange.period_end.date,
interval: selectedDateRange.interval,
},
{ enabled: !!replica }
)
const chartData = useMemo(
() =>
infraMonitoringData?.data.map((x) => ({
timestamp: x.period_start,
[attribute]: (x as Record<string, string | number>)[attribute],
})) ?? [],
[infraMonitoringData?.data]
)
return (
<>
<Chart className="mt-6" isLoading={isLoadingLag || isFetchingInfraMonitoring}>
<ChartCard className="rounded-none border-x-0">
<ChartHeader className="px-10">
<ChartMetric
label="Replication lag"
value={lagDuration !== undefined ? `${lagDuration}s` : '-'}
/>
</ChartHeader>
<ChartContent
isEmpty={chartData.length === 0}
emptyState={
<ChartEmptyState
icon={<BarChart2 size={16} />}
title="No data to show"
description="It may take up to 24 hours for data to refresh"
/>
}
loadingState={<ChartLoadingState className="h-[228px]" />}
>
<div className="h-56 px-5">
<ChartLine
showGrid
showYAxis
isFullHeight
data={chartData}
dataKey={attribute}
YAxisProps={{
tickFormatter: (value) => `${value}s`,
width: 80,
}}
config={{
[attribute]: { label: 'Replication lag' },
}}
/>
</div>
</ChartContent>
</ChartCard>
</Chart>
<ScaffoldContainer>
<ScaffoldSection isFullWidth>
<Card>
<CardHeader>
<CardTitle>Replica Information</CardTitle>
</CardHeader>
{isLoadingDatabases ? (
<CardContent>
<GenericSkeletonLoader />
</CardContent>
) : (
<>
<CardContent>
<FormItemLayout
isReactForm={false}
layout="horizontal"
label="Load Balancer URL"
description="RESTful endpoint for querying and managing your databases through your load balancer."
>
<Input readOnly copy className="input-mono" value={loadBalancer?.endpoint} />
</FormItemLayout>
</CardContent>
<CardContent className="flex flex-col gap-y-4">
<FormItemLayout isReactForm={false} layout="horizontal" label="Replica URL">
<Input readOnly copy className="input-mono" value={restUrl} />
</FormItemLayout>
<FormItemLayout isReactForm={false} layout="horizontal" label="Region">
<Input
readOnly
className="input-mono"
value={regionLabel}
icon={
<img
alt="region icon"
className="w-5 rounded-xs"
src={`${BASE_PATH}/img/regions/${region ?? ''}.svg`}
/>
}
/>
</FormItemLayout>
<FormItemLayout
isReactForm={false}
layout="horizontal"
label="Compute Size"
description="Size of replica will be identical to the primary database."
>
<Input readOnly className="input-mono" value={size} />
</FormItemLayout>
</CardContent>
</>
)}
</Card>
</ScaffoldSection>
</ScaffoldContainer>
</>
)
}