diff --git a/apps/studio/components/interfaces/Database/Backups/DatabaseBackupsNav.tsx b/apps/studio/components/interfaces/Database/Backups/DatabaseBackupsNav.tsx
index f0fe0b1d5e4..70a5894d1ff 100644
--- a/apps/studio/components/interfaces/Database/Backups/DatabaseBackupsNav.tsx
+++ b/apps/studio/components/interfaces/Database/Backups/DatabaseBackupsNav.tsx
@@ -1,36 +1,56 @@
+import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
+import { useFlag } from 'hooks/ui/useFlag'
import Link from 'next/link'
import React from 'react'
-import { useParams } from 'common'
-import { NavMenu, NavMenuItem } from 'ui'
+import { Badge, NavMenu, NavMenuItem } from 'ui'
type Props = {
- active: 'scheduled' | 'pitr'
+ active: 'pitr' | 'scheduled' | 'rtnp'
}
function DatabaseBackupsNav({ active }: Props) {
- const { ref } = useParams()
+ const isCloneToNewProjectEnabled = useFlag('clonetonewproject')
+ const ref = useProjectContext()?.project?.ref
const navMenuItems = [
{
+ enabled: true,
id: 'scheduled',
label: 'Scheduled backups',
href: `/project/${ref}/database/backups/scheduled`,
},
{
+ enabled: true,
id: 'pitr',
label: 'Point in time',
href: `/project/${ref}/database/backups/pitr`,
},
- ]
+ {
+ enabled: isCloneToNewProjectEnabled,
+ id: 'rtnp',
+ label: (
+
+ Restore to new project{' '}
+
+ New
+
+
+ ),
+ href: `/project/${ref}/database/backups/restore-to-new-project`,
+ },
+ ] as const
return (
- {navMenuItems.map((item) => (
-
- {item.label}
-
- ))}
+ {navMenuItems.map(
+ (item) =>
+ item.enabled && (
+
+ {item.label}
+
+ )
+ )}
)
}
diff --git a/apps/studio/components/interfaces/Database/Backups/PITR/PITRSelection.tsx b/apps/studio/components/interfaces/Database/Backups/PITR/PITRSelection.tsx
index c5af3d64a9c..bfc8fb9a35b 100644
--- a/apps/studio/components/interfaces/Database/Backups/PITR/PITRSelection.tsx
+++ b/apps/studio/components/interfaces/Database/Backups/PITR/PITRSelection.tsx
@@ -1,23 +1,14 @@
-import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useQueryClient } from '@tanstack/react-query'
-import { format } from 'date-fns'
-import dayjs from 'dayjs'
-import { ChevronLeft, ChevronRight, HelpCircle } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useState } from 'react'
-import DatePicker from 'react-datepicker'
import { useParams } from 'common'
-import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { FormHeader } from 'components/ui/Forms/FormHeader'
-import { FormPanel } from 'components/ui/Forms/FormPanel'
-import InformationBox from 'components/ui/InformationBox'
import { useBackupsQuery } from 'data/database/backups-query'
import { usePitrRestoreMutation } from 'data/database/pitr-restore-mutation'
import { setProjectStatus } from 'data/projects/projects-query'
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
-import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { PROJECT_STATUS } from 'lib/constants'
import {
Alert,
@@ -31,15 +22,9 @@ import {
import BackupsEmpty from '../BackupsEmpty'
import BackupsStorageAlert from '../BackupsStorageAlert'
import type { Timezone } from './PITR.types'
-import {
- constrainDateToRange,
- formatNumberToTwoDigits,
- getClientTimezone,
- getDatesBetweenRange,
-} from './PITR.utils'
+import { getClientTimezone } from './PITR.utils'
import PITRStatus from './PITRStatus'
-import TimeInput from './TimeInput'
-import { TimezoneSelection } from './TimezoneSelection'
+import { PITRForm } from './pitr-form'
const PITRSelection = () => {
const router = useRouter()
@@ -51,11 +36,11 @@ const PITRSelection = () => {
const [showConfiguration, setShowConfiguration] = useState(false)
const [showConfirmation, setShowConfirmation] = useState(false)
const [selectedTimezone, setSelectedTimezone] = useState(getClientTimezone())
-
- const canTriggerPhysicalBackups = useCheckPermissions(
- PermissionAction.INFRA_EXECUTE,
- 'queue_job.restore.prepare'
- )
+ const [selectedRecoveryPoint, setSelectedRecoveryPoint] = useState<{
+ recoveryTimeTargetUnix: number
+ recoveryTimeString: string
+ recoveryTimeStringUtc: string
+ }>()
const hasReadReplicas = (databases ?? []).length > 1
@@ -76,67 +61,16 @@ const PITRSelection = () => {
const { earliestPhysicalBackupDateUnix, latestPhysicalBackupDateUnix } =
backups?.physicalBackupData ?? {}
const hasNoBackupsAvailable = !earliestPhysicalBackupDateUnix || !latestPhysicalBackupDateUnix
- const earliestAvailableBackup = dayjs
- .unix(earliestPhysicalBackupDateUnix ?? 0)
- .tz(selectedTimezone.utc[0])
- const latestAvailableBackup = dayjs
- .unix(latestPhysicalBackupDateUnix ?? 0)
- .tz(selectedTimezone.utc[0])
-
- const [selectedDateRaw, setSelectedDateRaw] = useState(latestAvailableBackup.toDate())
- const selectedDate = dayjs(selectedDateRaw).tz(selectedTimezone.utc[0], true) // true to keep local time and just change +whatever
-
- const isSelectedOnEarliestDay = selectedDate.isSame(earliestAvailableBackup, 'day')
- const isSelectedOnLatestDay = selectedDate.isSame(latestAvailableBackup, 'day')
- const availableDates = getDatesBetweenRange(earliestAvailableBackup, latestAvailableBackup)
-
- const selectedTime = {
- h: selectedDate.hour(),
- m: selectedDate.minute(),
- s: selectedDate.second(),
- }
-
- const earliestAvailableBackupTime = {
- h: earliestAvailableBackup.hour(),
- m: earliestAvailableBackup.minute(),
- s: earliestAvailableBackup.second(),
- }
-
- const latestAvailableBackupTime = {
- h: latestAvailableBackup.hour(),
- m: latestAvailableBackup.minute(),
- s: latestAvailableBackup.second(),
- }
-
- // This will be the actual unix timestamp for the backup
- const recoveryTimeTargetUnix = selectedDate.unix()
- // Formatting from the unix again just to double check correctness
- const recoveryTimeString = selectedDate.format('DD MMM YYYY HH:mm:ss')
- const recoveryTimeStringUtc = selectedDate.utc().format('DD MMM YYYY HH:mm:ss')
-
- const isSelectedOutOfRange =
- selectedDate &&
- (selectedDate.isBefore(earliestAvailableBackup) || selectedDate.isAfter(latestAvailableBackup))
-
- const onUpdateDate = (date: Date) => {
- setSelectedDateRaw(
- constrainDateToRange(
- dayjs(date).tz(selectedTimezone.utc[0], true),
- earliestAvailableBackup,
- latestAvailableBackup
- ).toDate()
- )
- }
-
- const onCancel = () => {
- setShowConfiguration(false)
- setSelectedTimezone(getClientTimezone())
- }
const onConfirmRestore = async () => {
if (!ref) return console.error('Project ref is required')
- if (!recoveryTimeTargetUnix) return console.error('Recovery time target unix is required')
- restoreFromPitr({ ref, recovery_time_target_unix: recoveryTimeTargetUnix })
+ if (!selectedRecoveryPoint?.recoveryTimeTargetUnix)
+ return console.error('Recovery time target unix is required')
+
+ restoreFromPitr({
+ ref,
+ recovery_time_target_unix: selectedRecoveryPoint.recoveryTimeTargetUnix,
+ })
}
return (
@@ -177,173 +111,14 @@ const PITRSelection = () => {
onSetConfiguration={() => setShowConfiguration(true)}
/>
) : (
-
-
- Cancel
-
-
- setShowConfirmation(true)}
- tooltip={{
- content: {
- side: 'bottom',
- text: isSelectedOutOfRange
- ? 'Selected date is out of range where backups are available'
- : !canTriggerPhysicalBackups
- ? 'You need additional permissions to trigger a restore'
- : undefined,
- },
- }}
- >
- Review restore details
-
-
- }
- >
-
-
-
'cursor-pointer'}
- minDate={earliestAvailableBackup.toDate()}
- maxDate={latestAvailableBackup.toDate()}
- highlightDates={availableDates.map((date) => date.toDate())}
- renderCustomHeader={({
- date,
- decreaseMonth,
- increaseMonth,
- prevMonthButtonDisabled,
- nextMonthButtonDisabled,
- }) => (
-
-
-
-
-
-
- {format(date, 'MMMM yyyy')}
-
-
-
-
-
-
- )}
- />
-
-
-
Point in time back up available
-
-
-
-
- {!selectedDate ? (
-
-
- }
- title="Select a date which you'd like to restore your database to"
- />
-
-
- ) : (
-
-
-
Restore database to
-
- {dayjs(selectedDate).format('DD MMM YYYY')}
-
- , {formatNumberToTwoDigits(selectedTime.h)}:
- {formatNumberToTwoDigits(selectedTime.m)}:
- {formatNumberToTwoDigits(selectedTime.s)}
-
-
-
-
-
-
-
-
Recovery time
-
{
- const newDate = dayjs(selectedDateRaw)
- .set('hour', h)
- .set('minute', m)
- .set('second', s)
-
- setSelectedDateRaw(newDate.toDate())
- }}
- />
-
-
-
- Enter a time within the available range to restore from. Backups
- are captured every 2 minutes, allowing you to enter a time and restore
- your database to the closest backup point. We'll match the time you
- enter to the closest backup within the 2-minute window
-
-
-
-
- {isSelectedOnEarliestDay && (
-
- Earliest backup available for this date :{' '}
- {earliestAvailableBackup.format('HH:mm:ss')}
-
- )}
- {isSelectedOnLatestDay && (
-
- Latest backup available for this date :{' '}
- {latestAvailableBackup.format('HH:mm:ss')}
-
- )}
-
-
-
- )}
-
-
-
+ {
+ setSelectedRecoveryPoint(recoveryPoint)
+ setShowConfirmation(true)
+ }}
+ />
)}
>
)}
@@ -379,12 +154,12 @@ const PITRSelection = () => {
-
{selectedTimezone?.text}
-
{recoveryTimeString}
+
Local Time
+
{selectedRecoveryPoint?.recoveryTimeString}
(UTC+00:00)
-
{recoveryTimeStringUtc}
+
{selectedRecoveryPoint?.recoveryTimeStringUtc}
diff --git a/apps/studio/components/interfaces/Database/Backups/PITR/TimezoneSelection.tsx b/apps/studio/components/interfaces/Database/Backups/PITR/TimezoneSelection.tsx
index d8b943ae0bb..d527da4b6d4 100644
--- a/apps/studio/components/interfaces/Database/Backups/PITR/TimezoneSelection.tsx
+++ b/apps/studio/components/interfaces/Database/Backups/PITR/TimezoneSelection.tsx
@@ -61,7 +61,7 @@ export const TimezoneSelection = ({
value={option}
onSelect={(text) => {
const selectedTimezone = ALL_TIMEZONES.find(
- (option) => option.text.toLocaleLowerCase() === text
+ (option) => option.text === text
)
if (selectedTimezone) {
onSelectTimezone(selectedTimezone)
diff --git a/apps/studio/components/interfaces/Database/Backups/PITR/pitr-form.tsx b/apps/studio/components/interfaces/Database/Backups/PITR/pitr-form.tsx
new file mode 100644
index 00000000000..5bd537845cb
--- /dev/null
+++ b/apps/studio/components/interfaces/Database/Backups/PITR/pitr-form.tsx
@@ -0,0 +1,264 @@
+import { ButtonTooltip } from 'components/ui/ButtonTooltip'
+import DatePicker from 'react-datepicker'
+import { FormPanel } from 'components/ui/Forms/FormPanel'
+import { format } from 'date-fns'
+import InformationBox from 'components/ui/InformationBox'
+import dayjs from 'dayjs'
+import { ChevronLeft, ChevronRight, HelpCircle } from 'lucide-react'
+import {
+ constrainDateToRange,
+ formatNumberToTwoDigits,
+ getClientTimezone,
+ getDatesBetweenRange,
+} from './PITR.utils'
+import { TimezoneSelection } from './TimezoneSelection'
+import TimeInput from './TimeInput'
+import { Timezone } from './PITR.types'
+import { useMemo, useState } from 'react'
+
+type Props = {
+ onSubmit: (data: {
+ recoveryTimeTargetUnix: number
+ recoveryTimeString: string
+ recoveryTimeStringUtc: string
+ }) => void
+ earliestAvailableBackupUnix: number
+ latestAvailableBackupUnix: number
+}
+
+export function PITRForm({
+ onSubmit,
+ earliestAvailableBackupUnix,
+ latestAvailableBackupUnix,
+}: Props) {
+ const [selectedTimezone, setSelectedTimezone] = useState(getClientTimezone())
+ const earliestAvailableBackup = dayjs
+ .unix(earliestAvailableBackupUnix ?? 0)
+ .tz(selectedTimezone.utc[0])
+ const latestAvailableBackup = dayjs
+ .unix(latestAvailableBackupUnix ?? 0)
+ .tz(selectedTimezone.utc[0])
+
+ const [selectedDateRaw, setSelectedDateRaw] = useState(latestAvailableBackup.toDate())
+
+ const selectedDate = dayjs(selectedDateRaw).tz(selectedTimezone.utc[0], true)
+ const isSelectedOnEarliestDay = selectedDate.isSame(earliestAvailableBackup, 'day')
+ const isSelectedOnLatestDay = selectedDate.isSame(latestAvailableBackup, 'day')
+ const availableDates = getDatesBetweenRange(earliestAvailableBackup, latestAvailableBackup)
+
+ const selectedTime = {
+ h: selectedDate.hour(),
+ m: selectedDate.minute(),
+ s: selectedDate.second(),
+ }
+
+ const earliestAvailableBackupTime = {
+ h: earliestAvailableBackup.hour(),
+ m: earliestAvailableBackup.minute(),
+ s: earliestAvailableBackup.second(),
+ }
+
+ const latestAvailableBackupTime = {
+ h: latestAvailableBackup.hour(),
+ m: latestAvailableBackup.minute(),
+ s: latestAvailableBackup.second(),
+ }
+
+ const recoveryTimeTargetUnix = selectedDate.unix()
+ const recoveryTimeString = selectedDate.format('DD MMM YYYY HH:mm:ss')
+ const recoveryTimeStringUtc = selectedDate.utc().format('DD MMM YYYY HH:mm:ss')
+
+ const isWithinRange = useMemo(
+ () =>
+ (!!selectedDate && selectedDate.isSame(latestAvailableBackup)) ||
+ selectedDate.isSame(earliestAvailableBackup) ||
+ (selectedDate.isBefore(latestAvailableBackup) &&
+ selectedDate.isAfter(earliestAvailableBackup)),
+ [selectedDate, latestAvailableBackup, earliestAvailableBackup]
+ )
+
+ const onUpdateDate = (date: Date) => {
+ setSelectedDateRaw(
+ constrainDateToRange(
+ dayjs(date).tz(selectedTimezone.utc[0], true),
+ earliestAvailableBackup,
+ latestAvailableBackup
+ ).toDate()
+ )
+ }
+
+ const handleSubmit = () => {
+ onSubmit({
+ recoveryTimeTargetUnix,
+ recoveryTimeString,
+ recoveryTimeStringUtc,
+ })
+ }
+
+ return (
+
+
+
+ Continue
+
+
+ }
+ >
+
+
+
'cursor-pointer'}
+ minDate={earliestAvailableBackup.toDate()}
+ maxDate={latestAvailableBackup.toDate()}
+ highlightDates={availableDates.map((date) => date.toDate())}
+ renderCustomHeader={({
+ date,
+ decreaseMonth,
+ increaseMonth,
+ prevMonthButtonDisabled,
+ nextMonthButtonDisabled,
+ }) => (
+
+
+
+
+
+
+ {format(date, 'MMMM yyyy')}
+
+
+
+
+
+
+ )}
+ />
+ {availableDates.length > 1 && (
+
+
+
Point in time back up available
+
+ )}
+
+
+
+ {!selectedDate ? (
+
+
+ }
+ title="Select a date which you'd like to restore your database to"
+ />
+
+
+ ) : (
+
+
+
Date to restore to
+
+ {dayjs(selectedDate).format('DD MMM YYYY')}
+
+ , {formatNumberToTwoDigits(selectedTime.h)}:
+ {formatNumberToTwoDigits(selectedTime.m)}:
+ {formatNumberToTwoDigits(selectedTime.s)}
+
+
+
+
+
+
+
+
Recovery time
+
{
+ const newDate = dayjs(selectedDateRaw)
+ .set('hour', h)
+ .set('minute', m)
+ .set('second', s)
+
+ setSelectedDateRaw(newDate.toDate())
+ }}
+ />
+
+
+
+ Enter a time within the available range to restore from. Backups are
+ captured every 2 minutes, allowing you to enter a time and restore your
+ database to the closest backup point. We'll match the time you enter to the
+ closest backup within the 2-minute window
+
+
+
+
+ {isSelectedOnEarliestDay && (
+
+ Earliest backup available for this date :{' '}
+ {earliestAvailableBackup.format('HH:mm:ss')}
+
+ )}
+ {isSelectedOnLatestDay && (
+
+ Latest backup available for this date :{' '}
+ {latestAvailableBackup.format('HH:mm:ss')}
+
+ )}
+
+
+
+ )}
+
+
+
+
+ )
+}
diff --git a/apps/studio/components/interfaces/Settings/Logs/Logs.constants.ts b/apps/studio/components/interfaces/Settings/Logs/Logs.constants.ts
index 4168bea354c..2da4d335752 100644
--- a/apps/studio/components/interfaces/Settings/Logs/Logs.constants.ts
+++ b/apps/studio/components/interfaces/Settings/Logs/Logs.constants.ts
@@ -363,6 +363,7 @@ export enum LogsTableName {
POSTGREST = 'postgrest_logs',
SUPAVISOR = 'supavisor_logs',
WAREHOUSE = 'warehouse_logs',
+ CRON_JOBS = 'cron_job_run_details',
}
export const LOGS_TABLES = {
@@ -376,6 +377,7 @@ export const LOGS_TABLES = {
postgrest: LogsTableName.POSTGREST,
supavisor: LogsTableName.SUPAVISOR,
warehouse: LogsTableName.WAREHOUSE,
+ cron: LogsTableName.CRON_JOBS,
}
export const LOGS_SOURCE_DESCRIPTION = {
@@ -389,6 +391,7 @@ export const LOGS_SOURCE_DESCRIPTION = {
[LogsTableName.POSTGREST]: 'RESTful API web server logs',
[LogsTableName.SUPAVISOR]: 'Cloud-native Postgres connection pooler logs',
[LogsTableName.WAREHOUSE]: 'Logs obtained from a data warehouse collection',
+ [LogsTableName.CRON_JOBS]: 'Logs obtained from cron job runs',
}
export const genQueryParams = (params: { [k: string]: string }) => {
diff --git a/apps/studio/components/interfaces/Settings/Logs/Logs.types.ts b/apps/studio/components/interfaces/Settings/Logs/Logs.types.ts
index f9c055618af..3ba1eef4fb8 100644
--- a/apps/studio/components/interfaces/Settings/Logs/Logs.types.ts
+++ b/apps/studio/components/interfaces/Settings/Logs/Logs.types.ts
@@ -88,6 +88,7 @@ export type QueryType =
| 'supavisor'
| 'postgrest'
| 'warehouse'
+ | 'cron'
export type Mode = 'simple' | 'custom'
diff --git a/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts b/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts
index b4c4732ee15..6a4c84ec55c 100644
--- a/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts
+++ b/apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts
@@ -211,6 +211,9 @@ limit ${limit}
${orderBy}
limit ${limit}
`
+
+ case 'cron_job_run_details':
+ return `select status, start_time, end_time, jobid from ${table} ${where} ${orderBy} limit ${limit}`
}
}
diff --git a/apps/studio/components/layouts/LogsLayout/LogsSidebarMenuV2.tsx b/apps/studio/components/layouts/LogsLayout/LogsSidebarMenuV2.tsx
index ae123766bdf..0eda209a230 100644
--- a/apps/studio/components/layouts/LogsLayout/LogsSidebarMenuV2.tsx
+++ b/apps/studio/components/layouts/LogsLayout/LogsSidebarMenuV2.tsx
@@ -164,6 +164,12 @@ export function LogsSidebarMenuV2() {
url: `/project/${ref}/logs/edge-functions-logs`,
items: [],
},
+ {
+ name: 'Cron Jobs',
+ key: 'cron-logs',
+ url: `/project/${ref}/logs/cron-logs`,
+ items: [],
+ },
]
const filteredLogs = BASE_COLLECTIONS.filter((collection) => {
diff --git a/apps/studio/data/projects/clone-mutation.ts b/apps/studio/data/projects/clone-mutation.ts
new file mode 100644
index 00000000000..3a42a389506
--- /dev/null
+++ b/apps/studio/data/projects/clone-mutation.ts
@@ -0,0 +1,65 @@
+import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
+import { toast } from 'sonner'
+
+import { handleError, post } from 'data/fetchers'
+import type { ResponseError } from 'types'
+import { projectKeys } from './keys'
+
+export type ProjectCloneVariables = {
+ projectRef: string
+ newProjectName: string
+ newDbPass: string
+ cloneBackupId?: number
+ recoveryTimeTarget?: number
+}
+
+export async function triggerClone({
+ projectRef,
+ newProjectName,
+ cloneBackupId,
+ newDbPass,
+ recoveryTimeTarget,
+}: ProjectCloneVariables) {
+ if (!projectRef) throw new Error('Project ref is required')
+ const { data, error } = await post('/platform/database/{ref}/clone', {
+ params: { path: { ref: projectRef } },
+ body: { cloneBackupId, newProjectName, newDbPass, recoveryTimeTarget },
+ })
+
+ if (error) handleError(error)
+ return data
+}
+
+type ProjectCloneData = Awaited>
+
+export const useProjectCloneMutation = ({
+ onSuccess,
+ onError,
+ ...options
+}: Omit<
+ UseMutationOptions,
+ 'mutationFn'
+> = {}) => {
+ const queryClient = useQueryClient()
+
+ return useMutation(
+ (vars) => triggerClone(vars),
+ {
+ async onSuccess(data, variables, context) {
+ await queryClient.invalidateQueries({
+ queryKey: projectKeys.listCloneBackups(variables.projectRef),
+ })
+ await onSuccess?.(data, variables, context)
+ },
+ async onError(data, variables, context) {
+ if (onError === undefined) {
+ console.error(data)
+ toast.error(`Failed to trigger clone: ${data.message}`)
+ } else {
+ onError(data, variables, context)
+ }
+ },
+ ...options,
+ }
+ )
+}
diff --git a/apps/studio/data/projects/clone-query.ts b/apps/studio/data/projects/clone-query.ts
new file mode 100644
index 00000000000..d7f0114e1b6
--- /dev/null
+++ b/apps/studio/data/projects/clone-query.ts
@@ -0,0 +1,29 @@
+import { get, handleError } from 'data/fetchers'
+import { projectKeys } from './keys'
+import { useQuery, UseQueryOptions } from '@tanstack/react-query'
+import { ResponseError } from 'types'
+
+export async function getCloneBackups(projectRef?: string) {
+ if (!projectRef) throw new Error('Project ref is required')
+
+ const { data, error } = await get(`/platform/database/{ref}/clone`, {
+ params: { path: { ref: projectRef } },
+ })
+
+ if (error) handleError(error)
+ return data
+}
+
+export type CloneBackupsData = Awaited>
+export type CloneBackupsError = ResponseError
+
+export const useCloneBackupsQuery = (
+ { projectRef }: { projectRef?: string },
+ options: UseQueryOptions = {}
+) => {
+ return useQuery(
+ projectKeys.listCloneBackups(projectRef),
+ () => getCloneBackups(projectRef),
+ { enabled: !!projectRef, ...options }
+ )
+}
diff --git a/apps/studio/data/projects/clone-status-query.ts b/apps/studio/data/projects/clone-status-query.ts
new file mode 100644
index 00000000000..9f9b6a5253b
--- /dev/null
+++ b/apps/studio/data/projects/clone-status-query.ts
@@ -0,0 +1,29 @@
+import { get, handleError } from 'data/fetchers'
+import { projectKeys } from './keys'
+import { useQuery, UseQueryOptions } from '@tanstack/react-query'
+import { ResponseError } from 'types'
+
+export async function getCloneStatus(projectRef?: string) {
+ if (!projectRef) throw new Error('Project ref is required')
+
+ const { data, error } = await get(`/platform/database/{ref}/clone/status`, {
+ params: { path: { ref: projectRef } },
+ })
+
+ if (error) handleError(error)
+ return data
+}
+
+export type CloneStatusData = Awaited>
+export type CloneStatusError = ResponseError
+
+export const useCloneStatusQuery = (
+ { projectRef }: { projectRef?: string },
+ options: UseQueryOptions = {}
+) => {
+ return useQuery(
+ projectKeys.listCloneStatus(projectRef),
+ () => getCloneStatus(projectRef),
+ { enabled: !!projectRef, ...options }
+ )
+}
diff --git a/apps/studio/data/projects/keys.ts b/apps/studio/data/projects/keys.ts
index 87d8627132d..726558d8c5c 100644
--- a/apps/studio/data/projects/keys.ts
+++ b/apps/studio/data/projects/keys.ts
@@ -18,4 +18,10 @@ export const projectKeys = {
['projects', projectRef, 'pause-status'] as const,
orgProjects: (slug: string | undefined) => ['projects', 'org', slug] as const,
+
+ // Clone to new project
+ listCloneBackups: (projectRef: string | undefined) =>
+ ['projects', projectRef, 'clone-backups'] as const,
+ listCloneStatus: (projectRef: string | undefined) =>
+ ['projects', projectRef, 'clone-status'] as const,
}
diff --git a/apps/studio/data/projects/new-project.constants.ts b/apps/studio/data/projects/new-project.constants.ts
new file mode 100644
index 00000000000..82a51c5f84b
--- /dev/null
+++ b/apps/studio/data/projects/new-project.constants.ts
@@ -0,0 +1,97 @@
+import { components } from 'api-types'
+import { PROVIDERS } from 'lib/constants'
+
+type DesiredInstanceSize = components['schemas']['DesiredInstanceSize']
+
+export const instanceSizeSpecs: Record<
+ DesiredInstanceSize,
+ {
+ label: string
+ ram: string
+ cpu: string
+ priceHourly: number
+ priceMonthly: number
+ cloud_providers: string[]
+ }
+> = {
+ micro: {
+ label: 'Micro',
+ ram: '1 GB',
+ cpu: '2-core',
+ priceHourly: 0.01344,
+ priceMonthly: 10,
+ cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.FLY.id],
+ },
+ small: {
+ label: 'Small',
+ ram: '2 GB',
+ cpu: '2-core',
+ priceHourly: 0.0206,
+ priceMonthly: 15,
+ cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.FLY.id],
+ },
+ medium: {
+ label: 'Medium',
+ ram: '4 GB',
+ cpu: '2-core',
+ priceHourly: 0.0822,
+ priceMonthly: 60,
+ cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.FLY.id],
+ },
+ large: {
+ label: 'Large',
+ ram: '8 GB',
+ cpu: '2-core',
+ priceHourly: 0.1517,
+ priceMonthly: 110,
+ cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.FLY.id],
+ },
+ xlarge: {
+ label: 'XL',
+ ram: '16 GB',
+ cpu: '4-core',
+ priceHourly: 0.2877,
+ priceMonthly: 210,
+ cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.FLY.id],
+ },
+ '2xlarge': {
+ label: '2XL',
+ ram: '32 GB',
+ cpu: '8-core',
+ priceHourly: 0.562,
+ priceMonthly: 410,
+ cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.FLY.id],
+ },
+ '4xlarge': {
+ label: '4XL',
+ ram: '64 GB',
+ cpu: '16-core',
+ priceHourly: 1.32,
+ priceMonthly: 960,
+ cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.FLY.id],
+ },
+ '8xlarge': {
+ label: '8XL',
+ ram: '128 GB',
+ cpu: '32-core',
+ priceHourly: 2.562,
+ priceMonthly: 1870,
+ cloud_providers: [PROVIDERS.AWS.id],
+ },
+ '12xlarge': {
+ label: '12XL',
+ ram: '192 GB',
+ cpu: '48-core',
+ priceHourly: 3.836,
+ priceMonthly: 2800,
+ cloud_providers: [PROVIDERS.AWS.id],
+ },
+ '16xlarge': {
+ label: '16XL',
+ ram: '256 GB',
+ cpu: '64-core',
+ priceHourly: 5.12,
+ priceMonthly: 3730,
+ cloud_providers: [PROVIDERS.AWS.id],
+ },
+}
diff --git a/apps/studio/pages/new/[slug].tsx b/apps/studio/pages/new/[slug].tsx
index cf59f410810..8c2724a60e6 100644
--- a/apps/studio/pages/new/[slug].tsx
+++ b/apps/studio/pages/new/[slug].tsx
@@ -84,6 +84,7 @@ import { Input } from 'ui-patterns/DataInputs/Input'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { InfoTooltip } from 'ui-patterns/info-tooltip'
import { useOverdueInvoicesQuery } from 'data/invoices/invoices-overdue-query'
+import { instanceSizeSpecs } from 'data/projects/new-project.constants'
type DesiredInstanceSize = components['schemas']['DesiredInstanceSize']
@@ -100,99 +101,6 @@ const sizes: DesiredInstanceSize[] = [
'16xlarge',
]
-const instanceSizeSpecs: Record<
- DesiredInstanceSize,
- {
- label: string
- ram: string
- cpu: string
- priceHourly: number
- priceMonthly: number
- cloud_providers: string[]
- }
-> = {
- micro: {
- label: 'Micro',
- ram: '1 GB',
- cpu: '2-core',
- priceHourly: 0.01344,
- priceMonthly: 10,
- cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.FLY.id],
- },
- small: {
- label: 'Small',
- ram: '2 GB',
- cpu: '2-core',
- priceHourly: 0.0206,
- priceMonthly: 15,
- cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.FLY.id],
- },
- medium: {
- label: 'Medium',
- ram: '4 GB',
- cpu: '2-core',
- priceHourly: 0.0822,
- priceMonthly: 60,
- cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.FLY.id],
- },
- large: {
- label: 'Large',
- ram: '8 GB',
- cpu: '2-core',
- priceHourly: 0.1517,
- priceMonthly: 110,
- cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.FLY.id],
- },
- xlarge: {
- label: 'XL',
- ram: '16 GB',
- cpu: '4-core',
- priceHourly: 0.2877,
- priceMonthly: 210,
- cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.FLY.id],
- },
- '2xlarge': {
- label: '2XL',
- ram: '32 GB',
- cpu: '8-core',
- priceHourly: 0.562,
- priceMonthly: 410,
- cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.FLY.id],
- },
- '4xlarge': {
- label: '4XL',
- ram: '64 GB',
- cpu: '16-core',
- priceHourly: 1.32,
- priceMonthly: 960,
- cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.FLY.id],
- },
- '8xlarge': {
- label: '8XL',
- ram: '128 GB',
- cpu: '32-core',
- priceHourly: 2.562,
- priceMonthly: 1870,
- cloud_providers: [PROVIDERS.AWS.id],
- },
- '12xlarge': {
- label: '12XL',
- ram: '192 GB',
- cpu: '48-core',
- priceHourly: 3.836,
- priceMonthly: 2800,
- cloud_providers: [PROVIDERS.AWS.id],
- },
- '16xlarge': {
- label: '16XL',
- ram: '256 GB',
- cpu: '64-core',
- priceHourly: 5.12,
- priceMonthly: 3730,
- cloud_providers: [PROVIDERS.AWS.id],
- },
-}
-
const Wizard: NextPageWithLayout = () => {
const router = useRouter()
const { slug, projectName } = useParams()
diff --git a/apps/studio/pages/project/[ref]/database/backups/restore-to-new-project.tsx b/apps/studio/pages/project/[ref]/database/backups/restore-to-new-project.tsx
new file mode 100644
index 00000000000..632ac45122a
--- /dev/null
+++ b/apps/studio/pages/project/[ref]/database/backups/restore-to-new-project.tsx
@@ -0,0 +1,588 @@
+import { zodResolver } from '@hookform/resolvers/zod'
+import { PermissionAction } from '@supabase/shared-types/out/constants'
+import BackupsEmpty from 'components/interfaces/Database/Backups/BackupsEmpty'
+
+import DatabaseBackupsNav from 'components/interfaces/Database/Backups/DatabaseBackupsNav'
+import DatabaseLayout from 'components/layouts/DatabaseLayout/DatabaseLayout'
+import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
+import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
+import AlertError from 'components/ui/AlertError'
+import { FormHeader } from 'components/ui/Forms/FormHeader'
+import NoPermission from 'components/ui/NoPermission'
+import Panel from 'components/ui/Panel'
+import PasswordStrengthBar from 'components/ui/PasswordStrengthBar'
+import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
+import UpgradeToPro from 'components/ui/UpgradeToPro'
+import { useProjectCloneMutation } from 'data/projects/clone-mutation'
+import { useCloneBackupsQuery } from 'data/projects/clone-query'
+import { useCloneStatusQuery } from 'data/projects/clone-status-query'
+import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
+import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions'
+import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
+import { PROJECT_STATUS } from 'lib/constants'
+import { getDatabaseMajorVersion, passwordStrength } from 'lib/helpers'
+import Link from 'next/link'
+import { useState, useRef } from 'react'
+import { useForm } from 'react-hook-form'
+import type { NextPageWithLayout } from 'types'
+import {
+ Badge,
+ Button,
+ Dialog,
+ DialogContent,
+ DialogTitle,
+ DialogHeader,
+ DialogDescription,
+ DialogFooter,
+ DialogSection,
+ Input,
+ Form_Shadcn_,
+ FormControl_Shadcn_,
+ FormField_Shadcn_,
+ Input_Shadcn_,
+ Alert_Shadcn_,
+ AlertTitle_Shadcn_,
+ AlertDescription_Shadcn_,
+} from 'ui'
+import { Admonition, TimestampInfo } from 'ui-patterns'
+import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
+import { z } from 'zod'
+import { debounce } from 'lodash'
+import generator from 'generate-password-browser'
+import { toast } from 'sonner'
+import { Loader2 } from 'lucide-react'
+import { PITRForm } from 'components/interfaces/Database/Backups/PITR/pitr-form'
+import { instanceSizeSpecs } from 'data/projects/new-project.constants'
+import { Markdown } from 'components/interfaces/Markdown'
+
+const RestoreToNewProjectPage: NextPageWithLayout = () => {
+ return (
+
+
+
+
+
+ )
+}
+
+RestoreToNewProjectPage.getLayout = (page) => (
+ {page}
+)
+
+const RestoreToNewProject = () => {
+ const { project } = useProjectContext()
+ const organization = useSelectedOrganization()
+ const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
+ const isFreePlan = subscription?.plan?.id === 'free'
+
+ const {
+ data: cloneBackups,
+ error,
+ isLoading: cloneBackupsLoading,
+ isError,
+ } = useCloneBackupsQuery(
+ {
+ projectRef: project?.ref,
+ },
+ {
+ enabled: !isFreePlan,
+ }
+ )
+
+ const plan = subscription?.plan?.id
+ const isActiveHealthy = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY
+
+ const isPermissionsLoaded = usePermissionsLoaded()
+ const canReadPhysicalBackups = useCheckPermissions(PermissionAction.READ, 'physical_backups')
+ const canTriggerPhysicalBackups = useCheckPermissions(
+ PermissionAction.INFRA_EXECUTE,
+ 'queue_job.restore.prepare'
+ )
+ const hasPITREnabled = cloneBackups?.pitr_enabled
+
+ const dbVersion = getDatabaseMajorVersion(project?.dbVersion ?? '')
+ const IS_PG15_OR_ABOVE = dbVersion >= 15
+ const PHYSICAL_BACKUPS_ENABLED = project?.is_physical_backups_enabled
+
+ const {
+ data: cloneStatus,
+ refetch: refetchCloneStatus,
+ isLoading: cloneStatusLoading,
+ } = useCloneStatusQuery({
+ projectRef: project?.ref,
+ })
+ const lastClone = cloneStatus?.clones?.[cloneStatus?.clones.length - 1]
+ const [selectedBackupId, setSelectedBackupId] = useState(null)
+ const [showConfirmationDialog, setShowConfirmationDialog] = useState(false)
+ const [showNewProjectDialog, setShowNewProjectDialog] = useState(false)
+ const [recoveryTimeTarget, setRecoveryTimeTarget] = useState(null)
+ const IS_CLONED_PROJECT = (cloneStatus?.cloned_from?.source_project as any)?.ref ? true : false
+
+ const isLoading = !isPermissionsLoaded || cloneBackupsLoading || cloneStatusLoading
+
+ /**
+ * New project will have the same compute size and disk size as the original project
+ */
+ function getAdditionalMonthlySpend() {
+ const currentProjectComputeSize = project?.infra_compute_size
+ if (!currentProjectComputeSize) {
+ return null
+ }
+
+ if (currentProjectComputeSize === 'nano') {
+ return null
+ }
+
+ const additionalMonthlySpend = instanceSizeSpecs[currentProjectComputeSize]
+
+ return additionalMonthlySpend
+ }
+
+ const { mutate: triggerClone, isLoading: cloneMutationLoading } = useProjectCloneMutation({
+ onError: (error) => {
+ console.error('error', error)
+ toast.error('Failed to restore to new project')
+ },
+ onSuccess: () => {
+ toast.success('Restoration process started')
+ refetchCloneStatus()
+ setShowNewProjectDialog(false)
+ },
+ })
+
+ const FormSchema = z.object({
+ name: z.string().min(1),
+ password: z.string().min(1),
+ })
+
+ const form = useForm>({
+ resolver: zodResolver(FormSchema),
+ defaultValues: {
+ name: '',
+ password: '',
+ },
+ })
+
+ const [passwordStrengthScore, setPasswordStrengthScore] = useState(0)
+ const [passwordStrengthMessage, setPasswordStrengthMessage] = useState('')
+
+ const delayedCheckPasswordStrength = useRef(
+ debounce((value: string) => checkPasswordStrength(value), 300)
+ ).current
+
+ async function checkPasswordStrength(value: string) {
+ const { message, strength } = await passwordStrength(value)
+ setPasswordStrengthScore(strength)
+ setPasswordStrengthMessage(message)
+ }
+
+ function generateStrongPassword() {
+ const password = generator.generate({
+ length: 16,
+ numbers: true,
+ uppercase: true,
+ })
+
+ form.setValue('password', password)
+ delayedCheckPasswordStrength(password)
+ }
+
+ function AdditionalMonthlySpend() {
+ const additionalMonthlySpend = getAdditionalMonthlySpend()
+ if (!additionalMonthlySpend) {
+ return null
+ }
+
+ return (
+
+
+ The new project will have the same compute size and disk size as this project. You will be
+ able to update the compute size and disk size after the new project is created in{' '}
+
+ Project Settings > Compute and Disk
+
+
+
+
Additional Monthly Compute + Disk Cost
+
${additionalMonthlySpend.priceMonthly}
+
+
+ )
+ }
+
+ if (isLoading) {
+ return
+ }
+
+ if (!canReadPhysicalBackups) {
+ return
+ }
+
+ if (!canTriggerPhysicalBackups) {
+ return
+ }
+
+ if (!IS_PG15_OR_ABOVE) {
+ return (
+
+
+
+ )
+ }
+
+ if (!PHYSICAL_BACKUPS_ENABLED) {
+ return (
+
+ Physical backups must be enabled to restore your database to a new project.
+ Find out more about how backups work at supabase{' '}
+
+ in our docs
+
+ .
+ >
+ }
+ />
+ )
+ }
+
+ if (plan === 'free') {
+ return (
+
+ )
+ }
+
+ if (IS_CLONED_PROJECT) {
+ return (
+
+
+
+ )
+ }
+
+ if (isError) {
+ return
+ }
+
+ if (!isActiveHealthy) {
+ return (
+
+ )
+ }
+
+ if (lastClone?.status === 'FAILED') {
+ return (
+
+
+
+ )
+ }
+
+ if (lastClone?.status === 'COMPLETED') {
+ return (
+
+
+
+ )
+ }
+
+ if (lastClone?.status === 'IN_PROGRESS') {
+ return (
+
+
+ Restoration in progress
+
+ The new project is being created.
+
+
+ Go to new project
+
+
+
+ )
+ }
+
+ if (
+ !isLoading &&
+ hasPITREnabled &&
+ !cloneBackups?.physicalBackupData.earliestPhysicalBackupDateUnix
+ ) {
+ return (
+
+ )
+ }
+
+ if (!isLoading && !hasPITREnabled && cloneBackups?.backups.length === 0) {
+ return
+ }
+
+ return (
+ <>
+
+
+
+ Confirm restore to a new project
+
+ This process will create a new project and restore your database to it.
+
+
+
+
+
+ Project organization will stay the same: {organization?.name}
+
+
+ Project region will stay the same: {project?.region || ''}
+
+
+ A project can only be restored to another project once.
+
+ This is a temporary limitation. Please contact us if you need to restore a project
+ to multiple other projects.
+
+
+
+
+ What will be transferred?
+
+ Database schema (tables, views, procedures)
+ All data and indexes
+ Database roles, permissions and users
+
+
+
+ What needs manual reconfiguration?
+
+ Storage objects & settings
+ Edge Functions
+ Auth settings & API keys
+ Realtime settings
+ Database extensions and settings
+ Read replicas
+
+
+
+
+
+ setShowConfirmationDialog(false)}>
+ Cancel
+
+ {
+ setShowConfirmationDialog(false)
+ setShowNewProjectDialog(true)
+ }}
+ >
+ Continue
+
+
+
+
+
+
+
+ Create new project
+
+ This process will create a new project and restore your database to it.
+
+
+
+
+
+
+
+ {hasPITREnabled ? (
+ <>
+ {
+ setShowConfirmationDialog(true)
+ setRecoveryTimeTarget(v.recoveryTimeTargetUnix)
+ }}
+ earliestAvailableBackupUnix={
+ cloneBackups?.physicalBackupData.earliestPhysicalBackupDateUnix || 0
+ }
+ latestAvailableBackupUnix={
+ cloneBackups?.physicalBackupData.latestPhysicalBackupDateUnix || 0
+ }
+ />
+ >
+ ) : (
+ <>
+
+ {cloneBackups?.backups.length === 0 ? (
+ <>
+
+ >
+ ) : (
+
+ {/*
{JSON.stringify({ cloneStatus }, null, 2)} */}
+ {cloneBackups?.backups.map((backup) => {
+ if (!backup.isPhysicalBackup) return null
+ return (
+
+
+
+
+
{JSON.stringify(backup.status).replaceAll('"', '')}
+ {(backup.status as any) === 'COMPLETED' && (
+
{
+ setSelectedBackupId(backup.id)
+ setShowConfirmationDialog(true)
+ }}
+ >
+ Restore
+
+ )}
+
+ )
+ })}
+
+ )}
+
+ >
+ )}
+ >
+ )
+}
+
+export default RestoreToNewProjectPage
diff --git a/apps/studio/pages/project/[ref]/logs/cron-logs.tsx b/apps/studio/pages/project/[ref]/logs/cron-logs.tsx
new file mode 100644
index 00000000000..cb80975342b
--- /dev/null
+++ b/apps/studio/pages/project/[ref]/logs/cron-logs.tsx
@@ -0,0 +1,26 @@
+// select * from cron.job_run_details where jobid = '1' order by start_time desc limit 10
+
+import { useRouter } from 'next/router'
+
+import LogsPreviewer from 'components/interfaces/Settings/Logs/LogsPreviewer'
+import LogsLayout from 'components/layouts/LogsLayout/LogsLayout'
+import type { NextPageWithLayout } from 'types'
+import { LogsTableName } from 'components/interfaces/Settings/Logs/Logs.constants'
+
+export const LogPage: NextPageWithLayout = () => {
+ const router = useRouter()
+ const { ref } = router.query
+
+ return (
+
+ )
+}
+
+LogPage.getLayout = (page) => {page}
+
+export default LogPage
diff --git a/apps/studio/styles/date-picker.scss b/apps/studio/styles/date-picker.scss
index f21c7c88557..7825357eb34 100644
--- a/apps/studio/styles/date-picker.scss
+++ b/apps/studio/styles/date-picker.scss
@@ -96,7 +96,7 @@
}
.react-datepicker__day--highlighted {
- @apply bg-brand-600 text-foreground-light;
+ @apply bg-overlay-hover text-foreground-light;
}
.react-datepicker__aria-live {
diff --git a/packages/api-types/types/api.d.ts b/packages/api-types/types/api.d.ts
index e4922dc9677..081229cb3ec 100644
--- a/packages/api-types/types/api.d.ts
+++ b/packages/api-types/types/api.d.ts
@@ -185,6 +185,16 @@ export interface paths {
/** Restore project with a physical backup */
post: operations['BackupsController_restorePhysicalBackup']
}
+ '/platform/database/{ref}/clone': {
+ /** List valid backups to clone from */
+ get: operations['CloneController_getValidBackups']
+ /** Clone the current project from a backup */
+ post: operations['CloneController_cloneCurrentProject']
+ }
+ '/platform/database/{ref}/clone/status': {
+ /** Retrieve the current status of an existing cloning process */
+ get: operations['CloneController_cloneProjectStatus']
+ }
'/platform/database/{ref}/hook-enable': {
/** Enables Database Webhooks on the project */
post: operations['HooksController_enableHooks']
@@ -330,6 +340,10 @@ export interface paths {
/** Gets invoice with the given invoice ID */
get: operations['OrgInvoicesController_getInvoice']
}
+ '/platform/organizations/{slug}/billing/invoices/{invoiceId}/payment-link': {
+ /** Gets the payment link to manually pay the given invoice */
+ get: operations['OrgInvoicesController_getInvoicePaymentLink']
+ }
'/platform/organizations/{slug}/billing/invoices/upcoming': {
/** Gets the upcoming invoice */
get: operations['OrgInvoicesController_getUpcomingInvoice']
@@ -752,18 +766,22 @@ export interface paths {
delete: operations['v1-delete-a-warehouse-token']
}
'/platform/projects/{ref}/analytics/warehouse/collections': {
- /** Lists project's warehouse collections from logflare */
- get: operations['v1-list-all-warehouse-collections']
- /** Create a warehouse collection */
- post: operations['v1-create-a-warehouse-collection']
+ /** Lists project's telemetry collections from logflare */
+ get: operations['v1-list-all-telemetry-collections']
+ /** Create a telemetry collection */
+ post: operations['v1-create-a-telemetry-collection']
}
'/platform/projects/{ref}/analytics/warehouse/collections/{token}': {
- /** Get a warehouse collection */
- get: operations['v1-get-a-warehouse-collection']
- /** Delete a warehouse collection */
- delete: operations['v1-delete-a-warehouse-collection']
- /** Update a warehouse collection */
- patch: operations['v1-update-a-warehouse-collection']
+ /** Get a telemetry collection */
+ get: operations['v1-get-a-telemetry-collection']
+ /** Delete a telemetry collection */
+ delete: operations['v1-delete-a-telemetry-collection']
+ /** Update a telemetry collection */
+ patch: operations['v1-update-a-telemetry-collection']
+ }
+ '/platform/projects/{ref}/analytics/warehouse/collections/{token}/schema': {
+ /** Get a telemetry collection schema */
+ get: operations['v1-get-a-telemetry-collection-schema']
}
'/platform/projects/{ref}/analytics/warehouse/endpoints': {
/** Lists project's warehouse endpoints from logflare */
@@ -907,6 +925,12 @@ export interface paths {
/** Modify database disk */
post: operations['DiskController_modifyDisk']
}
+ '/platform/projects/{ref}/disk/custom-config': {
+ /** Gets disk autoscale config */
+ get: operations['DiskController_getDiskAutoscaleConfig']
+ /** Updates disk autoscale config */
+ post: operations['DiskController_updateDiskAutoscaleConfig']
+ }
'/platform/projects/{ref}/disk/util': {
/** Get disk utilization */
get: operations['DiskController_getDiskUtilization']
@@ -967,6 +991,10 @@ export interface paths {
/** Gets project's settings */
get: operations['SettingsController_getProjectApi']
}
+ '/platform/projects/{ref}/settings/sensitivity': {
+ /** Updates the given project sensitivity */
+ patch: operations['SensitivityController_updateProjectSensitivity']
+ }
'/platform/projects/{ref}/status': {
/** Gets project's status */
get: operations['ProjectStatusController_getStatus']
@@ -1191,6 +1219,10 @@ export interface paths {
/** Processes Orb events */
post: operations['OrbWebhooksController_processEvent']
}
+ '/system/organizations/{slug}': {
+ /** Deletes organization */
+ delete: operations['OrganizationSlugSystemController_deleteOrganization']
+ }
'/system/organizations/{slug}/billing/subscription': {
/** Gets the current subscription */
get: operations['OrgSubscriptionSystemController_getSubscription']
@@ -1217,6 +1249,10 @@ export interface paths {
/** Create a project */
post: operations['SystemProjectsController_createProject']
}
+ '/system/projects/{ref}': {
+ /** Deletes the given project */
+ delete: operations['ProjectRefSystemController_deleteProject']
+ }
'/system/projects/{ref}/billing/addons': {
/** Updates project addon */
post: operations['AddonsController_updateAddon']
@@ -1225,6 +1261,14 @@ export interface paths {
/** Removes project addon */
delete: operations['AddonsController_removeAddon']
}
+ '/system/projects/{ref}/config/email-restrictions': {
+ /** Gets the current email restrictions for a project. */
+ get: operations['ProjectEmailRestrictionsController_getEmailRestrictions']
+ /** Updates the current email restrictions for a project. */
+ put: operations['ProjectEmailRestrictionsController_updateEmailRestrictions']
+ /** Removes the current email restrictions for a project. */
+ delete: operations['ProjectEmailRestrictionsController_deleteEmailRestrictions']
+ }
'/system/projects/{ref}/config/update-jwt/complete': {
/** Handle update project jwt on completion */
post: operations['ProjectUpdateJwtController_completeUpdateJwt']
@@ -1239,6 +1283,12 @@ export interface paths {
/** Modify database disk */
post: operations['SystemProjectDiskController_modifyDisk']
}
+ '/system/projects/{ref}/disk/custom-config': {
+ /** Gets disk autoscale config */
+ get: operations['SystemProjectDiskController_getDiskAutoscaleConfig']
+ /** Updates disk autoscale config */
+ post: operations['SystemProjectDiskController_updateDiskAutoscaleConfig']
+ }
'/system/projects/{ref}/disk/util': {
/** Get disk utilization */
get: operations['SystemProjectDiskController_getDiskUtilization']
@@ -1310,6 +1360,10 @@ export interface paths {
/** Processes a project's WAL verification report. */
put: operations['WalVerificationReportingController_processWalVerification']
}
+ '/system/projects/email-abuse': {
+ /** Reports email abuse from a postmark */
+ post: operations['ProjectEmailAbuseController_reportEmailAbuseWebhookPostmark']
+ }
'/system/stripe/webhooks': {
/** Processes Stripe event */
post: operations['StripeWebhooksController_processEvent']
@@ -1404,6 +1458,16 @@ export interface paths {
/** Restore project with a physical backup */
post: operations['BackupsController_restorePhysicalBackup']
}
+ '/v0/database/{ref}/clone': {
+ /** List valid backups to clone from */
+ get: operations['CloneController_getValidBackups']
+ /** Clone the current project from a backup */
+ post: operations['CloneController_cloneCurrentProject']
+ }
+ '/v0/database/{ref}/clone/status': {
+ /** Retrieve the current status of an existing cloning process */
+ get: operations['CloneController_cloneProjectStatus']
+ }
'/v0/database/{ref}/hook-enable': {
/** Enables Database Webhooks on the project */
post: operations['HooksController_enableHooks']
@@ -1701,18 +1765,22 @@ export interface paths {
delete: operations['v1-delete-a-warehouse-token']
}
'/v0/projects/{ref}/analytics/warehouse/collections': {
- /** Lists project's warehouse collections from logflare */
- get: operations['v1-list-all-warehouse-collections']
- /** Create a warehouse collection */
- post: operations['v1-create-a-warehouse-collection']
+ /** Lists project's telemetry collections from logflare */
+ get: operations['v1-list-all-telemetry-collections']
+ /** Create a telemetry collection */
+ post: operations['v1-create-a-telemetry-collection']
}
'/v0/projects/{ref}/analytics/warehouse/collections/{token}': {
- /** Get a warehouse collection */
- get: operations['v1-get-a-warehouse-collection']
- /** Delete a warehouse collection */
- delete: operations['v1-delete-a-warehouse-collection']
- /** Update a warehouse collection */
- patch: operations['v1-update-a-warehouse-collection']
+ /** Get a telemetry collection */
+ get: operations['v1-get-a-telemetry-collection']
+ /** Delete a telemetry collection */
+ delete: operations['v1-delete-a-telemetry-collection']
+ /** Update a telemetry collection */
+ patch: operations['v1-update-a-telemetry-collection']
+ }
+ '/v0/projects/{ref}/analytics/warehouse/collections/{token}/schema': {
+ /** Get a telemetry collection schema */
+ get: operations['v1-get-a-telemetry-collection-schema']
}
'/v0/projects/{ref}/analytics/warehouse/endpoints': {
/** Lists project's warehouse endpoints from logflare */
@@ -1838,6 +1906,12 @@ export interface paths {
/** Modify database disk */
post: operations['DiskController_modifyDisk']
}
+ '/v0/projects/{ref}/disk/custom-config': {
+ /** Gets disk autoscale config */
+ get: operations['DiskController_getDiskAutoscaleConfig']
+ /** Updates disk autoscale config */
+ post: operations['DiskController_updateDiskAutoscaleConfig']
+ }
'/v0/projects/{ref}/disk/util': {
/** Get disk utilization */
get: operations['DiskController_getDiskUtilization']
@@ -1890,6 +1964,10 @@ export interface paths {
/** Gets project's settings */
get: operations['SettingsController_getProjectApi']
}
+ '/v0/projects/{ref}/settings/sensitivity': {
+ /** Updates the given project sensitivity */
+ patch: operations['SensitivityController_updateProjectSensitivity']
+ }
'/v0/projects/{ref}/status': {
/** Gets project's status */
get: operations['ProjectStatusController_getStatus']
@@ -2047,6 +2125,8 @@ export interface paths {
post: operations['ApiKeysController_createApiKey']
}
'/v1/projects/{ref}/api-keys/{id}': {
+ /** [Alpha] Get API key */
+ get: operations['ApiKeysController_getApiKey']
/** [Alpha] Deletes an API key for the project */
delete: operations['ApiKeysController_deleteApiKey']
/** [Alpha] Updates an API key for the project */
@@ -2388,7 +2468,8 @@ export interface components {
name: string
prefix?: string | null
secret_jwt_template?: components['schemas']['ApiKeySecretJWTTemplate'] | null
- type?: unknown
+ /** @enum {string|null} */
+ type?: 'publishable' | 'secret' | 'legacy' | null
updated_at?: string | null
}
ApiKeySecretJWTTemplate: {
@@ -2744,6 +2825,14 @@ export interface components {
result: components['schemas']['CustomHostnameDetails']
success: boolean
}
+ CloneProjectDto: {
+ /** @default 0 */
+ cloneBackupId?: number
+ newDbPass: string
+ newProjectName: string
+ /** @default 0 */
+ recoveryTimeTarget?: number
+ }
Column: {
id: number
name: string
@@ -2931,7 +3020,7 @@ export interface components {
/** @enum {string} */
tier: 'tier_payg' | 'tier_pro' | 'tier_free' | 'tier_team' | 'tier_enterprise'
}
- CreateOrganizationBodyV1: {
+ CreateOrganizationV1Dto: {
name: string
}
CreatePolicyBody: {
@@ -3353,6 +3442,14 @@ export interface components {
DetachPaymentMethodBody: {
card_id: string
}
+ DiskAutoscaleConfig: {
+ /** @description Growth percentage for disk autoscaling */
+ growth_percent?: number | null
+ /** @description Maximum limit the disk size will grow to in GB */
+ max_size_gb?: number | null
+ /** @description Minimum increment size for disk autoscaling in GB */
+ min_increment_gb?: number | null
+ }
DiskRequestAttributesGP3: {
iops: number
size_gb: number
@@ -3433,6 +3530,14 @@ export interface components {
downloadName?: string
transform?: components['schemas']['StorageObjectTransformOptions']
}
+ EmailRestrictionsResponseBody: {
+ inserted_at?: string
+ is_restricted: boolean
+ project_id: number
+ project_ref: string
+ restricted_to_quota?: string
+ updated_at?: string
+ }
EventBody: {
eventType: string
message: string
@@ -3499,6 +3604,7 @@ export interface components {
import_map?: boolean
import_map_path?: string
name: string
+ resource_multiplier?: string
slug: string
/** @enum {string} */
status: 'ACTIVE' | 'REMOVED' | 'THROTTLED'
@@ -3515,6 +3621,7 @@ export interface components {
import_map?: boolean
import_map_path?: string
name: string
+ resource_multiplier?: string
slug: string
/** @enum {string} */
status: 'ACTIVE' | 'REMOVED' | 'THROTTLED'
@@ -3998,6 +4105,9 @@ export interface components {
subscription: string | null
subtotal: number
}
+ InvoicePaymentLinkResponse: {
+ redirectUrl: string
+ }
JoinResponse: {
billing_email: string
id: number
@@ -4121,6 +4231,9 @@ export interface components {
MarkDefaultPaymentMethodBody: {
payment_method_id: string
}
+ MarkSensitiveBody: {
+ is_sensitive: boolean
+ }
Member: {
gotrue_id: string
is_sso_user: boolean | null
@@ -4779,6 +4892,16 @@ export interface components {
db_schema: string
endpoint: string
}
+ ProjectClonedResponse: {
+ source_project_ref: string
+ target_project_ref: string
+ }
+ ProjectClonedStatusResponse: {
+ cloned_from: Record
+ clones: components['schemas']['TargetCloneStatus'][]
+ id: number
+ ref: string
+ }
ProjectCreationVersionInfo: {
postgres_engine: components['schemas']['PostgresEngine']
release_channel: components['schemas']['ReleaseChannel']
@@ -4807,7 +4930,6 @@ export interface components {
inserted_at: string
is_branch_enabled: boolean
is_physical_backups_enabled: boolean
- kpsVersion?: string
lastDatabaseResizeAt?: string
maxDatabasePreprovisionGb?: number
name: string
@@ -4816,7 +4938,6 @@ export interface components {
ref: string
region: string
restUrl: string
- serviceVersions?: components['schemas']['ServiceVersions']
/** @enum {string} */
status:
| 'ACTIVE_HEALTHY'
@@ -4904,6 +5025,10 @@ export interface components {
| 'unsupported_reg_types'
| 'auth_otp_long_expiry'
| 'auth_otp_short_length'
+ | 'ssl_not_enforced'
+ | 'network_restrictions_not_set'
+ | 'password_requirements_min_length'
+ | 'pitr_not_enabled'
remediation: Record
title: string
}
@@ -4918,6 +5043,8 @@ export interface components {
ref: string
}
ProjectResourceWarningsResponse: {
+ /** @enum {string|null} */
+ auth_email_offender: 'critical' | 'warning' | null
/** @enum {string|null} */
auth_rate_limit_exhaustion: 'critical' | 'warning' | null
/** @enum {string|null} */
@@ -4952,6 +5079,9 @@ export interface components {
ssl_enforced: boolean
status: string
}
+ ProjectSensitivityResponse: {
+ is_sensitive: boolean
+ }
ProjectServiceApiKeyResponse: {
api_key: string
name: string
@@ -4967,6 +5097,7 @@ export interface components {
db_port: number
db_user: string
inserted_at: string
+ is_sensitive?: boolean
jwt_secret?: string
name: string
ref: string
@@ -5680,6 +5811,18 @@ export interface components {
| 'REFERENCES'
| 'TRIGGER'
}
+ TargetClonedProject: {
+ ref: string
+ }
+ TargetCloneStatus: {
+ inserted_at: string | null
+ project_id: number
+ /** @enum {string} */
+ status: 'COMPLETED' | 'IN_PROGRESS' | 'FAILED' | 'REMOVED'
+ target_project: components['schemas']['TargetClonedProject']
+ target_project_id: number
+ updated_at: string | null
+ }
TaxId: {
country: string
type: string
@@ -6028,6 +6171,23 @@ export interface components {
| '4_origin_setup_completed'
| '5_services_reconfigured'
}
+ UpdateDiskAutoscaleConfig: {
+ /**
+ * @description Growth percentage for disk autoscaling
+ * @default 50
+ */
+ growth_percent?: number | null
+ /**
+ * @description Maximum limit the disk size will grow to in GB
+ * @default 61440
+ */
+ max_size_gb?: number | null
+ /**
+ * @description Minimum increment size for disk autoscaling in GB
+ * @default 4
+ */
+ min_increment_gb?: number | null
+ }
UpdateFunctionBody: {
args?: string[]
/** @enum {string} */
@@ -6360,6 +6520,9 @@ export interface components {
/** @enum {string} */
restriction_status: 'grace_period' | 'grace_period_over' | 'null' | 'restricted'
}
+ UpdateRestrictionsRequestBody: {
+ restricted_to_quota: string
+ }
UpdateRestrictionsResponse: {
message?: string
restriction_data?: components['schemas']['RestrictionData']
@@ -6651,13 +6814,25 @@ export interface components {
V1CreateFunctionBody: {
body: string
name: string
+ resource_multiplier?: string
slug: string
verify_jwt?: boolean
}
- V1CreateProjectBody: {
+ V1CreateProjectBodyDto: {
/** @description Database password */
db_pass: string
- desired_instance_size?: components['schemas']['DesiredInstanceSize']
+ /** @enum {string} */
+ desired_instance_size?:
+ | 'micro'
+ | 'small'
+ | 'medium'
+ | 'large'
+ | 'xlarge'
+ | '2xlarge'
+ | '4xlarge'
+ | '8xlarge'
+ | '12xlarge'
+ | '16xlarge'
/**
* @deprecated
* @description This field is deprecated and is ignored in this request
@@ -6670,14 +6845,16 @@ export interface components {
/**
* @deprecated
* @description Subscription Plan is now set on organization level and is ignored in this request
- * @example free
* @enum {string}
*/
plan?: 'free' | 'pro'
- postgres_engine?: components['schemas']['PostgresEngine']
+ /**
+ * @description Postgres engine version. If not provided, the latest version will be used.
+ * @enum {string}
+ */
+ postgres_engine?: '15'
/**
* @description Region you want your server to reside in
- * @example us-east-1
* @enum {string}
*/
region:
@@ -6699,8 +6876,13 @@ export interface components {
| 'ca-central-1'
| 'ap-south-1'
| 'sa-east-1'
- release_channel?: components['schemas']['ReleaseChannel']
/**
+ * @description Release channel. If not provided, GA will be used.
+ * @enum {string}
+ */
+ release_channel?: 'internal' | 'alpha' | 'beta' | 'ga' | 'withdrawn'
+ /**
+ * Format: uri
* @description Template URL used to create the project from the CLI.
* @example https://github.com/supabase/supabase/tree/master/examples/slack-clone/nextjs-slack-clone
*/
@@ -6763,7 +6945,42 @@ export interface components {
* @example 2023-03-29T16:32:59Z
*/
created_at: string
- database?: components['schemas']['V1DatabaseResponse']
+ /** @description Id of your project */
+ id: string
+ /** @description Name of your project */
+ name: string
+ /** @description Slug of your organization */
+ organization_id: string
+ /**
+ * @description Region of your project
+ * @example us-east-1
+ */
+ region: string
+ /** @enum {string} */
+ status:
+ | 'ACTIVE_HEALTHY'
+ | 'ACTIVE_UNHEALTHY'
+ | 'COMING_UP'
+ | 'GOING_DOWN'
+ | 'INACTIVE'
+ | 'INIT_FAILED'
+ | 'REMOVED'
+ | 'RESTARTING'
+ | 'UNKNOWN'
+ | 'UPGRADING'
+ | 'PAUSING'
+ | 'RESTORING'
+ | 'RESTORE_FAILED'
+ | 'PAUSE_FAILED'
+ | 'RESIZING'
+ }
+ V1ProjectWithDatabaseResponse: {
+ /**
+ * @description Creation timestamp
+ * @example 2023-03-29T16:32:59Z
+ */
+ created_at: string
+ database: components['schemas']['V1DatabaseResponse']
/** @description Id of your project */
id: string
/** @description Name of your project */
@@ -6822,6 +7039,7 @@ export interface components {
V1UpdateFunctionBody: {
body?: string
name?: string
+ resource_multiplier?: string
verify_jwt?: boolean
}
ValidateQueryBody: {
@@ -7762,6 +7980,71 @@ export interface operations {
}
}
}
+ /** List valid backups to clone from */
+ CloneController_getValidBackups: {
+ parameters: {
+ path: {
+ /** @description Project ref */
+ ref: string
+ }
+ }
+ responses: {
+ 200: {
+ content: {
+ 'application/json': components['schemas']['BackupsResponse']
+ }
+ }
+ /** @description Failed to list available valid backups */
+ 500: {
+ content: never
+ }
+ }
+ }
+ /** Clone the current project from a backup */
+ CloneController_cloneCurrentProject: {
+ parameters: {
+ path: {
+ /** @description Project ref */
+ ref: string
+ }
+ }
+ requestBody: {
+ content: {
+ 'application/json': components['schemas']['CloneProjectDto']
+ }
+ }
+ responses: {
+ 201: {
+ content: {
+ 'application/json': components['schemas']['ProjectClonedResponse']
+ }
+ }
+ /** @description Failed to clone the current project */
+ 500: {
+ content: never
+ }
+ }
+ }
+ /** Retrieve the current status of an existing cloning process */
+ CloneController_cloneProjectStatus: {
+ parameters: {
+ path: {
+ /** @description Project ref */
+ ref: string
+ }
+ }
+ responses: {
+ 200: {
+ content: {
+ 'application/json': components['schemas']['ProjectClonedStatusResponse']
+ }
+ }
+ /** @description Failed to retrieve clone project status */
+ 500: {
+ content: never
+ }
+ }
+ }
/** Enables Database Webhooks on the project */
HooksController_enableHooks: {
parameters: {
@@ -8559,6 +8842,29 @@ export interface operations {
}
}
}
+ /** Gets the payment link to manually pay the given invoice */
+ OrgInvoicesController_getInvoicePaymentLink: {
+ parameters: {
+ path: {
+ invoiceId: string
+ slug: string
+ }
+ }
+ responses: {
+ 200: {
+ content: {
+ 'application/json': components['schemas']['InvoicePaymentLinkResponse']
+ }
+ }
+ 403: {
+ content: never
+ }
+ /** @description Failed to retrieve invoice payment link */
+ 500: {
+ content: never
+ }
+ }
+ }
/** Gets the upcoming invoice */
OrgInvoicesController_getUpcomingInvoice: {
parameters: {
@@ -11811,8 +12117,8 @@ export interface operations {
}
}
}
- /** Lists project's warehouse collections from logflare */
- 'v1-list-all-warehouse-collections': {
+ /** Lists project's telemetry collections from logflare */
+ 'v1-list-all-telemetry-collections': {
parameters: {
path: {
/** @description Project ref */
@@ -11828,14 +12134,14 @@ export interface operations {
403: {
content: never
}
- /** @description Failed to fetch warehouse collections */
+ /** @description Failed to fetch telemetry collections */
500: {
content: never
}
}
}
- /** Create a warehouse collection */
- 'v1-create-a-warehouse-collection': {
+ /** Create a telemetry collection */
+ 'v1-create-a-telemetry-collection': {
responses: {
201: {
content: {
@@ -11845,14 +12151,14 @@ export interface operations {
403: {
content: never
}
- /** @description Failed to create warehouse collection */
+ /** @description Failed to create telemetry collection */
500: {
content: never
}
}
}
- /** Get a warehouse collection */
- 'v1-get-a-warehouse-collection': {
+ /** Get a telemetry collection */
+ 'v1-get-a-telemetry-collection': {
responses: {
200: {
content: {
@@ -11862,14 +12168,14 @@ export interface operations {
403: {
content: never
}
- /** @description Failed to fetch warehouse collection */
+ /** @description Failed to fetch telemetry collection */
500: {
content: never
}
}
}
- /** Delete a warehouse collection */
- 'v1-delete-a-warehouse-collection': {
+ /** Delete a telemetry collection */
+ 'v1-delete-a-telemetry-collection': {
responses: {
200: {
content: {
@@ -11879,14 +12185,14 @@ export interface operations {
403: {
content: never
}
- /** @description Failed to delete warehouse collection */
+ /** @description Failed to delete telemetry collection */
500: {
content: never
}
}
}
- /** Update a warehouse collection */
- 'v1-update-a-warehouse-collection': {
+ /** Update a telemetry collection */
+ 'v1-update-a-telemetry-collection': {
responses: {
200: {
content: {
@@ -11896,7 +12202,24 @@ export interface operations {
403: {
content: never
}
- /** @description Failed to update warehouse collection */
+ /** @description Failed to update telemetry collection */
+ 500: {
+ content: never
+ }
+ }
+ }
+ /** Get a telemetry collection schema */
+ 'v1-get-a-telemetry-collection-schema': {
+ responses: {
+ 200: {
+ content: {
+ 'application/json': components['schemas']['LFSource']
+ }
+ }
+ 403: {
+ content: never
+ }
+ /** @description Failed to fetch telemetry collection schema */
500: {
content: never
}
@@ -12912,6 +13235,57 @@ export interface operations {
}
}
}
+ /** Gets disk autoscale config */
+ DiskController_getDiskAutoscaleConfig: {
+ parameters: {
+ path: {
+ /** @description Project ref */
+ ref: string
+ }
+ }
+ responses: {
+ 200: {
+ content: {
+ 'application/json': components['schemas']['DiskAutoscaleConfig']
+ }
+ }
+ 403: {
+ content: never
+ }
+ /** @description Failed to get project disk autoscale config */
+ 500: {
+ content: never
+ }
+ }
+ }
+ /** Updates disk autoscale config */
+ DiskController_updateDiskAutoscaleConfig: {
+ parameters: {
+ path: {
+ /** @description Project ref */
+ ref: string
+ }
+ }
+ requestBody: {
+ content: {
+ 'application/json': components['schemas']['UpdateDiskAutoscaleConfig']
+ }
+ }
+ responses: {
+ 201: {
+ content: {
+ 'application/json': components['schemas']['DiskAutoscaleConfig']
+ }
+ }
+ 403: {
+ content: never
+ }
+ /** @description Failed to update project disk autoscale config */
+ 500: {
+ content: never
+ }
+ }
+ }
/** Get disk utilization */
DiskController_getDiskUtilization: {
parameters: {
@@ -13246,6 +13620,34 @@ export interface operations {
}
}
}
+ /** Updates the given project sensitivity */
+ SensitivityController_updateProjectSensitivity: {
+ parameters: {
+ path: {
+ /** @description Project ref */
+ ref: string
+ }
+ }
+ requestBody: {
+ content: {
+ 'application/json': components['schemas']['MarkSensitiveBody']
+ }
+ }
+ responses: {
+ 200: {
+ content: {
+ 'application/json': components['schemas']['ProjectSensitivityResponse']
+ }
+ }
+ 403: {
+ content: never
+ }
+ /** @description Failed to update project */
+ 500: {
+ content: never
+ }
+ }
+ }
/** Gets project's status */
ProjectStatusController_getStatus: {
parameters: {
@@ -14479,6 +14881,24 @@ export interface operations {
}
}
}
+ /** Deletes organization */
+ OrganizationSlugSystemController_deleteOrganization: {
+ parameters: {
+ path: {
+ /** @description Organization slug */
+ slug: string
+ }
+ }
+ responses: {
+ 200: {
+ content: never
+ }
+ /** @description Failed to delete organization */
+ 500: {
+ content: never
+ }
+ }
+ }
/** Gets the current subscription */
OrgSubscriptionSystemController_getSubscription: {
parameters: {
@@ -14627,6 +15047,20 @@ export interface operations {
}
}
}
+ /** Deletes the given project */
+ ProjectRefSystemController_deleteProject: {
+ parameters: {
+ path: {
+ /** @description Project ref */
+ ref: string
+ }
+ }
+ responses: {
+ 204: {
+ content: never
+ }
+ }
+ }
/** Updates project addon */
AddonsController_updateAddon: {
parameters: {
@@ -14669,6 +15103,71 @@ export interface operations {
}
}
}
+ /** Gets the current email restrictions for a project. */
+ ProjectEmailRestrictionsController_getEmailRestrictions: {
+ parameters: {
+ path: {
+ /** @description Project ref */
+ ref: string
+ }
+ }
+ responses: {
+ 200: {
+ content: {
+ 'application/json': components['schemas']['EmailRestrictionsResponseBody']
+ }
+ }
+ /** @description Failed to retrieve email restrictions. */
+ 500: {
+ content: never
+ }
+ }
+ }
+ /** Updates the current email restrictions for a project. */
+ ProjectEmailRestrictionsController_updateEmailRestrictions: {
+ parameters: {
+ path: {
+ /** @description Project ref */
+ ref: string
+ }
+ }
+ requestBody: {
+ content: {
+ 'application/json': components['schemas']['UpdateRestrictionsRequestBody']
+ }
+ }
+ responses: {
+ 200: {
+ content: {
+ 'application/json': components['schemas']['EmailRestrictionsResponseBody']
+ }
+ }
+ /** @description Failed to update email restrictions. */
+ 500: {
+ content: never
+ }
+ }
+ }
+ /** Removes the current email restrictions for a project. */
+ ProjectEmailRestrictionsController_deleteEmailRestrictions: {
+ parameters: {
+ path: {
+ /** @description Project ref */
+ ref: string
+ }
+ }
+ responses: {
+ 200: {
+ content: {
+ 'application/json': components['schemas']['EmailRestrictionsResponseBody']
+ }
+ }
+ /** @description Failed to remove email restrictions. */
+ 500: {
+ content: never
+ }
+ }
+ }
/** Handle update project jwt on completion */
ProjectUpdateJwtController_completeUpdateJwt: {
parameters: {
@@ -14757,6 +15256,57 @@ export interface operations {
}
}
}
+ /** Gets disk autoscale config */
+ SystemProjectDiskController_getDiskAutoscaleConfig: {
+ parameters: {
+ path: {
+ /** @description Project ref */
+ ref: string
+ }
+ }
+ responses: {
+ 200: {
+ content: {
+ 'application/json': components['schemas']['DiskAutoscaleConfig']
+ }
+ }
+ 403: {
+ content: never
+ }
+ /** @description Failed to get project disk autoscale config */
+ 500: {
+ content: never
+ }
+ }
+ }
+ /** Updates disk autoscale config */
+ SystemProjectDiskController_updateDiskAutoscaleConfig: {
+ parameters: {
+ path: {
+ /** @description Project ref */
+ ref: string
+ }
+ }
+ requestBody: {
+ content: {
+ 'application/json': components['schemas']['UpdateDiskAutoscaleConfig']
+ }
+ }
+ responses: {
+ 201: {
+ content: {
+ 'application/json': components['schemas']['DiskAutoscaleConfig']
+ }
+ }
+ 403: {
+ content: never
+ }
+ /** @description Failed to update project disk autoscale config */
+ 500: {
+ content: never
+ }
+ }
+ }
/** Get disk utilization */
SystemProjectDiskController_getDiskUtilization: {
parameters: {
@@ -14833,6 +15383,7 @@ export interface operations {
import_map?: boolean
entrypoint_path?: string
import_map_path?: string
+ resource_multiplier?: string
}
path: {
/** @description Project ref */
@@ -14913,6 +15464,7 @@ export interface operations {
import_map?: boolean
entrypoint_path?: string
import_map_path?: string
+ resource_multiplier?: string
}
path: {
/** @description Project ref */
@@ -15134,6 +15686,18 @@ export interface operations {
}
}
}
+ /** Reports email abuse from a postmark */
+ ProjectEmailAbuseController_reportEmailAbuseWebhookPostmark: {
+ responses: {
+ 201: {
+ content: never
+ }
+ /** @description Failed to report email abuse */
+ 500: {
+ content: never
+ }
+ }
+ }
/** Processes Stripe event */
StripeWebhooksController_processEvent: {
parameters: {
@@ -15328,7 +15892,7 @@ export interface operations {
'v1-create-an-organization': {
requestBody: {
content: {
- 'application/json': components['schemas']['CreateOrganizationBodyV1']
+ 'application/json': components['schemas']['CreateOrganizationV1Dto']
}
}
responses: {
@@ -15381,7 +15945,7 @@ export interface operations {
responses: {
200: {
content: {
- 'application/json': components['schemas']['V1ProjectResponse'][]
+ 'application/json': components['schemas']['V1ProjectWithDatabaseResponse'][]
}
}
}
@@ -15390,7 +15954,7 @@ export interface operations {
'v1-create-a-project': {
requestBody: {
content: {
- 'application/json': components['schemas']['V1CreateProjectBody']
+ 'application/json': components['schemas']['V1CreateProjectBodyDto']
}
}
responses: {
@@ -15412,7 +15976,7 @@ export interface operations {
responses: {
200: {
content: {
- 'application/json': components['schemas']['V1ProjectResponse']
+ 'application/json': components['schemas']['V1ProjectWithDatabaseResponse']
}
}
/** @description Failed to retrieve project */
@@ -15443,6 +16007,9 @@ export interface operations {
/** Get project api keys */
'v1-get-project-api-keys': {
parameters: {
+ query: {
+ reveal: boolean
+ }
path: {
/** @description Project ref */
ref: string
@@ -15459,6 +16026,9 @@ export interface operations {
/** [Alpha] Creates a new API key for the project */
ApiKeysController_createApiKey: {
parameters: {
+ query: {
+ reveal: boolean
+ }
path: {
/** @description Project ref */
ref: string
@@ -15477,9 +16047,32 @@ export interface operations {
}
}
}
+ /** [Alpha] Get API key */
+ ApiKeysController_getApiKey: {
+ parameters: {
+ query: {
+ reveal: boolean
+ }
+ path: {
+ /** @description Project ref */
+ ref: string
+ id: string
+ }
+ }
+ responses: {
+ 200: {
+ content: {
+ 'application/json': components['schemas']['ApiKeyResponse']
+ }
+ }
+ }
+ }
/** [Alpha] Deletes an API key for the project */
ApiKeysController_deleteApiKey: {
parameters: {
+ query: {
+ reveal: boolean
+ }
path: {
/** @description Project ref */
ref: string
@@ -15500,6 +16093,9 @@ export interface operations {
/** [Alpha] Updates an API key for the project */
ApiKeysController_updateApiKey: {
parameters: {
+ query: {
+ reveal: boolean
+ }
path: {
/** @description Project ref */
ref: string
@@ -16804,7 +17400,7 @@ export interface operations {
'v1-get-a-snippet': {
parameters: {
path: {
- id: string
+ id: Record
}
}
responses: {
diff --git a/packages/ui-patterns/TimestampInfo/index.tsx b/packages/ui-patterns/TimestampInfo/index.tsx
index 505a610611e..c7f49108553 100644
--- a/packages/ui-patterns/TimestampInfo/index.tsx
+++ b/packages/ui-patterns/TimestampInfo/index.tsx
@@ -84,12 +84,22 @@ export const TimestampInfo = ({
setCopied(false)
}, 1000)
}}
- className={cn('grid grid-cols-2 gap-2 hover:bg-surface-100 px-2 py-1', {
- 'bg-surface-100': copied,
- })}
+ className={cn(
+ 'relative cursor-default grid grid-cols-2 gap-2 hover:bg-surface-100 px-2 py-1',
+ {
+ 'bg-surface-100': copied,
+ }
+ )}
>
{label}:
- {copied ? 'Copied!' : value}
+
+ {copied && (
+
+ Copied!
+
+ )}
+ {value}
+
)
}