Skip to content

Commit d583f19

Browse files
author
Bob Strahan
committed
Add configuration import/export functionality
1 parent 1795983 commit d583f19

File tree

1 file changed

+129
-0
lines changed

1 file changed

+129
-0
lines changed

src/ui/src/components/configuration-layout/ConfigurationLayout.jsx

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import {
1313
Form,
1414
SegmentedControl,
1515
Modal,
16+
FormField,
17+
Input,
18+
RadioGroup,
1619
} from '@awsui/components-react';
1720
import 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

Comments
 (0)