From d5d51c9bcd23fe19ca53bc129c346e79391c2e0c Mon Sep 17 00:00:00 2001 From: Guilhem Lemouel Date: Thu, 13 Aug 2026 10:38:31 +0200 Subject: [PATCH] feat(frontend): gate the supabase resource path behind the dev flag --- .../src/lib/components/ApiConnectForm.svelte | 135 ++----------- .../src/lib/components/SupabaseConnect.svelte | 189 ++++++++++++++++++ .../SupabaseResourceConnect.svelte | 119 +++++++++++ .../(root)/(logged)/resources/+page.svelte | 8 + .../oauth/callback_supabase/+page.svelte | 8 +- 5 files changed, 341 insertions(+), 118 deletions(-) create mode 100644 frontend/src/lib/components/SupabaseConnect.svelte create mode 100644 frontend/src/lib/components/workspaceSettings/SupabaseResourceConnect.svelte diff --git a/frontend/src/lib/components/ApiConnectForm.svelte b/frontend/src/lib/components/ApiConnectForm.svelte index 47043735fe..f86b913c98 100644 --- a/frontend/src/lib/components/ApiConnectForm.svelte +++ b/frontend/src/lib/components/ApiConnectForm.svelte @@ -16,15 +16,9 @@ import { isCloudHosted } from '$lib/cloud' import ResourceGen from './copilot/ResourceGen.svelte' import SyncResourceTypes from './SyncResourceTypes.svelte' - import Modal2 from './common/modal/Modal2.svelte' - import SupabaseProjectStep from './workspaceSettings/SupabaseProjectStep.svelte' - import { newWizardState } from './workspaceSettings/addDataTableModel' - import { - resolveSupabaseConnection, - supabaseResourceValue - } from './workspaceSettings/supabaseProvisioning' - import { useSupabaseOauth } from './workspaceSettings/supabaseOauth.svelte' - import { sendUserToast } from '$lib/toast' + import { base } from '$lib/base' + import SupabaseResourceConnect from './workspaceSettings/SupabaseResourceConnect.svelte' + import { isDataTableWizardEnabled } from './workspaceSettings/utils.svelte' import { parsePostgresConnectionString } from '$lib/utils/postgresConnectionString' interface Props { @@ -133,74 +127,14 @@ let rawCodeEditor: { setCode: (code: string) => void } | undefined = $state(undefined) let textFileContent: string | undefined = $state(undefined) - let supabaseOpen = $state(false) - let supaBusy = $state(false) - // Only the intent this form can act on. Creating a project is a billed action and belongs - // in the data table wizard, which can show what it is provisioning and record the result; - // a resource form has nowhere to put either. - let supaIntent = $state(newWizardState({ name: '', projectName: '', folder: '' }).supabase) + // The wizard's Supabase entry point is opt-in for now; without it the form keeps the link + // that hands the whole leg over to the resources page. + const wizardEnabled = isDataTableWizardEnabled() - // Authorizing is not something to present a dialog about first: the button goes straight - // to the popup, and the dialog opens on the way back, already holding the projects. - // No `redirectIfBlocked`: navigating this tab away would take the half-filled resource form - // with it, and there is nothing here to park and resume. - const supaOauth = useSupabaseOauth({ - onFallbackBlocked: () => { - awaitingSupabaseAuth = false - sendUserToast('Allow pop-ups for this site to connect your Supabase account.', true) - }, - onAbandoned: () => (awaitingSupabaseAuth = false) - }) - let awaitingSupabaseAuth = $state(false) - - function connectSupabase() { - if (supaOauth.authed) { - supabaseOpen = true - return - } - awaitingSupabaseAuth = true - supaOauth.connect() - } - - $effect(() => { - if (awaitingSupabaseAuth && supaOauth.authed) { - awaitingSupabaseAuth = false - supabaseOpen = true - } - }) - - // The resource is being edited here rather than created for us, so the project's password - // goes straight into the form as a value. The user can link it to a secret variable with - // the same affordance every other password field has. - async function applySupabasePick() { - const project = supaIntent.project - if (!project || !supaIntent.password) return - supaBusy = true - try { - const connection = await resolveSupabaseConnection( - supaOauth.token!, - project, - supaIntent.connectionMode - ) - args = { - ...(args ?? {}), - ...supabaseResourceValue(project, '', connection), - password: supaIntent.password - } - rawCode = JSON.stringify(args, null, 2) - rawCodeEditor?.setCode(rawCode) - supabaseOpen = false - sendUserToast( - connection.unavailable - ? `Filled in a direct connection for ${project.name}: ${connection.unavailable}` - : `Filled in the connection for ${project.name}`, - !!connection.unavailable - ) - } catch (err) { - sendUserToast(String(err), true) - } finally { - supaBusy = false - } + function applySupabasePick(value: Record) { + args = { ...(args ?? {}), ...value } + rawCode = JSON.stringify(args, null, 2) + rawCodeEditor?.setCode(rawCode) } function parseTextFileContent() { @@ -282,15 +216,18 @@ {/if} {#if resourceType == 'postgresql' && supabaseWizard} - + {#if wizardEnabled} + + {:else} + + +
Connect Supabase
+
+ {/if} {/if} {/if} - - -
-
- {#if supaOauth.token} - - {/if} -
-
- -
-
-
diff --git a/frontend/src/lib/components/SupabaseConnect.svelte b/frontend/src/lib/components/SupabaseConnect.svelte new file mode 100644 index 0000000000..e44399ee84 --- /dev/null +++ b/frontend/src/lib/components/SupabaseConnect.svelte @@ -0,0 +1,189 @@ + + + + + + + {#if step === 'init' || selectedDatabase == undefined} +

Connect an existing database
+

+
+ + {#if databases == undefined} + + {:else} +
+ {#each databases as database} + + {/each} + {/if} + +

Create a new database

+

Create a new database in your Supabase account + +

+ {:else if step === 'resource'} + + +

Database Password

+

For security reasons from supabase, the password of the database cannot be retrieved + automatically. In a future update, a dedicated role for windmill will be created and the + password for it will be generated automatically. The password of the database is shown + during the project creation.

+ + +

Description

+ + +
+

A resource and a variable will be created at path: {path}. The content of the resource will + be:

+ + {/if} + {#snippet actions()} +
+ {#if step == 'resource' && selectedDatabase != undefined} + + + + {/if} +
+ {/snippet} +
+
diff --git a/frontend/src/lib/components/workspaceSettings/SupabaseResourceConnect.svelte b/frontend/src/lib/components/workspaceSettings/SupabaseResourceConnect.svelte new file mode 100644 index 0000000000..6b13ac95b5 --- /dev/null +++ b/frontend/src/lib/components/workspaceSettings/SupabaseResourceConnect.svelte @@ -0,0 +1,119 @@ + + + + + +
+
+ {#if oauth.token} + + {/if} +
+
+ +
+
+
diff --git a/frontend/src/routes/(root)/(logged)/resources/+page.svelte b/frontend/src/routes/(root)/(logged)/resources/+page.svelte index 92e21512d3..27fdd286b5 100644 --- a/frontend/src/routes/(root)/(logged)/resources/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/resources/+page.svelte @@ -27,6 +27,7 @@ import InheritedLabels from '$lib/components/InheritedLabels.svelte' import ShareModal from '$lib/components/ShareModal.svelte' import SimpleEditor from '$lib/components/SimpleEditor.svelte' + import SupabaseConnect from '$lib/components/SupabaseConnect.svelte' import Cell from '$lib/components/table/Cell.svelte' import DataTable from '$lib/components/table/DataTable.svelte' import Head from '$lib/components/table/Head.svelte' @@ -119,6 +120,7 @@ let resourceEditor: ResourceEditorDrawer | undefined = $state(undefined) let shareModal: ShareModal | undefined = $state(undefined) let appConnect: AppConnect | undefined = $state(undefined) + let supabaseConnect: SupabaseConnect | undefined = $state(undefined) let deleteConfirmedCallback: (() => void) | undefined = $state(undefined) let deleteIsLinked = $state(false) let deletePath = $state('') @@ -393,6 +395,11 @@ } onMount(() => { + const callback = page.url.searchParams.get('callback') + if (callback == 'supabase_wizard') { + supabaseConnect?.open?.() + } + const connect_app = page.url.searchParams.get('connect_app') if (connect_app) { const rt = connect_app ?? undefined @@ -1363,6 +1370,7 @@ {/if} +