You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ma...@apache.org on 2022/11/11 14:05:45 UTC

[camel-karavan] 02/04: Templates in cloud-app

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

commit 25898ed5f64fc5465c4c8b17ed318fd57f48cd56
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Thu Nov 10 15:55:05 2022 -0500

    Templates in cloud-app
---
 .../org/apache/camel/karavan/model/Project.java    |  3 +
 .../apache/camel/karavan/service/CodeService.java  | 35 ++++++++++-
 .../camel/karavan/service/ImportService.java       | 22 +++++++
 .../camel/karavan/service/KaravanService.java      |  2 +
 .../src/main/resources/application.properties      |  3 +
 ...s => quarkus-kubernetes-application.properties} |  8 +--
 .../quarkus-openshift-application.properties       |  2 -
 ... spring-boot-kubernetes-application.properties} |  5 +-
 .../spring-boot-openshift-application.properties   |  6 +-
 karavan-app/src/main/webapp/src/Main.tsx           | 11 ++--
 karavan-app/src/main/webapp/src/index.css          | 27 ++++++++
 .../src/main/webapp/src/projects/ProjectModels.ts  |  6 +-
 .../src/main/webapp/src/projects/ProjectsPage.tsx  | 71 ++++++++++++++--------
 .../main/webapp/src/projects/PropertiesTable.tsx   |  2 +-
 .../src/designer/utils/KaravanIcons.tsx            | 44 ++++++++++++++
 karavan-vscode/package.json                        |  4 +-
 16 files changed, 200 insertions(+), 51 deletions(-)

diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/model/Project.java b/karavan-app/src/main/java/org/apache/camel/karavan/model/Project.java
index 699c3fc..2626356 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/model/Project.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/model/Project.java
@@ -6,6 +6,9 @@ import org.infinispan.protostream.annotations.ProtoField;
 public class Project {
     public static final String CACHE = "projects";
 
+    public static final String NAME_TEMPLATES = "templates";
+    public static final String NAME_KAMELETS = "kamelets";
+
     @ProtoField(number = 1)
     String projectId;
     @ProtoField(number = 2)
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
index 16aa195..ee7c57c 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
@@ -27,6 +27,7 @@ import org.apache.camel.generator.openapi.RestDslGenerator;
 import org.apache.camel.impl.lw.LightweightCamelContext;
 import org.apache.camel.karavan.api.KameletResources;
 import org.apache.camel.karavan.model.Project;
+import org.apache.camel.karavan.model.ProjectFile;
 import org.jboss.logging.Logger;
 import org.yaml.snakeyaml.Yaml;
 import org.yaml.snakeyaml.constructor.SafeConstructor;
@@ -37,6 +38,8 @@ import java.io.BufferedReader;
 import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -49,14 +52,16 @@ public class CodeService {
     @Inject
     KubernetesService kubernetesService;
 
+    @Inject
+    InfinispanService infinispanService;
+
     @Inject
     Engine engine;
 
     public String getApplicationProperties(Project project) {
         String target = kubernetesService.isOpenshift() ? "openshift" : "kubernetes";
-
-        String templatePath = "/snippets/" + project.getRuntime() + "-" + target + "-application.properties";
-        String templateText = getResourceFile(templatePath);
+        String templateName = project.getRuntime() + "-" + target + "-application.properties";
+        String templateText = getTemplateText(templateName);
         Template result = engine.parse(templateText);
         return result
                 .data("projectId", project.getProjectId())
@@ -66,6 +71,30 @@ public class CodeService {
                 .render();
     }
 
+    private String getTemplateText(String fileName) {
+        try {
+            List<ProjectFile> files = infinispanService.getProjectFiles(Project.NAME_TEMPLATES);
+            return files.stream().filter(f -> f.getName().equalsIgnoreCase(fileName))
+                    .map(f -> f.getCode()).findFirst().orElse(null);
+        } catch (Exception e){
+            LOGGER.error(e.getMessage());
+        }
+        return null;
+    }
+
+    public Map<String,String> getApplicationPropertiesTemplates() {
+        Map<String, String> result = new HashMap<>(4);
+        List.of("quarkus", "spring-boot").forEach(runtime -> {
+            List.of("openshift", "kubernetes").forEach(target -> {
+                String templateName = runtime + "-" + target + "-application.properties";
+                String templatePath = "/snippets/" + templateName;
+                String templateText = getResourceFile(templatePath);
+                result.put(templateName, templateText);
+            });
+        });
+        return result;
+    }
+
     public String getResourceFile(String path) {
         try {
             InputStream inputStream = KameletResources.class.getResourceAsStream(path);
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/ImportService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/ImportService.java
index 2585861..12d153a 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/ImportService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/ImportService.java
@@ -35,9 +35,11 @@ import java.util.stream.Collectors;
 public class ImportService {
 
     private static final Logger LOGGER = Logger.getLogger(ImportService.class.getName());
+    public static final String IMPORT_TEMPLATES = "import-templates";
     public static final String IMPORT_PROJECTS = "import-projects";
     public static final String IMPORT_KAMELETS = "import-kamelets";
 
+
     @Inject
     InfinispanService infinispanService;
 
@@ -72,6 +74,26 @@ public class ImportService {
         } catch (Exception e) {
             LOGGER.error("Error during project import", e);
         }
+        addTemplates("");
+    }
+
+    @ConsumeEvent(value = IMPORT_TEMPLATES, blocking = true)
+    void addTemplates(String data) {
+        LOGGER.info("Add templates if not exists");
+        try {
+            Project templates  = infinispanService.getProject(Project.NAME_TEMPLATES);
+            if (templates == null) {
+                templates = new Project("templates", "Templates", "Templates", "quarkus", "");
+                infinispanService.saveProject(templates, true);
+
+                codeService.getApplicationPropertiesTemplates().forEach((name, value) -> {
+                    ProjectFile file = new ProjectFile(name, value, Project.NAME_TEMPLATES);
+                    infinispanService.saveProjectFile(file);
+                });
+            }
+        } catch (Exception e) {
+            LOGGER.error("Error during project import", e);
+        }
     }
 
     @ConsumeEvent(value = IMPORT_KAMELETS, blocking = true)
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java
index d7507b6..932d22b 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java
@@ -67,6 +67,8 @@ public class KaravanService {
         if (infinispanService.getProjects().isEmpty()) {
             LOGGER.info("No projects found in the Data Grid");
             bus.publish(ImportService.IMPORT_PROJECTS, "");
+        } else {
+            bus.publish(ImportService.IMPORT_TEMPLATES, "");
         }
         bus.publish(ImportService.IMPORT_KAMELETS, "");
     }
diff --git a/karavan-app/src/main/resources/application.properties b/karavan-app/src/main/resources/application.properties
index c5dd790..e0efd62 100644
--- a/karavan-app/src/main/resources/application.properties
+++ b/karavan-app/src/main/resources/application.properties
@@ -86,6 +86,9 @@ quarkus.package.type=uber-jar
 quarkus.docker.dockerfile-jvm-path=src/main/docker/Dockerfile.distroless
 #quarkus.docker.dockerfile-jvm-path=src/main/docker/Dockerfile.legacy-jar
 
+quarkus.qute.strict-rendering=false
+quarkus.qute.property-not-found-strategy=output-original
+
 quarkus.container-image.builder=docker
 
 quarkus.kubernetes-client.connection-timeout=2000
diff --git a/karavan-app/src/main/resources/snippets/quarkus-openshift-application.properties b/karavan-app/src/main/resources/snippets/quarkus-kubernetes-application.properties
similarity index 62%
copy from karavan-app/src/main/resources/snippets/quarkus-openshift-application.properties
copy to karavan-app/src/main/resources/snippets/quarkus-kubernetes-application.properties
index 60dd1d0..bf32b86 100644
--- a/karavan-app/src/main/resources/snippets/quarkus-openshift-application.properties
+++ b/karavan-app/src/main/resources/snippets/quarkus-kubernetes-application.properties
@@ -4,12 +4,8 @@ camel.karavan.project-description={projectDescription}
 camel.jbang.gav=org.camel.karavan.demo:{projectId}:1
 camel.jbang.runtime=quarkus
 camel.jbang.quarkusVersion=2.14.0.Final
-camel.jbang.dependencies=camel:microprofile-health,mvn:io.quarkus:quarkus-openshift,mvn:io.quarkus:quarkus-container-image-jib
+camel.jbang.dependencies=camel:microprofile-health,mvn:io.quarkus:quarkus-kubernetes,mvn:io.quarkus:quarkus-container-image-jib
 camel.health.enabled=true
 camel.health.exposure-level=full
-quarkus.kubernetes-client.trust-certs=true
-quarkus.container-image.group=${namespace}
 quarkus.container-image.name={projectId}
-quarkus.openshift.route.expose=false
-quarkus.openshift.part-of={projectId}
-quarkus.openshift.replicas=1
\ No newline at end of file
+quarkus.kubernetes.replicas=1
\ No newline at end of file
diff --git a/karavan-app/src/main/resources/snippets/quarkus-openshift-application.properties b/karavan-app/src/main/resources/snippets/quarkus-openshift-application.properties
index 60dd1d0..80a0b61 100644
--- a/karavan-app/src/main/resources/snippets/quarkus-openshift-application.properties
+++ b/karavan-app/src/main/resources/snippets/quarkus-openshift-application.properties
@@ -7,8 +7,6 @@ camel.jbang.quarkusVersion=2.14.0.Final
 camel.jbang.dependencies=camel:microprofile-health,mvn:io.quarkus:quarkus-openshift,mvn:io.quarkus:quarkus-container-image-jib
 camel.health.enabled=true
 camel.health.exposure-level=full
-quarkus.kubernetes-client.trust-certs=true
-quarkus.container-image.group=${namespace}
 quarkus.container-image.name={projectId}
 quarkus.openshift.route.expose=false
 quarkus.openshift.part-of={projectId}
diff --git a/karavan-app/src/main/resources/snippets/spring-boot-openshift-application.properties b/karavan-app/src/main/resources/snippets/spring-boot-kubernetes-application.properties
similarity index 72%
copy from karavan-app/src/main/resources/snippets/spring-boot-openshift-application.properties
copy to karavan-app/src/main/resources/snippets/spring-boot-kubernetes-application.properties
index b1fb0f3..b49c48e 100644
--- a/karavan-app/src/main/resources/snippets/spring-boot-openshift-application.properties
+++ b/karavan-app/src/main/resources/snippets/spring-boot-kubernetes-application.properties
@@ -6,6 +6,7 @@ camel.jbang.runtime=spring-boot
 camel.jbang.dependencies=camel:microprofile-health
 camel.health.enabled=true
 camel.health.exposure-level=full
-jkube.version=1.9.1"
-jkube.namespace=${namespace}
+jkube.version=1.10.0
+jkube.build.strategy=jib
+jkube.imagePullPolicy=IfNotPresent
 jkube.enricher.jkube-controller.replicaCount=1
\ No newline at end of file
diff --git a/karavan-app/src/main/resources/snippets/spring-boot-openshift-application.properties b/karavan-app/src/main/resources/snippets/spring-boot-openshift-application.properties
index b1fb0f3..34a4e95 100644
--- a/karavan-app/src/main/resources/snippets/spring-boot-openshift-application.properties
+++ b/karavan-app/src/main/resources/snippets/spring-boot-openshift-application.properties
@@ -6,6 +6,8 @@ camel.jbang.runtime=spring-boot
 camel.jbang.dependencies=camel:microprofile-health
 camel.health.enabled=true
 camel.health.exposure-level=full
-jkube.version=1.9.1"
-jkube.namespace=${namespace}
+jkube.version=1.10.0
+jkube.build.strategy=jib
+jkube.imagePullPolicy=IfNotPresent
+jkube.enricher.jkube-controller.type=Deployment
 jkube.enricher.jkube-controller.replicaCount=1
\ No newline at end of file
diff --git a/karavan-app/src/main/webapp/src/Main.tsx b/karavan-app/src/main/webapp/src/Main.tsx
index f69f8d8..ccb825e 100644
--- a/karavan-app/src/main/webapp/src/Main.tsx
+++ b/karavan-app/src/main/webapp/src/Main.tsx
@@ -124,7 +124,7 @@ export class Main extends React.Component<Props, State> {
 
     getData() {
         KaravanApi.getConfiguration((config: any) => {
-            this.setState({config: config});
+            this.setState({config: config, request: uuidv4()});
         });
         this.updateKamelets();
         this.updateComponents();
@@ -255,10 +255,11 @@ export class Main extends React.Component<Props, State> {
     render() {
         return (
             <Page className="karavan">
-                {KaravanApi.authType === undefined && <Bullseye className="loading-page">
-                    <Spinner className="spinner" isSVG diameter="140px" aria-label="Loading..."/>
-                    <div className="logo-placeholder">{Icon()}</div>
-                </Bullseye>}
+                {KaravanApi.authType === undefined &&
+                    <Bullseye className="loading-page">
+                        <Spinner className="spinner" isSVG diameter="140px" aria-label="Loading..."/>
+                        <div className="logo-placeholder">{Icon()}</div>
+                    </Bullseye>}
                 {(KaravanApi.isAuthorized || KaravanApi.authType === 'public') && this.getMain()}
                 {!KaravanApi.isAuthorized && KaravanApi.authType === 'basic' &&
                     <MainLogin config={this.state.config} onLogin={this.onLogin}/>}
diff --git a/karavan-app/src/main/webapp/src/index.css b/karavan-app/src/main/webapp/src/index.css
index f170c70..43ae76f 100644
--- a/karavan-app/src/main/webapp/src/index.css
+++ b/karavan-app/src/main/webapp/src/index.css
@@ -272,4 +272,31 @@
 
 .karavan .loading-page .logo {
   height: 100px;
+}
+
+/*Modals*/
+
+.new-project .pf-c-radio,
+.new-project .runtime-radio {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+}
+
+.new-project .radio {
+  margin-right: 16px;
+}
+
+.new-project .pf-c-radio__body {
+  margin: 0 6px 0 6px;
+}
+
+.new-project .runtime-radio .icon {
+  width: 24px;
+  height: 24px;
+}
+
+.new-project .runtime-label {
+  font-size: 14px;
+  margin-left: 6px;
 }
\ No newline at end of file
diff --git a/karavan-app/src/main/webapp/src/projects/ProjectModels.ts b/karavan-app/src/main/webapp/src/projects/ProjectModels.ts
index 7165507..1b8515d 100644
--- a/karavan-app/src/main/webapp/src/projects/ProjectModels.ts
+++ b/karavan-app/src/main/webapp/src/projects/ProjectModels.ts
@@ -2,9 +2,10 @@ export class Project {
     projectId: string = '';
     name: string = '';
     description: string = '';
+    runtime: string = '';
     lastCommit: string = '';
 
-    public constructor(projectId: string, name: string, description: string, lastCommit: string);
+    public constructor(projectId: string, name: string, description: string, runtime: string, lastCommit: string);
     public constructor(init?: Partial<Project>);
     public constructor(...args: any[]) {
         if (args.length === 1) {
@@ -14,7 +15,8 @@ export class Project {
             this.projectId = args[0];
             this.name = args[1];
             this.description = args[2];
-            this.lastCommit = args[3];
+            this.runtime = args[3];
+            this.lastCommit = args[4];
             return;
         }
     }
diff --git a/karavan-app/src/main/webapp/src/projects/ProjectsPage.tsx b/karavan-app/src/main/webapp/src/projects/ProjectsPage.tsx
index 3f7560c..15676f6 100644
--- a/karavan-app/src/main/webapp/src/projects/ProjectsPage.tsx
+++ b/karavan-app/src/main/webapp/src/projects/ProjectsPage.tsx
@@ -23,7 +23,7 @@ import {
     OverflowMenuContent,
     OverflowMenuGroup,
     OverflowMenuItem,
-    Flex, FlexItem
+    Flex, FlexItem, Radio
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {MainToolbar} from "../MainToolbar";
@@ -36,6 +36,8 @@ import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
 import CopyIcon from "@patternfly/react-icons/dist/esm/icons/copy-icon";
 import {CamelUi} from "../designer/utils/CamelUi";
 import {KaravanApi} from "../api/KaravanApi";
+import {QuarkusIcon, SpringIcon} from "../designer/utils/KaravanIcons";
+import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 
 interface Props {
     config: any,
@@ -55,6 +57,7 @@ interface State {
     name: string,
     description: string,
     projectId: string,
+    runtime: string,
 }
 
 export class ProjectsPage extends React.Component<Props, State> {
@@ -69,6 +72,7 @@ export class ProjectsPage extends React.Component<Props, State> {
         name: '',
         description: '',
         projectId: '',
+        runtime: this.props.config.runtime
     };
     interval: any;
 
@@ -92,7 +96,7 @@ export class ProjectsPage extends React.Component<Props, State> {
                     this.props.toast?.call(this, "Success", "Project deleted", "success");
                     this.onGetProjects();
                 } else {
-                    this.props.toast?.call(this,"Error", res.statusText, "danger");
+                    this.props.toast?.call(this, "Error", res.statusText, "danger");
                 }
             });
         this.setState({isDeleteModalOpen: false})
@@ -102,23 +106,20 @@ export class ProjectsPage extends React.Component<Props, State> {
         KaravanApi.postProject(project, res => {
             console.log(res.status)
             if (res.status === 200 || res.status === 201) {
-                this.props.toast?.call(this,"Success", "Project created", "success");
+                this.props.toast?.call(this, "Success", "Project created", "success");
             } else {
-                this.props.toast?.call(this,"Error", res.status + ", " + res.statusText, "danger");
+                this.props.toast?.call(this, "Error", res.status + ", " + res.statusText, "danger");
             }
         });
     };
 
     onGetProjects = () => {
-        KaravanApi.getConfiguration((config: any) => {
-            KaravanApi.getProjects((projects: Project[]) => {
-                this.setState({ projects: projects })
-            });
-            KaravanApi.getDeploymentStatuses(config.environment, (statuses: DeploymentStatus[]) => {
-                this.setState({ deploymentStatuses: statuses });
-            });
+        KaravanApi.getProjects((projects: Project[]) => {
+            this.setState({projects: projects})
+        });
+        KaravanApi.getDeploymentStatuses(this.props.config.environment, (statuses: DeploymentStatus[]) => {
+            this.setState({deploymentStatuses: statuses});
         });
-
     }
 
     tools = () => (<Toolbar id="toolbar-group-types">
@@ -143,15 +144,15 @@ export class ProjectsPage extends React.Component<Props, State> {
     </TextContent>);
 
     closeModal = () => {
-        this.setState({isCreateModalOpen: false, isCopy: false, name: this.props.config.groupId, description:'', projectId: ''});
+        this.setState({isCreateModalOpen: false, isCopy: false, name: this.props.config.groupId, description: '', projectId: ''});
         this.onGetProjects();
     }
 
     saveAndCloseCreateModal = () => {
-        const {name, description, projectId} = this.state;
-        const p = new Project(projectId, name, description, '');
+        const {name, description, projectId, runtime} = this.state;
+        const p = new Project(projectId, name, description, runtime, '');
         this.onProjectCreate(p);
-        this.setState({isCreateModalOpen: false, isCopy: false, name: this.props.config.groupId, description: '',  projectId: ''});
+        this.setState({isCreateModalOpen: false, isCopy: false, name: this.props.config.groupId, description: '', projectId: ''});
     }
 
     onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
@@ -162,6 +163,7 @@ export class ProjectsPage extends React.Component<Props, State> {
 
     createModalForm() {
         const {isCopy, projectToCopy, projectId, name, isCreateModalOpen} = this.state;
+        const {runtimes} = this.props.config;
         return (
             <Modal
                 title={!isCopy ? "Create new project" : "Copy project from " + projectToCopy?.projectId}
@@ -173,6 +175,7 @@ export class ProjectsPage extends React.Component<Props, State> {
                     <Button key="confirm" variant="primary" onClick={this.saveAndCloseCreateModal}>Save</Button>,
                     <Button key="cancel" variant="secondary" onClick={this.closeModal}>Cancel</Button>
                 ]}
+                className="new-project"
             >
                 <Form isHorizontal={true} autoComplete="off">
                     <FormGroup label="Name" fieldId="name" isRequired>
@@ -188,9 +191,24 @@ export class ProjectsPage extends React.Component<Props, State> {
                     <FormGroup label="Project ID" fieldId="projectId" isRequired helperText="Unique project name">
                         <TextInput className="text-field" type="text" id="projectId" name="projectId"
                                    value={this.state.projectId}
-                                   onFocus={e => this.setState({projectId : projectId === '' ? CamelUi.nameFromTitle(name) : projectId})}
+                                   onFocus={e => this.setState({projectId: projectId === '' ? CamelUi.nameFromTitle(name) : projectId})}
                                    onChange={e => this.setState({projectId: CamelUi.nameFromTitle(e)})}/>
                     </FormGroup>
+                    <FormGroup label="Runtime" fieldId="runtime" isRequired>
+                        {runtimes?.map((runtime: string) => (
+                            <Radio key={runtime} id={runtime} name={runtime} className="radio"
+                                   isChecked={this.state.runtime === runtime}
+                                   onChange={checked => {
+                                       if (checked) this.setState({runtime: runtime})
+                                   }}
+                                   body={
+                                       <div className="runtime-radio">
+                                           {runtime === 'quarkus' ? QuarkusIcon() : SpringIcon()}
+                                           <div className="runtime-label">{CamelUtil.capitalizeName(runtime)}</div>
+                                       </div>}
+                            />
+                        ))}
+                    </FormGroup>
                 </Form>
             </Modal>
         )
@@ -214,7 +232,7 @@ export class ProjectsPage extends React.Component<Props, State> {
         )
     }
 
-    getEnvironments(): string []{
+    getEnvironments(): string [] {
         return this.props.config.environments && Array.isArray(this.props.config.environments) ? Array.from(this.props.config.environments) : [];
     }
 
@@ -235,7 +253,7 @@ export class ProjectsPage extends React.Component<Props, State> {
                 <PageSection className="tools-section" padding={{default: 'noPadding'}}>
                     <MainToolbar title={this.title()} tools={this.tools()}/>
                 </PageSection>
-                <PageSection isFilled className="kamelets-page" >
+                <PageSection isFilled className="kamelets-page">
                     <TableComposable aria-label="Projects" variant={"compact"}>
                         <Thead>
                             <Tr>
@@ -253,11 +271,11 @@ export class ProjectsPage extends React.Component<Props, State> {
                                 <Tr key={project.projectId}>
                                     <Td modifier={"fitContent"}>
                                         <Tooltip content={runtime} position={"left"}>
-                                            <Badge className="runtime-badge">{runtime.substring(0,1)}</Badge>
+                                            <Badge className="runtime-badge">{runtime.substring(0, 1)}</Badge>
                                         </Tooltip>
                                     </Td>
                                     <Td>
-                                        <Button style={{padding: '6px'}} variant={"link"} onClick={e=>this.props.onSelect?.call(this, project)}>
+                                        <Button style={{padding: '6px'}} variant={"link"} onClick={e => this.props.onSelect?.call(this, project)}>
                                             {project.projectId}
                                         </Button>
                                     </Td>
@@ -268,11 +286,11 @@ export class ProjectsPage extends React.Component<Props, State> {
                                             <Badge>{project.lastCommit?.substr(0, 7)}</Badge>
                                         </Tooltip>
                                     </Td>
-                                    <Td noPadding style={{width:"180px"}}>
+                                    <Td noPadding style={{width: "180px"}}>
                                         <Flex direction={{default: "row"}}>
                                             {this.getDeploymentByEnvironments(project.projectId).map(value => (
                                                 <FlexItem className="badge-flex-item" key={value[0]}>
-                                                    <Badge className="badge"isRead={!value[1]}>{value[0]}</Badge>
+                                                    <Badge className="badge" isRead={!value[1]}>{value[0]}</Badge>
                                                 </FlexItem>
                                             ))}
                                         </Flex>
@@ -283,12 +301,13 @@ export class ProjectsPage extends React.Component<Props, State> {
                                                 <OverflowMenuGroup groupType="button">
                                                     <OverflowMenuItem>
                                                         <Tooltip content={"Copy project"} position={"bottom"}>
-                                                            <Button variant={"plain"} icon={<CopyIcon/>} onClick={e=>this.setState({isCreateModalOpen: true, isCopy: true, projectToCopy: project})}></Button>
+                                                            <Button variant={"plain"} icon={<CopyIcon/>}
+                                                                    onClick={e => this.setState({isCreateModalOpen: true, isCopy: true, projectToCopy: project})}></Button>
                                                         </Tooltip>
                                                     </OverflowMenuItem>
                                                     <OverflowMenuItem>
                                                         <Tooltip content={"Delete project"} position={"bottom"}>
-                                                            <Button variant={"plain"} icon={<DeleteIcon/>} onClick={e=>this.onProjectDelete(project)}></Button>
+                                                            <Button variant={"plain"} icon={<DeleteIcon/>} onClick={e => this.onProjectDelete(project)}></Button>
                                                         </Tooltip>
                                                     </OverflowMenuItem>
                                                 </OverflowMenuGroup>
@@ -302,7 +321,7 @@ export class ProjectsPage extends React.Component<Props, State> {
                                     <Td colSpan={8}>
                                         <Bullseye>
                                             <EmptyState variant={EmptyStateVariant.small}>
-                                                <EmptyStateIcon icon={SearchIcon} />
+                                                <EmptyStateIcon icon={SearchIcon}/>
                                                 <Title headingLevel="h2" size="lg">
                                                     No results found
                                                 </Title>
diff --git a/karavan-app/src/main/webapp/src/projects/PropertiesTable.tsx b/karavan-app/src/main/webapp/src/projects/PropertiesTable.tsx
index 6f83381..72287a3 100644
--- a/karavan-app/src/main/webapp/src/projects/PropertiesTable.tsx
+++ b/karavan-app/src/main/webapp/src/projects/PropertiesTable.tsx
@@ -106,7 +106,7 @@ export class PropertiesTable extends React.Component<Props, State> {
                         </Thead>
                         <Tbody>
                             {properties.map((property, idx: number) => {
-                                const readOnly = property.key.startsWith("camel.jbang") && !this.props.editAdvanced;
+                                const readOnly = (property.key.startsWith("camel.jbang") || property.key.startsWith("camel.karavan")) && !this.props.editAdvanced;
                                 return (
                                     <Tr key={property.id}>
                                         <Td noPadding width={10} dataLabel="key">{this.getTextInputField(property, "key", readOnly)}</Td>
diff --git a/karavan-designer/src/designer/utils/KaravanIcons.tsx b/karavan-designer/src/designer/utils/KaravanIcons.tsx
index 26860d5..a215143 100644
--- a/karavan-designer/src/designer/utils/KaravanIcons.tsx
+++ b/karavan-designer/src/designer/utils/KaravanIcons.tsx
@@ -283,4 +283,48 @@ export function SortIcon() {
             <path d="M123.55 502.12L160.62 531.37 197.7 502.12" className="b"></path>
         </svg>
     );
+}
+
+export function SpringIcon() {
+    return (
+        <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="32"
+            height="32"
+            viewBox="0 0 32 32"
+            className="icon">
+            <g fill="none" fillRule="evenodd">
+                <path d="M0 0h32v32H0z"></path>
+                <path
+                    fill="#70AD51"
+                    d="M5.466 27.993a1.364 1.364 0 10-.087-.076l-.266-.234C1.972 24.762 0 20.597 0 15.978 0 7.168 7.168 0 15.98 0c4.48 0 8.53 1.857 11.435 4.836a14.681 14.681 0 001.7-3.015c2.036 6.118 3.233 11.26 2.795 15.31-.592 8.274-7.508 14.83-15.93 14.83a15.903 15.903 0 01-10.276-3.757l-.238-.21zm23.58-4.982c4.01-5.336 1.775-13.965-.085-19.48-1.657 3.453-5.738 6.094-9.262 6.93-3.303.788-6.226.142-9.283 1.318-6.97 2.68-6.86 10.992-3.02 12.86.002 0 .23.124.227.12 0-.002 5.644-1.122 8. [...]
+                ></path>
+            </g>
+        </svg>
+    );
+}
+
+export function QuarkusIcon() {
+    return (
+        <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="257"
+            height="257"
+            preserveAspectRatio="xMidYMid"
+            viewBox="-0.5 0 257 257"
+            className="icon">
+            <path
+                fill="#4695EB"
+                d="M213.554 0c23.418.08 42.377 19.052 42.443 42.47v171.084c-.066 23.428-19.042 42.404-42.47 42.47h-25.439l-11.661-28.318h37.127c7.774-.1 14.051-6.378 14.152-14.152V42.47c-.1-7.774-6.378-14.051-14.152-14.152H42.47c-7.774.1-14.051 6.378-14.152 14.152v171.084c.1 7.774 6.378 14.051 14.152 14.152h62.607l22.935-48.494 31.625 76.812H42.47C19.042 255.958.066 236.982 0 213.554V42.47C.066 19.042 19.042.066 42.47 0h171.084zm-43.983 139.727v45.51l-39.417-22.748 39.417-22.762zM86.453  [...]
+            ></path>
+            <path
+                fill="#FF004A"
+                d="M86.453 139.727l39.417 22.762-39.417 22.748v-45.51zm83.118-45.509l39.418 22.748-39.418 22.761V94.218zM88.595 44.987l39.417 22.748-39.417 22.761V44.987z"
+            ></path>
+            <path
+                fill="#091313"
+                d="M86.453 94.218l39.417 22.748v45.523l-39.417-22.762V94.218zm83.118 0v45.51l-39.417 22.76v-45.522l39.417-22.748zm-41.559-26.483l39.417 22.761-39.417 22.761-39.417-22.76 39.417-22.762z"
+            ></path>
+        </svg>
+    );
 }
\ No newline at end of file
diff --git a/karavan-vscode/package.json b/karavan-vscode/package.json
index b0c34e8..193c45b 100644
--- a/karavan-vscode/package.json
+++ b/karavan-vscode/package.json
@@ -265,7 +265,7 @@
           "default": [
             "camel.jbang.dependencies=camel:microprofile-health",
             "# jkube properties",
-            "jkube.version=1.9.1",
+            "jkube.version=1.10.0",
             "jkube.build.strategy=s2i",
             "jkube.namespace=${NAMESPACE}",
             "jkube.generator.name=image-registry.openshift-image-registry.svc:5000/${NAMESPACE}/$NAME:${DATE}",
@@ -286,7 +286,7 @@
           "default": [
             "camel.jbang.dependencies=camel:microprofile-health",
             "# jkube properties",
-            "jkube.version=1.9.1",
+            "jkube.version=1.10.0",
             "jkube.build.strategy=docker",
             "jkube.namespace=default",
             "jkube.generator.name=default/$NAME:${DATE}",