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

(camel-karavan) branch main updated: Fixes for demo

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 812f39bd Fixes for demo
812f39bd is described below

commit 812f39bd40acada7660749a2d5d6ffb6bb5b1bd4
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Wed Dec 13 20:32:29 2023 -0500

    Fixes for demo
---
 .../apache/camel/karavan/api/BuildResource.java    |  56 +++++++++
 .../camel/karavan/api/ContainerResource.java       |   2 +-
 .../apache/camel/karavan/api/DevModeResource.java  |   2 +-
 .../org/apache/camel/karavan/code/CodeService.java |   9 +-
 .../org/apache/camel/karavan/git/GitService.java   |   2 +
 .../karavan/infinispan/InfinispanService.java      |   6 +-
 .../karavan/kubernetes/KubernetesService.java      | 100 +++++++++++-----
 .../camel/karavan/kubernetes/PodEventHandler.java  |  13 ++-
 .../apache/camel/karavan/service/CamelService.java |  46 +++++---
 .../karavan/service/ContainerStatusService.java    |   1 +
 .../camel/karavan/service/ProjectService.java      |  17 +--
 .../org/apache/camel/karavan/shared/Constants.java |   5 +
 .../src/main/webui/src/api/KaravanApi.tsx          |  11 ++
 .../designer/route/element/DslElementHeader.tsx    |   5 +-
 .../route/property/ComponentParameterField.tsx     |   5 +-
 .../designer/route/property/DslPropertyField.tsx   |   3 +-
 .../src/designer/route/useRouteDesignerHook.tsx    |  20 +++-
 .../webui/src/project/{file => }/FileEditor.tsx    |  21 ++--
 .../src/main/webui/src/project/ProjectPage.tsx     |   2 +-
 .../webui/src/project/file/PropertiesPanel.tsx     |  61 ----------
 .../webui/src/project/file/PropertiesTable.tsx     | 126 ---------------------
 .../webui/src/project/file/PropertiesToolbar.tsx   |  63 -----------
 .../main/webui/src/project/file/PropertyField.tsx  |  73 ------------
 .../main/webui/src/project/files/FilesToolbar.tsx  |  26 ++++-
 24 files changed, 268 insertions(+), 407 deletions(-)

diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/BuildResource.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/BuildResource.java
new file mode 100644
index 00000000..ace9fa8c
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/BuildResource.java
@@ -0,0 +1,56 @@
+/*
+ * 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.api;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.apache.camel.karavan.code.CodeService;
+import org.apache.camel.karavan.infinispan.InfinispanService;
+import org.apache.camel.karavan.kubernetes.KubernetesService;
+
+@Path("/api/build")
+public class BuildResource {
+
+    @Inject
+    InfinispanService infinispanService;
+
+    @Inject
+    KubernetesService kubernetesService;
+
+    @Inject
+    CodeService codeService;
+
+    @POST
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Path("/update-config-map")
+    public Response updateConfigMaps() {
+        if (infinispanService.isReady()) {
+            String script = codeService.getBuilderScript();
+            kubernetesService.createBuildScriptConfigmap(script, true);
+            return Response.ok().build();
+        } else {
+            return Response.noContent().build();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ContainerResource.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ContainerResource.java
index 8effafe0..bfcae002 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ContainerResource.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ContainerResource.java
@@ -156,7 +156,7 @@ public class ContainerResource {
             status = ContainerStatus.createByType(projectId, environment, ContainerStatus.ContainerType.valueOf(type));
         }
         status.setInTransit(true);
-        eventBus.send(CONTAINER_STATUS, JsonObject.mapFrom(status));
+        eventBus.publish(CONTAINER_STATUS, JsonObject.mapFrom(status));
     }
 
     @GET
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java
index 1a8fe8ad..14bbc5a6 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java
@@ -114,7 +114,7 @@ public class DevModeResource {
             status = ContainerStatus.createByType(name, environment, ContainerStatus.ContainerType.valueOf(type));
         }
         status.setInTransit(true);
-        eventBus.send(CONTAINER_STATUS, JsonObject.mapFrom(status));
+        eventBus.publish(CONTAINER_STATUS, JsonObject.mapFrom(status));
     }
 
     @GET
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java
index cab3a77e..6bd42fec 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java
@@ -40,6 +40,7 @@ import org.apache.camel.karavan.infinispan.model.Project;
 import org.apache.camel.karavan.infinispan.model.ProjectFile;
 import org.apache.camel.karavan.kubernetes.KubernetesService;
 import org.apache.camel.karavan.service.ConfigService;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.jboss.logging.Logger;
 import org.yaml.snakeyaml.LoaderOptions;
 import org.yaml.snakeyaml.Yaml;
@@ -58,12 +59,16 @@ public class CodeService {
     public static final String BUILD_SCRIPT_FILENAME = "build.sh";
     public static final String DEV_SERVICES_FILENAME = "devservices.yaml";
     public static final String PROJECT_COMPOSE_FILENAME = "docker-compose.yaml";
+    public static final String MARKDOWN_EXTENSION = ".md";
     public static final String PROJECT_JKUBE_EXTENSION = ".jkube.yaml";
     public static final String PROJECT_DEPLOYMENT_JKUBE_FILENAME = "deployment" + PROJECT_JKUBE_EXTENSION;
     private static final String SNIPPETS_PATH = "/snippets/";
     private static final String DATA_FOLDER = System.getProperty("user.dir") + File.separator + "data";
     private static final int INTERNAL_PORT = 8080;
 
+    @ConfigProperty(name = "karavan.environment")
+    String environment;
+
     @Inject
     KubernetesService kubernetesService;
 
@@ -91,6 +96,7 @@ public class CodeService {
 
     public Map<String, String> getProjectFilesForDevMode(String projectId, Boolean withKamelets) {
         Map<String, String> files = infinispanService.getProjectFiles(projectId).stream()
+                .filter(f -> !f.getName().endsWith(MARKDOWN_EXTENSION))
                 .filter(f -> !Objects.equals(f.getName(), PROJECT_COMPOSE_FILENAME))
                 .filter(f -> !f.getName().endsWith(PROJECT_JKUBE_EXTENSION))
                 .collect(Collectors.toMap(ProjectFile::getName, ProjectFile::getCode));
@@ -142,7 +148,8 @@ public class CodeService {
                 ? (kubernetesService.isOpenshift() ? "openshift" : "kubernetes")
                 : "docker";
         String templateName = target + "-" + BUILD_SCRIPT_FILENAME;
-        return getTemplateText(templateName);
+        String envTemplate = getTemplateText(environment + "." + templateName);
+        return envTemplate != null ? envTemplate : getTemplateText(templateName);
     }
 
     public String getTemplateText(String fileName) {
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/git/GitService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/git/GitService.java
index 779e0edb..c07496b0 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/git/GitService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/git/GitService.java
@@ -457,6 +457,8 @@ public class GitService {
         String folder = vertx.fileSystem().createTempDirectoryBlocking(uuid);
         try (Git git = clone(folder, gitConfig.getUri(), gitConfig.getBranch(), cred)) {
             LOGGER.info("Git is ready");
+        } catch (Exception e) {
+            LOGGER.info("Error connecting git: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
         }
         return true;
     }
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
index 109781e3..a2f4c6fe 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
@@ -255,7 +255,11 @@ public class InfinispanService implements HealthCheck {
     }
 
     public ContainerStatus getContainerStatus(String projectId, String env, String containerName) {
-        return containerStatuses.get(GroupedKey.create(projectId, env, containerName));
+        return getContainerStatus(GroupedKey.create(projectId, env, containerName));
+    }
+
+    public ContainerStatus getContainerStatus(GroupedKey key) {
+        return containerStatuses.get(key);
     }
 
     public ContainerStatus getDevModeContainerStatus(String projectId, String env) {
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/KubernetesService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/KubernetesService.java
index 5b2459ab..d3123d13 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/KubernetesService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/KubernetesService.java
@@ -18,8 +18,8 @@ package org.apache.camel.karavan.kubernetes;
 
 import io.fabric8.kubernetes.api.model.*;
 import io.fabric8.kubernetes.api.model.apps.Deployment;
-import io.fabric8.kubernetes.client.DefaultKubernetesClient;
 import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClientBuilder;
 import io.fabric8.kubernetes.client.dsl.LogWatch;
 import io.fabric8.kubernetes.client.informers.SharedIndexInformer;
 import io.fabric8.openshift.api.model.ImageStream;
@@ -43,6 +43,7 @@ import org.eclipse.microprofile.health.Readiness;
 import org.jboss.logging.Logger;
 
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Paths;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -62,11 +63,14 @@ public class KubernetesService implements HealthCheck {
     @Inject
     InfinispanService infinispanService;
 
+    @Inject
+    CodeService codeService;
+
     private String namespace;
 
     @Produces
     public KubernetesClient kubernetesClient() {
-        return new DefaultKubernetesClient();
+        return new KubernetesClientBuilder().build();
     }
 
     @Produces
@@ -77,6 +81,12 @@ public class KubernetesService implements HealthCheck {
     @ConfigProperty(name = "karavan.environment")
     public String environment;
 
+    @ConfigProperty(name = "karavan.devmode.image")
+    public String devmodeImage;
+
+    @ConfigProperty(name = "karavan.devmode.create-pvc")
+    public Boolean devmodePVC;
+
     @ConfigProperty(name = "karavan.version")
     String version;
 
@@ -131,19 +141,27 @@ public class KubernetesService implements HealthCheck {
         informers.clear();
     }
 
+    public void createBuildScriptConfigmap(String script, boolean overwrite) {
+        try (KubernetesClient client = kubernetesClient()) {
+            ConfigMap configMap = client.configMaps().inNamespace(getNamespace()).withName(BUILD_CONFIG_MAP).get();
+            if (configMap == null) {
+                configMap = getConfigMapForBuilder(BUILD_CONFIG_MAP, getPartOfLabels());
+                configMap.setData(Map.of("build.sh", script));
+                client.resource(configMap).create();
+            } else if (overwrite) {
+                configMap.setData(Map.of("build.sh", script));
+                client.resource(configMap).patch();
+            }
+        } catch (Exception e) {
+            LOGGER.error("Error starting informers: " + e.getMessage());
+        }
+    }
     public void runBuildProject(Project project, String script, List<String> env, String tag) {
         try (KubernetesClient client = kubernetesClient()) {
             String containerName = project.getProjectId() + BUILDER_SUFFIX;
             Map<String, String> labels = getLabels(containerName, project, ContainerStatus.ContainerType.build);
 //        createPVC(containerName, labels);
-
-            // create script configMap
-            ConfigMap configMap = client.configMaps().inNamespace(getNamespace()).withName(containerName).get();
-            if (configMap == null) {
-                configMap = getConfigMapForBuilder(containerName, labels);
-            }
-            configMap.setData(Map.of("build.sh", script));
-            client.resource(configMap).serverSideApply();
+            createBuildScriptConfigmap(script, false);
 
 //        Delete old build pod
             Pod old = client.pods().inNamespace(getNamespace()).withName(containerName).get();
@@ -151,9 +169,11 @@ public class KubernetesService implements HealthCheck {
                 client.resource(old).delete();
             }
             Pod pod = getBuilderPod(containerName, env, labels);
-            Pod result = client.resource(pod).serverSideApply();
+            Pod result = client.resource(pod).create();
 
             LOGGER.info("Created pod " + result.getMetadata().getName());
+        } catch (Exception e) {
+            LOGGER.error("Error creating build container: " + e.getMessage());
         }
     }
 
@@ -166,6 +186,9 @@ public class KubernetesService implements HealthCheck {
         if (type != null) {
             labels.put(LABEL_TYPE, type.name());
         }
+        if (Objects.equals(type, ContainerStatus.ContainerType.devmode)) {
+            labels.put(LABEL_CAMEL_RUNTIME, CamelRuntime.CAMEL_MAIN.getValue());
+        }
         return labels;
     }
 
@@ -238,13 +261,13 @@ public class KubernetesService implements HealthCheck {
 
         Container container = new ContainerBuilder()
                 .withName(name)
-                .withImage("ghcr.io/apache/camel-karavan-devmode:" + version)
+                .withImage(devmodeImage)
                 .withPorts(port)
                 .withImagePullPolicy("Always")
                 .withEnv(envVars)
                 .withCommand("/bin/sh", "-c", "/karavan/builder/build.sh")
                 .withVolumeMounts(
-                        new VolumeMountBuilder().withName("builder").withMountPath("/karavan/builder").withReadOnly(true).build()
+                        new VolumeMountBuilder().withName(BUILD_CONFIG_MAP).withMountPath("/karavan/builder").withReadOnly(true).build()
                 )
                 .build();
 
@@ -254,14 +277,15 @@ public class KubernetesService implements HealthCheck {
                 .withRestartPolicy("Never")
                 .withServiceAccount(KARAVAN_SERVICE_ACCOUNT)
                 .withVolumes(
-                        new VolumeBuilder().withName("builder")
-                                .withConfigMap(new ConfigMapVolumeSourceBuilder().withName(name).withItems(
+                        new VolumeBuilder().withName(BUILD_CONFIG_MAP)
+                                .withConfigMap(new ConfigMapVolumeSourceBuilder().withName(BUILD_CONFIG_MAP).withItems(
                                         new KeyToPathBuilder().withKey("build.sh").withPath("build.sh").build()
                                 ).withDefaultMode(511).build()).build()
 //                        new VolumeBuilder().withName("maven-settings")
 //                                .withConfigMap(new ConfigMapVolumeSourceBuilder()
 //                                        .withName("karavan").build()).build()
-                ).build();
+                )
+                .build();
 
         return new PodBuilder()
                 .withMetadata(meta)
@@ -363,22 +387,38 @@ public class KubernetesService implements HealthCheck {
         return result;
     }
 
-    public void runDevModeContainer(Project project, String jBangOptions) {
+    public void runDevModeContainer(Project project, String jBangOptions, Map<String, String> files) {
         String name = project.getProjectId();
         Map<String, String> labels = getLabels(name, project, ContainerStatus.ContainerType.devmode);
+
         try (KubernetesClient client = kubernetesClient()) {
-            createPVC(name, labels);
+            if (devmodePVC) {
+                createPVC(name, labels);
+            }
             Pod old = client.pods().inNamespace(getNamespace()).withName(name).get();
             if (old == null) {
                 Map<String, String> containerResources = CodeService.DEFAULT_CONTAINER_RESOURCES;
                 Pod pod = getDevModePod(name, jBangOptions, containerResources, labels);
                 Pod result = client.resource(pod).createOrReplace();
+                copyFilesToContainer(result, files, "/karavan/code");
                 LOGGER.info("Created pod " + result.getMetadata().getName());
             }
         }
         createService(name, labels);
     }
 
+    private void copyFilesToContainer(Pod pod, Map<String, String> files, String dirName) {
+        try (KubernetesClient client = kubernetesClient()) {
+            String temp = codeService.saveProjectFilesInTemp(files);
+            client.pods().inNamespace(getNamespace())
+                    .withName(pod.getMetadata().getName())
+                    .dir(dirName)
+                    .upload(Paths.get(temp));
+        } catch (Exception e) {
+            LOGGER.info("Error copying filed to devmode pod: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
+        }
+    }
+
     public void deleteDevModePod(String name, boolean deletePVC) {
         try (KubernetesClient client = kubernetesClient()) {
             LOGGER.info("Delete devmode pod: " + name + " in the namespace: " + getNamespace());
@@ -418,27 +458,29 @@ public class KubernetesService implements HealthCheck {
 
         Container container = new ContainerBuilder()
                 .withName(name)
-                .withImage("ghcr.io/apache/camel-karavan-devmode:" + version)
+                .withImage(devmodeImage)
                 .withPorts(port)
                 .withResources(resources)
                 .withImagePullPolicy("Always")
                 .withEnv(new EnvVarBuilder().withName(ENV_VAR_JBANG_OPTIONS).withValue(jbangOptions).build())
-//                .withVolumeMounts(
-//                        new VolumeMountBuilder().withName("maven-settings").withSubPath("maven-settings.xml")
-//                                .withMountPath("/karavan-config-map/maven-settings.xml").build()
-//                )
                 .build();
 
+
+        List<Volume> volumes = new ArrayList<>();
+        volumes.add(new VolumeBuilder().withName("maven-settings")
+                .withConfigMap(new ConfigMapVolumeSourceBuilder()
+                        .withName("karavan").build()).build());
+
+        if (devmodePVC) {
+            volumes.add(new VolumeBuilder().withName(name)
+                    .withNewPersistentVolumeClaim(name, false).build());
+        }
+
         PodSpec spec = new PodSpecBuilder()
                 .withTerminationGracePeriodSeconds(0L)
                 .withContainers(container)
                 .withRestartPolicy("Never")
-                .withVolumes(
-                        new VolumeBuilder().withName(name)
-                                .withNewPersistentVolumeClaim(name, false).build(),
-                        new VolumeBuilder().withName("maven-settings")
-                                .withConfigMap(new ConfigMapVolumeSourceBuilder()
-                                        .withName("karavan").build()).build())
+                .withVolumes(volumes)
                 .build();
 
         return new PodBuilder()
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/PodEventHandler.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/PodEventHandler.java
index 0af91999..937cb2f2 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/PodEventHandler.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/PodEventHandler.java
@@ -33,8 +33,7 @@ import java.util.Objects;
 
 import static org.apache.camel.karavan.code.CodeService.DEFAULT_CONTAINER_RESOURCES;
 import static org.apache.camel.karavan.service.ContainerStatusService.CONTAINER_STATUS;
-import static org.apache.camel.karavan.shared.Constants.LABEL_PROJECT_ID;
-import static org.apache.camel.karavan.shared.Constants.LABEL_TYPE;
+import static org.apache.camel.karavan.shared.Constants.*;
 
 public class PodEventHandler implements ResourceEventHandler<Pod> {
 
@@ -55,7 +54,7 @@ public class PodEventHandler implements ResourceEventHandler<Pod> {
             LOGGER.info("onAdd " + pod.getMetadata().getName());
             ContainerStatus ps = getPodStatus(pod);
             if (ps != null) {
-                eventBus.send(CONTAINER_STATUS, JsonObject.mapFrom(ps));
+                eventBus.publish(CONTAINER_STATUS, JsonObject.mapFrom(ps));
             }
         } catch (Exception e) {
             LOGGER.error(e.getMessage(), e.getCause());
@@ -69,7 +68,7 @@ public class PodEventHandler implements ResourceEventHandler<Pod> {
             if (!newPod.isMarkedForDeletion() && newPod.getMetadata().getDeletionTimestamp() == null) {
                 ContainerStatus ps = getPodStatus(newPod);
                 if (ps != null) {
-                    eventBus.send(CONTAINER_STATUS, JsonObject.mapFrom(ps));
+                    eventBus.publish(CONTAINER_STATUS, JsonObject.mapFrom(ps));
                 }
             }
         } catch (Exception e) {
@@ -94,6 +93,8 @@ public class PodEventHandler implements ResourceEventHandler<Pod> {
     public ContainerStatus getPodStatus(Pod pod) {
         String deployment = pod.getMetadata().getLabels().get("app");
         String projectId = deployment != null ? deployment : pod.getMetadata().getLabels().get(LABEL_PROJECT_ID);
+        String camel = deployment != null ? deployment : pod.getMetadata().getLabels().get(LABEL_KUBERNETES_RUNTIME);
+        String runtime = deployment != null ? deployment : pod.getMetadata().getLabels().get(LABEL_CAMEL_RUNTIME);
         String type = pod.getMetadata().getLabels().get(LABEL_TYPE);
         ContainerStatus.ContainerType containerType = deployment != null
                 ? ContainerStatus.ContainerType.project
@@ -125,7 +126,8 @@ public class PodEventHandler implements ResourceEventHandler<Pod> {
             status.setContainerId(pod.getMetadata().getName());
             status.setPhase(pod.getStatus().getPhase());
             status.setPodIP(pod.getStatus().getPodIP());
-            if (ready) {
+            status.setCamelRuntime(runtime != null ? runtime : (camel != null ? CamelRuntime.CAMEL_MAIN.getValue() : ""));
+            if (running) {
                 status.setState(ContainerStatus.State.running.name());
             } else if (failed) {
                 status.setState(ContainerStatus.State.dead.name());
@@ -136,6 +138,7 @@ public class PodEventHandler implements ResourceEventHandler<Pod> {
             }
             return status;
         } catch (Exception ex) {
+            ex.printStackTrace();
             LOGGER.error(ex.getMessage(), ex.getCause());
             return null;
         }
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CamelService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CamelService.java
index 134b41fe..0923251a 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CamelService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CamelService.java
@@ -79,6 +79,7 @@ public class CamelService {
 
     @Scheduled(every = "{karavan.camel.status.interval}", concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
     public void collectCamelStatuses() {
+        LOGGER.info("Collect Camel Statuses");
         if (infinispanService.isReady()) {
             infinispanService.getContainerStatuses(environment).stream()
                     .filter(cs ->
@@ -98,12 +99,13 @@ public class CamelService {
     public void reloadProjectCode(String projectId) {
         LOGGER.info("Reload project code " + projectId);
         try {
+            deleteRequest(projectId);
             Map<String, String> files = codeService.getProjectFilesForDevMode(projectId, true);
             files.forEach((name, code) -> putRequest(projectId, name, code, 1000));
             reloadRequest(projectId);
             ContainerStatus containerStatus = infinispanService.getDevModeContainerStatus(projectId, environment);
             containerStatus.setCodeLoaded(true);
-            eventBus.send(ContainerStatusService.CONTAINER_STATUS, JsonObject.mapFrom(containerStatus));
+            eventBus.publish(ContainerStatusService.CONTAINER_STATUS, JsonObject.mapFrom(containerStatus));
         } catch (Exception ex) {
             LOGGER.error(ex.getMessage());
         }
@@ -122,10 +124,20 @@ public class CamelService {
         return false;
     }
 
+    public String deleteRequest(String containerName) {
+        String url = getContainerAddressForReload(containerName) + "/q/upload/*";
+        try {
+            return deleteResult(url, 1000);
+        } catch (InterruptedException | ExecutionException e) {
+            LOGGER.error(e.getMessage());
+        }
+        return null;
+    }
+
     public String reloadRequest(String containerName) {
         String url = getContainerAddressForReload(containerName) + "/q/dev/reload?reload=true";
         try {
-            return result(url, 1000);
+            return getResult(url, 1000);
         } catch (InterruptedException | ExecutionException e) {
             LOGGER.error(e.getMessage());
         }
@@ -158,7 +170,7 @@ public class CamelService {
     public String getCamelStatus(ContainerStatus containerStatus, CamelStatusValue.Name statusName) {
         String url = getContainerAddressForStatus(containerStatus) + "/q/dev/" + statusName.name();
         try {
-            return result(url, 500);
+            return getResult(url, 500);
         } catch (InterruptedException | ExecutionException e) {
             LOGGER.error(e.getMessage());
         }
@@ -169,6 +181,7 @@ public class CamelService {
     public void collectCamelStatuses(JsonObject data) {
         CamelStatusRequest dms = data.getJsonObject("camelStatusRequest").mapTo(CamelStatusRequest.class);
         ContainerStatus containerStatus = data.getJsonObject("containerStatus").mapTo(ContainerStatus.class);
+        LOGGER.info("Collect Camel Status for " + containerStatus.getContainerName());
         String projectId = dms.getProjectId();
         String containerName = dms.getContainerName();
         List<CamelStatusValue> statuses = new ArrayList<>();
@@ -176,27 +189,14 @@ public class CamelService {
             String status = getCamelStatus(containerStatus, statusName);
             if (status != null) {
                 statuses.add(new CamelStatusValue(statusName, status));
-                if (ConfigService.inKubernetes() && Objects.equals(statusName, CamelStatusValue.Name.context)) {
-                    checkReloadRequired(containerStatus);
-                }
             }
         });
         CamelStatus cs = new CamelStatus(projectId, containerName, statuses, environment);
         infinispanService.saveCamelStatus(cs);
     }
 
-    private void checkReloadRequired(ContainerStatus cs) {
-        if (ConfigService.inKubernetes()) {
-            if (!Objects.equals(cs.getCodeLoaded(), true)
-                    && Objects.equals(cs.getState(), ContainerStatus.State.running.name())
-                    && Objects.equals(cs.getType(), ContainerStatus.ContainerType.devmode)) {
-                eventBus.publish(RELOAD_PROJECT_CODE, cs.getProjectId());
-            }
-        }
-    }
-
     @CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 0.5, delay = 1000)
-    public String result(String url, int timeout) throws InterruptedException, ExecutionException {
+    public String getResult(String url, int timeout) throws InterruptedException, ExecutionException {
         try {
             HttpResponse<Buffer> result = getWebClient().getAbs(url).putHeader("Accept", "application/json")
                     .timeout(timeout).send().subscribeAsCompletionStage().toCompletableFuture().get();
@@ -210,6 +210,18 @@ public class CamelService {
         return null;
     }
 
+    public String deleteResult(String url, int timeout) throws InterruptedException, ExecutionException {
+        try {
+            HttpResponse<Buffer> result = getWebClient().deleteAbs(url)
+                    .timeout(timeout).send().subscribeAsCompletionStage().toCompletableFuture().get();
+                JsonObject res = result.bodyAsJsonObject();
+                return res.encodePrettily();
+        } catch (Exception e) {
+            LOGGER.info(e.getMessage());
+        }
+        return null;
+    }
+
     public static class CamelStatusRequest {
         private String projectId;
         private String containerName;
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ContainerStatusService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ContainerStatusService.java
index 9909b20b..712eb638 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ContainerStatusService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ContainerStatusService.java
@@ -103,6 +103,7 @@ public class ContainerStatusService {
         if (infinispanService.isReady()) {
             ContainerStatus newStatus = data.mapTo(ContainerStatus.class);
             ContainerStatus oldStatus = infinispanService.getContainerStatus(newStatus.getProjectId(), newStatus.getEnv(), newStatus.getContainerName());
+
             if (oldStatus == null) {
                 infinispanService.saveContainerStatus(newStatus);
             } else if (Objects.equals(oldStatus.getInTransit(), Boolean.FALSE)) {
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
index 3634fee3..75d0f004 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
@@ -34,6 +34,7 @@ import org.apache.camel.karavan.infinispan.model.Project;
 import org.apache.camel.karavan.infinispan.model.ProjectFile;
 import org.apache.camel.karavan.kubernetes.KubernetesService;
 import org.apache.camel.karavan.registry.RegistryService;
+import org.apache.camel.karavan.shared.Constants;
 import org.apache.camel.karavan.shared.Property;
 import org.apache.camel.karavan.shared.exception.ProjectExistsException;
 import org.apache.commons.lang3.StringUtils;
@@ -99,16 +100,14 @@ public class ProjectService implements HealthCheck {
         if (status == null) {
             status = ContainerStatus.createDevMode(project.getProjectId(), environment);
         }
-
         if (!Objects.equals(status.getState(), ContainerStatus.State.running.name())) {
             status.setInTransit(true);
-            eventBus.send(ContainerStatusService.CONTAINER_STATUS, JsonObject.mapFrom(status));
+            eventBus.publish(ContainerStatusService.CONTAINER_STATUS, JsonObject.mapFrom(status));
 
+            Map<String, String> files = codeService.getProjectFilesForDevMode(project.getProjectId(), true);
             if (ConfigService.inKubernetes()) {
-                kubernetesService.runDevModeContainer(project, jBangOptions);
+                kubernetesService.runDevModeContainer(project, jBangOptions, files);
             } else {
-                Map<String, String> files = codeService.getProjectFilesForDevMode(project.getProjectId(), true);
-
                 DockerComposeService dcs = codeService.getDockerComposeService(project.getProjectId());
                 dockerForKaravan.runProjectInDevMode(project.getProjectId(), jBangOptions, dcs.getPortsMap(), files);
             }
@@ -260,9 +259,11 @@ public class ProjectService implements HealthCheck {
             if (infinispanService.getProjects().isEmpty()) {
                 importAllProjects();
             }
-            addKameletsProject();
-            addTemplatesProject();
-            addServicesProject();
+            if (Objects.equals(environment, Constants.DEV_ENV)) {
+                addKameletsProject();
+                addTemplatesProject();
+                addServicesProject();
+            }
             ready.set(true);
         } else {
             LOGGER.info("Projects are not ready");
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java
index 8b633ed0..72ea919d 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java
@@ -18,12 +18,14 @@ package org.apache.camel.karavan.shared;
 
 public class Constants {
 
+    public static final String DEV_ENV = "dev";
     public static final String ENV_VAR_JBANG_OPTIONS = "JBANG_OPTIONS";
 
     public static final String LABEL_PART_OF = "app.kubernetes.io/part-of";
     public static final String LABEL_TYPE = "org.apache.camel.karavan/type";
     public static final String LABEL_PROJECT_ID = "org.apache.camel.karavan/projectId";
     public static final String LABEL_CAMEL_RUNTIME = "org.apache.camel.karavan/runtime";
+    public static final String LABEL_KUBERNETES_RUNTIME = "app.kubernetes.io/runtime";
     public static final String LABEL_TAG = "org.apache.camel.karavan/tag";
 
     public static final String BUILDER_SUFFIX = "-builder";
@@ -36,6 +38,9 @@ public class Constants {
     public static final String M2_CACHE_SUFFIX = "m2-cache";
     public static final String PVC_MAVEN_SETTINGS = "maven-settings";
 
+    public static final String BUILD_CONFIG_MAP = "build-config-map";
+    public static final String BUILD_SCRIPT_FILENAME_SUFFIX = "-build.sh";
+
     public enum CamelRuntime {
         CAMEL_MAIN("camel-main"),
         QUARKUS("quarkus"),
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
index 5c01bd6f..0d07f0da 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
@@ -273,6 +273,17 @@ export class KaravanApi {
         });
     }
 
+    static async updateBuildConfigMap(after: (res: AxiosResponse<any>) => void) {
+        instance.post('/api/build/update-config-map', "{}")
+            .then(res => {
+                if (res.status === 200) {
+                    after(res.data);
+                }
+            }).catch(err => {
+            console.log(err);
+        });
+    }
+
     static async getFiles(projectId: string, after: (files: ProjectFile[]) => void) {
         instance.get('/api/file/' + projectId)
             .then(res => {
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElementHeader.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElementHeader.tsx
index d201d105..c850e514 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElementHeader.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElementHeader.tsx
@@ -147,8 +147,9 @@ export function DslElementHeader(props: Props) {
             classes.push(isElementSelected() ? 'header-bottom-selected' : 'header-bottom-not-selected')
         } else if (step.dslName === 'RouteConfigurationDefinition') {
             classes.push('header-route')
-            if (hasElements(step)) classes.push('header-bottom-line')
-            classes.push(isElementSelected() ? 'header-bottom-selected' : 'header-bottom-not-selected')
+            if (hasElements(step)) {
+                classes.push(isElementSelected() ? 'header-bottom-selected' : 'header-bottom-not-selected')
+            }
         } else {
             classes.push('header')
         }
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/property/ComponentParameterField.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/property/ComponentParameterField.tsx
index f80c3e75..49eb2f6a 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/route/property/ComponentParameterField.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/property/ComponentParameterField.tsx
@@ -297,13 +297,14 @@ export function ComponentParameterField(props: Props) {
     }
 
     function getSwitch(property: ComponentProperty, value: any) {
+        const isChecked = value !== undefined ? Boolean(value) : (property.defaultValue !== undefined && ['true', true].includes(property.defaultValue))
         return (
             <Switch
                 id={id} name={id}
                 value={value?.toString()}
                 aria-label={id}
-                isChecked={value !== undefined ? Boolean(value) : property.defaultValue !== undefined && property.defaultValue === 'true'}
-                onChange={e => parametersChanged(property.name, !Boolean(value))}/>
+                isChecked={isChecked}
+                onChange={(e, checked) => parametersChanged(property.name, checked)}/>
         )
     }
 
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx
index 77974d13..a7feb03c 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx
@@ -171,7 +171,8 @@ export function DslPropertyField(props: Props) {
 
     function isUriReadOnly(property: PropertyMeta): boolean {
         const dslName: string = props.element?.dslName || '';
-        return property.name === 'uri' && !['ToDynamicDefinition', 'WireTapDefinition', 'InterceptFromDefinition'].includes(dslName)
+        return property.name === 'uri'
+            && !['ToDynamicDefinition', 'WireTapDefinition', 'InterceptFromDefinition', 'InterceptSendToEndpointDefinition'].includes(dslName)
     }
 
     function selectInfrastructure(value: string) {
diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx
index 1457adba..51b97e20 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx
@@ -18,7 +18,14 @@ import React from 'react';
 import '../karavan.css';
 import {DslMetaModel} from "../utils/DslMetaModel";
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
-import {ChoiceDefinition, FromDefinition, LogDefinition, RouteConfigurationDefinition, RouteDefinition} from "karavan-core/lib/model/CamelDefinition";
+import {
+    ChoiceDefinition,
+    FromDefinition, JsonDataFormat,
+    LogDefinition,
+    MarshalDefinition,
+    RouteConfigurationDefinition,
+    RouteDefinition, UnmarshalDefinition
+} from "karavan-core/lib/model/CamelDefinition";
 import {CamelElement, MetadataLabels} from "karavan-core/lib/model/IntegrationDefinition";
 import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
 import {CamelDefinitionApi} from "karavan-core/lib/api/CamelDefinitionApi";
@@ -247,6 +254,7 @@ export function useRouteDesignerHook () {
             default:
                 const step = CamelDefinitionApi.createStep(dsl.dsl, undefined);
                 const augmentedStep = setDslDefaults(step);
+                console.log(step, augmentedStep)
                 addStep(augmentedStep, parentId, position)
                 break;
         }
@@ -261,6 +269,16 @@ export function useRouteDesignerHook () {
             (step as ChoiceDefinition).when?.push(CamelDefinitionApi.createStep('WhenDefinition', undefined));
             (step as ChoiceDefinition).otherwise = CamelDefinitionApi.createStep('OtherwiseDefinition', undefined);
         }
+        if (step.dslName === 'MarshalDefinition') {
+            if (CamelDefinitionApiExt.getDataFormat(step) === undefined) {
+                (step as MarshalDefinition).json = new JsonDataFormat()
+            }
+        }
+        if (step.dslName === 'UnmarshalDefinition') {
+            if (CamelDefinitionApiExt.getDataFormat(step) === undefined) {
+                (step as UnmarshalDefinition).json = new JsonDataFormat()
+            }
+        }
         return step;
     }
 
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/file/FileEditor.tsx b/karavan-web/karavan-app/src/main/webui/src/project/FileEditor.tsx
similarity index 82%
rename from karavan-web/karavan-app/src/main/webui/src/project/file/FileEditor.tsx
rename to karavan-web/karavan-app/src/main/webui/src/project/FileEditor.tsx
index b5bf2fdc..b98285eb 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/file/FileEditor.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/FileEditor.tsx
@@ -15,18 +15,14 @@
  * limitations under the License.
  */
 import React from 'react';
