Skip to content

Commit 793e2db

Browse files
authored
api+ui: expose scope in writes + CreatableSecretPicker (#334)
- 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.
2 parents 5a05685 + 5ac514d commit 793e2db

3 files changed

Lines changed: 68 additions & 12 deletions

File tree

‎packages/core/api/src/handlers/scope.ts‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,15 @@ export const ScopeHandlers = HttpApiBuilder.group(ExecutorApi, "scope", (handler
99
handlers.handle("info", () =>
1010
capture(Effect.gen(function* () {
1111
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)!;
1217
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,
1621
};
1722
})),
1823
),

‎packages/core/api/src/handlers/secrets.ts‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { HttpApiBuilder } from "@effect/platform";
22
import { Effect } from "effect";
3-
import { SecretNotFoundError, type SecretRef } from "@executor/sdk";
3+
import { SecretNotFoundError, SetSecretInput, type SecretRef } from "@executor/sdk";
44

55
import { ExecutorApi } from "../api";
66
import { ExecutorService } from "../services";
@@ -30,15 +30,18 @@ export const SecretsHandlers = HttpApiBuilder.group(ExecutorApi, "secrets", (han
3030
return { secretId: path.secretId, status };
3131
})),
3232
)
33-
.handle("set", ({ payload }) =>
33+
.handle("set", ({ path, payload }) =>
3434
capture(Effect.gen(function* () {
3535
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+
);
4245
return refToResponse(ref);
4346
})),
4447
)

‎packages/react/src/plugins/secret-header-auth.tsx‎

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ function slugifyForSecretId(input: string): string {
6868
.replace(/^-+|-+$/g, "");
6969
}
7070

71-
function InlineCreateSecret(props: {
71+
export function InlineCreateSecret(props: {
7272
suggestedId: string;
7373
suggestedName: string;
7474
onCreated: (secretId: string) => void;
@@ -416,3 +416,51 @@ export function SecretHeaderAuthRow(props: {
416416
</div>
417417
);
418418
}
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+
}

0 commit comments

Comments
 (0)