@@ -42,6 +42,7 @@ const ConfigurationLayout = () => {
4242 const [ validationErrors , setValidationErrors ] = useState ( [ ] ) ;
4343 const [ viewMode , setViewMode ] = useState ( 'form' ) ; // Form view as default
4444 const [ showResetModal , setShowResetModal ] = useState ( false ) ;
45+ const [ showSaveAsDefaultModal , setShowSaveAsDefaultModal ] = useState ( false ) ;
4546
4647 const editorRef = useRef ( null ) ;
4748
@@ -473,7 +474,7 @@ const ConfigurationLayout = () => {
473474 }
474475 } ;
475476
476- const handleSave = async ( ) => {
477+ const handleSave = async ( saveAsDefault = false ) => {
477478 // Validate content before saving
478479 const currentErrors = validateCurrentContent ( ) ;
479480
@@ -580,95 +581,106 @@ const ConfigurationLayout = () => {
580581 return newResult ;
581582 } ;
582583
583- // Create our customized config by comparing with defaults
584- const differences = compareWithDefault ( formValues , defaultConfig ) ;
584+ let configToSave ;
585585
586- // Flatten path results into a proper object structure - revised to avoid ESLint errors
587- const buildObjectFromPaths = ( paths ) => {
588- // Create a fresh result object
589- const newResult = { } ;
590-
591- Object . entries ( paths ) . forEach ( ( [ path , value ] ) => {
592- if ( ! path ) return ; // Skip empty paths
593-
594- // For paths with dots, build nested structure
595- if ( path . includes ( '.' ) || path . includes ( '[' ) ) {
596- // Handle array notation
597- if ( path . includes ( '[' ) ) {
598- // Arrays need special handling
599- // This is simplified - we'll include the whole array when it's customized
600- const arrayPath = path . split ( '[' ) [ 0 ] ;
601- if ( ! Object . prototype . hasOwnProperty . call ( newResult , arrayPath ) ) {
602- // Find the array in formValues
603- const arrayValue = path . split ( '.' ) . reduce ( ( acc , part ) => {
604- if ( ! acc ) return undefined ;
605- return acc [ part . replace ( / \[ \d + \] $ / , '' ) ] ;
606- } , formValues ) ;
607-
608- if ( arrayValue ) {
609- // Create a new object with this property
610- Object . assign ( newResult , { [ arrayPath ] : arrayValue } ) ;
586+ if ( saveAsDefault ) {
587+ // When saving as default, save the entire current configuration
588+ configToSave = { ...formValues , saveAsDefault : true } ;
589+ console . log ( 'Saving entire config as new default:' , configToSave ) ;
590+ } else {
591+ // Create our customized config by comparing with defaults
592+ const differences = compareWithDefault ( formValues , defaultConfig ) ;
593+
594+ // Flatten path results into a proper object structure - revised to avoid ESLint errors
595+ const buildObjectFromPaths = ( paths ) => {
596+ // Create a fresh result object
597+ const newResult = { } ;
598+
599+ Object . entries ( paths ) . forEach ( ( [ path , value ] ) => {
600+ if ( ! path ) return ; // Skip empty paths
601+
602+ // For paths with dots, build nested structure
603+ if ( path . includes ( '.' ) || path . includes ( '[' ) ) {
604+ // Handle array notation
605+ if ( path . includes ( '[' ) ) {
606+ // Arrays need special handling
607+ // This is simplified - we'll include the whole array when it's customized
608+ const arrayPath = path . split ( '[' ) [ 0 ] ;
609+ if ( ! Object . prototype . hasOwnProperty . call ( newResult , arrayPath ) ) {
610+ // Find the array in formValues
611+ const arrayValue = path . split ( '.' ) . reduce ( ( acc , part ) => {
612+ if ( ! acc ) return undefined ;
613+ return acc [ part . replace ( / \[ \d + \] $ / , '' ) ] ;
614+ } , formValues ) ;
615+
616+ if ( arrayValue ) {
617+ // Create a new object with this property
618+ Object . assign ( newResult , { [ arrayPath ] : arrayValue } ) ;
619+ }
620+ }
621+ } else {
622+ // Regular object paths
623+ const parts = path . split ( '.' ) ;
624+
625+ // Build an object to merge
626+ const objectToMerge = { } ;
627+ let current = objectToMerge ;
628+
629+ // Build nested structure without modifying existing objects
630+ for ( let i = 0 ; i < parts . length - 1 ; i += 1 ) {
631+ // Use += 1 instead of ++
632+ current [ parts [ i ] ] = { } ;
633+ current = current [ parts [ i ] ] ;
611634 }
612- }
613- } else {
614- // Regular object paths
615- const parts = path . split ( '.' ) ;
616-
617- // Build an object to merge
618- const objectToMerge = { } ;
619- let current = objectToMerge ;
620-
621- // Build nested structure without modifying existing objects
622- for ( let i = 0 ; i < parts . length - 1 ; i += 1 ) {
623- // Use += 1 instead of ++
624- current [ parts [ i ] ] = { } ;
625- current = current [ parts [ i ] ] ;
626- }
627635
628- // Set the value at the final path
629- current [ parts [ parts . length - 1 ] ] = value ;
636+ // Set the value at the final path
637+ current [ parts [ parts . length - 1 ] ] = value ;
630638
631- // Deep merge this into result
632- const deepMerge = ( target , source ) => {
633- const output = { ...target } ;
639+ // Deep merge this into result
640+ const deepMerge = ( target , source ) => {
641+ const output = { ...target } ;
634642
635- Object . keys ( source ) . forEach ( ( key ) => {
636- if ( source [ key ] && typeof source [ key ] === 'object' && ! Array . isArray ( source [ key ] ) ) {
637- if ( target [ key ] && typeof target [ key ] === 'object' ) {
638- output [ key ] = deepMerge ( target [ key ] , source [ key ] ) ;
643+ Object . keys ( source ) . forEach ( ( key ) => {
644+ if ( source [ key ] && typeof source [ key ] === 'object' && ! Array . isArray ( source [ key ] ) ) {
645+ if ( target [ key ] && typeof target [ key ] === 'object' ) {
646+ output [ key ] = deepMerge ( target [ key ] , source [ key ] ) ;
647+ } else {
648+ output [ key ] = { ...source [ key ] } ;
649+ }
639650 } else {
640- output [ key ] = { ... source [ key ] } ;
651+ output [ key ] = source [ key ] ;
641652 }
642- } else {
643- output [ key ] = source [ key ] ;
644- }
645- } ) ;
653+ } ) ;
646654
647- return output ;
648- } ;
655+ return output ;
656+ } ;
649657
650- // Merge into result without modifying original objects
651- Object . assign ( newResult , deepMerge ( newResult , objectToMerge ) ) ;
658+ // Merge into result without modifying original objects
659+ Object . assign ( newResult , deepMerge ( newResult , objectToMerge ) ) ;
660+ }
661+ } else {
662+ // For top-level values, create a new object with the property
663+ Object . assign ( newResult , { [ path ] : value } ) ;
652664 }
653- } else {
654- // For top-level values, create a new object with the property
655- Object . assign ( newResult , { [ path ] : value } ) ;
656- }
657- } ) ;
658-
659- return newResult ;
660- } ;
665+ } ) ;
661666
662- // Convert the difference paths to a proper nested structure
663- Object . assign ( customConfigToSave , buildObjectFromPaths ( differences ) ) ;
667+ return newResult ;
668+ } ;
664669
665- console . log ( 'Saving customized config:' , customConfigToSave ) ;
670+ // Convert the difference paths to a proper nested structure
671+ Object . assign ( customConfigToSave , buildObjectFromPaths ( differences ) ) ;
672+ configToSave = customConfigToSave ;
673+ console . log ( 'Saving customized config:' , configToSave ) ;
674+ }
666675
667676 // Make sure we send at least the Info field, even if no customizations
668- const success = await updateConfiguration ( customConfigToSave ) ;
677+ const success = await updateConfiguration ( configToSave ) ;
669678
670679 if ( success ) {
671680 setSaveSuccess ( true ) ;
681+ if ( saveAsDefault ) {
682+ setShowSaveAsDefaultModal ( false ) ;
683+ }
672684 // Force a refresh of the configuration to ensure UI is in sync with backend
673685 setTimeout ( ( ) => {
674686 fetchConfiguration ( ) ;
@@ -810,6 +822,29 @@ const ConfigurationLayout = () => {
810822 </ Box >
811823 </ Modal >
812824
825+ < Modal
826+ visible = { showSaveAsDefaultModal }
827+ onDismiss = { ( ) => setShowSaveAsDefaultModal ( false ) }
828+ header = "Save as New Default"
829+ footer = {
830+ < Box float = "right" >
831+ < SpaceBetween direction = "horizontal" size = "xs" >
832+ < Button variant = "link" onClick = { ( ) => setShowSaveAsDefaultModal ( false ) } >
833+ Cancel
834+ </ Button >
835+ < Button variant = "primary" onClick = { ( ) => handleSave ( true ) } loading = { isSaving } >
836+ Save as Default
837+ </ Button >
838+ </ SpaceBetween >
839+ </ Box >
840+ }
841+ >
842+ < Box variant = "span" >
843+ Are you sure you want to save the current configuration as the new default? This will replace the existing
844+ default configuration and cannot be undone.
845+ </ Box >
846+ </ Modal >
847+
813848 < Container
814849 header = {
815850 < Header
@@ -838,7 +873,10 @@ const ConfigurationLayout = () => {
838873 < Button variant = "normal" onClick = { ( ) => setShowResetModal ( true ) } >
839874 Restore default (All)
840875 </ Button >
841- < Button variant = "primary" onClick = { handleSave } loading = { isSaving } >
876+ < Button variant = "normal" onClick = { ( ) => setShowSaveAsDefaultModal ( true ) } >
877+ Save as default
878+ </ Button >
879+ < Button variant = "primary" onClick = { ( ) => handleSave ( false ) } loading = { isSaving } >
842880 Save changes
843881 </ Button >
844882 </ SpaceBetween >
0 commit comments