-import '../../designer/karavan.css';
+import '../designer/karavan.css';
 import Editor from "@monaco-editor/react";
 import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
-import {ProjectFile} from "../../api/ProjectModels";
-import {useFilesStore, useFileStore} from "../../api/ProjectStore";
-import {KaravanDesigner} from "../../designer/KaravanDesigner";
-import {ProjectService} from "../../api/ProjectService";
-import {PropertiesTable} from "./PropertiesTable";
+import {ProjectFile} from "../api/ProjectModels";
+import {useFilesStore, useFileStore} from "../api/ProjectStore";
+import {KaravanDesigner} from "../designer/KaravanDesigner";
+import {ProjectService} from "../api/ProjectService";
 import {shallow} from "zustand/shallow";
-import {PropertiesToolbar} from "./PropertiesToolbar";
-import {Card, Panel} from "@patternfly/react-core";
-import {PropertiesPanel} from "./PropertiesPanel";
 
 interface Props {
     projectId: string
@@ -34,7 +30,8 @@ interface Props {
 
 const languages = new Map<string, string>([
     ['sh', 'shell'],
-    ['md', 'markdown']
+    ['md', 'markdown'],
+    ['properties', 'ini']
 ])
 
 export function FileEditor (props: Props) {
@@ -93,14 +90,12 @@ export function FileEditor (props: Props) {
     const isCamelYaml = file !== undefined && file.name.endsWith(".camel.yaml");
     const isKameletYaml = file !== undefined && file.name.endsWith(".kamelet.yaml");
     const isIntegration = isCamelYaml && file?.code && CamelDefinitionYaml.yamlIsIntegration(file.code);
-    const isProperties = file !== undefined && file.name.endsWith("properties");
     const showDesigner = (isCamelYaml && isIntegration) || isKameletYaml;
-    const showEditor = !showDesigner && !isProperties;
+    const showEditor = !showDesigner;
     return (
         <>
             {showDesigner && getDesigner()}
             {showEditor && getEditor()}
-            {isProperties && file !== undefined && <PropertiesPanel/>}
         </>
     )
 }
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx b/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx
index 39c4ff1e..59950bb6 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx
@@ -29,7 +29,7 @@ import {useAppConfigStore, useFilesStore, useFileStore, useProjectsStore, usePro
 import {MainToolbar} from "../designer/MainToolbar";
 import {ProjectTitle} from "./ProjectTitle";
 import {ProjectPanel} from "./ProjectPanel";
-import {FileEditor} from "./file/FileEditor";
+import {FileEditor} from "./FileEditor";
 import {shallow} from "zustand/shallow";
 import {useParams} from "react-router-dom";
 import {KaravanApi} from "../api/KaravanApi";
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesPanel.tsx b/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesPanel.tsx
deleted file mode 100644
index db4e290c..00000000
--- a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesPanel.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import React from 'react';
-import {
-    PageSection, PanelHeader, Panel
-} from '@patternfly/react-core';
-import '../../designer/karavan.css';
-import { useProjectStore} from "../../api/ProjectStore";
-import { ProjectFileTypes} from "../../api/ProjectModels";
-import {shallow} from "zustand/shallow";
-import {PropertiesToolbar} from "./PropertiesToolbar";
-import {PropertiesTable} from "./PropertiesTable";
-
-export function PropertiesPanel () {
-
-    const [project] = useProjectStore((s) => [s.project], shallow);
-
-    function isBuildIn(): boolean {
-        return ['kamelets', 'templates', 'services'].includes(project.projectId);
-    }
-
-    function canDeleteFiles(): boolean {
-        return !['templates', 'services'].includes(project.projectId);
-    }
-
-    function isKameletsProject(): boolean {
-        return project.projectId === 'kamelets';
-    }
-
-    const types = isBuildIn()
-        ? (isKameletsProject() ? ['KAMELET'] : ['CODE', 'PROPERTIES'])
-        : ProjectFileTypes.filter(p => !['PROPERTIES', 'LOG', 'KAMELET'].includes(p.name)).map(p => p.name);
-
-    return (
-        <PageSection padding={{default: 'noPadding'}} className="scrollable-out">
-            <PageSection isFilled padding={{default: 'padding'}} className="scrollable-in">
-            <Panel>
-                <PanelHeader>
-                    <PropertiesToolbar/>
-                </PanelHeader>
-            </Panel>
-                <PropertiesTable/>
-        </PageSection>
-        </PageSection>
-    )
-}
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesTable.tsx b/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesTable.tsx
deleted file mode 100644
index 71357cbb..00000000
--- a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesTable.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import React, {useEffect, useState} from 'react';
-import {
-    Button,
-    Modal,
-} from '@patternfly/react-core';
-import '../../designer/karavan.css';
-import {
-    Tbody,
-    Th,
-    Thead,
-    Tr
-} from '@patternfly/react-table';
-import {
-    Table
-} from '@patternfly/react-table/deprecated';
-
-import {ProjectModel, ProjectProperty} from "karavan-core/lib/model/ProjectModel";
-import {useFileStore} from "../../api/ProjectStore";
-import {ProjectModelApi} from "karavan-core/lib/api/ProjectModelApi";
-import {shallow} from "zustand/shallow"
-import {PropertyField} from "./PropertyField";
-import {ProjectService} from "../../api/ProjectService";
-
-export function PropertiesTable() {
-
-    const [showDeleteConfirmation, setShowDeleteConfirmation] = useState<boolean>(false);
-    const [deleteId, setDeleteId] = useState<string | undefined>(undefined);
-    const [key, setKey] = useState<string | undefined>(undefined);
-    const [properties, setProperties] = useState<ProjectProperty[]>([]);
-    const [file, editAdvancedProperties, addProperty, setAddProperty] = useFileStore((state) =>
-        [state.file, state.editAdvancedProperties, state.addProperty, state.setAddProperty], shallow)
-
-    useEffect(() => {
-        setProperties(getProjectModel().properties)
-    }, [addProperty,setDeleteId]);
-
-    function save(props: ProjectProperty[]) {
-        if (file) {
-            file.code = ProjectModelApi.propertiesToString(props);
-            ProjectService.saveFile(file, true);
-        }
-    }
-
-    function getProjectModel(): ProjectModel {
-        return file ? ProjectModelApi.propertiesToProject(file?.code) : ProjectModel.createNew()
-    }
-
-    function changeProperty(property: ProjectProperty) {
-        const props = properties.map(prop => prop.id === property.id ? property : prop);
-        save(props);
-    }
-
-    function startDelete(id: string) {
-        setShowDeleteConfirmation(true);
-        setDeleteId(id);
-    }
-
-    function confirmDelete() {
-        const props = properties.filter(p => p.id !== deleteId);
-        save(props);
-        setShowDeleteConfirmation(false);
-        setDeleteId(undefined);
-        setAddProperty(Math.random().toString());
-    }
-
-    function getDeleteConfirmation() {
-        return (<Modal
-            className="modal-delete"
-            title="Confirmation"
-            isOpen={showDeleteConfirmation}
-            onClose={() => setShowDeleteConfirmation(false)}
-            actions={[
-                <Button key="confirm" variant="primary" onClick={e => confirmDelete()}>Delete</Button>,
-                <Button key="cancel" variant="link"
-                        onClick={e => setShowDeleteConfirmation(false)}>Cancel</Button>
-            ]}
-            onEscapePress={e => setShowDeleteConfirmation(false)}>
-            <div>Delete property?</div>
-        </Modal>)
-    }
-
-    return (
-        <>
-            {properties.length > 0 &&
-                <Table aria-label="Property table" variant='compact' borders={false}
-                       className="project-properties">
-                    <Thead>
-                        <Tr>
-                            <Th key='name'>Name</Th>
-                            <Th key='value'>Value</Th>
-                            <Th></Th>
-                        </Tr>
-                    </Thead>
-                    <Tbody>
-                        {properties.map((property, idx: number) => {
-                            const readOnly = (property.key.startsWith("camel.jbang") || property.key.startsWith("camel.karavan")) && !editAdvancedProperties;
-                            return (
-                                <PropertyField key={idx + property.key}
-                                               property={property}
-                                               readOnly={readOnly}
-                                               changeProperty={changeProperty}
-                                               onDelete={startDelete}/>
-                            )
-                        })}
-                    </Tbody>
-                </Table>}
-            {showDeleteConfirmation && getDeleteConfirmation()}
-        </>
-    )
-}
\ No newline at end of file
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesToolbar.tsx b/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesToolbar.tsx
deleted file mode 100644
index 598dc95e..00000000
--- a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesToolbar.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import React from 'react';
-import {
-    Button, Checkbox,
-    Flex,
-    FlexItem
-} from '@patternfly/react-core';
-import '../../designer/karavan.css';
-import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
-import {useFileStore} from "../../api/ProjectStore";
-import {shallow} from "zustand/shallow";
-import {ProjectService} from "../../api/ProjectService";
-import {ProjectModelApi} from "karavan-core/lib/api/ProjectModelApi";
-import {ProjectModel, ProjectProperty} from "karavan-core/lib/model/ProjectModel";
-
-export function PropertiesToolbar () {
-
-    const [file, editAdvancedProperties, setEditAdvancedProperties, setAddProperty] = useFileStore((state) =>
-        [state.file, state.editAdvancedProperties, state.setEditAdvancedProperties, state.setAddProperty], shallow )
-
-
-    function addProperty() {
-        if (file) {
-            const project = file ? ProjectModelApi.propertiesToProject(file?.code) : ProjectModel.createNew();
-            const props = project.properties;
-            props.push(ProjectProperty.createNew("", ""));
-            file.code = ProjectModelApi.propertiesToString(props);
-            ProjectService.saveFile(file, true);
-            setAddProperty(Math.random().toString());
-        }
-    }
-
-    return (
-        <Flex className="toolbar" direction={{default: "row"}} justifyContent={{default: "justifyContentFlexEnd"}}>
-            <FlexItem>
-                <Checkbox
-                    id="advanced"
-                    label="Edit advanced"
-                    isChecked={editAdvancedProperties}
-                    onChange={(_, checked) => setEditAdvancedProperties(checked)}
-                />
-            </FlexItem>
-            <FlexItem>
-                <Button size="sm" variant="primary" icon={<PlusIcon/>} onClick={e => addProperty()}>Add property</Button>
-            </FlexItem>
-        </Flex>
-    )
-}
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertyField.tsx b/karavan-web/karavan-app/src/main/webui/src/project/file/PropertyField.tsx
deleted file mode 100644
index 2393164d..00000000
--- a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertyField.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import React, {useEffect, useState} from 'react';
-import {
-    Button,
-    TextInput
-} from '@patternfly/react-core';
-import '../../designer/karavan.css';
-import {ProjectProperty} from "karavan-core/lib/model/ProjectModel";
-import {Td, Tr} from "@patternfly/react-table";
-import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
-
-interface Props {
-    property: ProjectProperty,
-    readOnly: boolean,
-    changeProperty: (p: ProjectProperty) => void
-    onDelete: (id: string) => void
-}
-
-export function PropertyField (props: Props) {
-
-    const [key, setKey] = useState<string | undefined>(props.property.key);
-    const [value, setValue] = useState<string | undefined>(props.property.value);
-
-    useEffect(() => {
-    }, []);
-
-    return (
-        <Tr key={props.property.id}>
-            <Td noPadding width={10} dataLabel="key">
-                <TextInput isDisabled={props.readOnly} isRequired={true} className="text-field" type={"text"}
-                           id={"key-" + props.property.id}
-                           value={key}
-                           onChange={(e, val) => {
-                               e.preventDefault();
-                               setKey(val)
-                               props.changeProperty(new ProjectProperty({id: props.property.id, key: val, value: value}));
-                           }}/>
-            </Td>
-            <Td noPadding width={20} dataLabel="value">
-                <TextInput isDisabled={props.readOnly} isRequired={true} className="text-field" type={"text"}
-                           id={"value-" + props.property.id}
-                           value={value }
-                           onChange={(e, val) => {
-                               e.preventDefault();
-                               setValue(val);
-                               props.changeProperty(new ProjectProperty({id: props.property.id, key: key, value: val}));
-                           }}/>
-            </Td>
-            <Td noPadding isActionCell dataLabel="delete" className="delete-cell">
-                {!props.readOnly && <Button variant={"plain"} icon={<DeleteIcon/>} className={"delete-button"}
-                                      onClick={event => {
-                                          props.onDelete(props.property.id)
-                                      }}/>}
-            </Td>
-        </Tr>
-
-    )
-}
\ No newline at end of file
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx b/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
index 2b22b917..033ad6e0 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
@@ -33,14 +33,19 @@ import {
 import '../../designer/karavan.css';
 import UploadIcon from "@patternfly/react-icons/dist/esm/icons/upload-icon";
 import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
-import {useFilesStore, useFileStore, useProjectStore} from "../../api/ProjectStore";
+import {useAppConfigStore, useFilesStore, useFileStore, useProjectStore} from "../../api/ProjectStore";
 import {shallow} from "zustand/shallow";
 import {ProjectService} from "../../api/ProjectService";
 import PushIcon from "@patternfly/react-icons/dist/esm/icons/code-branch-icon";
+import UpdateIcon from "@patternfly/react-icons/dist/esm/icons/cog-icon";
 import RefreshIcon from "@patternfly/react-icons/dist/esm/icons/sync-alt-icon";
+import {ProjectType} from "../../api/ProjectModels";
+import {KaravanApi} from "../../api/KaravanApi";
+import {EventBus} from "../../designer/utils/EventBus";
 
 export function FileToolbar () {
 
+    const {config} = useAppConfigStore();
     const [commitMessageIsOpen, setCommitMessageIsOpen] = useState(false);
     const [pullIsOpen, setPullIsOpen] = useState(false);
     const [commitMessage, setCommitMessage] = useState('');
@@ -58,6 +63,12 @@ export function FileToolbar () {
         ProjectService.pushProject(project, commitMessage);
     }
 
+    function updateScripts () {
+        KaravanApi.updateBuildConfigMap(res => {
+            EventBus.sendAlert("Success", "Script updated!", "info")
+        })
+    }
+
     function pull () {
         setPullIsOpen(false);
         ProjectService.pullProject(project.projectId);
@@ -67,6 +78,10 @@ export function FileToolbar () {
         return !['templates', 'services'].includes(project.projectId);
     }
 
+    function isTemplates(): boolean {
+        return project.projectId === 'templates' && project.type === ProjectType.templates;
+    }
+
     function getCommitModal() {
         return (
             <Modal
@@ -198,6 +213,15 @@ export function FileToolbar () {
                 </Button>
             </Tooltip>
         </FlexItem>
+        {isTemplates() && config.infrastructure === 'kubernetes' && <FlexItem>
+            <Tooltip content="Update Build Script in Config Maps" position={"bottom-end"}>
+                <Button size="sm" variant={"primary"} icon={<UpdateIcon/>}
+                        onClick={e => updateScripts()}
+                >
+                    Update Script
+                </Button>
+            </Tooltip>
+        </FlexItem>}
         {canAddFiles() && <FlexItem>
             <Button size="sm" variant={"primary"} icon={<PlusIcon/>}
                     onClick={e => setFile("create")}>Create</Button>