From b4d134bb87100f6f86c25f3e45387a17596a8f73 Mon Sep 17 00:00:00 2001 From: Gaelle Fournier Date: Fri, 11 Apr 2025 17:32:42 +0200 Subject: [PATCH 1/4] Use camel-dashboard-operator for camel discovery --- console-extensions.json | 2 +- ...lugin__camel-openshift-console-plugin.json | 17 ++-- .../camel-app-details/CamelAppDetails.tsx | 83 +++++-------------- .../camel-app-details/CamelAppStatusPod.tsx | 71 ++++++++++++++++ src/components/camel-app-page/CamelApp.tsx | 13 +-- .../camel-app-page/CamelAppTitle.tsx | 10 ++- src/components/camel-app-page/useCamelApp.tsx | 5 +- .../camel-app-resources/CamelAppJobs.tsx | 23 +++-- .../CamelAppOwnerResource.tsx | 36 ++++++++ .../camel-app-resources/CamelAppPods.tsx | 9 +- .../camel-app-resources/CamelAppResources.tsx | 43 ++++++++-- .../camel-app-resources/CamelAppRoutes.tsx | 11 ++- .../camel-app-resources/CamelAppServices.tsx | 13 ++- ...pResources.tsx => useCamelAppResources.ts} | 17 ++++ .../camel-list-page/CamelAppList.tsx | 4 +- .../camel-list-page/CamelAppRow.tsx | 31 +++---- .../camel-list-page/CamelAppStatus.tsx | 31 ------- .../camel-list-page/camelAppVersion.ts | 37 +++++++++ ...elAppColumns.tsx => useCamelAppColumns.ts} | 29 ++----- .../camel-list-page/useCamelAppList.ts | 24 ++++++ .../camel-list-page/useCamelAppList.tsx | 69 --------------- src/const.ts | 28 ++++--- src/types.ts | 36 +++++++- src/utils.ts | 17 ++-- 24 files changed, 375 insertions(+), 284 deletions(-) create mode 100644 src/components/camel-app-details/CamelAppStatusPod.tsx create mode 100644 src/components/camel-app-resources/CamelAppOwnerResource.tsx rename src/components/camel-app-resources/{useCamelAppResources.tsx => useCamelAppResources.ts} (89%) delete mode 100644 src/components/camel-list-page/CamelAppStatus.tsx create mode 100644 src/components/camel-list-page/camelAppVersion.ts rename src/components/camel-list-page/{useCamelAppColumns.tsx => useCamelAppColumns.ts} (58%) create mode 100644 src/components/camel-list-page/useCamelAppList.ts delete mode 100644 src/components/camel-list-page/useCamelAppList.tsx diff --git a/console-extensions.json b/console-extensions.json index 3b1b6bd..0b490c0 100644 --- a/console-extensions.json +++ b/console-extensions.json @@ -3,7 +3,7 @@ "type": "console.page/route", "properties": { "exact": false, - "path": "/camel/app/ns/:ns/kind/:kind/name/:name", + "path": "/camel/app/ns/:ns/name/:name", "component": { "$codeRef": "CamelApp" } } }, diff --git a/locales/en/plugin__camel-openshift-console-plugin.json b/locales/en/plugin__camel-openshift-console-plugin.json index 13ee2d8..69c8cd5 100644 --- a/locales/en/plugin__camel-openshift-console-plugin.json +++ b/locales/en/plugin__camel-openshift-console-plugin.json @@ -1,31 +1,30 @@ { - "Build Timestamp": "Build Timestamp", "C": "C", "Camel": "Camel", "Camel App Details": "Camel App Details", "Camel Details": "Camel Details", - "Created": "Created", + "Camel Version": "Camel Version", "Details": "Details", "Endpoints": "Endpoints", - "Frameworks": "Frameworks", - "Health Endpoints": "Health Endpoints", + "Exchange": "Exchange", + "Health": "Health", + "Image": "Image", + "Internal IP": "Internal IP", "Kind": "Kind", "Location:": "Location:", - "Metrics Endpoint": "Metrics Endpoint", + "Metrics": "Metrics", "Name": "Name", "Namespace": "Namespace", - "No build timestamp": "No build timestamp", "No camel version": "No camel version", "No namespace": "No namespace", "No resources found": "No resources found", - "No version": "No version", "Pod port:": "Pod port:", "Resources": "Resources", "Runtime": "Runtime", - "Runtime version": "Runtime version", + "Runtime Provider": "Runtime Provider", + "Runtime Version": "Runtime Version", "Service port:": "Service port:", "Status": "Status", "unknown host": "unknown host", - "Version": "Version", "View Logs": "View Logs" } \ No newline at end of file diff --git a/src/components/camel-app-details/CamelAppDetails.tsx b/src/components/camel-app-details/CamelAppDetails.tsx index 31c9843..c49fc66 100644 --- a/src/components/camel-app-details/CamelAppDetails.tsx +++ b/src/components/camel-app-details/CamelAppDetails.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import { CamelAppKind } from '../../types'; -import { Card, CardBody, CardTitle, TextContent } from '@patternfly/react-core'; +import { Card, CardBody, TextContent } from '@patternfly/react-core'; import { useTranslation } from 'react-i18next'; import { K8sGroupVersionKind, ResourceLink } from '@openshift-console/dynamic-plugin-sdk'; -import { CamelAppGVK, getBuildTimestamp, getHealthEndpoints } from '../../utils'; +import { camelAppGVK } from '../../const'; +import CamelAppStatusPod from './CamelAppStatusPod'; type CamelAppDetailsProps = { obj: CamelAppKind; @@ -25,77 +26,35 @@ type CamelAppDetails = { const CamelAppDetails: React.FC = ({ obj: camelInt }) => { const { t } = useTranslation('plugin__camel-openshift-console-plugin'); - // TODO : replace with real CR values - const CamelAppDetails = { - groupVersionKind: CamelAppGVK(camelInt.kind), - name: camelInt.metadata.name, - namespace: camelInt.metadata.namespace, - version: '4.10.2', - buildTimestamp: getBuildTimestamp(camelInt), - runtimeFramework: 'quarkus', - runtimeVersion: '3.20.0', - healthEndpoints: getHealthEndpoints('quarkus'), - metricsEndpoint: '/observe/metrics', - }; - return (

{t('Camel App Details')}

- {t('Details')} - {t('Version')}: - {CamelAppDetails.version || ( - {t('No version')} - )} - - - {t('Build Timestamp')}: - {CamelAppDetails.buildTimestamp || ( - {t('No build timestamp')} - )} - - - - - {t('Endpoints')} - - - {t('Health Endpoints')}: - {CamelAppDetails.healthEndpoints - ? CamelAppDetails.healthEndpoints.map((endpoint, i) => { - return {endpoint}; - }) - : '-'} - - - {t('Metrics Endpoint')}: - {CamelAppDetails.metricsEndpoint ? ( - {CamelAppDetails.metricsEndpoint} - ) : ( - '-' - )} - - - - - {t('Frameworks')} - - - {t('Runtime')}: {CamelAppDetails.runtimeFramework} - - - {t('Runtime version')}: {CamelAppDetails.runtimeVersion} + {t('Image')}: + {camelInt.status.image} + +
    + {camelInt.status.pods + ? camelInt.status.pods.map((pod, i) => { + return ( +
  • + +
  • + ); + }) + : ''} +
); }; diff --git a/src/components/camel-app-details/CamelAppStatusPod.tsx b/src/components/camel-app-details/CamelAppStatusPod.tsx new file mode 100644 index 0000000..0f313f4 --- /dev/null +++ b/src/components/camel-app-details/CamelAppStatusPod.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; +import { CamelAppKind, CamelAppStatusPod } from '../../types'; +import { Card, CardBody, TextContent } from '@patternfly/react-core'; +import { podGVK } from '../../const'; +import { ResourceLink, ResourceStatus } from '@openshift-console/dynamic-plugin-sdk'; +import { useTranslation } from 'react-i18next'; +import Status from '@openshift-console/dynamic-plugin-sdk/lib/app/components/status/Status'; + +type CamelAppStatusPodProps = { + obj: CamelAppKind; + pod: CamelAppStatusPod; +}; + +const CamelAppStatusPod: React.FC = ({ obj: camelInt, pod: camelPod }) => { + const { t } = useTranslation('plugin__camel-openshift-console-plugin'); + + return ( + <> + + + + + + + + + + + {t('Internal IP')}: + {camelPod.internalIp} + +

{t('Runtime')}:

+ + + {t('Camel Version')}: + {camelPod.runtime.camelVersion} + + + {t('Runtime Provider')}: + {camelPod.runtime.runtimeProvider} + + + {t('Runtime Version')}: + {camelPod.runtime.runtimeVersion} + + + + {t('Exchange')}: TODO + + +

{t('Endpoints')}:

+ + {t('Health')}: + {camelPod.observe.healthEndpoint}:{camelPod.observe.healthPort} + + + {t('Metrics')}: + {camelPod.observe.metricsEndpoint}:{camelPod.observe.metricsPort} + +
+
+ + ); +}; + +export default CamelAppStatusPod; diff --git a/src/components/camel-app-page/CamelApp.tsx b/src/components/camel-app-page/CamelApp.tsx index 452be29..d73174d 100644 --- a/src/components/camel-app-page/CamelApp.tsx +++ b/src/components/camel-app-page/CamelApp.tsx @@ -7,17 +7,12 @@ import { useCamelAppTabs } from './useCamelAppTabs'; import CamelAppTitle from './CamelAppTitle'; const CamelApp: React.FC = () => { - const { - ns: namespace, - name, - kind, - } = useParams<{ + const { ns: namespace, name } = useParams<{ ns?: string; name?: string; - kind?: string; }>(); - const { CamelApp, isLoading, error } = useCamelApp(name, namespace, kind); + const { CamelApp, isLoading, error } = useCamelApp(name, namespace); const pages = useCamelAppTabs(CamelApp); @@ -25,7 +20,7 @@ const CamelApp: React.FC = () => { if (isLoading) { return ( <> - + ); @@ -39,7 +34,7 @@ const CamelApp: React.FC = () => { return ( <> - + ); diff --git a/src/components/camel-app-page/CamelAppTitle.tsx b/src/components/camel-app-page/CamelAppTitle.tsx index a128a83..27ff6c0 100644 --- a/src/components/camel-app-page/CamelAppTitle.tsx +++ b/src/components/camel-app-page/CamelAppTitle.tsx @@ -3,11 +3,14 @@ import { Link } from 'react-router-dom-v5-compat'; import { Breadcrumb, BreadcrumbItem } from '@patternfly/react-core'; import { useTranslation } from 'react-i18next'; import { ALL_NAMESPACES_KEY } from '../../const'; -import { useActiveNamespace } from '@openshift-console/dynamic-plugin-sdk'; +import { ResourceStatus, useActiveNamespace } from '@openshift-console/dynamic-plugin-sdk'; +import { CamelAppKind } from 'src/types'; +import Status from '@openshift-console/dynamic-plugin-sdk/lib/app/components/status/Status'; type CamelAppTitleProps = { name: string; namespace: string; + obj: CamelAppKind; }; export const getUrlList = (namespace): string => { @@ -18,7 +21,7 @@ export const getUrlList = (namespace): string => { } }; -const CamelAppTitle: React.FC = ({ name }) => { +const CamelAppTitle: React.FC = ({ name, obj }) => { const { t } = useTranslation('plugin__camel-openshift-console-plugin'); const [activeNamespace] = useActiveNamespace(); @@ -40,6 +43,9 @@ const CamelAppTitle: React.FC = ({ name }) => { {t('C')} {name} + + + diff --git a/src/components/camel-app-page/useCamelApp.tsx b/src/components/camel-app-page/useCamelApp.tsx index 514c67f..3dc238f 100644 --- a/src/components/camel-app-page/useCamelApp.tsx +++ b/src/components/camel-app-page/useCamelApp.tsx @@ -1,16 +1,15 @@ import { useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk'; -import { CamelAppGVK } from '../../utils'; import { CamelAppKind } from '../../types'; +import { camelAppGVK } from '../../const'; export const useCamelApp = ( name: string, namespace: string, - kind: string, ): { CamelApp: CamelAppKind; isLoading: boolean; error: string } => { const [CamelAppDatas, loaded, loadError] = useK8sWatchResource({ name: name, namespace: namespace, - groupVersionKind: CamelAppGVK(kind), + groupVersionKind: camelAppGVK, isList: false, }); diff --git a/src/components/camel-app-resources/CamelAppJobs.tsx b/src/components/camel-app-resources/CamelAppJobs.tsx index a447282..20f0f7d 100644 --- a/src/components/camel-app-resources/CamelAppJobs.tsx +++ b/src/components/camel-app-resources/CamelAppJobs.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import { CamelAppKind } from '../../types'; import { useCamelAppJobs } from './useCamelAppResources'; import { Card, CardBody, CardTitle, Spinner } from '@patternfly/react-core'; import { jobGVK } from '../../const'; @@ -7,7 +6,7 @@ import { K8sResourceKind, ResourceLink } from '@openshift-console/dynamic-plugin import Status from '@openshift-console/dynamic-plugin-sdk/lib/app/components/status/Status'; type CamelAppJobsProps = { - obj: CamelAppKind; + obj: K8sResourceKind; }; type Resources = { @@ -15,12 +14,12 @@ type Resources = { status: string; }; -const CamelAppJobs: React.FC = ({ obj: camelInt }) => { +const CamelAppJobs: React.FC = ({ obj: camelAppOwner }) => { const jobs: Resources[] = []; const { CamelAppJobs, loaded: loadedJobs } = useCamelAppJobs( - camelInt.metadata.namespace, - camelInt.spec.selector, + camelAppOwner.metadata.namespace, + camelAppOwner.spec.selector, ); if (loadedJobs && CamelAppJobs.length > 0) { CamelAppJobs.forEach((job) => { @@ -55,15 +54,15 @@ const CamelAppJobs: React.FC = ({ obj: camelInt }) => { return (
  • - - + + - +
  • diff --git a/src/components/camel-app-resources/CamelAppOwnerResource.tsx b/src/components/camel-app-resources/CamelAppOwnerResource.tsx new file mode 100644 index 0000000..871e2a7 --- /dev/null +++ b/src/components/camel-app-resources/CamelAppOwnerResource.tsx @@ -0,0 +1,36 @@ +import * as React from 'react'; +import { Card, CardBody, CardTitle } from '@patternfly/react-core'; +import { + K8sGroupVersionKind, + K8sResourceKind, + ResourceLink, +} from '@openshift-console/dynamic-plugin-sdk'; + +type CamelAppOwnerResourceProps = { + obj: K8sResourceKind; + gvk: K8sGroupVersionKind; +}; + +const CamelAppOwnerResource: React.FC = ({ + obj: camelAppOwner, + gvk: ownerGvk, +}) => { + return ( + + {camelAppOwner.kind} + +
      +
    • + +
    • +
    +
    +
    + ); +}; + +export default CamelAppOwnerResource; diff --git a/src/components/camel-app-resources/CamelAppPods.tsx b/src/components/camel-app-resources/CamelAppPods.tsx index e534b7e..8e59347 100644 --- a/src/components/camel-app-resources/CamelAppPods.tsx +++ b/src/components/camel-app-resources/CamelAppPods.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { Card, CardBody, CardTitle, Spinner, TextContent } from '@patternfly/react-core'; -import { CamelAppKind } from '../../types'; -import { ResourceLink } from '@openshift-console/dynamic-plugin-sdk'; +import { K8sResourceKind, ResourceLink } from '@openshift-console/dynamic-plugin-sdk'; import { podGVK } from '../../const'; import Status from '@openshift-console/dynamic-plugin-sdk/lib/app/components/status/Status'; import { useCamelAppPods } from './useCamelAppResources'; @@ -9,7 +8,7 @@ import { getPodStatus } from '../../utils'; import { useTranslation } from 'react-i18next'; type CamelAppPodsProps = { - obj: CamelAppKind; + obj: K8sResourceKind; }; type Resources = { @@ -75,9 +74,7 @@ const CamelAppPods: React.FC = ({ obj: camelInt }) => { - + {t('View Logs')} diff --git a/src/components/camel-app-resources/CamelAppResources.tsx b/src/components/camel-app-resources/CamelAppResources.tsx index 20bba37..f16a5c9 100644 --- a/src/components/camel-app-resources/CamelAppResources.tsx +++ b/src/components/camel-app-resources/CamelAppResources.tsx @@ -3,8 +3,11 @@ import { CamelAppKind } from '../../types'; import CamelAppPods from './CamelAppPods'; import CamelAppServices from './CamelAppServices'; import CamelAppRoutes from './CamelAppRoutes'; -import { Card, CardBody, CardTitle } from '@patternfly/react-core'; +import CamelAppOwnerResource from './CamelAppOwnerResource'; +import { Card, CardBody, CardTitle, Spinner } from '@patternfly/react-core'; import CamelAppJobs from './CamelAppJobs'; +import { CamelAppOwnerGVK } from '../../utils'; +import { useCamelAppOwner } from './useCamelAppResources'; type CamelAppResourcesProps = { obj: CamelAppKind; @@ -12,14 +15,42 @@ type CamelAppResourcesProps = { // TODO : add volumes const CamelAppResources: React.FC = ({ obj: camelInt }) => { + const ownerReference = camelInt.metadata.ownerReferences[0]; + const ownerGvk = CamelAppOwnerGVK(ownerReference.kind); + + const { CamelAppOwner, isLoading, error } = useCamelAppOwner( + ownerReference.name, + camelInt.metadata.namespace, + ownerGvk, + ); + + // TODO A common loading spinner component + if (isLoading) { + return ( + <> + + Resources + + + + + + ); + } + + // TODO A common error component + if (error) { + return <>{error}; + } + return ( - Resources - - - - + + + + + ); diff --git a/src/components/camel-app-resources/CamelAppRoutes.tsx b/src/components/camel-app-resources/CamelAppRoutes.tsx index 051541e..989dabf 100644 --- a/src/components/camel-app-resources/CamelAppRoutes.tsx +++ b/src/components/camel-app-resources/CamelAppRoutes.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import { CamelAppKind } from '../../types'; import { Card, CardBody, CardTitle, Spinner } from '@patternfly/react-core'; import { useCamelAppRoutes } from './useCamelAppResources'; import { K8sResourceKind, ResourceLink, Selector } from '@openshift-console/dynamic-plugin-sdk'; @@ -9,7 +8,7 @@ import RouteLocation from './RouteLocation'; import { useTranslation } from 'react-i18next'; type CamelAppRoutesProps = { - obj: CamelAppKind; + obj: K8sResourceKind; }; type Resources = { @@ -17,19 +16,19 @@ type Resources = { route: K8sResourceKind; }; -const CamelAppRoutes: React.FC = ({ obj: camelInt }) => { +const CamelAppRoutes: React.FC = ({ obj: camelAppOwner }) => { const { t } = useTranslation('plugin__camel-openshift-console-plugin'); const routes: Resources[] = []; const serviceSelector: Selector = { matchLabels: { - 'app.kubernetes.io/name': serviceMatchLabelValue(camelInt), + 'app.kubernetes.io/name': serviceMatchLabelValue(camelAppOwner), }, }; const { CamelAppRoutes, loaded: loadedRoutes } = useCamelAppRoutes( - camelInt.metadata.namespace, + camelAppOwner.metadata.namespace, serviceSelector, ); if (loadedRoutes && CamelAppRoutes.length > 0) { @@ -67,7 +66,7 @@ const CamelAppRoutes: React.FC = ({ obj: camelInt }) => { {t('Location:')} diff --git a/src/components/camel-app-resources/CamelAppServices.tsx b/src/components/camel-app-resources/CamelAppServices.tsx index 8c1f964..5447672 100644 --- a/src/components/camel-app-resources/CamelAppServices.tsx +++ b/src/components/camel-app-resources/CamelAppServices.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { Card, CardBody, CardTitle, Spinner } from '@patternfly/react-core'; -import { CamelAppKind } from '../../types'; -import { ResourceLink, Selector } from '@openshift-console/dynamic-plugin-sdk'; +import { K8sResourceKind, ResourceLink, Selector } from '@openshift-console/dynamic-plugin-sdk'; import { serviceGVK } from '../../const'; import { useCamelAppServices } from './useCamelAppResources'; import { serviceMatchLabelValue } from '../../utils'; @@ -9,7 +8,7 @@ import { LongArrowAltRightIcon } from '@patternfly/react-icons'; import { useTranslation } from 'react-i18next'; type CamelAppServicesProps = { - obj: CamelAppKind; + obj: K8sResourceKind; }; type Resources = { @@ -17,19 +16,19 @@ type Resources = { ports: []; }; -const CamelAppServices: React.FC = ({ obj: camelInt }) => { +const CamelAppServices: React.FC = ({ obj: camelAppOwner }) => { const { t } = useTranslation('plugin__camel-openshift-console-plugin'); const services: Resources[] = []; const serviceSelector: Selector = { matchLabels: { - 'app.kubernetes.io/name': serviceMatchLabelValue(camelInt), + 'app.kubernetes.io/name': serviceMatchLabelValue(camelAppOwner), }, }; const { CamelAppServices, loaded: loadedServices } = useCamelAppServices( - camelInt.metadata.namespace, + camelAppOwner.metadata.namespace, serviceSelector, ); if (loadedServices && CamelAppServices.length > 0) { @@ -67,7 +66,7 @@ const CamelAppServices: React.FC = ({ obj: camelInt }) =>
      {resource.ports.map(({ name, port, protocol, targetPort }) => ( diff --git a/src/components/camel-app-resources/useCamelAppResources.tsx b/src/components/camel-app-resources/useCamelAppResources.ts similarity index 89% rename from src/components/camel-app-resources/useCamelAppResources.tsx rename to src/components/camel-app-resources/useCamelAppResources.ts index 4140625..311deac 100644 --- a/src/components/camel-app-resources/useCamelAppResources.tsx +++ b/src/components/camel-app-resources/useCamelAppResources.ts @@ -1,10 +1,27 @@ import { + K8sGroupVersionKind, K8sResourceKind, Selector, + useK8sWatchResource, useK8sWatchResources, } from '@openshift-console/dynamic-plugin-sdk'; import { cronJobGVK, jobGVK, podGVK, routeGVK, serviceGVK } from '../../const'; +export const useCamelAppOwner = ( + name: string, + namespace: string, + gvk: K8sGroupVersionKind, +): { CamelAppOwner: K8sResourceKind; isLoading: boolean; error: string } => { + const [CamelAppOwner, loaded, loadError] = useK8sWatchResource({ + name: name, + namespace: namespace, + groupVersionKind: gvk, + isList: false, + }); + + return { CamelAppOwner, isLoading: !loaded, error: loadError }; +}; + export const useCamelAppPods = ( namespace: string, parentKind: string, diff --git a/src/components/camel-list-page/CamelAppList.tsx b/src/components/camel-list-page/CamelAppList.tsx index f9df1f6..185e229 100644 --- a/src/components/camel-list-page/CamelAppList.tsx +++ b/src/components/camel-list-page/CamelAppList.tsx @@ -32,9 +32,7 @@ const CamelAppList: React.FC = ({ ns, showTitle = true }) => { }; const columns = useCamelAppColumns(filterCamelAppsNamespace(activeNamespace)); - const { CamelApps, loaded, error } = useCamelAppList( - filterCamelAppsNamespace(activeNamespace), - ); + const { CamelApps, loaded, error } = useCamelAppList(filterCamelAppsNamespace(activeNamespace)); const [staticData, filteredData, onFilterChange] = useListPageFilter(CamelApps); diff --git a/src/components/camel-list-page/CamelAppRow.tsx b/src/components/camel-list-page/CamelAppRow.tsx index d4cf131..87d77ec 100644 --- a/src/components/camel-list-page/CamelAppRow.tsx +++ b/src/components/camel-list-page/CamelAppRow.tsx @@ -5,19 +5,15 @@ import { ResourceLink, RowProps, TableData, - Timestamp, } from '@openshift-console/dynamic-plugin-sdk'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; import Status from '@openshift-console/dynamic-plugin-sdk/lib/app/components/status/Status'; -import { CamelAppStatusTitle, CamelAppStatusValue } from './CamelAppStatus'; +import { getCamelVersionAsString } from './camelAppVersion'; -const getKind = (obj) => obj.kind; +const getKind = (obj) => obj.metadata.ownerReferences[0].kind; const getNamespace = (obj) => obj.metadata?.namespace; -const getCamelVersion = (obj: K8sResourceKind): string => - obj.metadata.annotations?.['camel/camel-core-version']; - // Check for a modified mouse event. For example - Ctrl + Click const isModifiedEvent = (event: React.MouseEvent) => { return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); @@ -25,7 +21,7 @@ const isModifiedEvent = (event: React.MouseEvent) => { const CamelAppRow: React.FC> = ({ obj: camelInt, activeColumnIDs }) => { const { t } = useTranslation('plugin__camel-openshift-console-plugin'); - const camelVersion = getCamelVersion(camelInt); + console.log(camelInt); // Dead code ? const handleClick = (e) => { @@ -41,7 +37,7 @@ const CamelAppRow: React.FC> = ({ obj: camelInt, activ C > = ({ obj: camelInt, activ @@ -66,16 +62,15 @@ const CamelAppRow: React.FC> = ({ obj: camelInt, activ - + - - {camelVersion || {t('No camel version')}} + + - - + + {getCamelVersionAsString(camelInt, 'asc') || ( + {t('No camel version')} + )} ); diff --git a/src/components/camel-list-page/CamelAppStatus.tsx b/src/components/camel-list-page/CamelAppStatus.tsx deleted file mode 100644 index 09187e4..0000000 --- a/src/components/camel-list-page/CamelAppStatus.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { K8sResourceKind } from '@openshift-console/dynamic-plugin-sdk'; - -export const CamelAppStatuses = ['Succeeded', 'Failed', 'Unknown'] as const; - -export type CamelAppStatuses = typeof CamelAppStatuses[number]; - -export const CamelAppStatusValue = ( - camelInt: K8sResourceKind, -): CamelAppStatuses => { - if (camelInt.kind == 'Deployment' || camelInt.kind == 'DeploymentConfig') { - return camelInt.status.availableReplicas === camelInt.status.replicas ? 'Succeeded' : 'Failed'; - } - - if (camelInt.kind == 'CronJob') { - return camelInt.status.lastSuccessfulTime ? 'Succeeded' : 'Failed'; - } - - return 'Unknown'; -}; - -export const CamelAppStatusTitle = (camelInt: K8sResourceKind): string => { - if (camelInt.kind == 'Deployment' || camelInt.kind == 'DeploymentConfig') { - return camelInt.status.availableReplicas + ' of ' + camelInt.status.replicas + ' pods'; - } - - if (camelInt.kind == 'CronJob') { - return camelInt.status.lastSuccessfulTime; - } - - return 'Unknown'; -}; diff --git a/src/components/camel-list-page/camelAppVersion.ts b/src/components/camel-list-page/camelAppVersion.ts new file mode 100644 index 0000000..287cff7 --- /dev/null +++ b/src/components/camel-list-page/camelAppVersion.ts @@ -0,0 +1,37 @@ +import { K8sResourceKind } from '@openshift-console/dynamic-plugin-sdk'; + +export const sortResourceByCamelVersion = + (direction: string) => (a: K8sResourceKind, b: K8sResourceKind) => { + const { first, second } = + direction === 'asc' ? { first: a, second: b } : { first: b, second: a }; + + const firstValue = getCamelVersions(first, direction); + const secondValue = getCamelVersions(second, direction); + + return firstValue[0]?.localeCompare(secondValue[0]); + }; + +export const getCamelVersions = (camelInt: K8sResourceKind, direction: string): string[] => { + if (direction == 'asc') { + return camelInt.status?.pods + ?.map((pod) => pod.runtime.camelVersion) + .reduce((acc, item) => { + if (!acc.includes(item)) acc.push(item); + return acc; + }, []) + .sort(); + } else { + return camelInt.status?.pods + ?.map((pod) => pod.runtime.camelVersion) + .reduce((acc, item) => { + if (!acc.includes(item)) acc.push(item); + return acc; + }, []) + .sort() + .reverse(); + } +}; + +export const getCamelVersionAsString = (camelInt: K8sResourceKind, direction: string): string => { + return getCamelVersions(camelInt, direction).join(','); +}; diff --git a/src/components/camel-list-page/useCamelAppColumns.tsx b/src/components/camel-list-page/useCamelAppColumns.ts similarity index 58% rename from src/components/camel-list-page/useCamelAppColumns.tsx rename to src/components/camel-list-page/useCamelAppColumns.ts index 3471913..fb70556 100644 --- a/src/components/camel-list-page/useCamelAppColumns.tsx +++ b/src/components/camel-list-page/useCamelAppColumns.ts @@ -1,18 +1,7 @@ import { K8sResourceKind, TableColumn } from '@openshift-console/dynamic-plugin-sdk'; import { sortable } from '@patternfly/react-table'; import { useTranslation } from 'react-i18next'; -import { CamelAppStatusValue } from './CamelAppStatus'; - -export const sortResourceByStatus = - (direction: string) => (a: K8sResourceKind, b: K8sResourceKind) => { - const { first, second } = - direction === 'asc' ? { first: a, second: b } : { first: b, second: a }; - - const firstValue = CamelAppStatusValue(first); - const secondValue = CamelAppStatusValue(second); - - return firstValue?.localeCompare(secondValue); - }; +import { sortResourceByCamelVersion } from './camelAppVersion'; const useCamelAppColumns = (namespace): TableColumn[] => { const { t } = useTranslation('plugin__camel-openshift-console-plugin'); @@ -26,7 +15,7 @@ const useCamelAppColumns = (namespace): TableColumn[] => { { title: t('Kind'), id: 'kind', - sort: 'kind', + sort: 'metadata.ownerReferences[0].kind', transforms: [sortable], }, ...(!namespace @@ -42,19 +31,19 @@ const useCamelAppColumns = (namespace): TableColumn[] => { { title: t('Status'), id: 'status', - sort: (data, direction) => data?.sort(sortResourceByStatus(direction)), + sort: 'status.phase', transforms: [sortable], }, { - title: t('Camel'), - id: 'camel', - sort: "metadata.annotations.['camel/camel-core-version']", + title: t('Runtime Provider'), + id: 'runtime', + sort: 'status.pods[0].runtime.runtimeProvider', transforms: [sortable], }, { - title: t('Created'), - id: 'created', - sort: 'metadata.creationTimestamp', + title: t('Camel Version'), + id: 'camel', + sort: (data, direction) => data?.sort(sortResourceByCamelVersion(direction)), transforms: [sortable], }, ]; diff --git a/src/components/camel-list-page/useCamelAppList.ts b/src/components/camel-list-page/useCamelAppList.ts new file mode 100644 index 0000000..fee9fa0 --- /dev/null +++ b/src/components/camel-list-page/useCamelAppList.ts @@ -0,0 +1,24 @@ +import { useK8sWatchResources } from '@openshift-console/dynamic-plugin-sdk'; +import { CamelAppKind } from '../../types'; +import { camelAppGVK } from '../../const'; + +export const useCamelAppList = ( + namespace: string, +): { CamelApps: CamelAppKind[]; loaded: boolean; error: string } => { + const resources = useK8sWatchResources<{ + camelApps: CamelAppKind[]; + }>({ + camelApps: { + isList: true, + groupVersionKind: camelAppGVK, + namespaced: true, + namespace: namespace, + }, + }); + + return { + CamelApps: resources.camelApps.data, + loaded: resources.camelApps.loaded, + error: resources.camelApps.loadError, + }; +}; diff --git a/src/components/camel-list-page/useCamelAppList.tsx b/src/components/camel-list-page/useCamelAppList.tsx deleted file mode 100644 index 2f70b4e..0000000 --- a/src/components/camel-list-page/useCamelAppList.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { useK8sWatchResources } from '@openshift-console/dynamic-plugin-sdk'; -import { CamelAppKind } from '../../types'; -import { - cronJobGVK, - deploymentConfigGVK, - deploymentGVK, - METADATA_LABEL_SELECTOR_CAMEL_APP_KEY, - METADATA_LABEL_SELECTOR_CAMEL_APP_VALUE, -} from '../../const'; - -export const useCamelAppList = ( - namespace: string, -): { CamelApps: CamelAppKind[]; loaded: boolean; error: string } => { - const resources = useK8sWatchResources<{ - deployments: CamelAppKind[]; - deploymentConfigs: CamelAppKind[]; - cronJobs: CamelAppKind[]; - }>({ - deployments: { - isList: true, - groupVersionKind: deploymentGVK, - namespaced: true, - namespace: namespace, - selector: { - matchLabels: { - [METADATA_LABEL_SELECTOR_CAMEL_APP_KEY]: METADATA_LABEL_SELECTOR_CAMEL_APP_VALUE, - }, - }, - }, - deploymentConfigs: { - isList: true, - groupVersionKind: deploymentConfigGVK, - namespaced: true, - namespace: namespace, - selector: { - matchLabels: { - [METADATA_LABEL_SELECTOR_CAMEL_APP_KEY]: METADATA_LABEL_SELECTOR_CAMEL_APP_VALUE, - }, - }, - }, - cronJobs: { - isList: true, - groupVersionKind: cronJobGVK, - namespaced: true, - namespace: namespace, - selector: { - matchLabels: { - [METADATA_LABEL_SELECTOR_CAMEL_APP_KEY]: METADATA_LABEL_SELECTOR_CAMEL_APP_VALUE, - }, - }, - }, - }); - - const resourcesData = [ - ...resources.deploymentConfigs.data, - ...resources.deployments.data, - ...resources.cronJobs.data, - ]; - - const resourcesLoaded = - resources.deploymentConfigs.loaded && resources.deployments.loaded && resources.cronJobs.loaded; - - const resourcesLoadError = - resources.deploymentConfigs.loadError + - resources.deployments.loadError + - resources.cronJobs.loadError; - - return { CamelApps: resourcesData, loaded: resourcesLoaded, error: resourcesLoadError }; -}; diff --git a/src/const.ts b/src/const.ts index 87d63ba..7e95c67 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,3 +1,5 @@ +import { K8sGroupVersionKind } from '@openshift-console/dynamic-plugin-sdk'; + export const FLAG_OPENSHIFT_CAMEL = 'OPENSHIFT_CAMEL'; export const METADATA_LABEL_SELECTOR_CAMEL_APP_KEY = 'camel/integration-runtime'; @@ -15,54 +17,60 @@ export const METADATA_ANNOTATION_QUARKUS_BUILD_TIMESTAMP = 'app.quarkus.io/build export const METADATA_ANNOTATION_CAMEL_SPRINGBOOT_VERSION = 'camel/spring-boot-version'; export const METADATA_ANNOTATION_CAMEL_CSB_VERSION = 'camel/camel-spring-boot-version'; -export const deploymentGVK = { +export const camelAppGVK: K8sGroupVersionKind = { + group: 'camel.apache.org', + version: 'v1alpha1', + kind: 'App', +}; + +export const deploymentGVK: K8sGroupVersionKind = { group: 'apps', version: 'v1', kind: 'Deployment', }; -export const deploymentConfigGVK = { +export const deploymentConfigGVK: K8sGroupVersionKind = { group: 'apps.openshift.io', version: 'v1', kind: 'DeploymentConfig', }; -export const cronJobGVK = { +export const cronJobGVK: K8sGroupVersionKind = { group: 'batch', version: 'v1', kind: 'CronJob', }; -export const jobGVK = { +export const jobGVK: K8sGroupVersionKind = { group: 'batch', version: 'v1', kind: 'Job', }; -export const podGVK = { +export const podGVK: K8sGroupVersionKind = { group: '', version: 'v1', kind: 'Pod', }; -export const serviceGVK = { +export const serviceGVK: K8sGroupVersionKind = { group: '', version: 'v1', kind: 'Service', }; -export const routeGVK = { +export const routeGVK: K8sGroupVersionKind = { group: 'route.openshift.io', version: 'v1', kind: 'Route', }; -export const configMapGVK = { +export const configMapGVK: K8sGroupVersionKind = { group: '', version: 'v1', kind: 'ConfigMap', }; -export const secretGVK = { +export const secretGVK: K8sGroupVersionKind = { group: '', version: 'v1', kind: 'Secret', }; -export const persistentVolumeClaimGVK = { +export const persistentVolumeClaimGVK: K8sGroupVersionKind = { group: '', version: 'v1', kind: 'PersistentVolumeClaim', diff --git a/src/types.ts b/src/types.ts index 5da26ef..a5805b1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,7 +2,39 @@ import { K8sResourceKind } from '@openshift-console/dynamic-plugin-sdk'; // See how to enrich camelSpec export type CamelAppKind = K8sResourceKind & { - spec?: { - camelSpec: string; + status: { + pods: CamelAppStatusPod[]; + phase: string; }; }; + +export type CamelAppStatusPod = { + name: string; + internalIp: string; + observe: CamelAppObservability; + ready: boolean; + runtime: CamelAppRuntime; + status: string; +}; + +export type CamelAppObservability = { + healthEndpoint: string; + healthPort: number; + metricsEndpoint: string; + metricsPort: number; +}; + +export type CamelAppRuntime = { + camelVersion: string; + exchange: CamelAppExchange; + runtimeProvider: string; + runtimeVersion: string; + status: string; +}; + +export type CamelAppExchange = { + failed: number; + pending: number; + succeed: number; + total: number; +}; diff --git a/src/utils.ts b/src/utils.ts index 0746ae4..eb3964e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -7,8 +7,9 @@ import { METADATA_ANNOTATION_QUARKUS_BUILD_TIMESTAMP, } from './const'; import { CamelAppKind } from './types'; +import { K8sResourceKind } from '@openshift-console/dynamic-plugin-sdk'; -export function CamelAppGVK(kind: string) { +export function CamelAppOwnerGVK(kind: string) { switch (kind) { case deploymentConfigGVK.kind: return deploymentConfigGVK; @@ -45,13 +46,13 @@ export function getHealthEndpoints(framework: string): string[] { } // TODO use something else than Unknown -export function serviceMatchLabelValue(camelInt: CamelAppKind): string { - if (camelInt.kind == 'Deployment') { - return camelInt.spec.selector.matchLabels['app.kubernetes.io/name']; - } else if (camelInt.kind == 'DeploymentConfig') { - return camelInt.spec.selector['app.kubernetes.io/name']; - } else if (camelInt.kind == 'CronJob') { - return camelInt.metadata.labels['app.kubernetes.io/name']; +export function serviceMatchLabelValue(camelAppOwner: K8sResourceKind): string { + if (camelAppOwner.kind == 'Deployment') { + return camelAppOwner.spec.selector.matchLabels['app.kubernetes.io/name']; + } else if (camelAppOwner.kind == 'DeploymentConfig') { + return camelAppOwner.spec.selector['app.kubernetes.io/name']; + } else if (camelAppOwner.kind == 'CronJob') { + return camelAppOwner.metadata.labels['app.kubernetes.io/name']; } return 'Unknown'; } From 2977e7838e8c5cfb6252cb5ebcb1105d591cc8db Mon Sep 17 00:00:00 2001 From: Gaelle Fournier Date: Mon, 14 Apr 2025 18:14:16 +0200 Subject: [PATCH 2/4] Fix UI Camel App details --- ...lugin__camel-openshift-console-plugin.json | 4 + .../camel-app-details/CamelAppStatusPod.tsx | 113 ++++++++++++------ 2 files changed, 83 insertions(+), 34 deletions(-) diff --git a/locales/en/plugin__camel-openshift-console-plugin.json b/locales/en/plugin__camel-openshift-console-plugin.json index 69c8cd5..bee788f 100644 --- a/locales/en/plugin__camel-openshift-console-plugin.json +++ b/locales/en/plugin__camel-openshift-console-plugin.json @@ -7,6 +7,7 @@ "Details": "Details", "Endpoints": "Endpoints", "Exchange": "Exchange", + "failed": "failed", "Health": "Health", "Image": "Image", "Internal IP": "Internal IP", @@ -18,6 +19,7 @@ "No camel version": "No camel version", "No namespace": "No namespace", "No resources found": "No resources found", + "pending": "pending", "Pod port:": "Pod port:", "Resources": "Resources", "Runtime": "Runtime", @@ -25,6 +27,8 @@ "Runtime Version": "Runtime Version", "Service port:": "Service port:", "Status": "Status", + "succeed": "succeed", + "total": "total", "unknown host": "unknown host", "View Logs": "View Logs" } \ No newline at end of file diff --git a/src/components/camel-app-details/CamelAppStatusPod.tsx b/src/components/camel-app-details/CamelAppStatusPod.tsx index 0f313f4..6c02dbb 100644 --- a/src/components/camel-app-details/CamelAppStatusPod.tsx +++ b/src/components/camel-app-details/CamelAppStatusPod.tsx @@ -1,6 +1,15 @@ import * as React from 'react'; import { CamelAppKind, CamelAppStatusPod } from '../../types'; -import { Card, CardBody, TextContent } from '@patternfly/react-core'; +import { + Card, + CardBody, + TextList, + TextListItem, + DescriptionList, + DescriptionListGroup, + DescriptionListTerm, + DescriptionListDescription, +} from '@patternfly/react-core'; import { podGVK } from '../../const'; import { ResourceLink, ResourceStatus } from '@openshift-console/dynamic-plugin-sdk'; import { useTranslation } from 'react-i18next'; @@ -18,7 +27,7 @@ const CamelAppStatusPod: React.FC = ({ obj: camelInt, po <> - +

      = ({ obj: camelInt, po - - - {t('Internal IP')}: - {camelPod.internalIp} - -

      {t('Runtime')}:

      + + + + {t('Internal IP')}: + {camelPod.internalIp} + + + {t('Runtime')}: + + + + {t('Camel Version')}: + {camelPod.runtime.camelVersion} + + + {t('Runtime Provider')}: + {camelPod.runtime.runtimeProvider} + + + {t('Runtime Version')}: + {camelPod.runtime.runtimeVersion} + + + + + + {t('Exchange')}: + + + + {t('succeed')}: {' '} + {camelPod.runtime.exchange.succeed ? camelPod.runtime.exchange.succeed : 0} + + + {t('pending')}: {' '} + {camelPod.runtime.exchange.pending ? camelPod.runtime.exchange.pending : 0} + + + {t('failed')}: {' '} + {camelPod.runtime.exchange.failed ? camelPod.runtime.exchange.failed : 0} + + + {t('total')}: {' '} + {camelPod.runtime.exchange.total ? camelPod.runtime.exchange.total : 0} + + + + - - {t('Camel Version')}: - {camelPod.runtime.camelVersion} - - - {t('Runtime Provider')}: - {camelPod.runtime.runtimeProvider} - - - {t('Runtime Version')}: - {camelPod.runtime.runtimeVersion} - - - - {t('Exchange')}: TODO - - -

      {t('Endpoints')}:

      - - {t('Health')}: - {camelPod.observe.healthEndpoint}:{camelPod.observe.healthPort} - - - {t('Metrics')}: - {camelPod.observe.metricsEndpoint}:{camelPod.observe.metricsPort} - + + {t('Endpoints')}: + + + + {t('Health')}: + {camelPod.observe.healthEndpoint}:{camelPod.observe.healthPort} + + + {t('Metrics')}: + {camelPod.observe.metricsEndpoint}:{camelPod.observe.metricsPort} + + + + +
      From 636d5abc45bf10b61e55a11906a2f93df24ac384 Mon Sep 17 00:00:00 2001 From: Gaelle Fournier Date: Tue, 15 Apr 2025 10:11:17 +0200 Subject: [PATCH 3/4] Quick update to README --- README.md | 43 +++++++++++++++---------------------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 3d230e0..6a5ddab 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,31 @@ -# camel-openshift-console-plugin -Camel Openshift Console Plugin +# Camel Openshift Console Plugin + +> [!WARNING] +> The project is still a work in progress that has not been released yet. +> Unstability is to be expected. This project provides a [console plugin](https://github.com/openshift/console/tree/master/frontend/packages/console-dynamic-plugin-sdk) for [Camel](https://camel.apache.org). The project is created using [openshift console plugin template](https://github.com/openshift/console-plugin-template) +It requires: +* OpenShift 4.18 +* [Camel Dashboard Operator](https://github.com/squakez/camel-dashboard-operator) + # Local Development +Node.js 20+ and Yarn are required to build and run this locally. To run OpenShift console in a container, [podman 3.2.0+](https://podman.io) or [Docker](https://www.docker.com) is required. + For development you can login to an existing [OpenShift](https://www.redhat.com/en/technologies/cloud-computing/openshift) and run the console with the plugin included locally. -**Note**: Works well with [OpenShift Sandbox](https://developers.redhat.com/developer-sandbox). In one terminal window, run: -```sh -yarn install -yarn run start -``` +1. `yarn install` +2. `yarn run start` In another terminal window, run: -After running `oc login` (requires [oc](https://console.redhat.com/openshift/downloads) and an [OpenShift cluster](https://console.redhat.com/openshift/create)) - -```sh -yarn run start-console -``` -(requires [podman 3.2.0+](https://podman.io) or [Docker](https://www.docker.com)) - +1. `oc login` (requires [oc](https://console.redhat.com/openshift/downloads) and an [OpenShift cluster](https://console.redhat.com/openshift/create)) +2. `yarn run start-console` (requires [Docker](https://www.docker.com) or [podman 3.2.0+](https://podman.io)) This will run the OpenShift console in a container connected to the cluster you've logged into. The plugin HTTP server runs on port 9001 with CORS enabled. @@ -59,17 +60,3 @@ In the developer perpective the Camel section is now shown: [![The Camel Plugin Home](screenshots/home.png)](screenshots/home.png) -# Development notes - -The frontend is able to retrieve information from the console using the following APIs: - -## Kubernetes API /api/kubernetes -**Examples**: - - /api/kubernetes/apis/apps/v1/namespaces//deployments - - /api/kubernetes/apis/apps.openshift.io/v1/namespaces//deploymentconfigs - -## Prometheus API /api/prometheus - -**Examples**: -- /api/prometheus/api/v1/query_range - From c2d704311ecf4b804f6282e5e327de096bfb3be6 Mon Sep 17 00:00:00 2001 From: Gaelle Fournier Date: Tue, 15 Apr 2025 11:21:49 +0200 Subject: [PATCH 4/4] CI also send a latest image --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5168443..eec0e4a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -47,9 +47,11 @@ jobs: shell: bash run: | make CUSTOM_PLUGIN_IMAGE=quay.io/camel-tooling/camel-openshift-console-plugin image + make CUSTOM_PLUGIN_IMAGE=quay.io/camel-tooling/camel-openshift-console-plugin CUSTOM_PLUGIN_VERSION=latest image - name: Push plugin container if: github.ref == 'refs/heads/main' shell: bash run: | make CUSTOM_PLUGIN_IMAGE=quay.io/camel-tooling/camel-openshift-console-plugin push + make CUSTOM_PLUGIN_IMAGE=quay.io/camel-tooling/camel-openshift-console-plugin CUSTOM_PLUGIN_VERSION=latest push