Skip to content

Commit

Permalink
frontend: Add create resource UI
Browse files Browse the repository at this point in the history
These changes introduce a new UI feature that allows users to create
resources from the associated list view. Clicking the 'Create' button
opens up the EditorDialog used in the generic 'Create / Apply' button,
now accepting generic YAML/JSON text rather than explicitly expecting an
item that looks like a Kubernetes resource. The dialog box also includes
a generic template for each resource.

Fixes: #1820

Signed-off-by: Evangelos Skopelitis <[email protected]>
  • Loading branch information
skoeva committed May 28, 2024
1 parent cbbe5fa commit fe2cec7
Show file tree
Hide file tree
Showing 16 changed files with 256 additions and 44 deletions.
137 changes: 137 additions & 0 deletions frontend/src/components/common/CreateResourceButton.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<React.Fragment>
<ActionButton
color="primary"
description={t('translation|Create {{ resource }}', { resource })}
icon={'mdi:plus-circle'}
onClick={() => {
setOpenDialog(true);
}}
/>

<EditorDialog
item={getDefaultContent()}
open={openDialog}
onClose={() => setOpenDialog(false)}
onSave={handleSave}
saveLabel={t('translation|Apply')}
errorMessage={errorMessage}
onEditorChanged={() => setErrorMessage('')}
title={t('translation|Create {{ resource }}', { resource })}
/>
</React.Fragment>
);
}
1 change: 1 addition & 0 deletions frontend/src/components/common/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const checkExports = [
'Chart',
'ConfirmDialog',
'ConfirmButton',
'CreateResourceButton',
'Dialog',
'EmptyContent',
'ErrorPage',
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
4 changes: 4 additions & 0 deletions frontend/src/components/configmap/List.tsx
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -8,6 +9,9 @@ export default function ConfigMapList() {
return (
<ResourceListView
title={t('Config Maps')}
headerProps={{
titleSideActions: [<CreateResourceButton resource="Config Map" />],
}}
resourceClass={ConfigMap}
columns={[
'name',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,20 @@ exports[`Storyshots ConfigMap/ListView Items 1`] = `
</h1>
<div
class="MuiBox-root css-ldp2l3"
/>
>
<button
aria-label="Create Config Map"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeMedium css-whz9ym-MuiButtonBase-root-MuiIconButton-root"
data-mui-internal-clone-element="true"
tabindex="0"
type="button"
>
<span />
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
</div>
</div>
</div>
<div
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/components/lease/List.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { useTranslation } from 'react-i18next';
import { Lease } from '../../lib/k8s/lease';
import { CreateResourceButton } from '../common/CreateResourceButton';
import ResourceListView from '../common/Resource/ResourceListView';

export function LeaseList() {
const { t } = useTranslation(['glossary', 'translation']);
return (
<ResourceListView
title={t('glossary|Lease')}
headerProps={{
titleSideActions: [<CreateResourceButton resource="Lease" />],
}}
resourceClass={Lease}
columns={[
'name',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,20 @@ exports[`Storyshots Lease/ListView Items 1`] = `
</h1>
<div
class="MuiBox-root css-ldp2l3"
/>
>
<button
aria-label="Create Lease"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeMedium css-whz9ym-MuiButtonBase-root-MuiIconButton-root"
data-mui-internal-clone-element="true"
tabindex="0"
type="button"
>
<span />
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
</div>
</div>
</div>
<div
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/components/runtimeClass/List.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useTranslation } from 'react-i18next';
import { RuntimeClass } from '../../lib/k8s/runtime';
import { CreateResourceButton } from '../common/CreateResourceButton';
import ResourceListView from '../common/Resource/ResourceListView';

export function RuntimeClassList() {
Expand All @@ -8,6 +9,9 @@ export function RuntimeClassList() {
return (
<ResourceListView
title={t('glossary|RuntimeClass')}
headerProps={{
titleSideActions: [<CreateResourceButton resource="RuntimeClass" />],
}}
resourceClass={RuntimeClass}
columns={[
'name',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,20 @@ exports[`Storyshots RuntimeClass/ListView Items 1`] = `
</h1>
<div
class="MuiBox-root css-ldp2l3"
/>
>
<button
aria-label="Create RuntimeClass"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeMedium css-whz9ym-MuiButtonBase-root-MuiIconButton-root"
data-mui-internal-clone-element="true"
tabindex="0"
type="button"
>
<span />
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
</div>
</div>
</div>
</div>
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/components/secret/List.tsx
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -8,6 +9,9 @@ export default function SecretList() {
return (
<ResourceListView
title={t('Secrets')}
headerProps={{
titleSideActions: [<CreateResourceButton resource="Secret" />],
}}
resourceClass={Secret}
columns={[
'name',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,20 @@ exports[`Storyshots Secret/ListView Items 1`] = `
</h1>
<div
class="MuiBox-root css-ldp2l3"
/>
>
<button
aria-label="Create Secret"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeMedium css-whz9ym-MuiButtonBase-root-MuiIconButton-root"
data-mui-internal-clone-element="true"
tabindex="0"
type="button"
>
<span />
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
</div>
</div>
</div>
<div
Expand Down
17 changes: 9 additions & 8 deletions frontend/src/i18n/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,15 @@
"Something went wrong.": "Etwas ist schief gelaufen.",
"No": "Nein",
"Yes": "Ja",
"Failed to create {{ kind }} {{ name }}.": "Erstellung von {{ kind }} fehlgeschlagen {{ name }}.",
"Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.": "Fehler beim Erstellen von {{ kind }} {{ name }} in {{ apiVersion }}.",
"Invalid: One or more of resources doesn't have a name property": "Ungültig: Eine oder mehrere der Ressourcen haben keine Namenseigenschaft",
"Invalid: Please set a kind to the resource": "Ungültig: Bitte geben Sie einen Typ für die Ressource an",
"Applying {{ newItemName }}…": "Anwenden von {{ newItemName }}…",
"Cancelled applying {{ newItemName }}.": "Die Anwendung von {{ newItemName }} wurde abgebrochen.",
"Applied {{ newItemName }}.": "Angewandt {{ newItemName }}.",
"Failed to apply {{ newItemName }}.": "Die Anwendung von {{ newItemName }} ist fehlgeschlagen.",
"Create {{ resource }}": "",
"Toggle fullscreen": "Vollbild ein/aus",
"Close": "Schließen",
"Uh-oh! Something went wrong.": "Oh-oh! Etwas ist schief gelaufen.",
Expand Down Expand Up @@ -154,14 +163,6 @@
"Read more": "Mehr lesen",
"Dismiss": "Schließen",
"Install the metrics-server to get usage data.": "Installieren Sie den Metriken-Server, um Nutzungsdaten zu erhalten.",
"Failed to create {{ kind }} {{ name }}.": "Erstellung von {{ kind }} fehlgeschlagen {{ name }}.",
"Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.": "Fehler beim Erstellen von {{ kind }} {{ name }} in {{ apiVersion }}.",
"Invalid: One or more of resources doesn't have a name property": "Ungültig: Eine oder mehrere der Ressourcen haben keine Namenseigenschaft",
"Invalid: Please set a kind to the resource": "Ungültig: Bitte geben Sie einen Typ für die Ressource an",
"Applying {{ newItemName }}…": "Anwenden von {{ newItemName }}…",
"Cancelled applying {{ newItemName }}.": "Die Anwendung von {{ newItemName }} wurde abgebrochen.",
"Applied {{ newItemName }}.": "Angewandt {{ newItemName }}.",
"Failed to apply {{ newItemName }}.": "Die Anwendung von {{ newItemName }} ist fehlgeschlagen.",
"Create / Apply": "Erstellen / Anwenden",
"Create": "Erstellen",
"Deleting item {{ itemName }}…": "Lösche Element {{ itemName }} …",
Expand Down
17 changes: 9 additions & 8 deletions frontend/src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,15 @@
"Something went wrong.": "Something went wrong.",
"No": "No",
"Yes": "Yes",
"Failed to create {{ kind }} {{ name }}.": "Failed to create {{ kind }} {{ name }}.",
"Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.": "Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.",
"Invalid: One or more of resources doesn't have a name property": "Invalid: One or more of resources doesn't have a name property",
"Invalid: Please set a kind to the resource": "Invalid: Please set a kind to the resource",
"Applying {{ newItemName }}…": "Applying {{ newItemName }}…",
"Cancelled applying {{ newItemName }}.": "Cancelled applying {{ newItemName }}.",
"Applied {{ newItemName }}.": "Applied {{ newItemName }}.",
"Failed to apply {{ newItemName }}.": "Failed to apply {{ newItemName }}.",
"Create {{ resource }}": "Create {{ resource }}",
"Toggle fullscreen": "Toggle fullscreen",
"Close": "Close",
"Uh-oh! Something went wrong.": "Uh-oh! Something went wrong.",
Expand Down Expand Up @@ -154,14 +163,6 @@
"Read more": "Read more",
"Dismiss": "Dismiss",
"Install the metrics-server to get usage data.": "Install the metrics-server to get usage data.",
"Failed to create {{ kind }} {{ name }}.": "Failed to create {{ kind }} {{ name }}.",
"Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.": "Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.",
"Invalid: One or more of resources doesn't have a name property": "Invalid: One or more of resources doesn't have a name property",
"Invalid: Please set a kind to the resource": "Invalid: Please set a kind to the resource",
"Applying {{ newItemName }}…": "Applying {{ newItemName }}…",
"Cancelled applying {{ newItemName }}.": "Cancelled applying {{ newItemName }}.",
"Applied {{ newItemName }}.": "Applied {{ newItemName }}.",
"Failed to apply {{ newItemName }}.": "Failed to apply {{ newItemName }}.",
"Create / Apply": "Create / Apply",
"Create": "Create",
"Deleting item {{ itemName }}…": "Deleting item {{ itemName }}…",
Expand Down
17 changes: 9 additions & 8 deletions frontend/src/i18n/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,15 @@
"Something went wrong.": "Algo ha fallado.",
"No": "No",
"Yes": "",
"Failed to create {{ kind }} {{ name }}.": "Fallo al crear {{ kind }} {{ name }}.",
"Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.": "Fallo al crear {{ kind }} {{ name }} en {{ apiVersion }}.",
"Invalid: One or more of resources doesn't have a name property": "Inválido: Uno o más recursos no tiene la propriedad \"name\"",
"Invalid: Please set a kind to the resource": "Inválido: Por favor asigne el \"kind\" al recurso.",
"Applying {{ newItemName }}…": "Aplicando {{ newItemName }}…",
"Cancelled applying {{ newItemName }}.": "Se ha cancelado la aplicación de {{ newItemName }}.",
"Applied {{ newItemName }}.": "Se ha aplicado {{ newItemName }}.",
"Failed to apply {{ newItemName }}.": "Fallo al aplicar {{ newItemName }}.",
"Create {{ resource }}": "",
"Toggle fullscreen": "Alternar pantalla completa",
"Close": "Cerrar",
"Uh-oh! Something went wrong.": "¡Ups! Algo ha fallado.",
Expand Down Expand Up @@ -154,14 +163,6 @@
"Read more": "Leer más",
"Dismiss": "Descartar",
"Install the metrics-server to get usage data.": "Instale el metrics-server para obtener datos de uso.",
"Failed to create {{ kind }} {{ name }}.": "Fallo al crear {{ kind }} {{ name }}.",
"Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.": "Fallo al crear {{ kind }} {{ name }} en {{ apiVersion }}.",
"Invalid: One or more of resources doesn't have a name property": "Inválido: Uno o más recursos no tiene la propriedad \"name\"",
"Invalid: Please set a kind to the resource": "Inválido: Por favor asigne el \"kind\" al recurso.",
"Applying {{ newItemName }}…": "Aplicando {{ newItemName }}…",
"Cancelled applying {{ newItemName }}.": "Se ha cancelado la aplicación de {{ newItemName }}.",
"Applied {{ newItemName }}.": "Se ha aplicado {{ newItemName }}.",
"Failed to apply {{ newItemName }}.": "Fallo al aplicar {{ newItemName }}.",
"Create / Apply": "Crear / Aplicar",
"Create": "Crear",
"Deleting item {{ itemName }}…": "Eliminando item {{ itemName }}…",
Expand Down
Loading

0 comments on commit fe2cec7

Please sign in to comment.