- scope handler: surfaces outermost scope (executor.scopes.at(-1)) instead of the old single scope. - secrets handler: set takes path.scopeId and threads it as SetSecretInput.scope so the executor writes at the right scope. - secret-header-auth: exports CreatableSecretPicker + InlineCreateSecret for reuse by the OpenAPI connections pane.
Unified · Split
| @@ -9,10 +9,15 @@ export const ScopeHandlers = HttpApiBuilder.group(ExecutorApi, "scope", (handler · 8 unmodified lines | ||
| 9 | 9 | handlers.handle("info", () => |
| 10 | 10 | capture(Effect.gen(function* () { |
| 11 | 11 | const executor = yield* ExecutorService; |
| 12 | + // Outermost scope (organization / workspace). A follow-up that | |
| 13 | + // exposes per-user stacks end-to-end can extend this response | |
| 14 | + // with the full list; for now, single-scope deployments and the | |
| 15 | + // current `[org]` cloud setup see identical output. | |
| 16 | + const scope = executor.scopes.at(-1)!; | |
| 12 | 17 | return { |
| 13 | − id: executor.scope.id, | |
| 14 | − name: executor.scope.name, | |
| 15 | − dir: executor.scope.name, | |
| 18 | + id: scope.id, | |
| 19 | + name: scope.name, | |
| 20 | + dir: scope.name, | |
| 16 | 21 | }; |
| 17 | 22 | })), |
| 18 | 23 | ), |
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | import { HttpApiBuilder } from "@effect/platform"; |
| 2 | 2 | import { Effect } from "effect"; |
| 3 | −import { SecretNotFoundError, type SecretRef } from "@executor/sdk"; | |
| 3 | +import { SecretNotFoundError, SetSecretInput, type SecretRef } from "@executor/sdk"; | |
| 4 | 4 | |
| 5 | 5 | import { ExecutorApi } from "../api"; |
| 6 | 6 | import { ExecutorService } from "../services"; |
| @@ -30,15 +30,18 @@ export const SecretsHandlers = HttpApiBuilder.group(ExecutorApi, "secrets", (han · 23 unmodified lines | ||
| 30 | 30 | return { secretId: path.secretId, status }; |
| 31 | 31 | })), |
| 32 | 32 | ) |
| 33 | − .handle("set", ({ payload }) => | |
| 33 | + .handle("set", ({ path, payload }) => | |
| 34 | 34 | capture(Effect.gen(function* () { |
| 35 | 35 | const executor = yield* ExecutorService; |
| 36 | − const ref = yield* executor.secrets.set({ | |
| 37 | − id: payload.id, | |
| 38 | − name: payload.name, | |
| 39 | − value: payload.value, | |
| 40 | − provider: payload.provider, | |
| 41 | − }); | |
| 36 | + const ref = yield* executor.secrets.set( | |
| 37 | + new SetSecretInput({ | |
| 38 | + id: payload.id, | |
| 39 | + scope: path.scopeId, | |
| 40 | + name: payload.name, | |
| 41 | + value: payload.value, | |
| 42 | + provider: payload.provider, | |
| 43 | + }), | |
| 44 | + ); | |
| 42 | 45 | return refToResponse(ref); |
| 43 | 46 | })), |
| 44 | 47 | ) |
| @@ -68,7 +68,7 @@ function slugifyForSecretId(input: string): string { · 67 unmodified lines | ||
| 68 | 68 | .replace(/^-+|-+$/g, ""); |
| 69 | 69 | } |
| 70 | 70 | |
| 71 | −function InlineCreateSecret(props: { | |
| 71 | +export function InlineCreateSecret(props: { | |
| 72 | 72 | suggestedId: string; |
| 73 | 73 | suggestedName: string; |
| 74 | 74 | onCreated: (secretId: string) => void; |
| @@ -416,3 +416,51 @@ export function SecretHeaderAuthRow(props: { · 341 unmodified lines | ||
| 416 | 416 | </div> |
| 417 | 417 | ); |
| 418 | 418 | } |
| 419 | + | |
| 420 | +// --------------------------------------------------------------------------- | |
| 421 | +// CreatableSecretPicker — SecretPicker + inline "+ New secret" create flow | |
| 422 | +// --------------------------------------------------------------------------- | |
| 423 | + | |
| 424 | +export function CreatableSecretPicker(props: { | |
| 425 | + readonly value: string | null; | |
| 426 | + readonly onSelect: (secretId: string) => void; | |
| 427 | + readonly secrets: readonly SecretPickerSecret[]; | |
| 428 | + readonly placeholder?: string; | |
| 429 | + /** | |
| 430 | + * Display name of the source the secret belongs to (e.g. "Stripe"). | |
| 431 | + * Combined with `secretLabel` to produce a suggested name/ID. | |
| 432 | + */ | |
| 433 | + readonly sourceName?: string; | |
| 434 | + /** Role of this secret (e.g. "Client ID", "API Token"). */ | |
| 435 | + readonly secretLabel: string; | |
| 436 | +}) { | |
| 437 | + const { value, onSelect, secrets, placeholder, sourceName, secretLabel } = props; | |
| 438 | + const [creating, setCreating] = useState(false); | |
| 439 | + | |
| 440 | + const suggestedName = [sourceName?.trim(), secretLabel].filter(Boolean).join(" "); | |
| 441 | + const suggestedId = slugifyForSecretId(suggestedName) || "secret"; | |
| 442 | + | |
| 443 | + if (creating) { | |
| 444 | + return ( | |
| 445 | + <InlineCreateSecret | |
| 446 | + suggestedId={suggestedId} | |
| 447 | + suggestedName={suggestedName} | |
| 448 | + onCreated={(id) => { | |
| 449 | + onSelect(id); | |
| 450 | + setCreating(false); | |
| 451 | + }} | |
| 452 | + onCancel={() => setCreating(false)} | |
| 453 | + /> | |
| 454 | + ); | |
| 455 | + } | |
| 456 | + | |
| 457 | + return ( | |
| 458 | + <SecretPicker | |
| 459 | + value={value} | |
| 460 | + onSelect={onSelect} | |
| 461 | + secrets={secrets} | |
| 462 | + placeholder={placeholder} | |
| 463 | + onCreateNew={() => setCreating(true)} | |
| 464 | + /> | |
| 465 | + ); | |
| 466 | +} | |