diff --git a/frontend/src/components/App/icons.ts b/frontend/src/components/App/icons.ts
index d188f1bdacf..7b5a66d3d24 100644
--- a/frontend/src/components/App/icons.ts
+++ b/frontend/src/components/App/icons.ts
@@ -311,6 +311,9 @@ const mdiIcons = {
'expand-all': {
body: '',
},
+ puzzle: {
+ body: '',
+ },
},
aliases: {
'more-vert': {
diff --git a/frontend/src/components/Sidebar/prepareRoutes.ts b/frontend/src/components/Sidebar/prepareRoutes.ts
index 53804a152a7..153820c084a 100644
--- a/frontend/src/components/Sidebar/prepareRoutes.ts
+++ b/frontend/src/components/Sidebar/prepareRoutes.ts
@@ -68,10 +68,6 @@ function prepareRoutes(
name: 'nodes',
label: t('glossary|Nodes'),
},
- {
- name: 'crds',
- label: t('glossary|Custom Resources'),
- },
],
},
{
@@ -234,6 +230,17 @@ function prepareRoutes(
},
],
},
+ {
+ name: 'crds',
+ label: t('glossary|Custom Resources'),
+ icon: 'mdi:puzzle',
+ subList: [
+ {
+ name: 'crs',
+ label: t('translation|Instances'),
+ },
+ ],
+ },
];
const sidebars: { [key: string]: SidebarItemProps[] } = {
diff --git a/frontend/src/components/crd/CustomResourceInstancesList.tsx b/frontend/src/components/crd/CustomResourceInstancesList.tsx
new file mode 100644
index 00000000000..ee5636df317
--- /dev/null
+++ b/frontend/src/components/crd/CustomResourceInstancesList.tsx
@@ -0,0 +1,142 @@
+import React, { useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import CRD, { KubeCRD } from '../../lib/k8s/crd';
+import { Link, Loader, SectionBox } from '../common/';
+import Empty from '../common/EmptyContent';
+import { ResourceListView } from '../common/Resource';
+
+interface State {
+ crList: KubeCRD[];
+ loading: boolean;
+ crDictionary: Map;
+}
+
+export function CrInstanceList() {
+ const { t } = useTranslation(['glossary', 'translation']);
+ const [crds, crdsError] = CRD.useList();
+ const [state, setState] = useState({
+ crList: [],
+ loading: true,
+ crDictionary: new Map(),
+ });
+
+ useEffect(() => {
+ const fetchCRs = async () => {
+ const allCrs: KubeCRD[] = [];
+ const newCrDictionary = new Map();
+
+ for (const crd of crds) {
+ const crClass = crd.makeCRClass();
+ const [crItems, crError] = await new Promise<[KubeCRD[] | null, any | null]>(resolve => {
+ crClass.apiList(
+ (items: KubeCRD[]) => resolve([items, null]),
+ (err: any) => resolve([null, err])
+ )();
+ });
+
+ if (crError) {
+ console.error('Error fetching CRs:', crError);
+ continue;
+ }
+
+ if (crItems && crItems.length > 0) {
+ allCrs.push(...crItems);
+ for (const item of crItems) {
+ newCrDictionary.set(item.metadata.name, crd);
+ }
+ }
+ }
+
+ setState({
+ crList: allCrs,
+ loading: false,
+ crDictionary: newCrDictionary,
+ });
+ };
+
+ if (crds) {
+ fetchCRs();
+ }
+ }, [crds]);
+
+ if (crdsError) {
+ return (
+
+ {t('translation|Error getting custom resource definitions: {{ errorMessage }}', {
+ errorMessage: crdsError,
+ })}
+
+ );
+ }
+
+ if (state.loading) {
+ return ;
+ }
+
+ if (state.crList.length === 0) {
+ return {t('translation|No custom resources found.')};
+ }
+
+ return (
+
+ {
+ return cr.metadata.name;
+ },
+ render: cr => {
+ return (
+
+ {cr.metadata.name} {/*crd.metadata.name*/}
+
+ );
+ },
+ },
+ {
+ label: 'CRD',
+ getValue: cr => {
+ return cr.kind;
+ },
+ render: cr => {
+ return (
+
+ {cr.kind} {/*crd.metadata.name*/}
+
+ );
+ },
+ },
+ {
+ label: 'Categories',
+ getValue: cr => {
+ const categories = state.crDictionary.get(cr.metadata.name)?.jsonData!.status
+ .acceptedNames.categories;
+ return categories !== undefined ? categories.toString().split(',').join(', ') : '';
+ },
+ },
+ 'namespace',
+
+ 'age',
+ ]}
+ />
+
+ );
+}
diff --git a/frontend/src/lib/router.tsx b/frontend/src/lib/router.tsx
index 279cff2fecb..d73e5c0aa93 100644
--- a/frontend/src/lib/router.tsx
+++ b/frontend/src/lib/router.tsx
@@ -15,6 +15,7 @@ import { PageGrid } from '../components/common/Resource/Resource';
import ConfigDetails from '../components/configmap/Details';
import ConfigMapList from '../components/configmap/List';
import CustomResourceDetails from '../components/crd/CustomResourceDetails';
+import { CrInstanceList } from '../components/crd/CustomResourceInstancesList';
import CustomResourceList from '../components/crd/CustomResourceList';
import CustomResourceDefinitionDetails from '../components/crd/Details';
import CustomResourceDefinitionList from '../components/crd/List';
@@ -652,6 +653,13 @@ const defaultRoutes: {
sidebar: 'crds',
component: () => ,
},
+ crs: {
+ path: '/crs',
+ exact: true,
+ name: 'CRInstances',
+ sidebar: 'crs',
+ component: () => ,
+ },
notifications: {
path: '/notifications',
exact: true,