diff --git a/frontend/src/components/common/Resource/CreateButton.tsx b/frontend/src/components/common/Resource/CreateButton.tsx index 4bd579fdb7..5b9c4245f9 100644 --- a/frontend/src/components/common/Resource/CreateButton.tsx +++ b/frontend/src/components/common/Resource/CreateButton.tsx @@ -36,6 +36,7 @@ export default function CreateButton(props: CreateButtonProps) { const dispatchCreateEvent = useEventCallback(HeadlampEventType.CREATE_RESOURCE); const clusters = useClusterGroup(); const [targetCluster, setTargetCluster] = React.useState(clusters[0] || ''); + const [currentItem, setCurrentItem] = React.useState({}); // eslint-disable-line no-unused-vars // When the clusters in the group change, we want to reset the target cluster // if it's not in the new list of clusters. @@ -148,7 +149,7 @@ export default function CreateButton(props: CreateButtonProps) { )} setOpenDialog(false)} onSave={handleSave} diff --git a/frontend/src/components/common/Resource/EditorDialog.tsx b/frontend/src/components/common/Resource/EditorDialog.tsx index 58bd07b2e6..983c30cd50 100644 --- a/frontend/src/components/common/Resource/EditorDialog.tsx +++ b/frontend/src/components/common/Resource/EditorDialog.tsx @@ -49,8 +49,8 @@ import SimpleEditor from './SimpleEditor'; type KubeObjectIsh = Partial; export interface EditorDialogProps extends DialogProps { - /** The object to edit, or null to make the dialog be in "loading mode". Pass it an empty object if no contents are to be shown when the dialog is first open. */ - item: KubeObjectIsh | null; + /** The object(s) to edit, or null to make the dialog be in "loading mode". Pass it an empty object if no contents are to be shown when the dialog is first open. */ + item: KubeObjectIsh | object | object[] | string | null; /** Called when the dialog is closed. */ onClose: () => void; /** Called when the user clicks the save button. */ @@ -88,11 +88,14 @@ export default function EditorDialog(props: EditorDialogProps) { const [lang, setLang] = React.useState(i18n.language); const themeName = getThemeName(); - const originalCodeRef = React.useRef({ code: '', format: item ? 'yaml' : '' }); + const initialCode = typeof item === 'string' ? item : yaml.dump(item || {}); + const originalCodeRef = React.useRef({ code: initialCode, format: item ? 'yaml' : '' }); const [code, setCode] = React.useState(originalCodeRef.current); const codeRef = React.useRef(code); const lastCodeCheckHandler = React.useRef(0); - const previousVersionRef = React.useRef(item?.metadata?.resourceVersion || ''); + const previousVersionRef = React.useRef( + isKubeObjectIsh(item) ? item?.metadata?.resourceVersion || '' : '' + ); const [error, setError] = React.useState(''); const [docSpecs, setDocSpecs] = React.useState< KubeObjectInterface | KubeObjectInterface[] | null @@ -109,36 +112,45 @@ export default function EditorDialog(props: EditorDialogProps) { setUseSimpleEditorState(data); } + function isKubeObjectIsh(item: any): item is KubeObjectIsh { + return item && typeof item === 'object' && !Array.isArray(item) && 'metadata' in item; + } + // Update the code when the item changes, but only if the code hasn't been touched. React.useEffect(() => { if (!item || Object.keys(item || {}).length === 0) { + const defaultCode = '# Enter your YAML or JSON here'; + originalCodeRef.current = { code: defaultCode, format: 'yaml' }; + setCode({ code: defaultCode, format: 'yaml' }); return; } - const originalCode = originalCodeRef.current.code; - const itemCode = - originalCodeRef.current.format === 'json' ? JSON.stringify(item) : yaml.dump(item); - if (itemCode !== originalCodeRef.current.code) { - originalCodeRef.current = { code: itemCode, format: originalCodeRef.current.format }; - } + // Determine the format (YAML or JSON) and serialize to string + const format = looksLikeJson(originalCodeRef.current.code) ? 'json' : 'yaml'; + const itemCode = format === 'json' ? JSON.stringify(item) : yaml.dump(item); - if (!item.metadata) { - return; + // Update the code if the item representation has changed + if (itemCode !== originalCodeRef.current.code) { + originalCodeRef.current = { code: itemCode, format }; + setCode({ code: itemCode, format }); } - const resourceVersionsDiffer = - (previousVersionRef.current || '') !== (item.metadata!.resourceVersion || ''); - // Only change if the code hasn't been touched. - // We use the codeRef in this effect instead of the code, because we need to access the current - // state of the code but we don't want to trigger a re-render when we set the code here. - if (resourceVersionsDiffer || codeRef.current.code === originalCode) { - // Prevent updating to the same code, which would lead to an infinite loop. - if (codeRef.current.code !== itemCode) { - setCode({ code: itemCode, format: originalCodeRef.current.format }); - } + // Additional handling for Kubernetes objects + if (isKubeObjectIsh(item) && item.metadata) { + const resourceVersionsDiffer = + (previousVersionRef.current || '') !== (item.metadata!.resourceVersion || ''); + // Only change if the code hasn't been touched. + // We use the codeRef in this effect instead of the code, because we need to access the current + // state of the code but we don't want to trigger a re-render when we set the code here. + if (resourceVersionsDiffer || codeRef.current.code === originalCodeRef.current.code) { + // Prevent updating to the same code, which would lead to an infinite loop. + if (codeRef.current.code !== itemCode) { + setCode({ code: itemCode, format: originalCodeRef.current.format }); + } - if (resourceVersionsDiffer && !!item.metadata!.resourceVersion) { - previousVersionRef.current = item.metadata!.resourceVersion; + if (resourceVersionsDiffer && !!item.metadata!.resourceVersion) { + previousVersionRef.current = item.metadata!.resourceVersion; + } } } }, [item]); @@ -309,7 +321,9 @@ export default function EditorDialog(props: EditorDialogProps) { const errorLabel = error || errorMessage; let dialogTitle = title; if (!dialogTitle && item) { - const itemName = item.metadata?.name || t('New Object'); + const itemName = isKubeObjectIsh(item) + ? item.metadata?.name || t('New Object') + : t('New Object'); dialogTitle = isReadOnly() ? t('translation|View: {{ itemName }}', { itemName }) : t('translation|Edit: {{ itemName }}', { itemName });