mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 09:55:06 +03:00
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? - We made the Next step copy a bit more generic so it doesn't read like it's pointing you back to Inspector UI. - Added CTA on key stored screen to send you to Edge Function Secrets directly. - Tidies up footer area to always be centrally aligned across all states. ### 1. Enable the feature flag ### 2. Preview states via URL Mock mode is enabled automatically in local/staging. Navigate to `/mcp/secrets` with a `state` query param: http://localhost:8082/mcp/secrets?state=<state> States that need no other params: | `state` value | What it shows | | ----------------- | --------------------------------------- | | `loading` | Loading skeleton | | `expired` | Link expired | | `cancelled` | Request cancelled | | `paused` | Storing keys paused | | `wrong-account` | Signed in as the wrong account | | `error` | Generic failure | States that need a real project —ame=<KEY_NAME>`: | `state` value | What it sh | | -------------------- | ---------------------- | | `form` | The "st | | `stored` | Success | | `stored-timeout` | Successopped waiting | | `already-stored` | Key was already stored, nothing to do | Example: http://localhost:8082/mcp/secretsJECT_REF&name=OPENAI_API_KEY ### 3. What to check - [ ] `stored` / `already-stored`tions secrets"** button linking to `/project/<ref>/functions/secrets - [ ] States without a project re `paused`, `error`) don't show that button - [ ] Footer text is centered on - [ ] `wrong-account` → **Switch its footer is centered - [ ] The provider-dashboard link` state, use a `name` like`OPENAI_API_KEY` or `RESEND_API_Khint) is centered too <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a project-specific link to Edge Functions secrets from the MCP setup screen when a project is available. * Added a separator to distinguish the secrets link from the remaining setup guidance. * **Improvements** * Updated completion guidance to tell users to return to their agent and confirm the setup is finished. * Standardized interstitial footer content with centered guidance and consistent provider dashboard instructions. * **Tests** * Added coverage for displaying the project-specific secrets link and hiding it when no project is associated. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Ali Waseem <waseema393@gmail.com>
149 lines
4.9 KiB
TypeScript
149 lines
4.9 KiB
TypeScript
import dayjs from 'dayjs'
|
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
|
import utc from 'dayjs/plugin/utc'
|
|
|
|
import type { InterstitialTerminalCopy } from '../InterstitialTerminalScreen'
|
|
import type {
|
|
SecretProviderHint,
|
|
SecretRequest,
|
|
SecretsOutcomeState,
|
|
SecretsState,
|
|
} from './McpSecrets.types'
|
|
|
|
dayjs.extend(relativeTime)
|
|
dayjs.extend(utc)
|
|
|
|
const RETURN_TO_CLIENT_STEP = `Head back to your agent and let it know you're finished.`
|
|
|
|
const CLOSE_TAB_FOOTER = 'You can close this tab.'
|
|
|
|
const UNVERIFIED_KEY_FOOTER =
|
|
"Supabase doesn't verify keys. You can view or replace this one in Edge Functions secrets."
|
|
|
|
export function getSecretsCopy(state: SecretsOutcomeState): InterstitialTerminalCopy {
|
|
switch (state.status) {
|
|
case 'stored': {
|
|
const { keyName, project } = state.request
|
|
const savedSentence = `${keyName} is saved for ${project}.`
|
|
|
|
if (state.timedOut) {
|
|
return {
|
|
title: 'Key stored',
|
|
subtitle: `${savedSentence} This took longer than your client waits, so it may have stopped listening.`,
|
|
calloutTitle: 'Next step',
|
|
calloutBody:
|
|
'Ask your agent to store the key again. It will find the saved key and finish without sending you back here.',
|
|
footer: UNVERIFIED_KEY_FOOTER,
|
|
projectRef: state.request.ref,
|
|
}
|
|
}
|
|
|
|
return {
|
|
title: 'Key stored',
|
|
subtitle: savedSentence,
|
|
calloutTitle: 'Next step',
|
|
calloutBody: RETURN_TO_CLIENT_STEP,
|
|
footer: UNVERIFIED_KEY_FOOTER,
|
|
projectRef: state.request.ref,
|
|
}
|
|
}
|
|
|
|
case 'already-stored':
|
|
return {
|
|
title: 'This key is already stored',
|
|
subtitle: `${state.request.keyName} was saved for ${state.request.project}. Nothing further to do here.`,
|
|
calloutTitle: 'Next step',
|
|
calloutBody: RETURN_TO_CLIENT_STEP,
|
|
footer: CLOSE_TAB_FOOTER,
|
|
projectRef: state.request.ref,
|
|
}
|
|
|
|
case 'expired':
|
|
return {
|
|
title: 'This link has expired',
|
|
subtitle: 'Nothing was stored. Your key is still safe where you copied it from.',
|
|
calloutTitle: 'Next step',
|
|
calloutBody: "Ask your agent to store your API key again. You'll get a fresh link.",
|
|
footer: CLOSE_TAB_FOOTER,
|
|
}
|
|
|
|
case 'cancelled':
|
|
return {
|
|
title: 'This request was cancelled',
|
|
subtitle: 'Nothing was stored.',
|
|
calloutTitle: 'Next step',
|
|
calloutBody: 'Ask your agent to run the tool again if you still need to store the key.',
|
|
footer: CLOSE_TAB_FOOTER,
|
|
}
|
|
|
|
case 'paused':
|
|
return {
|
|
title: 'Storing keys is paused',
|
|
subtitle: 'Supabase has turned this off for now. Nothing was stored.',
|
|
calloutTitle: 'Next step',
|
|
calloutBody: 'Try again later, or set the key in Edge Functions secrets instead.',
|
|
footer: CLOSE_TAB_FOOTER,
|
|
}
|
|
|
|
case 'error':
|
|
return {
|
|
title: "Couldn't complete this request",
|
|
subtitle: 'Nothing was stored.',
|
|
calloutTitle: 'Next step',
|
|
calloutBody:
|
|
'Ask your agent to run the tool again, or set the key in Edge Functions secrets instead.',
|
|
footer: CLOSE_TAB_FOOTER,
|
|
}
|
|
}
|
|
}
|
|
|
|
export function getSecretsAnnouncement(state: SecretsState | undefined): string {
|
|
if (state === undefined) return ''
|
|
|
|
switch (state.status) {
|
|
case 'loading':
|
|
return 'Loading request details'
|
|
case 'form':
|
|
return `Ready to save ${state.request.keyName} for ${state.request.project}`
|
|
case 'wrong-account':
|
|
return 'This account cannot access the request'
|
|
default: {
|
|
const { title, subtitle } = getSecretsCopy(state)
|
|
return `${title}. ${subtitle}`
|
|
}
|
|
}
|
|
}
|
|
|
|
export function getSecretHelperText(project: string) {
|
|
return `Stored encrypted for ${project}. Anyone with write access to this project can use it. Remove it any time from Edge Functions secrets.`
|
|
}
|
|
|
|
export function getOverwriteWarning(request: SecretRequest) {
|
|
const { existingSecret, keyName } = request
|
|
if (existingSecret === undefined) return undefined
|
|
|
|
const updatedAt = existingSecret.updatedAt
|
|
const age = updatedAt === undefined ? undefined : formatSecretAge(updatedAt)
|
|
|
|
return age === undefined
|
|
? `${keyName} already exists. Storing will replace it.`
|
|
: `${keyName} already exists — updated ${age} ago. Storing will replace it.`
|
|
}
|
|
|
|
function formatSecretAge(updatedAt: string) {
|
|
const isUnixMicro = !Number.isNaN(Number(updatedAt)) && updatedAt.length === 16
|
|
const parsed = isUnixMicro ? dayjs.unix(Number(updatedAt) / 1000 / 1000) : dayjs.utc(updatedAt)
|
|
|
|
return parsed.isValid() ? parsed.fromNow(true) : undefined
|
|
}
|
|
|
|
export function getSecretPrefixWarning(
|
|
value: string,
|
|
providerHint: SecretProviderHint | undefined
|
|
) {
|
|
const prefix = providerHint?.prefix
|
|
if (!prefix || value.length === 0 || value.startsWith(prefix)) return undefined
|
|
|
|
return `${providerHint.name} keys usually start with ${prefix}. You can still save this one.`
|
|
}
|