@@ -13,6 +13,9 @@ import {
1313 Form ,
1414 SegmentedControl ,
1515 Modal ,
16+ FormField ,
17+ Input ,
18+ RadioGroup ,
1619} from '@awsui/components-react' ;
1720import Editor from '@monaco-editor/react' ;
1821// eslint-disable-next-line import/no-extraneous-dependencies
@@ -43,6 +46,10 @@ const ConfigurationLayout = () => {
4346 const [ viewMode , setViewMode ] = useState ( 'form' ) ; // Form view as default
4447 const [ showResetModal , setShowResetModal ] = useState ( false ) ;
4548 const [ showSaveAsDefaultModal , setShowSaveAsDefaultModal ] = useState ( false ) ;
49+ const [ showExportModal , setShowExportModal ] = useState ( false ) ;
50+ const [ exportFormat , setExportFormat ] = useState ( 'json' ) ;
51+ const [ exportFileName , setExportFileName ] = useState ( 'configuration' ) ;
52+ const [ importError , setImportError ] = useState ( null ) ;
4653
4754 const editorRef = useRef ( null ) ;
4855
@@ -761,6 +768,71 @@ const ConfigurationLayout = () => {
761768 }
762769 } ;
763770
771+ const handleExport = ( ) => {
772+ try {
773+ let content ;
774+ let mimeType ;
775+ let fileExtension ;
776+
777+ if ( exportFormat === 'yaml' ) {
778+ content = yaml . dump ( mergedConfig ) ;
779+ mimeType = 'text/yaml' ;
780+ fileExtension = 'yaml' ;
781+ } else {
782+ content = JSON . stringify ( mergedConfig , null , 2 ) ;
783+ mimeType = 'application/json' ;
784+ fileExtension = 'json' ;
785+ }
786+
787+ const blob = new Blob ( [ content ] , { type : mimeType } ) ;
788+ const url = URL . createObjectURL ( blob ) ;
789+ const link = document . createElement ( 'a' ) ;
790+ link . href = url ;
791+ link . download = `${ exportFileName } .${ fileExtension } ` ;
792+ document . body . appendChild ( link ) ;
793+ link . click ( ) ;
794+ document . body . removeChild ( link ) ;
795+ URL . revokeObjectURL ( url ) ;
796+ setShowExportModal ( false ) ;
797+ } catch ( err ) {
798+ setSaveError ( `Export failed: ${ err . message } ` ) ;
799+ }
800+ } ;
801+
802+ const handleImport = ( event ) => {
803+ const file = event . target . files [ 0 ] ;
804+ if ( ! file ) return ;
805+
806+ const reader = new FileReader ( ) ;
807+ reader . onload = ( e ) => {
808+ try {
809+ setImportError ( null ) ;
810+ let importedConfig ;
811+ const content = e . target . result ;
812+
813+ if ( file . name . endsWith ( '.yaml' ) || file . name . endsWith ( '.yml' ) ) {
814+ importedConfig = yaml . load ( content ) ;
815+ } else {
816+ importedConfig = JSON . parse ( content ) ;
817+ }
818+
819+ if ( importedConfig && typeof importedConfig === 'object' ) {
820+ handleFormChange ( importedConfig ) ;
821+ setSaveSuccess ( false ) ;
822+ setSaveError ( null ) ;
823+ } else {
824+ setImportError ( 'Invalid configuration file format' ) ;
825+ }
826+ } catch ( err ) {
827+ setImportError ( `Import failed: ${ err . message } ` ) ;
828+ }
829+ } ;
830+ reader . readAsText ( file ) ;
831+ // Clear the input value to allow re-importing the same file
832+ const input = event . target ;
833+ input . value = '' ;
834+ } ;
835+
764836 if ( loading ) {
765837 return (
766838 < Container header = { < Header variant = "h2" > Configuration</ Header > } >
@@ -845,6 +917,44 @@ const ConfigurationLayout = () => {
845917 </ Box >
846918 </ Modal >
847919
920+ < Modal
921+ visible = { showExportModal }
922+ onDismiss = { ( ) => setShowExportModal ( false ) }
923+ header = "Export Configuration"
924+ footer = {
925+ < Box float = "right" >
926+ < SpaceBetween direction = "horizontal" size = "xs" >
927+ < Button variant = "link" onClick = { ( ) => setShowExportModal ( false ) } >
928+ Cancel
929+ </ Button >
930+ < Button variant = "primary" onClick = { handleExport } >
931+ Export
932+ </ Button >
933+ </ SpaceBetween >
934+ </ Box >
935+ }
936+ >
937+ < SpaceBetween direction = "vertical" size = "l" >
938+ < FormField label = "File format" >
939+ < RadioGroup
940+ value = { exportFormat }
941+ onChange = { ( { detail } ) => setExportFormat ( detail . value ) }
942+ items = { [
943+ { value : 'json' , label : 'JSON' } ,
944+ { value : 'yaml' , label : 'YAML' } ,
945+ ] }
946+ />
947+ </ FormField >
948+ < FormField label = "File name" >
949+ < Input
950+ value = { exportFileName }
951+ onChange = { ( { detail } ) => setExportFileName ( detail . value ) }
952+ placeholder = "configuration"
953+ />
954+ </ FormField >
955+ </ SpaceBetween >
956+ </ Modal >
957+
848958 < Container
849959 header = {
850960 < Header
@@ -870,6 +980,19 @@ const ConfigurationLayout = () => {
870980 Format YAML
871981 </ Button >
872982 ) }
983+ < Button variant = "normal" onClick = { ( ) => setShowExportModal ( true ) } >
984+ Export
985+ </ Button >
986+ < Button variant = "normal" onClick = { ( ) => document . getElementById ( 'import-file' ) . click ( ) } >
987+ Import
988+ </ Button >
989+ < input
990+ id = "import-file"
991+ type = "file"
992+ accept = ".json,.yaml,.yml"
993+ style = { { display : 'none' } }
994+ onChange = { handleImport }
995+ />
873996 < Button variant = "normal" onClick = { ( ) => setShowResetModal ( true ) } >
874997 Restore default (All)
875998 </ Button >
@@ -904,6 +1027,12 @@ const ConfigurationLayout = () => {
9041027 </ Alert >
9051028 ) }
9061029
1030+ { importError && (
1031+ < Alert type = "error" dismissible onDismiss = { ( ) => setImportError ( null ) } header = "Import error" >
1032+ { importError }
1033+ </ Alert >
1034+ ) }
1035+
9071036 { validationErrors . length > 0 && (
9081037 < Alert type = "warning" header = "Validation errors" >
9091038 < ul >
0 commit comments