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 f8463a1  Project Builder UI (#333)
f8463a1 is described below

commit f8463a1c1415dbb769478e12a06ce35f3032b0e9
Author: Marat Gubaidullin <[email protected]>
AuthorDate: Thu May 5 12:11:46 2022 -0400

    Project Builder UI (#333)
    
    * Project Builder UI
    
    * Builder dark mode vscode
    
    * Properties to project model
    
    * Properties edited
    
    * COnfig
    
    * Package from UI
    
    * First UI that works
    
    * Jbang UI works with Openshift
---
 karavan-core/src/core/api/ProjectModelApi.ts       | 151 ++++++++++
 karavan-core/src/core/model/ProjectModel.ts        |  60 ++++
 karavan-core/test/application.properties           |  26 ++
 karavan-core/test/application.spec.ts              |  45 +++
 karavan-demo/builder/application.properties        |  32 ++
 karavan-demo/builder/builder-demo.yaml             |   9 +
 karavan-designer/src/App.tsx                       |  54 ++--
 karavan-designer/src/builder/BuilderPage.tsx       | 326 +++++++++++++++++++++
 karavan-designer/src/builder/FileSelector.tsx      |  81 +++++
 karavan-designer/src/designer/KaravanDesigner.tsx  |   2 +-
 karavan-designer/src/designer/karavan.css          |  81 ++++-
 karavan-designer/src/designer/route/DslElement.tsx |   8 +
 .../src/designer/utils/KaravanIcons.tsx            | 117 ++++++++
 karavan-designer/src/index.tsx                     |   1 +
 karavan-generator/pom.xml                          |   5 +
 .../karavan/generator/CamelJbangGenerator.java     |  70 +++++
 karavan-vscode/icons/dark/builder.svg              |  61 ++++
 karavan-vscode/icons/light/builder.svg             |   1 +
 karavan-vscode/package.json                        |  23 +-
 karavan-vscode/src/builderView.ts                  | 208 +++++++++++++
 karavan-vscode/src/commands.ts                     | 157 ++++++++++
 karavan-vscode/src/extension.ts                    |   4 +
 karavan-vscode/src/openapiView.ts                  |  11 +-
 karavan-vscode/src/utils.ts                        |  60 +---
 karavan-vscode/webview/App.tsx                     |  29 +-
 karavan-vscode/webview/builder/BuilderPage.tsx     | 326 +++++++++++++++++++++
 karavan-vscode/webview/builder/FileSelector.tsx    |  81 +++++
 karavan-vscode/webview/index.css                   |  52 ++++
 28 files changed, 1990 insertions(+), 91 deletions(-)

diff --git a/karavan-core/src/core/api/ProjectModelApi.ts 
b/karavan-core/src/core/api/ProjectModelApi.ts
new file mode 100644
index 0000000..93001ed
--- /dev/null
+++ b/karavan-core/src/core/api/ProjectModelApi.ts
@@ -0,0 +1,151 @@
+/*
+ * 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 {ProjectModel, ProjectStatus} from "../model/ProjectModel";
+
+const PREFIX_JBANG = "camel.jbang";
+const PREFIX_MAIN = "camel.main";
+
+export class ProjectModelApi {
+
+    static propertiesToProject = (properties: string): ProjectModel => {
+        const lines = properties.split(/\r?\n/).filter(text => 
text.trim().length > 0 &&
+            (text.startsWith(PREFIX_JBANG) || 
text.startsWith(this.getKeyWithPrefix('routesIncludePattern'))));
+        const map = this.propertiesToMap(lines);
+        const project = new ProjectModel();
+        project.name = this.getValue(map, "project.name");
+        project.version = this.getValue(map, "project.version");
+        project.namespace = this.getValue(map, "project.namespace");
+        project.cleanup = this.getValue(map, "project.cleanup") === "true";
+        const tag = this.getValue(map, "build.image.tag", "deploy.image");
+        project.tag = tag ? tag : project.namespace + "/" + project.name + ":" 
+ project.version;
+        project.classpathFiles = this.getValue(map, "classpathFiles");
+        project.routesIncludePattern = this.getValue(map, 
"routesIncludePattern");
+        project.sourceImage = this.getValue(map, "build.image.source-image");
+        project.from = this.getValue(map, "build.image.from");
+
+        project.filename = this.getValue(map, "build.image.jar", 
"package.uber-jar.jar");
+        project.replicas = this.getValue(map, "deploy.replicas");
+        project.nodePort = this.getValue(map, "deploy.node-port");
+        project.server = this.getValue(map, "deploy.server", 
"build.image.server");
+        const openshift = this.getValue(map, "deploy.openshift", 
"build.image.openshift") === "true";
+        const minikube = this.getValue(map, "deploy.minikube", 
"build.image.minikube") === "true";
+        project.target = openshift ? "openshift" : (minikube ? "minikube" : 
"kubernetes");
+        project.deploy = this.getValue(map, "deploy") === "true";
+        project.build = this.getValue(map, "build.image") === "true";
+        project.uberJar = this.getValue(map, "package") === "true";
+        project.manifests = this.getValue(map, "manifests") === "true";
+        project.path = this.getValue(map, "manifests.path");
+        project.status = new ProjectStatus();
+
+        Object.keys(project).forEach(key => {
+            const value = (project as any)[key];
+            if ( value === undefined || value === 'undefined') delete (project 
as any)[key];
+        })
+        return new ProjectModel(JSON.parse(JSON.stringify(project)) as 
ProjectModel);
+    }
+
+    static getValue = (map: Map<string, any>, ...keys: string[]): any => {
+        for (const key of keys) {
+            const k = this.getKeyWithPrefix(key);
+            if  (map.has(k)) return map.get(k);
+        }
+        return undefined;
+    }
+
+    static propertiesToMap = (properties: string[]): Map<string, any> => {
+        const result = new Map<string, any>();
+        properties.forEach(line => {
+            const pair = line.split("=").map(line => line.trim());
+            result.set(pair[0], pair[1]);
+        })
+        return result;
+    }
+
+    static updateProperties = (properties: string, project: ProjectModel): 
string => {
+        const linesAll = properties.split(/\r?\n/);
+        const nonPropLines = linesAll.filter(text => text.trim().length === 0
+            || (!text.startsWith(PREFIX_JBANG) && 
!text.startsWith(this.getKeyWithPrefix('routesIncludePattern'))));
+        const propLines = linesAll.filter(text => text.trim().length > 0
+            && (text.startsWith(PREFIX_JBANG) || 
text.startsWith(this.getKeyWithPrefix('routesIncludePattern'))));
+        const mapFromFile = this.propertiesToMap(propLines);
+        const mapFromProject = this.projectToMap(project);
+        const result: string[] = [...nonPropLines];
+        mapFromFile.forEach((value, key) => {
+            if (!mapFromProject.has(key) && mapFromProject.get(key) !== 
undefined) result.push(key + "=" + value);
+        })
+        mapFromProject.forEach((value, key) => {
+            if (value !== undefined) result.push(key + "=" + value);
+        })
+        return result.join("\n");
+    }
+
+    static projectToMap = (project: ProjectModel): Map<string, any> => {
+        const map = new Map<string, any>();
+        if (project.tag?.length === 0) project.tag = project.namespace + "/" + 
project.name + ":" + project.version;
+        this.setValue(map, "project.name", project.name);
+        this.setValue(map, "project.version", project.version);
+        this.setValue(map, "project.namespace", project.namespace);
+        this.setValue(map, "project.cleanup", project.cleanup);
+        this.setValue(map, "package", project.uberJar);
+        this.setValue(map, "package.uber-jar.jar", project.filename);
+        this.setValue(map, "package.uber-jar.fresh", true);
+        this.setValue(map, "classpathFiles", project.classpathFiles);
+        this.setValue(map, "routesIncludePattern", 
project.routesIncludePattern);
+        this.setValue(map, "build.image", project.build);
+        this.setValue(map, "build.image.openshift", project.target === 
'openshift');
+        this.setValue(map, "build.image.minikube", project.target === 
'minikube');
+        this.setValue(map, "build.image.jar", project.filename);
+        this.setValue(map, "build.image.tag", project.tag);
+        this.setValue(map, "build.image.source-image", project.sourceImage);
+        this.setValue(map, "build.image.from", project.from);
+        this.setValue(map, "build.image.server", project.server);
+        this.setValue(map, "deploy", project.deploy);
+        this.setValue(map, "deploy.openshift", project.target === 'openshift');
+        this.setValue(map, "deploy.minikube", project.target === 'minikube');
+        this.setValue(map, "deploy.image", project.tag);
+        this.setValue(map, "deploy.replicas", project.replicas);
+        this.setValue(map, "deploy.node-port", project.nodePort);
+        this.setValue(map, "deploy.server", project.server);
+        this.setValue(map, "undeploy.openshift", project.target === 
'openshift');
+        this.setValue(map, "undeploy.minikube", project.target === 'minikube');
+        this.setValue(map, "undeploy.server", project.server);
+        this.setValue(map, "manifests", project.manifests);
+        this.setValue(map, "manifests.path", project.manifests);
+        this.setValue(map, "manifests.openshift", project.target === 
'openshift');
+        this.setValue(map, "manifests.minikube", project.target === 
'minikube');
+        this.setValue(map, "manifests.image", project.tag);
+        this.setValue(map, "manifests.replicas", project.replicas);
+        this.setValue(map, "manifests.node-port", project.nodePort);
+        this.setValue(map, "manifests.server", project.server);
+        this.setValue(map, "manifests.jar", project.filename);
+
+        return map;
+    }
+
+    static setValue = (map: Map<string, any>, key: string, value: any): 
Map<string, any> => {
+        if (value !== undefined && value?.toString().length > 0)
+            map.set(this.getKeyWithPrefix(key), value);
+        else
+            map.set(this.getKeyWithPrefix(key), undefined);
+        return map;
+    }
+
+    static getKeyWithPrefix = (key: string): string => {
+        const prefix = key === 'routesIncludePattern' ? PREFIX_MAIN : 
PREFIX_JBANG;
+        return prefix + "." + key;
+    }
+}
diff --git a/karavan-core/src/core/model/ProjectModel.ts 
b/karavan-core/src/core/model/ProjectModel.ts
new file mode 100644
index 0000000..e9a4b6f
--- /dev/null
+++ b/karavan-core/src/core/model/ProjectModel.ts
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+export class ProjectStatus {
+    uberJar: 'pending' | 'progress' | 'done' | 'error' = 'pending';
+    build: 'pending' | 'progress' | 'done' | 'error' = 'pending';
+    deploy: 'pending' | 'progress' | 'done'| 'error' = 'pending';
+    undeploy: 'pending' | 'progress' | 'done'| 'error' = 'pending';
+    active: boolean = false;
+
+    public constructor(init?: Partial<ProjectStatus>) {
+        Object.assign(this, init);
+    }
+}
+
+export class ProjectModel {
+    name: string = 'demo'
+    version: string = '1.0.0'
+    filename: string = 'camel-runner.jar'
+    namespace: string = 'default'
+    cleanup: boolean = false
+    tag?: string = this.namespace + "/" + this.name + ":" + this.version
+    sourceImage: string = 'java:openjdk-11-ubi8'
+    from: string = 'gcr.io/distroless/java:11'
+    replicas: number = 1
+    nodePort: number = 30777
+    server?: string
+    token?: string
+    target: 'openshift' | 'minikube' | 'kubernetes' = 'minikube'
+    deploy: boolean = false
+    build: boolean = false
+    uberJar: boolean = true
+    manifests: boolean = true
+    path: string = ''
+    classpathFiles: string = ''
+    routesIncludePattern: string = ''
+    status: ProjectStatus = new ProjectStatus()
+
+    public constructor(init?: Partial<ProjectModel>) {
+        Object.assign(this, init);
+    }
+
+    static createNew(): ProjectModel {
+        return new ProjectModel({})
+    }
+}
\ No newline at end of file
diff --git a/karavan-core/test/application.properties 
b/karavan-core/test/application.properties
new file mode 100644
index 0000000..d641244
--- /dev/null
+++ b/karavan-core/test/application.properties
@@ -0,0 +1,26 @@
+# main props
+loggingLevel=info
+camel.main.name=CamelJBang
+camel.main.shutdownTimeout=5
+camel.main.routesReloadEnabled=false
+camel.main.sourceLocationEnabled=true
+camel.main.tracing=false
+camel.main.modeline=true
+camel.main.routesCompileDirectory=.camel-jbang
+camel.jbang.health=false
+camel.jbang.console=false
+camel.main.routesIncludePattern=file:CustomProcessor.java,file:postman.yaml
+camel.jbang.classpathFiles=parcels.png,postgres_db.sql,start.sh
+dependency=mvn:org.apache.camel:camel-kamelet:3.17.0-SNAPSHOT
+dependency=mvn:org.apache.camel:camel-java-joor-dsl:3.17.0-SNAPSHOT
+dependency=mvn:org.apache.camel:camel-rest:3.17.0-SNAPSHOT
+dependency=mvn:org.apache.camel:camel-core-languages:3.17.0-SNAPSHOT
+dependency=mvn:org.apache.camel:camel-direct:3.17.0-SNAPSHOT
+
+# jband build props
+camel.jbang.project.namespace=development
+camel.jbang.project.name=demo
+camel.jbang.project.version=1.0.1
+camel.jbang.build.image.openshift=true
+# camel.jbang.deploy.image=karavan/deploy-demo:1.0.0
+camel.jbang.package=true
diff --git a/karavan-core/test/application.spec.ts 
b/karavan-core/test/application.spec.ts
new file mode 100644
index 0000000..6c9bc90
--- /dev/null
+++ b/karavan-core/test/application.spec.ts
@@ -0,0 +1,45 @@
+/*
+ * 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 * as fs from 'fs';
+import 'mocha';
+import {expect} from "chai";
+import {ProjectModelApi} from "../src/core/api/ProjectModelApi";
+
+describe('Project configuration', () => {
+
+    it('Read properties', () => {
+        const props = 
fs.readFileSync('test/application.properties',{encoding:'utf8', flag:'r'});
+        const project = ProjectModelApi.propertiesToProject(props);
+        expect(project.name).to.equal('demo');
+        expect(project.namespace).to.equal('development');
+
+        project.tag = 'newtag/proj:latest';
+        let newProperties = ProjectModelApi.updateProperties(props, project);
+        const tag = newProperties.split(/\r?\n/).filter(l => 
l.startsWith("camel.jbang.build.image.tag"))[0].split("=")[1];
+        expect(tag).to.equal(project.tag);
+
+
+        project.routesIncludePattern = "file:x";
+        newProperties = ProjectModelApi.updateProperties(newProperties, 
project);
+        project.routesIncludePattern = "file:y";
+        newProperties = ProjectModelApi.updateProperties(newProperties, 
project);
+        project.routesIncludePattern = "";
+        newProperties = ProjectModelApi.updateProperties(newProperties, 
project);
+        // console.log(newProperties);
+    });
+
+});
diff --git a/karavan-demo/builder/application.properties 
b/karavan-demo/builder/application.properties
new file mode 100644
index 0000000..3b9a9b6
--- /dev/null
+++ b/karavan-demo/builder/application.properties
@@ -0,0 +1,32 @@
+
+camel.jbang.project.name=demo
+camel.jbang.project.version=1.0.0
+camel.jbang.project.namespace=default
+camel.jbang.project.cleanup=true
+camel.jbang.package=true
+camel.jbang.package.uber-jar.jar=camel-runner.jar
+camel.jbang.package.uber-jar.fresh=true
+camel.main.routesIncludePattern=file:builder-demo.yaml
+camel.jbang.build.image=true
+camel.jbang.build.image.openshift=false
+camel.jbang.build.image.minikube=true
+camel.jbang.build.image.jar=camel-runner.jar
+camel.jbang.build.image.tag=default/demo:1.0.0
+camel.jbang.build.image.source-image=java:openjdk-11-ubi8
+camel.jbang.build.image.from=gcr.io/distroless/java:11
+camel.jbang.deploy=true
+camel.jbang.deploy.openshift=false
+camel.jbang.deploy.minikube=true
+camel.jbang.deploy.image=default/demo:1.0.0
+camel.jbang.deploy.replicas=1
+camel.jbang.deploy.node-port=30777
+camel.jbang.undeploy.openshift=false
+camel.jbang.undeploy.minikube=true
+camel.jbang.manifests=true
+camel.jbang.manifests.path=true
+camel.jbang.manifests.openshift=false
+camel.jbang.manifests.minikube=true
+camel.jbang.manifests.image=default/demo:1.0.0
+camel.jbang.manifests.replicas=1
+camel.jbang.manifests.node-port=30777
+camel.jbang.manifests.jar=camel-runner.jar
\ No newline at end of file
diff --git a/karavan-demo/builder/builder-demo.yaml 
b/karavan-demo/builder/builder-demo.yaml
new file mode 100644
index 0000000..d8763d6
--- /dev/null
+++ b/karavan-demo/builder/builder-demo.yaml
@@ -0,0 +1,9 @@
+- route:
+    from:
+      uri: kamelet:timer-source
+      steps:
+        - log:
+            message: ${body}
+      parameters:
+        period: 2000
+        message: Hello World
diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx
index 67426b7..1a83bcd 100644
--- a/karavan-designer/src/App.tsx
+++ b/karavan-designer/src/App.tsx
@@ -24,9 +24,11 @@ import {KaravanDesigner} from "./designer/KaravanDesigner";
 import {KameletsPage} from "./kamelets/KameletsPage";
 import {ComponentsPage} from "./components/ComponentsPage";
 import {EipPage} from "./eip/EipPage";
+import {BuilderPage} from "./builder/BuilderPage";
+import {ProjectModel} from "karavan-core/lib/model/ProjectModel";
 
 interface Props {
-    page: "designer" | "kamelets" | "components" | "eip";
+    page: "designer" | "kamelets" | "components" | "eip" | "builder";
 }
 
 interface State {
@@ -41,27 +43,26 @@ class App extends React.Component<Props, State> {
         name: 'demo.yaml',
         key: '',
         yaml:
-        // 'apiVersion: camel.apache.org/v1\n' +
-        // 'kind: Integration\n' +
-        // 'metadata:\n' +
-        // '  name: postman.yaml\n' +
-        // 'spec:\n' +
-        // '  flows:\n' +
-        // '    - route:\n' +
-        // '        from:\n' +
-        // '          uri: direct:post\n' +
-        // '          steps:\n' +
-        // '            - log:\n' +
-        // '                message: \'Received: ${body}\'\n' +
-        // '            - log:\n' +
-        // '                message: \'Received: ${body}\'\n' +
-        // '            - log:\n' +
-        // '                message: \'Received: ${body}\'\n' +
-        // '            - to:\n' +
-        // '                uri: kamelet:kafka-sink\n' +
-        // '                parameters:\n' +
-        // '                  topic: topic1\n' +
-        // '        id: post\n' +
+        'apiVersion: camel.apache.org/v1\n' +
+        'kind: Integration\n' +
+        'metadata:\n' +
+        '  name: postman.yaml\n' +
+        'spec:\n' +
+        '  flows:\n' +
+        '    - route:\n' +
+        '        from:\n' +
+        '          uri: kamelet:timer-source\n' +
+        '          steps:\n' +
+        '            - log:\n' +
+        '                message: ${body}\n' +
+        '            - aggregate: {}\n' +
+        '            - choice: {}\n' +
+        '            - split:\n' +
+        '                expression: {}\n' +
+        '            - saga: {}\n' +
+        '          parameters:\n' +
+        '            period: 2000\n' +
+        '            message: Hello World\n' +
             ''
     };
 
@@ -113,6 +114,9 @@ class App extends React.Component<Props, State> {
     }
 
     public render() {
+        const project = ProjectModel.createNew();
+        project.status.active = true;
+        project.status.uberJar = "progress";
         return (
             <Page className="karavan">
                 {this.props.page === "designer" && <KaravanDesigner 
key={this.state.key} filename={this.state.name} yaml={this.state.yaml}
@@ -122,6 +126,12 @@ class App extends React.Component<Props, State> {
                 {this.props.page === "kamelets" && <KameletsPage 
dark={document.body.className.includes('vscode-dark')} />}
                 {this.props.page === "components" && <ComponentsPage 
dark={document.body.className.includes('vscode-dark')} />}
                 {this.props.page === "eip" && <EipPage 
dark={document.body.className.includes('vscode-dark')} />}
+                {this.props.page === "builder" && <BuilderPage 
dark={document.body.className.includes('vscode-dark')} project={project}
+                                                               
onChange={project => {
+                                                                   
console.log("routesIncludePattern", project.routesIncludePattern);
+                                                                   
console.log("classpathFiles", project.classpathFiles);
+                                                               }}
+                                                               
files={'demo.yaml,CustomProcessor.java,script.groovy,docker-compose.yaml,README.MD'}/>}
             </Page>
         );
     }
diff --git a/karavan-designer/src/builder/BuilderPage.tsx 
b/karavan-designer/src/builder/BuilderPage.tsx
new file mode 100644
index 0000000..6b33006
--- /dev/null
+++ b/karavan-designer/src/builder/BuilderPage.tsx
@@ -0,0 +1,326 @@
+import React from 'react';
+import {
+    Toolbar,
+    ToolbarContent,
+    ToolbarItem,
+    TextInput,
+    PageSection,
+    TextContent,
+    Text,
+    PageSectionVariants,
+    Flex,
+    FlexItem,
+    Badge,
+    Button,
+    FormGroup,
+    Form,
+    Card,
+    CardTitle,
+    CardBody,
+    CardFooter,
+    CardHeader,
+    CardHeaderMain,
+    CardActions,
+    Checkbox,
+    Switch,
+    ToggleGroup,
+    ToggleGroupItem,
+    PopoverPosition,
+    Popover,
+    InputGroup,
+    ProgressStep,
+    ProgressStepper,
+    Spinner,
+    HelperTextItem, HelperText
+} from '@patternfly/react-core';
+import '../designer/karavan.css';
+import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon";
+import InProgressIcon from 
'@patternfly/react-icons/dist/esm/icons/in-progress-icon';
+import AutomationIcon from 
'@patternfly/react-icons/dist/esm/icons/bundle-icon';
+import PendingIcon from '@patternfly/react-icons/dist/esm/icons/pending-icon';
+import ExclamationCircleIcon from 
'@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon';
+import CheckCircleIcon from 
'@patternfly/react-icons/dist/esm/icons/check-circle-icon';
+import RunIcon from '@patternfly/react-icons/dist/esm/icons/forward-icon';
+import StopIcon from '@patternfly/react-icons/dist/esm/icons/stop-icon';
+import JarIcon from '@patternfly/react-icons/dist/esm/icons/hotjar-icon';
+import ImageIcon from '@patternfly/react-icons/dist/esm/icons/docker-icon';
+import DeployIcon from 
'@patternfly/react-icons/dist/esm/icons/cloud-upload-alt-icon';
+import CleanupIcon from '@patternfly/react-icons/dist/esm/icons/remove2-icon';
+import ProjectIcon from '@patternfly/react-icons/dist/esm/icons/cubes-icon';
+import {FileSelector} from "./FileSelector";
+import {ProjectModel, ProjectStatus} from 
"karavan-core/lib/model/ProjectModel";
+
+interface Props {
+    dark: boolean
+    project: ProjectModel
+    files: string
+    onChange?: (project: ProjectModel) => void
+    onAction?: (action: "start" | "stop" | "undeploy", project: ProjectModel) 
=> void
+}
+
+interface State {
+    name: string,
+    version: string,
+    filename: string,
+    namespace: string,
+    tag?: string,
+    sourceImage: string,
+    from: string,
+    replicas: number,
+    nodePort: number,
+    server?: string,
+    token?: string,
+    target: 'openshift' | 'minikube' | 'kubernetes',
+    deploy: boolean,
+    build: boolean,
+    uberJar: boolean,
+    routesIncludePattern: string,
+    classpathFiles: string,
+    status: ProjectStatus,
+    cleanup: boolean,
+    manifests: boolean,
+    path: string,
+}
+
+export class BuilderPage extends React.Component<Props, State> {
+
+    public state: State = this.props.project;
+
+    componentDidUpdate = (prevProps: Readonly<Props>, prevState: 
Readonly<State>, snapshot?: any) => {
+        this.props.onChange?.call(this, this.state);
+    }
+
+    getHelp(text: string) {
+        return <Popover
+            aria-label={text}
+            position={PopoverPosition.left}
+            bodyContent={text}>
+            <Button variant="plain" onClick={e => {
+            }}>
+                <HelpIcon/>
+            </Button>
+        </Popover>
+    }
+
+    getField(name: string, label: string, type: 'text' | 'date' | 
'datetime-local' | 'email' | 'month' | 'number' | 'password' | 'search' | 'tel' 
| 'time' | 'url',
+             value: any, help: string, onChange: (val: any) => void, 
isRequired: boolean = false, enabled: boolean = true) {
+        return <FormGroup label={label} fieldId={name} isRequired={isRequired}>
+            <InputGroup>
+                <TextInput isRequired={isRequired} isDisabled={!enabled} 
className="text-field" type={type} id={name} name={name} value={value}
+                           onChange={val => onChange?.call(this, val)}/>
+                {this.getHelp(help)}
+            </InputGroup>
+        </FormGroup>
+    }
+
+    getCardHeader(title: string, icon: any, optional: boolean = true, checked: 
boolean = false, onCheck?: (check: boolean) => void) {
+        return <CardHeader>
+            <CardHeaderMain>
+                <CardTitle className="card-header">
+                    {icon}{title}
+                </CardTitle>
+            </CardHeaderMain>
+            <CardActions hasNoOffset={true}>
+                {optional && <Switch isChecked={checked} onChange={checked => 
onCheck?.call(this, checked)} id={title} name={title} aria-label={"xxx"}/>}
+            </CardActions>
+        </CardHeader>
+    }
+
+    getProjectForm() {
+        return (
+            <Card className="builder-card" isCompact style={{width: "100%"}}>
+                {this.getCardHeader("Project", <ProjectIcon/>, false)}
+                <CardBody>
+                    <Form isHorizontal>
+                        {this.getField("name", "Name", "text", 
this.state.name, "Project name", val => this.setState({name: val}), true)}
+                        {this.getField("version", "Version", "text", 
this.state.version, "Project version", val => this.setState({version: val}), 
true)}
+                    </Form>
+                </CardBody>
+            </Card>
+        )
+    }
+
+    getPackageForm() {
+        const {uberJar, classpathFiles, routesIncludePattern} = this.state;
+        return <Card className="builder-card" isCompact style={{width: 
"100%"}}>
+            {this.getCardHeader("Package", <JarIcon/>, true, 
this.state.uberJar, check => this.setState({uberJar: check}))}
+            <CardBody className={uberJar ? "" : "card-disabled"}>
+                <Form isHorizontal>
+                    {this.getField("filename", "Jar", "text", 
this.state.filename, "Jar file name", val => this.setState({filename: val}), 
true, uberJar)}
+                    {this.props.files.length >0 &&
+                        <FileSelector source={true} label="Route/source files" 
help="Routes and source to build and package" files={this.props.files} 
filesSelected={routesIncludePattern} onChange={filesSelected => 
this.setState({routesIncludePattern: filesSelected})}/>}
+                    {this.props.files.length >0 &&
+                        <FileSelector source={false} label="Resources" 
help="Files to package as resources" files={this.props.files} 
filesSelected={classpathFiles} onChange={filesSelected => 
this.setState({classpathFiles: filesSelected})}/>}
+                </Form>
+            </CardBody>
+        </Card>
+    }
+
+    getBuildForm() {
+        const {target, namespace, build, tag, sourceImage, server, token, 
from} = this.state;
+        return <Card className="builder-card" isCompact style={{width: 
"100%"}}>
+            {this.getCardHeader("Build", <ImageIcon/>, true, this.state.build, 
check => this.setState({build: check}))}
+            <CardBody className={build ? "" : "card-disabled"}>
+                <Form isHorizontal>
+                    <FormGroup label="Target" fieldId="tag" isRequired 
disabled={true}>
+                        <ToggleGroup aria-label="Select target">
+                            <ToggleGroupItem isDisabled={!build} 
text="Minikube" buttonId="minikube" isSelected={target === 'minikube'}
+                                             onChange={selected => selected ? 
this.setState({target: 'minikube'}) : {}}/>
+                            <ToggleGroupItem isDisabled={!build} 
text="Kubernetes" buttonId="kubernetes" isSelected={target === 'kubernetes'}
+                                             onChange={selected => selected ? 
this.setState({target: 'kubernetes'}) : {}}/>
+                            <ToggleGroupItem isDisabled={!build} 
text="Openshift" buttonId="openshift" isSelected={target === 'openshift'}
+                                             onChange={selected => selected ? 
this.setState({target: 'openshift'}) : {}}/>
+                        </ToggleGroup>
+                    </FormGroup>
+                    {this.getField("namespace", "Namespace", "text", 
namespace, "Namespace to build and/or deploy", val => this.setState({namespace: 
val}), true, build)}
+                    {this.getField("tag", "Image tag", "text", tag, "Image 
tag", val => this.setState({tag: val}), true, build)}
+                    {target !== 'openshift' && this.getField("from", "Base 
Image", "text", from, "Base Image", val => this.setState({from: val}), true, 
build)}
+                    {target === 'openshift' && this.getField("sourceImage", 
"Source tag", "text", sourceImage, "Source image name (for OpenShift 
BuildConfig)", val => this.setState({sourceImage: val}), true, build)}
+                    {target === 'openshift' && this.getField("server", 
"Server", "text", server, "Master URL", val => this.setState({server: val}), 
true, build)}
+                    {target === 'openshift' && this.getField("token", "Token", 
"password", token, "Authentication Token (Token will not be saved)", val => 
this.setState({token: val}), true, build)}
+                </Form>
+            </CardBody>
+        </Card>
+    }
+
+    getDeployForm() {
+        const {target, deploy, build} = this.state;
+        return <Card className="builder-card" isCompact style={{width: 
"100%"}}>
+            {this.getCardHeader("Deploy", <DeployIcon/>, true, deploy, check 
=> this.setState({deploy: check}))}
+            <CardBody className={build ? "" : "card-disabled"}>
+                <Form isHorizontal>
+                    {deploy && this.getField("replicas", "Replicas", "number", 
this.state.replicas, "Number of replicas of the application", val => 
this.setState({replicas: val}), true, build)}
+                    {deploy && target === 'minikube' && 
this.getField("nodePort", "Node port", "number", this.state.nodePort, "Node 
port (minikube)", val => this.setState({nodePort: val}), true, build)}
+                </Form>
+            </CardBody>
+        </Card>
+    }
+
+    getCleanupForm() {
+        const {cleanup} = this.state;
+        return <Card className="builder-card" isCompact style={{width: 
"100%"}}>
+            {this.getCardHeader("Cleanup", <CleanupIcon/>, true, cleanup, 
check => this.setState({cleanup: check}))}
+            <CardBody className={cleanup ? "" : "card-disabled"}>
+                <HelperText>
+                    <HelperTextItem variant="indeterminate">Remove created jar 
and .camel-jbang after build and/or deploy.</HelperTextItem>
+                </HelperText>
+            </CardBody>
+        </Card>
+    }
+
+    getProgressIcon(status: 'pending' | 'progress' | 'done' | 'error') {
+        switch (status) {
+            case "pending":
+                return <PendingIcon/>;
+            case "progress":
+                return <Spinner isSVG size="md"/>
+            case "done":
+                return <CheckCircleIcon/>;
+            case "error":
+                return <ExclamationCircleIcon/>;
+            default:
+                return undefined;
+        }
+    }
+
+    getProgress() {
+        const {status, uberJar, build, deploy} = this.state;
+        const undeploying = status.active && status.undeploy === "progress";
+        return (
+            <ProgressStepper isCenterAligned style={{visibility: "visible"}}>
+                {!undeploying && uberJar && <ProgressStep variant="pending" 
id="package" titleId="package" aria-label="package" 
icon={this.getProgressIcon(status.uberJar)}>Package</ProgressStep>}
+                {!undeploying && build && <ProgressStep variant="pending" 
isCurrent id="build" titleId="build" aria-label="build" 
icon={this.getProgressIcon(status.build)}>Build</ProgressStep>}
+                {!undeploying && deploy && <ProgressStep variant="pending" 
id="deploy" titleId="deploy" aria-label="deploy" 
icon={this.getProgressIcon(status.deploy)}>Deploy</ProgressStep>}
+                {undeploying &&
+                    <ProgressStep variant="pending" id="undeploy" 
titleId="undeploy" aria-label="undeploy" 
icon={this.getProgressIcon(status.undeploy)}>Undeploy</ProgressStep>}
+            </ProgressStepper>
+        )
+    }
+
+    getHeader() {
+        return (
+            <PageSection className="tools-section" variant={this.props.dark ? 
PageSectionVariants.darker : PageSectionVariants.light}>
+                <Flex className="tools" direction={{default: 'row'}} 
justifyContent={{default: 'justifyContentSpaceBetween'}} spaceItems={{default: 
'spaceItemsLg'}}>
+                    <FlexItem>
+                        <TextContent className="header">
+                            <Text component="h2">Project Builder</Text>
+                            <Badge isRead className="labels">Powered by Camel 
JBang</Badge>
+                        </TextContent>
+                    </FlexItem>
+                    <FlexItem>
+                        <Toolbar id="toolbar-group-types">
+                            <ToolbarContent>
+                                <ToolbarItem>
+                                    <Button variant="plain" onClick={e => {
+                                    }}><HelpIcon/></Button>
+                                </ToolbarItem>
+                            </ToolbarContent>
+                        </Toolbar>
+                    </FlexItem>
+                </Flex>
+            </PageSection>
+        )
+    }
+
+    onButtonClick(action: "start" | "stop" | "undeploy") {
+        this.props.onAction?.call(this, action, this.state);
+    }
+
+    getFooter() {
+        const active = this.state.status.active;
+        const label = active ? "Stop" : "Start";
+        const icon = active ? <InProgressIcon/> : <AutomationIcon/>;
+        return <div className="footer">
+                    <div className="progress">
+                        {active && this.getProgress()}
+                    </div>
+                    <div className="buttons">
+                        <Toolbar id="toolbar-items">
+                            <ToolbarContent>
+                                {!active && <ToolbarItem>
+                                    <Button variant="secondary" isSmall 
onClick={event => this.onButtonClick("undeploy")}>Undeploy</Button>
+                                </ToolbarItem>}
+                                <ToolbarItem>
+                                    <Button variant="primary" isSmall 
icon={icon} onClick={event => this.onButtonClick(active ? "stop" : 
"start")}>{label}</Button>
+                                </ToolbarItem>
+                            </ToolbarContent>
+                        </Toolbar>
+                    </div>
+                </div>
+    }
+
+    getCenter() {
+        return (
+            <div className="center">
+                <div className="center-column">
+                    {this.getProjectForm()}
+                    {this.getPackageForm()}
+                </div>
+                <div className="center-column">
+                    {this.getBuildForm()}
+                    {this.getDeployForm()}
+                    {this.getCleanupForm()}
+                </div>
+            </div>
+        )
+    }
+
+    render() {
+        return (
+            <PageSection className="project-builder" variant={this.props.dark 
? PageSectionVariants.darker : PageSectionVariants.light}
+                         padding={{default: 'noPadding'}}>
+                <div style={{height: "100%", display: "flex", flexDirection: 
"column"}}>
+                    <div style={{flexShrink: "0"}}>
+                        {this.getHeader()}
+                    </div>
+                    <div style={{overflow: "auto", flexGrow: 1}}>
+                        {this.getCenter()}
+                    </div>
+                    <div style={{flexShrink: "0"}}>
+                        {this.getFooter()}
+                    </div>
+                </div>
+            </PageSection>
+        )
+    }
+};
\ No newline at end of file
diff --git a/karavan-designer/src/builder/FileSelector.tsx 
b/karavan-designer/src/builder/FileSelector.tsx
new file mode 100644
index 0000000..983a2a6
--- /dev/null
+++ b/karavan-designer/src/builder/FileSelector.tsx
@@ -0,0 +1,81 @@
+import React from 'react';
+import {
+    Button,
+    FormGroup,
+    Checkbox, PopoverPosition, Popover, InputGroup
+} from '@patternfly/react-core';
+import '../designer/karavan.css';
+import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon";
+
+interface Props {
+    label: string
+    help: string
+    files: string
+    filesSelected: string
+    onChange: (files: string) => void
+    source: boolean
+}
+
+interface State {
+    selected: []
+}
+
+export class FileSelector extends React.Component<Props, State> {
+
+    public state: State = {
+        selected: []
+    };
+
+    isChecked(file: string) {
+        const finalFile = this.props.source ? "file:" + file : file;
+        const s = this.props.filesSelected.split(",").map(value => 
value.trim());
+        return s.includes(finalFile);
+    }
+
+    onChange(file: string, checked: boolean) {
+        const finalFile = this.props.source ? "file:" + file : file;
+        const s = this.props.filesSelected.split(",").map(f => 
f.trim()).filter(f => f.length > 0);
+        const already = s.includes(finalFile);
+        if (checked && !already) {
+            s.push(finalFile);
+            this.props.onChange?.call(this, s.join(","));
+        } else if (!checked) {
+            const result = s.filter(f => f !== finalFile);
+            this.props.onChange?.call(this, result.join(","));
+        }
+    }
+
+    getFiles(): string[] {
+        const allFiles = (this.props.files ? this.props.files.split(",") : []);
+        if (this.props.source){
+            const extensions = ['yaml', 'yml', 'java', 'js', 'kt', 'groovy', 
'xml'];
+            return  allFiles.filter(file => {
+                const extension = file.split(".").pop() || '';
+                return extensions.includes(extension);
+            }).map(file => file.replace("file:", ""))
+        }
+        return allFiles;
+    }
+
+    render() {
+        const files = this.getFiles();
+        return (
+            <FormGroup label={this.props.label} fieldId="files">
+                <InputGroup>
+                    <div style={{width:"100%"}}>
+                        {files.map(file => {
+                            const key = file + this.props.source;
+                         return <Checkbox key={key} label={file} 
isChecked={this.isChecked(file)} onChange={checked => this.onChange(file, 
checked)} id={key} name={key}/>
+                        })}
+                    </div>
+                    <Popover aria-label="files" position={PopoverPosition.left}
+                        bodyContent={this.props.help}>
+                        <Button variant="plain" onClick={e => {}}>
+                            <HelpIcon/>
+                        </Button>
+                    </Popover>
+                </InputGroup>
+            </FormGroup>
+        )
+    }
+};
\ No newline at end of file
diff --git a/karavan-designer/src/designer/KaravanDesigner.tsx 
b/karavan-designer/src/designer/KaravanDesigner.tsx
index 0360cc1..bc0c755 100644
--- a/karavan-designer/src/designer/KaravanDesigner.tsx
+++ b/karavan-designer/src/designer/KaravanDesigner.tsx
@@ -140,7 +140,7 @@ export class KaravanDesigner extends React.Component<Props, 
State> {
                     <Tab eventKey='rest' title={this.getTab("REST", "REST 
services", "rest")}></Tab>
                     <Tab eventKey='beans' title={this.getTab("Beans", "Beans 
Configuration", "beans")}></Tab>
                     <Tab eventKey='dependencies' 
title={this.getTab("Dependencies", "Dependencies", "dependencies")}></Tab>
-                    <Tab eventKey='traits' title={this.getTab("Traits", 
"traits configuration", "traits")}></Tab>
+                    {/*<Tab eventKey='traits' title={this.getTab("Traits", 
"traits configuration", "traits")}></Tab>*/}
                     <Tab eventKey='error' title={this.getTab("Error", "Error 
Handler", "error")}></Tab>
                     <Tab eventKey='exception' title={this.getTab("Exceptions", 
"Exception Clauses per type", "exception")}></Tab>
                     {/*<Tab eventKey='code' title={this.getTab("Code", "Code", 
"code")}></Tab>*/}
diff --git a/karavan-designer/src/designer/karavan.css 
b/karavan-designer/src/designer/karavan.css
index 4249a17..1800c04 100644
--- a/karavan-designer/src/designer/karavan.css
+++ b/karavan-designer/src/designer/karavan.css
@@ -1097,4 +1097,83 @@ reactour-portal .reactour__popover button:focus-visible {
 .karavan .tools-section .tools .header .labels {
     height: fit-content;
     margin-left: 3px;
-}
\ No newline at end of file
+}
+
+/* Project Tools */
+.karavan .project-builder {
+    height: 100%;
+    font-size: 14px;
+}
+
+.karavan .project-builder .card-header svg {
+    margin-right: 6px;
+}
+
+.karavan .project-builder .card-disabled {
+    opacity: 0.4;
+}
+
+.karavan .project-builder .pf-c-form__label {
+    font-size: 14px;
+}
+
+.karavan .project-builder .pf-c-check__label {
+    font-size: 14px;
+}
+
+.karavan .project-builder .text-field {
+    font-size: 14px;
+    height: auto;
+}
+
+.karavan .project-builder .pf-c-input-group button {
+    font-size: 14px;
+    height: auto;
+}
+
+.karavan .project-builder .pf-c-form {
+    --pf-c-form--GridGap: 1em;
+}
+
+.karavan .project-builder .pf-c-card__body {
+    --pf-c-card--child--PaddingRight: 0.5em;
+    --pf-c-card--child--PaddingBottom: 1em;
+    --pf-c-card--child--PaddingLeft: 1em;
+}
+
+.karavan .project-builder .pf-c-card__header {
+    --pf-c-card--first-child--PaddingTop: 0.5em;
+}
+
+.karavan .project-builder .center {
+    height: 100%;
+    width: 100%;
+    display: flex;
+    flex-direction: row;
+    gap: 10px;
+    padding: 10px;
+}
+.karavan .project-builder .center-column {
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+}
+
+.karavan .project-builder .footer {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    padding: 10px;
+}
+
+.karavan .project-builder .footer .progress {
+    flex-grow: 4;
+}
+
+.karavan .project-builder .footer .buttons {
+    height: 60px;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-around;
+}
diff --git a/karavan-designer/src/designer/route/DslElement.tsx 
b/karavan-designer/src/designer/route/DslElement.tsx
index ddc391a..bdfec5b 100644
--- a/karavan-designer/src/designer/route/DslElement.tsx
+++ b/karavan-designer/src/designer/route/DslElement.tsx
@@ -28,6 +28,7 @@ import {EventBus} from "../utils/EventBus";
 import {ChildElement, CamelDefinitionApiExt} from 
"karavan-core/lib/api/CamelDefinitionApiExt";
 import ReactDOM from "react-dom";
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
+import {AggregateIcon, ChoiceIcon, FilterIcon, SagaIcon, SortIcon, SplitIcon, 
TransformIcon} from "../utils/KaravanIcons";
 
 interface Props {
     step: CamelElement,
@@ -197,6 +198,13 @@ export class DslElement extends React.Component<Props, 
State> {
                          data-tour={step.dslName + "-icon"}
                          className={"header-icon"}
                          style={this.isWide() ? {width: ""} : {}}>
+                        {/*{step.dslName === 'AggregateDefinition' && 
<AggregateIcon/>}*/}
+                        {/*{step.dslName === 'ChoiceDefinition' && 
<ChoiceIcon/>}*/}
+                        {/*{step.dslName === 'SplitDefinition' && 
<SplitIcon/>}*/}
+                        {/*{step.dslName === 'SagaDefinition' && 
<SagaIcon/>}*/}
+                        {/*{step.dslName === 'TransformDefinition' && 
<TransformIcon/>}*/}
+                        {/*{step.dslName === 'FilterDefinition' && 
<FilterIcon/>}*/}
+                        {/*{step.dslName === 'SortDefinition' && 
<SortIcon/>}*/}
                         <img draggable={false} src={CamelUi.getIcon(step)} 
className="icon" alt="icon"/>
                     </div>
                 }
diff --git a/karavan-designer/src/designer/utils/KaravanIcons.tsx 
b/karavan-designer/src/designer/utils/KaravanIcons.tsx
index 2246fee..b40a7a9 100644
--- a/karavan-designer/src/designer/utils/KaravanIcons.tsx
+++ b/karavan-designer/src/designer/utils/KaravanIcons.tsx
@@ -217,4 +217,121 @@ export class ConceptIcon extends React.Component<any> {
             </svg>
         )
     }
+}
+
+export function AggregateIcon() {
+    return (
+        <svg xmlns="http://www.w3.org/2000/svg"; id="a" viewBox="0 0 700 700">
+            <path d="M661.91 195.36v89.37c0 3.9-3.17 7.08-7.07 
7.08H500.48c-3.9 0-7.08-3.18-7.08-7.08v-89.38l66.28 51.46.58.41c5.29 3.56 11.34 
5.35 17.4 5.35s12.11-1.79 17.4-5.35l.29-.2 66.56-51.66z"></path>
+            <path d="M640.53 184.98l-57.63 44.74a9.755 9.755 0 01-10.49 
0l-57.63-44.74h125.74zM412.03 87.97l-56.86 44.14a9.64 9.64 0 01-10.34 
0l-56.86-44.14h124.07z"></path>
+            <path d="M433.12 98.21v88.18c0 3.84-3.13 6.97-6.97 
6.97H273.87c-3.84 0-6.98-3.13-6.98-6.97v-88.2l65.39 50.77.57.41c5.22 3.52 11.18 
5.28 17.17 5.28s11.95-1.75 17.17-5.28l.28-.2 65.66-50.97zM185.21 195.35l-57.63 
44.74a9.755 9.755 0 01-10.49 0l-57.63-44.74H185.2z"></path>
+            <path d="M206.59 205.73v89.37c0 3.9-3.17 7.08-7.07 7.08H45.16c-3.9 
0-7.08-3.18-7.08-7.08v-89.38l66.28 51.46.58.41c5.29 3.56 11.34 5.35 17.4 
5.35s12.11-1.79 17.4-5.35l.29-.2 66.56-51.66z"></path>
+            <g>
+                <path d="M496.2 395.04l-130.22 
101.1c-.19.14-.39.29-.59.42a28.39 28.39 0 01-30.77 
0c-.21-.13-.4-.28-.59-.42L203.8 395.04h292.4z"></path>
+                <path d="M516.11 403.56v202.1c0 4.12-3.34 7.46-7.45 
7.46h-317.3c-4.11 0-7.46-3.34-7.46-7.46V403.55l138.52 107.53c.68.53 1.31.98 
1.94 1.38 7.79 5.04 16.72 7.55 25.66 7.55s17.86-2.52 25.66-7.55c.62-.4 1.25-.85 
1.94-1.38l138.5-107.52z"></path>
+            </g>
+            <path d="M381.11 331.09l-25.8 
20.35-.05.04-.02.02c-.19.14-.38.28-.58.41s-.4.26-.61.36c-.14.08-.28.16-.43.22-.12.06-.24.11-.36.16-.15.07-.3.12-.45.17-.11.04-.22.08-.33.11a.83.83
 0 01-.21.06l-.36.09c-.22.05-.43.1-.65.12-.19.04-.38.06-.57.07a7.961 7.961 0 
01-1.39 
0c-.24-.02-.47-.05-.7-.09-.26-.04-.51-.09-.77-.16-.2-.05-.4-.11-.6-.18-.69-.24-1.35-.57-1.97-.98-.16-.1-.3-.21-.45-.32-.03-.02-.06-.05-.09-.07l-25.83-20.38a8.496
 8.496 0 01-1.41-11.94c2.91-3.68 8.25-4.32 11.94-1.41l12.0 [...]
+            <g>
+                <path d="M617.35 343.67l-51.11 51.11 15.28 1.8c4.66.55 8 4.78 
7.45 9.44-.51 4.32-4.19 7.51-8.43 7.51-.34 0-.67-.02-1.01-.06l-32.4-3.83c-.17 
0-.33-.03-.5-.06-.11-.01-.22-.03-.32-.06-.19-.03-.38-.07-.57-.12-.22-.05-.43-.11-.64-.18-.5-.17-.98-.39-1.43-.65-.12-.06-.25-.14-.37-.22-.09-.05-.18-.11-.26-.17-.17-.11-.33-.23-.48-.34-.19-.16-.38-.32-.55-.48
 0-.01 
0-.02-.02-.02-.17-.17-.34-.34-.5-.52-.03-.03-.06-.06-.09-.1-.12-.14-.24-.29-.35-.44a.38.38
 0 01-.09-.13c-.27-.36-.5-.74-. [...]
+                <path d="M539.37 401.96a6.487 6.487 0 00.02.2l-.02-.2zM540.25 
405.01c.21.4.44.78.71 1.14-.27-.36-.51-.74-.71-1.14zM547.13 
409.64l-.29-.03-.21-.03c.17.03.33.05.5.06z"></path>
+            </g>
+            <g>
+                <path d="M160.62 400.04l-.02.19a6.796 6.796 0 
00.02-.19z"></path>
+                <path d="M164.46 367.54l-3.84 
32.5c-.02.26-.06.51-.11.77-.03.2-.08.41-.14.61-.04.16-.09.32-.14.48-.02.06-.03.11-.06.16-.04.14-.09.27-.15.4-.17.44-.39.86-.64
 
1.26-.12.19-.24.37-.38.55-.14.21-.3.4-.47.59-.17.19-.34.37-.53.55-.22.22-.46.42-.71.6-.14.11-.29.22-.44.31a.18.18
 0 
01-.07.05c-.2.13-.4.25-.62.37-.4.21-.82.4-1.26.55-.21.07-.42.13-.64.18-.19.05-.38.09-.57.12-.1.03-.2.05-.31.05-.17.04-.34.06-.52.07l-32.39
 3.83a8.5 8.5 0 01-9.44-7.45 8.501 8.501 0 017.45-9.44l15.27-1.8- [...]
+                <path d="M152.86 
407.71c.18-.01.35-.03.52-.07-.07.02-.15.03-.22.04l-.3.03z"></path>
+            </g>
+        </svg>
+    );
+}
+
+export function ChoiceIcon() {
+    return (
+        <svg xmlns="http://www.w3.org/2000/svg"; id="a" viewBox="0 0 700 700">
+            <rect
+                width="142.27"
+                height="505.23"
+                x="270.87"
+                y="80.9"
+                rx="5"
+                ry="5"
+            ></rect>
+            <path d="M662.24 257.74l-60.87 69.08a4.999 4.999 0 01-3.76 
1.69H431.14c-2.76 0-5-2.24-5-5V189.69c0-2.76 2.24-5 5-5H597.8c1.38 0 2.7.58 
3.65 1.58l60.69 64.75a4.979 4.979 0 01.1 6.72zM257.87 343.51v133.83c0 2.75-2.25 
5-5 5H86.21c-1.38 0-2.71-.58-3.65-1.58l-60.69-64.75a4.981 4.981 0 
01-.11-6.73l60.88-69.07c.95-1.08 2.31-1.7 3.75-1.7h166.48c2.75 0 5 2.25 5 
5z"></path>
+        </svg>
+    );
+}
+
+
+export function SplitIcon() {
+    return (
+        <svg xmlns="http://www.w3.org/2000/svg"; id="a" viewBox="0 0 700 700">
+            <path d="M496.2 79.83l-130.22 101.1c-.19.14-.39.29-.59.42a28.39 
28.39 0 01-30.77 0c-.21-.13-.4-.28-.59-.42L203.8 79.83h292.4zm19.91 210.63c0 
4.12-3.34 7.46-7.45 7.46h-317.3c-4.11 0-7.46-3.34-7.46-7.46V88.34l138.52 
107.54c.68.53 1.31.98 1.94 1.38 7.79 5.04 16.72 7.55 25.66 7.55s17.86-2.52 
25.66-7.55c.62-.4 1.25-.85 1.94-1.38L516.13 88.36v202.1z"></path>
+            <path d="M496.2 79.83l-130.22 101.1c-.19.14-.39.29-.59.42a28.39 
28.39 0 01-30.77 0c-.21-.13-.4-.28-.59-.42L203.8 79.83h292.4z"></path>
+            <path d="M516.1 88.35v202.1c0 4.12-3.34 7.46-7.45 
7.46H191.36c-4.11 0-7.46-3.34-7.46-7.46V88.34l138.52 107.54c.68.53 1.31.98 1.94 
1.38 7.79 5.04 16.72 7.55 25.66 7.55s17.86-2.52 25.66-7.55c.62-.4 1.25-.85 
1.94-1.38L516.13 88.36zM640.53 450.59l-57.63 44.74a9.755 9.755 0 01-10.49 
0l-57.63-44.74h125.74z"></path>
+            <path d="M661.92 460.96v89.37c0 3.9-3.17 7.08-7.07 
7.08H500.48c-3.9 0-7.08-3.18-7.08-7.08v-89.39l66.28 51.46.58.41c5.29 3.56 11.34 
5.35 17.4 5.35s12.11-1.79 17.4-5.35l.29-.2 66.56-51.66zM412.88 512.96l-57.64 
44.75a9.775 9.775 0 01-10.49 0l-57.64-44.75h125.77z"></path>
+            <path d="M434.26 523.33v89.39c0 3.9-3.17 7.07-7.07 
7.07H272.82c-3.9 0-7.08-3.17-7.08-7.07v-89.4l66.28 51.46.58.41c5.29 3.57 11.34 
5.35 17.4 5.35s12.11-1.78 17.4-5.35l.29-.2 66.56-51.66z"></path>
+            <g>
+                <path d="M185.21 460.96l-57.63 44.74a9.755 9.755 0 01-10.49 
0l-57.63-44.74H185.2z"></path>
+                <path d="M206.6 471.33v89.37c0 3.9-3.17 7.08-7.07 
7.08H45.16c-3.9 0-7.08-3.18-7.08-7.08v-89.39l66.28 51.46.58.41c5.29 3.56 11.34 
5.35 17.4 5.35s12.11-1.79 17.4-5.35l.29-.2 66.56-51.66z"></path>
+            </g>
+            <path d="M381.11 412.41l-25.8 
20.35-.05.04-.02.02c-.19.14-.38.28-.58.41s-.4.26-.61.36c-.14.08-.28.16-.43.22-.12.06-.24.11-.36.16-.15.07-.3.12-.45.17-.11.04-.22.08-.33.11a.83.83
 0 01-.21.06l-.36.09c-.22.05-.43.1-.65.12-.19.04-.38.06-.57.07a7.961 7.961 0 
01-1.39 
0c-.24-.02-.47-.05-.7-.09-.26-.04-.51-.09-.77-.16-.2-.05-.4-.11-.6-.18-.69-.24-1.35-.57-1.97-.98-.16-.1-.3-.21-.45-.32-.03-.02-.06-.05-.09-.07l-25.83-20.38a8.496
 8.496 0 01-1.41-11.94c2.91-3.68 8.25-4.32 11.94-1.41l12.0 [...]
+            <g>
+                <path d="M215.62 342.32l-51.11 51.11 15.28 1.8c4.66.55 8 4.78 
7.45 9.44-.51 4.32-4.19 7.51-8.43 7.51-.34 0-.67-.02-1.01-.06l-32.4-3.83c-.17 
0-.33-.03-.5-.06-.11-.01-.22-.03-.32-.06-.19-.03-.38-.07-.57-.12-.22-.05-.43-.11-.64-.18-.5-.17-.98-.39-1.43-.65-.12-.06-.25-.14-.37-.22-.09-.05-.18-.11-.26-.17-.17-.11-.33-.23-.48-.34-.19-.16-.38-.32-.55-.48
 0-.01 
0-.02-.02-.02-.17-.17-.34-.34-.5-.52-.03-.03-.06-.06-.09-.1-.12-.14-.24-.29-.35-.44a.38.38
 0 01-.09-.13c-.27-.36-.5-.74-. [...]
+                <path d="M137.64 400.61a6.487 6.487 0 00.02.2l-.02-.2zM138.52 
403.66c.21.4.44.78.71 1.14-.27-.36-.51-.74-.71-1.14zM145.4 
408.29l-.29-.03-.21-.03c.17.03.33.05.5.06z"></path>
+            </g>
+            <g>
+                <path d="M562.44 400.62l-.02.19a6.796 6.796 0 
00.02-.19z"></path>
+                <path d="M566.28 368.12l-3.84 
32.5c-.02.26-.06.51-.11.77-.03.2-.08.41-.14.61-.04.16-.09.32-.14.48-.02.06-.03.11-.06.16-.04.14-.09.27-.15.4-.17.44-.39.86-.64
 
1.26-.12.19-.24.37-.38.55-.14.21-.3.4-.47.59-.17.19-.34.37-.53.55-.22.22-.46.42-.71.6-.14.11-.29.22-.44.31a.18.18
 0 
01-.07.05c-.2.13-.4.25-.62.37-.4.21-.82.4-1.26.55-.21.07-.42.13-.64.18-.19.05-.38.09-.57.12-.1.03-.2.05-.31.05-.17.04-.34.06-.52.07l-32.39
 3.83a8.5 8.5 0 01-9.44-7.45 8.501 8.501 0 017.45-9.44l15.27-1.8- [...]
+                <path d="M554.68 
408.29c.18-.01.35-.03.52-.07-.07.02-.15.03-.22.04l-.3.03z"></path>
+            </g>
+        </svg>
+    );
+}
+
+export function SagaIcon() {
+    return (
+        <svg xmlns="http://www.w3.org/2000/svg"; viewBox="0 0 700 700">
+            <defs>
+                <style>{".b {fill: #fff;}"}</style>
+            </defs>
+            <path
+                
d="M616,263.74c-3.24,4.11-9.18,4.81-13.29,1.57l-31.58-24.92v198.52c0,32.76-13.59,64.51-37.28,87.12-22.2,21.19-50.77,32.66-80.97,32.66-1.86,0-3.73-.03-5.6-.12-63.15-2.95-112.63-54.83-112.63-118.12v-3.02c-44.07-4.73-78.49-42.14-78.49-87.43s34.42-82.69,78.49-87.42v-1.48c0-27.61-11.46-54.37-31.43-73.44-19.79-18.88-45.57-28.59-72.62-27.33-53.05,2.48-94.6,46.06-94.6,99.22v180.9h-.48c20.3,4.17,35.61,22.17,35.61,43.69,0,24.58-20,44.58-44.59,44.58s-44.58-20-44.58-44.58c0-21.52,15.
 [...]
+            <path className="b"
+                  
d="M334.78,376.47h-.09c-2.54-.03-4.96-1.07-6.73-2.9l-16.78-17.45c-3.62-3.77-3.5-9.75,.26-13.37,3.76-3.62,9.75-3.51,13.37,.26l10.09,10.5,28.63-28.63c3.69-3.69,9.68-3.69,13.38,0,3.69,3.69,3.69,9.68,0,13.38l-35.45,35.45c-1.77,1.77-4.18,2.77-6.69,2.77Z"/>
+        </svg>
+    );
+}
+
+export function TransformIcon() {
+    return (
+        <svg xmlns="http://www.w3.org/2000/svg"; viewBox="0 0 700 700">
+            <path d="M441.77 277.51l-82.73 64.23c-.07.05-.13.09-.19.13-5.37 
3.48-12.33 3.48-17.69.01-.07-.05-.13-.09-.18-.13l-82.76-64.24h183.54z"></path>
+            <path d="M462.2 287.02V420.7c0 .98-.79 1.77-1.77 1.77H239.57c-.98 
0-1.77-.79-1.77-1.77V287.02l90.91 70.56c.54.44 1.06.8 1.57 1.12 5.99 3.88 12.86 
5.81 19.72 5.81s13.73-1.94 19.73-5.81c.49-.32 1.01-.68 
1.58-1.13l90.89-70.55zM622.28 330.68l-35.89 31.78a1.48 1.48 0 01-1.98 
0l-35.89-31.78c-.3-.26-.48-.63-.51-1.03-.02-.4.11-.79.38-1.09l11.28-12.73c.55-.61
 1.49-.67 2.11-.12l12.44 
11.02c-5.24-51.26-28.18-99.47-64.84-136.12-35.82-35.82-81.13-58.05-131.04-64.27-.1
 0-.19-.03-.28-.06v.0 [...]
+        </svg>
+    );
+}
+
+export function FilterIcon() {
+    return (
+        <svg xmlns="http://www.w3.org/2000/svg"; id="a" viewBox="0 0 700 700">
+            <path d="M565.62 156.56L413.36 350.33a10.032 10.032 0 00-2.14 
6.18v190.52c0 19.05-25.01 34.49-55.86 
34.49s-55.86-15.44-55.86-34.49V356.51c0-2.24-.75-4.42-2.14-6.18L145.1 
156.56c-5.15-6.56-.48-16.18 7.87-16.18h404.79c8.34 0 13.02 9.62 7.86 
16.18z"></path>
+        </svg>
+    );
+}
+
+export function SortIcon() {
+    return (
+        <svg xmlns="http://www.w3.org/2000/svg"; id="a" viewBox="0 0 700 700">
+            <path d="M563.24 203.21H271.33c-5.49 
0-9.94-4.45-9.94-9.94s4.45-9.94 9.94-9.94h291.91c5.49 0 9.94 4.45 9.94 
9.94s-4.45 9.94-9.94 9.94zM516.63 312.59h-245.3c-5.49 
0-9.94-4.45-9.94-9.94s4.45-9.94 9.94-9.94h245.3c5.49 0 9.94 4.45 9.94 
9.94s-4.45 9.94-9.94 9.94zM470.02 421.98H271.33c-5.49 
0-9.94-4.45-9.94-9.94s4.45-9.94 9.94-9.94h198.69c5.49 0 9.94 4.45 9.94 
9.94s-4.45 9.94-9.94 9.94zM423.41 531.36H271.33c-5.49 
0-9.94-4.45-9.94-9.94s4.45-9.94 9.94-9.94h152.08c5.49 0 9.94 4.45 9.9 [...]
+            <g>
+                <path d="M174 531.35c.19 0 .37 0 
.55-.01-.18.02-.36.02-.54.02-1.65 0-3.3-.41-4.76-1.22 1.47.8 3.11 1.2 4.75 
1.2zM175.11 531.3c.55-.07 1.1-.18 1.64-.33-.54.16-1.09.27-1.64.33zM178.34 
530.36c-.51.26-1.04.47-1.59.61.54-.15 1.08-.36 1.59-.61z"></path>
+            </g>
+            <g>
+                <path d="M219.05 485.88c3.39 4.31 2.65 10.56-1.65 13.96l-37.24 
29.38c-.27.21-.55.41-.84.58-.16.12-.33.21-.49.29-.16.11-.33.19-.49.26-.51.25-1.05.46-1.59.61-.54.15-1.09.26-1.64.33-.19.01-.37.04-.56.05-.18.01-.36.01-.55.01-1.64
 
0-3.27-.41-4.75-1.2h-.01c-.23-.14-.47-.28-.7-.43-.23-.15-.47-.32-.69-.49-.07-.05-.13-.09-.19-.14l-37.06-29.24c-4.3-3.4-5.04-9.65-1.64-13.96
 3.39-4.3 9.65-5.04 13.95-1.64l21.15 16.67V199.08l-21.15 16.68a9.866 9.866 0 
01-6.15 2.13c-2.93 0-5.85-1.3-7.8- [...]
+                <path d="M169.24 
169.85s.02-.01.04-.01h-.01c-.06.05-.13.08-.19.12-.19.09-.36.2-.53.32.22-.15.46-.29.69-.42zM170.45
 169.3s.09-.04.14-.05c-.15.05-.29.11-.43.16.09-.05.19-.08.29-.12zM171.83 
168.88c.06-.02.11-.04.16-.04-.32.07-.63.15-.94.25.11-.04.21-.07.33-.11.15-.04.29-.07.44-.11zM174.03
 168.63c.16 0 .32 0 .48.02-.18-.01-.34-.01-.51-.01s-.34 
0-.51.01c-.16.01-.33.02-.5.04.07-.01.14-.01.21-.02.26-.02.51-.04.77-.04h.07zM174.81
 168.67c.07.01.14.01.21.02-.16-.01-.34-.02-.5-.04.0 [...]
+            </g>
+            <g>
+                <path d="M169.24 169.85s.01-.01.02 
0c-.06.04-.13.07-.19.11.05-.05.11-.07.16-.11zM170.45 
169.3c.2-.08.4-.15.61-.21-.16.05-.32.09-.47.16-.15.05-.29.11-.43.16.09-.05.19-.08.29-.12zM171.83
 
168.88c.06-.02.11-.04.16-.04-.32.07-.63.15-.94.25.11-.04.21-.07.33-.11.15-.04.29-.07.44-.11zM172
 168.85c.22-.06.46-.09.68-.12-.22.02-.44.06-.65.12h-.02zM173.19 
168.67c.09-.01.2-.01.29-.01-.16.01-.33.02-.5.04.07-.01.14-.01.21-.02zM174.81 
168.67c.07.01.14.01.21.02-.16-.01-.34-.02-.5-.04-.18-. [...]
+            </g>
+        </svg>
+    );
 }
\ No newline at end of file
diff --git a/karavan-designer/src/index.tsx b/karavan-designer/src/index.tsx
index a39991f..b8d7e70 100644
--- a/karavan-designer/src/index.tsx
+++ b/karavan-designer/src/index.tsx
@@ -33,6 +33,7 @@ render(
             <Route path="kamelets-page" element={<App page="kamelets"/>} />
             <Route path="components-page" element={<App page="components"/>} />
             <Route path="eip-page" element={<App page="eip"/>} />
+            <Route path="builder" element={<App page="builder"/>} />
         </Routes>
     </BrowserRouter>,
     rootElement
diff --git a/karavan-generator/pom.xml b/karavan-generator/pom.xml
index 845333c..3628a62 100644
--- a/karavan-generator/pom.xml
+++ b/karavan-generator/pom.xml
@@ -74,6 +74,11 @@
             <artifactId>camel-kamelets-catalog</artifactId>
             <version>${version.camel-kamelet}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-jbang-core</artifactId>
+            <version>3.17.0-SNAPSHOT</version>
+        </dependency>
     </dependencies>
     <build>
         <resources>
diff --git 
a/karavan-generator/src/main/java/org/apache/camel/karavan/generator/CamelJbangGenerator.java
 
b/karavan-generator/src/main/java/org/apache/camel/karavan/generator/CamelJbangGenerator.java
new file mode 100644
index 0000000..0eed94b
--- /dev/null
+++ 
b/karavan-generator/src/main/java/org/apache/camel/karavan/generator/CamelJbangGenerator.java
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+package org.apache.camel.karavan.generator;
+
+import org.apache.camel.dsl.jbang.core.commands.Build;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.commands.CodeGenerator;
+import org.apache.camel.dsl.jbang.core.commands.CodeRestGenerator;
+import org.apache.camel.dsl.jbang.core.commands.Deploy;
+import org.apache.camel.dsl.jbang.core.commands.Image;
+import org.apache.camel.dsl.jbang.core.commands.Manifest;
+import org.apache.camel.dsl.jbang.core.commands.Package;
+import org.apache.camel.dsl.jbang.core.commands.Run;
+import org.apache.camel.dsl.jbang.core.commands.UberJar;
+import org.apache.camel.dsl.jbang.core.commands.Undeploy;
+import picocli.CommandLine;
+
+public final class CamelJbangGenerator extends AbstractGenerator {
+
+    final static String modelHeader = 
"karavan-generator/src/main/resources/CamelMetadata.header.ts";
+    final static String targetModel = 
"karavan-core/src/core/model/CamelMetadata.ts";
+
+    public static void main(String[] args) throws Exception {
+        CamelJbangGenerator.generate();
+        System.exit(0);
+    }
+
+    public static void generate() throws Exception {
+        CamelJbangGenerator g = new CamelJbangGenerator();
+        g.createJbangDefinitions();
+    }
+
+    private void createJbangDefinitions() throws Exception {
+        StringBuilder camelModel = new StringBuilder();
+        camelModel.append(readFileText(modelHeader));
+
+
+        CommandLine commandLine = new CommandLine(new CamelJBangMain())
+                .addSubcommand("run", new CommandLine(new Run()))
+                .addSubcommand("package", new CommandLine(new Package())
+                        .addSubcommand("uber-jar", new UberJar()))
+                .addSubcommand("generate", new CommandLine(new CodeGenerator())
+                        .addSubcommand("rest", new CodeRestGenerator()))
+                .addSubcommand("build", new CommandLine(new Build())
+                        .addSubcommand("manifests", new Manifest())
+                        .addSubcommand("image", new Image()))
+                .addSubcommand("deploy", new CommandLine(new Deploy()))
+                .addSubcommand("undeploy", new CommandLine(new Undeploy()));
+
+
+
+        writeFileText(targetModel, camelModel.toString());
+    }
+
+
+}
diff --git a/karavan-vscode/icons/dark/builder.svg 
b/karavan-vscode/icons/dark/builder.svg
new file mode 100644
index 0000000..a1e57ce
--- /dev/null
+++ b/karavan-vscode/icons/dark/builder.svg
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   width="32px"
+   height="32px"
+   viewBox="0 0 32 32"
+   id="icon"
+   version="1.1"
+   sodipodi:docname="builder.svg"
+   inkscape:version="1.1.2 (b8e25be8, 2022-02-05)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:dc="http://purl.org/dc/elements/1.1/";>
+  <sodipodi:namedview
+     id="namedview834"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     showgrid="false"
+     inkscape:zoom="22.46875"
+     inkscape:cx="13.79694"
+     inkscape:cy="16.534075"
+     inkscape:window-width="1312"
+     inkscape:window-height="969"
+     inkscape:window-x="72"
+     inkscape:window-y="25"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="icon" />
+  <defs
+     id="defs826">
+    <style
+       id="style824">.cls-1{fill:none;}</style>
+  </defs>
+  <title
+     id="title828">tools</title>
+  <path
+     
d="M12.1,2A9.8,9.8,0,0,0,6.7,3.6L13.1,10a2.1,2.1,0,0,1,.2,3,2.1,2.1,0,0,1-3-.2L3.7,6.4A9.84,9.84,0,0,0,2,12.1,10.14,10.14,0,0,0,12.1,22.2a10.9,10.9,0,0,0,2.6-.3l6.7,6.7a5,5,0,0,0,7.1-7.1l-6.7-6.7a10.9,10.9,0,0,0,.3-2.6A10,10,0,0,0,12.1,2Zm8,10.1a7.61,7.61,0,0,1-.3,2.1l-.3,1.1.8.8L27,22.8a2.88,2.88,0,0,1,.9,2.1A2.72,2.72,0,0,1,27,27a2.9,2.9,0,0,1-4.2,0l-6.7-6.7-.8-.8-1.1.3a7.61,7.61,0,0,1-2.1.3,8.27,8.27,0,0,1-5.7-2.3A7.63,7.63,0,0,1,4,12.1a8.33,8.33,0,0,1,.3-2.2l4.4,4.4a4.14,4.14,0,
 [...]
+     id="path830"
+     style="stroke:#c5c5c5;stroke-opacity:1;fill:#c5c5c5;fill-opacity:1" />
+  <rect
+     id="_Transparent_Rectangle_"
+     data-name="&lt;Transparent Rectangle&gt;"
+     class="cls-1"
+     width="32"
+     height="32" />
+  <metadata
+     id="metadata916">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:title>tools</dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+</svg>
diff --git a/karavan-vscode/icons/light/builder.svg 
b/karavan-vscode/icons/light/builder.svg
new file mode 100644
index 0000000..16574e1
--- /dev/null
+++ b/karavan-vscode/icons/light/builder.svg
@@ -0,0 +1 @@
+<svg width="32px" height="32px" viewBox="0 0 32 32" id="icon" 
xmlns="http://www.w3.org/2000/svg";><defs><style>.cls-1{fill:none;}</style></defs><title>tools</title><path
 
d="M12.1,2A9.8,9.8,0,0,0,6.7,3.6L13.1,10a2.1,2.1,0,0,1,.2,3,2.1,2.1,0,0,1-3-.2L3.7,6.4A9.84,9.84,0,0,0,2,12.1,10.14,10.14,0,0,0,12.1,22.2a10.9,10.9,0,0,0,2.6-.3l6.7,6.7a5,5,0,0,0,7.1-7.1l-6.7-6.7a10.9,10.9,0,0,0,.3-2.6A10,10,0,0,0,12.1,2Zm8,10.1a7.61,7.61,0,0,1-.3,2.1l-.3,1.1.8.8L27,22.8a2.88,2.88,0,0,1,.9,2.1A2.72,2.72,0
 [...]
\ No newline at end of file
diff --git a/karavan-vscode/package.json b/karavan-vscode/package.json
index e163ae3..830e325 100644
--- a/karavan-vscode/package.json
+++ b/karavan-vscode/package.json
@@ -53,7 +53,8 @@
     "onCommand:karavan.openKamelets",
     "onCommand:karavan.openComponents",
     "onCommand:karavan.openEip",
-    "onCommand:karavan.reportIssue"
+    "onCommand:karavan.reportIssue",
+    "onCommand:karavan.projectBuilder"
   ],
   "main": "./dist/extension.js",
   "contributes": {
@@ -119,7 +120,7 @@
       },
       {
         "command": "karavan.create-yaml",
-        "title": "Karavan: Create YAML"
+        "title": "Karavan: Create Integration"
       },
       {
         "command": "karavan.open",
@@ -133,6 +134,14 @@
         "command": "karavan.open-file",
         "title": "Karavan: Open editor"
       },
+      {
+        "command": "karavan.projectBuilder",
+        "title": "Karavan: Project Builder",
+        "icon": {
+          "light": "icons/light/builder.svg",
+          "dark": "icons/dark/builder.svg"
+        }
+      },
       {
         "command": "karavan.jbang-run",
         "title": "Karavan: JBang Run",
@@ -199,6 +208,9 @@
         {
           "command": "karavan.generate-rest",
           "when": "resourceExtname == .json"
+        },
+        {
+          "command": "karavan.projectBuilder"
         }
       ],
       "editor/title": [
@@ -224,6 +236,11 @@
           "when": "view == integrations",
           "group": "navigation"
         },
+        {
+          "command": "karavan.projectBuilder",
+          "when": "view == integrations",
+          "group": "navigation"
+        },
         {
           "command": "openapi.refresh",
           "when": "view == openapi",
@@ -289,7 +306,7 @@
     ]
   },
   "scripts": {
-    "copy-designer": "cp -r ../karavan-designer/src/designer webview && cp -r 
../karavan-designer/src/kamelets webview && cp -r 
../karavan-designer/src/components webview && cp -r ../karavan-designer/src/eip 
webview",
+    "copy-designer": "cp -r ../karavan-designer/src/designer webview && cp -r 
../karavan-designer/src/kamelets webview && cp -r 
../karavan-designer/src/components webview && cp -r ../karavan-designer/src/eip 
webview && cp -r ../karavan-designer/src/builder webview",
     "vscode:prepublish": "npm run copy-designer && npm run package",
     "compile": "npm run copy-designer && cross-env NODE_ENV=development 
webpack --progress",
     "watch": "npm run copy-designer && cross-env NODE_ENV=development webpack 
--progress --watch",
diff --git a/karavan-vscode/src/builderView.ts 
b/karavan-vscode/src/builderView.ts
new file mode 100644
index 0000000..63c972d
--- /dev/null
+++ b/karavan-vscode/src/builderView.ts
@@ -0,0 +1,208 @@
+/*
+ * 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 * as vscode from "vscode";
+import * as fs from "fs";
+import * as path from "path";
+import * as utils from "./utils";
+import * as commands from "./commands";
+import { ProjectModel } from "karavan-core/lib/model/ProjectModel";
+import { ProjectModelApi } from "karavan-core/lib/api/ProjectModelApi";
+
+let builderPanel: vscode.WebviewPanel | undefined;
+const filename = "application.properties";
+
+export class BuilderView {
+
+    constructor(private context: vscode.ExtensionContext, private 
webviewContent: string, private rootPath?: string) {
+
+    }
+
+    openProject() {
+        if (builderPanel === undefined) {
+            // Karavan webview
+            builderPanel = vscode.window.createWebviewPanel(
+                "karavan",
+                "Builder",
+                vscode.ViewColumn.One,
+                {
+                    enableScripts: true,
+                    retainContextWhenHidden: true,
+                    localResourceRoots: [
+                        vscode.Uri.joinPath(this.context.extensionUri, "dist"),
+                    ],
+                }
+            );
+            builderPanel.webview.html = this.webviewContent;
+            builderPanel.iconPath = vscode.Uri.joinPath(
+                this.context.extensionUri,
+                "icons/karavan.svg"
+            );
+
+            // Handle messages from the webview
+            builderPanel.webview.onDidReceiveMessage(
+                message => {
+                    switch (message.command) {
+                        case 'getData':
+                            this.sendData("builder");
+                            break;
+                        case 'saveProject':
+                            this.saveProject(message.project);
+                            break;
+                        case 'action':
+                            this.actionProject(message.action, 
message.project);
+                            break;
+                    }
+                },
+                undefined,
+                this.context.subscriptions
+            );
+            // Handle close event
+            builderPanel.onDidDispose(() => {
+                builderPanel = undefined;
+                console.log("dispose");
+            }, null, this.context.subscriptions);
+
+            // Handle reopen
+            builderPanel.onDidChangeViewState((e: 
vscode.WebviewPanelOnDidChangeViewStateEvent) => {
+                if (e.webviewPanel.active) {
+                    e.webviewPanel.webview.postMessage({ command: 'reread' })
+                }
+            });
+        } else {
+            builderPanel?.reveal(undefined, true);
+            builderPanel?.webview.postMessage({ command: 'activate' });
+        }
+    }
+
+    sendData(page: string) {
+        builderPanel?.webview.postMessage({ command: 'open', page: page });
+        console.log(this.rootPath);
+        if (this.rootPath) {
+            const [project, files] = this.readProjectInfo(this.rootPath);
+            // Send data
+            builderPanel?.webview.postMessage({ command: 'project', files: 
files, project: project });
+        }
+    }
+
+    actionProject(action: "start" | "stop" | "undeploy", project: 
ProjectModel) {
+        switch (action){
+            case "start" : this.start(project); break;
+            case "stop" : {}; break;
+            case "undeploy" : this.undelpoy(project); break;
+        }
+    }
+
+    start(project: ProjectModel) {
+        const [x, files] = this.readProjectInfo(this.rootPath || '');
+        project.status.active = true;
+        project.status.uberJar = "pending";
+        project.status.build = "pending";
+        project.status.deploy = "pending";
+        project.status.undeploy = "pending";
+        if (project.uberJar) {
+            project.status.uberJar = "progress";
+            builderPanel?.webview.postMessage({ command: 'project', files: 
files, project: project });
+            this.package(project, files);
+        }
+    }
+
+    package(project: ProjectModel, files: string) {
+        console.log("package", project);
+        project.status.uberJar = "progress";
+        builderPanel?.webview.postMessage({ command: 'project', files: files, 
project: project });
+
+        commands.camelJbangPackage(this.rootPath || "", code => {
+            project.status.uberJar = code === 0 ? "done" : "error";
+            builderPanel?.webview.postMessage({ command: 'project', files: 
files, project: project });
+            if (code === 0 && project.build) {
+                this.buildImage(project, files);
+            } else {
+                this.finish(project, files, code);
+            }
+        });
+    }
+
+    buildImage(project: ProjectModel, files: string) {
+        console.log("buildImage", project);
+        project.status.build = "progress";
+        builderPanel?.webview.postMessage({ command: 'project', files: files, 
project: project });
+
+        commands.camelJbangBuildImage(this.rootPath || "", project,  code => {
+            project.status.build = code === 0 ? "done" : "error";
+            builderPanel?.webview.postMessage({ command: 'project', files: 
files, project: project });
+            if (code === 0 && project.deploy) {
+                this.deploy(project, files);
+            } else {
+                this.finish(project, files, code);
+            }
+        });
+    }
+
+    deploy(project: ProjectModel, files: string) {
+        console.log("deploy", project);
+        project.status.deploy = "progress";
+        builderPanel?.webview.postMessage({ command: 'project', files: files, 
project: project });
+
+        commands.camelJbangDeploy(this.rootPath || "", project, code => {
+            project.status.deploy = code === 0 ? "done" : "error";
+            builderPanel?.webview.postMessage({ command: 'project', files: 
files, project: project });
+            this.finish(project, files, code);
+        });
+    }
+
+
+    finish(project: ProjectModel, files: string,  code: number) {
+        console.log("finish", project);
+        if (project.cleanup) commands.cleanup(this.rootPath || "", project, () 
=> {})
+        setTimeout(() => {
+            project.status.active = false;
+            builderPanel?.webview.postMessage({ command: 'project', files: 
files, project: project });
+        }, 1000);
+    }
+
+    saveProject(project: ProjectModel) {
+        let properties = ''
+        try {
+            properties = fs.readFileSync(path.resolve(this.rootPath || '', 
filename)).toString('utf8');
+        } catch (err: any) {
+            if (err.code !== 'ENOENT') throw err;
+        }
+        const newProperties = ProjectModelApi.updateProperties(properties, 
project);
+        utils.save(filename, newProperties);
+    }
+
+    readProjectInfo(rootPath: string): [ProjectModel, string] {
+        const files = utils.getAllFiles(rootPath, []).map(f => 
utils.getRalativePath(f)).join(",");
+        let project = ProjectModel.createNew();
+        try {
+            const properties = fs.readFileSync(path.resolve(rootPath, 
filename)).toString('utf8');
+            project = ProjectModelApi.propertiesToProject(properties);
+        } catch (err: any) {
+            if (err.code !== 'ENOENT') throw err;
+        }
+        return [project, files];
+    }
+
+    undelpoy(project: ProjectModel) {
+        const [x, files] = this.readProjectInfo(this.rootPath || '');
+        console.log("undelpoy", project);
+        project.status.active = true;
+        project.status.undeploy = "progress";
+        builderPanel?.webview.postMessage({ command: 'project', files: files, 
project: project });
+        commands.camelJbangUndeploy(this.rootPath || '', project, (code) => 
this.finish(project, files, code));
+    }
+}
diff --git a/karavan-vscode/src/commands.ts b/karavan-vscode/src/commands.ts
new file mode 100644
index 0000000..6be1b06
--- /dev/null
+++ b/karavan-vscode/src/commands.ts
@@ -0,0 +1,157 @@
+/*
+ * 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 * as vscode from "vscode";
+import * as fs from "fs";
+import * as path from "path";
+import * as shell from 'shelljs';
+import { CamelDefinitionYaml } from "karavan-core/lib/api/CamelDefinitionYaml";
+import { ProjectModel } from "karavan-core/lib/model/ProjectModel";
+
+export function camelJbangGenerate(rootPath: string, openApiFullPath: string, 
fullPath: string, add: boolean, crd?: boolean, generateRoutes?: boolean) {
+    let command = prepareCommand("generate rest -i " + openApiFullPath);
+    if (generateRoutes === true) command = command + " --routes";
+    executeJbangCommand(rootPath, command, (code, stdout, stderr) => {
+        console.log('Exit code:', code);
+        if (code === 0) {
+            // console.log('Program output:', stdout);
+            const filename = path.basename(fullPath);
+            let yaml;
+            if (add) {
+                const camelYaml = 
fs.readFileSync(path.resolve(fullPath)).toString('utf8');
+                yaml = createYaml(filename, stdout, camelYaml, undefined);
+            } else {
+                yaml = createYaml(filename, stdout, undefined, crd);
+            }
+            const uriFile: vscode.Uri = vscode.Uri.file(fullPath);
+            fs.writeFile(uriFile.fsPath, yaml, err => {
+                if (err) vscode.window.showErrorMessage("Error: " + 
err?.message);
+                else {
+                    vscode.commands.executeCommand('integrations.refresh')
+                        .then(x => 
vscode.commands.executeCommand('karavan.open', { fsPath: fullPath, tab: 'rest' 
}));
+                }
+            });
+        } else {
+            vscode.window.showErrorMessage(stderr);
+        }
+    });
+}
+
+export function createYaml(filename: string, restYaml: string, camelYaml?: 
string, crd?: boolean): string {
+    if (camelYaml) {
+        const i = CamelDefinitionYaml.yamlToIntegration(filename, camelYaml);
+        const rest = CamelDefinitionYaml.yamlToIntegration(filename, restYaml);
+        i.spec.flows = i.spec.flows?.filter(f => f.dslName !== 
'RestDefinition');
+        i.spec.flows?.push(...rest.spec.flows || []);
+        return CamelDefinitionYaml.integrationToYaml(i);
+    } else if (crd === true) {
+        const i = CamelDefinitionYaml.yamlToIntegration(filename, restYaml);
+        i.crd = true;
+        return CamelDefinitionYaml.integrationToYaml(i);
+    } else {
+        return restYaml;
+    }
+}
+
+export function camelJbangPackage(rootPath: string, callback: (code: number) 
=> any) {
+    executeJbangCommand(rootPath, prepareCommand("package uber-jar"), (code, 
stdout, stderr) => callback(code));
+}
+
+export function camelJbangBuildImage(rootPath: string, project: ProjectModel, 
callback: (code: number) => any) {
+    const munikubeCommand = "minikube -p minikube docker-env";
+    let command = prepareCommand("build image", project);
+    if (project.target === 'minikube') {
+        console.log("Build in minikube")
+        executeJbangCommand(rootPath, munikubeCommand, (code, stdout, stderr) 
=> {
+            if (code === 0) {
+                setMinikubeEnvVariables(stdout).forEach((value: string, key: 
string) => shell.env[key] = value);
+                executeJbangCommand(rootPath, command, (code, stdout, stderr) 
=> callback(code));
+            }
+        })
+    } else {
+        removeMinikubeEnvVariables();
+        console.log(shell.env)
+        executeJbangCommand(rootPath, command, (code, stdout, stderr) => 
callback(code));
+    }
+}
+
+export function camelJbangDeploy(rootPath: string, project: ProjectModel, 
callback: (code: number) => any) {
+    executeJbangCommand(rootPath, prepareCommand("deploy", project), (code, 
stdout, stderr) => callback(code));
+}
+
+export function camelJbangManifests(rootPath: string, project: ProjectModel, 
callback: (code: number) => any) {
+    executeJbangCommand(rootPath, prepareCommand("build manifests", project), 
(code, stdout, stderr) => callback(code));
+}
+
+
+export function camelJbangUndeploy(rootPath: string, project: ProjectModel, 
callback: (code: number) => any) {
+    executeJbangCommand(rootPath, prepareCommand("undeploy", project), (code, 
stdout, stderr) => callback(code));
+}
+
+export function cacheClear(rootPath: string, callback: (code: number) => any) {
+    executeJbangCommand(rootPath, "jbang cache clear", (code, stdout, stderr) 
=> callback(code));
+}
+
+function prepareCommand(command: string, project?: ProjectModel): string {
+    const version = vscode.workspace.getConfiguration().get("camel.version");
+    const token = project && project.target === 'openshift' ? " --token " + 
project.token : "";
+    return "jbang -Dcamel.jbang.version=" + version + " camel@apache/camel " + 
command + token;
+}
+
+function executeJbangCommand(rootPath: string, command: string, callback: 
(code: number, stdout: any, stderr: any) => any) {
+    const jbang = shell.which('jbang');
+    if (jbang) {
+        shell.config.execPath = String(jbang);
+        shell.cd(rootPath);
+        shell.exec(command, { async: false }, (code, stdout, stderr) => {
+            if (code === 0) {
+                // vscode.window.showInformationMessage(stdout);
+            } else {
+                vscode.window.showErrorMessage(stderr);
+            }
+            callback(code, stdout, stderr);
+        });
+    } else {
+        vscode.window.showErrorMessage("JBang not found!");
+    }
+}
+
+function setMinikubeEnvVariables(env: string): Map<string, string> {
+    const map = new Map<string, string>();
+    const linesAll = env.split(/\r?\n/);
+    const vars = linesAll.filter(l => l !== undefined && 
l.startsWith("export")).map(line => line.replace("export", ""));
+    vars.forEach(line => {
+        const parts = line.split("=");
+        const key = parts[0].trim();
+        const value = parts[1].replaceAll('"', '').trim();
+        map.set(key, value);
+    })
+    return map;
+}
+
+function removeMinikubeEnvVariables() {
+    delete shell.env['DOCKER_TLS_VERIFY'];
+    delete shell.env['DOCKER_HOST'];
+    delete shell.env['DOCKER_CERT_PATH'];
+    delete shell.env['MINIKUBE_ACTIVE_DOCKERD'];
+}
+
+export function cleanup(rootPath: string, project: ProjectModel, callback: 
(code: number, stdout: any, stderr: any) => any) {
+    shell.cd(rootPath);
+    shell.rm('-r', path.resolve(rootPath, ".camel-jbang"));
+    shell.rm(path.resolve(rootPath, project.filename));  
+}
+
diff --git a/karavan-vscode/src/extension.ts b/karavan-vscode/src/extension.ts
index 8395223..854688d 100644
--- a/karavan-vscode/src/extension.ts
+++ b/karavan-vscode/src/extension.ts
@@ -20,6 +20,7 @@ import { DesignerView } from "./designerView";
 import {IntegrationView} from "./integrationView";
 import { HelpView } from "./helpView";
 import { selectFileName, inputFileName, OpenApiView, OpenApiItem } from 
"./openapiView";
+import { BuilderView } from './builderView';
 
 const KARAVAN_LOADED = "karavan:loaded";
 
@@ -62,6 +63,9 @@ export function activate(context: vscode.ExtensionContext) {
     vscode.commands.registerCommand('karavan.openComponents', () => 
helpView.openKaravanWebView("components"));
     vscode.commands.registerCommand('karavan.openEip', () => 
helpView.openKaravanWebView("eip"));
 
+    const builderView = new BuilderView(context, webviewContent, rootPath);
+    vscode.commands.registerCommand("karavan.projectBuilder", (...args: any[]) 
=> builderView.openProject());
+
     // Create new Integration CRD command
     const createCrd = vscode.commands.registerCommand("karavan.create-crd", 
(...args: any[]) => {
         if (args.length > 0) designer.createIntegration(true, args[0].fsPath)
diff --git a/karavan-vscode/src/openapiView.ts 
b/karavan-vscode/src/openapiView.ts
index eb169dc..f0c34b6 100644
--- a/karavan-vscode/src/openapiView.ts
+++ b/karavan-vscode/src/openapiView.ts
@@ -17,6 +17,7 @@
 import vscode, { window } from "vscode";
 import * as path from "path";
 import * as utils from "./utils";
+import * as commands from "./commands";
 import * as fs from "fs";
 import { ThemeIcon } from "vscode";
 import { CamelDefinitionYaml } from "karavan-core/lib/api/CamelDefinitionYaml";
@@ -74,14 +75,14 @@ export class OpenApiItem extends vscode.TreeItem {
 /**
  * Select routes generation
  */
-export async function selectRouteGeneration(openApiFullPath: string, fullPath: 
string, add: boolean, crd?: boolean) {
+export async function selectRouteGeneration(rootPath: string, openApiFullPath: 
string, fullPath: string, add: boolean, crd?: boolean) {
        const options = ["Generate REST and Routes", 'Generate REST only'];
        await window.showQuickPick(options, {
                title: "Generate route stubs for REST API",
                placeHolder: 'Select option',
        }).then(option => {
                const generateRoutes: boolean = option !== undefined && option 
=== options[0];
-               utils.camelJbangGenerate(openApiFullPath, fullPath, add, crd, 
generateRoutes);
+               commands.camelJbangGenerate(rootPath, openApiFullPath, 
fullPath, add, crd, generateRoutes);
        });
 }
 
@@ -96,7 +97,7 @@ export async function selectFileName(rootPath?: string, 
openApi?: OpenApiItem) {
                        placeHolder: 'Select file',
                }).then(fullPath => {
                        if (fullPath && openApi?.fsPath) {
-                               selectRouteGeneration(openApi.fsPath, fullPath, 
true, undefined);
+                               selectRouteGeneration(rootPath, openApi.fsPath, 
fullPath, true, undefined);
                        }
                });
        }
@@ -118,9 +119,9 @@ export async function inputFileName(crd: boolean, 
rootPath?: string, openApi?: O
                        }
                }
        }).then(filename => {
-               if (filename && openApi?.fsPath) {
+               if (filename && openApi?.fsPath && rootPath) {
                        const fullPath = rootPath + path.sep + filename;
-                       selectRouteGeneration(openApi.fsPath, fullPath, false, 
crd);
+                       selectRouteGeneration(rootPath, openApi.fsPath, 
fullPath, false, crd);
                }
        });
 }
