You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ma...@apache.org on 2023/10/01 20:49:34 UTC

[camel-karavan] branch main updated: Kamelets Definition UI #315

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 df8c086d Kamelets Definition UI #315
df8c086d is described below

commit df8c086ddfc902704fa9323d4a7fec28d3b22854
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Sun Oct 1 16:49:23 2023 -0400

    Kamelets Definition UI #315
---
 karavan-core/src/core/api/CamelDefinitionYaml.ts   |  21 +-
 .../src/core/model/IntegrationDefinition.ts        |  46 +++--
 karavan-core/test/isIntegration.spec.ts            |  12 +-
 karavan-core/test/kamelet.spec.ts                  |  22 +-
 karavan-core/test/topology.spec.ts                 |   1 -
 .../public/example/postgresql-source.kamelet.yaml  | 114 +++++++++++
 karavan-designer/src/App.tsx                       |   6 +-
 karavan-designer/src/designer/KaravanDesigner.tsx  |  17 +-
 .../src/designer/beans/BeanProperties.tsx          |   7 +-
 .../designer/kamelet/KameletAnnotationsPanel.tsx   | 133 +++++++++++++
 .../kamelet/KameletDefinitionPropertyCard.tsx      | 221 +++++++++++++++++++++
 .../designer/kamelet/KameletDefinitionsPanel.tsx   | 108 ++++++++++
 .../src/designer/kamelet/KameletDesigner.tsx       | 132 ++++++++++++
 .../KameletProperties.tsx}                         |   9 +-
 karavan-designer/src/designer/kamelet/kamelet.css  |  47 +++++
 .../src/designer/route/DslProperties.tsx           |   3 +-
 .../src/designer/utils/IntegrationHeader.tsx       |  19 +-
 .../src/designer/utils/KaravanIcons.tsx            |  45 +++--
 18 files changed, 885 insertions(+), 78 deletions(-)

diff --git a/karavan-core/src/core/api/CamelDefinitionYaml.ts b/karavan-core/src/core/api/CamelDefinitionYaml.ts
index 44feabdd..aa6f425b 100644
--- a/karavan-core/src/core/api/CamelDefinitionYaml.ts
+++ b/karavan-core/src/core/api/CamelDefinitionYaml.ts
@@ -237,25 +237,24 @@ export class CamelDefinitionYaml {
         return integration;
     };
 
