mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 02:15:05 +03:00
Merge pull request #12587 from supabase/chore/read-only-db-alert
Chore/read only db alert
This commit is contained in:
3 files changed
+201
-21
No files matched your search
@@ -1,8 +1,18 @@
|
||||
import { FC, useEffect } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { Badge, Button, IconAlertCircle, IconInfo, Loading, IconExternalLink } from 'ui'
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
IconAlertCircle,
|
||||
IconInfo,
|
||||
Loading,
|
||||
IconExternalLink,
|
||||
Alert,
|
||||
IconBookOpen,
|
||||
} from 'ui'
|
||||
|
||||
import { checkPermissions, useStore } from 'hooks'
|
||||
import { formatBytes } from 'lib/helpers'
|
||||
@@ -11,6 +21,8 @@ import SparkBar from 'components/ui/SparkBar'
|
||||
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import InformationBox from 'components/ui/InformationBox'
|
||||
import { USAGE_BASED_PRODUCTS } from 'components/interfaces/Billing/Billing.constants'
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { ProjectUsageResponseUsageKeys, useProjectUsageQuery } from 'data/usage/project-usage-query'
|
||||
|
||||
interface Props {
|
||||
@@ -22,13 +34,14 @@ const ProjectUsage: FC<Props> = ({ projectRef }) => {
|
||||
const { data: usage, error, isLoading } = useProjectUsageQuery({ projectRef })
|
||||
const router = useRouter()
|
||||
|
||||
const { project } = useProjectContext()
|
||||
const [isReadOnlyMode, setIsReadOnlyMode] = useState(false)
|
||||
const canUpdateSubscription = checkPermissions(
|
||||
PermissionAction.BILLING_WRITE,
|
||||
'stripe.subscriptions'
|
||||
)
|
||||
|
||||
const subscriptionTier = ui.selectedProject?.subscription_tier
|
||||
|
||||
const projectHasNoLimits =
|
||||
subscriptionTier === PRICING_TIER_PRODUCT_IDS.PAYG ||
|
||||
subscriptionTier === PRICING_TIER_PRODUCT_IDS.TEAM ||
|
||||
@@ -65,11 +78,25 @@ const ProjectUsage: FC<Props> = ({ projectRef }) => {
|
||||
/>
|
||||
)
|
||||
}
|
||||
// [Terry]
|
||||
// temporary solution to check if project is in read only mode
|
||||
// until we get an api endpoint for this
|
||||
|
||||
async function checkForReadOnlyMode() {
|
||||
const sql = `show default_transaction_read_only;`
|
||||
const connectionString = project?.connectionString
|
||||
const { result: readOnlyStatus } = await executeSql({ projectRef, connectionString, sql })
|
||||
if (readOnlyStatus[0]?.default_transaction_read_only === 'on') setIsReadOnlyMode(true)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (projectRef) checkForReadOnlyMode()
|
||||
}, [projectRef])
|
||||
|
||||
const isPaidTier = subscriptionTier !== PRICING_TIER_PRODUCT_IDS.FREE
|
||||
|
||||
const featureFootnotes: Record<string, JSX.Element> = {
|
||||
db_size: (
|
||||
db_size: isPaidTier ? (
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex flex-row space-x-4 text-scale-1000">
|
||||
{usage?.disk_volume_size_gb && <span>Disk Size: {usage.disk_volume_size_gb} GB</span>}
|
||||
@@ -83,6 +110,8 @@ const ProjectUsage: FC<Props> = ({ projectRef }) => {
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
),
|
||||
}
|
||||
|
||||
@@ -133,7 +162,6 @@ const ProjectUsage: FC<Props> = ({ projectRef }) => {
|
||||
<th className="p-3 text-xs font-medium leading-4 text-left text-gray-400" />
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{/* Line items */}
|
||||
{usage === undefined ? (
|
||||
<div className="px-4 pt-1 pb-4 w-96">
|
||||
@@ -261,6 +289,121 @@ const ProjectUsage: FC<Props> = ({ projectRef }) => {
|
||||
</tbody>
|
||||
)}
|
||||
</table>
|
||||
|
||||
{!isReadOnlyMode && product.title === 'Database' && (
|
||||
<div className="p-6">
|
||||
<Alert title={'Database is in read-only mode'} variant="danger" withIcon>
|
||||
<p>
|
||||
{isPaidTier ? (
|
||||
<>
|
||||
Your disk has reached 95% capacity and has entered{' '}
|
||||
<a
|
||||
href={`https://supabase.com/docs/guides/platform/database-usage#paid-tier-disk-auto-scaling`}
|
||||
className="underline transition hover:text-scale-1200"
|
||||
>
|
||||
read-only mode
|
||||
</a>
|
||||
.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
You have exceeded the 500mb Database size limit and your project is now
|
||||
in{' '}
|
||||
<a
|
||||
href={`https://supabase.com/docs/guides/platform/database-usage#free-tier-project-read-only-mode`}
|
||||
className="underline transition hover:text-scale-1200"
|
||||
>
|
||||
read-only mode
|
||||
</a>
|
||||
.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
{isPaidTier ? (
|
||||
<>
|
||||
<p>
|
||||
For Pro and Enterprise projects,{' '}
|
||||
<a
|
||||
href="https://supabase.com/docs/guides/platform/database-usage#paid-tier-disk-auto-scaling"
|
||||
className="underline transition hover:text-scale-1200"
|
||||
>
|
||||
Disk Size expands automatically
|
||||
</a>{' '}
|
||||
when it reaches 90% capacity, but can only occur once every six hours.
|
||||
If the disk size has already expanded and then reaches 95% capacity
|
||||
within 6 hours, then{' '}
|
||||
<a
|
||||
href="https://supabase.com/docs/guides/platform/database-usage#paid-tier-project-read-only-mode"
|
||||
className="underline transition hover:text-scale-1200"
|
||||
>
|
||||
your disk will enter read-only mode
|
||||
</a>{' '}
|
||||
until it can resize again after 6 hours.
|
||||
</p>
|
||||
<p className="mt-2">
|
||||
If you require help or need your disk changed to read/write mode so you
|
||||
can delete data,{' '}
|
||||
<Link
|
||||
href={`/support/new?ref=${
|
||||
projectRef ? projectRef : ''
|
||||
}&category=Database_unresponsive&Subject=Read%20only%20mode%20issue`}
|
||||
>
|
||||
<a className="underline transition hover:text-scale-1200">
|
||||
you can contact the support team
|
||||
</a>
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<p>
|
||||
You can either{' '}
|
||||
<a
|
||||
href="https://supabase.com/docs/guides/platform/database-usage#increasing-available-disk-size"
|
||||
className="underline transition hover:text-scale-1200"
|
||||
>
|
||||
reduce your disk usage below 500mb
|
||||
</a>{' '}
|
||||
or{' '}
|
||||
<Link
|
||||
href={`/project/${
|
||||
projectRef ? projectRef : ''
|
||||
}/settings/billing/subscription`}
|
||||
>
|
||||
<a className="underline transition hover:text-scale-1200">
|
||||
upgrade your project
|
||||
</a>
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{!isPaidTier ? (
|
||||
<Button type="danger" className="mt-3">
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://supabase.com/docs/guides/platform/database-usage#database-storage-management"
|
||||
>
|
||||
Upgrade this project
|
||||
</a>
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="danger"
|
||||
icon={<IconBookOpen size={14} strokeWidth={1.5} />}
|
||||
className="mt-3"
|
||||
>
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://supabase.com/docs/guides/platform/database-usage#database-storage-management"
|
||||
>
|
||||
Database storage management docs
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -11,19 +11,43 @@ import HelpPopover from './HelpPopover'
|
||||
import NotificationsPopover from './NotificationsPopover'
|
||||
import { getResourcesExceededLimits } from 'components/ui/OveragesBanner/OveragesBanner.utils'
|
||||
import { useProjectUsageQuery } from 'data/usage/project-usage-query'
|
||||
import { Badge } from 'ui'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
const LayoutHeader = ({ customHeaderComponents, breadcrumbs = [], headerBorder = true }: any) => {
|
||||
const { ui } = useStore()
|
||||
const { selectedOrganization, selectedProject } = ui
|
||||
|
||||
const { ref: projectRef } = useParams()
|
||||
const { project } = useProjectContext()
|
||||
|
||||
const [isReadOnlyMode, setIsReadOnlyMode] = useState(false)
|
||||
|
||||
// [Terry]
|
||||
// temporary solution to check if project is in read only mode
|
||||
// until we get an api endpoint for this
|
||||
async function checkForReadOnlyMode() {
|
||||
const sql = `show default_transaction_read_only;`
|
||||
const connectionString = project?.connectionString
|
||||
const { result: readOnlyStatus } = await executeSql({ projectRef, connectionString, sql })
|
||||
if (readOnlyStatus[0]?.default_transaction_read_only === 'on') setIsReadOnlyMode(true)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (projectRef) checkForReadOnlyMode()
|
||||
}, [projectRef])
|
||||
|
||||
const { data: usage } = useProjectUsageQuery({ projectRef })
|
||||
const resourcesExceededLimits = getResourcesExceededLimits(usage)
|
||||
|
||||
const projectHasNoLimits =
|
||||
ui.selectedProject?.subscription_tier === PRICING_TIER_PRODUCT_IDS.PAYG ||
|
||||
ui.selectedProject?.subscription_tier === PRICING_TIER_PRODUCT_IDS.ENTERPRISE ||
|
||||
ui.selectedProject?.subscription_tier === PRICING_TIER_PRODUCT_IDS.TEAM
|
||||
|
||||
const showOverUsageBadge =
|
||||
selectedProject?.subscription_tier !== undefined &&
|
||||
!projectHasNoLimits &&
|
||||
@@ -63,6 +87,18 @@ const LayoutHeader = ({ customHeaderComponents, breadcrumbs = [], headerBorder =
|
||||
{/* Project Dropdown */}
|
||||
<ProjectDropdown />
|
||||
|
||||
{/* [Terry] Temporary until we figure out how we want to display this permanently */}
|
||||
{/* context: https://www.notion.so/supabase/DB-Disk-Size-Free-tier-Read-only-Critical-f2b8937c13a149e3ac769fe5888f6db0*/}
|
||||
{!isReadOnlyMode && (
|
||||
<div className="ml-2">
|
||||
<Link href={`/project/${projectRef}/settings/billing/usage`}>
|
||||
<a>
|
||||
<Badge color="red">Project is in read-only mode</Badge>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* [Joshen TODO] Temporarily hidden until usage endpoint is sorted out */}
|
||||
{/* {showOverUsageBadge && (
|
||||
<div className="ml-2">
|
||||
|
||||
@@ -103,24 +103,25 @@ const DatabaseUsage: FC<any> = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center mt-3">
|
||||
<div className="flex flex-row space-x-3 text-scale-1000 text-sm">
|
||||
{usage?.disk_volume_size_gb && (
|
||||
<span>Disk Size: {usage.disk_volume_size_gb} GB</span>
|
||||
)}
|
||||
{isPaidTier && (
|
||||
<div className="flex justify-between items-center mt-3">
|
||||
<div className="flex flex-row space-x-3 text-scale-1000 text-sm">
|
||||
{usage?.disk_volume_size_gb && (
|
||||
<span>Disk Size: {usage.disk_volume_size_gb} GB</span>
|
||||
)}
|
||||
<Badge>Auto-Scaling</Badge>
|
||||
</div>
|
||||
|
||||
{isPaidTier && <Badge>Auto-Scaling</Badge>}
|
||||
<Button type="default" icon={<IconExternalLink size={14} strokeWidth={1.5} />}>
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://supabase.com/docs/guides/platform/database-usage"
|
||||
>
|
||||
What is disk size?
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button type="default" icon={<IconExternalLink size={14} strokeWidth={1.5} />}>
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://supabase.com/docs/guides/platform/database-usage"
|
||||
>
|
||||
What is disk size?
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Panel.Content>
|
||||
<Panel.Content>
|
||||
<div className="space-y-1">
|
||||
|
||||
Reference in new issue
Block a user