diff --git a/ui/src/components/config-form-collection-defaults.browser.test.ts b/ui/src/components/config-form-collection-defaults.browser.test.ts index ecf92a42eae5..81568a94fabe 100644 --- a/ui/src/components/config-form-collection-defaults.browser.test.ts +++ b/ui/src/components/config-form-collection-defaults.browser.test.ts @@ -132,6 +132,65 @@ describe("config form collection defaults", () => { expect(onPatch).not.toHaveBeenCalled(); }); + it("conceals sensitive collection defaults and disables restore until revealed", () => { + const container = document.createElement("div"); + const onPatch = vi.fn(); + const hints = { + "settings.profile": { sensitive: true }, + "settings.tokens": { sensitive: true }, + }; + + render( + renderObject( + { + schema: { + type: "object", + title: "Profile", + default: { apiKey: "default-secret" }, + properties: { apiKey: { type: "string" } }, + }, + value: { apiKey: "authored-secret" }, + path: ["settings", "profile"], + hints, + unsupported: new Set(), + disabled: false, + revealSensitive: false, + onPatch, + }, + renderNode, + ), + container, + ); + + expect(container.textContent).not.toContain("default-secret"); + expect(resetButton(container).disabled).toBe(true); + + render( + renderArray( + { + schema: { + type: "array", + title: "Tokens", + items: { type: "string" }, + default: ["default-token"], + }, + value: ["authored-token"], + path: ["settings", "tokens"], + hints, + unsupported: new Set(), + disabled: false, + revealSensitive: false, + onPatch, + }, + renderNode, + ), + container, + ); + + expect(container.textContent).not.toContain("default-token"); + expect(resetButton(container).disabled).toBe(true); + }); + it("removes an optional object as one value and keeps inherited children inherited", () => { const container = document.createElement("div"); const onPatch = vi.fn(); diff --git a/ui/src/components/config-form.node.collection.ts b/ui/src/components/config-form.node.collection.ts index 3072c82b8c5a..0a216441bfec 100644 --- a/ui/src/components/config-form.node.collection.ts +++ b/ui/src/components/config-form.node.collection.ts @@ -53,16 +53,30 @@ import { configFieldId, hintForPath, type JsonSchema } from "./config-form.share import { renderSettingsEmpty } from "./settings-ui.ts"; const UNSET_ARRAY_SOURCE_IDENTITY = Symbol("unset-array-source"); +const UNSET_MAP_SOURCE_IDENTITY = Symbol("unset-map-source"); + +function collectionDefaultPresentation(params: ConfigNodeRenderParams, effectiveValue: unknown) { + const redacted = getSensitiveRenderState({ + path: params.path, + value: effectiveValue, + hints: params.hints, + revealSensitive: params.revealSensitive ?? false, + isSensitivePathRevealed: params.isSensitivePathRevealed, + }).isRedacted; + return { + description: redacted ? nothing : renderSchemaDefaultDescription(params.schema, params.value), + action: renderRestoreDefaultButton({ + ...params, + disabled: params.disabled || redacted, + }), + }; +} function openCollectionDraft(event: Event, draftId: string): void { const block = (event.currentTarget as HTMLElement).closest(".cfg-block"); const draft = Array.from(block?.children ?? []).find((child) => child.id === draftId); - const openDraft = (draft as Partial | undefined)?.openDraft; - if (typeof openDraft === "function") { - openDraft.call(draft); - } + (draft as Partial | undefined)?.openDraft?.call(draft); } -const UNSET_MAP_SOURCE_IDENTITY = Symbol("unset-map-source"); export function renderObject( params: ConfigNodeRenderParams, @@ -97,6 +111,7 @@ export function renderObject( fallback && typeof fallback === "object" && !Array.isArray(fallback) ? (fallback as Record) : {}; + const defaultPresentation = collectionDefaultPresentation(params, fallback); const entries = objectPropertyKeys(schema) .map((key) => [key, objectPropertySchema(schema, key)] as const) .filter((entry): entry is readonly [string, ConfigNodeRenderParams["schema"]] => @@ -206,14 +221,12 @@ export function renderObject( ${label} ${help ? html`${help}` : nothing} ${schema.default !== undefined - ? html`${renderSchemaDefaultDescription(schema, value)}` + ? html`${defaultPresentation.description}` : nothing} ${renderTags(tags)}
- ${renderRestoreDefaultButton(params)} + ${defaultPresentation.action} ${icons.chevronDown}
@@ -271,6 +284,7 @@ export function renderArray( : Array.isArray(schema.default) ? schema.default : UNSET_ARRAY_SOURCE_IDENTITY; + const defaultPresentation = collectionDefaultPresentation(params, arrayValue); const rowIdentities = rowIdentitiesForArray(arrayValue); const { minItems: minimumItems, @@ -352,9 +366,7 @@ export function renderArray( ${showLabel ? html`${label}` : nothing} ${showLabel && help ? html`${help}` : nothing} ${showLabel && schema.default !== undefined - ? html`${renderSchemaDefaultDescription(schema, value)}` + ? html`${defaultPresentation.description}` : nothing} ${renderTags(tags)} @@ -364,7 +376,7 @@ export function renderArray( count: String(arrayValue.length), })} - ${renderRestoreDefaultButton(params)} + ${defaultPresentation.action}