Files
supabase/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.tsx
T
Joshen LimandJonathan Summers-Muir 3620ca8ebe RR Scaffolding Work (#18658)
* Init demo world map for read replicas

* Update package.json

* Scaffold and mock data for flow view of project settings infrastructure configuration

* Shift map stuff and install deck.gl to experiment

* Flesh out map view a bit more

* Scaffold deploy new replica panel

* Add restart replica option

* Add animations, shift node generation into utils, attempt to show region nodes

* Experiments

* General improvement to chart and map UI

* Improve some copy writing for restart and drop replica modals

* Simplify loading, error, success states in DatabaseSettings

* Scaffold DB selection in connection string section

* Link replica node to connection string

* Scaffold database selection for SQL editor

* Make DatabaseSelector into a component

* Add database selector to infra activity

* Update

* Hide read replicas UI behind a feature flag

* Update flags

* Update package-lock

---------

Co-authored-by: Jonathan Summers-Muir <MildTomato@users.noreply.github.com>
2023-11-27 12:50:19 +08:00

388 lines
14 KiB
TypeScript

import { useParams, useTelemetryProps } from 'common'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect, useRef, useState } from 'react'
import {
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Alert_Shadcn_,
Button,
IconAlertTriangle,
IconExternalLink,
Input,
Tabs,
} from 'ui'
import AlertError from 'components/ui/AlertError'
import DatabaseSelector from 'components/ui/DatabaseSelector'
import Panel from 'components/ui/Panel'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query'
import { useFlag, useSelectedOrganization } from 'hooks'
import { pluckObjectFields } from 'lib/helpers'
import Telemetry from 'lib/telemetry'
import { MOCK_DATABASES } from '../../Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants'
import ConfirmDisableReadOnlyModeModal from './ConfirmDisableReadOnlyModal'
import ResetDbPassword from './ResetDbPassword'
const DatabaseSettings = () => {
const router = useRouter()
const { ref: projectRef, connectionString } = useParams()
const telemetryProps = useTelemetryProps()
const organization = useSelectedOrganization()
const selectedOrganization = useSelectedOrganization()
const readReplicasEnabled = useFlag('readReplicas')
const connectionStringsRef = useRef<HTMLDivElement>(null)
const [selectedDatabaseId, setSelectedDatabaseId] = useState<string>('1')
const [showConfirmationModal, setShowConfirmationModal] = useState(false)
// [Joshen] Read replicas mock UI stuff
const [open, setOpen] = useState(false)
const databases = MOCK_DATABASES
const selectedDatabase = databases.find((db) => db.id.toString() === selectedDatabaseId)
const { data, error, isLoading, isError, isSuccess } = useProjectSettingsQuery({ projectRef })
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const { data: resourceWarnings } = useResourceWarningsQuery()
const isReadOnlyMode =
(resourceWarnings ?? [])?.find((warning) => warning.project === projectRef)
?.is_readonly_mode_enabled ?? false
const { project } = data ?? {}
const DB_FIELDS = ['db_host', 'db_name', 'db_port', 'db_user', 'inserted_at']
const connectionInfo =
project !== undefined
? pluckObjectFields(project, DB_FIELDS)
: { db_user: '', db_host: '', db_port: '', db_name: '' }
const handleCopy = (labelValue?: string) =>
Telemetry.sendEvent(
{
category: 'settings',
action: 'copy_connection_string',
label: labelValue ? labelValue : '',
},
telemetryProps,
router
)
const uriConnString =
`postgresql://${connectionInfo.db_user}:[YOUR-PASSWORD]@` +
`${connectionInfo.db_host}:${connectionInfo.db_port.toString()}` +
`/${connectionInfo.db_name}`
const golangConnString =
`user=${connectionInfo.db_user} password=[YOUR-PASSWORD] ` +
`host=${connectionInfo.db_host} port=${connectionInfo.db_port.toString()}` +
` dbname=${connectionInfo.db_name}`
const psqlConnString =
`psql -h ${connectionInfo.db_host} -p ` +
`${connectionInfo.db_port.toString()} -d ${connectionInfo.db_name} ` +
`-U ${connectionInfo.db_user}`
useEffect(() => {
if (connectionString !== undefined && connectionStringsRef.current !== undefined) {
setSelectedDatabaseId(connectionString)
connectionStringsRef.current?.scrollIntoView({ block: 'center', behavior: 'smooth' })
}
}, [connectionString])
return (
<>
<div className="space-y-10">
<section className="space-y-6">
<h3 className="text-foreground mb-2 text-xl">Database Settings</h3>
{isReadOnlyMode && (
<Alert_Shadcn_ variant="destructive">
<IconAlertTriangle />
<AlertTitle_Shadcn_>
Project is in read-only mode and database is no longer accepting write requests
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
You have reached 95% of your project's disk space, and read-only mode has been
enabled to preserve your database's stability and prevent your project from
exceeding its current billing plan. To resolve this, you may:
<ul className="list-disc pl-6 mt-1">
<li>
Temporarily disable read-only mode to free up space and reduce your database
size
</li>
{subscription?.plan.id === 'free' ? (
<li>
<Link
href={`/org/${selectedOrganization?.slug}/billing?panel=subscriptionPlan`}
>
<a className="text underline">Upgrade to the Pro plan</a>
</Link>{' '}
to increase your database size limit to 8GB.
</li>
) : subscription?.plan.id === 'pro' && subscription?.usage_billing_enabled ? (
<li>
<Link
href={`/org/${selectedOrganization?.slug}/billing?panel=subscriptionPlan`}
>
<a className="text-foreground underline">Disable your Spend Cap</a>
</Link>{' '}
to allow your project to auto-scale and expand beyond the 8GB database size
limit
</li>
) : null}
</ul>
</AlertDescription_Shadcn_>
<div className="mt-4 flex items-center space-x-2">
<Button type="default" onClick={() => setShowConfirmationModal(true)}>
Disable read-only mode
</Button>
<Button asChild type="default" icon={<IconExternalLink />}>
<Link
href="https://supabase.com/docs/guides/platform/database-size#disabling-read-only-mode"
target="_blank"
rel="noreferrer"
>
Learn more
</Link>
</Button>
</div>
</Alert_Shadcn_>
)}
<Panel
title={
<h5 key="panel-title" className="mb-0">
Connection info
</h5>
}
className="!m-0"
>
<Panel.Content className="space-y-6">
{isLoading &&
Array.from({ length: 5 }).map((_, i) => (
<div
key={i}
className="grid gap-2 items-center md:grid md:grid-cols-12 md:gap-x-4 w-full"
>
<ShimmeringLoader className="h-4 w-1/3 col-span-4" delayIndex={i} />
<ShimmeringLoader className="h-8 w-full col-span-8" delayIndex={i} />
</div>
))}
{isError && (
<AlertError error={error} subject="Failed to retrieve database settings" />
)}
{isSuccess && (
<>
<Input
className="input-mono"
layout="horizontal"
readOnly
copy
disabled
value={connectionInfo.db_host}
label="Host"
onCopy={() => {
handleCopy('Host')
}}
/>
<Input
className="input-mono"
layout="horizontal"
readOnly
copy
disabled
value={connectionInfo.db_name}
label="Database name"
/>
<Input
className="input-mono"
layout="horizontal"
readOnly
copy
disabled
value={connectionInfo.db_port.toString()}
label="Port"
/>
<Input
layout="horizontal"
className="input-mono table-input-cell text-base"
readOnly
copy
disabled
value={connectionInfo.db_user}
label="User"
/>
<Input
className="input-mono"
layout="horizontal"
disabled
readOnly
value={'[The password you provided when you created this project]'}
label="Password"
/>
</>
)}
</Panel.Content>
</Panel>
</section>
<ResetDbPassword disabled={isLoading || isError} />
<section className="space-y-6">
<Panel
title={
<div ref={connectionStringsRef} className="w-full flex items-center justify-between">
<h5 key="panel-title" className="mb-0">
Connection string
</h5>
{readReplicasEnabled && (
<DatabaseSelector
selectedDatabaseId={selectedDatabaseId}
onChangeDatabaseId={setSelectedDatabaseId}
/>
)}
</div>
}
className="!m-0"
>
<Panel.Content>
{isLoading && <ShimmeringLoader className="h-8 w-full" />}
{isError && (
<AlertError error={error} subject="Failed to retrieve database settings" />
)}
{isSuccess && (
<Tabs type="underlined" size="small">
<Tabs.Panel id="psql" label="PSQL">
<Input
copy
readOnly
disabled
value={psqlConnString}
onCopy={() => {
handleCopy('PSQL')
}}
/>
</Tabs.Panel>
<Tabs.Panel id="uri" label="URI">
<Input
copy
readOnly
disabled
value={uriConnString}
onCopy={() => {
handleCopy('URI')
}}
/>
</Tabs.Panel>
<Tabs.Panel id="golang" label="Golang">
<Input
copy
readOnly
disabled
value={golangConnString}
onCopy={() => {
handleCopy('Golang')
}}
/>
</Tabs.Panel>
<Tabs.Panel id="jdbc" label="JDBC">
<Input
copy
readOnly
disabled
value={
`jdbc:postgresql://${
connectionInfo.db_host
}:${connectionInfo.db_port.toString()}` +
`/${connectionInfo.db_name}?user=${connectionInfo.db_user}&password=[YOUR-PASSWORD]`
}
onCopy={() => {
handleCopy('JDBC')
}}
/>
</Tabs.Panel>
<Tabs.Panel id="dotnet" label=".NET">
<Input
copy
readOnly
disabled
value={
`User Id=${connectionInfo.db_user};Password=[YOUR-PASSWORD];` +
`Server=${
connectionInfo.db_host
};Port=${connectionInfo.db_port.toString()};` +
`Database=${connectionInfo.db_name}`
}
onCopy={() => {
handleCopy('.NET')
}}
/>
</Tabs.Panel>
<Tabs.Panel id="nodejs" label="Nodejs">
<Input
copy
readOnly
disabled
value={uriConnString}
onCopy={() => {
handleCopy('Nodejs')
}}
/>
</Tabs.Panel>
<Tabs.Panel id="php" label="PHP">
<Input
copy
readOnly
disabled
value={golangConnString}
onCopy={() => {
handleCopy('PHP')
}}
/>
</Tabs.Panel>
<Tabs.Panel id="python" label="Python">
<Input
copy
readOnly
disabled
value={
`user=${connectionInfo.db_user} password=[YOUR-PASSWORD]` +
` host=${
connectionInfo.db_host
} port=${connectionInfo.db_port.toString()}` +
` database=${connectionInfo.db_name}`
}
onCopy={() => {
handleCopy('Python')
}}
/>
</Tabs.Panel>
</Tabs>
)}
</Panel.Content>
</Panel>
</section>
</div>
<ConfirmDisableReadOnlyModeModal
visible={showConfirmationModal}
onClose={() => setShowConfirmationModal(false)}
/>
</>
)
}
export default DatabaseSettings