1- import { Alert , Badge , Box , ButtonGroup , Chip , CircularProgress , Dialog , DialogContent , DialogTitle , Divider , FormControlLabel , FormHelperText , Grid2 , IconButton , Link , Modal , Paper , Stack , Switch , Tab , Tabs , TextField , Tooltip , Typography , useTheme } from "@mui/material" ;
1+ import { Alert , Badge , Box , ButtonGroup , Chip , CircularProgress , Dialog , DialogContent , DialogTitle , Divider , FormControlLabel , FormHelperText , Grid2 , IconButton , Link , ListItem , Modal , Paper , Stack , Switch , Tab , Tabs , TextField , Tooltip , Typography , useTheme } from "@mui/material" ;
22import { CheckOutlined , Close , CloseOutlined , Code , Delete , DeleteOutline , DeleteOutlined , LockReset , Save , SaveOutlined } from "@mui/icons-material" ;
3- import { useEffect , useState } from "react" ;
3+ import { useEffect , useMemo , useState } from "react" ;
44import { CatalogItemWithName } from "../../types/catalog" ;
55import Secrets from "../../Secrets" ;
66import { v1 } from "@docker/extension-api-client-types" ;
77import { useCatalogContext } from "../../context/CatalogContext" ;
88import { useConfigContext } from "../../context/ConfigContext" ;
9- import { deepFlattenObject , deepGet , deepSet , mergeDeep } from "../../MergeDeep" ;
10- import { DeepObject } from "../../types/utils" ;
11- import { Parameter , ParameterArray , ParameterObject , Parameters , ParsedParameters , Config } from "../../types/config" ;
12- import { Ref } from "../../Refs" ;
139import { ASSIGNED_SECRET_PLACEHOLDER , CATALOG_LAYOUT_SX , UNASSIGNED_SECRET_PLACEHOLDER } from "../../Constants" ;
14- import JsonSchemaLibrary from "json-schema-library" ;
1510import ConfigEditor from "./ConfigEditor" ;
1611
1712// Styles for the tab panel
@@ -42,11 +37,6 @@ function TabPanel(props: TabPanelProps) {
4237 ) ;
4338}
4439
45- // Define types reference
46- const types = [ 'string' , 'number' , 'boolean' , 'array' , 'object' ] as const ;
47-
48-
49-
5040interface ConfigurationModalProps {
5141 open : boolean ;
5242 onClose : ( ) => void ;
@@ -55,7 +45,6 @@ interface ConfigurationModalProps {
5545 onToggleRegister : ( checked : boolean ) => void ;
5646 registered : boolean ;
5747 onSecretChange : ( secret : { name : string , value : string } ) => Promise < void > ;
58- unAssignedSecrets : { name : string , assigned : boolean } [ ] ;
5948}
6049
6150
@@ -67,42 +56,13 @@ const ConfigurationModal = ({
6756 onToggleRegister,
6857 registered,
6958 onSecretChange,
70- unAssignedSecrets,
7159} : ConfigurationModalProps ) => {
7260
73- const { registryItems, secrets } = useCatalogContext ( ) ;
74- const { config , configLoading } = useConfigContext ( ) ;
61+ const { registryItems, secrets, getCanRegisterCatalogItem } = useCatalogContext ( ) ;
62+ const { configLoading , config } = useConfigContext ( ) ;
7563 const [ localSecrets , setLocalSecrets ] = useState < { [ key : string ] : string | undefined } > ( { } ) ;
7664 const theme = useTheme ( ) ;
7765
78- // Helper function to get default values based on type
79- const getDefaultValue = ( schema : any ) => {
80- if ( ! schema || ! schema . type ) return '' ;
81-
82- switch ( schema . type ) {
83- case 'string' :
84- return schema . default || '' ;
85- case 'number' :
86- case 'integer' :
87- return schema . default || 0 ;
88- case 'boolean' :
89- return schema . default || false ;
90- case 'array' :
91- return schema . default || [ ] ;
92- case 'object' :
93- if ( schema . properties ) {
94- const objTemplate : Record < string , any > = { } ;
95- Object . entries ( schema . properties ) . forEach ( ( [ key , propSchema ] : [ string , any ] ) => {
96- objTemplate [ key ] = getDefaultValue ( propSchema ) ;
97- } ) ;
98- return schema . default || objTemplate ;
99- }
100- return schema . default || { } ;
101- default :
102- return '' ;
103- }
104- } ;
105-
10666 const toolChipStyle = {
10767 padding : '2px 8px' ,
10868 justifyContent : 'center' ,
@@ -128,7 +88,7 @@ const ConfigurationModal = ({
12888
12989 // Load assigned secrets
13090 useEffect ( ( ) => {
131- const loadedSecrets = Secrets . getAssignedSecrets ( catalogItem , secrets ) ;
91+ const loadedSecrets = Secrets . getSecretsWithAssignment ( catalogItem , secrets ) ;
13292 setAssignedSecrets ( loadedSecrets ) ;
13393 setLocalSecrets ( loadedSecrets . reduce ( ( acc , secret ) => {
13494 acc [ secret . name ] = secret . assigned ? ASSIGNED_SECRET_PLACEHOLDER : '' ;
@@ -142,25 +102,16 @@ const ConfigurationModal = ({
142102 setTabValue ( newValue ) ;
143103 } ;
144104
145- // Determine if we should show the secrets tab and config tab
146- const hasSecrets = assignedSecrets . length > 0 ;
147- const hasConfig = catalogItem . config && catalogItem . config . length > 0 ;
105+ // Memoize the canRegister value to prevent unnecessary recalculations
106+ const canRegister = useMemo ( ( ) => getCanRegisterCatalogItem ( catalogItem ) , [ getCanRegisterCatalogItem , catalogItem , config ] ) ;
148107
149- // If there's only one tab to show, automatically select it
150- useEffect ( ( ) => {
151- if ( ! hasSecrets && hasConfig || hasSecrets && ! hasConfig ) {
152- setTabValue ( 0 ) ; // Config tab
153- }
154- } , [ hasSecrets , hasConfig ] ) ;
155-
156- const hasAllSecrets = unAssignedSecrets . length === 0
157- const emptyConfig = catalogItem . config && ( ! config ?. [ catalogItem . name ] || Object . keys ( config ?. [ catalogItem . name ] || { } ) . length === 0 )
108+ const contributesNoConfigOrSecrets = ( ! catalogItem . config || catalogItem . config . length === 0 ) && ( ! catalogItem . secrets || catalogItem . secrets . length === 0 ) ;
158109
159110 useEffect ( ( ) => {
160- if ( ! hasAllSecrets || emptyConfig ) {
161- setTabValue ( 1 ) ;
111+ if ( ! canRegister && ! contributesNoConfigOrSecrets ) {
112+ setTabValue ( 1 ) ; // Config tab
162113 }
163- } , [ hasAllSecrets , emptyConfig ] ) ;
114+ } , [ canRegister , contributesNoConfigOrSecrets ] ) ;
164115
165116
166117 if ( ! registryItems ) {
@@ -200,8 +151,8 @@ const ConfigurationModal = ({
200151 < Typography sx = { { mt : 2 , maxHeight : '5em' , overflow : 'auto' } } color = "text.secondary" >
201152 { catalogItem . description }
202153 </ Typography >
203- < Tooltip placement = "right" title = { ! hasAllSecrets || emptyConfig ? 'You must assign all secrets and configure the item before it can be used.' : '' } >
204- < FormControlLabel control = { < Switch disabled = { ! hasAllSecrets || emptyConfig } checked = { registered } onChange = { ( e ) => onToggleRegister ( e . target . checked ) } /> } label = { registered ? 'Disable ' + `${ catalogItem . name } tools` : 'Enable ' + `${ catalogItem . name } tools` } sx = { { mt : 2 } } />
154+ < Tooltip placement = "right" title = { ! canRegister ? 'You must assign all secrets and configure the item before it can be used.' : '' } >
155+ < FormControlLabel control = { < Switch disabled = { ! canRegister } checked = { registered } onChange = { ( e ) => onToggleRegister ( e . target . checked ) } /> } label = { registered ? 'Disable ' + `${ catalogItem . name } tools` : 'Enable ' + `${ catalogItem . name } tools` } sx = { { mt : 2 } } />
205156 </ Tooltip >
206157 < Divider sx = { { mt : 2 } } />
207158 < Typography variant = "caption" sx = { { mt : 2 , color : 'text.secondary' } } >
@@ -220,10 +171,9 @@ const ConfigurationModal = ({
220171 < Tabs value = { tabValue } onChange = { handleTabChange } >
221172 < Tab label = "Tools" />
222173 { /* <Tab label="Prompts" /> */ }
223- < Tab disabled = { ! hasSecrets && ! hasConfig } label = { < Badge invisible = { hasAllSecrets && ! emptyConfig } sx = { { pl : 1 , pr : 1 } } variant = "dot" badgeContent = { hasSecrets ? 'Secrets' : 'Config' } color = "error" > Config & Secrets </ Badge > } />
174+ < Tab disabled = { contributesNoConfigOrSecrets } label = { < Badge invisible = { canRegister } sx = { { pl : 1 , pr : 1 } } variant = "dot" badgeContent = { catalogItem . config && catalogItem . config . length > 0 ? 'Secrets' : 'Config' } color = "error" > Config & Secrets </ Badge > } />
224175 </ Tabs >
225176 </ Box >
226-
227177 < TabPanel value = { tabValue } index = { 0 } >
228178 { ! catalogItem ?. tools ?. length && (
229179 < Typography >
@@ -247,46 +197,51 @@ const ConfigurationModal = ({
247197 < Stack direction = "column" spacing = { 2 } sx = { { border : '2px solid' , borderColor : theme . palette . warning . contrastText , borderRadius : 2 , p : 2 , mt : 2 } } >
248198 < ConfigEditor catalogItem = { catalogItem } />
249199 < Typography variant = "h6" sx = { { mb : 1 } } > Secrets</ Typography >
250- { assignedSecrets ?. map ( secret => {
251- const secretEdited = secret . assigned ? localSecrets [ secret . name ] !== ASSIGNED_SECRET_PLACEHOLDER : localSecrets [ secret . name ] !== '' ;
252- return (
253- < Stack key = { secret . name } direction = "row" spacing = { 2 } alignItems = "center" >
254- < TextField key = { secret . name } label = { secret . name } value = { localSecrets [ secret . name ] } fullWidth onChange = { ( e ) => {
255- setLocalSecrets ( { ...localSecrets , [ secret . name ] : e . target . value } ) ;
256- } } type = 'password' />
257- { secret . assigned && ! secretEdited && < IconButton size = "small" color = "error" onClick = { ( ) => {
258- setLocalSecrets ( { ...localSecrets , [ secret . name ] : UNASSIGNED_SECRET_PLACEHOLDER } ) ;
259- onSecretChange ( { name : secret . name , value : UNASSIGNED_SECRET_PLACEHOLDER } ) ;
260- } } >
261- < DeleteOutlined />
262- </ IconButton > }
263- { secretEdited && < ButtonGroup >
264- < IconButton onClick = { async ( ) => {
265- await onSecretChange ( { name : secret . name , value : localSecrets [ secret . name ] ! } ) ;
266- } } >
267- < CheckOutlined sx = { { color : 'success.main' } } />
268- </ IconButton >
269- < IconButton onClick = { async ( ) => {
270- setLocalSecrets ( { ...localSecrets , [ secret . name ] : secret . assigned ? ASSIGNED_SECRET_PLACEHOLDER : '' } ) ;
271- } } >
272- < CloseOutlined sx = { { color : 'error.main' } } />
273- </ IconButton >
274- </ ButtonGroup > }
275- </ Stack >
200+ {
201+ catalogItem . secrets && catalogItem . secrets ?. length > 0 ? (
202+ assignedSecrets ?. map ( secret => {
203+ const secretEdited = secret . assigned ? localSecrets [ secret . name ] !== ASSIGNED_SECRET_PLACEHOLDER : localSecrets [ secret . name ] !== '' ;
204+ return (
205+ < Stack key = { secret . name } direction = "row" spacing = { 2 } alignItems = "center" >
206+ < TextField key = { secret . name } label = { secret . name } value = { localSecrets [ secret . name ] } fullWidth onChange = { ( e ) => {
207+ setLocalSecrets ( { ...localSecrets , [ secret . name ] : e . target . value } ) ;
208+ } } type = 'password' />
209+ { secret . assigned && ! secretEdited && < IconButton size = "small" color = "error" onClick = { ( ) => {
210+ setLocalSecrets ( { ...localSecrets , [ secret . name ] : UNASSIGNED_SECRET_PLACEHOLDER } ) ;
211+ onSecretChange ( { name : secret . name , value : UNASSIGNED_SECRET_PLACEHOLDER } ) ;
212+ } } >
213+ < DeleteOutlined />
214+ </ IconButton > }
215+ { secretEdited && < ButtonGroup >
216+ < IconButton onClick = { async ( ) => {
217+ await onSecretChange ( { name : secret . name , value : localSecrets [ secret . name ] ! } ) ;
218+ } } >
219+ < CheckOutlined sx = { { color : 'success.main' } } />
220+ </ IconButton >
221+ < IconButton onClick = { async ( ) => {
222+ setLocalSecrets ( { ...localSecrets , [ secret . name ] : secret . assigned ? ASSIGNED_SECRET_PLACEHOLDER : '' } ) ;
223+ } } >
224+ < CloseOutlined sx = { { color : 'error.main' } } />
225+ </ IconButton >
226+ </ ButtonGroup > }
227+ </ Stack >
228+ )
229+ } ) ) : (
230+ < Alert severity = "info" > No secrets available for this item.</ Alert >
276231 )
277- } ) }
232+ }
278233 </ Stack >
279234 </ Stack >
280- </ TabPanel >
235+ </ TabPanel >
281236 < TabPanel value = { tabValue } index = { 2 } >
282237 < Typography > Examples</ Typography >
283238 WIP
284239 </ TabPanel >
285240 </ >
286241 ) }
287- </ Paper >
288- </ Modal >
289- ) ;
242+ </ Paper >
243+ </ Modal >
244+ )
290245} ;
291246
292247export default ConfigurationModal ;
0 commit comments