This is an automated email from the ASF dual-hosted git repository.

marat pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git


The following commit(s) were added to refs/heads/main by this push:
     new 197b4f5d Fix #1362
197b4f5d is described below

commit 197b4f5d856dd665e75eb1d6dd3918c6dd3a153b
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Fri Aug 16 13:58:31 2024 -0400

    Fix #1362
---
 .../src/main/webui/src/project/ProjectPanel.tsx    |   2 +
 .../src/main/webui/src/topology/CustomGroup.tsx    |   6 +-
 .../src/main/webui/src/topology/TopologyApi.tsx    | 119 ++++++++++++---------
 .../src/main/webui/src/topology/TopologyTab.tsx    |   5 +-
 .../main/webui/src/topology/TopologyToolbar.tsx    |   7 ++
 .../src/main/webui/src/topology/topology.css       |   4 +
 karavan-core/src/core/api/TopologyUtils.ts         |  12 +--
 karavan-designer/src/topology/CustomGroup.tsx      |   6 +-
 karavan-designer/src/topology/TopologyApi.tsx      | 119 ++++++++++++---------
 karavan-designer/src/topology/TopologyTab.tsx      |   5 +-
 karavan-designer/src/topology/TopologyToolbar.tsx  |   7 ++
 karavan-designer/src/topology/topology.css         |   4 +
 karavan-space/src/topology/CustomGroup.tsx         |   6 +-
 karavan-space/src/topology/TopologyApi.tsx         | 119 ++++++++++++---------
 karavan-space/src/topology/TopologyTab.tsx         |   5 +-
 karavan-space/src/topology/TopologyToolbar.tsx     |   7 ++
 karavan-space/src/topology/topology.css            |   4 +
 17 files changed, 269 insertions(+), 168 deletions(-)

