11import { useEffect , useMemo , useRef , useState } from "react" ;
2- import { Trash2 } from "lucide-react" ;
2+ import { ChevronDown , ChevronRight , Eye , EyeOff , Trash2 } from "lucide-react" ;
33
44// Keep in sync with ReservedEnvVarNames in control-plane/internal/handlers/envvars.go.
55const RESERVED = new Set ( [
@@ -19,6 +19,8 @@ export interface EnvVarsDelta {
1919interface Props {
2020 /** Current plaintext values from the API. */
2121 values : Record < string , string > ;
22+ /** Read-only values inherited from a broader scope, e.g. global settings. */
23+ inheritedValues ?: Record < string , string > ;
2224 /** Section title shown in the card header. */
2325 title : string ;
2426 /** Help text rendered below the title. */
@@ -70,6 +72,7 @@ function buildInitialRows(values: Record<string, string>): EditRow[] {
7072
7173export default function EnvVarsEditor ( {
7274 values,
75+ inheritedValues = { } ,
7376 title,
7477 description,
7578 onSave,
@@ -82,6 +85,8 @@ export default function EnvVarsEditor({
8285 const [ rows , setRows ] = useState < EditRow [ ] > ( ( ) =>
8386 inline ? buildInitialRows ( values ) : [ ] ,
8487 ) ;
88+ const [ showValues , setShowValues ] = useState ( false ) ;
89+ const [ showInheritedValues , setShowInheritedValues ] = useState ( false ) ;
8590 const [ error , setError ] = useState < string | null > ( null ) ;
8691
8792 // In inline mode, report the current valid map upward whenever rows change.
@@ -227,34 +232,81 @@ export default function EnvVarsEditor({
227232 } ;
228233
229234 const valueKeys = useMemo ( ( ) => Object . keys ( values ) . sort ( ) , [ values ] ) ;
235+ const inheritedKeys = useMemo (
236+ ( ) => Object . keys ( inheritedValues ) . filter ( ( key ) => values [ key ] === undefined ) . sort ( ) ,
237+ [ inheritedValues , values ] ,
238+ ) ;
230239
231240 return (
232241 < div className = "bg-white rounded-lg border border-gray-200 p-6" >
233242 < div className = "flex items-center justify-between mb-2" >
234243 < h3 className = "text-sm font-medium text-gray-900" > { title } </ h3 >
235244 { ! inline && mode === "display" && (
236- < button
237- type = "button"
238- onClick = { beginEdit }
239- className = "text-xs text-blue-600 hover:text-blue-800"
240- >
241- Edit
242- </ button >
245+ < div className = "flex items-center gap-3" >
246+ < button
247+ type = "button"
248+ onClick = { ( ) => setShowValues ( ( prev ) => ! prev ) }
249+ className = "text-gray-400 hover:text-gray-600"
250+ title = { showValues ? "Hide values" : "Show values" }
251+ aria-label = { showValues ? "Hide values" : "Show values" }
252+ >
253+ { showValues ? < EyeOff size = { 14 } /> : < Eye size = { 14 } /> }
254+ </ button >
255+ < button
256+ type = "button"
257+ onClick = { beginEdit }
258+ className = "text-xs text-blue-600 hover:text-blue-800"
259+ >
260+ Edit
261+ </ button >
262+ </ div >
243263 ) }
244264 </ div >
245265 < p className = "text-xs text-gray-500 mb-4" > { description } </ p >
246266
247267 { mode === "display" ? (
248- valueKeys . length === 0 ? (
268+ valueKeys . length === 0 && inheritedKeys . length === 0 ? (
249269 < p className = "text-sm text-gray-400 italic" > { emptyMessage } </ p >
250270 ) : (
251- < div className = "divide-y divide-gray-100" >
252- { valueKeys . map ( ( k ) => (
253- < div key = { k } className = "py-2 flex items-center justify-between gap-4" >
254- < span className = "text-sm font-mono text-gray-900" > { k } </ span >
255- < span className = "text-xs font-mono text-gray-500 truncate" > { values [ k ] } </ span >
271+ < div className = "space-y-4" >
272+ { valueKeys . length > 0 && (
273+ < div >
274+ < p className = "text-xs font-medium uppercase tracking-wide text-gray-500 mb-2" >
275+ Instance
276+ </ p >
277+ < div className = "divide-y divide-gray-100" >
278+ { valueKeys . map ( ( k ) => (
279+ < div key = { k } className = "py-2 flex items-center justify-between gap-4" >
280+ < span className = "text-sm font-mono text-gray-900" > { k } </ span >
281+ < span className = "text-xs font-mono text-gray-500 truncate" > { showValues ? values [ k ] : "••••••••" } </ span >
282+ </ div >
283+ ) ) }
284+ </ div >
285+ </ div >
286+ ) }
287+
288+ { inheritedKeys . length > 0 && (
289+ < div >
290+ < button
291+ type = "button"
292+ onClick = { ( ) => setShowInheritedValues ( ( prev ) => ! prev ) }
293+ className = "flex items-center gap-1 text-xs font-medium uppercase tracking-wide text-gray-500 mb-2 hover:text-gray-700"
294+ >
295+ { showInheritedValues ? < ChevronDown size = { 12 } /> : < ChevronRight size = { 12 } /> }
296+ < span > Inherited From Global Settings</ span >
297+ </ button >
298+ { showInheritedValues && (
299+ < div className = "divide-y divide-gray-100" >
300+ { inheritedKeys . map ( ( k ) => (
301+ < div key = { k } className = "py-2 flex items-center justify-between gap-4" >
302+ < span className = "text-sm font-mono text-gray-700" > { k } </ span >
303+ < span className = "text-xs font-mono text-gray-400 truncate" > { showValues ? inheritedValues [ k ] : "••••••••" } </ span >
304+ </ div >
305+ ) ) }
306+ </ div >
307+ ) }
256308 </ div >
257- ) ) }
309+ ) }
258310 </ div >
259311 )
260312 ) : (
0 commit comments