-    static yamlIsIntegration = (text: string): boolean => {
+    static yamlIsIntegration = (text: string): 'crd' | 'plain' | 'kamelet' | 'none' => {
         try {
             const fromYaml: any = yaml.load(text);
             const camelized: any = CamelUtil.camelizeObject(fromYaml);
-            if (
-                camelized?.apiVersion &&
-                camelized.apiVersion.startsWith('camel.apache.org') &&
-                camelized.kind &&
-                camelized.kind === 'Integration'
-            ) {
-                return true;
+            if (camelized?.apiVersion && camelized.apiVersion.startsWith('camel.apache.org') && camelized.kind) {
+                if (camelized.kind === 'Integration') {
+                    return 'crd';
+                } else if (camelized.kind === 'Kamelet') {
+                    return 'kamelet';
+                }
             } else if (Array.isArray(camelized)) {
-                return true;
+                return 'plain';
             } else {
-                return false;
+                return 'none';
             }
         } catch (e) {
-            return false;
         }
+        return 'none';
     };
     static flowsToCamelElements = (flows: any[]): any[] => {
         const rules: { [key: string]: (flow: any) => any } = {
diff --git a/karavan-core/src/core/model/IntegrationDefinition.ts b/karavan-core/src/core/model/IntegrationDefinition.ts
index c6c53d46..696e2da1 100644
--- a/karavan-core/src/core/model/IntegrationDefinition.ts
+++ b/karavan-core/src/core/model/IntegrationDefinition.ts
@@ -17,7 +17,7 @@
 import { v4 as uuidv4 } from 'uuid';
 import { RegistryBeanDefinition } from './CamelDefinition';
 
-export class KameletDefinitionProperty {
+export class DefinitionProperty {
     title: string = '';
     description: string = '';
     type: 'string' | 'integer' | 'boolean' = 'string';
@@ -25,27 +25,26 @@ export class KameletDefinitionProperty {
     example?: any;
     format?: string;
     "x-descriptors"?: string[];
-    properties: any = {};
 
-    public constructor(init?: Partial<KameletDefinition>) {
+    public constructor(init?: Partial<DefinitionProperty>) {
         Object.assign(this, init);
     }
 }
 
-export class KameletDefinition {
+export class Definition {
     title: string = '';
     description: string = '';
     required: string[] = [];
     type: string = 'object';
     properties: any = {};
 
-    public constructor(init?: Partial<KameletDefinition>) {
+    public constructor(init?: Partial<Definition>) {
         Object.assign(this, init);
     }
 }
 
 export class Spec {
-    definition?: KameletDefinition;
+    definition?: Definition;
     types?: any;
     flows?: any[] = [];
     template?: any;
@@ -56,30 +55,31 @@ export class Spec {
     }
 }
 
-export class MetadataLabel {
-    "camel.apache.org/kamelet.type": "sink" | "source" | "action"
+export class MetadataLabels {
+    "camel.apache.org/kamelet.type": "sink" | "source" | "action" = 'source'
 
-    public constructor(init?: Partial<MetadataLabel>) {
+    public constructor(init?: Partial<MetadataLabels>) {
         Object.assign(this, init);
     }
 }
 
-export class MetadataAnnotation {
-    "camel.apache.org/catalog.version"?: string;
-    "camel.apache.org/kamelet.icon"?: string;
-    "camel.apache.org/provider"?: string;
-    "camel.apache.org/kamelet.group"?: string;
-    "camel.apache.org/kamelet.namespace"?: string;
+export class MetadataAnnotations {
+    "camel.apache.org/kamelet.support.level:": string = 'Preview';
+    "camel.apache.org/catalog.version": string = '';
+    "camel.apache.org/kamelet.icon": string = '';
+    "camel.apache.org/provider": string = '';
+    "camel.apache.org/kamelet.group": string = '';
+    "camel.apache.org/kamelet.namespace": string = '';
 
-    public constructor(init?: Partial<MetadataAnnotation>) {
+    public constructor(init?: Partial<MetadataAnnotations>) {
         Object.assign(this, init);
     }
 }
 
 export class Metadata {
     name: string = '';
-    annotations?: MetadataAnnotation;
-    labels?: MetadataLabel[];
+    annotations?: MetadataAnnotations;
+    labels?: MetadataLabels;
 
     public constructor(init?: Partial<Metadata>) {
         Object.assign(this, init);
@@ -98,10 +98,18 @@ export class Integration {
     }
 
     static createNew(name?: string, type: 'crd' | 'plain' | 'kamelet' = 'plain'): Integration {
-        return new Integration({ type: type,
+        const i = new Integration({ type: type,
             metadata: new Metadata({ name: name }),
             kind : type === 'kamelet' ? 'Kamelet' : 'Integration',
             spec: new Spec({ flows: [] }) });
+
+        if (type === 'kamelet') {
+            i.metadata.annotations = new MetadataAnnotations({})
+            i.spec.definition = new Definition({})
+            i.spec.types = {}
+        }
+
+        return i;
     }
 }
 
diff --git a/karavan-core/test/isIntegration.spec.ts b/karavan-core/test/isIntegration.spec.ts
index a14ed140..61f3c218 100644
--- a/karavan-core/test/isIntegration.spec.ts
+++ b/karavan-core/test/isIntegration.spec.ts
@@ -27,13 +27,19 @@ describe('Is Integration', () => {
     it('Is not integration', () => {
         const yaml = fs.readFileSync('test/is-not-integration.yaml',{encoding:'utf8', flag:'r'});
         const i = CamelDefinitionYaml.yamlIsIntegration(yaml);
-        expect(i).to.equal(false);
+        expect(i).to.equal('none');
     });
 
-    it('Is integration', () => {
+    it('Is integration CRD', () => {
         const yaml = fs.readFileSync('test/integration1.yaml',{encoding:'utf8', flag:'r'});
         const i = CamelDefinitionYaml.yamlIsIntegration(yaml);
-        expect(i).to.equal(true);
+        expect(i).to.equal('crd');
+    });
+
+    it('Is integration plain', () => {
+        const yaml = fs.readFileSync('test/plain1.yaml',{encoding:'utf8', flag:'r'});
+        const i = CamelDefinitionYaml.yamlIsIntegration(yaml);
+        expect(i).to.equal('plain');
     });
 
 });
\ No newline at end of file
diff --git a/karavan-core/test/kamelet.spec.ts b/karavan-core/test/kamelet.spec.ts
index 691bf48e..65e94987 100644
--- a/karavan-core/test/kamelet.spec.ts
+++ b/karavan-core/test/kamelet.spec.ts
@@ -14,29 +14,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {expect} from 'chai';
 import * as fs from 'fs';
 import 'mocha';
 import {CamelDefinitionYaml} from "../src/core/api/CamelDefinitionYaml";
-import {
-    ChoiceDefinition,
-    ExpressionDefinition,
-    FilterDefinition, FromDefinition, LogDefinition,
-    ToDefinition,
-    WhenDefinition,
-} from '../src/core/model/CamelDefinition';
+import { FromDefinition, LogDefinition, } from '../src/core/model/CamelDefinition';
 import { RouteDefinition} from "../src/core/model/CamelDefinition";
-import { Beans, Integration, MetadataAnnotation } from '../src/core/model/IntegrationDefinition';
-import { KameletMetadata } from '../lib/model/KameletModels';
-import { RegistryBeanDefinition } from '../lib/model/CamelDefinition';
+import { Beans, Definition, Integration } from '../src/core/model/IntegrationDefinition';
+import { RegistryBeanDefinition } from '../src/core/model/CamelDefinition';
+import { MetadataAnnotations } from '../src/core/model/IntegrationDefinition';
 
 describe('Kamelet <=> YAML', () => {
 
     it('Yaml to Kamelet', () => {
         const yaml = fs.readFileSync('test/postgresql-source.kamelet.yaml',{encoding:'utf8', flag:'r'});
         const i = CamelDefinitionYaml.yamlToIntegration("postgresql-source.kamelet.yaml", yaml);
-        console.log(i)
-        console.log(CamelDefinitionYaml.integrationToYaml(i))
+        // console.log(i)
     });
 
     it('Kamelet to YAML with beans', () => {
@@ -50,7 +42,7 @@ describe('Kamelet <=> YAML', () => {
         b.beans.push(new RegistryBeanDefinition({name: "beanDS1", type: "String.class"}));
         b.beans.push(new RegistryBeanDefinition({name: "beanDS2", type: "String.class"}));
         i.spec.flows?.push(b);
-        const a = new MetadataAnnotation({"camel.apache.org/kamelet.group" : "hello world"})
+        const a = new MetadataAnnotations({"camel.apache.org/kamelet.group" : "hello world"})
         i.metadata.annotations = a
     });
 
@@ -60,6 +52,8 @@ describe('Kamelet <=> YAML', () => {
         const flow1 = new FromDefinition({uri: "direct1"});
         flow1.steps?.push(new LogDefinition({logName: 'log11', message: "hello11"}));
         i.spec.flows?.push(new RouteDefinition({from:flow1}));
+
+        console.log(CamelDefinitionYaml.integrationToYaml(i))
     });
 
 
diff --git a/karavan-core/test/topology.spec.ts b/karavan-core/test/topology.spec.ts
index e76ead2b..db689f21 100644
--- a/karavan-core/test/topology.spec.ts
+++ b/karavan-core/test/topology.spec.ts
@@ -30,7 +30,6 @@ describe('Topology functions', () => {
         const tin = TopologyUtils.findTopologyIncomingNodes([i1, i2]);
         const trn = TopologyUtils.findTopologyRestNodes([i1, i2]);
         const ton = TopologyUtils.findTopologyOutgoingNodes([i1, i2]);
-        console.log(tin)
     });
 
 });
diff --git a/karavan-designer/public/example/postgresql-source.kamelet.yaml b/karavan-designer/public/example/postgresql-source.kamelet.yaml
new file mode 100644
index 00000000..2fcd541e
--- /dev/null
+++ b/karavan-designer/public/example/postgresql-source.kamelet.yaml
@@ -0,0 +1,114 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+# ---------------------------------------------------------------------------
+apiVersion: camel.apache.org/v1
+kind: Kamelet
+metadata:
+  name: postgresql-source
+  annotations:
+    camel.apache.org/kamelet.support.level: "Stable"
+    camel.apache.org/catalog.version: "4.1.0-SNAPSHOT"
+    camel.apache.org/kamelet.icon: "data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+UG9zdGdyZVNRTCBpY29uPC90aXRsZT48cGF0aCBkPSJNMTcuMTI4IDBhMTAuMTM0IDEwLjEzNCAwIDAgMC0yLjc1NS40MDNsLS4wNjMuMDJBMTAuOTIyIDEwLjkyMiAwIDAgMCAxMi42LjI1OEMxMS40MjIuMjM4IDEwLjQxLjUyNCA5LjU5NCAxIDguNzkuNzIxIDcuMTIyLjI0IDUuMzY0LjMzNiA0LjE0LjQwMyAyLjgwNC43NzUgMS44MTQgMS44Mi44MjcgMi44NjUuMzA1IDQuNDgyLjQxNSA2LjY4MmMuMDMuNjA3LjIwMyAxLjU5Ny [...]
+    camel.apache.org/provider: "Apache Software Foundation"
+    camel.apache.org/kamelet.group: "SQL"
+    camel.apache.org/kamelet.namespace: "Database"
+  labels:
+    camel.apache.org/kamelet.type: "source"
+spec:
+  definition:
+    title: "PostgreSQL Source"
+    description: |-
+      Query data from a PostgreSQL Database.
+    required:
+      - serverName
+      - username
+      - password
+      - query
+      - databaseName
+    type: object
+    properties:
+      serverName:
+        title: Server Name
+        description: The server name for the data source.
+        type: string
+        example: localhost
+      serverPort:
+        title: Server Port
+        description: The server port for the data source.
+        type: string
+        default: 5432
+      username:
+        title: Username
+        description: The username to access a secured PostgreSQL Database.
+        type: string
+        x-descriptors:
+          - urn:camel:group:credentials
+      password:
+        title: Password
+        description: The password to access a secured PostgreSQL Database.
+        type: string
+        format: password
+        x-descriptors:
+          - urn:alm:descriptor:com.tectonic.ui:password
+          - urn:camel:group:credentials
+      query:
+        title: Query
+        description: The query to execute against the PostgreSQL Database.
+        type: string
+        example: 'INSERT INTO accounts (username,city) VALUES (:#username,:#city)'
+      databaseName:
+        title: Database Name
+        description: The name of the PostgreSQL Database.
+        type: string
+      consumedQuery:
+        title: Consumed Query
+        description: A query to run on a tuple consumed.
+        type: string
+        example: 'DELETE FROM accounts where user_id = :#user_id'
+      delay:
+        title: Delay
+        description: The number of milliseconds before the next poll
+        type: integer
+        default: 500
+  types:
+    out:
+      mediaType: application/json
+  dependencies:
+    - "camel:jackson"
+    - "camel:kamelet"
+    - "camel:sql"
+    - "mvn:org.postgresql:postgresql:42.6.0"
+    - "mvn:org.apache.commons:commons-dbcp2:2.10.0"
+  template:
+    beans:
+      - name: dsBean
+        type: "#class:org.apache.commons.dbcp2.BasicDataSource"
+        properties:
+          username: '{{username}}'
+          password: '{{password}}'
+          url: 'jdbc:postgresql://{{serverName}}:{{serverPort}}/{{databaseName}}'
+          driverClassName: 'org.postgresql.Driver'
+    from:
+      uri: "sql:{{query}}"
+      parameters:
+        dataSource: "#bean:{{dsBean}}"
+        onConsume: "{{?consumedQuery}}"
+        delay: "{{delay}}"
+      steps:
+        - marshal:
+            json:
+              library: Jackson
+        - to: "kamelet:sink"
\ No newline at end of file
diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx
index b71267ba..dcea7fc1 100644
--- a/karavan-designer/src/App.tsx
+++ b/karavan-designer/src/App.tsx
@@ -69,7 +69,8 @@ class App extends React.Component<Props, State> {
             fetch("components/components.json"),
             fetch("snippets/org.apache.camel.AggregationStrategy"),
             fetch("snippets/org.apache.camel.Processor"),
-            fetch("example/demo.camel.yaml")
+            // fetch("example/demo.camel.yaml")
+            fetch("example/postgresql-source.kamelet.yaml")
             // fetch("components/supported-components.json"),
         ]).then(responses =>
             Promise.all(responses.map(response => response.text()))
@@ -88,7 +89,8 @@ class App extends React.Component<Props, State> {
             TemplateApi.saveTemplate("org.apache.camel.Processor", data[3]);
 
             if (data[4]) {
-                this.setState({yaml: data[4], name: "demo.camel.yaml"})
+                // this.setState({yaml: data[4], name: "demo.camel.yaml"})
+                this.setState({yaml: data[4], name: "postgresql-source.kamelet.yaml"})
             }
 
             if (data[5]) {
diff --git a/karavan-designer/src/designer/KaravanDesigner.tsx b/karavan-designer/src/designer/KaravanDesigner.tsx
index cb58a377..316a71be 100644
--- a/karavan-designer/src/designer/KaravanDesigner.tsx
+++ b/karavan-designer/src/designer/KaravanDesigner.tsx
@@ -42,6 +42,7 @@ import {RestDesigner} from "./rest/RestDesigner";
 import {BeansDesigner} from "./beans/BeansDesigner";
 import {CodeEditor} from "./editor/CodeEditor";
 import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon';
+import {KameletDesigner} from "./kamelet/KameletDesigner";
 
 interface Props {
     onSave: (filename: string, yaml: string, propertyOnly: boolean) => void
@@ -71,7 +72,9 @@ export function KaravanDesigner(props: Props) {
         InfrastructureAPI.setOnSave(props.onSave);
 
         setSelectedStep(undefined);
-        setIntegration(makeIntegration(props.yaml, props.filename), false);
+        const i = makeIntegration(props.yaml, props.filename);
+        setIntegration(i, false);
+        setTab(i.kind === 'Kamelet' ? 'kamelet' : 'routes')
         reset();
         setDark(props.dark);
         setHideLogDSL(props.hideLogDSL === true);
@@ -84,8 +87,10 @@ export function KaravanDesigner(props: Props) {
 
     function makeIntegration(yaml: string, filename: string): Integration {
         try {
-            if (yaml && CamelDefinitionYaml.yamlIsIntegration(yaml)) {
-                return CamelDefinitionYaml.yamlToIntegration(props.filename, props.yaml)
+            const type = CamelDefinitionYaml.yamlIsIntegration(yaml);
+            if (yaml && type !== 'none') {
+                const i = CamelDefinitionYaml.yamlToIntegration(props.filename, props.yaml)
+                return i;
             } else {
                 return Integration.createNew(filename, 'plain');
             }
@@ -126,6 +131,8 @@ export function KaravanDesigner(props: Props) {
         )
     }
 
+    const isKamelet = integration.type === 'kamelet';
+
     return (
         <PageSection variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light} className="page"
                      isFilled padding={{default: 'noPadding'}}>
@@ -137,8 +144,9 @@ export function KaravanDesigner(props: Props) {
                           setSelectedStep(undefined);
                       }}
                       style={{width: "100%"}}>
+                    {isKamelet && <Tab eventKey='kamelet' title={getTab("Definitions", "Kamelet Definitions", "kamelet")}></Tab>}
                     <Tab eventKey='routes' title={getTab("Routes", "Integration flows", "routes")}></Tab>
-                    <Tab eventKey='rest' title={getTab("REST", "REST services", "rest")}></Tab>
+                    {!isKamelet && <Tab eventKey='rest' title={getTab("REST", "REST services", "rest")}></Tab>}
                     <Tab eventKey='beans' title={getTab("Beans", "Beans Configuration", "beans")}></Tab>
                     {props.showCodeTab && <Tab eventKey='code' title={getTab("YAML", "YAML Code", "code", true)}></Tab>}
                 </Tabs>
@@ -156,6 +164,7 @@ export function KaravanDesigner(props: Props) {
                 {/*    />*/}
                 {/*</Tooltip>}*/}
             </div>
+            {tab === 'kamelet' && <KameletDesigner/>}
             {tab === 'routes' && <RouteDesigner/>}
             {tab === 'rest' && <RestDesigner/>}
             {tab === 'beans' && <BeansDesigner/>}
diff --git a/karavan-designer/src/designer/beans/BeanProperties.tsx b/karavan-designer/src/designer/beans/BeanProperties.tsx
index 15b8f086..c27ea28c 100644
--- a/karavan-designer/src/designer/beans/BeanProperties.tsx
+++ b/karavan-designer/src/designer/beans/BeanProperties.tsx
@@ -194,7 +194,8 @@ export function BeanProperties (props: Props) {
                         const icon = InfrastructureAPI.infrastructure === 'kubernetes' ? <KubernetesIcon/> : <DockerIcon/>
                         return (
                             <div key={"key-" + i} className="bean-property">
-                                <TextInput placeholder="Bean Field Name" className="text-field" isRequired type="text" id="key" name="key" value={key}
+                                <TextInput placeholder="Bean Field Name" className="text-field" isRequired type="text" id={"key-" + i}
+                                           name={"key-" + i} value={key}
                                             onChange={(_, beanFieldName) => {
                                                 propertyChanged(i, beanFieldName, value, showPassword)
                                             }}/>
@@ -211,8 +212,8 @@ export function BeanProperties (props: Props) {
                                             type={isSecret && !showPassword ? "password" : "text"}
                                             className="text-field"
                                             isRequired
-                                            id="value"
-                                            name="value"
+                                            id={"value-" + i}
+                                            name={"value-" + i}
                                             value={value}
                                             onChange={(_, value) => {
                                                 propertyChanged(i, key, value, showPassword)
diff --git a/karavan-designer/src/designer/kamelet/KameletAnnotationsPanel.tsx b/karavan-designer/src/designer/kamelet/KameletAnnotationsPanel.tsx
new file mode 100644
index 00000000..3c997173
--- /dev/null
+++ b/karavan-designer/src/designer/kamelet/KameletAnnotationsPanel.tsx
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+import React from 'react';
+import {
+    capitalize,
+    Card,
+    CardBody,
+    CardTitle,
+    Form,
+    FormGroup, Grid, GridItem,
+    InputGroup,
+    InputGroupItem,
+    InputGroupText,
+    TextInput, ToggleGroup, ToggleGroupItem,
+} from '@patternfly/react-core';
+import '../karavan.css';
+import './kamelet.css';
+import {useIntegrationStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
+
+const PREFIX = 'camel.apache.org/';
+
+export function KameletAnnotationsPanel() {
+
+    const [integration, setIntegration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow)
+
+    function setValue(key: string, value: string) {
+        if (key && value && value.length > 0) {
+            (integration.metadata.annotations as any)[PREFIX + key] = value;
+            setIntegration(integration, true);
+        }
+    }
+
+    function getValue(key: string): string {
+        const annotations = integration.metadata.annotations;
+        if (annotations) {
+            return (annotations as any)[PREFIX + key];
+        } else {
+            return '';
+        }
+    }
+
+    function getElement(key: string, label: string, span: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12) {
+        return (
+            <GridItem span={span}>
+                <FormGroup label={label} fieldId={key} isRequired>
+                    <InputGroup>
+                        <InputGroupItem isFill>
+                            <TextInput className="text-field" type="text" id={key} name={key}
+                                       onChange={(_, value) => setValue(key, value)}
+                                       value={getValue(key)}/>
+                        </InputGroupItem>
+                    </InputGroup>
+                </FormGroup>
+            </GridItem>
+        )
+    }
+
+    function getElementToggleGroup(key: string, label: string, values: string[], span: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12) {
+        return (
+            <GridItem span={span}>
+                <FormGroup label={label} fieldId={key} isRequired>
+                    {/* eslint-disable-next-line react/jsx-no-undef */}
+                    <ToggleGroup aria-label={key}>
+                        {values.map(value =>
+                            <ToggleGroupItem
+                                key={value}
+                                text={capitalize(value)}
+                                buttonId="toggle-group-single-1"
+                                isSelected={getValue(key) === value}
+                                onChange={(_, selected) => setValue(key, value) }
+                            />
+                        )}
+                    </ToggleGroup>
+                </FormGroup>
+            </GridItem>
+        )
+    }
+
+    function getElementIcon(key: string, label: string, span: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12) {
+        return (
+            <GridItem span={span}>
+                <FormGroup label={label} fieldId={key} isRequired>
+                    <InputGroup>
+                        <InputGroupText id="username">
+                            <svg className="icon">
+                                <image href={getValue(key)} className="icon"/>
+                            </svg>
+                        </InputGroupText>
+                        <InputGroupItem isFill>
+                            <TextInput className="text-field" type="text" id={key} name={key}
+                                       onChange={(_, value) => setValue(key, value)}
+                                       value={getValue(key)}/>
+                        </InputGroupItem>
+                    </InputGroup>
+                </FormGroup>
+            </GridItem>
+        )
+    }
+
+    return (
+        <Card isCompact ouiaId="AnnotationsCard">
+            <CardTitle>Annotations</CardTitle>
+            <CardBody>
+                <Form>
+                    <Grid hasGutter md={6}>
+                        {getElementToggleGroup('kamelet.support.level', 'Support Level', ['Preview', 'Stable'], 2)}
+                        {getElementIcon('kamelet.icon', 'Icon', 10)}
+                        {getElement('catalog.version', 'Version', 3)}
+                        {getElement('provider', 'Provider', 3)}
+                        {getElement('kamelet.group', 'Group', 3)}
+                        {getElement('kamelet.namespace', 'Namespace', 3)}
+                    </Grid>
+                </Form>
+            </CardBody>
+        </Card>
+    )
+}
diff --git a/karavan-designer/src/designer/kamelet/KameletDefinitionPropertyCard.tsx b/karavan-designer/src/designer/kamelet/KameletDefinitionPropertyCard.tsx
new file mode 100644
index 00000000..a478c101
--- /dev/null
+++ b/karavan-designer/src/designer/kamelet/KameletDefinitionPropertyCard.tsx
@@ -0,0 +1,221 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+import React, {useState} from 'react';
+import {
+    Button,
+    Card,
+    CardBody,
+    CardTitle, Flex, FlexItem,
+    FormGroup, FormSelect, FormSelectOption,
+    Grid,
+    GridItem, Label, Modal, Switch,
+    TextInput,
+} from '@patternfly/react-core';
+import '../karavan.css';
+import './kamelet.css';
+import {useIntegrationStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
+import {DefinitionProperty} from "karavan-core/lib/model/IntegrationDefinition";
+
+interface Props {
+    index: number
+    propKey: string
+    property: DefinitionProperty
+}
+
+export function KameletDefinitionPropertyCard(props: Props) {
+
+    const [integration, setIntegration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow)
+    const [showDeleteConfirmation, setShowDeleteConfirmation] = useState<boolean>(false);
+
+    const key = props.propKey;
+    const required = integration.spec.definition?.required || [];
+
+    function setPropertyValue(field: string, value: string) {
+        if (integration.spec.definition?.properties) {
+            (integration.spec.definition?.properties as any)[key][field] = value;
+            setIntegration(integration, true);
+        }
+    }
+
+    function getPropertyValue(field: string) {
+        const properties: any = integration.spec.definition?.properties;
+        if (properties) {
+            return properties[key][field];
+        }
+        return undefined;
+    }
+
+
+    function getPropertyField(field: string, label: string, isRequired: boolean, span: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12) {
+        return (
+            <GridItem span={span}>
+                <FormGroup label={label} fieldId={key + field} isRequired={isRequired}>
+                    <TextInput className="text-field" type="text" id={key + field} name={key + field}
+                               onChange={(_, value) => setPropertyValue(field, value)}
+                               value={getPropertyValue(field)}/>
+                </FormGroup>
+            </GridItem>
+        )
+    }
+
+    function getPropertyTypeField(field: string, label: string, isRequired: boolean, span: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12) {
+        return (
+            <GridItem span={span}>
+                <FormGroup label={label} fieldId={key + field} isRequired={isRequired}>
+                    <FormSelect
+                        value={getPropertyValue(field)}
+                        onChange={(_, value) => setPropertyValue(field, value)}
+                        aria-label="FormSelect Input"
+                        ouiaId="BasicFormSelect"
+                    >
+                        {['string', 'number', 'boolean'].map((option, index) => (
+                            <FormSelectOption key={option} isDisabled={false} id={key + field} name={key + field} value={option} label={option} />
+                        ))}
+                    </FormSelect>
+                </FormGroup>
+            </GridItem>
+        )
+    }
+
+    function renameProperty(newKey: string) {
+        const oldKey = key;
+        newKey = newKey.replace(/[\W_]+/g,'');
+        if (oldKey !== newKey) {
+            if (integration.spec.definition?.properties) {
+                const o = (integration.spec.definition?.properties as any)
+                const newObject: any = {};
+                Object.keys(o).forEach(k => {
+                    if (k !== oldKey) {
+                        newObject[k] = o[k];
+                    } else {
+                        newObject[newKey] = o[k];
+                    }
+                })
+                integration.spec.definition.properties = newObject;
+                setIntegration(integration, true);
+            }
+        }
+    }
+
+    function deleteProperty() {
+        if (integration.spec.definition?.properties) {
+            delete integration.spec.definition.properties[key];
+            setIntegration(integration, true);
+        }
+    }
+
+    function getDeleteConfirmation() {
+        return (<Modal
+            className="modal-delete"
+            title="Confirmation"
+            isOpen={showDeleteConfirmation}
+            onClose={() => setShowDeleteConfirmation(false)}
+            actions={[
+                <Button key="confirm" variant="primary" onClick={e => deleteProperty()}>Delete</Button>,
+                <Button key="cancel" variant="link"
+                        onClick={e => setShowDeleteConfirmation(false)}>Cancel</Button>
+            ]}
+            onEscapePress={e => setShowDeleteConfirmation(false)}>
+            <div>
+                Delete {key} property?
+            </div>
+        </Modal>)
+    }
+
+    function setRequired(checked: boolean) {
+        console.log(required, key)
+        const newRequired = [...required];
+        if (checked && !newRequired.includes(key)) {
+            newRequired.push(key);
+        } else if (!checked && newRequired.includes(key)) {
+            const index = newRequired.findIndex(r => r === key);
+            newRequired.splice(index, 1);
+        }
+        // console.log(newRequired)
+        if (integration.spec.definition?.required) {
+            integration.spec.definition.required.length = 0;
+            integration.spec.definition.required.push(...newRequired)
+        }
+        setIntegration(integration, true);
+    }
+
+    function getTitle() {
+        return (
+            <Flex>
+                <FlexItem>
+                    <Label
+                        color="blue"
+                        onClose={() => {
+                            setShowDeleteConfirmation(true);
+                        }}
+                        closeBtnAriaLabel="Delete Property"
+                        onEditCancel={(_, previousText) => {
+                        }}
+                        onEditComplete={(event, newText) => {
+                            if (event.type === 'mousedown') {
+                                renameProperty(newText)
+                            } else if (event.type === 'keydown' && (event as KeyboardEvent).key === 'Tab') {
+                                renameProperty(newText)
+                            } else if (event.type === 'keydown' && (event as KeyboardEvent).key === 'Enter') {
+                                renameProperty(newText)
+                            } else {
+                                renameProperty(key)
+                            }
+                        }}
+                        isEditable
+                        editableProps={{
+                            'aria-label': `Editable property with text ${key}`,
+                            id: 'editable-property'
+                        }}
+                    >
+                        {key}
+                    </Label>
+                </FlexItem>
+                <FlexItem align={{default: "alignRight"}}>
+                    <Switch
+                        label={"Required"}
+                        isChecked={required.includes(key)}
+                        onChange={(_, checked) => setRequired(checked)}
+                        isReversed
+                    />
+                </FlexItem>
+            </Flex>
+        )
+    }
+
+
+    return (
+        <Card isClickable isCompact isFlat ouiaId="PropertyCard" className="property-card">
+            <CardTitle>
+                {getTitle()}
+            </CardTitle>
+            <CardBody>
+                <Grid hasGutter>
+                    {getPropertyField("title", "Title", true, 3)}
+                    {getPropertyField("description", "Description", true, 6)}
+                    {getPropertyTypeField("type", "Type", true, 3)}
+                    {getPropertyField("format", "Format", false, 3)}
+                    {getPropertyField("example", "Example", false, 6)}
+                    {getPropertyField("default", "Default", false, 3)}
+                    {/*{getPropertyField("x-descriptors", "Descriptors", false, 12)}*/}
+                </Grid>
+            </CardBody>
+            {getDeleteConfirmation()}
+        </Card>
+    )
+}
diff --git a/karavan-designer/src/designer/kamelet/KameletDefinitionsPanel.tsx b/karavan-designer/src/designer/kamelet/KameletDefinitionsPanel.tsx
new file mode 100644
index 00000000..4c638ee3
--- /dev/null
+++ b/karavan-designer/src/designer/kamelet/KameletDefinitionsPanel.tsx
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+import React from 'react';
+import {
+    Button,
+    Card,
+    CardBody,
+    CardTitle, Flex, FlexItem,
+    Form,
+    FormGroup,
+    Grid,
+    GridItem,
+    TextInput,
+} from '@patternfly/react-core';
+import '../karavan.css';
+import './kamelet.css';
+import {useIntegrationStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
+import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon";
+import {KameletDefinitionPropertyCard} from "./KameletDefinitionPropertyCard";
+
+export function KameletDefinitionsPanel() {
+
+    const [integration, setIntegration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow)
+
+    function setValue(key: string, value: string) {
+        if (key && value && value.length > 0) {
+            (integration.spec.definition as any)[key] = value;
+            setIntegration(integration, true);
+        }
+    }
+
+    function getValue(key: string): string {
+        const annotations = integration.spec.definition;
+        if (annotations) {
+            return (annotations as any)[key];
+        } else {
+            return '';
+        }
+    }
+
+    function getElement(key: string, label: string, span: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12) {
+        return (
+            <GridItem span={span}>
+                <FormGroup label={label} fieldId={key} isRequired>
+                    <TextInput className="text-field" type="text" id={key} name={key}
+                               onChange={(_, value) => setValue(key, value)}
+                               value={getValue(key)}/>
+                </FormGroup>
+            </GridItem>
+        )
+    }
+
+    const properties = integration.spec.definition?.properties ? Object.keys(integration.spec.definition?.properties) : [];
+    return (
+        <>
+            <Card isCompact ouiaId="DefinitionsCard">
+                <CardTitle>Definitions</CardTitle>
+                <CardBody>
+                    <Form>
+                        <Grid hasGutter>
+                            {getElement('title', 'Title', 4)}
+                            {getElement('description', 'Description', 6)}
+                            {getElement('type', 'Type', 2)}
+                        </Grid>
+                    </Form>
+                </CardBody>
+            </Card>
+            <div style={{height: "20px"}}/>
+            <Card isCompact ouiaId="PropertiesCard">
+                <CardTitle>
+                    <Flex>
+                        <FlexItem>Properties</FlexItem>
+                        <FlexItem align={{default: "alignRight"}}>
+                            <Button variant={"link"} icon={<AddIcon/>}>Add property</Button>
+                        </FlexItem>
+                    </Flex>
+                </CardTitle>
+                <CardBody>
+                    <Form>
+                        {properties.map((key: string, index: number) => {
+                            const property = (integration.spec.definition?.properties as any)[key];
+                            return <KameletDefinitionPropertyCard key={key}
+                                                                  index={index}
+                                                                  propKey={key}
+                                                                  property={property}/>
+                        })}
+                    </Form>
+                </CardBody>
+            </Card>
+        </>
+
+    )
+}
diff --git a/karavan-designer/src/designer/kamelet/KameletDesigner.tsx b/karavan-designer/src/designer/kamelet/KameletDesigner.tsx
new file mode 100644
index 00000000..3c0b5fa8
--- /dev/null
+++ b/karavan-designer/src/designer/kamelet/KameletDesigner.tsx
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+import React from 'react';
+import {
+    Button, Card, CardBody, CardFooter, CardTitle, Divider,
+    Drawer,
+    DrawerContent,
+    DrawerContentBody,
+    DrawerPanelContent, Flex, FlexItem, Gallery, GalleryItem,
+    Modal,
+    PageSection,
+} from '@patternfly/react-core';
+import '../karavan.css';
+import './kamelet.css';
+import {RegistryBeanDefinition} from "karavan-core/lib/model/CamelDefinition";
+import {CamelUi} from "../utils/CamelUi";
+import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
+import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
+import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
+import {useDesignerStore, useIntegrationStore} from "../KaravanStore";
+import {shallow} from "zustand/shallow";
+import {BeanProperties} from "../beans/BeanProperties";
+import {BeanCard} from "../beans/BeanCard";
+import {KameletAnnotationsPanel} from "./KameletAnnotationsPanel";
+import {KameletDefinitionsPanel} from "./KameletDefinitionsPanel";
+import {KameletProperties} from "./KameletProperties";
+
+export function KameletDesigner() {
+
+    const [integration, setIntegration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow)
+    const [dark, selectedStep, showDeleteConfirmation, setShowDeleteConfirmation, setSelectedStep] = useDesignerStore((s) =>
+        [s.dark, s.selectedStep, s.showDeleteConfirmation, s.setShowDeleteConfirmation, s.setSelectedStep], shallow)
+
+
+    function onShowDeleteConfirmation(bean: RegistryBeanDefinition) {
+        setSelectedStep(bean);
+        setShowDeleteConfirmation(true);
+    }
+
+    function deleteBean() {
+        const i = CamelDefinitionApiExt.deleteBeanFromIntegration(integration, selectedStep);
+        setIntegration(i, false);
+        setShowDeleteConfirmation(false);
+        setSelectedStep(undefined);
+    }
+
+    function changeBean(bean: RegistryBeanDefinition) {
+        const clone = CamelUtil.cloneIntegration(integration);
+        const i = CamelDefinitionApiExt.addBeanToIntegration(clone, bean);
+        setIntegration(i, false);
+        setSelectedStep(bean);
+    }
+
+    function getDeleteConfirmation() {
+        return (<Modal
+            className="modal-delete"
+            title="Confirmation"
+            isOpen={showDeleteConfirmation}
+            onClose={() => setShowDeleteConfirmation(false)}
+            actions={[
+                <Button key="confirm" variant="primary" onClick={e => deleteBean()}>Delete</Button>,
+                <Button key="cancel" variant="link"
+                        onClick={e => setShowDeleteConfirmation(false)}>Cancel</Button>
+            ]}
+            onEscapePress={e => setShowDeleteConfirmation(false)}>
+            <div>
+                Delete bean from integration?
+            </div>
+        </Modal>)
+    }
+
+    function selectBean(bean?: RegistryBeanDefinition) {
+        setSelectedStep(bean);
+    }
+
+    function unselectBean(evt: React.MouseEvent<HTMLDivElement, MouseEvent>) {
+        if ((evt.target as any).dataset.click === 'BEANS') {
+            evt.stopPropagation()
+            setSelectedStep(undefined);
+        }
+    };
+
+    function createBean() {
+        changeBean(new RegistryBeanDefinition());
+    }
+
+    function getPropertiesPanel() {
+        return (
+            <DrawerPanelContent isResizable
+                                hasNoBorder
+                                defaultSize={'400px'}
+                                maxSize={'800px'}
+                                minSize={'400px'}>
+                <KameletProperties integration={integration}
+                                dark={dark}
+                                onChange={changeBean}
+                                onClone={changeBean}/>
+            </DrawerPanelContent>
+        )
+    }
+
+    return (
+        <PageSection className="kamelet-designer" isFilled padding={{default: 'noPadding'}}>
+            <Drawer isExpanded isInline>
+                <DrawerContent panelContent={getPropertiesPanel()}>
+                    <DrawerContentBody>
+                        <PageSection className="main">
+                            <KameletAnnotationsPanel/>
+                            <div style={{height:"20px"}}/>
+                            <KameletDefinitionsPanel/>
+                        </PageSection>
+                    </DrawerContentBody>
+                </DrawerContent>
+            </Drawer>
+            {getDeleteConfirmation()}
+        </PageSection>
+    )
+}
diff --git a/karavan-designer/src/designer/beans/BeanProperties.tsx b/karavan-designer/src/designer/kamelet/KameletProperties.tsx
similarity index 97%
copy from karavan-designer/src/designer/beans/BeanProperties.tsx
copy to karavan-designer/src/designer/kamelet/KameletProperties.tsx
index 15b8f086..93c696fa 100644
--- a/karavan-designer/src/designer/beans/BeanProperties.tsx
+++ b/karavan-designer/src/designer/kamelet/KameletProperties.tsx
@@ -51,7 +51,7 @@ interface Props {
     onClone: (bean: RegistryBeanDefinition) => void
 }
 
-export function BeanProperties (props: Props) {
+export function KameletProperties (props: Props) {
 
     const [selectedStep] = useDesignerStore((s) => [s.selectedStep], shallow);
     const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false);
@@ -194,7 +194,8 @@ export function BeanProperties (props: Props) {
                         const icon = InfrastructureAPI.infrastructure === 'kubernetes' ? <KubernetesIcon/> : <DockerIcon/>
                         return (
                             <div key={"key-" + i} className="bean-property">
-                                <TextInput placeholder="Bean Field Name" className="text-field" isRequired type="text" id="key" name="key" value={key}
+                                <TextInput placeholder="Bean Field Name" className="text-field" isRequired type="text" id={"key-" + i}
+                                           name={"key-" + i} value={key}
                                             onChange={(_, beanFieldName) => {
                                                 propertyChanged(i, beanFieldName, value, showPassword)
                                             }}/>
@@ -211,8 +212,8 @@ export function BeanProperties (props: Props) {
                                             type={isSecret && !showPassword ? "password" : "text"}
                                             className="text-field"
                                             isRequired
-                                            id="value"
-                                            name="value"
+                                            id={"value-" + i}
+                                            name={"value-" + i}
                                             value={value}
                                             onChange={(_, value) => {
                                                 propertyChanged(i, key, value, showPassword)
diff --git a/karavan-designer/src/designer/kamelet/kamelet.css b/karavan-designer/src/designer/kamelet/kamelet.css
new file mode 100644
index 00000000..bbdf28a9
--- /dev/null
+++ b/karavan-designer/src/designer/kamelet/kamelet.css
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+.karavan .kamelet-designer {
+    display: block;
+    height: 100vh;
+    width: 100%;
+    overflow-y: auto;
+    padding-bottom: 106px;
+}
+
+.karavan .kamelet-designer .main {
+    background-color: var(--pf-v5-global--BackgroundColor--light-300);
+}
+.karavan .kamelet-designer .icon {
+    height: 20px;
+    width: 20px;
+    border: none;
+    -webkit-user-select: none;
+    -o-user-select: none;
+    user-select: none;
+}
+
+.karavan .kamelet-designer .properties {
+    padding: 10px 10px 10px 10px;
+    background: transparent;
+    width: 100%;
+    height: 100%;
+    overflow: auto;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+}
\ No newline at end of file
diff --git a/karavan-designer/src/designer/route/DslProperties.tsx b/karavan-designer/src/designer/route/DslProperties.tsx
index 69729121..ef933585 100644
--- a/karavan-designer/src/designer/route/DslProperties.tsx
+++ b/karavan-designer/src/designer/route/DslProperties.tsx
@@ -46,8 +46,7 @@ export function DslProperties(props: Props) {
 
     const {cloneElement, onDataFormatChange, onPropertyChange, onParametersChange, onExpressionChange} = usePropertiesHook(props.isRouteDesigner);
 
-    const [selectedStep, dark, setSelectedStep, setSelectedUuids] = useDesignerStore((s) =>
-        [s.selectedStep, s.dark, s.setSelectedStep, s.setSelectedUuids], shallow)
+    const [selectedStep, dark] = useDesignerStore((s) => [s.selectedStep, s.dark], shallow)
 
     const [showAdvanced, setShowAdvanced] = useState<boolean>(false);
     const [isDescriptionExpanded, setIsDescriptionExpanded] = useState<boolean>(false);
diff --git a/karavan-designer/src/designer/utils/IntegrationHeader.tsx b/karavan-designer/src/designer/utils/IntegrationHeader.tsx
index c117d434..0725d1e1 100644
--- a/karavan-designer/src/designer/utils/IntegrationHeader.tsx
+++ b/karavan-designer/src/designer/utils/IntegrationHeader.tsx
@@ -23,19 +23,36 @@ export function IntegrationHeader () {
 
     const [integration] = useIntegrationStore((state) => [state.integration], shallow)
 
+    const isKamelet = integration.type === 'kamelet';
+
+    function getKameletType(): string {
+        // const labels = integration.metadata.labels;
+        // if (labels && labels.l)
+        // "camel.apache.org/kamelet.type"
+        return '';
+    }
+
     return (
         <div className="headers">
-            <Title headingLevel="h1" size="md">Integration</Title>
+            {/*<Title headingLevel="h1" size="md">Integration</Title>*/}
             {/*<FormGroup label="Title" fieldId="title" isRequired>*/}
             {/*    <TextInput className="text-field" type="text" id="title" name="title" isReadOnly*/}
             {/*               value={*/}
             {/*                   CamelUi.titleFromName(this.props.integration.metadata.name)*/}
             {/*               }/>*/}
             {/*</FormGroup>*/}
+            <FormGroup label="Kind" fieldId="kind" isRequired>
+                <TextInput className="text-field" type="text" id="kind" name="kind"
+                           value={integration.kind} readOnlyVariant="default"/>
+            </FormGroup>
             <FormGroup label="Name" fieldId="name" isRequired>
                 <TextInput className="text-field" type="text" id="name" name="name"
                            value={integration.metadata.name} readOnlyVariant="default"/>
             </FormGroup>
+            {isKamelet && <FormGroup label="Kamelet type" fieldId="type" isRequired>
+                <TextInput className="text-field" type="text" id="type" name="type"
+                           value={integration.metadata.labels?.["camel.apache.org/kamelet.type"]} readOnlyVariant="default"/>
+            </FormGroup>}
         </div>
     )
 }
diff --git a/karavan-designer/src/designer/utils/KaravanIcons.tsx b/karavan-designer/src/designer/utils/KaravanIcons.tsx
index 677945f5..906ba86e 100644
--- a/karavan-designer/src/designer/utils/KaravanIcons.tsx
+++ b/karavan-designer/src/designer/utils/KaravanIcons.tsx
@@ -263,21 +263,38 @@ export function CamelIcon(props?: (JSX.IntrinsicAttributes & React.SVGProps<SVGS
 }
 
 export function getDesignerIcon(icon: string) {
-    if (icon === 'code') return (
-    <svg
-        className="top-icon" id="icon"
-        xmlns="http://www.w3.org/2000/svg"
-        width="24"
-        height="24"
-        fill="none"
-        viewBox="0 0 24 24"
-    >
-        <path
-            fill="#000000"
-            d="M8.502 5.387a.75.75 0 00-1.004-1.115L5.761 5.836c-.737.663-1.347 1.212-1.767 1.71-.44.525-.754 1.088-.754 1.784 0 .695.313 1.258.754 1.782.42.499 1.03 1.049 1.767 1.711l1.737 1.564a.75.75 0 101.004-1.115l-1.697-1.527c-.788-.709-1.319-1.19-1.663-1.598-.33-.393-.402-.622-.402-.817 0-.196.072-.425.402-.818.344-.409.875-.889 1.663-1.598l1.697-1.527zM14.18 4.275a.75.75 0 01.532.918l-3.987 15a.75.75 0 11-1.45-.386l3.987-15a.75.75 0 01.918-.532zM15.443 10.498a.75.75 0 011.059-.05 [...]
-        ></path>
-    </svg>
+    if (icon === 'kamelet') return (
+        <svg
+            className="top-icon" id="icon"
+            xmlns="http://www.w3.org/2000/svg"
+            viewBox="0 0 32 32"
+        >
+            <title>{"application"}</title>
+            <path d="M16 18H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2ZM6 6v10h10V6ZM26 12v4h-4v-4h4m0-2h-4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2ZM26 22v4h-4v-4h4m0-2h-4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2ZM16 22v4h-4v-4h4m0-2h-4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2Z" />
+            <path
+                d="M0 0h32v32H0z"
+                data-name="&lt;Transparent Rectangle&gt;"
+                style={{
+                    fill: "none",
+                }}
+            />
+        </svg>
         )
+    if (icon === 'code') return (
+        <svg
+
+            xmlns="http://www.w3.org/2000/svg"
+            width="24"
+            height="24"
+            fill="none"
+            viewBox="0 0 24 24"
+        >
+            <path
+                fill="#000000"
+                d="M8.502 5.387a.75.75 0 00-1.004-1.115L5.761 5.836c-.737.663-1.347 1.212-1.767 1.71-.44.525-.754 1.088-.754 1.784 0 .695.313 1.258.754 1.782.42.499 1.03 1.049 1.767 1.711l1.737 1.564a.75.75 0 101.004-1.115l-1.697-1.527c-.788-.709-1.319-1.19-1.663-1.598-.33-.393-.402-.622-.402-.817 0-.196.072-.425.402-.818.344-.409.875-.889 1.663-1.598l1.697-1.527zM14.18 4.275a.75.75 0 01.532.918l-3.987 15a.75.75 0 11-1.45-.386l3.987-15a.75.75 0 01.918-.532zM15.443 10.498a.75.75 0 011.059 [...]
+            ></path>
+        </svg>
+    )
     if (icon === 'routes') return (
         <svg className="top-icon" width="32px" height="32px" viewBox="0 0 32 32" id="icon">
             <defs>