import { Suspense } from "react"; import { useAtomRefresh, useAtomValue } from "@effect/atom-react"; import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import type { ProviderKey } from "@executor-js/sdk/shared"; import { useSecretProviderPlugins } from "@executor-js/sdk/client"; import { providersAtom } from "../api/atoms"; import { CardStack, CardStackContent, CardStackEntry, CardStackEntryActions, CardStackEntryContent, CardStackEntryDescription, CardStackEntryTitle, CardStackHeader, } from "../components/card-stack"; import { Badge } from "../components/badge"; import { PageContainer, PageHeader } from "../components/page"; import { useExecutorDocumentTitle } from "../lib/document-title"; import { ErrorState } from "../components/error-state"; import { isAsyncResultLoading } from "../lib/async-result"; // --------------------------------------------------------------------------- // Providers page (v2) — repurposed from the v1 Secrets page. // // v1 stored standalone secrets and bound them per-source. v2 makes a connection // the credential, and a `CredentialProvider` is where its value lives (the // default store for pasted values, or an external backend like 1Password / // keychain). This page surfaces the registered providers plus any provider // plugin's settings card. The route still exports `SecretsPage` so existing app // wiring keeps resolving; new callers should treat it as the Providers view. // --------------------------------------------------------------------------- const PROVIDER_LABELS: Record = { encrypted: "Encrypted store", keychain: "Keychain", file: "Local file", memory: "Memory", onepassword: "1Password", "workos-vault": "WorkOS Vault", }; const providerLabel = (key: string): string => PROVIDER_LABELS[key] ?? key .split(/[-_]/g) .filter(Boolean) .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join(" "); export function SecretsPage(props: { showProviderInfo?: boolean }) { useExecutorDocumentTitle("Providers"); const showProviderInfo = props.showProviderInfo ?? true; const secretProviderPlugins = useSecretProviderPlugins(); const providers = useAtomValue(providersAtom); const refreshProviders = useAtomRefresh(providersAtom); return ( {/* Provider plugins (settings cards) */} {showProviderInfo && secretProviderPlugins.length > 0 && (
Configure providers {secretProviderPlugins.map((plugin) => (
} >
))}
)} {/* Registered providers */} {isAsyncResultLoading(providers) ? (

Loading providers…

) : ( AsyncResult.match(providers, { onInitial: () => (

Loading providers…

), onFailure: () => ( ), onSuccess: ({ value }) => ( Available providers {value.length === 0 ? ( No credential providers are registered. ) : ( value.map((key: ProviderKey) => ( {providerLabel(String(key))} {String(key) === "encrypted" ? "Values you paste are encrypted and stored in this instance's database." : `${providerLabel(String(key))} credential provider.`} {String(key) === "encrypted" ? "default" : "provider"} )) )} ), }) )} ); }