diff --git a/packages/renderer/src/App.svelte b/packages/renderer/src/App.svelte
index 44e789e09f6..d3e964c66f5 100644
--- a/packages/renderer/src/App.svelte
+++ b/packages/renderer/src/App.svelte
@@ -10,6 +10,9 @@ import type { NavigationRequest } from '/@api/navigation-request';
import AppNavigation from './AppNavigation.svelte';
import Appearance from './lib/appearance/Appearance.svelte';
import ComposeDetails from './lib/compose/ComposeDetails.svelte';
+import ConfigMapDetails from './lib/configmaps-secrets/ConfigMapDetails.svelte';
+import ConfigMapSecretList from './lib/configmaps-secrets/ConfigMapSecretList.svelte';
+import SecretDetails from './lib/configmaps-secrets/SecretDetails.svelte';
import ContainerDetails from './lib/container/ContainerDetails.svelte';
import ContainerExport from './lib/container/ContainerExport.svelte';
import ContainerList from './lib/container/ContainerList.svelte';
@@ -244,6 +247,23 @@ window.events?.receive('navigate', (navigationRequest: unknown) => {
navigationHint="details">
+
+
+
+
+
+
+
+
+ {
vi.mocked(kubeContextStore).kubernetesCurrentContextIngresses = readable([]);
vi.mocked(kubeContextStore).kubernetesCurrentContextRoutes = readable([]);
vi.mocked(kubeContextStore).kubernetesCurrentContextNodes = readable([]);
+ vi.mocked(kubeContextStore).kubernetesCurrentContextConfigMaps = readable([]);
+ vi.mocked(kubeContextStore).kubernetesCurrentContextSecrets = readable([]);
vi.mocked(kubeContextStore).kubernetesCurrentContextPersistentVolumeClaims = readable([]);
render(AppNavigation, {
diff --git a/packages/renderer/src/AppNavigation.svelte b/packages/renderer/src/AppNavigation.svelte
index d4d6fe5f2c0..8bdc1f633ba 100644
--- a/packages/renderer/src/AppNavigation.svelte
+++ b/packages/renderer/src/AppNavigation.svelte
@@ -13,6 +13,7 @@ import type { ImageInfo } from '/@api/image-info';
import { CommandRegistry } from './lib/CommandRegistry';
import NewContentOnDashboardBadge from './lib/dashboard/NewContentOnDashboardBadge.svelte';
import { ImageUtils } from './lib/image/image-utils';
+import ConfigMapSecretIcon from './lib/images/ConfigMapSecretIcon.svelte';
import DashboardIcon from './lib/images/DashboardIcon.svelte';
import DeploymentIcon from './lib/images/DeploymentIcon.svelte';
import ExtensionIcon from './lib/images/ExtensionIcon.svelte';
@@ -34,11 +35,13 @@ import { contributions } from './stores/contribs';
import { imagesInfos } from './stores/images';
import { kubernetesContexts } from './stores/kubernetes-contexts';
import {
+ kubernetesCurrentContextConfigMaps,
kubernetesCurrentContextDeployments,
kubernetesCurrentContextIngresses,
kubernetesCurrentContextNodes,
kubernetesCurrentContextPersistentVolumeClaims,
kubernetesCurrentContextRoutes,
+ kubernetesCurrentContextSecrets,
kubernetesCurrentContextServices,
} from './stores/kubernetes-contexts-state';
import { podsInfos } from './stores/pods';
@@ -55,12 +58,17 @@ let persistentVolumeClaimsSubscribe: Unsubscriber;
let servicesSubscribe: Unsubscriber;
let ingressesSubscribe: Unsubscriber;
let routesSubscribe: Unsubscriber;
+let configmapsSubscribe: Unsubscriber;
+let secretsSubscribe: Unsubscriber;
let combinedInstalledExtensionsSubscribe: Unsubscriber;
let podCount = '';
let containerCount = '';
let imageCount = '';
let volumeCount = '';
+let configmapsCount = 0;
+let secretsCount = 0;
+let configmapSecretsCount = '';
let persistentVolumeClaimsCount = '';
let contextCount = 0;
let deploymentCount = '';
@@ -145,6 +153,15 @@ onMount(async () => {
routesCount = value.length;
updateIngressesRoutesCount(ingressesCount + routesCount);
});
+
+ configmapsSubscribe = kubernetesCurrentContextConfigMaps.subscribe(value => {
+ configmapsCount = value.length;
+ updateConfigMapSecretsCount(configmapsCount + secretsCount);
+ });
+ secretsSubscribe = kubernetesCurrentContextSecrets.subscribe(value => {
+ secretsCount = value.length;
+ updateConfigMapSecretsCount(configmapsCount + secretsCount);
+ });
contextsSubscribe = kubernetesContexts.subscribe(value => {
contextCount = value.length;
});
@@ -185,8 +202,16 @@ onDestroy(() => {
if (servicesSubscribe) {
servicesSubscribe();
}
+ if (configmapsSubscribe) {
+ configmapsSubscribe();
+ }
+ if (secretsSubscribe) {
+ secretsSubscribe();
+ }
ingressesSubscribe?.();
routesSubscribe?.();
+ configmapsSubscribe?.();
+ secretsSubscribe?.();
combinedInstalledExtensionsSubscribe?.();
});
@@ -198,6 +223,14 @@ function updateIngressesRoutesCount(count: number) {
}
}
+function updateConfigMapSecretsCount(count: number) {
+ if (count > 0) {
+ configmapSecretsCount = ' (' + count + ')';
+ } else {
+ configmapSecretsCount = '';
+ }
+}
+
function clickSettings(b: boolean) {
if (b) {
exitSettingsCallback();
@@ -262,6 +295,13 @@ export let meta: TinroRouteMeta;
bind:meta="{meta}">
+
+
+
{/if}
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetails.spec.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetails.spec.ts
new file mode 100644
index 00000000000..369a0540b33
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetails.spec.ts
@@ -0,0 +1,57 @@
+/**********************************************************************
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ***********************************************************************/
+
+import '@testing-library/jest-dom/vitest';
+
+import type { KubernetesObject, V1ConfigMap } from '@kubernetes/client-node';
+import { render, screen } from '@testing-library/svelte';
+import { writable } from 'svelte/store';
+import { beforeAll, expect, test, vi } from 'vitest';
+
+import * as kubeContextStore from '/@/stores/kubernetes-contexts-state';
+
+import ConfigMapDetails from './ConfigMapDetails.svelte';
+
+const configMap: V1ConfigMap = {
+ metadata: {
+ name: 'my-configmap',
+ namespace: 'default',
+ },
+ data: {},
+};
+
+vi.mock('/@/stores/kubernetes-contexts-state', async () => {
+ return {
+ kubernetesCurrentContextConfigMaps: vi.fn(),
+ };
+});
+
+beforeAll(() => {
+ (window as any).kubernetesReadNamespacedConfigMap = vi.fn();
+});
+
+test('Confirm renders configmap details', async () => {
+ // mock object store
+ const configMaps = writable([configMap]);
+ vi.mocked(kubeContextStore).kubernetesCurrentContextConfigMaps = configMaps;
+
+ render(ConfigMapDetails, { name: 'my-configmap', namespace: 'default' });
+
+ expect(screen.getByText('my-configmap')).toBeInTheDocument();
+ expect(screen.getByText('default')).toBeInTheDocument();
+});
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetails.svelte b/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetails.svelte
new file mode 100644
index 00000000000..c2e599ba28c
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetails.svelte
@@ -0,0 +1,96 @@
+
+
+{#if configMap}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{/if}
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetailsSummary.spec.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetailsSummary.spec.ts
new file mode 100644
index 00000000000..db2aa78281a
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetailsSummary.spec.ts
@@ -0,0 +1,72 @@
+/**********************************************************************
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ***********************************************************************/
+import '@testing-library/jest-dom/vitest';
+
+import type { V1ConfigMap } from '@kubernetes/client-node';
+import { render, screen } from '@testing-library/svelte';
+import { beforeEach, expect, test, vi } from 'vitest';
+
+import ConfigMapDetailsSummary from './ConfigMapDetailsSummary.svelte';
+
+const configMap: V1ConfigMap = {
+ metadata: {
+ name: 'my-configmap',
+ namespace: 'default',
+ },
+ data: {
+ key1: 'value1',
+ key2: 'value2',
+ },
+ binaryData: {
+ key3: 'value3',
+ },
+};
+
+const kubeError = 'Error retrieving node details';
+
+beforeEach(() => {
+ vi.resetAllMocks();
+ vi.clearAllMocks();
+});
+
+test('Confirm renders configmap details summary', async () => {
+ render(ConfigMapDetailsSummary, { configMap });
+
+ expect(screen.getByText('my-configmap')).toBeInTheDocument();
+ expect(screen.getByText('default')).toBeInTheDocument();
+ expect(screen.getByText('key1')).toBeInTheDocument();
+ expect(screen.getByText('value1')).toBeInTheDocument();
+ expect(screen.getByText('key2')).toBeInTheDocument();
+ expect(screen.getByText('value2')).toBeInTheDocument();
+ // binary data just shows the key and size, not the data
+ expect(screen.getByText('key3: 6 bytes')).toBeInTheDocument();
+});
+
+test('Expect to show loading if there is no data present', async () => {
+ render(ConfigMapDetailsSummary, {});
+
+ const loadingMessage = screen.getByText('Loading ...');
+ expect(loadingMessage).toBeInTheDocument();
+});
+
+test('Expect to show error message when there is a kube error', async () => {
+ render(ConfigMapDetailsSummary, { configMap, kubeError: kubeError });
+
+ const errorMessage = screen.getByText(kubeError);
+ expect(errorMessage).toBeInTheDocument();
+});
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetailsSummary.svelte b/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetailsSummary.svelte
new file mode 100644
index 00000000000..55844bf7b2c
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapDetailsSummary.svelte
@@ -0,0 +1,27 @@
+
+
+
+{#if kubeError}
+
+{/if}
+
+
+ {#if configMap}
+
+
+ {:else}
+
Loading ...
+ {/if}
+
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretActions.spec.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretActions.spec.ts
new file mode 100644
index 00000000000..d64273da037
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretActions.spec.ts
@@ -0,0 +1,82 @@
+/**********************************************************************
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ***********************************************************************/
+
+import '@testing-library/jest-dom/vitest';
+
+import { fireEvent, render, screen, waitFor } from '@testing-library/svelte';
+import { afterEach, beforeEach, expect, test, vi } from 'vitest';
+
+import ConfigMapSecretActions from './ConfigMapSecretActions.svelte';
+import type { ConfigMapSecretUI } from './ConfigMapSecretUI';
+
+const updateMock = vi.fn();
+const deleteMock = vi.fn();
+const showMessageBoxMock = vi.fn();
+
+const fakeConfigMap: ConfigMapSecretUI = {
+ name: 'my-configmap',
+ namespace: '',
+ selected: false,
+ type: 'ConfigMap',
+ status: '',
+ keys: [],
+};
+
+const fakeSecret: ConfigMapSecretUI = {
+ name: 'my-secret',
+ namespace: '',
+ selected: false,
+ type: 'Secret',
+ status: '',
+ keys: [],
+};
+
+beforeEach(() => {
+ (window as any).showMessageBox = showMessageBoxMock;
+ (window as any).kubernetesDeleteConfigMap = deleteMock;
+ (window as any).kubernetesDeleteSecret = deleteMock;
+});
+
+afterEach(() => {
+ vi.resetAllMocks();
+ vi.clearAllMocks();
+});
+
+test('Expect no error when deleting configmap', async () => {
+ showMessageBoxMock.mockResolvedValue({ response: 0 });
+ render(ConfigMapSecretActions, { configMapSecret: fakeConfigMap, onUpdate: updateMock });
+
+ // click on delete button
+ const deleteButton = screen.getByRole('button', { name: 'Delete ConfigMap' });
+ await fireEvent.click(deleteButton);
+
+ // wait for the delete function to be called
+ await waitFor(() => expect(deleteMock).toHaveBeenCalled());
+});
+
+test('Expect no error when deleting secret', async () => {
+ showMessageBoxMock.mockResolvedValue({ response: 0 });
+ render(ConfigMapSecretActions, { configMapSecret: fakeSecret, onUpdate: updateMock });
+
+ // click on delete button
+ const deleteButton = screen.getByRole('button', { name: 'Delete Secret' });
+ await fireEvent.click(deleteButton);
+
+ // wait for the delete function to be called
+ await waitFor(() => expect(deleteMock).toHaveBeenCalled());
+});
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretActions.svelte b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretActions.svelte
new file mode 100644
index 00000000000..2e9f6978d7c
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretActions.svelte
@@ -0,0 +1,41 @@
+
+
+
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnActions.spec.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnActions.spec.ts
new file mode 100644
index 00000000000..67cb05b926e
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnActions.spec.ts
@@ -0,0 +1,41 @@
+/**********************************************************************
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ***********************************************************************/
+
+import '@testing-library/jest-dom/vitest';
+
+import { render, screen } from '@testing-library/svelte';
+import { expect, test } from 'vitest';
+
+import ConfigMapSecretColumnActions from './ConfigMapSecretColumnActions.svelte';
+import type { ConfigMapSecretUI } from './ConfigMapSecretUI';
+
+test('Expect action buttons', async () => {
+ const configMap: ConfigMapSecretUI = {
+ name: 'my-configmap',
+ namespace: '',
+ selected: false,
+ type: 'ConfigMap',
+ status: '',
+ keys: [],
+ };
+
+ render(ConfigMapSecretColumnActions, { object: configMap });
+
+ const buttons = await screen.findAllByRole('button');
+ expect(buttons).toHaveLength(1);
+});
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnActions.svelte b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnActions.svelte
new file mode 100644
index 00000000000..46894d1f2ed
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnActions.svelte
@@ -0,0 +1,8 @@
+
+
+
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnName.spec.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnName.spec.ts
new file mode 100644
index 00000000000..5660c770886
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnName.spec.ts
@@ -0,0 +1,88 @@
+/**********************************************************************
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ***********************************************************************/
+
+import '@testing-library/jest-dom/vitest';
+
+import { fireEvent, render, screen } from '@testing-library/svelte';
+import { router } from 'tinro';
+import { expect, test, vi } from 'vitest';
+
+import ConfigMapSecretColumnName from './ConfigMapSecretColumnName.svelte';
+import type { ConfigMapSecretUI } from './ConfigMapSecretUI';
+
+const configMap: ConfigMapSecretUI = {
+ name: 'my-configmap',
+ namespace: 'default',
+ selected: false,
+ type: 'ConfigMap',
+ status: '',
+ keys: [],
+};
+
+const secret: ConfigMapSecretUI = {
+ name: 'my-secret',
+ namespace: 'default',
+ selected: false,
+ type: 'Secret',
+ status: '',
+ keys: [],
+};
+
+test('Expect simple column styling', async () => {
+ render(ConfigMapSecretColumnName, { object: configMap });
+
+ const text = screen.getByText(configMap.name);
+ expect(text).toBeInTheDocument();
+ expect(text).toHaveClass('text-sm');
+ expect(text).toHaveClass('text-[var(--pd-table-body-text-highlight)]');
+});
+
+test('Configmap: Expect clicking works', async () => {
+ render(ConfigMapSecretColumnName, { object: configMap });
+
+ const text = screen.getByText(configMap.name);
+ expect(text).toBeInTheDocument();
+
+ // test click
+ const routerGotoSpy = vi.spyOn(router, 'goto');
+
+ fireEvent.click(text);
+
+ expect(routerGotoSpy).toBeCalledWith('/configmapsSecrets/configmap/my-configmap/default/summary');
+});
+
+test('Secret: Expect clicking works', async () => {
+ render(ConfigMapSecretColumnName, { object: secret });
+
+ const text = screen.getByText(secret.name);
+ expect(text).toBeInTheDocument();
+
+ // test click
+ const routerGotoSpy = vi.spyOn(router, 'goto');
+
+ fireEvent.click(text);
+
+ expect(routerGotoSpy).toBeCalledWith('/configmapsSecrets/secret/my-secret/default/summary');
+});
+
+test('Expect namespace in column', async () => {
+ render(ConfigMapSecretColumnName, { object: configMap });
+
+ const text = screen.getByText(configMap.namespace);
+ expect(text).toBeInTheDocument();
+});
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnName.svelte b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnName.svelte
new file mode 100644
index 00000000000..c5ae9ab308b
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnName.svelte
@@ -0,0 +1,30 @@
+
+
+
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnStatus.spec.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnStatus.spec.ts
new file mode 100644
index 00000000000..39eeb39fc24
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnStatus.spec.ts
@@ -0,0 +1,42 @@
+/**********************************************************************
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ***********************************************************************/
+
+import '@testing-library/jest-dom/vitest';
+
+import { render, screen } from '@testing-library/svelte';
+import { expect, test } from 'vitest';
+
+import ConfigMapSecretColumnStatus from './ConfigMapSecretColumnStatus.svelte';
+import type { ConfigMapSecretUI } from './ConfigMapSecretUI';
+
+test('Expect status styling for running', async () => {
+ const configMap: ConfigMapSecretUI = {
+ name: 'my-configmap',
+ namespace: '',
+ selected: false,
+ type: 'ConfigMap',
+ status: 'RUNNING',
+ keys: [],
+ };
+
+ render(ConfigMapSecretColumnStatus, { object: configMap });
+
+ const text = screen.getByRole('status');
+ expect(text).toBeInTheDocument();
+ expect(text).toHaveClass('bg-[var(--pd-status-running)]');
+});
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnStatus.svelte b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnStatus.svelte
new file mode 100644
index 00000000000..d26e7e23d96
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnStatus.svelte
@@ -0,0 +1,10 @@
+
+
+
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnType.spec.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnType.spec.ts
new file mode 100644
index 00000000000..b3c48c3b15c
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnType.spec.ts
@@ -0,0 +1,63 @@
+/**********************************************************************
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ***********************************************************************/
+
+import '@testing-library/jest-dom/vitest';
+
+import { render, screen } from '@testing-library/svelte';
+import { expect, test } from 'vitest';
+
+import ConfigMapSecretColumnType from './ConfigMapSecretColumnType.svelte';
+import type { ConfigMapSecretUI } from './ConfigMapSecretUI';
+
+test('Expect type display for ConfigMap', async () => {
+ const configMap: ConfigMapSecretUI = {
+ name: 'my-configmap',
+ namespace: '',
+ selected: false,
+ type: 'ConfigMap',
+ status: '',
+ keys: [],
+ };
+
+ render(ConfigMapSecretColumnType, { object: configMap });
+
+ const text = screen.getByText('ConfigMap');
+ expect(text).toBeInTheDocument();
+ const svg = text.parentElement?.querySelector('svg');
+ expect(svg).toBeInTheDocument();
+ expect(svg).toHaveClass('text-[var(--pd-status-running)]');
+});
+
+test('Expect type display for Secret', async () => {
+ const secret: ConfigMapSecretUI = {
+ name: 'my-secret',
+ namespace: '',
+ selected: false,
+ type: 'Secret',
+ status: '',
+ keys: [],
+ };
+
+ render(ConfigMapSecretColumnType, { object: secret });
+
+ const text = screen.getByText('Secret');
+ expect(text).toBeInTheDocument();
+ const svg = text.parentElement?.querySelector('svg');
+ expect(svg).toBeInTheDocument();
+ expect(svg).toHaveClass('text-[var(--pd-status-running)]');
+});
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnType.svelte b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnType.svelte
new file mode 100644
index 00000000000..afaba9b940a
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretColumnType.svelte
@@ -0,0 +1,24 @@
+
+
+
+
+
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretEmptyScreen.spec.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretEmptyScreen.spec.ts
new file mode 100644
index 00000000000..b2e8067dc83
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretEmptyScreen.spec.ts
@@ -0,0 +1,32 @@
+/**********************************************************************
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ***********************************************************************/
+
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+import '@testing-library/jest-dom/vitest';
+
+import { render, screen } from '@testing-library/svelte';
+import { expect, test } from 'vitest';
+
+import ConfigMapSecretEmptyScreen from './ConfigMapSecretEmptyScreen.svelte';
+
+test('Expect configmap empty screen', async () => {
+ render(ConfigMapSecretEmptyScreen);
+ const noNodes = screen.getByRole('heading', { name: 'No configmaps or secrets' });
+ expect(noNodes).toBeInTheDocument();
+});
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretEmptyScreen.svelte b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretEmptyScreen.svelte
new file mode 100644
index 00000000000..213f20a02b2
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretEmptyScreen.svelte
@@ -0,0 +1,10 @@
+
+
+
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretList.spec.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretList.spec.ts
new file mode 100644
index 00000000000..e4ef4315bb9
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretList.spec.ts
@@ -0,0 +1,105 @@
+/**********************************************************************
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ***********************************************************************/
+
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+import '@testing-library/jest-dom/vitest';
+
+import type { V1ConfigMap, V1Secret } from '@kubernetes/client-node';
+import { render, screen } from '@testing-library/svelte';
+/* eslint-disable import/no-duplicates */
+import { tick } from 'svelte';
+import { get } from 'svelte/store';
+/* eslint-enable import/no-duplicates */
+import { beforeAll, beforeEach, expect, test, vi } from 'vitest';
+
+import { kubernetesCurrentContextConfigMaps } from '/@/stores/kubernetes-contexts-state';
+
+import ConfigMapSecretList from './ConfigMapSecretList.svelte';
+
+const kubernetesRegisterGetCurrentContextResourcesMock = vi.fn();
+
+beforeAll(() => {
+ (window as any).kubernetesRegisterGetCurrentContextResources = kubernetesRegisterGetCurrentContextResourcesMock;
+});
+
+beforeEach(() => {
+ vi.resetAllMocks();
+ vi.clearAllMocks();
+ (window as any).kubernetesGetContextsGeneralState = () => Promise.resolve(new Map());
+ (window as any).kubernetesGetCurrentContextGeneralState = () => Promise.resolve({});
+ (window as any).window.kubernetesUnregisterGetCurrentContextResources = () => Promise.resolve(undefined);
+});
+
+async function waitRender(customProperties: object): Promise {
+ render(ConfigMapSecretList, { ...customProperties });
+ await tick();
+}
+
+test('Expect configmap empty screen', async () => {
+ kubernetesRegisterGetCurrentContextResourcesMock.mockResolvedValue([]);
+ render(ConfigMapSecretList);
+ const noNodes = screen.getByRole('heading', { name: 'No configmaps or secrets' });
+ expect(noNodes).toBeInTheDocument();
+});
+
+test('Expect configmap and secrets list', async () => {
+ const configMap: V1ConfigMap = {
+ metadata: {
+ name: 'my-configmap',
+ namespace: 'my-namespace',
+ },
+ data: {
+ key1: 'value1',
+ key2: 'value2',
+ },
+ };
+
+ const secret: V1Secret = {
+ metadata: {
+ name: 'my-secret',
+ namespace: 'my-namespace',
+ },
+ data: {
+ secretkey1: 'value1',
+ secretkey2: 'value2',
+ },
+ type: 'Opaque',
+ };
+
+ kubernetesRegisterGetCurrentContextResourcesMock.mockResolvedValue([configMap, secret]);
+
+ // wait while store is populated
+ while (get(kubernetesCurrentContextConfigMaps).length === 0) {
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+
+ await waitRender({});
+
+ const configMapName = screen.getByRole('cell', { name: 'my-configmap my-namespace' });
+ expect(configMapName).toBeInTheDocument();
+ // Expect ConfigMap type
+ const configMapType = screen.getByRole('cell', { name: 'ConfigMap' });
+ expect(configMapType).toBeInTheDocument();
+
+ const secretName = screen.getByRole('cell', { name: 'my-secret my-namespace' });
+ expect(secretName).toBeInTheDocument();
+ // Expect Opaque type
+ const secretType = screen.getByRole('cell', { name: 'Opaque' });
+ expect(secretType).toBeInTheDocument();
+});
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretList.svelte b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretList.svelte
new file mode 100644
index 00000000000..4079ccbfb10
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretList.svelte
@@ -0,0 +1,190 @@
+
+
+
+
+
+
+
+
+ {#if selectedItemsNumber > 0}
+
+ On {selectedItemsNumber} selected items.
+ {/if}
+
+
diff --git a/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretUI.ts b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretUI.ts
new file mode 100644
index 00000000000..9874dadaff6
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/ConfigMapSecretUI.ts
@@ -0,0 +1,27 @@
+/**********************************************************************
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ***********************************************************************/
+
+export interface ConfigMapSecretUI {
+ name: string;
+ namespace: string;
+ status: string;
+ keys: string[];
+ selected: boolean;
+ type: string;
+ created?: Date;
+}
diff --git a/packages/renderer/src/lib/configmaps-secrets/SecretDetails.spec.ts b/packages/renderer/src/lib/configmaps-secrets/SecretDetails.spec.ts
new file mode 100644
index 00000000000..27f96250e4c
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/SecretDetails.spec.ts
@@ -0,0 +1,57 @@
+/**********************************************************************
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ***********************************************************************/
+
+import '@testing-library/jest-dom/vitest';
+
+import type { KubernetesObject, V1Secret } from '@kubernetes/client-node';
+import { render, screen } from '@testing-library/svelte';
+import { writable } from 'svelte/store';
+import { beforeAll, expect, test, vi } from 'vitest';
+
+import * as kubeContextStore from '/@/stores/kubernetes-contexts-state';
+
+import SecretDetails from './SecretDetails.svelte';
+
+const secret: V1Secret = {
+ metadata: {
+ name: 'my-secret',
+ namespace: 'default',
+ },
+ data: {},
+};
+
+vi.mock('/@/stores/kubernetes-contexts-state', async () => {
+ return {
+ kubernetesCurrentContextSecrets: vi.fn(),
+ };
+});
+
+beforeAll(() => {
+ (window as any).kubernetesReadNamespacedSecret = vi.fn();
+});
+
+test('Confirm renders secret details', async () => {
+ // mock object store
+ const secrets = writable([secret]);
+ vi.mocked(kubeContextStore).kubernetesCurrentContextSecrets = secrets;
+
+ render(SecretDetails, { name: 'my-secret', namespace: 'default' });
+
+ expect(screen.getByText('my-secret')).toBeInTheDocument();
+ expect(screen.getByText('default')).toBeInTheDocument();
+});
diff --git a/packages/renderer/src/lib/configmaps-secrets/SecretDetails.svelte b/packages/renderer/src/lib/configmaps-secrets/SecretDetails.svelte
new file mode 100644
index 00000000000..e36c9afdd4e
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/SecretDetails.svelte
@@ -0,0 +1,93 @@
+
+
+{#if secret}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{/if}
diff --git a/packages/renderer/src/lib/configmaps-secrets/SecretDetailsSummary.spec.ts b/packages/renderer/src/lib/configmaps-secrets/SecretDetailsSummary.spec.ts
new file mode 100644
index 00000000000..d07db86a3cc
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/SecretDetailsSummary.spec.ts
@@ -0,0 +1,71 @@
+/**********************************************************************
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ***********************************************************************/
+import '@testing-library/jest-dom/vitest';
+
+import type { V1Secret } from '@kubernetes/client-node';
+import { render, screen } from '@testing-library/svelte';
+import { beforeEach, expect, test, vi } from 'vitest';
+
+import SecretDetailsSummary from './SecretDetailsSummary.svelte';
+
+const secret: V1Secret = {
+ metadata: {
+ name: 'my-secret',
+ namespace: 'default',
+ },
+ data: {
+ key1: 'value1',
+ key2: 'value2',
+ },
+ type: 'Opaque',
+};
+
+const kubeError = 'Error retrieving node details';
+
+beforeEach(() => {
+ vi.resetAllMocks();
+ vi.clearAllMocks();
+});
+
+test('Confirm renders secret details summary', async () => {
+ render(SecretDetailsSummary, { secret });
+
+ expect(screen.getByText('my-secret')).toBeInTheDocument();
+ expect(screen.getByText('default')).toBeInTheDocument();
+ expect(screen.getByText('key1')).toBeInTheDocument();
+ expect(screen.getByText('value1')).toBeInTheDocument();
+ expect(screen.getByText('key2')).toBeInTheDocument();
+ expect(screen.getByText('value2')).toBeInTheDocument();
+
+ // expect type to be shown
+ expect(screen.getByText('Opaque')).toBeInTheDocument();
+});
+
+test('Expect to show loading if there is no data present', async () => {
+ render(SecretDetailsSummary, {});
+
+ const loadingMessage = screen.getByText('Loading ...');
+ expect(loadingMessage).toBeInTheDocument();
+});
+
+test('Expect to show error message when there is a kube error', async () => {
+ render(SecretDetailsSummary, { secret, kubeError: kubeError });
+
+ const errorMessage = screen.getByText(kubeError);
+ expect(errorMessage).toBeInTheDocument();
+});
diff --git a/packages/renderer/src/lib/configmaps-secrets/SecretDetailsSummary.svelte b/packages/renderer/src/lib/configmaps-secrets/SecretDetailsSummary.svelte
new file mode 100644
index 00000000000..99f7418ccf2
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/SecretDetailsSummary.svelte
@@ -0,0 +1,27 @@
+
+
+
+{#if kubeError}
+
+{/if}
+
+
+ {#if secret}
+
+
+ {:else}
+
Loading ...
+ {/if}
+
diff --git a/packages/renderer/src/lib/configmaps-secrets/configmap-secret-utils.spec.ts b/packages/renderer/src/lib/configmaps-secrets/configmap-secret-utils.spec.ts
new file mode 100644
index 00000000000..1ad2ce75bc6
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/configmap-secret-utils.spec.ts
@@ -0,0 +1,65 @@
+/**********************************************************************
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ***********************************************************************/
+
+import type { V1ConfigMap, V1Secret } from '@kubernetes/client-node';
+import { beforeEach, expect, test, vi } from 'vitest';
+
+import { ConfigMapSecretUtils } from './configmap-secret-utils';
+
+let configMapSecretUtils: ConfigMapSecretUtils;
+
+beforeEach(() => {
+ vi.clearAllMocks();
+ configMapSecretUtils = new ConfigMapSecretUtils();
+});
+
+test('expect configmap UI conversion', async () => {
+ const configMap = {
+ metadata: {
+ name: 'my-configmap',
+ namespace: 'test-namespace',
+ },
+ data: {
+ key1: 'value1',
+ key2: 'value2',
+ },
+ } as V1ConfigMap;
+ const configMapUI = configMapSecretUtils.getConfigMapSecretUI(configMap);
+ expect(configMapUI.name).toEqual('my-configmap');
+ expect(configMapUI.namespace).toEqual('test-namespace');
+ expect(configMapUI.keys).toEqual(['key1', 'key2']);
+});
+
+test('expect secret UI conversion', async () => {
+ const secret = {
+ metadata: {
+ name: 'my-secret',
+ namespace: 'test-namespace',
+ },
+ data: {
+ key1: 'value1',
+ key2: 'value2',
+ },
+ type: 'Opaque',
+ } as V1Secret;
+ const secretUI = configMapSecretUtils.getConfigMapSecretUI(secret);
+ expect(secretUI.name).toEqual('my-secret');
+ expect(secretUI.namespace).toEqual('test-namespace');
+ expect(secretUI.keys).toEqual(['key1', 'key2']);
+ expect(secretUI.type).toEqual('Opaque');
+});
diff --git a/packages/renderer/src/lib/configmaps-secrets/configmap-secret-utils.ts b/packages/renderer/src/lib/configmaps-secrets/configmap-secret-utils.ts
new file mode 100644
index 00000000000..7039dd51605
--- /dev/null
+++ b/packages/renderer/src/lib/configmaps-secrets/configmap-secret-utils.ts
@@ -0,0 +1,55 @@
+/**********************************************************************
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ***********************************************************************/
+
+import type { V1ConfigMap, V1Secret } from '@kubernetes/client-node';
+
+import type { ConfigMapSecretUI } from './ConfigMapSecretUI';
+
+export class ConfigMapSecretUtils {
+ // If it is a secret, then it will have a type property, as well as NO binaryData property
+ isSecret(storage: V1ConfigMap | V1Secret): storage is V1Secret {
+ return 'type' in storage && storage.type !== 'ConfigMap' && !('binaryData' in storage);
+ }
+
+ // If it is a configMap, then the type property will either be undefined, or it will be 'ConfigMap'
+ isConfigMap(storage: V1ConfigMap | V1Secret): storage is V1ConfigMap {
+ return ('type' in storage && storage.type === 'ConfigMap') || 'type' in storage === undefined;
+ }
+
+ getConfigMapSecretUI(storage: V1ConfigMap | V1Secret): ConfigMapSecretUI {
+ const created = storage.metadata?.creationTimestamp;
+ const keys = Object.keys(storage.data ?? {});
+
+ // If storage.type does not exist, it's V1ConfigMap and just set the type as 'ConfigMap'
+ let type = 'ConfigMap';
+ // If storage.type exists, it's V1Secret and set the type as storage.type
+ if ('type' in storage && storage.type) {
+ type = storage.type;
+ }
+
+ return {
+ name: storage.metadata?.name ?? '',
+ namespace: storage.metadata?.namespace ?? '',
+ status: 'RUNNING',
+ keys,
+ selected: false,
+ type,
+ created,
+ };
+ }
+}
diff --git a/packages/renderer/src/lib/kube/details/KubeConfigMapArtifact.svelte b/packages/renderer/src/lib/kube/details/KubeConfigMapArtifact.svelte
new file mode 100644
index 00000000000..1d1d91b4fae
--- /dev/null
+++ b/packages/renderer/src/lib/kube/details/KubeConfigMapArtifact.svelte
@@ -0,0 +1,41 @@
+
+
+{#if artifact}
+