diff --git a/karavan-app/src/main/webui/src/project/ProjectPanel.tsx 
b/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
index f41d5710..9b46a45c 100644
--- a/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
@@ -45,6 +45,7 @@ export function ProjectPanel() {
     const [setFile] = useFileStore((s) => [s.setFile], shallow);
     const [files, setFiles] = useFilesStore((s) => [s.files, s.setFiles], 
shallow);
     const [setShowWizard] = useWizardStore((s) => [s.setShowWizard], shallow)
+    const isDev = config.environment === 'dev';
 
     useEffect(() => {
         onRefresh();
@@ -90,6 +91,7 @@ export function ProjectPanel() {
                                  setShowWizard(true)
                              }}
                              onSetFile={(fileName) => selectFile(fileName)}
+                             isDev={isDev}
                 />
                 <CreateIntegrationModal/>
                 <BeanWizard/>
diff --git a/karavan-app/src/main/webui/src/topology/CustomGroup.tsx 
b/karavan-app/src/main/webui/src/topology/CustomGroup.tsx
index 9e366c7c..595250a8 100644
--- a/karavan-app/src/main/webui/src/topology/CustomGroup.tsx
+++ b/karavan-app/src/main/webui/src/topology/CustomGroup.tsx
@@ -18,13 +18,11 @@
 import * as React from 'react';
 
 import './topology.css';
-import { DefaultGroup, observer} from '@patternfly/react-topology';
-
+import {DefaultGroup, observer} from '@patternfly/react-topology';
 
 const CustomGroup: React.FC<any> = observer(({ element, ...rest }) => {
-
     return (
-        <DefaultGroup element={element} {...rest}>
+        <DefaultGroup element={element} className={"topology-group"} {...rest}>
         </DefaultGroup>
     )
 })
diff --git a/karavan-app/src/main/webui/src/topology/TopologyApi.tsx 
b/karavan-app/src/main/webui/src/topology/TopologyApi.tsx
index 174ad6a6..a5342a37 100644
--- a/karavan-app/src/main/webui/src/topology/TopologyApi.tsx
+++ b/karavan-app/src/main/webui/src/topology/TopologyApi.tsx
@@ -33,13 +33,7 @@ import CustomNode from "./CustomNode";
 import {Integration, IntegrationFile} from 
"karavan-core/lib/model/IntegrationDefinition";
 import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
 import {TopologyUtils} from "karavan-core/lib/api/TopologyUtils";
-import {
-    TopologyIncomingNode,
-    TopologyOutgoingNode,
-    TopologyRestNode,
-    TopologyRouteConfigurationNode,
-    TopologyRouteNode
-} from "karavan-core/lib/model/TopologyDefinition";
+import {TopologyIncomingNode, TopologyOutgoingNode, TopologyRestNode, 
TopologyRouteConfigurationNode, TopologyRouteNode} from 
"karavan-core/lib/model/TopologyDefinition";
 import CustomEdge from "./CustomEdge";
 import CustomGroup from "./CustomGroup";
 
@@ -174,16 +168,18 @@ export function getExternalEdges(tons: 
TopologyOutgoingNode[], tins: TopologyInc
     tons.filter(ton => ton.type === 'external').forEach((ton, index) => {
         const uniqueUri = ton.uniqueUri;
         if (uniqueUri) {
-            const target = TopologyUtils.getNodeIdByUniqueUri(tins, uniqueUri);
-            const node: EdgeModel = {
-                id: 'external-' + ton.id + '-' + index,
-                type: 'edge',
-                source: ton.id,
-                target: target,
-                edgeStyle: EdgeStyle.dotted,
-                animationSpeed: EdgeAnimationSpeed.slow
-            }
-            if (target) result.push(node);
+            TopologyUtils.getNodeIdByUniqueUri(tins, uniqueUri).forEach(target 
=> {
+                const node: EdgeModel = {
+                    id: 'external-' + ton.id + '-' + target,
+                    type: 'edge',
+                    source: ton.id,
+                    target: target,
+                    edgeStyle: EdgeStyle.dotted,
+                    animationSpeed: EdgeAnimationSpeed.medium,
+                    data : {groupName: uniqueUri}
+                }
+                result.push(node);
+            });
         }
     });
     return result;
@@ -280,33 +276,6 @@ export function getModel(files: IntegrationFile[], 
grouping?: boolean): Model {
     const trcons = 
TopologyUtils.findTopologyRouteConfigurationOutgoingNodes(integrations);
 
     const nodes: NodeModel[] = [];
-    const groups: NodeModel[] = [];
-
-    const children1: string[] = [];
-    children1.push(...tins.filter(i => i.type === 'external').map(i => i.id));
-    children1.push(...trestns.map(i => i.id));
-    groups.push({
-        id: 'consumer-group',
-        children: children1,
-        type: 'group',
-        group: true,
-        label: 'Consumer group',
-        style: {
-            padding: 25,
-        }
-    })
-
-    const children2 = [...tons.filter(i => i.type === 'external').map(i => 
i.id)];
-    groups.push({
-        id: 'producer-group',
-        children: children2,
-        type: 'group',
-        group: true,
-        label: 'Producer group',
-        style: {
-            padding: 25,
-        }
-    })
 
     nodes.push(...getRestNodes(trestns))
     nodes.push(...getIncomingNodes(tins))
@@ -315,17 +284,69 @@ export function getModel(files: IntegrationFile[], 
grouping?: boolean): Model {
     nodes.push(...getOutgoingNodes(tons))
     nodes.push(...getOutgoingNodes(trcons))
 
-    if (grouping === true) {
-        nodes.push(...groups)
-    }
-
     const edges: EdgeModel[] = [];
     edges.push(...getIncomingEdges(tins));
     edges.push(...getOutgoingEdges(tons));
     edges.push(...getRestEdges(trestns, tins));
     edges.push(...getInternalEdges(tons, tins));
     edges.push(...getInternalEdges(trcons, tins));
-    // edges.push(...getExternalEdges(tons,tins));
+
+
+    // Groups
+    const groups: NodeModel[] = [];
+    if (grouping === true) {
+        const children1: string[] = [];
+        children1.push(...tins.filter(i => i.type === 'external').map(i => 
i.id));
+        children1.push(...trestns.map(i => i.id));
+        groups.push({
+            id: 'consumer-group',
+            children: children1,
+            type: 'group',
+            group: true,
+            label: 'Consumer group',
+            style: {
+                padding: 20,
+            }
+        })
+
+        const children2 = [...tons.filter(i => i.type === 'external').map(i => 
i.id)];
+        groups.push({
+            id: 'producer-group',
+            children: children2,
+            type: 'group',
+            group: true,
+            label: 'Producer group',
+            style: {
+                padding: 20,
+            },
+        })
+    } else {
+        const externalEdges = getExternalEdges(tons,tins);
+        edges.push(...externalEdges);
+        const uniqueGroups: Map<string, string[]> = new Map();
+
+        externalEdges.forEach(edge => {
+            const groupName =  edge.data.groupName;
+            const children = uniqueGroups.get(groupName) || [];
+            if (edge.source) children.push(edge.source)
+            if (edge.target) children.push(edge.target)
+            uniqueGroups.set(groupName, [...new Set(children)]);
+        });
+
+        uniqueGroups.forEach((children, groupName) => {
+            groups.push({
+                id: groupName + '-group',
+                children: children,
+                type: 'group',
+                group: true,
+                // label: edge.id + ' group',
+                style: {
+                    padding: 20,
+                }
+            })
+        })
+    }
+    nodes.push(...groups)
 
     return {nodes: nodes, edges: edges, graph: {id: 'g1', type: 'graph', 
layout: 'Dagre'}};
 }
diff --git a/karavan-app/src/main/webui/src/topology/TopologyTab.tsx 
b/karavan-app/src/main/webui/src/topology/TopologyTab.tsx
index cc189e29..d8408a99 100644
--- a/karavan-app/src/main/webui/src/topology/TopologyTab.tsx
+++ b/karavan-app/src/main/webui/src/topology/TopologyTab.tsx
@@ -45,6 +45,7 @@ interface Props {
     onClickAddREST: () => void
     onClickAddKamelet: () => void
     onClickAddBean: () => void
+    isDev?: boolean
 }
 
 export function TopologyTab(props: Props) {
@@ -146,7 +147,9 @@ export function TopologyTab(props: Props) {
                 ? <TopologyToolbar onClickAddRoute={props.onClickAddRoute}
                                    onClickAddBean={props.onClickAddBean}
                                    onClickAddKamelet={props.onClickAddKamelet}
-                                   onClickAddREST={props.onClickAddREST}/>
+                                   onClickAddREST={props.onClickAddREST}
+                                   isDev={props.isDev}
+                />
                 : undefined}
             sideBar={<TopologyPropertiesPanel onSetFile={props.onSetFile}/>}
             controlBar={
diff --git a/karavan-app/src/main/webui/src/topology/TopologyToolbar.tsx 
b/karavan-app/src/main/webui/src/topology/TopologyToolbar.tsx
index 0b23c542..2ac6eda9 100644
--- a/karavan-app/src/main/webui/src/topology/TopologyToolbar.tsx
+++ b/karavan-app/src/main/webui/src/topology/TopologyToolbar.tsx
@@ -24,17 +24,20 @@ import PlusIcon from 
"@patternfly/react-icons/dist/esm/icons/plus-icon";
 import {useTopologyStore} from "./TopologyStore";
 import {shallow} from "zustand/shallow";
 
+
 interface Props {
     onClickAddRoute: () => void
     onClickAddREST: () => void
     onClickAddKamelet: () => void
     onClickAddBean: () => void
+    isDev?: boolean
 }
 
 export function TopologyToolbar (props: Props) {
 
     const [showGroups, setShowGroups] = useTopologyStore((s) =>
         [s.showGroups, s.setShowGroups], shallow);
+    const isDev = props.isDev
 
     return (
         <div className='topology-toolbar'>
@@ -52,6 +55,7 @@ export function TopologyToolbar (props: Props) {
             <ToolbarItem align={{default:"alignRight"}}>
                 <Tooltip content={"Add Integration Route"} position={"bottom"}>
                     <Button className="dev-action-button" size="sm"
+                            isDisabled={!isDev}
                             variant={"primary"}
                             icon={<PlusIcon/>}
                             onClick={e => props.onClickAddRoute()}
@@ -63,6 +67,7 @@ export function TopologyToolbar (props: Props) {
             <ToolbarItem align={{default:"alignRight"}}>
                 <Tooltip content={"Add REST API"} position={"bottom"}>
                     <Button className="dev-action-button" size="sm"
+                            isDisabled={!isDev}
                             variant={"secondary"}
                             icon={<PlusIcon/>}
                             onClick={e => props.onClickAddREST()}
@@ -74,6 +79,7 @@ export function TopologyToolbar (props: Props) {
             <ToolbarItem align={{default:"alignRight"}}>
                 <Tooltip content={"Add Kamelet"} position={"bottom"}>
                     <Button className="dev-action-button" size="sm"
+                            isDisabled={!isDev}
                             variant={"secondary"}
                             icon={<PlusIcon/>}
                             onClick={e => props.onClickAddKamelet()}
@@ -85,6 +91,7 @@ export function TopologyToolbar (props: Props) {
             <ToolbarItem align={{default:"alignRight"}}>
                 <Tooltip content={"Add Bean"} position={"bottom"}>
                     <Button className="dev-action-button" size="sm"
+                            isDisabled={!isDev}
                             variant={"secondary"}
                             icon={<PlusIcon/>}
                             onClick={e => props.onClickAddBean()}
diff --git a/karavan-app/src/main/webui/src/topology/topology.css 
b/karavan-app/src/main/webui/src/topology/topology.css
index 1828bd1e..6edc633d 100644
--- a/karavan-app/src/main/webui/src/topology/topology.css
+++ b/karavan-app/src/main/webui/src/topology/topology.css
@@ -115,4 +115,8 @@
 
 .karavan .topology-panel .auto-start .text {
     fill: var(--pf-topology__node__label__text--Fill);
+}
+
+.karavan .topology-group .pf-topology__group__label {
+    display: none;
 }
\ No newline at end of file
diff --git a/karavan-core/src/core/api/TopologyUtils.ts 
b/karavan-core/src/core/api/TopologyUtils.ts
index 1788f740..bfe60e22 100644
--- a/karavan-core/src/core/api/TopologyUtils.ts
+++ b/karavan-core/src/core/api/TopologyUtils.ts
@@ -38,6 +38,7 @@ import { ComponentApi } from './ComponentApi';
 import { CamelDefinitionApiExt } from './CamelDefinitionApiExt';
 import { CamelDisplayUtil } from './CamelDisplayUtil';
 import { CamelUtil } from './CamelUtil';
+import { notDeepEqual } from 'node:assert';
 
 const outgoingDefinitions: string[] = ['ToDefinition', 'KameletDefinition', 
'ToDynamicDefinition', "PollEnrichDefinition", "EnrichDefinition", 
"WireTapDefinition", "SagaDefinition"];
 
@@ -342,12 +343,11 @@ export class TopologyUtils {
         }
     }
 
-    static getNodeIdByUniqueUri(tins: TopologyIncomingNode[], uniqueUri: 
string): string | undefined {
-        const node =  tins
-            .filter(r => r.uniqueUri === uniqueUri).at(0);
-        if (node) {
-            return node.id;
-        }
+    static getNodeIdByUniqueUri(tins: TopologyIncomingNode[], uniqueUri: 
string): string [] {
+        const result: string[] = [];
+        tins.filter(r => r.uniqueUri === uniqueUri)
+            ?.forEach(node => result.push(node.id))
+        return result;
     }
 
     static getRouteIdByUri(tins: TopologyIncomingNode[], uri: string): string 
| undefined {
diff --git a/karavan-designer/src/topology/CustomGroup.tsx 
b/karavan-designer/src/topology/CustomGroup.tsx
index 9e366c7c..595250a8 100644
--- a/karavan-designer/src/topology/CustomGroup.tsx
+++ b/karavan-designer/src/topology/CustomGroup.tsx
@@ -18,13 +18,11 @@
 import * as React from 'react';
 
 import './topology.css';
-import { DefaultGroup, observer} from '@patternfly/react-topology';
-
+import {DefaultGroup, observer} from '@patternfly/react-topology';
 
 const CustomGroup: React.FC<any> = observer(({ element, ...rest }) => {
-
     return (
-        <DefaultGroup element={element} {...rest}>
+        <DefaultGroup element={element} className={"topology-group"} {...rest}>
         </DefaultGroup>
     )
 })
diff --git a/karavan-designer/src/topology/TopologyApi.tsx 
b/karavan-designer/src/topology/TopologyApi.tsx
index 174ad6a6..a5342a37 100644
--- a/karavan-designer/src/topology/TopologyApi.tsx
+++ b/karavan-designer/src/topology/TopologyApi.tsx
@@ -33,13 +33,7 @@ import CustomNode from "./CustomNode";
 import {Integration, IntegrationFile} from 
"karavan-core/lib/model/IntegrationDefinition";
 import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
 import {TopologyUtils} from "karavan-core/lib/api/TopologyUtils";
-import {
-    TopologyIncomingNode,
-    TopologyOutgoingNode,
-    TopologyRestNode,
-    TopologyRouteConfigurationNode,
-    TopologyRouteNode
-} from "karavan-core/lib/model/TopologyDefinition";
+import {TopologyIncomingNode, TopologyOutgoingNode, TopologyRestNode, 
TopologyRouteConfigurationNode, TopologyRouteNode} from 
"karavan-core/lib/model/TopologyDefinition";
 import CustomEdge from "./CustomEdge";
 import CustomGroup from "./CustomGroup";
 
@@ -174,16 +168,18 @@ export function getExternalEdges(tons: 
TopologyOutgoingNode[], tins: TopologyInc
     tons.filter(ton => ton.type === 'external').forEach((ton, index) => {
         const uniqueUri = ton.uniqueUri;
         if (uniqueUri) {
-            const target = TopologyUtils.getNodeIdByUniqueUri(tins, uniqueUri);
-            const node: EdgeModel = {
-                id: 'external-' + ton.id + '-' + index,
-                type: 'edge',
-                source: ton.id,
-                target: target,
-                edgeStyle: EdgeStyle.dotted,
-                animationSpeed: EdgeAnimationSpeed.slow
-            }
-            if (target) result.push(node);
+            TopologyUtils.getNodeIdByUniqueUri(tins, uniqueUri).forEach(target 
=> {
+                const node: EdgeModel = {
+                    id: 'external-' + ton.id + '-' + target,
+                    type: 'edge',
+                    source: ton.id,
+                    target: target,
+                    edgeStyle: EdgeStyle.dotted,
+                    animationSpeed: EdgeAnimationSpeed.medium,
+                    data : {groupName: uniqueUri}
+                }
+                result.push(node);
+            });
         }
     });
     return result;
@@ -280,33 +276,6 @@ export function getModel(files: IntegrationFile[], 
grouping?: boolean): Model {
     const trcons = 
TopologyUtils.findTopologyRouteConfigurationOutgoingNodes(integrations);
 
     const nodes: NodeModel[] = [];
-    const groups: NodeModel[] = [];
-
-    const children1: string[] = [];
-    children1.push(...tins.filter(i => i.type === 'external').map(i => i.id));
-    children1.push(...trestns.map(i => i.id));
-    groups.push({
-        id: 'consumer-group',
-        children: children1,
-        type: 'group',
-        group: true,
-        label: 'Consumer group',
-        style: {
-            padding: 25,
-        }
-    })
-
-    const children2 = [...tons.filter(i => i.type === 'external').map(i => 
i.id)];
-    groups.push({
-        id: 'producer-group',
-        children: children2,
-        type: 'group',
-        group: true,
-        label: 'Producer group',
-        style: {
-            padding: 25,
-        }
-    })
 
     nodes.push(...getRestNodes(trestns))
     nodes.push(...getIncomingNodes(tins))
@@ -315,17 +284,69 @@ export function getModel(files: IntegrationFile[], 
grouping?: boolean): Model {
     nodes.push(...getOutgoingNodes(tons))
     nodes.push(...getOutgoingNodes(trcons))
 
-    if (grouping === true) {
-        nodes.push(...groups)
-    }
-
     const edges: EdgeModel[] = [];
     edges.push(...getIncomingEdges(tins));
     edges.push(...getOutgoingEdges(tons));
     edges.push(...getRestEdges(trestns, tins));
     edges.push(...getInternalEdges(tons, tins));
     edges.push(...getInternalEdges(trcons, tins));
-    // edges.push(...getExternalEdges(tons,tins));
+
+
+    // Groups
+    const groups: NodeModel[] = [];
+    if (grouping === true) {
+        const children1: string[] = [];
+        children1.push(...tins.filter(i => i.type === 'external').map(i => 
i.id));
+        children1.push(...trestns.map(i => i.id));
+        groups.push({
+            id: 'consumer-group',
+            children: children1,
+            type: 'group',
+            group: true,
+            label: 'Consumer group',
+            style: {
+                padding: 20,
+            }
+        })
+
+        const children2 = [...tons.filter(i => i.type === 'external').map(i => 
i.id)];
+        groups.push({
+            id: 'producer-group',
+            children: children2,
+            type: 'group',
+            group: true,
+            label: 'Producer group',
+            style: {
+                padding: 20,
+            },
+        })
+    } else {
+        const externalEdges = getExternalEdges(tons,tins);
+        edges.push(...externalEdges);
+        const uniqueGroups: Map<string, string[]> = new Map();
+
+        externalEdges.forEach(edge => {
+            const groupName =  edge.data.groupName;
+            const children = uniqueGroups.get(groupName) || [];
+            if (edge.source) children.push(edge.source)
+            if (edge.target) children.push(edge.target)
+            uniqueGroups.set(groupName, [...new Set(children)]);
+        });
+
+        uniqueGroups.forEach((children, groupName) => {
+            groups.push({
+                id: groupName + '-group',
+                children: children,
+                type: 'group',
+                group: true,
+                // label: edge.id + ' group',
+                style: {
+                    padding: 20,
+                }
+            })
+        })
+    }
+    nodes.push(...groups)
 
     return {nodes: nodes, edges: edges, graph: {id: 'g1', type: 'graph', 
layout: 'Dagre'}};
 }
diff --git a/karavan-designer/src/topology/TopologyTab.tsx 
b/karavan-designer/src/topology/TopologyTab.tsx
index cc189e29..d8408a99 100644
--- a/karavan-designer/src/topology/TopologyTab.tsx
+++ b/karavan-designer/src/topology/TopologyTab.tsx
@@ -45,6 +45,7 @@ interface Props {
     onClickAddREST: () => void
     onClickAddKamelet: () => void
     onClickAddBean: () => void
+    isDev?: boolean
 }
 
 export function TopologyTab(props: Props) {
@@ -146,7 +147,9 @@ export function TopologyTab(props: Props) {
                 ? <TopologyToolbar onClickAddRoute={props.onClickAddRoute}
                                    onClickAddBean={props.onClickAddBean}
                                    onClickAddKamelet={props.onClickAddKamelet}
-                                   onClickAddREST={props.onClickAddREST}/>
+                                   onClickAddREST={props.onClickAddREST}
+                                   isDev={props.isDev}
+                />
                 : undefined}
             sideBar={<TopologyPropertiesPanel onSetFile={props.onSetFile}/>}
             controlBar={
diff --git a/karavan-designer/src/topology/TopologyToolbar.tsx 
b/karavan-designer/src/topology/TopologyToolbar.tsx
index 0b23c542..2ac6eda9 100644
--- a/karavan-designer/src/topology/TopologyToolbar.tsx
+++ b/karavan-designer/src/topology/TopologyToolbar.tsx
@@ -24,17 +24,20 @@ import PlusIcon from 
"@patternfly/react-icons/dist/esm/icons/plus-icon";
 import {useTopologyStore} from "./TopologyStore";
 import {shallow} from "zustand/shallow";
 
+
 interface Props {
     onClickAddRoute: () => void
     onClickAddREST: () => void
     onClickAddKamelet: () => void
     onClickAddBean: () => void
+    isDev?: boolean
 }
 
 export function TopologyToolbar (props: Props) {
 
     const [showGroups, setShowGroups] = useTopologyStore((s) =>
         [s.showGroups, s.setShowGroups], shallow);
+    const isDev = props.isDev
 
     return (
         <div className='topology-toolbar'>
@@ -52,6 +55,7 @@ export function TopologyToolbar (props: Props) {
             <ToolbarItem align={{default:"alignRight"}}>
                 <Tooltip content={"Add Integration Route"} position={"bottom"}>
                     <Button className="dev-action-button" size="sm"
+                            isDisabled={!isDev}
                             variant={"primary"}
                             icon={<PlusIcon/>}
                             onClick={e => props.onClickAddRoute()}
@@ -63,6 +67,7 @@ export function TopologyToolbar (props: Props) {
             <ToolbarItem align={{default:"alignRight"}}>
                 <Tooltip content={"Add REST API"} position={"bottom"}>
                     <Button className="dev-action-button" size="sm"
+                            isDisabled={!isDev}
                             variant={"secondary"}
                             icon={<PlusIcon/>}
                             onClick={e => props.onClickAddREST()}
@@ -74,6 +79,7 @@ export function TopologyToolbar (props: Props) {
             <ToolbarItem align={{default:"alignRight"}}>
                 <Tooltip content={"Add Kamelet"} position={"bottom"}>
                     <Button className="dev-action-button" size="sm"
+                            isDisabled={!isDev}
                             variant={"secondary"}
                             icon={<PlusIcon/>}
                             onClick={e => props.onClickAddKamelet()}
@@ -85,6 +91,7 @@ export function TopologyToolbar (props: Props) {
             <ToolbarItem align={{default:"alignRight"}}>
                 <Tooltip content={"Add Bean"} position={"bottom"}>
                     <Button className="dev-action-button" size="sm"
+                            isDisabled={!isDev}
                             variant={"secondary"}
                             icon={<PlusIcon/>}
                             onClick={e => props.onClickAddBean()}
diff --git a/karavan-designer/src/topology/topology.css 
b/karavan-designer/src/topology/topology.css
index 1828bd1e..6edc633d 100644
--- a/karavan-designer/src/topology/topology.css
+++ b/karavan-designer/src/topology/topology.css
@@ -115,4 +115,8 @@
 
 .karavan .topology-panel .auto-start .text {
     fill: var(--pf-topology__node__label__text--Fill);
+}
+
+.karavan .topology-group .pf-topology__group__label {
+    display: none;
 }
\ No newline at end of file
diff --git a/karavan-space/src/topology/CustomGroup.tsx 
b/karavan-space/src/topology/CustomGroup.tsx
index 9e366c7c..595250a8 100644
--- a/karavan-space/src/topology/CustomGroup.tsx
+++ b/karavan-space/src/topology/CustomGroup.tsx
@@ -18,13 +18,11 @@
 import * as React from 'react';
 
 import './topology.css';
-import { DefaultGroup, observer} from '@patternfly/react-topology';
-
+import {DefaultGroup, observer} from '@patternfly/react-topology';
 
 const CustomGroup: React.FC<any> = observer(({ element, ...rest }) => {
-
     return (
-        <DefaultGroup element={element} {...rest}>
+        <DefaultGroup element={element} className={"topology-group"} {...rest}>
         </DefaultGroup>
     )
 })
diff --git a/karavan-space/src/topology/TopologyApi.tsx 
b/karavan-space/src/topology/TopologyApi.tsx
index 174ad6a6..a5342a37 100644
--- a/karavan-space/src/topology/TopologyApi.tsx
+++ b/karavan-space/src/topology/TopologyApi.tsx
@@ -33,13 +33,7 @@ import CustomNode from "./CustomNode";
 import {Integration, IntegrationFile} from 
"karavan-core/lib/model/IntegrationDefinition";
 import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
 import {TopologyUtils} from "karavan-core/lib/api/TopologyUtils";
-import {
-    TopologyIncomingNode,
-    TopologyOutgoingNode,
-    TopologyRestNode,
-    TopologyRouteConfigurationNode,
-    TopologyRouteNode
-} from "karavan-core/lib/model/TopologyDefinition";
+import {TopologyIncomingNode, TopologyOutgoingNode, TopologyRestNode, 
TopologyRouteConfigurationNode, TopologyRouteNode} from 
"karavan-core/lib/model/TopologyDefinition";
 import CustomEdge from "./CustomEdge";
 import CustomGroup from "./CustomGroup";
 
@@ -174,16 +168,18 @@ export function getExternalEdges(tons: 
TopologyOutgoingNode[], tins: TopologyInc
     tons.filter(ton => ton.type === 'external').forEach((ton, index) => {
         const uniqueUri = ton.uniqueUri;
         if (uniqueUri) {
-            const target = TopologyUtils.getNodeIdByUniqueUri(tins, uniqueUri);
-            const node: EdgeModel = {
-                id: 'external-' + ton.id + '-' + index,
-                type: 'edge',
-                source: ton.id,
-                target: target,
-                edgeStyle: EdgeStyle.dotted,
-                animationSpeed: EdgeAnimationSpeed.slow
-            }
-            if (target) result.push(node);
+            TopologyUtils.getNodeIdByUniqueUri(tins, uniqueUri).forEach(target 
=> {
+                const node: EdgeModel = {
+                    id: 'external-' + ton.id + '-' + target,
+                    type: 'edge',
+                    source: ton.id,
+                    target: target,
+                    edgeStyle: EdgeStyle.dotted,
+                    animationSpeed: EdgeAnimationSpeed.medium,
+                    data : {groupName: uniqueUri}
+                }
+                result.push(node);
+            });
         }
     });
     return result;
@@ -280,33 +276,6 @@ export function getModel(files: IntegrationFile[], 
grouping?: boolean): Model {
     const trcons = 
TopologyUtils.findTopologyRouteConfigurationOutgoingNodes(integrations);
 
     const nodes: NodeModel[] = [];
-    const groups: NodeModel[] = [];
-
-    const children1: string[] = [];
-    children1.push(...tins.filter(i => i.type === 'external').map(i => i.id));
-    children1.push(...trestns.map(i => i.id));
-    groups.push({
-        id: 'consumer-group',
-        children: children1,
-        type: 'group',
-        group: true,
-        label: 'Consumer group',
-        style: {
-            padding: 25,
-        }
-    })
-
-    const children2 = [...tons.filter(i => i.type === 'external').map(i => 
i.id)];
-    groups.push({
-        id: 'producer-group',
-        children: children2,
-        type: 'group',
-        group: true,
-        label: 'Producer group',
-        style: {
-            padding: 25,
-        }
-    })
 
     nodes.push(...getRestNodes(trestns))
     nodes.push(...getIncomingNodes(tins))
@@ -315,17 +284,69 @@ export function getModel(files: IntegrationFile[], 
grouping?: boolean): Model {
     nodes.push(...getOutgoingNodes(tons))
     nodes.push(...getOutgoingNodes(trcons))
 
-    if (grouping === true) {
-        nodes.push(...groups)
-    }
-
     const edges: EdgeModel[] = [];
     edges.push(...getIncomingEdges(tins));
     edges.push(...getOutgoingEdges(tons));
     edges.push(...getRestEdges(trestns, tins));
     edges.push(...getInternalEdges(tons, tins));
     edges.push(...getInternalEdges(trcons, tins));
-    // edges.push(...getExternalEdges(tons,tins));
+
+
+    // Groups
+    const groups: NodeModel[] = [];
+    if (grouping === true) {
+        const children1: string[] = [];
+        children1.push(...tins.filter(i => i.type === 'external').map(i => 
i.id));
+        children1.push(...trestns.map(i => i.id));
+        groups.push({
+            id: 'consumer-group',
+            children: children1,
+            type: 'group',
+            group: true,
+            label: 'Consumer group',
+            style: {
+                padding: 20,
+            }
+        })
+
+        const children2 = [...tons.filter(i => i.type === 'external').map(i => 
i.id)];
+        groups.push({
+            id: 'producer-group',
+            children: children2,
+            type: 'group',
+            group: true,
+            label: 'Producer group',
+            style: {
+                padding: 20,
+            },
+        })
+    } else {
+        const externalEdges = getExternalEdges(tons,tins);
+        edges.push(...externalEdges);
+        const uniqueGroups: Map<string, string[]> = new Map();
+
+        externalEdges.forEach(edge => {
+            const groupName =  edge.data.groupName;
+            const children = uniqueGroups.get(groupName) || [];
+            if (edge.source) children.push(edge.source)
+            if (edge.target) children.push(edge.target)
+            uniqueGroups.set(groupName, [...new Set(children)]);
+        });
+
+        uniqueGroups.forEach((children, groupName) => {
+            groups.push({
+                id: groupName + '-group',
+                children: children,
+                type: 'group',
+                group: true,
+                // label: edge.id + ' group',
+                style: {
+                    padding: 20,
+                }
+            })
+        })
+    }
+    nodes.push(...groups)
 
     return {nodes: nodes, edges: edges, graph: {id: 'g1', type: 'graph', 
layout: 'Dagre'}};
 }
diff --git a/karavan-space/src/topology/TopologyTab.tsx 
b/karavan-space/src/topology/TopologyTab.tsx
index cc189e29..d8408a99 100644
--- a/karavan-space/src/topology/TopologyTab.tsx
+++ b/karavan-space/src/topology/TopologyTab.tsx
@@ -45,6 +45,7 @@ interface Props {
     onClickAddREST: () => void
     onClickAddKamelet: () => void
     onClickAddBean: () => void
+    isDev?: boolean
 }
 
 export function TopologyTab(props: Props) {
@@ -146,7 +147,9 @@ export function TopologyTab(props: Props) {
                 ? <TopologyToolbar onClickAddRoute={props.onClickAddRoute}
                                    onClickAddBean={props.onClickAddBean}
                                    onClickAddKamelet={props.onClickAddKamelet}
-                                   onClickAddREST={props.onClickAddREST}/>
+                                   onClickAddREST={props.onClickAddREST}
+                                   isDev={props.isDev}
+                />
                 : undefined}
             sideBar={<TopologyPropertiesPanel onSetFile={props.onSetFile}/>}
             controlBar={
diff --git a/karavan-space/src/topology/TopologyToolbar.tsx 
b/karavan-space/src/topology/TopologyToolbar.tsx
index 0b23c542..2ac6eda9 100644
--- a/karavan-space/src/topology/TopologyToolbar.tsx
+++ b/karavan-space/src/topology/TopologyToolbar.tsx
@@ -24,17 +24,20 @@ import PlusIcon from 
"@patternfly/react-icons/dist/esm/icons/plus-icon";
 import {useTopologyStore} from "./TopologyStore";
 import {shallow} from "zustand/shallow";
 
+
 interface Props {
     onClickAddRoute: () => void
     onClickAddREST: () => void
     onClickAddKamelet: () => void
     onClickAddBean: () => void
+    isDev?: boolean
 }
 
 export function TopologyToolbar (props: Props) {
 
     const [showGroups, setShowGroups] = useTopologyStore((s) =>
         [s.showGroups, s.setShowGroups], shallow);
+    const isDev = props.isDev
 
     return (
         <div className='topology-toolbar'>
@@ -52,6 +55,7 @@ export function TopologyToolbar (props: Props) {
             <ToolbarItem align={{default:"alignRight"}}>
                 <Tooltip content={"Add Integration Route"} position={"bottom"}>
                     <Button className="dev-action-button" size="sm"
+                            isDisabled={!isDev}
                             variant={"primary"}
                             icon={<PlusIcon/>}
                             onClick={e => props.onClickAddRoute()}
@@ -63,6 +67,7 @@ export function TopologyToolbar (props: Props) {
             <ToolbarItem align={{default:"alignRight"}}>
                 <Tooltip content={"Add REST API"} position={"bottom"}>
                     <Button className="dev-action-button" size="sm"
+                            isDisabled={!isDev}
                             variant={"secondary"}
                             icon={<PlusIcon/>}
                             onClick={e => props.onClickAddREST()}
@@ -74,6 +79,7 @@ export function TopologyToolbar (props: Props) {
             <ToolbarItem align={{default:"alignRight"}}>
                 <Tooltip content={"Add Kamelet"} position={"bottom"}>
                     <Button className="dev-action-button" size="sm"
+                            isDisabled={!isDev}
                             variant={"secondary"}
                             icon={<PlusIcon/>}
                             onClick={e => props.onClickAddKamelet()}
@@ -85,6 +91,7 @@ export function TopologyToolbar (props: Props) {
             <ToolbarItem align={{default:"alignRight"}}>
                 <Tooltip content={"Add Bean"} position={"bottom"}>
                     <Button className="dev-action-button" size="sm"
+                            isDisabled={!isDev}
                             variant={"secondary"}
                             icon={<PlusIcon/>}
                             onClick={e => props.onClickAddBean()}
diff --git a/karavan-space/src/topology/topology.css 
b/karavan-space/src/topology/topology.css
index 1828bd1e..6edc633d 100644
--- a/karavan-space/src/topology/topology.css
+++ b/karavan-space/src/topology/topology.css
@@ -115,4 +115,8 @@
 
 .karavan .topology-panel .auto-start .text {
     fill: var(--pf-topology__node__label__text--Fill);
+}
+
+.karavan .topology-group .pf-topology__group__label {
+    display: none;
 }
\ No newline at end of file


Reply via email to