diff --git a/frontend/src/components/common/CreateResourceButton.tsx b/frontend/src/components/common/CreateResourceButton.tsx new file mode 100644 index 0000000000..b70b990549 --- /dev/null +++ b/frontend/src/components/common/CreateResourceButton.tsx @@ -0,0 +1,137 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDispatch } from 'react-redux'; +import { getCluster } from '../../lib/cluster'; +import { apply } from '../../lib/k8s/apiProxy'; +import { KubeObjectInterface } from '../../lib/k8s/cluster'; +import { clusterAction } from '../../redux/clusterActionSlice'; +import { EventStatus, HeadlampEventType, useEventCallback } from '../../redux/headlampEventSlice'; +import { ActionButton, EditorDialog } from '../common'; +import { BASE_EMPTY_CONFIG_MAP } from '../configmap/storyHelper'; +import { LEASE_DUMMY_DATA } from '../lease/storyHelper'; +import { BASE_RC } from '../runtimeClass/storyHelper'; +import { BASE_EMPTY_SECRET } from '../secret/storyHelper'; + +interface CreateResourceButtonProps { + resource: string; +} + +export function CreateResourceButton(props: CreateResourceButtonProps) { + const { resource } = props; + const { t } = useTranslation(['glossary', 'translation']); + const [openDialog, setOpenDialog] = React.useState(false); + const [errorMessage, setErrorMessage] = React.useState(''); + const dispatchCreateEvent = useEventCallback(HeadlampEventType.CREATE_RESOURCE); + const dispatch = useDispatch(); + + const applyFunc = async (newItems: KubeObjectInterface[], clusterName: string) => { + await Promise.allSettled(newItems.map(newItem => apply(newItem, clusterName))).then( + (values: any) => { + values.forEach((value: any, index: number) => { + if (value.status === 'rejected') { + let msg; + const kind = newItems[index].kind; + const name = newItems[index].metadata.name; + const apiVersion = newItems[index].apiVersion; + if (newItems.length === 1) { + msg = t('translation|Failed to create {{ kind }} {{ name }}.', { kind, name }); + } else { + msg = t('translation|Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.', { + kind, + name, + apiVersion, + }); + } + setErrorMessage(msg); + setOpenDialog(true); + throw msg; + } + }); + } + ); + }; + + function handleSave(newItemDefs: KubeObjectInterface[]) { + let massagedNewItemDefs = newItemDefs; + const cancelUrl = location.pathname; + + // check if all yaml objects are valid + for (let i = 0; i < massagedNewItemDefs.length; i++) { + if (massagedNewItemDefs[i].kind === 'List') { + // flatten this List kind with the items that it has which is a list of valid k8s resources + const deletedItem = massagedNewItemDefs.splice(i, 1); + massagedNewItemDefs = massagedNewItemDefs.concat(deletedItem[0].items); + } + if (!massagedNewItemDefs[i].metadata?.name) { + setErrorMessage( + t(`translation|Invalid: One or more of resources doesn't have a name property`) + ); + return; + } + if (!massagedNewItemDefs[i].kind) { + setErrorMessage(t('translation|Invalid: Please set a kind to the resource')); + return; + } + } + // all resources name + const resourceNames = massagedNewItemDefs.map(newItemDef => newItemDef.metadata.name); + setOpenDialog(false); + + const clusterName = getCluster() || ''; + + dispatch( + clusterAction(() => applyFunc(massagedNewItemDefs, clusterName), { + startMessage: t('translation|Applying {{ newItemName }}…', { + newItemName: resourceNames.join(','), + }), + cancelledMessage: t('translation|Cancelled applying {{ newItemName }}.', { + newItemName: resourceNames.join(','), + }), + successMessage: t('translation|Applied {{ newItemName }}.', { + newItemName: resourceNames.join(','), + }), + errorMessage: t('translation|Failed to apply {{ newItemName }}.', { + newItemName: resourceNames.join(','), + }), + cancelUrl, + }) + ); + + dispatchCreateEvent({ + status: EventStatus.CONFIRMED, + }); + } + + const defaultContentMap: { [key: string]: any } = { + 'Config Map': BASE_EMPTY_CONFIG_MAP, + Secret: BASE_EMPTY_SECRET, + Lease: LEASE_DUMMY_DATA, + RuntimeClass: BASE_RC, + }; + + const getDefaultContent = () => defaultContentMap[resource] || ''; + + return ( + + { + setOpenDialog(true); + }} + /> + + setOpenDialog(false)} + onSave={handleSave} + saveLabel={t('translation|Apply')} + errorMessage={errorMessage} + onEditorChanged={() => setErrorMessage('')} + title={t('translation|Create {{ resource }}', { resource })} + /> + + ); +} diff --git a/frontend/src/components/common/index.test.ts b/frontend/src/components/common/index.test.ts index 60713d3769..e52251eb03 100644 --- a/frontend/src/components/common/index.test.ts +++ b/frontend/src/components/common/index.test.ts @@ -19,6 +19,7 @@ const checkExports = [ 'Chart', 'ConfirmDialog', 'ConfirmButton', + 'CreateResourceButton', 'Dialog', 'EmptyContent', 'ErrorPage', diff --git a/frontend/src/components/common/index.ts b/frontend/src/components/common/index.ts index 4e35bcc8c7..54e664535d 100644 --- a/frontend/src/components/common/index.ts +++ b/frontend/src/components/common/index.ts @@ -50,3 +50,4 @@ export { default as ConfirmButton } from './ConfirmButton'; export * from './NamespacesAutocomplete'; export * from './Table/Table'; export { default as Table } from './Table'; +export * from './CreateResourceButton'; diff --git a/frontend/src/components/configmap/List.tsx b/frontend/src/components/configmap/List.tsx index a29dace767..d14d112ae3 100644 --- a/frontend/src/components/configmap/List.tsx +++ b/frontend/src/components/configmap/List.tsx @@ -1,5 +1,6 @@ import { useTranslation } from 'react-i18next'; import ConfigMap from '../../lib/k8s/configMap'; +import { CreateResourceButton } from '../common/CreateResourceButton'; import ResourceListView from '../common/Resource/ResourceListView'; export default function ConfigMapList() { @@ -8,6 +9,9 @@ export default function ConfigMapList() { return ( ], + }} resourceClass={ConfigMap} columns={[ 'name', diff --git a/frontend/src/components/configmap/__snapshots__/List.stories.storyshot b/frontend/src/components/configmap/__snapshots__/List.stories.storyshot index e35143bb09..6fe58d6308 100644 --- a/frontend/src/components/configmap/__snapshots__/List.stories.storyshot +++ b/frontend/src/components/configmap/__snapshots__/List.stories.storyshot @@ -21,7 +21,20 @@ exports[`Storyshots ConfigMap/ListView Items 1`] = `
+ > + +
], + }} resourceClass={Lease} columns={[ 'name', diff --git a/frontend/src/components/lease/__snapshots__/List.stories.storyshot b/frontend/src/components/lease/__snapshots__/List.stories.storyshot index 0bb9f4b97f..ea578197dd 100644 --- a/frontend/src/components/lease/__snapshots__/List.stories.storyshot +++ b/frontend/src/components/lease/__snapshots__/List.stories.storyshot @@ -21,7 +21,20 @@ exports[`Storyshots Lease/ListView Items 1`] = `
+ > + +
], + }} resourceClass={RuntimeClass} columns={[ 'name', diff --git a/frontend/src/components/runtimeClass/__snapshots__/List.stories.storyshot b/frontend/src/components/runtimeClass/__snapshots__/List.stories.storyshot index eeeb7cef60..277218b0fa 100644 --- a/frontend/src/components/runtimeClass/__snapshots__/List.stories.storyshot +++ b/frontend/src/components/runtimeClass/__snapshots__/List.stories.storyshot @@ -21,7 +21,20 @@ exports[`Storyshots RuntimeClass/ListView Items 1`] = `
+ > + +
diff --git a/frontend/src/components/secret/List.tsx b/frontend/src/components/secret/List.tsx index e4729c372a..2faed67891 100644 --- a/frontend/src/components/secret/List.tsx +++ b/frontend/src/components/secret/List.tsx @@ -1,5 +1,6 @@ import { useTranslation } from 'react-i18next'; import Secret from '../../lib/k8s/secret'; +import { CreateResourceButton } from '../common/CreateResourceButton'; import ResourceListView from '../common/Resource/ResourceListView'; export default function SecretList() { @@ -8,6 +9,9 @@ export default function SecretList() { return ( ], + }} resourceClass={Secret} columns={[ 'name', diff --git a/frontend/src/components/secret/__snapshots__/List.stories.storyshot b/frontend/src/components/secret/__snapshots__/List.stories.storyshot index 9e5666c130..8ce3de4c76 100644 --- a/frontend/src/components/secret/__snapshots__/List.stories.storyshot +++ b/frontend/src/components/secret/__snapshots__/List.stories.storyshot @@ -21,7 +21,20 @@ exports[`Storyshots Secret/ListView Items 1`] = `
+ > + +