diff --git a/studio/components/interfaces/Organization/GeneralSettings/GeneralSettings.tsx b/studio/components/interfaces/Organization/GeneralSettings/GeneralSettings.tsx index a46a2d8d4ed..6aec5f64ec0 100644 --- a/studio/components/interfaces/Organization/GeneralSettings/GeneralSettings.tsx +++ b/studio/components/interfaces/Organization/GeneralSettings/GeneralSettings.tsx @@ -71,7 +71,6 @@ const GeneralSettings = observer(() => { const hasChanges = JSON.stringify(values) !== JSON.stringify(initialValues) return ( { > {!canUpdateOrganization && (

- You need additional permissions to manage your organization's settings + You need additional permissions to manage this organization's settings

)} = ({ project, type = 'danger' }) => { +const DeleteProjectPanel: FC = ({}) => { const router = useRouter() - const { ui, app } = useStore() + const { app, ui } = useStore() + const project = ui.selectedProject const [isOpen, setIsOpen] = useState(false) const [loading, setLoading] = useState(false) @@ -26,6 +24,8 @@ const DeleteProjectButton: FC = ({ project, type = 'danger' }) => { } async function handleDeleteProject() { + if (project === undefined) return + setLoading(true) try { const response = await delete_(`${API_URL}/projects/${project.ref}`) @@ -42,13 +42,32 @@ const DeleteProjectButton: FC = ({ project, type = 'danger' }) => { } } + if (project === undefined) return <> + return ( <> -
- -
+
+ Danger Zone

}> + + +
+

+ Make sure you have made a backup if you want to keep your data. +

+
+ +
+
+
+
+
+
= ({ project, type = 'danger' }) => { ) } -export default DeleteProjectButton +export default DeleteProjectPanel diff --git a/studio/components/interfaces/Settings/General/General.tsx b/studio/components/interfaces/Settings/General/General.tsx new file mode 100644 index 00000000000..889736d6ace --- /dev/null +++ b/studio/components/interfaces/Settings/General/General.tsx @@ -0,0 +1,89 @@ +import { FC } from 'react' +import { Form, Input } from '@supabase/ui' +import { PermissionAction } from '@supabase/shared-types/out/constants' + +import { useStore, checkPermissions } from 'hooks' +import { API_URL } from 'lib/constants' +import { post } from 'lib/common/fetch' +import { + FormHeader, + FormPanel, + FormActions, + FormSection, + FormSectionLabel, + FormSectionContent, +} from 'components/ui/Forms' + +interface Props {} + +const General: FC = ({}) => { + const { app, ui } = useStore() + const project = ui.selectedProject + + const formId = 'project-general-settings' + const initialValues = { name: project?.name ?? '' } + const canUpdateProject = checkPermissions(PermissionAction.SQL_UPDATE, 'postgres.public.projects') + + const onSubmit = async (values: any, { setSubmitting, resetForm }: any) => { + const response = await post(`${API_URL}/projects/${project?.ref}/update`, values) + if (response.error) { + ui.setNotification({ + category: 'error', + message: `Update project failed: ${response.error.message}`, + }) + } else { + const { name } = response + resetForm({ values: { name }, initialValues: { name } }) + + app.onProjectUpdated(response) + ui.setNotification({ + category: 'success', + message: 'Successfully saved settings', + }) + } + } + + return ( +
+ {({ isSubmitting, handleReset, values, initialValues }: any) => { + const hasChanges = JSON.stringify(values) !== JSON.stringify(initialValues) + return ( + <> + + + + {!canUpdateProject && ( +

+ You need additional permissions to manage this project's settings +

+ )} + + + } + > + General settings}> + + + + +
+ + ) + }} + + ) +} + +export default General diff --git a/studio/components/interfaces/Settings/General/Infrastructure/Infrastructure.tsx b/studio/components/interfaces/Settings/General/Infrastructure/Infrastructure.tsx new file mode 100644 index 00000000000..eab6ab727df --- /dev/null +++ b/studio/components/interfaces/Settings/General/Infrastructure/Infrastructure.tsx @@ -0,0 +1,76 @@ +import { FC } from 'react' +import { Input } from '@supabase/ui' +import { observer } from 'mobx-react-lite' + +import { useStore, useFlag } from 'hooks' +import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants' +import { + FormHeader, + FormPanel, + FormSection, + FormSectionLabel, + FormSectionContent, +} from 'components/ui/Forms' +import PauseProjectButton from './PauseProjectButton' +import RestartServerButton from './RestartServerButton' + +interface Props {} + +const Infrastructure: FC = ({}) => { + const { ui } = useStore() + const isProjectPauseEnabled = useFlag('projectPausing') + + const project = ui.selectedProject + const isFreeProject = project?.subscription_tier === PRICING_TIER_PRODUCT_IDS.FREE + + return ( +
+ + +
+
+

Restart server

+
+

+ Your project will not be available for a few minutes. +

+
+
+ {project && } +
+ + {isProjectPauseEnabled && isFreeProject && ( + <> +
+
+
+

Pause project

+
+

+ Your project will not be accessible while it is paused. +

+
+
+ {project && ( + + )} +
+ + )} + + } + > + Configuration}> + + + + + + +
+ ) +} + +export default observer(Infrastructure) diff --git a/studio/components/interfaces/Settings/General/PauseProjectButton.tsx b/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx similarity index 60% rename from studio/components/interfaces/Settings/General/PauseProjectButton.tsx rename to studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx index 5d19c10b5e0..a925bbd150f 100644 --- a/studio/components/interfaces/Settings/General/PauseProjectButton.tsx +++ b/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx @@ -1,9 +1,11 @@ import { FC, useState } from 'react' import { useRouter } from 'next/router' import { observer } from 'mobx-react-lite' +import * as Tooltip from '@radix-ui/react-tooltip' import { Button, IconPause } from '@supabase/ui' +import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useStore } from 'hooks' +import { checkPermissions, useStore } from 'hooks' import { post } from 'lib/common/fetch' import { API_URL, PROJECT_STATUS } from 'lib/constants' import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog' @@ -22,6 +24,8 @@ const PauseProjectButton: FC = observer(({ projectRef, projectId }) => { const openModal = () => setIsModalOpen(true) const closeModal = () => setIsModalOpen(false) + const canReboot = checkPermissions(PermissionAction.INFRA_EXECUTE, 'reboot') + const requestPauseProject = async () => { setLoading(true) const res = await post(`${API_URL}/projects/${projectRef}/pause`, {}) @@ -45,6 +49,34 @@ const PauseProjectButton: FC = observer(({ projectRef, projectId }) => { return ( <> + + + + + {!canReboot && ( + + +
+ + You need additional permissions to pause this project + +
+
+ )} +
= observer(({ projectRef, projectId }) => { onSelectCancel={closeModal} onSelectConfirm={requestPauseProject} /> - ) }) diff --git a/studio/components/interfaces/Settings/General/RestartServerButton.tsx b/studio/components/interfaces/Settings/General/Infrastructure/RestartServerButton.tsx similarity index 59% rename from studio/components/interfaces/Settings/General/RestartServerButton.tsx rename to studio/components/interfaces/Settings/General/Infrastructure/RestartServerButton.tsx index b981b68d0ad..989be3bc8da 100644 --- a/studio/components/interfaces/Settings/General/RestartServerButton.tsx +++ b/studio/components/interfaces/Settings/General/Infrastructure/RestartServerButton.tsx @@ -1,9 +1,11 @@ import { FC, useState } from 'react' import { useRouter } from 'next/router' import { observer } from 'mobx-react-lite' +import * as Tooltip from '@radix-ui/react-tooltip' import { Button, IconRefreshCcw } from '@supabase/ui' +import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useStore } from 'hooks' +import { useStore, checkPermissions } from 'hooks' import { post } from 'lib/common/fetch' import { API_URL } from 'lib/constants' import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog' @@ -22,6 +24,8 @@ const RestartServerButton: FC = observer(({ projectRef, projectId }) => { const openModal = () => setIsModalOpen(true) const closeModal = () => setIsModalOpen(false) + const canReboot = checkPermissions(PermissionAction.INFRA_EXECUTE, 'reboot') + const requestServerRestart = async () => { setLoading(true) const res = await post(`${API_URL}/projects/${projectRef}/restart`, {}) @@ -43,6 +47,34 @@ const RestartServerButton: FC = observer(({ projectRef, projectId }) => { return ( <> + + + + + {!canReboot && ( + + +
+ + You need additional permissions to restart this project + +
+
+ )} +
= observer(({ projectRef, projectId }) => { onSelectCancel={closeModal} onSelectConfirm={requestServerRestart} /> - ) }) diff --git a/studio/components/interfaces/Settings/General/index.ts b/studio/components/interfaces/Settings/General/index.ts index ad686684336..ab4d1031c5e 100644 --- a/studio/components/interfaces/Settings/General/index.ts +++ b/studio/components/interfaces/Settings/General/index.ts @@ -1,6 +1,5 @@ -import PauseProjectButton from './PauseProjectButton' -import RestartServerButton from './RestartServerButton' +import General from './General' +import Infrastructure from './Infrastructure/Infrastructure' +import DeleteProjectPanel from './DeleteProjectPanel' -import DeleteProjectButton from './DeleteProjectButton' - -export { PauseProjectButton, RestartServerButton, DeleteProjectButton } +export { General, Infrastructure, DeleteProjectPanel } diff --git a/studio/pages/project/[ref]/settings/general.tsx b/studio/pages/project/[ref]/settings/general.tsx index 48210d03acc..7cced5a8f45 100644 --- a/studio/pages/project/[ref]/settings/general.tsx +++ b/studio/pages/project/[ref]/settings/general.tsx @@ -1,163 +1,20 @@ -import { Alert, Input } from '@supabase/ui' -import { toJS } from 'mobx' import { observer } from 'mobx-react-lite' -import { projects } from 'stores/jsonSchema' -import { AutoField } from 'uniforms-bootstrap4' -import { PermissionAction } from '@supabase/shared-types/out/constants' -import { SettingsLayout } from 'components/layouts' -import SchemaFormPanel from 'components/to-be-cleaned/forms/SchemaFormPanel' -import Panel from 'components/ui/Panel' -import { checkPermissions, useStore } from 'hooks' -import { post } from 'lib/common/fetch' -import { API_URL } from 'lib/constants' -import { pluckJsonSchemaFields, pluckObjectFields } from 'lib/helpers' import { NextPageWithLayout } from 'types' - -import { - DeleteProjectButton, - PauseProjectButton, - RestartServerButton, -} from 'components/interfaces/Settings/General' -import { useFlag } from 'hooks' -import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants' +import { SettingsLayout } from 'components/layouts' +import { FormsContainer } from 'components/ui/Forms' +import { General, Infrastructure, DeleteProjectPanel } from 'components/interfaces/Settings/General' const ProjectSettings: NextPageWithLayout = () => { return ( -
-
-
- -
-
-
+ + + + + ) } ProjectSettings.getLayout = (page) => {page} export default observer(ProjectSettings) - -const GeneralSettings = observer(() => { - const { app, ui } = useStore() - const isProjectPauseEnabled = useFlag('projectPausing') - - const project = ui.selectedProject - const formModel = toJS(project) - const BASIC_FIELDS = ['name'] - - // @ts-ignore - const canReboot = checkPermissions(PermissionAction.TENANT_INFRA_EXECUTE, 'reboot') - const canUpdateProject = checkPermissions(PermissionAction.SQL_UPDATE, 'postgres.public.projects') - const isFreeProject = project?.subscription_tier === PRICING_TIER_PRODUCT_IDS.FREE - - const handleUpdateProject = async (model: any) => { - const response = await post(`${API_URL}/projects/${project?.ref}/update`, model) - if (response.error) { - ui.setNotification({ - category: 'error', - message: `Update project failed: ${response.error.message}`, - }) - } else { - const updatedProject = response - app.onProjectUpdated(updatedProject) - ui.setNotification({ - category: 'success', - message: 'Successfully saved settings', - }) - } - } - - return ( -
-
- handleUpdateProject(pluckObjectFields(model, BASIC_FIELDS))} - > - - -
- -
- - Infrastructure - - } - > - - - - - - - -
-
-

Restart server

-
-

- Your project will not be available for a few minutes. -

-
- {project && } -
- {project && } -
-
- {isProjectPauseEnabled && isFreeProject && ( - -
-
-

Pause project

-
-

- Your project will not be accessible while it is paused. -

-
-
- {project && } -
-
- )} -
-
- - {project !== undefined && ( -
- Danger Zone

}> - - -
-

- Make sure you have made a backup if you want to keep your data. -

- -
-
-
-
-
- )} -
- ) -})