ReadReplicaRow.tsx160 lines · main
1import { useParams } from 'common'
2import { Database as DatabaseIcon } from 'icons'
3import { Loader2, Minus, MoreVertical, RotateCcw, Trash } from 'lucide-react'
4import Link from 'next/link'
5import { useMemo, useState } from 'react'
6import { AWS_REGIONS } from 'shared-data'
7import {
8 Badge,
9 Button,
10 DropdownMenu,
11 DropdownMenuContent,
12 DropdownMenuItem,
13 DropdownMenuSeparator,
14 DropdownMenuTrigger,
15 TableCell,
16 TableRow,
17 Tooltip,
18 TooltipContent,
19 TooltipTrigger,
20} from 'ui'
21import { ShimmeringLoader } from 'ui-patterns'
22
23import { getIsInTransition, getStatusLabel } from './ReadReplicas.utils'
24import { DropReplicaConfirmationModal } from '@/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropReplicaConfirmationModal'
25import { REPLICA_STATUS } from '@/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants'
26import { RestartReplicaConfirmationModal } from '@/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/RestartReplicaConfirmationModal'
27import { useReplicationLagQuery } from '@/data/read-replicas/replica-lag-query'
28import { type Database } from '@/data/read-replicas/replicas-query'
29import { formatDatabaseID } from '@/data/read-replicas/replicas.utils'
30
31interface ReadReplicaRow {
32 replica: Database
33 onUpdateReplica: () => void
34}
35
36export const ReadReplicaRow = ({ replica, onUpdateReplica }: ReadReplicaRow) => {
37 const { ref } = useParams()
38 const { identifier, region, status } = replica
39 const formattedId = formatDatabaseID(identifier ?? '')
40
41 const {
42 data: lagDuration,
43 isPending: isLoadingLag,
44 isError: isErrorLag,
45 } = useReplicationLagQuery(
46 {
47 id: identifier,
48 projectRef: ref,
49 connectionString: replica.connectionString,
50 },
51 { enabled: status === REPLICA_STATUS.ACTIVE_HEALTHY }
52 )
53
54 const [showConfirmRestart, setShowConfirmRestart] = useState(false)
55 const [showConfirmDrop, setShowConfirmDrop] = useState(false)
56
57 const regionMeta = Object.values(AWS_REGIONS).find((x) => x.code === region)
58
59 const isInTransition = useMemo(() => getIsInTransition({ status }), [status])
60 const statusLabel = useMemo(() => getStatusLabel({ status }), [status])
61
62 return (
63 <>
64 <TableRow>
65 <TableCell>
66 <DatabaseIcon size={18} className="text-foreground-light" />
67 </TableCell>
68
69 <TableCell>
70 <div>
71 <p>Read Replica (ID: {formattedId})</p>
72 <Tooltip>
73 <TooltipTrigger asChild>
74 <p className="text-foreground-lighter w-fit">{regionMeta?.displayName}</p>
75 </TooltipTrigger>
76 <TooltipContent side="right">{regionMeta?.code}</TooltipContent>
77 </Tooltip>
78 </div>
79 </TableCell>
80
81 <TableCell>
82 <div className="flex items-center gap-x-2">
83 <Badge
84 variant={
85 statusLabel === 'Healthy'
86 ? 'success'
87 : statusLabel === 'Failed'
88 ? 'destructive'
89 : 'default'
90 }
91 >
92 {statusLabel}
93 </Badge>
94 {isInTransition && <Loader2 className="animate-spin w-3 h-3" />}
95 </div>
96 </TableCell>
97
98 <TableCell>
99 {isErrorLag || status !== REPLICA_STATUS.ACTIVE_HEALTHY ? (
100 <Minus size={18} className="text-foreground-lighter" />
101 ) : isLoadingLag ? (
102 <ShimmeringLoader />
103 ) : (
104 <p>{lagDuration}s</p>
105 )}
106 </TableCell>
107
108 <TableCell>
109 <Minus size={18} className="text-foreground-lighter" />
110 </TableCell>
111
112 <TableCell>
113 <div className="flex items-center justify-end gap-x-2">
114 <Button asChild type="default" className="relative" disabled={status === 'GOING_DOWN'}>
115 <Link href={`/project/${ref}/database/replication/replica/${replica.identifier}`}>
116 View replication
117 </Link>
118 </Button>
119 <DropdownMenu>
120 <DropdownMenuTrigger>
121 <Button type="default" icon={<MoreVertical />} className="w-7" />
122 </DropdownMenuTrigger>
123 <DropdownMenuContent align="end" className="w-52">
124 <DropdownMenuItem
125 className="gap-x-2"
126 disabled={status !== 'ACTIVE_HEALTHY'}
127 onClick={() => setShowConfirmRestart(true)}
128 >
129 <RotateCcw size={14} />
130 <span>Restart replica</span>
131 </DropdownMenuItem>
132 <DropdownMenuSeparator />
133 <DropdownMenuItem
134 className="gap-x-2"
135 disabled={status === 'GOING_DOWN'}
136 onClick={() => setShowConfirmDrop(true)}
137 >
138 <Trash size={14} />
139 <span>Drop replica</span>
140 </DropdownMenuItem>
141 </DropdownMenuContent>
142 </DropdownMenu>
143 </div>
144 </TableCell>
145 </TableRow>
146
147 <DropReplicaConfirmationModal
148 selectedReplica={showConfirmDrop ? replica : undefined}
149 onSuccess={() => onUpdateReplica()}
150 onCancel={() => setShowConfirmDrop(false)}
151 />
152
153 <RestartReplicaConfirmationModal
154 selectedReplica={showConfirmRestart ? replica : undefined}
155 onSuccess={() => onUpdateReplica()}
156 onCancel={() => setShowConfirmRestart(false)}
157 />
158 </>
159 )
160}