Config UI: click-to-reveal redacted env vars and use lightweight re-render (#49399)

* Refactor CSS styles: replace hardcoded colors with CSS variables for accent colors and optimize spacing rules in layout files.

* Update CSS styles: streamline selectors, enhance hover effects, and adjust focus states for chat components and layout elements.

* Enhance focus styles for chat components: update border colors and box-shadow effects for improved accessibility and visual consistency.

* Config UI: click-to-reveal redacted env vars and use lightweight re-render
This commit is contained in:
Val Alexander
2026-03-17 22:10:31 -05:00
committed by GitHub
parent 206d1be082
commit 53dcafbec3
4 changed files with 41 additions and 13 deletions

View File

@@ -962,6 +962,13 @@
font-size: 12px;
}
/* Redacted (click-to-reveal) */
.cfg-input--redacted,
.cfg-textarea--redacted {
cursor: pointer;
opacity: 0.7;
}
/* Number Input */
.cfg-number {
display: inline-flex;

View File

@@ -1512,6 +1512,7 @@ export function renderApp(state: AppViewState) {
onRawChange: (next) => {
state.configRaw = next;
},
onRequestUpdate: requestHostUpdate,
onFormModeChange: (mode) => (state.configFormMode = mode),
onFormPatch: (path, value) => updateConfigFormValue(state, path, value),
onSearchChange: (query) => (state.configSearchQuery = query),
@@ -1582,6 +1583,7 @@ export function renderApp(state: AppViewState) {
onRawChange: (next) => {
state.configRaw = next;
},
onRequestUpdate: requestHostUpdate,
onFormModeChange: (mode) => (state.communicationsFormMode = mode),
onFormPatch: (path, value) => updateConfigFormValue(state, path, value),
onSearchChange: (query) => (state.communicationsSearchQuery = query),
@@ -1646,6 +1648,7 @@ export function renderApp(state: AppViewState) {
onRawChange: (next) => {
state.configRaw = next;
},
onRequestUpdate: requestHostUpdate,
onFormModeChange: (mode) => (state.appearanceFormMode = mode),
onFormPatch: (path, value) => updateConfigFormValue(state, path, value),
onSearchChange: (query) => (state.appearanceSearchQuery = query),
@@ -1710,6 +1713,7 @@ export function renderApp(state: AppViewState) {
onRawChange: (next) => {
state.configRaw = next;
},
onRequestUpdate: requestHostUpdate,
onFormModeChange: (mode) => (state.automationFormMode = mode),
onFormPatch: (path, value) => updateConfigFormValue(state, path, value),
onSearchChange: (query) => (state.automationSearchQuery = query),
@@ -1774,6 +1778,7 @@ export function renderApp(state: AppViewState) {
onRawChange: (next) => {
state.configRaw = next;
},
onRequestUpdate: requestHostUpdate,
onFormModeChange: (mode) => (state.infrastructureFormMode = mode),
onFormPatch: (path, value) => updateConfigFormValue(state, path, value),
onSearchChange: (query) => (state.infrastructureSearchQuery = query),
@@ -1838,6 +1843,7 @@ export function renderApp(state: AppViewState) {
onRawChange: (next) => {
state.configRaw = next;
},
onRequestUpdate: requestHostUpdate,
onFormModeChange: (mode) => (state.aiAgentsFormMode = mode),
onFormPatch: (path, value) => updateConfigFormValue(state, path, value),
onSearchChange: (query) => (state.aiAgentsSearchQuery = query),

View File

@@ -646,7 +646,6 @@ function renderTextInput(params: {
// oxlint-disable typescript/no-base-to-string
(schema.default !== undefined ? `Default: ${String(schema.default)}` : ""));
const displayValue = sensitiveState.isRedacted ? "" : (value ?? "");
const effectiveDisabled = disabled || sensitiveState.isRedacted;
const effectiveInputType =
sensitiveState.isSensitive && !sensitiveState.isRedacted ? "text" : inputType;
@@ -658,11 +657,16 @@ function renderTextInput(params: {
<div class="cfg-input-wrap">
<input
type=${effectiveInputType}
class="cfg-input"
class="cfg-input${sensitiveState.isRedacted ? " cfg-input--redacted" : ""}"
placeholder=${placeholder}
.value=${displayValue == null ? "" : String(displayValue)}
?disabled=${effectiveDisabled}
?disabled=${disabled}
?readonly=${sensitiveState.isRedacted}
@click=${() => {
if (sensitiveState.isRedacted && params.onToggleSensitivePath) {
params.onToggleSensitivePath(path);
}
}}
@input=${(e: Event) => {
if (sensitiveState.isRedacted) {
return;
@@ -700,7 +704,7 @@ function renderTextInput(params: {
type="button"
class="cfg-input__reset"
title="Reset to default"
?disabled=${effectiveDisabled}
?disabled=${disabled || sensitiveState.isRedacted}
@click=${() => onPatch(path, schema.default)}
>↺</button>
`
@@ -830,7 +834,6 @@ function renderJsonTextarea(params: {
isSensitivePathRevealed: params.isSensitivePathRevealed,
});
const displayValue = sensitiveState.isRedacted ? "" : fallback;
const effectiveDisabled = disabled || sensitiveState.isRedacted;
return html`
<div class="cfg-field">
@@ -839,12 +842,17 @@ function renderJsonTextarea(params: {
${renderTags(tags)}
<div class="cfg-input-wrap">
<textarea
class="cfg-textarea"
class="cfg-textarea${sensitiveState.isRedacted ? " cfg-textarea--redacted" : ""}"
placeholder=${sensitiveState.isRedacted ? REDACTED_PLACEHOLDER : "JSON value"}
rows="3"
.value=${displayValue}
?disabled=${effectiveDisabled}
?disabled=${disabled}
?readonly=${sensitiveState.isRedacted}
@click=${() => {
if (sensitiveState.isRedacted && params.onToggleSensitivePath) {
params.onToggleSensitivePath(path);
}
}}
@change=${(e: Event) => {
if (sensitiveState.isRedacted) {
return;
@@ -1253,14 +1261,19 @@ function renderMapField(params: {
? html`
<div class="cfg-input-wrap">
<textarea
class="cfg-textarea cfg-textarea--sm"
class="cfg-textarea cfg-textarea--sm${sensitiveState.isRedacted ? " cfg-textarea--redacted" : ""}"
placeholder=${
sensitiveState.isRedacted ? REDACTED_PLACEHOLDER : "JSON value"
}
rows="2"
.value=${sensitiveState.isRedacted ? "" : fallback}
?disabled=${disabled || sensitiveState.isRedacted}
?disabled=${disabled}
?readonly=${sensitiveState.isRedacted}
@click=${() => {
if (sensitiveState.isRedacted && onToggleSensitivePath) {
onToggleSensitivePath(valuePath);
}
}}
@change=${(e: Event) => {
if (sensitiveState.isRedacted) {
return;

View File

@@ -56,6 +56,7 @@ export type ConfigProps = {
includeSections?: string[];
excludeSections?: string[];
includeVirtualSections?: boolean;
onRequestUpdate?: () => void;
};
// SVG Icons for sidebar (Lucide-style)
@@ -672,6 +673,7 @@ export function renderConfig(props: ConfigProps) {
const formUnsafe = analysis.schema ? analysis.unsupportedPaths.length > 0 : false;
const formMode = showModeToggle ? props.formMode : "form";
const envSensitiveVisible = cvs.envRevealed;
const requestUpdate = props.onRequestUpdate ?? (() => props.onRawChange(props.raw));
// Build categorised nav from schema - only include sections that exist in the schema
const schemaProps = analysis.schema?.properties ?? {};
@@ -905,7 +907,7 @@ export function renderConfig(props: ConfigProps) {
class="btn btn--sm"
@click=${() => {
cvs.validityDismissed = true;
props.onRawChange(props.raw);
requestUpdate();
}}
>Don't remind again</button>
</div>
@@ -982,7 +984,7 @@ export function renderConfig(props: ConfigProps) {
title=${envSensitiveVisible ? "Hide env values" : "Reveal env values"}
@click=${() => {
cvs.envRevealed = !cvs.envRevealed;
props.onRawChange(props.raw);
requestUpdate();
}}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="16" height="16">
@@ -1031,7 +1033,7 @@ export function renderConfig(props: ConfigProps) {
isSensitivePathRevealed,
onToggleSensitivePath: (path) => {
toggleSensitivePathReveal(path);
props.onRawChange(props.raw);
requestUpdate();
},
})
}
@@ -1071,7 +1073,7 @@ export function renderConfig(props: ConfigProps) {
aria-pressed=${!blurred}
@click=${() => {
cvs.rawRevealed = !cvs.rawRevealed;
props.onRawChange(props.raw);
requestUpdate();
}}
>
${blurred ? icons.eyeOff : icons.eye}