From 6507ea298794ebd490db21ef2e065daa2f12c8f2 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Tue, 21 Jun 2022 14:15:18 +0800 Subject: [PATCH] update page --- .../layouts/AuthLayout/AuthLayout.utils.ts | 23 +- .../pages/project/[ref]/auth/settings-new.tsx | 32 - .../pages/project/[ref]/auth/settings-old.tsx | 1132 ++++++++++++++++ studio/pages/project/[ref]/auth/settings.tsx | 1144 +---------------- 4 files changed, 1164 insertions(+), 1167 deletions(-) delete mode 100644 studio/pages/project/[ref]/auth/settings-new.tsx create mode 100644 studio/pages/project/[ref]/auth/settings-old.tsx diff --git a/studio/components/layouts/AuthLayout/AuthLayout.utils.ts b/studio/components/layouts/AuthLayout/AuthLayout.utils.ts index 37272a7c9f9..17de5da94ea 100644 --- a/studio/components/layouts/AuthLayout/AuthLayout.utils.ts +++ b/studio/components/layouts/AuthLayout/AuthLayout.utils.ts @@ -10,7 +10,6 @@ export const generateAuthMenu = (ref: string): ProductMenuGroup[] => { items: [ { name: 'Users', key: 'users', url: `/project/${ref}/auth/users`, items: [] }, { name: 'Policies', key: 'policies', url: `/project/${ref}/auth/policies`, items: [] }, - { name: 'Templates', key: 'templates', url: `/project/${ref}/auth/templates`, items: [] }, ...(IS_PLATFORM && logsAuth ? [ { @@ -28,18 +27,16 @@ export const generateAuthMenu = (ref: string): ProductMenuGroup[] => { { title: 'Configuration', items: [ - { - name: 'Settings', - key: 'settings', - url: `/project/${ref}/auth/settings`, - items: [], - }, - { - name: 'Settings new', - key: 'settings-new', - url: `/project/${ref}/auth/settings-new`, - items: [], - }, + ...(IS_PLATFORM && logsAuth + ? [ + { + name: 'Settings', + key: 'settings', + url: `/project/${ref}/auth/settings`, + items: [], + }, + ] + : []), { name: 'Templates', key: 'settings-templates', diff --git a/studio/pages/project/[ref]/auth/settings-new.tsx b/studio/pages/project/[ref]/auth/settings-new.tsx deleted file mode 100644 index 7b94774a78d..00000000000 --- a/studio/pages/project/[ref]/auth/settings-new.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { AuthProvidersForm } from 'components/interfaces' -import RedirectDomains from 'components/interfaces/Auth/RedirectDomains' -import { AuthLayout } from 'components/layouts' -import { AutoSchemaForm, FormsContainer } from 'components/ui/Forms' -import { useStore } from 'hooks' -import { observer } from 'mobx-react-lite' -import React, { useEffect } from 'react' -import { NextPageWithLayout } from 'types' - -const PageLayout: NextPageWithLayout = () => { - const { ui, authConfig } = useStore() - - useEffect(() => { - // temporary store loader - authConfig.load() - }, [ui.selectedProjectRef]) - - if (authConfig) - return ( - - - - - - ) -} - -PageLayout.getLayout = (page) => { - return {page} -} - -export default observer(PageLayout) diff --git a/studio/pages/project/[ref]/auth/settings-old.tsx b/studio/pages/project/[ref]/auth/settings-old.tsx new file mode 100644 index 00000000000..e32e1f4cfd8 --- /dev/null +++ b/studio/pages/project/[ref]/auth/settings-old.tsx @@ -0,0 +1,1132 @@ +import useSWR from 'swr' +import { observer } from 'mobx-react-lite' +import { useState, useEffect } from 'react' +import { useRouter } from 'next/router' +import { AutoField, NumField } from 'uniforms-bootstrap4' +import { Button, Typography, Toggle as UIToggle } from '@supabase/ui' +import Divider from 'components/ui/Divider' + +import { API_URL } from 'lib/constants' +import { useStore } from 'hooks' +import { authConfig } from 'stores/jsonSchema' +import { get, patch } from 'lib/common/fetch' +import { pluckJsonSchemaFields } from 'lib/helpers' +import { AuthLayout } from 'components/layouts' +import Table from 'components/to-be-cleaned/Table' +import Panel from 'components/ui/Panel' +import ToggleField from 'components/to-be-cleaned/forms/ToggleField' +import SecretField from 'components/to-be-cleaned/forms/SecretField' +import SchemaFormPanel from 'components/to-be-cleaned/forms/SchemaFormPanel' +import { NextPageWithLayout } from 'types' + +const Auth: NextPageWithLayout = () => { + return ( +
+ +
+ ) +} + +Auth.getLayout = (page) => {page} + +export default observer(Auth) + +const Settings = () => { + const router = useRouter() + const { ui } = useStore() + + const [model, setModel] = useState({}) + const [smsProviderModel, setSmsProviderModel] = useState({}) + const [externalProvidersModel, setExternalProvidersModel] = useState({}) + const [isCustomSMTPEnabled, setCustomSMTP] = useState(false) + const URL = `${API_URL}/auth/${router.query.ref}/config` + const { data: config, error }: any = useSWR(URL, get) + const { ref: projectRef } = router.query + + if (error) { + return ( + +

Error connecting to API

+

{`${error}`}

+
+ ) + } + + useEffect(() => { + if (config) { + const temp = + config.SMTP_ADMIN_EMAIL || + config.SMTP_HOST || + config.SMTP_PORT || + config.SMTP_USER || + config.SMTP_PASS + setCustomSMTP(temp) + } + setModel({ ...config }) + setExternalProvidersModel({ ...config }) + }, [config]) + + const onFormSubmit = async (model: any) => { + for (const [key, value] of Object.entries(model)) { + // remove any whitespaces in OAuth or Sms provider credentials + if ((key.includes('EXTERNAL') || key.includes('SMS')) && typeof value === 'string') { + model[key] = value.replace(/\s/g, '') + } + } + const response = await patch(URL, model) + if (response.error) { + ui.setNotification({ + category: 'error', + message: `Update config failed: ${response.error.message}`, + }) + } else { + const updates = response + setModel({ ...updates }) + ui.setNotification({ category: 'success', message: 'Settings saved' }) + } + } + + /* + * handleToggle + * + * This can be used for toggles that are not in a schema form + */ + const handleToggle = async (key: any, value?: any) => { + const payload = { + [key]: value ? value : !model[key], + } + + try { + const response = await patch(URL, payload) + if (response.error) throw response.error + const updates = response + setModel({ ...updates }) + ui.setNotification({ category: 'success', message: 'Settings saved' }) + } catch (error: any) { + ui.setNotification({ category: 'error', message: `Update config failed: ${error.message}` }) + } + } + + const onFormReset = () => { + setExternalProvidersModel({ ...config }) + } + + return ( +
+
+ onFormSubmit(model)} + > + + + + + + + + +
+
+ + onFormSubmit({ + ...model, + }) + } + > + { + handleToggle('EXTERNAL_EMAIL_ENABLED', value) + }} + checked={model.EXTERNAL_EMAIL_ENABLED} + descriptionText={authConfig.properties.EXTERNAL_EMAIL_ENABLED.help} + /> + + { + handleToggle('MAILER_SECURE_EMAIL_CHANGE_ENABLED', value) + }} + checked={model.MAILER_SECURE_EMAIL_CHANGE_ENABLED} + descriptionText={authConfig.properties.MAILER_SECURE_EMAIL_CHANGE_ENABLED.help} + /> + + { + // If MAILER_AUTOCONFIRM is set to true, it means we are disabling email confirmations. + // "Enable email confirmations" should be toggled off. + handleToggle('MAILER_AUTOCONFIRM', !value) + }} + checked={!model.MAILER_AUTOCONFIRM} + descriptionText={authConfig.properties.MAILER_AUTOCONFIRM.help} + /> + + { + /* + * temporary solution + * clear the values of SMTP when toggling + */ + if (!value) { + onFormSubmit({ + SMTP_ADMIN_EMAIL: '', + SMTP_HOST: '', + SMTP_PORT: '', + SMTP_USER: '', + SMTP_PASS: '', + SMTP_SENDER_NAME: '', + RATE_LIMIT_EMAIL_SENT: 30, + }) + } + setCustomSMTP(!isCustomSMTPEnabled) + }} + /> + {isCustomSMTPEnabled && ( + <> + + + + + + + + + )} + +
+ +
+ setSmsProviderModel(model)} + onSubmit={(model: any) => onFormSubmit(model)} + > + <> + { + handleToggle('EXTERNAL_PHONE_ENABLED') + }} + checked={model.EXTERNAL_PHONE_ENABLED} + descriptionText={authConfig.properties.EXTERNAL_PHONE_ENABLED.help} + /> + + {model.EXTERNAL_PHONE_ENABLED && ( + <> + + + + {smsProviderModel?.SMS_PROVIDER === 'messagebird' ? ( + <> + + + + ) : smsProviderModel?.SMS_PROVIDER === 'textlocal' ? ( + <> + + + + ) : smsProviderModel?.SMS_PROVIDER === 'vonage' ? ( + <> + + + + + ) : ( + <> + + + + + )} + + )} + { + // If SMS_AUTOCONFIRM is set to true, it means we are disabling phone confirmations. + // "Enable phone confirmations" should be toggled off. + handleToggle('SMS_AUTOCONFIRM', !value) + }} + // + checked={!model.SMS_AUTOCONFIRM} + descriptionText={authConfig.properties.SMS_AUTOCONFIRM.help} + /> + + +
+
+ setExternalProvidersModel(model)} + onReset={() => onFormReset()} + onSubmit={(model: any) => onFormSubmit(model)} + > + + Create new credentials + + ) + } + /> + {externalProvidersModel.EXTERNAL_APPLE_ENABLED && ( + <> + + + + )} + + + Create new credentials + + ) + } + /> + {externalProvidersModel.EXTERNAL_AZURE_ENABLED && ( + <> + + + + + )} + + + Create new credentials + + ) + } + /> + {externalProvidersModel.EXTERNAL_BITBUCKET_ENABLED && ( + <> + + + + )} + + + Create new credentials + + ) + } + /> + {externalProvidersModel.EXTERNAL_DISCORD_ENABLED && ( + <> + + + + )} + + + Create new credentials + + ) + } + /> + {externalProvidersModel.EXTERNAL_FACEBOOK_ENABLED && ( + <> + + + + )} + + + Create new credentials + + ) + } + /> + {externalProvidersModel.EXTERNAL_GITHUB_ENABLED && ( + <> + + + + )} + + + Create new credentials + + ) + } + /> + {externalProvidersModel.EXTERNAL_GITLAB_ENABLED && ( + <> + + + + )} + + + Create new credentials + + ) + } + /> + {externalProvidersModel.EXTERNAL_GOOGLE_ENABLED && ( + <> + + + + )} + + + Create new credentials + + ) + } + /> + {externalProvidersModel.EXTERNAL_KEYCLOAK_ENABLED && ( + <> + + + + + )} + + + Create new credentials + + ) + } + /> + {externalProvidersModel.EXTERNAL_LINKEDIN_ENABLED && ( + <> + + + + )} + + + Create new credentials + + ) + } + /> + {externalProvidersModel.EXTERNAL_NOTION_ENABLED && ( + <> + + + + )} + + + Create new credentials + + ) + } + /> + {externalProvidersModel.EXTERNAL_TWITCH_ENABLED && ( + <> + + + + )} + + + Create new credentials + + ) + } + /> + {externalProvidersModel.EXTERNAL_TWITTER_ENABLED && ( + <> + + + + )} + + + Create new credentials + + ) + } + /> + {externalProvidersModel.EXTERNAL_SLACK_ENABLED && ( + <> + + + + )} + + + Create new credentials + + ) + } + /> + {externalProvidersModel.EXTERNAL_SPOTIFY_ENABLED && ( + <> + + + + )} + + + Create new credentials + + ) + } + /> + {externalProvidersModel.EXTERNAL_WORKOS_ENABLED && ( + <> + + + + + )} + + + Create new credentials + + ) + } + /> + {externalProvidersModel.EXTERNAL_ZOOM_ENABLED && ( + <> + + + + )} + +
+ +
+ {config && ( + + )} +
+
+ ) +} + +const AuditLog = ({ interval, projectRef }: any) => { + const { meta } = useStore() + const [logs, setLogs] = useState([]) + const [isLoading, setIsLoading] = useState(false) + + const refreshLogs = async () => { + setIsLoading(true) + try { + let query = ` + SELECT + * + FROM + auth.audit_log_entries + WHERE + created_at > CURRENT_TIMESTAMP - interval '${interval}' + ORDER BY + created_at DESC + LIMIT 1000; + ` + const response = await meta.query(query) + if (response.error) throw response.error + setLogs(response) + } catch (error) { + console.error('Refresh logs error:', error) + } finally { + setIsLoading(false) + } + } + + useEffect(() => { + refreshLogs() + }, []) + + if (!logs.length) return
+ const cellCss = 'table-cell px-4 py-3 text-gray-200 font-mono text-xs' + return ( + + + Audit Trail + +
+ +
+ + } + > + + Time + Action + User ID + Identity + + } + body={logs.map((x: any, i: number) => ( + + + {x.payload.timestamp.replace('T', ' ')} + + + {x.payload.action} + + + {x.payload.actor_id || x.payload.traits?.user_id} + + + + {x.payload.actor_username || x.payload.traits?.user_email} + + + + ))} + /> + + ) +} diff --git a/studio/pages/project/[ref]/auth/settings.tsx b/studio/pages/project/[ref]/auth/settings.tsx index e32e1f4cfd8..7b94774a78d 100644 --- a/studio/pages/project/[ref]/auth/settings.tsx +++ b/studio/pages/project/[ref]/auth/settings.tsx @@ -1,1132 +1,32 @@ -import useSWR from 'swr' -import { observer } from 'mobx-react-lite' -import { useState, useEffect } from 'react' -import { useRouter } from 'next/router' -import { AutoField, NumField } from 'uniforms-bootstrap4' -import { Button, Typography, Toggle as UIToggle } from '@supabase/ui' -import Divider from 'components/ui/Divider' - -import { API_URL } from 'lib/constants' -import { useStore } from 'hooks' -import { authConfig } from 'stores/jsonSchema' -import { get, patch } from 'lib/common/fetch' -import { pluckJsonSchemaFields } from 'lib/helpers' +import { AuthProvidersForm } from 'components/interfaces' +import RedirectDomains from 'components/interfaces/Auth/RedirectDomains' import { AuthLayout } from 'components/layouts' -import Table from 'components/to-be-cleaned/Table' -import Panel from 'components/ui/Panel' -import ToggleField from 'components/to-be-cleaned/forms/ToggleField' -import SecretField from 'components/to-be-cleaned/forms/SecretField' -import SchemaFormPanel from 'components/to-be-cleaned/forms/SchemaFormPanel' +import { AutoSchemaForm, FormsContainer } from 'components/ui/Forms' +import { useStore } from 'hooks' +import { observer } from 'mobx-react-lite' +import React, { useEffect } from 'react' import { NextPageWithLayout } from 'types' -const Auth: NextPageWithLayout = () => { - return ( -
- -
- ) -} +const PageLayout: NextPageWithLayout = () => { + const { ui, authConfig } = useStore() -Auth.getLayout = (page) => {page} + useEffect(() => { + // temporary store loader + authConfig.load() + }, [ui.selectedProjectRef]) -export default observer(Auth) - -const Settings = () => { - const router = useRouter() - const { ui } = useStore() - - const [model, setModel] = useState({}) - const [smsProviderModel, setSmsProviderModel] = useState({}) - const [externalProvidersModel, setExternalProvidersModel] = useState({}) - const [isCustomSMTPEnabled, setCustomSMTP] = useState(false) - const URL = `${API_URL}/auth/${router.query.ref}/config` - const { data: config, error }: any = useSWR(URL, get) - const { ref: projectRef } = router.query - - if (error) { + if (authConfig) return ( - -

Error connecting to API

-

{`${error}`}

-
+ + + + + ) - } - - useEffect(() => { - if (config) { - const temp = - config.SMTP_ADMIN_EMAIL || - config.SMTP_HOST || - config.SMTP_PORT || - config.SMTP_USER || - config.SMTP_PASS - setCustomSMTP(temp) - } - setModel({ ...config }) - setExternalProvidersModel({ ...config }) - }, [config]) - - const onFormSubmit = async (model: any) => { - for (const [key, value] of Object.entries(model)) { - // remove any whitespaces in OAuth or Sms provider credentials - if ((key.includes('EXTERNAL') || key.includes('SMS')) && typeof value === 'string') { - model[key] = value.replace(/\s/g, '') - } - } - const response = await patch(URL, model) - if (response.error) { - ui.setNotification({ - category: 'error', - message: `Update config failed: ${response.error.message}`, - }) - } else { - const updates = response - setModel({ ...updates }) - ui.setNotification({ category: 'success', message: 'Settings saved' }) - } - } - - /* - * handleToggle - * - * This can be used for toggles that are not in a schema form - */ - const handleToggle = async (key: any, value?: any) => { - const payload = { - [key]: value ? value : !model[key], - } - - try { - const response = await patch(URL, payload) - if (response.error) throw response.error - const updates = response - setModel({ ...updates }) - ui.setNotification({ category: 'success', message: 'Settings saved' }) - } catch (error: any) { - ui.setNotification({ category: 'error', message: `Update config failed: ${error.message}` }) - } - } - - const onFormReset = () => { - setExternalProvidersModel({ ...config }) - } - - return ( -
-
- onFormSubmit(model)} - > - - - - - - - - -
-
- - onFormSubmit({ - ...model, - }) - } - > - { - handleToggle('EXTERNAL_EMAIL_ENABLED', value) - }} - checked={model.EXTERNAL_EMAIL_ENABLED} - descriptionText={authConfig.properties.EXTERNAL_EMAIL_ENABLED.help} - /> - - { - handleToggle('MAILER_SECURE_EMAIL_CHANGE_ENABLED', value) - }} - checked={model.MAILER_SECURE_EMAIL_CHANGE_ENABLED} - descriptionText={authConfig.properties.MAILER_SECURE_EMAIL_CHANGE_ENABLED.help} - /> - - { - // If MAILER_AUTOCONFIRM is set to true, it means we are disabling email confirmations. - // "Enable email confirmations" should be toggled off. - handleToggle('MAILER_AUTOCONFIRM', !value) - }} - checked={!model.MAILER_AUTOCONFIRM} - descriptionText={authConfig.properties.MAILER_AUTOCONFIRM.help} - /> - - { - /* - * temporary solution - * clear the values of SMTP when toggling - */ - if (!value) { - onFormSubmit({ - SMTP_ADMIN_EMAIL: '', - SMTP_HOST: '', - SMTP_PORT: '', - SMTP_USER: '', - SMTP_PASS: '', - SMTP_SENDER_NAME: '', - RATE_LIMIT_EMAIL_SENT: 30, - }) - } - setCustomSMTP(!isCustomSMTPEnabled) - }} - /> - {isCustomSMTPEnabled && ( - <> - - - - - - - - - )} - -
- -
- setSmsProviderModel(model)} - onSubmit={(model: any) => onFormSubmit(model)} - > - <> - { - handleToggle('EXTERNAL_PHONE_ENABLED') - }} - checked={model.EXTERNAL_PHONE_ENABLED} - descriptionText={authConfig.properties.EXTERNAL_PHONE_ENABLED.help} - /> - - {model.EXTERNAL_PHONE_ENABLED && ( - <> - - - - {smsProviderModel?.SMS_PROVIDER === 'messagebird' ? ( - <> - - - - ) : smsProviderModel?.SMS_PROVIDER === 'textlocal' ? ( - <> - - - - ) : smsProviderModel?.SMS_PROVIDER === 'vonage' ? ( - <> - - - - - ) : ( - <> - - - - - )} - - )} - { - // If SMS_AUTOCONFIRM is set to true, it means we are disabling phone confirmations. - // "Enable phone confirmations" should be toggled off. - handleToggle('SMS_AUTOCONFIRM', !value) - }} - // - checked={!model.SMS_AUTOCONFIRM} - descriptionText={authConfig.properties.SMS_AUTOCONFIRM.help} - /> - - -
-
- setExternalProvidersModel(model)} - onReset={() => onFormReset()} - onSubmit={(model: any) => onFormSubmit(model)} - > - - Create new credentials - - ) - } - /> - {externalProvidersModel.EXTERNAL_APPLE_ENABLED && ( - <> - - - - )} - - - Create new credentials - - ) - } - /> - {externalProvidersModel.EXTERNAL_AZURE_ENABLED && ( - <> - - - - - )} - - - Create new credentials - - ) - } - /> - {externalProvidersModel.EXTERNAL_BITBUCKET_ENABLED && ( - <> - - - - )} - - - Create new credentials - - ) - } - /> - {externalProvidersModel.EXTERNAL_DISCORD_ENABLED && ( - <> - - - - )} - - - Create new credentials - - ) - } - /> - {externalProvidersModel.EXTERNAL_FACEBOOK_ENABLED && ( - <> - - - - )} - - - Create new credentials - - ) - } - /> - {externalProvidersModel.EXTERNAL_GITHUB_ENABLED && ( - <> - - - - )} - - - Create new credentials - - ) - } - /> - {externalProvidersModel.EXTERNAL_GITLAB_ENABLED && ( - <> - - - - )} - - - Create new credentials - - ) - } - /> - {externalProvidersModel.EXTERNAL_GOOGLE_ENABLED && ( - <> - - - - )} - - - Create new credentials - - ) - } - /> - {externalProvidersModel.EXTERNAL_KEYCLOAK_ENABLED && ( - <> - - - - - )} - - - Create new credentials - - ) - } - /> - {externalProvidersModel.EXTERNAL_LINKEDIN_ENABLED && ( - <> - - - - )} - - - Create new credentials - - ) - } - /> - {externalProvidersModel.EXTERNAL_NOTION_ENABLED && ( - <> - - - - )} - - - Create new credentials - - ) - } - /> - {externalProvidersModel.EXTERNAL_TWITCH_ENABLED && ( - <> - - - - )} - - - Create new credentials - - ) - } - /> - {externalProvidersModel.EXTERNAL_TWITTER_ENABLED && ( - <> - - - - )} - - - Create new credentials - - ) - } - /> - {externalProvidersModel.EXTERNAL_SLACK_ENABLED && ( - <> - - - - )} - - - Create new credentials - - ) - } - /> - {externalProvidersModel.EXTERNAL_SPOTIFY_ENABLED && ( - <> - - - - )} - - - Create new credentials - - ) - } - /> - {externalProvidersModel.EXTERNAL_WORKOS_ENABLED && ( - <> - - - - - )} - - - Create new credentials - - ) - } - /> - {externalProvidersModel.EXTERNAL_ZOOM_ENABLED && ( - <> - - - - )} - -
- -
- {config && ( - - )} -
-
- ) } -const AuditLog = ({ interval, projectRef }: any) => { - const { meta } = useStore() - const [logs, setLogs] = useState([]) - const [isLoading, setIsLoading] = useState(false) - - const refreshLogs = async () => { - setIsLoading(true) - try { - let query = ` - SELECT - * - FROM - auth.audit_log_entries - WHERE - created_at > CURRENT_TIMESTAMP - interval '${interval}' - ORDER BY - created_at DESC - LIMIT 1000; - ` - const response = await meta.query(query) - if (response.error) throw response.error - setLogs(response) - } catch (error) { - console.error('Refresh logs error:', error) - } finally { - setIsLoading(false) - } - } - - useEffect(() => { - refreshLogs() - }, []) - - if (!logs.length) return
- const cellCss = 'table-cell px-4 py-3 text-gray-200 font-mono text-xs' - return ( - - - Audit Trail - -
- -
- - } - > -
- Time - Action - User ID - Identity - - } - body={logs.map((x: any, i: number) => ( - - - {x.payload.timestamp.replace('T', ' ')} - - - {x.payload.action} - - - {x.payload.actor_id || x.payload.traits?.user_id} - - - - {x.payload.actor_username || x.payload.traits?.user_email} - - - - ))} - /> - - ) +PageLayout.getLayout = (page) => { + return {page} } + +export default observer(PageLayout)