diff --git a/DEVELOPERS.md b/DEVELOPERS.md index 505029ef2e6..78cfcda3598 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -116,11 +116,45 @@ Now when you run a local development docs server you will see the new docs site. --- +## Running Docker + +To test your changes, you need to run Supabase using Docker. Docker lets you use features of your locally modified Supabase version. + +#### Prerequsites + +First, make sure you have the Docker application installed on your device. You can download and install it from [here](https://docs.docker.com/get-docker/). + +#### Getting Started + +1. Navigate to the `docker` directory in your forked repo + + ```sh + cd docker + ``` + +2. Copy the example `env` file + + ```sh + cp .env.example .env + ``` + +3. Run docker + + ```sh + docker-compose up + ``` + +This command initializes the containers specified in the docker-compose.yml file. It might take a few moments to complete, depending on your computer and internet connection. + +Once the `docker-compose up` process completes, you should have your local version of Supabase up and running within Docker containers. You can access and test the features you've modified or added. + +Remember to keep the Docker application open as long as you're working with your local Supabase instance. + ## Create a pull request -After making your changes, open a pull request (PR). Once you submit your pull request, others from the Supabase team/community will review it with you. +After making your changes, open a pull request. Once you submit your pull request, the Supabase team will review it with you. -If you have an issue, like a merge conflict, or don't know how to open a pull request then check out [GitHub's pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests) tutorial on how to resolve merge conflicts and other issues. Once your PR has been merged, you will be proudly listed as a contributor in the [contributor chart](https://github.com/supabase/supabase/graphs/contributors). +Once your PR has been merged, you will be proudly listed as a contributor in the [contributor chart](https://github.com/supabase/supabase/graphs/contributors). --- diff --git a/apps/docs/components/HomePageCover.tsx b/apps/docs/components/HomePageCover.tsx index 657083befd2..55d62c39620 100644 --- a/apps/docs/components/HomePageCover.tsx +++ b/apps/docs/components/HomePageCover.tsx @@ -63,7 +63,7 @@ const HomePageCover = (props) => { const GettingStarted = () => (
-
+
@@ -82,7 +82,7 @@ const HomePageCover = (props) => { Discover how to set up a database to an app making queries in just a few minutes.

-
+
-
+
diff --git a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx index 38fec39a043..b38ac77adf7 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx @@ -464,6 +464,8 @@ export const actions = { ```js src/routes/login/+page.server.ts // src/routes/login/+page.server.ts +import { fail } from '@sveltejs/kit' + export const actions = { default: async ({ request, url, locals: { supabase } }) => { const formData = await request.formData() diff --git a/apps/docs/pages/guides/cli/config.tsx b/apps/docs/pages/guides/cli/config.tsx index 6789c50ce74..febc8fe69d5 100644 --- a/apps/docs/pages/guides/cli/config.tsx +++ b/apps/docs/pages/guides/cli/config.tsx @@ -7,17 +7,15 @@ import Head from 'next/head' import { CodeBlock } from 'ui' // Parameters are grouped on the page by tag -const TAGS = ['General', 'Auth', 'API', 'Database', 'Dashboard', 'Local', 'Edge-Functions'] - const tocList = [] -const content = TAGS.map((tag) => { - tocList.push({ text: tag, link: `${tag.toLowerCase()}-config`, level: 2 }) +const content = specFile.info.tags.map((tag) => { + tocList.push({ text: tag.title, link: `${tag.id}-config`, level: 2 }) return (
- {tag} Config + {tag.title} Config {specFile.parameters - .filter((param: Parameter) => param.tags[0] === tag.toLowerCase()) - .map((parameter: Parameter, index) => { + .filter((param: Parameter) => param.tags[0] === tag.id) + .map((parameter: Parameter) => { tocList.push({ text: parameter.id, link: `#${parameter.id}`, level: 3 }) return })} diff --git a/apps/docs/pages/guides/platform/logs.mdx b/apps/docs/pages/guides/platform/logs.mdx index cbf4486c460..f1c430c4997 100644 --- a/apps/docs/pages/guides/platform/logs.mdx +++ b/apps/docs/pages/guides/platform/logs.mdx @@ -85,9 +85,15 @@ The Logs tab displays logs emitted during function execution. ## Logging Postgres Queries -By default, query logs are disabled for new Supabase projects, as they can reveal metadata about the contents of your database (such as table and column names). +By default, only DDL statements are logged for new Supabase projects. -To enable query logs: + + +For projects created before 18th July 2023, statement execution logging is disabled completely by default. + + + +To enable query logs for other categories of statements: 1. [Enable the pgAudit extension](https://supabase.com/dashboard/project/_/database/extensions). 2. [Restart your project](https://supabase.com/dashboard/project/_/settings/general) using the **Fast database reboot** option. diff --git a/apps/docs/pages/guides/self-hosting.mdx b/apps/docs/pages/guides/self-hosting.mdx index 4b631683686..b8468f8112a 100644 --- a/apps/docs/pages/guides/self-hosting.mdx +++ b/apps/docs/pages/guides/self-hosting.mdx @@ -26,7 +26,7 @@ export const official = [ { name: 'Docker', description: 'Deploy Supabase within your own infrastructure using Docker Compose.', - href: '/docs/guides/self-hosting/docker', + href: '/guides/self-hosting/docker', }, { name: 'BYO Cloud', diff --git a/apps/docs/pages/index.mdx b/apps/docs/pages/index.mdx index 77529472c4e..b8423ac2ab9 100644 --- a/apps/docs/pages/index.mdx +++ b/apps/docs/pages/index.mdx @@ -9,7 +9,6 @@ import { IconPlay, TextLink, } from 'ui' -import { LWAnnouncement } from 'ui' import HomeMenuIconPicker from '~/components/Navigation/NavigationMenu/HomeMenuIconPicker' @@ -21,10 +20,6 @@ export const meta = { }
- -
- -
## Products diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index b3e0a1be0f8..8489fe8d77a 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -60,6 +60,7 @@ Sreyas Udayavarman Stanislav M Steve Chavez Stojan Dimitrovski +Supun Sudaraka Kalidasa Terry Sutton Thor Schaeff Tyler Shukert diff --git a/apps/www/components/Nav/index.tsx b/apps/www/components/Nav/index.tsx index b36fd57c9dd..ac2f75dfdc2 100644 --- a/apps/www/components/Nav/index.tsx +++ b/apps/www/components/Nav/index.tsx @@ -13,7 +13,7 @@ import Developers from '~/components/Nav/Developers' import ScrollProgress from '~/components/ScrollProgress' -import { useIsLoggedIn, useTheme } from 'common' +import { useIsLoggedIn, useIsUserLoading, useTheme } from 'common' import TextLink from '../TextLink' import Image from 'next/image' import * as supabaseLogoWordmarkDark from 'common/assets/images/supabase-logo-wordmark--dark.png' @@ -26,6 +26,7 @@ const Nav = () => { const [openProduct, setOpenProduct] = useState(false) const [openDevelopers, setOpenDevelopers] = useState(false) const isLoggedIn = useIsLoggedIn() + const isUserLoading = useIsUserLoading() const isHomePage = router.pathname === '/' const isLaunchWeekPage = router.pathname.includes('launch-week') @@ -290,60 +291,66 @@ const Nav = () => {
- } - > - - Star us on GitHub - - - - - {isLoggedIn ? ( - - - - - - ) : ( + {!isUserLoading && ( <> - - - - - - - - - - + +
+ } + > + + Star us on GitHub + + + + + {isLoggedIn ? ( + + + + + + ) : ( + <> + + + + + + + + + + + + )} )}
diff --git a/docker/volumes/logs/vector.yml b/docker/volumes/logs/vector.yml index ddf9edb39ab..39a00772952 100644 --- a/docker/volumes/logs/vector.yml +++ b/docker/volumes/logs/vector.yml @@ -153,7 +153,7 @@ transforms: if .metadata.parsed.error_severity == "info" { .metadata.parsed.error_severity = "log" } - .metadata.parsed.error_severity = upcase(.metadata.parsed.error_severity) + .metadata.parsed.error_severity = upcase!(.metadata.parsed.error_severity) sinks: logflare_auth: diff --git a/packages/common/auth.tsx b/packages/common/auth.tsx index 9529f3cbe8c..4d5a943ab9d 100644 --- a/packages/common/auth.tsx +++ b/packages/common/auth.tsx @@ -103,6 +103,8 @@ export const useSession = () => useAuth().session export const useUser = () => useSession()?.user ?? null +export const useIsUserLoading = () => useAuth().isLoading + export const useIsLoggedIn = () => { const user = useUser() diff --git a/packages/ui/src/components/Toggle/Toggle.tsx b/packages/ui/src/components/Toggle/Toggle.tsx index 9ebbfbd6fff..3b9ad145f14 100644 --- a/packages/ui/src/components/Toggle/Toggle.tsx +++ b/packages/ui/src/components/Toggle/Toggle.tsx @@ -63,7 +63,9 @@ function Toggle({ }, []) function handleBlurEvent(e: React.FocusEvent) { - if (handleBlur) handleBlur(e) + setTimeout(() => { + if (handleBlur) handleBlur(e) + }, 100) if (onBlur) onBlur(e) } diff --git a/spec/cli_v1_config.yaml b/spec/cli_v1_config.yaml index 6f53e3deed6..b4e9cc67e52 100644 --- a/spec/cli_v1_config.yaml +++ b/spec/cli_v1_config.yaml @@ -7,37 +7,46 @@ configspec: '001' # This section outlines the general information for the tool. info: id: 'cli' # {string} A unique ID for this tool. - version: 'next' # {string} The current version number of the tool. + version: '1.93.0' # {string} The current version number of the tool. title: 'CLI' # {string} A readable name. source: 'https://github.com/supabase/cli' # {string} Where developers can find the source code. bugs: 'https://github.com/supabase/cli/issues' # {string} Where developers can file bugs. - spec: 'https://github.com/supabase/supabase/blob/master/web/spec/cli_config_001.yml' # {string} Where developers can find this spec (to link directly in the docs). + spec: 'https://github.com/supabase/supabase/spec/cli_v1_config.yaml' # {string} Where developers can find this spec (to link directly in the docs). description: | - A `config.toml` file is generated after running `supabase init`. + A `supabase/config.toml` file is generated after running `supabase init`. - This file is located in the `supabase` folder under `supabase/config.toml`. You can edit this file to change the settings for your locally running project. After you make changes, you will need to restart using `supabase stop` and then `supabase start` for the changes to take effect. + You can edit this file to change the settings for your locally running project. After you make changes, you will need to restart using `supabase stop` and then `supabase start` for the changes to take effect. tags: - id: general title: General description: General settings. - id: auth - title: Auth Settings - description: Auth Settings. + title: Auth + description: Auth settings. - id: api - title: API Settings - description: Auth Settings. + title: API + description: Auth settings. - id: database - title: Database Settings - description: Database Settings. + title: Database + description: Database settings. - id: dashboard - title: Dashboard Settings - description: Dashboard Settings. + title: Dashboard + description: Dashboard settings. + - id: realtime + title: Realtime + description: Dashboard settings. + - id: storage + title: Storage + description: Storage settings. + - id: edge-functions + title: Edge-Functions + description: Edge-Functions settings. + - id: analytics + title: Analytics + description: Analytics settings. - id: local title: Local Development - description: Local Development. - - id: edge-functions - title: Edge Functions - description: Edge Functions. + description: Local Development settings. # This section is an array of public functions which a user might need to execute. parameters: @@ -48,10 +57,22 @@ parameters: description: | A string used to distinguish different Supabase projects on the same host. Defaults to the working directory name when running `supabase init`. + - id: 'api.enabled' + title: 'api.enabled' + tags: ['api'] + required: false + default: 'true' + description: | + Enable the local PostgREST service. + links: + - name: 'PostgREST configuration' + link: 'https://postgrest.org/en/stable/configuration.html' + - id: 'api.port' title: 'api.port' tags: ['api'] - required: true + required: false + default: '54321' description: | Port to use for the API URL. links: @@ -67,8 +88,7 @@ parameters: required: false default: '["public", "storage", "graphql_public"]' description: | - Schemas to expose in your API. Tables, views and functions in this schema - will get API endpoints. `public` and `storage` are always included. + Schemas to expose in your API. Tables, views and functions in this schema will get API endpoints. `public` and `storage` are always included. links: - name: 'PostgREST configuration' link: 'https://postgrest.org/en/stable/configuration.html' @@ -77,10 +97,9 @@ parameters: title: 'api.extra_search_path' tags: ['api'] required: false - default: '["public"]' + default: '["public", "extensions"]' description: | - Extra schemas to add to the search_path of every request. public is always - included. + Extra schemas to add to the search_path of every request. public is always included. links: - name: 'PostgREST configuration' link: 'https://postgrest.org/en/stable/configuration.html' @@ -99,7 +118,8 @@ parameters: - id: 'db.port' title: 'db.port' tags: ['database'] - required: true + required: false + default: '54322' description: | Port to use for the local database URL. links: @@ -118,26 +138,130 @@ parameters: - id: 'db.major_version' title: 'db.major_version' tags: ['database'] - required: true - default: '14' + required: false + default: '15' description: | The database major version to use. This has to be the same as your remote database's. Run `SHOW server_version;` on the remote database to check. links: - name: 'PostgreSQL configuration' link: 'https://postgrest.org/en/stable/configuration.html' + - id: 'db.pooler.enabled' + title: 'db.pooler.enabled' + tags: ['database'] + required: false + default: 'false' + description: | + Enable the local PgBouncer service. + links: + - name: 'PgBouncer Configuration' + link: https://www.pgbouncer.org/config.html + + - id: 'db.pooler.port' + title: 'db.pooler.port' + tags: ['database'] + required: false + default: '54329' + description: | + Port to use for the local connection pooler. + links: + - name: 'PgBouncer Configuration' + link: https://www.pgbouncer.org/config.html#listen_port + + - id: 'db.pooler.pool_mode' + title: 'db.pooler.pool_mode' + tags: ['database'] + required: false + default: '"transaction"' + description: | + Specifies when a server connection can be reused by other clients. Configure one of the supported pooler modes: `transaction`, `session`. + links: + - name: 'PgBouncer Configuration' + link: https://www.pgbouncer.org/config.html#pool_mode + + - id: 'db.pooler.default_pool_size' + title: 'db.pooler.default_pool_size' + tags: ['database'] + required: false + default: '20' + description: | + How many server connections to allow per user/database pair. + links: + - name: 'PgBouncer Configuration' + link: https://www.pgbouncer.org/config.html#default_pool_size + + - id: 'db.pooler.max_client_conn' + title: 'db.pooler.max_client_conn' + tags: ['database'] + required: false + default: '100' + description: | + Maximum number of client connections allowed. + links: + - name: 'PgBouncer Configuration' + link: https://www.pgbouncer.org/config.html#max_client_conn + + - id: 'realtime.enabled' + title: 'realtime.enabled' + tags: ['realtime'] + required: false + default: 'true' + description: | + Enable the local Realtime service. + links: [] + + - id: 'realtime.ip_version' + title: 'realtime.ip_version' + tags: ['realtime'] + required: false + default: '"IPv6"' + description: | + Bind realtime via either IPv4 or IPv6. (default: IPv6) + links: [] + + - id: 'studio.enabled' + title: 'studio.enabled' + tags: ['dashboard'] + required: false + default: 'true' + description: | + Enable the local Supabase Studio dashboard. + links: [] + - id: 'studio.port' title: 'studio.port' tags: ['dashboard'] - required: true + required: false + default: '54323' description: | Port to use for Supabase Studio. links: [] + - id: 'studio.api_url' + title: 'studio.api_url' + tags: ['dashboard'] + required: false + default: '"http://localhost"' + description: | + External URL of the API server that frontend connects to. + links: [] + + - id: 'inbucket.enabled' + title: 'inbucket.enabled' + tags: ['local'] + required: false + default: 'true' + description: | + Enable the local InBucket service. + links: + - name: 'Inbucket documentation' + link: 'https://www.inbucket.org' + - id: 'inbucket.port' title: 'inbucket.port' tags: ['local'] - required: true + required: false + default: 'true' description: | Port to use for the email testing server web interface. @@ -150,6 +274,7 @@ parameters: title: 'inbucket.smtp_port' tags: ['local'] required: false + default: '54325' description: | Port to use for the email testing server SMTP port. @@ -164,6 +289,7 @@ parameters: title: 'inbucket.pop3_port' tags: ['local'] required: false + default: '54326' description: | Port to use for the email testing server POP3 port. @@ -174,20 +300,43 @@ parameters: - name: 'Inbucket documentation' link: 'https://www.inbucket.org' + - id: 'storage.enabled' + title: 'storage.enabled' + tags: ['storage'] + required: false + default: 'true' + description: | + Enable the local Storage service. + links: + - name: 'Storage server configuration' + link: 'https://supabase.com/docs/guides/self-hosting/storage/config' + - id: 'storage.file_size_limit' title: 'storage.file_size_limit' - tags: ['local'] + tags: ['storage'] required: false + default: '"50MiB"' description: | The maximum file size allowed (e.g. "5MB", "500KB"). links: - name: 'Storage server configuration' link: 'https://supabase.com/docs/guides/self-hosting/storage/config' + - id: 'auth.enabled' + title: 'auth.enabled' + tags: ['auth'] + required: false + default: 'true' + description: | + Enable the local GoTrue service. + links: + - name: 'Auth Server configuration' + link: 'https://supabase.com/docs/reference/auth' + - id: 'auth.site_url' title: 'auth.site_url' tags: ['auth'] - required: true + required: false default: '"http://localhost:3000"' description: | The base URL of your website. Used as an allow-list for redirects and for constructing URLs used in emails. @@ -198,7 +347,7 @@ parameters: - id: 'auth.additional_redirect_urls' title: 'auth.additional_redirect_urls' tags: ['auth'] - required: true + required: false default: '["https://localhost:3000"]' description: | A list of _exact_ URLs that auth providers are permitted to redirect to post authentication. @@ -209,7 +358,7 @@ parameters: - id: 'auth.jwt_expiry' title: 'auth.jwt_expiry' tags: ['auth'] - required: true + required: false default: '3600' description: | How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one week). @@ -217,10 +366,32 @@ parameters: - name: 'Auth Server configuration' link: 'https://supabase.com/docs/reference/auth' + - id: 'auth.enable_refresh_token_rotation' + title: 'auth.enable_refresh_token_rotation' + tags: ['auth'] + required: false + default: 'true' + description: | + If disabled, the refresh token will never expire. + links: + - name: 'Auth Server configuration' + link: 'https://supabase.com/docs/reference/auth' + + - id: 'auth.refresh_token_reuse_interval' + title: 'auth.refresh_token_reuse_interval' + tags: ['auth'] + required: false + default: '10' + description: | + Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. Requires enable_refresh_token_rotation = true. + links: + - name: 'Auth Server configuration' + link: 'https://supabase.com/docs/reference/auth' + - id: 'auth.enable_signup' title: 'auth.enable_signup' tags: ['auth'] - required: true + required: false default: 'true' description: | Allow/disallow new user signups to your project. @@ -231,7 +402,7 @@ parameters: - id: 'auth.email.enable_signup' title: 'auth.email.enable_signup' tags: ['auth'] - required: true + required: false default: 'true' description: | Allow/disallow new user signups via email to your project. @@ -242,7 +413,7 @@ parameters: - id: 'auth.email.double_confirm_changes' title: 'auth.email.double_confirm_changes' tags: ['auth'] - required: true + required: false default: 'true' description: | If enabled, a user will be required to confirm any email change on both the old, and new email addresses. If disabled, only the new email is required to confirm. @@ -253,8 +424,8 @@ parameters: - id: 'auth.email.enable_confirmations' title: 'auth.email.enable_confirmations' tags: ['auth'] - required: true - default: 'true' + required: false + default: 'false' description: | If enabled, users need to confirm their email address before signing in. links: @@ -293,11 +464,175 @@ parameters: - name: 'Auth Server configuration' link: 'https://supabase.com/docs/reference/auth' + - id: 'auth.sms.enable_signup' + title: 'auth.sms.enable_signup' + tags: ['auth'] + required: false + default: 'true' + description: | + Allow/disallow new user signups via SMS to your project. + links: + - name: 'Auth Server configuration' + link: 'https://supabase.com/docs/reference/auth' + + - id: 'auth.sms.enable_confirmations' + title: 'auth.sms.enable_confirmations' + tags: ['auth'] + required: false + default: 'false' + description: | + If enabled, users need to confirm their phone number before signing in. + links: + - name: 'Auth Server configuration' + link: 'https://supabase.com/docs/reference/auth' + + - id: 'auth.sms.test_otp' + title: 'auth.sms.test_otp' + tags: ['auth'] + required: false + description: | + Use pre-defined map of phone number to OTP for testing. + links: + - name: 'Auth Server configuration' + link: 'https://supabase.com/docs/reference/auth' + usage: | + [auth.sms.test_otp] + 4152127777 = "123456" + + - id: 'auth.sms.provider.enabled' + title: 'auth.sms..enabled' + tags: ['auth'] + required: false + default: 'false' + description: | + Use an external SMS provider. The full list of providers are: + + - `twilio` + - `twilio_verify` + - `messagebird` + - `textlocal` + - `vonage` + links: + - name: 'Auth Server configuration' + link: 'https://supabase.com/docs/reference/auth' + + - id: 'auth.sms.twilio.account_sid' + title: 'auth.sms..account_sid' + tags: ['auth'] + required: true + description: | + Twilio Account SID + links: + - name: 'Auth Server configuration' + link: 'https://supabase.com/docs/reference/auth' + + - id: 'auth.sms.twilio.message_service_sid' + title: 'auth.sms..message_service_sid' + tags: ['auth'] + required: true + description: | + Twilio Message Service SID + links: + - name: 'Auth Server configuration' + link: 'https://supabase.com/docs/reference/auth' + + - id: 'auth.sms.twilio.auth_token' + title: 'auth.sms..auth_token' + tags: ['auth'] + required: true + default: 'env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)' + description: | + Twilio Auth Token + + DO NOT commit your Twilio auth token to git. Use environment variable substitution instead. + links: + - name: 'Auth Server configuration' + link: 'https://supabase.com/docs/reference/auth' + + - id: 'auth.sms.messagebird.originator' + title: 'auth.sms.messagebird.originator' + tags: ['auth'] + required: true + description: | + MessageBird Originator + links: + - name: 'Auth Server configuration' + link: 'https://supabase.com/docs/reference/auth' + + - id: 'auth.sms.messagebird.access_key' + title: 'auth.sms.messagebird.access_key' + tags: ['auth'] + required: true + default: 'env(SUPABASE_AUTH_SMS_MESSAGEBIRD_ACCESS_KEY)' + description: | + MessageBird Access Key + + DO NOT commit your MessageBird access key to git. Use environment variable substitution instead. + links: + - name: 'Auth Server configuration' + link: 'https://supabase.com/docs/reference/auth' + + - id: 'auth.sms.textlocal.sender' + title: 'auth.sms.textlocal.sender' + tags: ['auth'] + required: true + description: | + TextLocal Sender + links: + - name: 'Auth Server configuration' + link: 'https://supabase.com/docs/reference/auth' + + - id: 'auth.sms.textlocal.api_key' + title: 'auth.sms.textlocal.api_key' + tags: ['auth'] + required: true + default: 'env(SUPABASE_AUTH_SMS_TEXTLOCAL_API_KEY)' + description: | + TextLocal API Key + + DO NOT commit your TextLocal API key to git. Use environment variable substitution instead. + links: + - name: 'Auth Server configuration' + link: 'https://supabase.com/docs/reference/auth' + + - id: 'auth.sms.vonage.from' + title: 'auth.sms.vonage.from' + tags: ['auth'] + required: true + description: | + Vonage From + links: + - name: 'Auth Server configuration' + link: 'https://supabase.com/docs/reference/auth' + + - id: 'auth.sms.vonage.api_key' + title: 'auth.sms.vonage.api_key' + tags: ['auth'] + required: true + description: | + Vonage API Key + links: + - name: 'Auth Server configuration' + link: 'https://supabase.com/docs/reference/auth' + + - id: 'auth.sms.vonage.api_secret' + title: 'auth.sms.vonage.api_secret' + tags: ['auth'] + required: true + default: 'env(SUPABASE_AUTH_SMS_VONAGE_API_SECRET)' + description: | + Vonage API Secret + + DO NOT commit your Vonage API secret to git. Use environment variable substitution instead. + links: + - name: 'Auth Server configuration' + link: 'https://supabase.com/docs/reference/auth' + - id: 'auth.external.provider.enabled' title: 'auth.external..enabled' tags: ['auth'] - required: true - default: 'true' + required: false + default: 'false' description: | Use an external OAuth provider. The full list of providers are: @@ -309,10 +644,15 @@ parameters: - `github` - `gitlab` - `google` + - `keycloak` + - `linkedin` + - `notion` - `twitch` - `twitter` - `slack` - `spotify` + - `workos` + - `zoom` links: - name: 'Auth Server configuration' link: 'https://supabase.com/docs/reference/auth' @@ -331,8 +671,11 @@ parameters: title: 'auth.external..secret' tags: ['auth'] required: true + default: 'env(SUPABASE_AUTH_EXTERNAL__SECRET)' description: | Client secret for the external OAuth provider. + + DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead. links: - name: 'Auth Server configuration' link: 'https://supabase.com/docs/reference/auth' @@ -341,7 +684,6 @@ parameters: title: 'auth.external..url' tags: ['auth'] required: false - default: '""' description: | The base URL used for constructing the URLs to request authorization and access tokens. Used by gitlab and keycloak. For gitlab it defaults to @@ -355,7 +697,6 @@ parameters: title: 'auth.external..redirect_uri' tags: ['auth'] required: false - default: '""' description: | The URI a OAuth2 provider will redirect to with the code and state values. links: @@ -388,3 +729,46 @@ parameters: links: - name: '`supabase functions` CLI subcommands' link: 'https://supabase.com/docs/reference/cli/supabase-functions' + + - id: 'analytics.enabled' + title: 'analytics.enabled' + tags: ['analytics'] + required: false + default: 'false' + description: | + Enable the local Logflare service. + links: + - name: Self-hosted Logflare Configuration + link: https://supabase.com/docs/reference/self-hosting-analytics/list-endpoints#getting-started + + - id: 'analytics.port' + title: 'analytics.port' + tags: ['analytics'] + required: false + default: '54327' + description: | + Port to the local Logflare service. + links: [] + + - id: 'analytics.vector_port' + title: 'analytics.vector_port' + tags: ['analytics'] + required: false + default: '54328' + description: | + Port to the local syslog ingest service. + links: [] + + - id: 'analytics.backend' + title: 'analytics.backend' + tags: ['analytics'] + required: false + default: '"postgres"' + description: | + Configure one of the supported backends: + + - `postgres` + - `bigquery` + links: + - name: Self-hosted Logflare Configuration + link: https://supabase.com/docs/reference/self-hosting-analytics/list-endpoints#getting-started diff --git a/studio/.gitignore b/studio/.gitignore index 9dd40bd86d1..da36361f436 100644 --- a/studio/.gitignore +++ b/studio/.gitignore @@ -33,3 +33,5 @@ yarn-error.log* .env.production.local .vercel + +/public/dashboard \ No newline at end of file diff --git a/studio/components/grid/SupabaseGrid.tsx b/studio/components/grid/SupabaseGrid.tsx index 0cd01351b10..11082c85985 100644 --- a/studio/components/grid/SupabaseGrid.tsx +++ b/studio/components/grid/SupabaseGrid.tsx @@ -36,168 +36,179 @@ export const SupabaseGrid = forwardRef((prop ) }) -const SupabaseGridLayout = forwardRef((props, ref) => { - const { - editable, - storageRef, - gridProps, - headerActions, - showCustomChildren, - customHeader, - children, - onAddRow, - onAddColumn, - updateTableRow, - onEditForeignKeyColumnValue, - onImportData, - } = props - const dispatch = useDispatch() - const state = useTrackedState() +const SupabaseGridLayout = forwardRef( + function SupabaseGridLayout(props, ref) { + const { + editable, + storageRef, + gridProps, + headerActions, + showCustomChildren, + customHeader, + children, + onAddRow, + onAddColumn, + updateTableRow, + onEditForeignKeyColumnValue, + onImportData, + } = props + const dispatch = useDispatch() + const state = useTrackedState() - const gridRef = useRef(null) - const [mounted, setMounted] = useState(false) + const gridRef = useRef(null) + const [mounted, setMounted] = useState(false) - const [{ sort, filter }, setParams] = useUrlState({ - arrayKeys: ['sort', 'filter'], - }) - const sorts = formatSortURLParams(sort as string[]) - const filters = formatFilterURLParams(filter as string[]) + const [{ sort, filter }, setParams] = useUrlState({ + arrayKeys: ['sort', 'filter'], + }) + const sorts = formatSortURLParams(sort as string[]) + const filters = formatFilterURLParams(filter as string[]) - const { project } = useProjectContext() - const { data, isLoading, isRefetching } = useTableRowsQuery( - { - queryKey: [props.table.schema, props.table.name], - projectRef: project?.ref, - connectionString: project?.connectionString, - table: props.table, - sorts, - filters, - page: state.page, - limit: state.rowsPerPage, - }, - { - keepPreviousData: true, - onSuccess(data) { + const { project } = useProjectContext() + const { data, error, isSuccess, isError, isLoading, isRefetching } = useTableRowsQuery( + { + queryKey: [props.table.schema, props.table.name], + projectRef: project?.ref, + connectionString: project?.connectionString, + table: props.table, + sorts, + filters, + page: state.page, + limit: state.rowsPerPage, + }, + { + keepPreviousData: true, + onSuccess(data) { + dispatch({ + type: 'SET_ROWS_COUNT', + payload: data.rows.length, + }) + }, + } + ) + + useImperativeHandle(ref, () => ({ + rowAdded(row: Dictionary) { dispatch({ - type: 'SET_ROWS_COUNT', - payload: data.rows.length, + type: 'ADD_NEW_ROW', + payload: row, }) }, - } - ) - - useImperativeHandle(ref, () => ({ - rowAdded(row: Dictionary) { - dispatch({ - type: 'ADD_NEW_ROW', - payload: row, - }) - }, - rowEdited(row: Dictionary, idx: number) { - dispatch({ - type: 'EDIT_ROW', - payload: { row, idx }, - }) - }, - })) - - useEffect(() => { - if (!mounted) setMounted(true) - }, []) - - useEffect(() => { - if (mounted) { - dispatch({ type: 'UPDATE_FILTERS', payload: {} }) - } - }, [JSON.stringify(filters)]) - - useEffect(() => { - if (mounted) { - dispatch({ type: 'UPDATE_SORTS', payload: {} }) - } - }, [JSON.stringify(sorts)]) - - useEffect(() => { - if (state.isInitialComplete && storageRef && state.table) { - saveStorageDebounced(state, storageRef, sort as string[], filter as string[]) - } - }, [ - state.table, - state.isInitialComplete, - state.gridColumns, - JSON.stringify(sorts), - JSON.stringify(filters), - storageRef, - ]) - - useEffect(() => { - dispatch({ - type: 'INIT_CALLBACK', - payload: { ...props }, - }) - }, []) - - useEffect(() => { - const initializeData = async () => { - const { savedState } = await initTable( - props, - state, - dispatch, - sort as string[], - filter as string[] - ) - - if (savedState.sorts || savedState.filters) { - setParams((prevParams) => { - return { - ...prevParams, - ...(savedState.sorts && { sort: savedState.sorts }), - ...(savedState.filters && { filter: savedState.filters }), - } + rowEdited(row: Dictionary, idx: number) { + dispatch({ + type: 'EDIT_ROW', + payload: { row, idx }, }) + }, + })) + + useEffect(() => { + if (!mounted) setMounted(true) + }, []) + + useEffect(() => { + if (mounted) { + dispatch({ type: 'UPDATE_FILTERS', payload: {} }) } - } + }, [JSON.stringify(filters)]) - const refreshTable = JSON.stringify(props.table) !== JSON.stringify(state.table) + useEffect(() => { + if (mounted) { + dispatch({ type: 'UPDATE_SORTS', payload: {} }) + } + }, [JSON.stringify(sorts)]) - if (!state.table || refreshTable) { - initializeData() - } - }, [state.table, props.table, props.schema]) + useEffect(() => { + if (state.isInitialComplete && storageRef && state.table) { + saveStorageDebounced(state, storageRef, sort as string[], filter as string[]) + } + }, [ + state.table, + state.isInitialComplete, + state.gridColumns, + JSON.stringify(sorts), + JSON.stringify(filters), + storageRef, + ]) - return ( -
-
- {showCustomChildren && children !== undefined ? ( - <>{children} - ) : ( - <> - -
- - - )} + useEffect(() => { + dispatch({ + type: 'INIT_CALLBACK', + payload: { ...props }, + }) + }, []) - {mounted && - createPortal(, document.body)} -
- ) -}) + useEffect(() => { + const initializeData = async () => { + const { savedState } = await initTable( + props, + state, + dispatch, + sort as string[], + filter as string[] + ) + + if (savedState.sorts || savedState.filters) { + setParams((prevParams) => { + return { + ...prevParams, + ...(savedState.sorts && { sort: savedState.sorts }), + ...(savedState.filters && { filter: savedState.filters }), + } + }) + } + } + + const refreshTable = JSON.stringify(props.table) !== JSON.stringify(state.table) + + if (!state.table || refreshTable) { + initializeData() + } + }, [state.table, props.table, props.schema]) + + return ( +
+
+ {showCustomChildren && children !== undefined ? ( + <>{children} + ) : ( + <> + +
+ + + )} + + {mounted && + createPortal( + , + document.body + )} +
+ ) + } +) diff --git a/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx b/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx index cab2f3416a2..d6a91029ddf 100644 --- a/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx +++ b/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx @@ -1,32 +1,44 @@ +import { FormatterProps } from '@supabase/react-data-grid' import Link from 'next/link' import { PropsWithChildren } from 'react' -import { FormatterProps } from '@supabase/react-data-grid' + +import * as Tooltip from '@radix-ui/react-tooltip' +import { useParams } from 'common/hooks' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import { useTableQuery } from 'data/tables/table-query' +import { Button, IconArrowRight } from 'ui' import { SupaRow } from '../../types' import { NullValue } from '../common' -import * as Tooltip from '@radix-ui/react-tooltip' -import { Button, IconArrowRight } from 'ui' -import { useStore } from 'hooks' -import { useParams } from 'common/hooks' +import { useTablesQuery } from 'data/tables/tables-query' export const ForeignKeyFormatter = (props: PropsWithChildren>) => { - const { ref, id } = useParams() - const { meta } = useStore() + const { project } = useProjectContext() + const { ref: projectRef, id: _id } = useParams() + const id = _id ? Number(_id) : undefined const { row, column } = props - const selectedTable = meta.tables.byId(id as string) + + const { data: selectedTable } = useTableQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + id, + }) const relationship = (selectedTable?.relationships ?? []).find( (r) => r.source_schema === selectedTable?.schema && r.source_table_name === selectedTable?.name && r.source_column_name === column.name ) - const targetTable = meta.tables - .list() - .find( - (table) => - table.schema === relationship?.target_table_schema && - table.name === relationship.target_table_name - ) + const { data: tables } = useTablesQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + schema: relationship?.target_table_schema, + }) + const targetTable = tables?.find( + (table) => + table.schema === relationship?.target_table_schema && + table.name === relationship.target_table_name + ) const value = row[column.key] @@ -39,7 +51,7 @@ export const ForeignKeyFormatter = (props: PropsWithChildren */} - {onAddRow !== undefined && onImportData !== undefined && ( - - )} -
+ {(filters ?? []).length === 0 ? ( +
+

This table is empty

+ {onAddRow !== undefined && onImportData !== undefined && ( + <> +

+ Add or generate rows to get started. +

+
+ {/* [Joshen] Leaving this as a placeholder */} + {/* */} + {onAddRow !== undefined && onImportData !== undefined && ( + + )} +
+ + )} +
+ ) : ( +
+

+ The filters applied has returned no results from this table +

+
+ +
+
+ )} )} -
+ } />
diff --git a/studio/components/grid/components/header/RLSBannerWarning.tsx b/studio/components/grid/components/header/RLSBannerWarning.tsx index 0695e64a4b2..6f27e1821de 100644 --- a/studio/components/grid/components/header/RLSBannerWarning.tsx +++ b/studio/components/grid/components/header/RLSBannerWarning.tsx @@ -1,24 +1,38 @@ -import { useCallback, useState } from 'react' -import { Button, IconAlertCircle, Modal } from 'ui' import Link from 'next/link' -import { useStore } from 'hooks' +import { useCallback, useState } from 'react' + import { useParams } from 'common/hooks' -import ConfirmationModal from 'components/ui/ConfirmationModal' -import type { PostgresTable } from '@supabase/postgres-meta' import { rlsAcknowledgedKey } from 'components/grid/constants' import RLSDisableModalContent from 'components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/RLSDisableModal' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import ConfirmationModal from 'components/ui/ConfirmationModal' +import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants' +import { useTableQuery } from 'data/tables/table-query' +import useEntityType from 'hooks/misc/useEntityType' +import { Button, IconAlertCircle, Modal } from 'ui' export default function RLSBannerWarning() { - const { meta } = useStore() - const { ref: projectRef, id: tableID } = useParams() + const { project } = useProjectContext() + const { ref: projectRef, id: _id } = useParams() + const tableID = _id ? Number(_id) : undefined const rlsKey = rlsAcknowledgedKey(tableID) const isAcknowledged = localStorage?.getItem(rlsKey) === 'true' ?? false const [isOpen, setIsOpen] = useState(false) - const tables: PostgresTable[] = meta.tables.list() - const currentTable = tables.find((table) => table.id.toString() === tableID) + const entityType = useEntityType(tableID) + const { data: currentTable } = useTableQuery( + { + projectRef: project?.ref, + connectionString: project?.connectionString, + id: tableID, + }, + { + enabled: entityType?.type === ENTITY_TYPE.TABLE, + } + ) + const rlsEnabled = currentTable?.rls_enabled const isPublicTable = currentTable?.schema === 'public' diff --git a/studio/components/grid/components/header/filter/FilterPopover.tsx b/studio/components/grid/components/header/filter/FilterPopover.tsx index c5e37224df0..d2b8458943a 100644 --- a/studio/components/grid/components/header/filter/FilterPopover.tsx +++ b/studio/components/grid/components/header/filter/FilterPopover.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo, useCallback } from 'react' +import { useState, useMemo, useCallback, KeyboardEvent } from 'react' import { isEqual } from 'lodash' import { Button, IconPlus, IconFilter, Popover } from 'ui' import update from 'immutability-helper' @@ -102,6 +102,12 @@ const FilterOverlay = ({ table, filters: filtersFromUrl, setParams }: FilterOver }) } + function handleEnterKeyDown(event: KeyboardEvent) { + if (event.key === 'Enter') { + onApplyFilter() + } + } + return (
@@ -113,6 +119,7 @@ const FilterOverlay = ({ table, filters: filtersFromUrl, setParams }: FilterOver filterIdx={index} onChange={onChangeFilter} onDelete={onDeleteFilter} + onKeyDown={handleEnterKeyDown} /> ))} {filters.length == 0 && ( diff --git a/studio/components/grid/components/header/filter/FilterRow.tsx b/studio/components/grid/components/header/filter/FilterRow.tsx index 18086df29b1..69ecd1f8a59 100644 --- a/studio/components/grid/components/header/filter/FilterRow.tsx +++ b/studio/components/grid/components/header/filter/FilterRow.tsx @@ -1,4 +1,4 @@ -import { memo } from 'react' +import { memo, KeyboardEvent } from 'react' import { Button, Input, IconChevronDown, IconX } from 'ui' import { Filter, FilterOperator, SupaTable } from 'components/grid/types' @@ -11,9 +11,10 @@ export interface FilterRowProps { filter: Filter onChange: (index: number, filter: Filter) => void onDelete: (index: number) => void + onKeyDown: (event: KeyboardEvent) => void } -const FilterRow = ({ table, filter, filterIdx, onChange, onDelete }: FilterRowProps) => { +const FilterRow = ({ table, filter, filterIdx, onChange, onDelete, onKeyDown }: FilterRowProps) => { const column = table.columns.find((x) => x.name === filter.column) const columnOptions = table.columns?.map((x) => { @@ -80,6 +81,7 @@ const FilterRow = ({ table, filter, filterIdx, onChange, onDelete }: FilterRowPr value: event.target.value, }) } + onKeyDown={onKeyDown} />