\ No newline at end of file
diff --git a/karavan-vscode/src/utils.ts b/karavan-vscode/src/utils.ts
index df000fb..c5c67a3 100644
--- a/karavan-vscode/src/utils.ts
+++ b/karavan-vscode/src/utils.ts
@@ -22,11 +22,11 @@ import { CamelDefinitionYaml } from 
"karavan-core/lib/api/CamelDefinitionYaml";
 
 const TERMINALS: Map<string, vscode.Terminal> = new Map<string, 
vscode.Terminal>();
 
-export function save(relativePath: string, yaml: string){
+export function save(relativePath: string, text: string){
     if (vscode.workspace.workspaceFolders) {
         const uriFolder: vscode.Uri = vscode.workspace.workspaceFolders[0].uri;
         const uriFile: vscode.Uri = vscode.Uri.file(path.join(uriFolder.path, 
relativePath));
-        fs.writeFile(uriFile.fsPath, yaml, err => {
+        fs.writeFile(uriFile.fsPath, text, err => {
             if (err) vscode.window.showErrorMessage("Error: " + err?.message);
         });
     }
@@ -105,7 +105,7 @@ export function nameFromTitle(title: string): string {
     return title.replace(/[^a-z0-9+]+/gi, "-").toLowerCase();
 }
 
-function getAllFiles (dirPath, arrayOfFiles: string[]): string[]  {
+export function getAllFiles (dirPath, arrayOfFiles: string[]): string[]  {
     const files = fs.readdirSync(dirPath)
   
     arrayOfFiles = arrayOfFiles || []
@@ -141,56 +141,4 @@ export function getIntegrationFiles(baseDir: string): 
string[]{
         const yaml = fs.readFileSync(path.resolve(f)).toString('utf8');
         return !f.startsWith(baseDir + path.sep + "target") && 
CamelDefinitionYaml.yamlIsIntegration(yaml);
     });
-}
-
-export function camelJbangGenerate(openApiFullPath: string, fullPath: string,  
add: boolean, crd?: boolean, generateRoutes?: boolean) {
-    const version = vscode.workspace.getConfiguration().get("camel.version");
-    let command = "jbang -Dcamel.jbang.version=" + version + " 
camel@apache/camel generate rest -i " + openApiFullPath;
-    if (generateRoutes === true) command = command + " --routes";
-    const jbang = shell.which('jbang');
-    if (jbang){
-        shell.config.execPath = String(jbang);
-        shell.exec(command, { async: true }, (code, stdout, stderr) => {
-            console.log('Exit code:', code);
-            if (code === 0){
-                // console.log('Program output:', stdout);
-                const filename = path.basename(fullPath);
-                let yaml;
-                if (add) {
-                    const camelYaml = 
fs.readFileSync(path.resolve(fullPath)).toString('utf8');
-                    yaml = createYaml(filename, stdout, camelYaml, undefined);
-                } else {
-                    yaml = createYaml(filename, stdout, undefined, crd);
-                }
-                const uriFile: vscode.Uri = vscode.Uri.file(fullPath);
-                fs.writeFile(uriFile.fsPath, yaml, err => {
-                    if (err) vscode.window.showErrorMessage("Error: " + 
err?.message);
-                    else {
-                        vscode.commands.executeCommand('integrations.refresh')
-                        .then(x => 
vscode.commands.executeCommand('karavan.open', {fsPath: fullPath, tab: 
'rest'}));
-                    }
-                });
-            } else {
-                vscode.window.showErrorMessage(stderr);
-            }
-        });
-    }
-}
-
-export function createYaml(filename: string, restYaml: string, camelYaml?: 
string, crd?: boolean): string {
-    if (camelYaml) {
-        const i = CamelDefinitionYaml.yamlToIntegration(filename, camelYaml);
-        const rest = CamelDefinitionYaml.yamlToIntegration(filename, restYaml);
-        i.spec.flows = i.spec.flows?.filter(f => f.dslName !== 
'RestDefinition');
-        i.spec.flows?.push(...rest.spec.flows || []);
-        return CamelDefinitionYaml.integrationToYaml(i);
-    } else if (crd === true){
-        const i = CamelDefinitionYaml.yamlToIntegration(filename, restYaml);
-        i.crd = true;
-        return CamelDefinitionYaml.integrationToYaml(i);
-    } else {
-        return restYaml;
-    }
-}
-
-
+}
\ No newline at end of file
diff --git a/karavan-vscode/webview/App.tsx b/karavan-vscode/webview/App.tsx
index b9b418a..e3cd816 100644
--- a/karavan-vscode/webview/App.tsx
+++ b/karavan-vscode/webview/App.tsx
@@ -25,6 +25,8 @@ import { ComponentApi } from 
"karavan-core/lib/api/ComponentApi";
 import { KameletsPage } from "./kamelets/KameletsPage";
 import { ComponentsPage } from "./components/ComponentsPage";
 import { EipPage } from "./eip/EipPage";
+import { BuilderPage } from "./builder/BuilderPage";
+import { ProjectModel } from "karavan-core/lib/model/ProjectModel";
 
 interface Props {
   dark: boolean
@@ -40,9 +42,11 @@ interface State {
   scheduledYaml: string
   hasChanges: boolean
   showStartHelp: boolean
-  page: "designer" | "kamelets" | "components" | "eip"
+  page: "designer" | "kamelets" | "components" | "eip" | "builder"
   active: boolean
   tab?: string
+  files: string
+  project: ProjectModel
 }
 
 class App extends React.Component<Props, State> {
@@ -57,7 +61,9 @@ class App extends React.Component<Props, State> {
     hasChanges: false,
     showStartHelp: false,
     page: "designer",
-    active: false
+    active: false,
+    files: '',
+    project: ProjectModel.createNew()
   };
 
   saveScheduledChanges = () => {
@@ -91,6 +97,10 @@ class App extends React.Component<Props, State> {
       case 'showStartHelp':
         this.setState({ showStartHelp: message.showStartHelp });
         break;
+      case 'project':
+        this.setState({ project: message.project, files: message.files, key: 
Math.random().toString() });
+        console.log(message.project)
+        break;
       case 'open':
         if (this.state.filename === '' && this.state.key === '') {
           if (message.page !== "designer" && this.state.interval) 
clearInterval(this.state.interval);
@@ -102,7 +112,7 @@ class App extends React.Component<Props, State> {
             relativePath: message.relativePath,
             key: Math.random().toString(),
             loaded: true,
-            active: true, 
+            active: true,
             tab: message.tab
           });
         }
@@ -128,6 +138,14 @@ class App extends React.Component<Props, State> {
     }
   }
 
+  saveProject(project: ProjectModel) {
+    vscode.postMessage({ command: 'saveProject', project: project });
+  }
+
+  actionProject(action: "start" | "stop" | "undeploy", project: ProjectModel) {
+    vscode.postMessage({ command: 'action', action: action, project: project 
});
+  }
+
   disableStartHelp() {
     vscode.postMessage({ command: 'disableStartHelp' });
   }
@@ -154,6 +172,11 @@ class App extends React.Component<Props, State> {
         {this.state.loaded && this.state.page === "kamelets" && <KameletsPage 
dark={this.props.dark} />}
         {this.state.loaded && this.state.page === "components" && 
<ComponentsPage dark={this.props.dark} />}
         {this.state.loaded && this.state.page === "eip" && <EipPage 
dark={this.props.dark} />}
+        {this.state.loaded && this.state.page === "builder" &&
+          <BuilderPage key={this.state.key} dark={this.props.dark} 
files={this.state.files} project={this.state.project}
+            onChange={project => this.saveProject(project)}
+            onAction={(action, project) => this.actionProject(action, project)}
+          />}
       </Page>
     )
   }
diff --git a/karavan-vscode/webview/builder/BuilderPage.tsx 
b/karavan-vscode/webview/builder/BuilderPage.tsx
new file mode 100644
index 0000000..903b909
--- /dev/null
+++ b/karavan-vscode/webview/builder/BuilderPage.tsx
@@ -0,0 +1,326 @@
+import React from 'react';
+import {
+    Toolbar,
+    ToolbarContent,
+    ToolbarItem,
+    TextInput,
+    PageSection,
+    TextContent,
+    Text,
+    PageSectionVariants,
+    Flex,
+    FlexItem,
+    Badge,
+    Button,
+    FormGroup,
+    Form,
+    Card,
+    CardTitle,
+    CardBody,
+    CardFooter,
+    CardHeader,
+    CardHeaderMain,
+    CardActions,
+    Checkbox,
+    Switch,
+    ToggleGroup,
+    ToggleGroupItem,
+    PopoverPosition,
+    Popover,
+    InputGroup,
+    ProgressStep,
+    ProgressStepper,
+    Spinner,
+    HelperTextItem, HelperText
+} from '@patternfly/react-core';
+import '../designer/karavan.css';
+import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon";
+import InProgressIcon from 
'@patternfly/react-icons/dist/esm/icons/in-progress-icon';
+import AutomationIcon from 
'@patternfly/react-icons/dist/esm/icons/bundle-icon';
+import PendingIcon from '@patternfly/react-icons/dist/esm/icons/pending-icon';
+import ExclamationCircleIcon from 
'@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon';
+import CheckCircleIcon from 
'@patternfly/react-icons/dist/esm/icons/check-circle-icon';
+import RunIcon from '@patternfly/react-icons/dist/esm/icons/forward-icon';
+import StopIcon from '@patternfly/react-icons/dist/esm/icons/stop-icon';
+import JarIcon from '@patternfly/react-icons/dist/esm/icons/hotjar-icon';
+import ImageIcon from '@patternfly/react-icons/dist/esm/icons/docker-icon';
+import DeployIcon from 
'@patternfly/react-icons/dist/esm/icons/cloud-upload-alt-icon';
+import CleanupIcon from '@patternfly/react-icons/dist/esm/icons/remove2-icon';
+import ProjectIcon from '@patternfly/react-icons/dist/esm/icons/cubes-icon';
+import {FileSelector} from "./FileSelector";
+import {ProjectModel, ProjectStatus} from 
"karavan-core/lib/model/ProjectModel";
+
+interface Props {
+    dark: boolean
+    project: ProjectModel
+    files: string
+    onChange?: (project: ProjectModel) => void
+    onAction?: (action: "start" | "stop" | "undeploy", project: ProjectModel) 
=> void
+}
+
+interface State {
+    name: string,
+    version: string,
+    filename: string,
+    namespace: string,
+    tag?: string,
+    sourceImage: string,
+    from: string,
+    replicas: number,
+    nodePort: number,
+    server?: string,
+    token?: string,
+    target: 'openshift' | 'minikube' | 'kubernetes',
+    deploy: boolean,
+    build: boolean,
+    uberJar: boolean,
+    routesIncludePattern: string,
+    classpathFiles: string,
+    status: ProjectStatus,
+    cleanup: boolean,
+    manifests: boolean,
+    path: string,
+}
+
+export class BuilderPage extends React.Component<Props, State> {
+
+    public state: State = this.props.project;
+
+    componentDidUpdate = (prevProps: Readonly<Props>, prevState: 
Readonly<State>, snapshot?: any) => {
+        this.props.onChange?.call(this, this.state);
+    }
+
+    getHelp(text: string) {
+        return <Popover
+            aria-label={text}
+            position={PopoverPosition.left}
+            bodyContent={text}>
+            <Button variant="plain" onClick={e => {
+            }}>
+                <HelpIcon/>
+            </Button>
+        </Popover>
+    }
+
+    getField(name: string, label: string, type: 'text' | 'date' | 
'datetime-local' | 'email' | 'month' | 'number' | 'password' | 'search' | 'tel' 
| 'time' | 'url',
+             value: any, help: string, onChange: (val: any) => void, 
isRequired: boolean = false, enabled: boolean = true) {
+        return <FormGroup label={label} fieldId={name} isRequired={isRequired}>
+            <InputGroup>
+                <TextInput isRequired={isRequired} isDisabled={!enabled} 
className="text-field" type={type} id={name} name={name} value={value}
+                           onChange={val => onChange?.call(this, val)}/>
+                {this.getHelp(help)}
+            </InputGroup>
+        </FormGroup>
+    }
+
+    getCardHeader(title: string, icon: any, optional: boolean = true, checked: 
boolean = false, onCheck?: (check: boolean) => void) {
+        return <CardHeader>
+            <CardHeaderMain>
+                <CardTitle className="card-header">
+                    {icon}{title}
+                </CardTitle>
+            </CardHeaderMain>
+            <CardActions hasNoOffset={true}>
+                {optional && <Switch isChecked={checked} onChange={checked => 
onCheck?.call(this, checked)} id={title} name={title} aria-label={"xxx"}/>}
+            </CardActions>
+        </CardHeader>
+    }
+
+    getProjectForm() {
+        return (
+            <Card className="builder-card" isCompact style={{width: "100%"}}>
+                {this.getCardHeader("Project", <ProjectIcon/>, false)}
+                <CardBody>
+                    <Form isHorizontal>
+                        {this.getField("name", "Name", "text", 
this.state.name, "Project name", val => this.setState({name: val}), true)}
+                        {this.getField("version", "Version", "text", 
this.state.version, "Project version", val => this.setState({version: val}), 
true)}
+                    </Form>
+                </CardBody>
+            </Card>
+        )
+    }
+
+    getPackageForm() {
+        const {uberJar, classpathFiles, routesIncludePattern} = this.state;
+        return <Card className="builder-card" isCompact style={{width: 
"100%"}}>
+            {this.getCardHeader("Package", <JarIcon/>, true, 
this.state.uberJar, check => this.setState({uberJar: check}))}
+            <CardBody className={uberJar ? "" : "card-disabled"}>
+                <Form isHorizontal>
+                    {this.getField("filename", "Jar", "text", 
this.state.filename, "Jar file name", val => this.setState({filename: val}), 
true, uberJar)}
+                    {this.props.files.length >0 &&
+                        <FileSelector source={true} label="Route/source files" 
help="Routes and source to build and package" files={this.props.files} 
filesSelected={routesIncludePattern} onChange={filesSelected => 
this.setState({routesIncludePattern: filesSelected})}/>}
+                    {this.props.files.length >0 &&
+                        <FileSelector source={false} label="Resources" 
help="Files to package as resources" files={this.props.files} 
filesSelected={classpathFiles} onChange={filesSelected => 
this.setState({classpathFiles: filesSelected})}/>}
+                </Form>
+            </CardBody>
+        </Card>
+    }
+
+    getBuildForm() {
+        const {target, namespace, build, tag, sourceImage, server, token, 
from} = this.state;
+        return <Card className="builder-card" isCompact style={{width: 
"100%"}}>
+            {this.getCardHeader("Build", <ImageIcon/>, true, this.state.build, 
check => this.setState({build: check}))}
+            <CardBody className={build ? "" : "card-disabled"}>
+                <Form isHorizontal>
+                    <FormGroup label="Target" fieldId="tag" isRequired 
disabled={true}>
+                        <ToggleGroup aria-label="Select target">
+                            <ToggleGroupItem isDisabled={!build} 
text="Minikube" buttonId="minikube" isSelected={target === 'minikube'}
+                                             onChange={selected => selected ? 
this.setState({target: 'minikube'}) : {}}/>
+                            <ToggleGroupItem isDisabled={!build} 
text="Kubernetes" buttonId="kubernetes" isSelected={target === 'kubernetes'}
+                                             onChange={selected => selected ? 
this.setState({target: 'kubernetes'}) : {}}/>
+                            <ToggleGroupItem isDisabled={!build} 
text="Openshift" buttonId="openshift" isSelected={target === 'openshift'}
+                                             onChange={selected => selected ? 
this.setState({target: 'openshift'}) : {}}/>
+                        </ToggleGroup>
+                    </FormGroup>
+                    {this.getField("namespace", "Namespace", "text", 
namespace, "Namespace to build and/or deploy", val => this.setState({namespace: 
val}), true, build)}
+                    {this.getField("tag", "Image tag", "text", tag, "Image 
tag", val => this.setState({tag: val}), true, build)}
+                    {target !== 'openshift' && this.getField("from", "Base 
Image", "text", from, "Base Image", val => this.setState({from: val}), true, 
build)}
+                    {target === 'openshift' && this.getField("sourceImage", 
"Source tag", "text", sourceImage, "Source image name (for OpenShift 
BuildConfig)", val => this.setState({sourceImage: val}), true, build)}
+                    {target === 'openshift' && this.getField("server", 
"Server", "text", server, "Master URL", val => this.setState({server: val}), 
true, build)}
+                    {target === 'openshift' && this.getField("token", "Token", 
"password", token, "Authentication Token", val => this.setState({token: val}), 
true, build)}
+                </Form>
+            </CardBody>
+        </Card>
+    }
+
+    getDeployForm() {
+        const {target, deploy, build} = this.state;
+        return <Card className="builder-card" isCompact style={{width: 
"100%"}}>
+            {this.getCardHeader("Deploy", <DeployIcon/>, true, deploy, check 
=> this.setState({deploy: check}))}
+            <CardBody className={build ? "" : "card-disabled"}>
+                <Form isHorizontal>
+                    {deploy && this.getField("replicas", "Replicas", "number", 
this.state.replicas, "Number of replicas of the application", val => 
this.setState({replicas: val}), true, build)}
+                    {deploy && target === 'minikube' && 
this.getField("nodePort", "Node port", "number", this.state.nodePort, "Node 
port (minikube)", val => this.setState({nodePort: val}), true, build)}
+                </Form>
+            </CardBody>
+        </Card>
+    }
+
+    getCleanupForm() {
+        const {cleanup} = this.state;
+        return <Card className="builder-card" isCompact style={{width: 
"100%"}}>
+            {this.getCardHeader("Cleanup", <CleanupIcon/>, true, cleanup, 
check => this.setState({cleanup: check}))}
+            <CardBody className={cleanup ? "" : "card-disabled"}>
+                <HelperText>
+                    <HelperTextItem variant="indeterminate">Remove created jar 
and .camel-jbang after build and/or deploy.</HelperTextItem>
+                </HelperText>
+            </CardBody>
+        </Card>
+    }
+
+    getProgressIcon(status: 'pending' | 'progress' | 'done' | 'error') {
+        switch (status) {
+            case "pending":
+                return <PendingIcon/>;
+            case "progress":
+                return <Spinner isSVG size="md"/>
+            case "done":
+                return <CheckCircleIcon/>;
+            case "error":
+                return <ExclamationCircleIcon/>;
+            default:
+                return undefined;
+        }
+    }
+
+    getProgress() {
+        const {status, uberJar, build, deploy} = this.state;
+        const undeploying = status.active && status.undeploy === "progress";
+        return (
+            <ProgressStepper isCenterAligned style={{visibility: "visible"}}>
+                {!undeploying && uberJar && <ProgressStep variant="pending" 
id="package" titleId="package" aria-label="package" 
icon={this.getProgressIcon(status.uberJar)}>Package</ProgressStep>}
+                {!undeploying && build && <ProgressStep variant="pending" 
isCurrent id="build" titleId="build" aria-label="build" 
icon={this.getProgressIcon(status.build)}>Build</ProgressStep>}
+                {!undeploying && deploy && <ProgressStep variant="pending" 
id="deploy" titleId="deploy" aria-label="deploy" 
icon={this.getProgressIcon(status.deploy)}>Deploy</ProgressStep>}
+                {undeploying &&
+                    <ProgressStep variant="pending" id="undeploy" 
titleId="undeploy" aria-label="undeploy" 
icon={this.getProgressIcon(status.undeploy)}>Undeploy</ProgressStep>}
+            </ProgressStepper>
+        )
+    }
+
+    getHeader() {
+        return (
+            <PageSection className="tools-section" variant={this.props.dark ? 
PageSectionVariants.darker : PageSectionVariants.light}>
+                <Flex className="tools" direction={{default: 'row'}} 
justifyContent={{default: 'justifyContentSpaceBetween'}} spaceItems={{default: 
'spaceItemsLg'}}>
+                    <FlexItem>
+                        <TextContent className="header">
+                            <Text component="h2">Project Builder</Text>
+                            <Badge isRead className="labels">Powered by Camel 
JBang</Badge>
+                        </TextContent>
+                    </FlexItem>
+                    <FlexItem>
+                        <Toolbar id="toolbar-group-types">
+                            <ToolbarContent>
+                                <ToolbarItem>
+                                    <Button variant="plain" onClick={e => {
+                                    }}><HelpIcon/></Button>
+                                </ToolbarItem>
+                            </ToolbarContent>
+                        </Toolbar>
+                    </FlexItem>
+                </Flex>
+            </PageSection>
+        )
+    }
+
+    onButtonClick(action: "start" | "stop" | "undeploy") {
+        this.props.onAction?.call(this, action, this.state);
+    }
+
+    getFooter() {
+        const active = this.state.status.active;
+        const label = active ? "Stop" : "Start";
+        const icon = active ? <InProgressIcon/> : <AutomationIcon/>;
+        return <div className="footer">
+                    <div className="progress">
+                        {active && this.getProgress()}
+                    </div>
+                    <div className="buttons">
+                        <Toolbar id="toolbar-items">
+                            <ToolbarContent>
+                                {!active && <ToolbarItem>
+                                    <Button variant="secondary" isSmall 
onClick={event => this.onButtonClick("undeploy")}>Undeploy</Button>
+                                </ToolbarItem>}
+                                <ToolbarItem>
+                                    <Button variant="primary" isSmall 
icon={icon} onClick={event => this.onButtonClick(active ? "stop" : 
"start")}>{label}</Button>
+                                </ToolbarItem>
+                            </ToolbarContent>
+                        </Toolbar>
+                    </div>
+                </div>
+    }
+
+    getCenter() {
+        return (
+            <div className="center">
+                <div className="center-column">
+                    {this.getProjectForm()}
+                    {this.getPackageForm()}
+                </div>
+                <div className="center-column">
+                    {this.getBuildForm()}
+                    {this.getDeployForm()}
+                    {this.getCleanupForm()}
+                </div>
+            </div>
+        )
+    }
+
+    render() {
+        return (
+            <PageSection className="project-builder" variant={this.props.dark 
? PageSectionVariants.darker : PageSectionVariants.light}
+                         padding={{default: 'noPadding'}}>
+                <div style={{height: "100%", display: "flex", flexDirection: 
"column"}}>
+                    <div style={{flexShrink: "0"}}>
+                        {this.getHeader()}
+                    </div>
+                    <div style={{overflow: "auto", flexGrow: 1}}>
+                        {this.getCenter()}
+                    </div>
+                    <div style={{flexShrink: "0"}}>
+                        {this.getFooter()}
+                    </div>
+                </div>
+            </PageSection>
+        )
+    }
+};
\ No newline at end of file
diff --git a/karavan-vscode/webview/builder/FileSelector.tsx 
b/karavan-vscode/webview/builder/FileSelector.tsx
new file mode 100644
index 0000000..983a2a6
--- /dev/null
+++ b/karavan-vscode/webview/builder/FileSelector.tsx
@@ -0,0 +1,81 @@
+import React from 'react';
+import {
+    Button,
+    FormGroup,
+    Checkbox, PopoverPosition, Popover, InputGroup
+} from '@patternfly/react-core';
+import '../designer/karavan.css';
+import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon";
+
+interface Props {
+    label: string
+    help: string
+    files: string
+    filesSelected: string
+    onChange: (files: string) => void
+    source: boolean
+}
+
+interface State {
+    selected: []
+}
+
+export class FileSelector extends React.Component<Props, State> {
+
+    public state: State = {
+        selected: []
+    };
+
+    isChecked(file: string) {
+        const finalFile = this.props.source ? "file:" + file : file;
+        const s = this.props.filesSelected.split(",").map(value => 
value.trim());
+        return s.includes(finalFile);
+    }
+
+    onChange(file: string, checked: boolean) {
+        const finalFile = this.props.source ? "file:" + file : file;
+        const s = this.props.filesSelected.split(",").map(f => 
f.trim()).filter(f => f.length > 0);
+        const already = s.includes(finalFile);
+        if (checked && !already) {
+            s.push(finalFile);
+            this.props.onChange?.call(this, s.join(","));
+        } else if (!checked) {
+            const result = s.filter(f => f !== finalFile);
+            this.props.onChange?.call(this, result.join(","));
+        }
+    }
+
+    getFiles(): string[] {
+        const allFiles = (this.props.files ? this.props.files.split(",") : []);
+        if (this.props.source){
+            const extensions = ['yaml', 'yml', 'java', 'js', 'kt', 'groovy', 
'xml'];
+            return  allFiles.filter(file => {
+                const extension = file.split(".").pop() || '';
+                return extensions.includes(extension);
+            }).map(file => file.replace("file:", ""))
+        }
+        return allFiles;
+    }
+
+    render() {
+        const files = this.getFiles();
+        return (
+            <FormGroup label={this.props.label} fieldId="files">
+                <InputGroup>
+                    <div style={{width:"100%"}}>
+                        {files.map(file => {
+                            const key = file + this.props.source;
+                         return <Checkbox key={key} label={file} 
isChecked={this.isChecked(file)} onChange={checked => this.onChange(file, 
checked)} id={key} name={key}/>
+                        })}
+                    </div>
+                    <Popover aria-label="files" position={PopoverPosition.left}
+                        bodyContent={this.props.help}>
+                        <Button variant="plain" onClick={e => {}}>
+                            <HelpIcon/>
+                        </Button>
+                    </Popover>
+                </InputGroup>
+            </FormGroup>
+        )
+    }
+};
\ No newline at end of file
diff --git a/karavan-vscode/webview/index.css b/karavan-vscode/webview/index.css
index 39225d0..8fde82f 100644
--- a/karavan-vscode/webview/index.css
+++ b/karavan-vscode/webview/index.css
@@ -335,5 +335,57 @@ color: var(--vscode-editor-foreground);
   color:  var(--vscode-input-foreground);
 }
 
+/* Builder */
+.vscode-dark .karavan .project-builder {
+  background: var(--vscode-tab-inactiveBackground);
+  border: none;
+}
+.vscode-dark .karavan .project-builder .header .pf-c-badge {
+  --pf-c-badge--BackgroundColor: var(--pf-global--primary-color--200);
+}
+
+
+.vscode-dark .karavan .project-builder h2,
+.vscode-dark .karavan .project-builder .pf-c-card__title,
+.vscode-dark .karavan .project-builder .pf-c-form__label-text {
+  color: var(--vscode-editor-foreground);
+}
 
+.vscode-dark .karavan .project-builder .builder-card {
+  background-color: var(--vscode-badge-background);
+}
+
+.vscode-dark .karavan .project-builder .builder-card .text-field {
+  background-color: var(--vscode-input-background);
+  border-color: var(--vscode-input-background);
+  color: var(--vscode-input-foreground);
+}
 
+.vscode-dark .karavan .project-builder .pf-c-input-group {
+  background-color: transparent;
+  border-color: var(--vscode-input-foreground);
+  color: var(--vscode-input-foreground);
+  --pf-c-text-input-group__text--before--BorderColor: transparent;
+  --pf-c-text-input-group__text--after--BorderBottomColor: transparent;
+}
+
+.vscode-dark .karavan .project-builder .pf-c-toggle-group {
+  --pf-c-toggle-group__button--BackgroundColor: transparent;
+  --pf-c-toggle-group__button--Color: var(--vscode-editor-foreground);
+}
+
+.vscode-dark .karavan .project-builder .pf-c-toggle-group 
.pf-c-toggle-group__button.pf-m-selected {
+  --pf-c-toggle-group__button--BackgroundColor: 
var(--pf-global--primary-color--200);
+}
+
+.vscode-dark .karavan .project-builder .pf-c-toggle-group 
.pf-c-toggle-group__button:hover {
+  --pf-c-toggle-group__button--BackgroundColor: var(--vscode-input-background);
+}
+
+.vscode-dark .karavan .project-builder .pf-c-progress-stepper__step-icon {
+  background-color: var(--vscode-input-background);
+}
+
+.vscode-dark .karavan .project-builder .footer .buttons .pf-c-toolbar {
+  background: transparent;
+}

Reply via email to