diff --git a/frontend/src/components/common/CreateResourceButton.stories.tsx b/frontend/src/components/common/CreateResourceButton.stories.tsx
new file mode 100644
index 0000000000..77f3e5b808
--- /dev/null
+++ b/frontend/src/components/common/CreateResourceButton.stories.tsx
@@ -0,0 +1,98 @@
+import { Meta, StoryObj } from '@storybook/react';
+import { expect, userEvent, waitFor } from '@storybook/test';
+import { screen } from '@testing-library/react';
+import React from 'react';
+import { Provider } from 'react-redux';
+import { KubeObjectClass } from '../../lib/k8s/cluster';
+import ConfigMap from '../../lib/k8s/configMap';
+import store from '../../redux/stores/store';
+import { TestContext } from '../../test';
+import { CreateResourceButton, CreateResourceButtonProps } from './CreateResourceButton';
+
+export default {
+ title: 'CreateResourceButton',
+ component: CreateResourceButton,
+ parameters: {
+ storyshots: {
+ disable: true,
+ },
+ },
+ decorators: [
+ Story => {
+ return (
+
+
+
+
+
+ );
+ },
+ ],
+} as Meta;
+
+type Story = StoryObj;
+
+export const ValidResource: Story = {
+ args: { resourceClass: ConfigMap as unknown as KubeObjectClass },
+
+ play: async ({ args }) => {
+ await userEvent.click(
+ screen.getByRole('button', {
+ name: `Create ${args.resourceClass.getBaseObject().kind}`,
+ })
+ );
+
+ await waitFor(() => expect(screen.getByRole('textbox')).toBeVisible());
+
+ await userEvent.click(screen.getByRole('textbox'));
+
+ await userEvent.keyboard('{Control>}a{/Control} {Backspace}');
+ await userEvent.keyboard(`apiVersion: v1{Enter}`);
+ await userEvent.keyboard(`kind: ConfigMap{Enter}`);
+ await userEvent.keyboard(`metadata:{Enter}`);
+ await userEvent.keyboard(` name: base-configmap`);
+
+ const button = await screen.findByRole('button', { name: 'Apply' });
+ expect(button).toBeVisible();
+ },
+};
+
+export const InvalidResource: Story = {
+ args: { resourceClass: ConfigMap as unknown as KubeObjectClass },
+
+ play: async ({ args }) => {
+ await userEvent.click(
+ screen.getByRole('button', {
+ name: `Create ${args.resourceClass.getBaseObject().kind}`,
+ })
+ );
+
+ await waitFor(() => expect(screen.getByRole('textbox')).toBeVisible());
+
+ await userEvent.click(screen.getByRole('textbox'));
+
+ await userEvent.keyboard('{Control>}a{/Control}');
+ await userEvent.keyboard(`apiVersion: v1{Enter}`);
+ await userEvent.keyboard(`kind: ConfigMap{Enter}`);
+ await userEvent.keyboard(`metadata:{Enter}`);
+ await userEvent.keyboard(` name: base-configmap{Enter}`);
+ await userEvent.keyboard(`creationTimestamp: ''`);
+
+ const button = await screen.findByRole('button', { name: 'Apply' });
+ expect(button).toBeVisible();
+
+ await userEvent.click(button);
+
+ await waitFor(() =>
+ userEvent.click(
+ screen.getByRole('button', {
+ name: `Create ${args.resourceClass.getBaseObject().kind}`,
+ })
+ )
+ );
+
+ await waitFor(() => expect(screen.getByText(/Failed/)).toBeVisible(), {
+ timeout: 15000,
+ });
+ },
+};
diff --git a/frontend/src/components/common/CreateResourceButton.tsx b/frontend/src/components/common/CreateResourceButton.tsx
new file mode 100644
index 0000000000..ad0fc1effb
--- /dev/null
+++ b/frontend/src/components/common/CreateResourceButton.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { KubeObjectClass } from '../../lib/k8s/cluster';
+import { ActionButton, AuthVisible, EditorDialog } from '../common';
+
+export interface CreateResourceButtonProps {
+ resourceClass: KubeObjectClass;
+ resourceName?: string;
+}
+
+export function CreateResourceButton(props: CreateResourceButtonProps) {
+ const { resourceClass, resourceName } = props;
+ const { t } = useTranslation(['glossary', 'translation']);
+ const [openDialog, setOpenDialog] = React.useState(false);
+ const [errorMessage, setErrorMessage] = React.useState('');
+
+ const baseObject = resourceClass.getBaseObject();
+ const name = resourceName ?? baseObject.kind;
+
+ return (
+
+ {
+ setOpenDialog(true);
+ }}
+ />
+ setOpenDialog(false)}
+ saveLabel={t('translation|Apply')}
+ errorMessage={errorMessage}
+ onEditorChanged={() => setErrorMessage('')}
+ title={t('translation|Create {{ name }}', { name })}
+ />
+
+ );
+}
diff --git a/frontend/src/components/common/Resource/CreateButton.tsx b/frontend/src/components/common/Resource/CreateButton.tsx
index bd7c466bdb..f97fc81d62 100644
--- a/frontend/src/components/common/Resource/CreateButton.tsx
+++ b/frontend/src/components/common/Resource/CreateButton.tsx
@@ -6,18 +6,7 @@ import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select';
import React from 'react';
import { useTranslation } from 'react-i18next';
-import { useDispatch } from 'react-redux';
-import { useLocation } from 'react-router-dom';
import { useClusterGroup } from '../../../lib/k8s';
-import { apply } from '../../../lib/k8s/apiProxy';
-import { KubeObjectInterface } from '../../../lib/k8s/KubeObject';
-import { clusterAction } from '../../../redux/clusterActionSlice';
-import {
- EventStatus,
- HeadlampEventType,
- useEventCallback,
-} from '../../../redux/headlampEventSlice';
-import { AppDispatch } from '../../../redux/stores/store';
import ActionButton from '../ActionButton';
import EditorDialog from './EditorDialog';
@@ -27,15 +16,14 @@ interface CreateButtonProps {
export default function CreateButton(props: CreateButtonProps) {
const { isNarrow } = props;
- const dispatch: AppDispatch = useDispatch();
const [openDialog, setOpenDialog] = React.useState(false);
const [errorMessage, setErrorMessage] = React.useState('');
- const location = useLocation();
const { t } = useTranslation(['translation']);
- const dispatchCreateEvent = useEventCallback(HeadlampEventType.CREATE_RESOURCE);
const clusters = useClusterGroup();
const [targetCluster, setTargetCluster] = React.useState(clusters[0] || '');
+
+ // We want to avoid resetting the dialog state on close.
const itemRef = React.useRef({});
// When the clusters in the group change, we want to reset the target cluster
@@ -48,82 +36,6 @@ export default function CreateButton(props: CreateButtonProps) {
}
}, [clusters]);
- 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);
-
- dispatch(
- clusterAction(() => applyFunc(massagedNewItemDefs, targetCluster), {
- 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,
- });
- }
-
return (
{isNarrow ? (
@@ -152,7 +64,7 @@ export default function CreateButton(props: CreateButtonProps) {
item={itemRef.current}
open={openDialog}
onClose={() => setOpenDialog(false)}
- onSave={handleSave}
+ setOpen={setOpenDialog}
saveLabel={t('translation|Apply')}
errorMessage={errorMessage}
onEditorChanged={() => setErrorMessage('')}
diff --git a/frontend/src/components/common/Resource/EditorDialog.stories.tsx b/frontend/src/components/common/Resource/EditorDialog.stories.tsx
index 0e974b6481..3ad4b5c66c 100644
--- a/frontend/src/components/common/Resource/EditorDialog.stories.tsx
+++ b/frontend/src/components/common/Resource/EditorDialog.stories.tsx
@@ -2,12 +2,23 @@ import FormControlLabel from '@mui/material/FormControlLabel';
import FormGroup from '@mui/material/FormGroup';
import Switch from '@mui/material/Switch';
import { Meta, StoryFn } from '@storybook/react';
+import { Provider } from 'react-redux';
+import store from '../../../redux/stores/store';
import { EditorDialog, EditorDialogProps } from '..';
export default {
title: 'Resource/EditorDialog',
component: EditorDialog,
argTypes: {},
+ decorators: [
+ Story => {
+ return (
+
+
+
+ );
+ },
+ ],
} as Meta;
const Template: StoryFn = args => {
diff --git a/frontend/src/components/common/Resource/EditorDialog.tsx b/frontend/src/components/common/Resource/EditorDialog.tsx
index 983c30cd50..2c182d6070 100644
--- a/frontend/src/components/common/Resource/EditorDialog.tsx
+++ b/frontend/src/components/common/Resource/EditorDialog.tsx
@@ -18,9 +18,19 @@ import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
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/KubeObject';
import { getThemeName } from '../../../lib/themes';
import { useId } from '../../../lib/util';
+import { clusterAction } from '../../../redux/clusterActionSlice';
+import {
+ EventStatus,
+ HeadlampEventType,
+ useEventCallback,
+} from '../../../redux/headlampEventSlice';
+import { AppDispatch } from '../../../redux/stores/store';
import ConfirmButton from '../ConfirmButton';
import { Dialog, DialogProps } from '../Dialog';
import Loader from '../Loader';
@@ -53,10 +63,12 @@ export interface EditorDialogProps extends DialogProps {
item: KubeObjectIsh | object | object[] | string | null;
/** Called when the dialog is closed. */
onClose: () => void;
- /** Called when the user clicks the save button. */
- onSave: ((...args: any[]) => void) | null;
+ /** Called by a component for when the user clicks the save button. When set to "default", internal save logic is applied. */
+ onSave?: ((...args: any[]) => void) | 'default' | null;
/** Called when the editor's contents change. */
onEditorChanged?: ((newValue: string) => void) | null;
+ /** The function to open the dialog. */
+ setOpen?: (open: boolean) => void;
/** The label to use for the save button. */
saveLabel?: string;
/** The error message to display. */
@@ -71,8 +83,9 @@ export default function EditorDialog(props: EditorDialogProps) {
const {
item,
onClose,
- onSave,
+ onSave = 'default',
onEditorChanged,
+ setOpen,
saveLabel,
errorMessage,
title,
@@ -106,6 +119,8 @@ export default function EditorDialog(props: EditorDialogProps) {
const localData = localStorage.getItem('useSimpleEditor');
return localData ? JSON.parse(localData) : false;
});
+ const dispatchCreateEvent = useEventCallback(HeadlampEventType.CREATE_RESOURCE);
+ const dispatch: AppDispatch = useDispatch();
function setUseSimpleEditor(data: boolean) {
localStorage.setItem('useSimpleEditor', JSON.stringify(data));
@@ -269,6 +284,34 @@ export default function EditorDialog(props: EditorDialogProps) {
setCode(originalCodeRef.current);
}
+ 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,
+ });
+ }
+ setError(msg);
+ setOpen?.(true);
+ // throw msg;
+ throw new Error(msg);
+ }
+ });
+ }
+ );
+ };
+
function handleSave() {
// Verify the YAML even means anything before trying to use it.
const { obj, format, error } = getObjectsFromCode(code);
@@ -285,7 +328,39 @@ export default function EditorDialog(props: EditorDialogProps) {
setError(t("Error parsing the code. Please verify it's valid YAML or JSON!"));
return;
}
- onSave!(obj);
+
+ const newItemDefs = obj!;
+
+ if (typeof onSave === 'string' && onSave === 'default') {
+ const resourceNames = newItemDefs.map(newItemDef => newItemDef.metadata.name);
+ const clusterName = getCluster() || '';
+
+ dispatch(
+ clusterAction(() => applyFunc(newItemDefs, 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: location.pathname,
+ })
+ );
+
+ dispatchCreateEvent({
+ status: EventStatus.CONFIRMED,
+ });
+
+ onClose();
+ } else if (typeof onSave === 'function') {
+ onSave!(obj);
+ }
}
function makeEditor() {
@@ -321,9 +396,7 @@ export default function EditorDialog(props: EditorDialogProps) {
const errorLabel = error || errorMessage;
let dialogTitle = title;
if (!dialogTitle && item) {
- const itemName = isKubeObjectIsh(item)
- ? item.metadata?.name || t('New Object')
- : t('New Object');
+ const itemName = (isKubeObjectIsh(item) && item.metadata?.name) || t('New Object');
dialogTitle = isReadOnly()
? t('translation|View: {{ itemName }}', { itemName })
: t('translation|Edit: {{ itemName }}', { itemName });
diff --git a/frontend/src/components/common/Resource/ResourceListView.tsx b/frontend/src/components/common/Resource/ResourceListView.tsx
index 19e6334b23..1157e0c674 100644
--- a/frontend/src/components/common/Resource/ResourceListView.tsx
+++ b/frontend/src/components/common/Resource/ResourceListView.tsx
@@ -1,6 +1,6 @@
import React, { PropsWithChildren, ReactElement, ReactNode } from 'react';
-import { KubeObject } from '../../../lib/k8s/KubeObject';
-import { KubeObjectClass } from '../../../lib/k8s/KubeObject';
+import { KubeObject, KubeObjectClass } from '../../../lib/k8s/KubeObject';
+import { CreateResourceButton } from '../CreateResourceButton';
import SectionBox from '../SectionBox';
import SectionFilterHeader, { SectionFilterHeaderProps } from '../SectionFilterHeader';
import ResourceTable, { ResourceTableProps } from './ResourceTable';
@@ -30,6 +30,8 @@ export default function ResourceListView(
) {
const { title, children, headerProps, ...tableProps } = props;
const withNamespaceFilter = 'resourceClass' in props && props.resourceClass?.isNamespaced;
+ const resourceClass = (props as ResourceListViewWithResourceClassProps)
+ .resourceClass as KubeObjectClass;
return (
] : undefined)
+ }
{...headerProps}
/>
) : (
diff --git a/frontend/src/components/common/Resource/ViewButton.stories.tsx b/frontend/src/components/common/Resource/ViewButton.stories.tsx
index 9f4867efcc..b61ab181be 100644
--- a/frontend/src/components/common/Resource/ViewButton.stories.tsx
+++ b/frontend/src/components/common/Resource/ViewButton.stories.tsx
@@ -1,7 +1,9 @@
import '../../../i18n/config';
import { Meta, StoryFn } from '@storybook/react';
import React from 'react';
+import { Provider } from 'react-redux';
import { KubeObject } from '../../../lib/k8s/KubeObject';
+import store from '../../../redux/stores/store';
import ViewButton from './ViewButton';
import { ViewButtonProps } from './ViewButton';
@@ -9,6 +11,15 @@ export default {
title: 'Resource/ViewButton',
component: ViewButton,
argTypes: {},
+ decorators: [
+ Story => {
+ return (
+
+
+
+ );
+ },
+ ],
} as Meta;
const Template: StoryFn = args => ;
diff --git a/frontend/src/components/common/__snapshots__/CreateResourceButton.ConfigMapStory.stories.storyshot b/frontend/src/components/common/__snapshots__/CreateResourceButton.ConfigMapStory.stories.storyshot
new file mode 100644
index 0000000000..df46f87231
--- /dev/null
+++ b/frontend/src/components/common/__snapshots__/CreateResourceButton.ConfigMapStory.stories.storyshot
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/components/common/__snapshots__/CreateResourceButton.InvalidResource.stories.storyshot b/frontend/src/components/common/__snapshots__/CreateResourceButton.InvalidResource.stories.storyshot
new file mode 100644
index 0000000000..895858ca2d
--- /dev/null
+++ b/frontend/src/components/common/__snapshots__/CreateResourceButton.InvalidResource.stories.storyshot
@@ -0,0 +1,13 @@
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/common/__snapshots__/CreateResourceButton.ValidResource.stories.storyshot b/frontend/src/components/common/__snapshots__/CreateResourceButton.ValidResource.stories.storyshot
new file mode 100644
index 0000000000..895858ca2d
--- /dev/null
+++ b/frontend/src/components/common/__snapshots__/CreateResourceButton.ValidResource.stories.storyshot
@@ -0,0 +1,13 @@
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/common/index.test.ts b/frontend/src/components/common/index.test.ts
index 0af5c688a8..a1d343491c 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/__snapshots__/List.Items.stories.storyshot b/frontend/src/components/configmap/__snapshots__/List.Items.stories.storyshot
index ebe816364f..9c475fc2f0 100644
--- a/frontend/src/components/configmap/__snapshots__/List.Items.stories.storyshot
+++ b/frontend/src/components/configmap/__snapshots__/List.Items.stories.storyshot
@@ -19,7 +19,19 @@
+ >
+
+
,
+ ]}
actions={[
@@ -176,6 +181,7 @@ export function CustomResourceListTable(props: CustomResourceTableProps) {
title={title}
headerProps={{
noNamespaceFilter: !crd.isNamespaced,
+ titleSideActions: [],
}}
resourceClass={CRClass}
columns={cols}
diff --git a/frontend/src/components/crd/__snapshots__/CustomResourceList.List.stories.storyshot b/frontend/src/components/crd/__snapshots__/CustomResourceList.List.stories.storyshot
index 77ffd55c80..e46f8c72eb 100644
--- a/frontend/src/components/crd/__snapshots__/CustomResourceList.List.stories.storyshot
+++ b/frontend/src/components/crd/__snapshots__/CustomResourceList.List.stories.storyshot
@@ -51,7 +51,19 @@
+ >
+
+
diff --git a/frontend/src/components/ingress/__snapshots__/List.Items.stories.storyshot b/frontend/src/components/ingress/__snapshots__/List.Items.stories.storyshot
index b92471f195..7854e29d42 100644
--- a/frontend/src/components/ingress/__snapshots__/List.Items.stories.storyshot
+++ b/frontend/src/components/ingress/__snapshots__/List.Items.stories.storyshot
@@ -19,7 +19,19 @@
+ >
+
+
diff --git a/frontend/src/components/podDisruptionBudget/__snapshots__/pdbList.Items.stories.storyshot b/frontend/src/components/podDisruptionBudget/__snapshots__/pdbList.Items.stories.storyshot
index 96a669fcf3..c8e50b1e4e 100644
--- a/frontend/src/components/podDisruptionBudget/__snapshots__/pdbList.Items.stories.storyshot
+++ b/frontend/src/components/podDisruptionBudget/__snapshots__/pdbList.Items.stories.storyshot
@@ -19,7 +19,19 @@
+ >
+
+
diff --git a/frontend/src/components/replicaset/__snapshots__/List.ReplicaSets.stories.storyshot b/frontend/src/components/replicaset/__snapshots__/List.ReplicaSets.stories.storyshot
index 1e7638b3ad..3da09c1b16 100644
--- a/frontend/src/components/replicaset/__snapshots__/List.ReplicaSets.stories.storyshot
+++ b/frontend/src/components/replicaset/__snapshots__/List.ReplicaSets.stories.storyshot
@@ -22,7 +22,19 @@
+ >
+
+
diff --git a/frontend/src/components/secret/__snapshots__/List.Items.stories.storyshot b/frontend/src/components/secret/__snapshots__/List.Items.stories.storyshot
index a321d36c1b..7f298e2da5 100644
--- a/frontend/src/components/secret/__snapshots__/List.Items.stories.storyshot
+++ b/frontend/src/components/secret/__snapshots__/List.Items.stories.storyshot
@@ -19,7 +19,19 @@
+ >
+
+
diff --git a/frontend/src/components/storage/__snapshots__/VolumeList.Items.stories.storyshot b/frontend/src/components/storage/__snapshots__/VolumeList.Items.stories.storyshot
index 08c33911e3..521587f3ff 100644
--- a/frontend/src/components/storage/__snapshots__/VolumeList.Items.stories.storyshot
+++ b/frontend/src/components/storage/__snapshots__/VolumeList.Items.stories.storyshot
@@ -19,7 +19,19 @@
+ >
+
+
diff --git a/frontend/src/components/verticalPodAutoscaler/__snapshots__/VPAList.List.stories.storyshot b/frontend/src/components/verticalPodAutoscaler/__snapshots__/VPAList.List.stories.storyshot
index 162a07e109..23d009bf5f 100644
--- a/frontend/src/components/verticalPodAutoscaler/__snapshots__/VPAList.List.stories.storyshot
+++ b/frontend/src/components/verticalPodAutoscaler/__snapshots__/VPAList.List.stories.storyshot
@@ -19,7 +19,19 @@
+ >
+
+
diff --git a/frontend/src/components/webhookconfiguration/__snapshots__/ValidatingWebhookConfigList.Items.stories.storyshot b/frontend/src/components/webhookconfiguration/__snapshots__/ValidatingWebhookConfigList.Items.stories.storyshot
index 05a6c863b3..9783b2ba34 100644
--- a/frontend/src/components/webhookconfiguration/__snapshots__/ValidatingWebhookConfigList.Items.stories.storyshot
+++ b/frontend/src/components/webhookconfiguration/__snapshots__/ValidatingWebhookConfigList.Items.stories.storyshot
@@ -22,7 +22,19 @@
+ >
+
+
diff --git a/frontend/src/i18n/locales/de/translation.json b/frontend/src/i18n/locales/de/translation.json
index 3705543014..0fbf20a578 100644
--- a/frontend/src/i18n/locales/de/translation.json
+++ b/frontend/src/i18n/locales/de/translation.json
@@ -146,6 +146,7 @@
"Lost connection to the cluster.": "",
"No": "Nein",
"Yes": "Ja",
+ "Create {{ name }}": "",
"Toggle fullscreen": "Vollbild ein/aus",
"Close": "Schließen",
"Head back <1>home1>.": "Head back <1>home1>.",
@@ -174,14 +175,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 }} …",
@@ -200,8 +193,14 @@
"Edit": "Bearbeiten",
"Invalid JSON": "Ungültiges JSON",
"Invalid YAML": "Ungültige YAML",
+ "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 }}.",
"Error parsing the code: {{error}}": "Fehler beim Parsen des Codes: {{error}}",
"Error parsing the code. Please verify it's valid YAML or JSON!": "Fehler beim Parsen des Codes. Bitte überprüfen Sie, ob es sich um gültiges YAML oder JSON handelt!",
+ "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.",
"New Object": "Neues Objekt",
"View: {{ itemName }}": "Ansicht: {{ itemName }}",
"Edit: {{ itemName }}": "Bearbeiten: {{ itemName }}",
diff --git a/frontend/src/i18n/locales/en/translation.json b/frontend/src/i18n/locales/en/translation.json
index 6b60b149ef..8972bb0a69 100644
--- a/frontend/src/i18n/locales/en/translation.json
+++ b/frontend/src/i18n/locales/en/translation.json
@@ -146,6 +146,7 @@
"Lost connection to the cluster.": "Lost connection to the cluster.",
"No": "No",
"Yes": "Yes",
+ "Create {{ name }}": "Create {{ name }}",
"Toggle fullscreen": "Toggle fullscreen",
"Close": "Close",
"Head back <1>home1>.": "Head back <1>home1>.",
@@ -174,14 +175,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 }}…",
@@ -200,8 +193,14 @@
"Edit": "Edit",
"Invalid JSON": "Invalid JSON",
"Invalid YAML": "Invalid YAML",
+ "Failed to create {{ kind }} {{ name }}.": "Failed to create {{ kind }} {{ name }}.",
+ "Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.": "Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.",
"Error parsing the code: {{error}}": "Error parsing the code: {{error}}",
"Error parsing the code. Please verify it's valid YAML or JSON!": "Error parsing the code. Please verify it's valid YAML or JSON!",
+ "Applying {{ newItemName }}…": "Applying {{ newItemName }}…",
+ "Cancelled applying {{ newItemName }}.": "Cancelled applying {{ newItemName }}.",
+ "Applied {{ newItemName }}.": "Applied {{ newItemName }}.",
+ "Failed to apply {{ newItemName }}.": "Failed to apply {{ newItemName }}.",
"New Object": "New Object",
"View: {{ itemName }}": "View: {{ itemName }}",
"Edit: {{ itemName }}": "Edit: {{ itemName }}",
diff --git a/frontend/src/i18n/locales/es/translation.json b/frontend/src/i18n/locales/es/translation.json
index 97e95dcc65..25a40ca6a8 100644
--- a/frontend/src/i18n/locales/es/translation.json
+++ b/frontend/src/i18n/locales/es/translation.json
@@ -146,6 +146,7 @@
"Lost connection to the cluster.": "",
"No": "No",
"Yes": "Sí",
+ "Create {{ name }}": "",
"Toggle fullscreen": "Alternar pantalla completa",
"Close": "Cerrar",
"Head back <1>home1>.": "Head back <1>home1>.",
@@ -175,14 +176,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 }}…",
@@ -201,8 +194,14 @@
"Edit": "Editar",
"Invalid JSON": "JSON Inválido",
"Invalid YAML": "YAML Inválido",
+ "Failed to create {{ kind }} {{ name }}.": "Fallo al crear {{ kind }} {{ name }}.",
+ "Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.": "Fallo al crear {{ kind }} {{ name }} en {{ apiVersion }}.",
"Error parsing the code: {{error}}": "Error al analizar el código: {{error}}",
"Error parsing the code. Please verify it's valid YAML or JSON!": "Error al analizar el código: {{error}}. ¡Por favor verifique que es YAML o JSON válidos!",
+ "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 }}.",
"New Object": "Nuevo Objeto",
"View: {{ itemName }}": "Ver: {{ itemName }}",
"Edit: {{ itemName }}": "Editar: {{ itemName }}",
diff --git a/frontend/src/i18n/locales/fr/translation.json b/frontend/src/i18n/locales/fr/translation.json
index 45bb73792b..4184de2367 100644
--- a/frontend/src/i18n/locales/fr/translation.json
+++ b/frontend/src/i18n/locales/fr/translation.json
@@ -146,6 +146,7 @@
"Lost connection to the cluster.": "",
"No": "Non",
"Yes": "Oui",
+ "Create {{ name }}": "",
"Toggle fullscreen": "Basculer en mode plein écran",
"Close": "Fermer",
"Head back <1>home1>.": "Head back <1>home1>.",
@@ -175,14 +176,6 @@
"Read more": "Lire la suite",
"Dismiss": "Rejeter",
"Install the metrics-server to get usage data.": "Installez le serveur de métriques pour obtenir des données d'utilisation.",
- "Failed to create {{ kind }} {{ name }}.": "Échec de la création de {{ kind }} {{ name }}.",
- "Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.": "Échec de la création de {{ kind }} {{ name }} dans {{ apiVersion }}.",
- "Invalid: One or more of resources doesn't have a name property": "Non valide : Une ou plusieurs ressources n'ont pas de propriété nom",
- "Invalid: Please set a kind to the resource": "Non valide : Veuillez définir un type pour la ressource",
- "Applying {{ newItemName }}…": "Application {{ newItemName }}…",
- "Cancelled applying {{ newItemName }}.": "Annulation de l'application {{ newItemName }}.",
- "Applied {{ newItemName }}.": "Appliqué {{ newItemName }}.",
- "Failed to apply {{ newItemName }}.": "Échec de l'application de {{ newItemName }}.",
"Create / Apply": "Créer / Appliquer",
"Create": "Créer",
"Deleting item {{ itemName }}…": "Suppression de l'élément {{ itemName }}…",
@@ -201,8 +194,14 @@
"Edit": "Éditer",
"Invalid JSON": "JSON non valide",
"Invalid YAML": "YAML non valide",
+ "Failed to create {{ kind }} {{ name }}.": "Échec de la création de {{ kind }} {{ name }}.",
+ "Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.": "Échec de la création de {{ kind }} {{ name }} dans {{ apiVersion }}.",
"Error parsing the code: {{error}}": "Erreur lors de l'analyse du code : {{error}}",
"Error parsing the code. Please verify it's valid YAML or JSON!": "Erreur lors de l'analyse du code. Veuillez vérifier qu'il s'agit d'un YAML ou d'un JSON valide !",
+ "Applying {{ newItemName }}…": "Application {{ newItemName }}…",
+ "Cancelled applying {{ newItemName }}.": "Annulation de l'application {{ newItemName }}.",
+ "Applied {{ newItemName }}.": "Appliqué {{ newItemName }}.",
+ "Failed to apply {{ newItemName }}.": "Échec de l'application de {{ newItemName }}.",
"New Object": "Nouvel objet",
"View: {{ itemName }}": "Regarder : {{ itemName }}",
"Edit: {{ itemName }}": "Modifier : {{ itemName }}",
diff --git a/frontend/src/i18n/locales/pt/translation.json b/frontend/src/i18n/locales/pt/translation.json
index 3f2a88813f..879cb1d98e 100644
--- a/frontend/src/i18n/locales/pt/translation.json
+++ b/frontend/src/i18n/locales/pt/translation.json
@@ -146,6 +146,7 @@
"Lost connection to the cluster.": "",
"No": "Não",
"Yes": "Sim",
+ "Create {{ name }}": "",
"Toggle fullscreen": "Alternar ecrã inteiro",
"Close": "Fechar",
"Head back <1>home1>.": "Voltar ao <1>início1>.",
@@ -175,14 +176,6 @@
"Read more": "Ler mais",
"Dismiss": "Dispensar",
"Install the metrics-server to get usage data.": "Instale o metrics-server para obter dados sobre o uso.",
- "Failed to create {{ kind }} {{ name }}.": "Falha ao criar {{ kind }} {{ name }}.",
- "Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.": "Falha ao criar {{ kind }} {{ name }} em {{ apiVersion }}.",
- "Invalid: One or more of resources doesn't have a name property": "Inválido: Um ou mais recursos não tem a propriedade \"name\"",
- "Invalid: Please set a kind to the resource": "Inválido: Por favor introduza o \"kind\" para este recurso",
- "Applying {{ newItemName }}…": "A aplicar {{ newItemName }}…",
- "Cancelled applying {{ newItemName }}.": "Cancelou-se a aplicação de {{ newItemNam }}.",
- "Applied {{ newItemName }}.": "Aplicou-se {{ newItemName }}",
- "Failed to apply {{ newItemName }}.": "Falha ao aplicar {{ newItemName }}.",
"Create / Apply": "Criar / Aplicar",
"Create": "Criar",
"Deleting item {{ itemName }}…": "A eliminar o item {{ itemName }}…",
@@ -201,8 +194,14 @@
"Edit": "Editar",
"Invalid JSON": "JSON Inválido",
"Invalid YAML": "YAML Inválido",
+ "Failed to create {{ kind }} {{ name }}.": "Falha ao criar {{ kind }} {{ name }}.",
+ "Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.": "Falha ao criar {{ kind }} {{ name }} em {{ apiVersion }}.",
"Error parsing the code: {{error}}": "Erro ao analisar o código: {{error}}",
"Error parsing the code. Please verify it's valid YAML or JSON!": "Erro ao analisar o código. Por favor verifique que o YAML ou JSON são válidos!",
+ "Applying {{ newItemName }}…": "A aplicar {{ newItemName }}…",
+ "Cancelled applying {{ newItemName }}.": "Cancelou-se a aplicação de {{ newItemNam }}.",
+ "Applied {{ newItemName }}.": "Aplicou-se {{ newItemName }}",
+ "Failed to apply {{ newItemName }}.": "Falha ao aplicar {{ newItemName }}.",
"New Object": "Novo Objecto",
"View: {{ itemName }}": "Ver: {{ itemName }}",
"Edit: {{ itemName }}": "Editar: {{ itemName }}",
diff --git a/frontend/src/i18n/locales/zh-tw/translation.json b/frontend/src/i18n/locales/zh-tw/translation.json
index d3f6241f5c..d071e986e6 100644
--- a/frontend/src/i18n/locales/zh-tw/translation.json
+++ b/frontend/src/i18n/locales/zh-tw/translation.json
@@ -146,6 +146,7 @@
"Lost connection to the cluster.": "與叢集的連接丟失。",
"No": "否",
"Yes": "是",
+ "Create {{ name }}": "",
"Toggle fullscreen": "切換全屏",
"Close": "關閉",
"Head back <1>home1>.": "返回<1>首頁1>。",
@@ -173,14 +174,6 @@
"Read more": "閱讀更多",
"Dismiss": "忽略",
"Install the metrics-server to get usage data.": "安裝 metrics-server 以獲取使用數據。",
- "Failed to create {{ kind }} {{ name }}.": "新增 {{ kind }} {{ name }} 失敗。",
- "Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.": "在 {{ apiVersion }} 中新增 {{ kind }} {{ name }} 失敗。",
- "Invalid: One or more of resources doesn't have a name property": "無效:一個或多個資源沒有名稱屬性",
- "Invalid: Please set a kind to the resource": "無效:請為資源設置一種類型",
- "Applying {{ newItemName }}…": "正在應用 {{ newItemName }}…",
- "Cancelled applying {{ newItemName }}.": "取消應用 {{ newItemName }}。",
- "Applied {{ newItemName }}.": "已應用 {{ newItemName }}。",
- "Failed to apply {{ newItemName }}.": "應用 {{ newItemName }} 失敗。",
"Create / Apply": "新增 / 應用",
"Create": "新增",
"Deleting item {{ itemName }}…": "正在刪除項目 {{ itemName }}…",
@@ -199,8 +192,14 @@
"Edit": "編輯",
"Invalid JSON": "無效的 JSON",
"Invalid YAML": "無效的 YAML",
+ "Failed to create {{ kind }} {{ name }}.": "新增 {{ kind }} {{ name }} 失敗。",
+ "Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.": "在 {{ apiVersion }} 中新增 {{ kind }} {{ name }} 失敗。",
"Error parsing the code: {{error}}": "解析代碼時出錯:{{error}}",
"Error parsing the code. Please verify it's valid YAML or JSON!": "解析代碼時出錯。請確認它是有效的 YAML 或 JSON!",
+ "Applying {{ newItemName }}…": "正在應用 {{ newItemName }}…",
+ "Cancelled applying {{ newItemName }}.": "取消應用 {{ newItemName }}。",
+ "Applied {{ newItemName }}.": "已應用 {{ newItemName }}。",
+ "Failed to apply {{ newItemName }}.": "應用 {{ newItemName }} 失敗。",
"New Object": "新對象",
"View: {{ itemName }}": "查看:{{ itemName }}",
"Edit: {{ itemName }}": "編輯:{{ itemName }}",
diff --git a/frontend/src/lib/k8s/KubeObject.ts b/frontend/src/lib/k8s/KubeObject.ts
index 4d457e2e7d..383870b6cb 100644
--- a/frontend/src/lib/k8s/KubeObject.ts
+++ b/frontend/src/lib/k8s/KubeObject.ts
@@ -599,6 +599,18 @@ export class KubeObject {
return 'Error';
}
}
+
+ static getBaseObject(): Omit & {
+ metadata: Partial;
+ } {
+ return {
+ apiVersion: Array.isArray(this.apiVersion) ? this.apiVersion[0] : this.apiVersion,
+ kind: this.kind,
+ metadata: {
+ name: '',
+ },
+ };
+ }
}
/**
diff --git a/frontend/src/lib/k8s/configMap.ts b/frontend/src/lib/k8s/configMap.ts
index 5609fd98cf..246b2cf0eb 100644
--- a/frontend/src/lib/k8s/configMap.ts
+++ b/frontend/src/lib/k8s/configMap.ts
@@ -14,6 +14,12 @@ class ConfigMap extends KubeObject {
get data() {
return this.jsonData.data;
}
+
+ static getBaseObject(): KubeConfigMap {
+ const baseObject = super.getBaseObject() as KubeConfigMap;
+ baseObject.data = {};
+ return baseObject;
+ }
}
export default ConfigMap;
diff --git a/frontend/src/lib/k8s/cronJob.ts b/frontend/src/lib/k8s/cronJob.ts
index 36a108cd22..ceddbfe815 100644
--- a/frontend/src/lib/k8s/cronJob.ts
+++ b/frontend/src/lib/k8s/cronJob.ts
@@ -49,6 +49,37 @@ class CronJob extends KubeObject {
return this.getValue('status');
}
+ static getBaseObject(): KubeCronJob {
+ const baseObject = super.getBaseObject() as KubeCronJob;
+ baseObject.metadata = {
+ ...baseObject.metadata,
+ namespace: '',
+ };
+ baseObject.spec = {
+ suspend: false,
+ schedule: '',
+ successfulJobsHistoryLimit: 3,
+ failedJobsHistoryLimit: 1,
+ concurrencyPolicy: 'Allow',
+ jobTemplate: {
+ spec: {
+ template: {
+ spec: {
+ containers: [
+ {
+ name: '',
+ image: '',
+ imagePullPolicy: 'Always',
+ },
+ ],
+ },
+ },
+ },
+ },
+ };
+ return baseObject;
+ }
+
getContainers(): KubeContainer[] {
return this.spec.jobTemplate?.spec?.template?.spec?.containers || [];
}
diff --git a/frontend/src/lib/k8s/daemonSet.ts b/frontend/src/lib/k8s/daemonSet.ts
index da4088da3c..5978f39f9c 100644
--- a/frontend/src/lib/k8s/daemonSet.ts
+++ b/frontend/src/lib/k8s/daemonSet.ts
@@ -13,7 +13,7 @@ export interface KubeDaemonSet extends KubeObjectInterface {
};
selector: LabelSelector;
template: {
- metadata: KubeMetadata;
+ metadata?: KubeMetadata;
spec: KubePodSpec;
};
[otherProps: string]: any;
@@ -37,6 +37,38 @@ class DaemonSet extends KubeObject {
return this.jsonData.status;
}
+ static getBaseObject(): KubeDaemonSet {
+ const baseObject = super.getBaseObject() as KubeDaemonSet;
+ baseObject.metadata = {
+ ...baseObject.metadata,
+ namespace: '',
+ };
+ baseObject.spec = {
+ updateStrategy: {
+ type: 'RollingUpdate',
+ rollingUpdate: {
+ maxUnavailable: 1,
+ },
+ },
+ selector: {
+ matchLabels: { app: 'headlamp' },
+ },
+ template: {
+ spec: {
+ containers: [
+ {
+ name: '',
+ image: '',
+ imagePullPolicy: 'Always',
+ },
+ ],
+ nodeName: '',
+ },
+ },
+ };
+ return baseObject;
+ }
+
getContainers(): KubeContainer[] {
return this.spec?.template?.spec?.containers || [];
}
diff --git a/frontend/src/lib/k8s/deployment.ts b/frontend/src/lib/k8s/deployment.ts
index 4632392f15..2871aabd61 100644
--- a/frontend/src/lib/k8s/deployment.ts
+++ b/frontend/src/lib/k8s/deployment.ts
@@ -43,6 +43,35 @@ class Deployment extends KubeObject {
const labels = this.spec.selector.matchLabels || {};
return Object.keys(labels).map(key => `${key}=${labels[key]}`);
}
+
+ static getBaseObject(): KubeDeployment {
+ const baseObject = super.getBaseObject() as KubeDeployment;
+ baseObject.metadata = {
+ ...baseObject.metadata,
+ namespace: '',
+ labels: { app: 'headlamp' },
+ };
+ baseObject.spec = {
+ selector: {
+ matchLabels: { app: 'headlamp' },
+ },
+ template: {
+ spec: {
+ containers: [
+ {
+ name: '',
+ image: '',
+ ports: [{ containerPort: 80 }],
+ imagePullPolicy: 'Always',
+ },
+ ],
+ nodeName: '',
+ },
+ },
+ };
+
+ return baseObject;
+ }
}
export default Deployment;
diff --git a/frontend/src/lib/k8s/endpoints.ts b/frontend/src/lib/k8s/endpoints.ts
index 90237b605d..40d47823da 100644
--- a/frontend/src/lib/k8s/endpoints.ts
+++ b/frontend/src/lib/k8s/endpoints.ts
@@ -34,6 +34,29 @@ class Endpoints extends KubeObject {
static apiVersion = 'v1';
static isNamespaced = true;
+ static getBaseObject(): KubeEndpoint {
+ const baseObject = super.getBaseObject() as KubeEndpoint;
+ baseObject.subsets = [
+ {
+ addresses: [
+ {
+ hostname: '',
+ ip: '',
+ },
+ ],
+ ports: [
+ {
+ name: '',
+ appProtocol: 'http',
+ port: 80,
+ protocol: 'TCP',
+ },
+ ],
+ },
+ ];
+ return baseObject;
+ }
+
// @todo Remove this when we can break backward compatibility.
static get detailsRoute() {
return 'Endpoint';
diff --git a/frontend/src/lib/k8s/hpa.ts b/frontend/src/lib/k8s/hpa.ts
index 95cc5518b7..42d881bd42 100644
--- a/frontend/src/lib/k8s/hpa.ts
+++ b/frontend/src/lib/k8s/hpa.ts
@@ -172,6 +172,17 @@ class HPA extends KubeObject {
static apiVersion = 'autoscaling/v2';
static isNamespaced = true;
+ static getBaseObject(): KubeHPA {
+ const baseObject = super.getBaseObject() as KubeHPA;
+ baseObject.spec = {
+ maxReplicas: 0,
+ minReplicas: 0,
+ scaleTargetRef: { apiVersion: '', kind: '', name: '' },
+ metrics: [],
+ };
+ return baseObject;
+ }
+
get spec(): HpaSpec {
return this.jsonData.spec;
}
diff --git a/frontend/src/lib/k8s/ingress.ts b/frontend/src/lib/k8s/ingress.ts
index ca4c913982..25a2f2e703 100644
--- a/frontend/src/lib/k8s/ingress.ts
+++ b/frontend/src/lib/k8s/ingress.ts
@@ -73,6 +73,39 @@ class Ingress extends KubeObject {
static apiVersion = ['networking.k8s.io/v1', 'extensions/v1beta1'];
static isNamespaced = true;
+ static getBaseObject(): KubeIngress {
+ const baseObject = super.getBaseObject() as KubeIngress;
+ baseObject.spec = {
+ rules: [
+ {
+ host: '',
+ http: {
+ paths: [
+ {
+ path: '',
+ backend: {
+ service: {
+ name: '',
+ port: {
+ number: 80,
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ ],
+ tls: [
+ {
+ hosts: [],
+ secretName: '',
+ },
+ ],
+ };
+ return baseObject;
+ }
+
// Normalized, cached rules.
private cachedRules: IngressRule[] = [];
diff --git a/frontend/src/lib/k8s/ingressClass.ts b/frontend/src/lib/k8s/ingressClass.ts
index bb16021d2a..703ff6b8b2 100644
--- a/frontend/src/lib/k8s/ingressClass.ts
+++ b/frontend/src/lib/k8s/ingressClass.ts
@@ -13,6 +13,12 @@ class IngressClass extends KubeObject {
static apiVersion = 'networking.k8s.io/v1';
static isNamespaced = false;
+ static getBaseObject(): KubeIngressClass {
+ const baseObject = super.getBaseObject() as KubeIngressClass;
+ baseObject.spec = { controller: '' };
+ return baseObject;
+ }
+
get spec(): KubeIngressClass['spec'] {
return this.jsonData.spec;
}
diff --git a/frontend/src/lib/k8s/lease.ts b/frontend/src/lib/k8s/lease.ts
index 954f290f6f..de826afe33 100644
--- a/frontend/src/lib/k8s/lease.ts
+++ b/frontend/src/lib/k8s/lease.ts
@@ -17,6 +17,17 @@ export class Lease extends KubeObject {
static apiVersion = 'coordination.k8s.io/v1';
static isNamespaced = true;
+ static getBaseObject(): KubeLease {
+ const baseObject = super.getBaseObject() as KubeLease;
+ baseObject.spec = {
+ holderIdentity: '',
+ leaseDurationSeconds: 0,
+ leaseTransitions: 0,
+ renewTime: '',
+ };
+ return baseObject;
+ }
+
get spec() {
return this.jsonData.spec;
}
diff --git a/frontend/src/lib/k8s/limitRange.tsx b/frontend/src/lib/k8s/limitRange.tsx
index 1380374c19..67a89fd3f8 100644
--- a/frontend/src/lib/k8s/limitRange.tsx
+++ b/frontend/src/lib/k8s/limitRange.tsx
@@ -32,6 +32,34 @@ export class LimitRange extends KubeObject {
static apiVersion = 'v1';
static isNamespaced = true;
+ static getBaseObject(): KubeLimitRange {
+ const baseObject = super.getBaseObject() as KubeLimitRange;
+ baseObject.spec = {
+ limits: [
+ {
+ default: {
+ cpu: '',
+ memory: '',
+ },
+ defaultRequest: {
+ cpu: '',
+ memory: '',
+ },
+ max: {
+ cpu: '',
+ memory: '',
+ },
+ min: {
+ cpu: '',
+ memory: '',
+ },
+ type: '',
+ },
+ ],
+ };
+ return baseObject;
+ }
+
get spec() {
return this.jsonData.spec;
}
diff --git a/frontend/src/lib/k8s/mutatingWebhookConfiguration.ts b/frontend/src/lib/k8s/mutatingWebhookConfiguration.ts
index d939d7782b..8afe757c36 100644
--- a/frontend/src/lib/k8s/mutatingWebhookConfiguration.ts
+++ b/frontend/src/lib/k8s/mutatingWebhookConfiguration.ts
@@ -48,6 +48,32 @@ class MutatingWebhookConfiguration extends KubeObject {
static apiVersion = 'networking.k8s.io/v1';
static isNamespaced = true;
+ static getBaseObject(): KubeNetworkPolicy {
+ const baseObject = super.getBaseObject() as KubeNetworkPolicy;
+ baseObject.egress = [
+ {
+ ports: [
+ {
+ port: 80,
+ protocol: 'TCP',
+ },
+ ],
+ to: [
+ {
+ podSelector: {
+ matchLabels: { app: 'headlamp' },
+ },
+ },
+ ],
+ },
+ ];
+ baseObject.ingress = [
+ {
+ ports: [
+ {
+ port: 80,
+ protocol: 'TCP',
+ },
+ ],
+ from: [
+ {
+ podSelector: {
+ matchLabels: { app: 'headlamp' },
+ },
+ },
+ ],
+ },
+ ];
+ baseObject.podSelector = {
+ matchLabels: { app: 'headlamp' },
+ };
+ baseObject.policyTypes = ['Ingress', 'Egress'];
+ return baseObject;
+ }
+
static get pluralName() {
return 'networkpolicies';
}
diff --git a/frontend/src/lib/k8s/persistentVolume.ts b/frontend/src/lib/k8s/persistentVolume.ts
index e442bde0f9..a2e94e1e94 100644
--- a/frontend/src/lib/k8s/persistentVolume.ts
+++ b/frontend/src/lib/k8s/persistentVolume.ts
@@ -20,6 +20,25 @@ class PersistentVolume extends KubeObject {
static apiVersion = 'v1';
static isNamespaced = false;
+ static getBaseObject(): KubePersistentVolume {
+ const baseObject = super.getBaseObject() as KubePersistentVolume;
+ baseObject.metadata = {
+ ...baseObject.metadata,
+ namespace: '',
+ };
+ baseObject.spec = {
+ capacity: {
+ storage: '',
+ },
+ };
+ baseObject.status = {
+ message: '',
+ phase: '',
+ reason: '',
+ };
+ return baseObject;
+ }
+
get spec() {
return this.jsonData.spec;
}
diff --git a/frontend/src/lib/k8s/persistentVolumeClaim.ts b/frontend/src/lib/k8s/persistentVolumeClaim.ts
index 9a366729db..240c6a11c4 100644
--- a/frontend/src/lib/k8s/persistentVolumeClaim.ts
+++ b/frontend/src/lib/k8s/persistentVolumeClaim.ts
@@ -31,6 +31,19 @@ class PersistentVolumeClaim extends KubeObject {
static apiVersion = 'v1';
static isNamespaced = true;
+ static getBaseObject(): KubePersistentVolumeClaim {
+ const baseObject = super.getBaseObject() as KubePersistentVolumeClaim;
+ baseObject.metadata = {
+ ...baseObject.metadata,
+ namespace: '',
+ };
+ baseObject.spec = {
+ storageClassName: '',
+ volumeName: '',
+ };
+ return baseObject;
+ }
+
get spec() {
return this.jsonData.spec;
}
diff --git a/frontend/src/lib/k8s/podDisruptionBudget.ts b/frontend/src/lib/k8s/podDisruptionBudget.ts
index e88b222899..32f87fd443 100644
--- a/frontend/src/lib/k8s/podDisruptionBudget.ts
+++ b/frontend/src/lib/k8s/podDisruptionBudget.ts
@@ -41,6 +41,12 @@ class PDB extends KubeObject {
static apiVersion = 'policy/v1';
static isNamespaced = true;
+ static getBaseObject(): KubePDB {
+ const baseObject = super.getBaseObject() as KubePDB;
+ baseObject.spec = { selector: { matchLabels: {} } };
+ return baseObject;
+ }
+
get spec(): KubePDB['spec'] {
return this.jsonData.spec;
}
diff --git a/frontend/src/lib/k8s/priorityClass.ts b/frontend/src/lib/k8s/priorityClass.ts
index 03a417f775..61ed932187 100644
--- a/frontend/src/lib/k8s/priorityClass.ts
+++ b/frontend/src/lib/k8s/priorityClass.ts
@@ -13,6 +13,15 @@ class PriorityClass extends KubeObject {
static apiVersion = 'scheduling.k8s.io/v1';
static isNamespaced = false;
+ static getBaseObject(): KubePriorityClass {
+ const baseObject = super.getBaseObject() as KubePriorityClass;
+ baseObject.value = 0;
+ baseObject.preemptionPolicy = '';
+ baseObject.globalDefault = false;
+ baseObject.description = '';
+ return baseObject;
+ }
+
get value(): number {
return this.jsonData!.value;
}
diff --git a/frontend/src/lib/k8s/replicaSet.ts b/frontend/src/lib/k8s/replicaSet.ts
index a20fc2b524..ad1c1de9fc 100644
--- a/frontend/src/lib/k8s/replicaSet.ts
+++ b/frontend/src/lib/k8s/replicaSet.ts
@@ -38,6 +38,34 @@ class ReplicaSet extends KubeObject {
return this.jsonData.status;
}
+ static getBaseObject(): KubeReplicaSet {
+ const baseObject = super.getBaseObject() as KubeReplicaSet;
+ baseObject.metadata = {
+ ...baseObject.metadata,
+ namespace: '',
+ };
+ baseObject.spec = {
+ minReadySeconds: 0,
+ replicas: 1,
+ selector: {
+ matchLabels: { app: 'headlamp' },
+ },
+ template: {
+ spec: {
+ containers: [
+ {
+ name: '',
+ image: '',
+ imagePullPolicy: 'Always',
+ },
+ ],
+ nodeName: '',
+ },
+ },
+ };
+ return baseObject;
+ }
+
getContainers(): KubeContainer[] {
return this.spec?.template?.spec?.containers || [];
}
diff --git a/frontend/src/lib/k8s/resourceQuota.ts b/frontend/src/lib/k8s/resourceQuota.ts
index 553f88fe88..064ccea5d5 100644
--- a/frontend/src/lib/k8s/resourceQuota.ts
+++ b/frontend/src/lib/k8s/resourceQuota.ts
@@ -35,6 +35,12 @@ class ResourceQuota extends KubeObject {
static apiVersion = 'v1';
static isNamespaced = true;
+ static getBaseObject(): KubeResourceQuota {
+ const baseObject = super.getBaseObject() as KubeResourceQuota;
+ baseObject.spec = { hard: {} };
+ return baseObject;
+ }
+
get spec(): spec {
return this.jsonData.spec;
}
diff --git a/frontend/src/lib/k8s/runtime.ts b/frontend/src/lib/k8s/runtime.ts
index 9d97aeec25..9dc08bb688 100644
--- a/frontend/src/lib/k8s/runtime.ts
+++ b/frontend/src/lib/k8s/runtime.ts
@@ -12,6 +12,12 @@ export class RuntimeClass extends KubeObject {
static apiVersion = 'node.k8s.io/v1';
static isNamespaced = false;
+ static getBaseObject(): KubeRuntimeClass {
+ const baseObject = super.getBaseObject() as KubeRuntimeClass;
+ baseObject.handler = '';
+ return baseObject;
+ }
+
get spec() {
return this.jsonData.spec;
}
diff --git a/frontend/src/lib/k8s/secret.ts b/frontend/src/lib/k8s/secret.ts
index 628b225599..db6e9b2ee4 100644
--- a/frontend/src/lib/k8s/secret.ts
+++ b/frontend/src/lib/k8s/secret.ts
@@ -11,6 +11,12 @@ class Secret extends KubeObject {
static apiVersion = 'v1';
static isNamespaced = true;
+ static getBaseObject(): KubeSecret {
+ const baseObject = super.getBaseObject() as KubeSecret;
+ baseObject.data = {};
+ return baseObject;
+ }
+
get data() {
return this.jsonData.data;
}
diff --git a/frontend/src/lib/k8s/service.ts b/frontend/src/lib/k8s/service.ts
index ac46d152db..32302d94e8 100644
--- a/frontend/src/lib/k8s/service.ts
+++ b/frontend/src/lib/k8s/service.ts
@@ -45,6 +45,26 @@ class Service extends KubeObject {
static apiVersion = 'v1';
static isNamespaced = true;
+ static getBaseObject(): KubeService {
+ const baseObject = super.getBaseObject() as KubeService;
+ baseObject.spec = {
+ clusterIP: '',
+ ports: [
+ {
+ name: '',
+ nodePort: 30000,
+ port: 80,
+ protocol: 'TCP',
+ targetPort: 80,
+ },
+ ],
+ type: 'ClusterIP',
+ externalIPs: [],
+ selector: {},
+ };
+ return baseObject;
+ }
+
get spec(): KubeService['spec'] {
return this.jsonData.spec;
}
diff --git a/frontend/src/lib/k8s/serviceAccount.ts b/frontend/src/lib/k8s/serviceAccount.ts
index f0623fd6b7..a68a7dfd7a 100644
--- a/frontend/src/lib/k8s/serviceAccount.ts
+++ b/frontend/src/lib/k8s/serviceAccount.ts
@@ -17,6 +17,16 @@ class ServiceAccount extends KubeObject {
static apiVersion = 'v1';
static isNamespaced = true;
+ static getBaseObject(): KubeServiceAccount {
+ const baseObject = super.getBaseObject() as KubeServiceAccount;
+ baseObject.metadata = {
+ ...baseObject.metadata,
+ namespace: '',
+ };
+ baseObject.secrets = [];
+ return baseObject;
+ }
+
get secrets(): KubeServiceAccount['secrets'] {
return this.jsonData.secrets;
}
diff --git a/frontend/src/lib/k8s/statefulSet.ts b/frontend/src/lib/k8s/statefulSet.ts
index 5859c7215b..dfa649373f 100644
--- a/frontend/src/lib/k8s/statefulSet.ts
+++ b/frontend/src/lib/k8s/statefulSet.ts
@@ -13,7 +13,7 @@ export interface KubeStatefulSet extends KubeObjectInterface {
type: string;
};
template: {
- metadata: KubeMetadata;
+ metadata?: KubeMetadata;
spec: KubePodSpec;
};
[other: string]: any;
@@ -37,6 +37,36 @@ class StatefulSet extends KubeObject {
return this.jsonData.status;
}
+ static getBaseObject(): KubeStatefulSet {
+ const baseObject = super.getBaseObject() as KubeStatefulSet;
+ baseObject.metadata = {
+ ...baseObject.metadata,
+ namespace: '',
+ };
+ baseObject.spec = {
+ selector: {
+ matchLabels: { app: 'headlamp' },
+ },
+ updateStrategy: {
+ type: 'RollingUpdate',
+ rollingUpdate: { partition: 0 },
+ },
+ template: {
+ spec: {
+ containers: [
+ {
+ name: '',
+ image: '',
+ imagePullPolicy: 'Always',
+ },
+ ],
+ nodeName: '',
+ },
+ },
+ };
+ return baseObject;
+ }
+
getContainers(): KubeContainer[] {
return this.spec?.template?.spec?.containers || [];
}
diff --git a/frontend/src/lib/k8s/storageClass.ts b/frontend/src/lib/k8s/storageClass.ts
index a13cb6c7ba..72f6156695 100644
--- a/frontend/src/lib/k8s/storageClass.ts
+++ b/frontend/src/lib/k8s/storageClass.ts
@@ -13,6 +13,15 @@ class StorageClass extends KubeObject {
static apiVersion = 'storage.k8s.io/v1';
static isNamespaced = false;
+ static getBaseObject(): KubeStorageClass {
+ const baseObject = super.getBaseObject() as KubeStorageClass;
+ baseObject.provisioner = '';
+ baseObject.reclaimPolicy = '';
+ baseObject.volumeBindingMode = '';
+ baseObject.allowVolumeExpansion = false;
+ return baseObject;
+ }
+
get provisioner() {
return this.jsonData.provisioner;
}
diff --git a/frontend/src/lib/k8s/validatingWebhookConfiguration.ts b/frontend/src/lib/k8s/validatingWebhookConfiguration.ts
index fc97a1dbd3..c49c66534f 100644
--- a/frontend/src/lib/k8s/validatingWebhookConfiguration.ts
+++ b/frontend/src/lib/k8s/validatingWebhookConfiguration.ts
@@ -29,6 +29,32 @@ class ValidatingWebhookConfiguration extends KubeObject {
static apiVersion = 'autoscaling.k8s.io/v1';
static isNamespaced = true;
+ static getBaseObject(): KubeVPA {
+ const baseObject = super.getBaseObject() as KubeVPA;
+ baseObject.spec = {
+ targetRef: {
+ apiVersion: '',
+ kind: '',
+ name: '',
+ },
+ };
+ return baseObject;
+ }
+
static async isEnabled(): Promise {
let res;
try {
diff --git a/frontend/src/plugin/__snapshots__/pluginLib.snapshot b/frontend/src/plugin/__snapshots__/pluginLib.snapshot
index 7bb73c5a37..7f8fa82a1e 100644
--- a/frontend/src/plugin/__snapshots__/pluginLib.snapshot
+++ b/frontend/src/plugin/__snapshots__/pluginLib.snapshot
@@ -35,6 +35,7 @@
"ConfirmButton": [Function],
"ConfirmDialog": [Function],
"CreateButton": [Function],
+ "CreateResourceButton": [Function],
"DateLabel": [Function],
"DeleteButton": [Function],
"Dialog": [Function],