mirror of
https://github.com/supabase/supabase.git
synced 2026-10-15 06:09:08 +03:00
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>
180 lines
6.8 KiB
TypeScript
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>
|
|
</>
|
|
)
|
|
}
|