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/07/26 00:40:25 UTC

[camel-karavan] branch main updated (f9c1ab4e -> c56e125b)

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

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


    from f9c1ab4e Fix #829
     new 5eafe295 Container management improvements #817
     new c56e125b DevMode improvements #817

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../apache/camel/karavan/api/DevModeResource.java  |  31 +++--
 .../camel/karavan/api/InfrastructureResource.java  |  62 +++++++--
 .../camel/karavan/docker/DockerEventListener.java  | 114 +++++++++--------
 .../apache/camel/karavan/docker/DockerService.java | 141 ++++++++++++++++-----
 .../karavan/kubernetes/KubernetesService.java      |  26 ++--
 .../camel/karavan/kubernetes/PodEventHandler.java  |  37 ++++--
 .../apache/camel/karavan/service/AuthService.java  |   1 -
 .../apache/camel/karavan/service/CamelService.java |   6 +-
 .../apache/camel/karavan/service/EventService.java |  41 +++---
 .../camel/karavan/service/ScheduledService.java    |  32 ++++-
 .../apache/camel/karavan/shared/Configuration.java |  16 +++
 .../org/apache/camel/karavan/shared/Constants.java |  14 +-
 .../org/apache/camel/karavan/shared/EventType.java |  18 ++-
 .../src/main/resources/application.properties      |   2 +-
 .../src/main/webui/src/api/KaravanApi.tsx          |  23 +++-
 .../src/main/webui/src/api/ProjectModels.ts        |   3 +
 .../src/main/webui/src/api/ProjectService.ts       |  65 +++++++---
 .../src/main/webui/src/api/ProjectStore.ts         |   6 +-
 .../webui/src/containers/ContainerTableRow.tsx     |  71 ++++++++---
 .../main/webui/src/containers/ContainersPage.tsx   |  35 ++---
 .../src/main/webui/src/project/DevModeToolbar.tsx  |  97 +++++++-------
 .../src/main/webui/src/project/ProjectPage.tsx     |   2 +-
 .../src/main/webui/src/project/ProjectToolbar.tsx  |   1 -
 .../webui/src/project/dashboard/InfoContainer.tsx  |   4 +-
 .../webui/src/project/pipeline/ProjectStatus.tsx   |   5 +-
 .../main/webui/src/services/ServicesTableRow.tsx   |   4 +-
 .../camel/karavan/headless/CamelService.java       |   4 +-
 .../camel/karavan/headless/HeadlessService.java    |   3 +-
 .../karavan/infinispan/InfinispanService.java      |  24 +++-
 .../karavan/infinispan/model/ContainerStatus.java  | 104 ++++++++++-----
 .../karavan/infinispan/model/KaravanSchema.java    |   4 +-
 31 files changed, 669 insertions(+), 327 deletions(-)
 copy karavan-vscode/webview/vscode.ts => karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java (72%)


[camel-karavan] 01/02: Container management improvements #817

Posted by ma...@apache.org.
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 5eafe2958b74831443eb3e552574953657e43a8f
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Tue Jul 25 17:27:28 2023 -0400

    Container management improvements #817
---
 .../apache/camel/karavan/api/DevModeResource.java  |  18 ++--
 .../camel/karavan/api/InfrastructureResource.java  |  62 +++++++++--
 .../camel/karavan/docker/DockerEventListener.java  | 111 +++++++++++---------
 .../apache/camel/karavan/docker/DockerService.java | 113 ++++++++++++++++-----
 .../karavan/kubernetes/KubernetesService.java      |   2 +-
 .../camel/karavan/kubernetes/PodEventHandler.java  |  28 +++--
 .../apache/camel/karavan/service/CamelService.java |   6 +-
 .../apache/camel/karavan/service/EventService.java |  41 ++++----
 .../camel/karavan/service/ScheduledService.java    |  32 +++++-
 .../org/apache/camel/karavan/shared/EventType.java |   2 +-
 .../src/main/resources/application.properties      |   2 +-
 .../src/main/webui/src/api/KaravanApi.tsx          |  23 +++--
 .../src/main/webui/src/api/ProjectModels.ts        |   3 +
 .../src/main/webui/src/api/ProjectService.ts       |  49 ++++++---
 .../src/main/webui/src/api/ProjectStore.ts         |   6 +-
 .../webui/src/containers/ContainerTableRow.tsx     |  71 +++++++++----
 .../main/webui/src/containers/ContainersPage.tsx   |  35 ++++---
 .../src/main/webui/src/project/DevModeToolbar.tsx  |  92 ++++++++---------
 .../src/main/webui/src/project/ProjectPage.tsx     |   2 +-
 .../webui/src/project/dashboard/InfoContainer.tsx  |   4 +-
 .../webui/src/project/pipeline/ProjectStatus.tsx   |   2 +-
 .../main/webui/src/services/ServicesTableRow.tsx   |   4 +-
 .../camel/karavan/headless/CamelService.java       |   4 +-
 .../camel/karavan/headless/HeadlessService.java    |   3 +-
 .../karavan/infinispan/InfinispanService.java      |  24 ++++-
 .../karavan/infinispan/model/ContainerStatus.java  | 104 +++++++++++++------
 .../karavan/infinispan/model/KaravanSchema.java    |   4 +-
 27 files changed, 572 insertions(+), 275 deletions(-)

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 13edee0e..6379b058 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
@@ -30,6 +30,7 @@ import javax.inject.Inject;
 import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import java.util.Objects;
 import java.util.Optional;
 
 @Path("/api/devmode")
@@ -58,7 +59,13 @@ public class DevModeResource {
         String containerName = project.getProjectId();
         ContainerStatus status = infinispanService.getDevModeContainerStatus(project.getProjectId(), environment);
         if (status == null) {
-            infinispanService.saveContainerStatus(ContainerStatus.createDevMode(project.getProjectId(), environment));
+            status = ContainerStatus.createDevMode(project.getProjectId(), environment);
+        }
+
+        if (!Objects.equals(status.getState(), ContainerStatus.State.running.name())){
+            status.setInTransit(true);
+            infinispanService.saveContainerStatus(status);
+
             if (ConfigService.inKubernetes()) {
                 kubernetesService.runDevModeContainer(project, "");
             } else {
@@ -93,15 +100,10 @@ public class DevModeResource {
     @Consumes(MediaType.APPLICATION_JSON)
     @Path("/{projectId}/{deletePVC}")
     public Response deleteDevMode(@PathParam("projectId") String projectId, @PathParam("deletePVC") boolean deletePVC) {
-        ContainerStatus status = infinispanService.getDevModeContainerStatus(projectId, environment);
-        if (status != null) {
-            status.setLifeCycle(ContainerStatus.Lifecycle.deleting);
-            infinispanService.saveContainerStatus(status);
-        }
+        infinispanService.setContainerStatusTransit(projectId, environment, projectId);
         if (ConfigService.inKubernetes()) {
             kubernetesService.deleteRunner(projectId, deletePVC);
         } else {
-            dockerService.stopContainer(projectId);
             dockerService.deleteContainer(projectId);
         }
         return Response.accepted().build();
@@ -109,7 +111,7 @@ public class DevModeResource {
 
     @GET
     @Produces(MediaType.APPLICATION_JSON)
-    @Path("/pod/{projectId}")
+    @Path("/container/{projectId}")
     public Response getPodStatus(@PathParam("projectId") String projectId) throws RuntimeException {
         if (infinispanService.isReady()) {
             ContainerStatus cs = infinispanService.getDevModeContainerStatus(projectId, environment);
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java
index 443c9e69..8291c7eb 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java
@@ -17,8 +17,10 @@
 package org.apache.camel.karavan.api;
 
 import io.smallrye.mutiny.Multi;
+import io.vertx.core.json.JsonObject;
 import io.vertx.mutiny.core.eventbus.EventBus;
 import io.vertx.mutiny.core.eventbus.Message;
+import org.apache.camel.karavan.docker.DockerService;
 import org.apache.camel.karavan.infinispan.InfinispanService;
 import org.apache.camel.karavan.infinispan.model.ContainerStatus;
 import org.apache.camel.karavan.infinispan.model.DeploymentStatus;
@@ -50,6 +52,9 @@ public class InfrastructureResource {
     @Inject
     KubernetesService kubernetesService;
 
+    @Inject
+    DockerService dockerService;
+
     @ConfigProperty(name = "karavan.environment")
     String environment;
 
@@ -101,9 +106,13 @@ public class InfrastructureResource {
     @Produces(MediaType.APPLICATION_JSON)
     @Path("/deployment")
     public List<DeploymentStatus> getAllDeploymentStatuses() throws Exception {
-        return infinispanService.getDeploymentStatuses().stream()
-                .sorted(Comparator.comparing(DeploymentStatus::getProjectId))
-                .collect(Collectors.toList());
+        if (infinispanService.isReady()) {
+            return infinispanService.getDeploymentStatuses().stream()
+                    .sorted(Comparator.comparing(DeploymentStatus::getProjectId))
+                    .collect(Collectors.toList());
+        } else {
+            return List.of();
+        }
     }
 
     @GET
@@ -154,9 +163,29 @@ public class InfrastructureResource {
         }
     }
 
+    @POST
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Path("/container/{env}/{name}")
+    public Response startContainer(@PathParam("env") String env, @PathParam("name") String name, JsonObject command) throws Exception {
+        if (infinispanService.isReady()) {
+            infinispanService.setContainerStatusTransit(name, env, name);
+            if (command.containsKey("command")) {
+                if (command.getString("command").equalsIgnoreCase("start")) {
+                    dockerService.startContainer(name);
+                    return Response.ok().build();
+                } else if (command.getString("command").equalsIgnoreCase("stop")) {
+                    dockerService.stopContainer(name);
+                    return Response.ok().build();
+                }
+            }
+        }
+        return Response.notModified().build();
+    }
+
     @GET
     @Produces(MediaType.APPLICATION_JSON)
-    @Path("/pod/{env}")
+    @Path("/container/{env}")
     public List<ContainerStatus> getContainerStatusesByEnv(@PathParam("env") String env) throws Exception {
         return infinispanService.getContainerStatuses(env).stream()
                 .sorted(Comparator.comparing(ContainerStatus::getProjectId))
@@ -165,10 +194,10 @@ public class InfrastructureResource {
 
     @GET
     @Produces(MediaType.APPLICATION_JSON)
-    @Path("/pod/{projectId}/{env}")
+    @Path("/container/{projectId}/{env}")
     public List<ContainerStatus> getContainerStatusesByProjectAndEnv(@PathParam("projectId") String projectId, @PathParam("env") String env) throws Exception {
         return infinispanService.getContainerStatuses(projectId, env).stream()
-                .filter(podStatus -> Objects.equals(podStatus.getType(), ContainerStatus.CType.project))
+                .filter(podStatus -> Objects.equals(podStatus.getType(), ContainerStatus.ContainerType.project))
                 .sorted(Comparator.comparing(ContainerStatus::getContainerName))
                 .collect(Collectors.toList());
     }
@@ -176,10 +205,23 @@ public class InfrastructureResource {
     @DELETE
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
-    @Path("/pod/{env}/{name}")
-    public Response deleteContainer(@PathParam("env") String env, @PathParam("name") String name) throws Exception {
-        kubernetesService.deletePod(name, kubernetesService.getNamespace());
-        return Response.accepted().build();
+    @Path("/container/{env}/{name}")
+    public Response deleteContainer(@PathParam("env") String env, @PathParam("name") String name) {
+        if (infinispanService.isReady()) {
+            infinispanService.setContainerStatusTransit(name, env, name);
+            try {
+                if (ConfigService.inKubernetes()) {
+                    kubernetesService.deletePod(name, kubernetesService.getNamespace());
+                } else {
+                    dockerService.deleteContainer(name);
+                }
+                return Response.accepted().build();
+            } catch (Exception e) {
+                LOGGER.error(e.getMessage());
+                return Response.notModified().build();
+            }
+        }
+        return Response.notModified().build();
     }
 
     @GET
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventListener.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventListener.java
index b055716d..86c40428 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventListener.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventListener.java
@@ -17,12 +17,10 @@ import javax.inject.Inject;
 import java.io.Closeable;
 import java.io.IOException;
 import java.time.Instant;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
 import java.util.stream.Collectors;
 
+import static org.apache.camel.karavan.docker.DockerService.LABEL_PROJECT_ID;
 import static org.apache.camel.karavan.docker.DockerService.LABEL_TYPE;
 import static org.apache.camel.karavan.shared.EventType.DEVMODE_CONTAINER_READY;
 import static org.apache.camel.karavan.shared.EventType.INFINISPAN_STARTED;
@@ -63,24 +61,22 @@ public class DockerEventListener implements ResultCallback<Event> {
 
     public void onContainerEvent(Event event, Container container) {
         if (infinispanService.isReady()) {
-            if (Arrays.asList("destroy", "stop", "die", "kill", "pause", "destroy", "rename").contains(event.getStatus())) {
-                onDeleteContainer(container);
-            } else if (Arrays.asList("create", "start", "unpause").contains(event.getStatus())) {
-                onCreateContainer(container, event);
-            } else {
+//             if (Arrays.asList("create", "start", "unpause", "stop", "pause").contains(event.getStatus())) {
+//                onExistingContainer(container);
+//            } else {
                 String status = event.getStatus();
                 if (status.startsWith("health_status:")) {
                     if (container.getNames()[0].equals("/infinispan")) {
                         onInfinispanHealthEvent(container, event);
                     } else if (inDevMode(container)) {
-                        onDevModeHealthEvent(container,event);
+                        onDevModeHealthEvent(container, event);
                     }
                 }
-            }
+//            }
         }
     }
 
-    private void onDeleteContainer(Container container){
+    public void onDeletedContainer(Container container) {
         String name = container.getNames()[0].replace("/", "");
         infinispanService.deleteContainerStatus(name, environment, name);
         if (inDevMode(container)) {
@@ -88,24 +84,27 @@ public class DockerEventListener implements ResultCallback<Event> {
         }
     }
 
-    protected void onCreateContainer(Container container, Event event){
-        String name = container.getNames()[0].replace("/", "");
-        List<Integer> ports = Arrays.stream(container.getPorts()).map(ContainerPort::getPrivatePort).filter(Objects::nonNull).collect(Collectors.toList());
-        ContainerStatus.Lifecycle lc = event.getStatus().equals("create") ? ContainerStatus.Lifecycle.init : ContainerStatus.Lifecycle.ready;
-        ContainerStatus.CType type = getCtype(container.getLabels());
-        String created = Instant.ofEpochSecond(container.getCreated()).toString();
-        ContainerStatus ci = infinispanService.getContainerStatus(name, environment, name);
-        if (ci == null) {
-            ci = ContainerStatus.createWithId(name, environment, container.getId(), container.getImage(), ports, type, lc, created);
-        } else {
-            ci.setContainerId(container.getId());
-            ci.setPorts(ports);
-            ci.setType(type);
-            ci.setLifeCycle(lc);
-            ci.setCreated(created);
-            ci.setImage(container.getImage());
+    protected void onExistingContainer(Container container) {
+        if (infinispanService.isReady()) {
+            String name = container.getNames()[0].replace("/", "");
+            List<Integer> ports = Arrays.stream(container.getPorts()).map(ContainerPort::getPrivatePort).filter(Objects::nonNull).collect(Collectors.toList());
+            List<ContainerStatus.Command> commands = getContainerCommand(container.getState());
+            ContainerStatus.ContainerType type = getContainerType(container.getLabels());
+            String created = Instant.ofEpochSecond(container.getCreated()).toString();
+            ContainerStatus ci = infinispanService.getContainerStatus(name, environment, name);
+            if (ci == null) {
+                ci = ContainerStatus.createWithId(name, environment, container.getId(), container.getImage(), ports, type, commands, container.getState(), created);
+            } else {
+                ci.setContainerId(container.getId());
+                ci.setPorts(ports);
+                ci.setType(type);
+                ci.setCommands(commands);
+                ci.setCreated(created);
+                ci.setState(container.getState());
+                ci.setImage(container.getImage());
+            }
+            infinispanService.saveContainerStatus(ci);
         }
-        infinispanService.saveContainerStatus(ci);
     }
 
     public void onInfinispanHealthEvent(Container container, Event event) {
@@ -116,36 +115,50 @@ public class DockerEventListener implements ResultCallback<Event> {
     }
 
     public void onDevModeHealthEvent(Container container, Event event) {
-        String name = container.getNames()[0].replace("/", "");
         String status = event.getStatus();
         String health = status.replace("health_status: ", "");
         LOGGER.infof("Container %s health status: %s", container.getNames()[0], health);
-        // update ContainerStatus: set ready and
-        ContainerStatus cs = infinispanService.getDevModeContainerStatus(name, environment);
-        if (cs != null) {
-            cs.setLifeCycle(ContainerStatus.Lifecycle.ready);
-            cs.setContainerId(container.getId());
-            infinispanService.saveContainerStatus(cs);
-            eventBus.publish(DEVMODE_CONTAINER_READY, JsonObject.mapFrom(cs));
-        }
+        eventBus.publish(DEVMODE_CONTAINER_READY, container.getLabels().get(LABEL_PROJECT_ID));
     }
 
     private boolean inDevMode(Container container) {
-        return Objects.equals(getCtype(container.getLabels()), ContainerStatus.CType.devmode);
+        return Objects.equals(getContainerType(container.getLabels()), ContainerStatus.ContainerType.devmode);
     }
 
-    private ContainerStatus.CType getCtype(Map<String, String> labels) {
+    private ContainerStatus.ContainerType getContainerType(Map<String, String> labels) {
         String type = labels.get(LABEL_TYPE);
-        if (Objects.equals(type, ContainerStatus.CType.devmode.name())) {
-            return ContainerStatus.CType.devmode;
-        } else if (Objects.equals(type, ContainerStatus.CType.devservice.name())) {
-            return ContainerStatus.CType.devservice;
-        } else if (Objects.equals(type, ContainerStatus.CType.project.name())) {
-            return ContainerStatus.CType.project;
-        } else if (Objects.equals(type, ContainerStatus.CType.internal.name())) {
-            return ContainerStatus.CType.internal;
+        if (Objects.equals(type, ContainerStatus.ContainerType.devmode.name())) {
+            return ContainerStatus.ContainerType.devmode;
+        } else if (Objects.equals(type, ContainerStatus.ContainerType.devservice.name())) {
+            return ContainerStatus.ContainerType.devservice;
+        } else if (Objects.equals(type, ContainerStatus.ContainerType.project.name())) {
+            return ContainerStatus.ContainerType.project;
+        } else if (Objects.equals(type, ContainerStatus.ContainerType.internal.name())) {
+            return ContainerStatus.ContainerType.internal;
+        }
+        return ContainerStatus.ContainerType.unknown;
+    }
+
+    private List<ContainerStatus.Command> getContainerCommand(String state) {
+        List<ContainerStatus.Command> result = new ArrayList<>();
+        if (Objects.equals(state, ContainerStatus.State.created.name())) {
+            result.add(ContainerStatus.Command.run);
+            result.add(ContainerStatus.Command.delete);
+        } else if (Objects.equals(state, ContainerStatus.State.exited.name())) {
+            result.add(ContainerStatus.Command.run);
+            result.add(ContainerStatus.Command.delete);
+        } else if (Objects.equals(state, ContainerStatus.State.running.name())) {
+            result.add(ContainerStatus.Command.pause);
+            result.add(ContainerStatus.Command.stop);
+            result.add(ContainerStatus.Command.delete);
+        } else if (Objects.equals(state, ContainerStatus.State.paused.name())) {
+            result.add(ContainerStatus.Command.run);
+            result.add(ContainerStatus.Command.stop);
+            result.add(ContainerStatus.Command.delete);
+        } else if (Objects.equals(state, ContainerStatus.State.dead.name())) {
+            result.add(ContainerStatus.Command.delete);
         }
-        return ContainerStatus.CType.unknown;
+        return result;
     }
 
     @Override
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
index 27d5a6b3..84178d09 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
@@ -14,7 +14,7 @@ import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
 import com.github.dockerjava.transport.DockerHttpClient;
 import io.smallrye.mutiny.tuples.Tuple2;
 import io.vertx.core.eventbus.EventBus;
-import io.vertx.core.json.JsonObject;
+import org.apache.camel.karavan.infinispan.InfinispanService;
 import org.apache.camel.karavan.infinispan.model.ContainerStatus;
 import org.apache.camel.karavan.infinispan.model.Project;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
@@ -24,6 +24,7 @@ import javax.enterprise.context.ApplicationScoped;
 import javax.inject.Inject;
 import java.io.IOException;
 import java.text.DecimalFormat;
+import java.time.Instant;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
@@ -47,6 +48,9 @@ public class DockerService {
     private static final Map<String, Tuple2<Long, Long>> previousStats = new ConcurrentHashMap<>();
     private static final List<String> infinispanHealthCheckCMD = List.of("CMD", "curl", "-f", "http://localhost:11222/rest/v2/cache-managers/default/health/status");
 
+    @ConfigProperty(name = "karavan.environment")
+    String environment;
+
     @ConfigProperty(name = "karavan.devmode.image")
     String devmodeImage;
 
@@ -67,17 +71,25 @@ public class DockerService {
     @Inject
     DockerEventListener dockerEventListener;
 
+    @Inject
+    InfinispanService infinispanService;
+
     @Inject
     EventBus eventBus;
 
+    private DockerClient dockerClient;
+
     public void runDevmodeContainer(Project project, String jBangOptions) throws InterruptedException {
         String projectId = project.getProjectId();
         LOGGER.infof("DevMode starting for %s", projectId);
+
         HealthCheck healthCheck = new HealthCheck().withTest(List.of("CMD", "curl", "-f", "http://localhost:8080/q/dev/health"))
                 .withInterval(10000000000L).withTimeout(10000000000L).withStartPeriod(10000000000L).withRetries(30);
+
         createContainer(projectId, devmodeImage,
                 List.of(), null, false, false, healthCheck,
-                Map.of(LABEL_TYPE, ContainerStatus.CType.devmode.name(), LABEL_PROJECT_ID, projectId));
+                Map.of(LABEL_TYPE, ContainerStatus.ContainerType.devmode.name(), LABEL_PROJECT_ID, projectId));
+
         startContainer(projectId);
         LOGGER.infof("DevMode started for %s", projectId);
     }
@@ -91,8 +103,9 @@ public class DockerService {
 
             createContainer(INFINISPAN_CONTAINER_NAME, infinispanImage,
                     List.of("USER=" + infinispanUsername, "PASS=" + infinispanPassword),
-                    infinispanPort, false, true, healthCheck, Map.of()
-            );
+                    infinispanPort, false, true, healthCheck,
+                    Map.of(LABEL_TYPE, ContainerStatus.ContainerType.internal.name()));
+
             startContainer(INFINISPAN_CONTAINER_NAME);
             LOGGER.info("Infinispan is started");
         } catch (Exception e) {
@@ -110,7 +123,9 @@ public class DockerService {
                             "INFINISPAN_USERNAME=" + infinispanUsername,
                             "INFINISPAN_PASSWORD=" + infinispanPassword
                     ),
-                    null, false, false, new HealthCheck(), Map.of());
+                    null, false, false, new HealthCheck(),
+                    Map.of(LABEL_TYPE, ContainerStatus.ContainerType.internal.name()));
+
             startContainer(KARAVAN_CONTAINER_NAME);
             LOGGER.info("Karavan headless is started");
         } catch (Exception e) {
@@ -127,26 +142,33 @@ public class DockerService {
         }
     }
 
-    public void collectContainersStats() {
-        getDockerClient().listContainersCmd().exec().forEach(container -> {
-            Statistics stats = getContainerStats(container.getId());
-
-            String name = container.getNames()[0].replace("/", "");
-            String memoryUsage = formatMemory(stats.getMemoryStats().getUsage());
-            String memoryLimit = formatMemory(stats.getMemoryStats().getLimit());
-            JsonObject data = JsonObject.of(
-                    "projectId", name,
-                    "memory", memoryUsage + " / " + memoryLimit,
-                    "cpu", formatCpu(name, stats)
-            );
-            eventBus.publish(CONTAINER_STATISTICS, data);
+    public List<ContainerStatus> collectContainersStatuses() {
+        List<ContainerStatus> result = new ArrayList<>();
+        getDockerClient().listContainersCmd().withShowAll(true).exec().forEach(container -> {
+            ContainerStatus containerStatus = getContainerStatus(container);
+            updateStatistics(containerStatus, container);
+            result.add(containerStatus);
         });
+        return result;
     }
 
-    public void collectContainersStatuses() {
-        getDockerClient().listContainersCmd().exec().forEach(container -> {
-            dockerEventListener.onCreateContainer(container, new Event(container.getStatus(), container.getId(), container.getImage(), container.getCreated()));
-        });
+    private ContainerStatus getContainerStatus(Container container) {
+        String name = container.getNames()[0].replace("/", "");
+        List<Integer> ports = Arrays.stream(container.getPorts()).map(ContainerPort::getPrivatePort).filter(Objects::nonNull).collect(Collectors.toList());
+        List<ContainerStatus.Command> commands = getContainerCommand(container.getState());
+        ContainerStatus.ContainerType type = getContainerType(container.getLabels());
+        String created = Instant.ofEpochSecond(container.getCreated()).toString();
+        return ContainerStatus.createWithId(name, environment, container.getId(), container.getImage(), ports, type, commands, container.getState(), created);
+    }
+
+    private void updateStatistics(ContainerStatus containerStatus, Container container) {
+        Statistics stats = getContainerStats(container.getId());
+        if (stats != null && stats.getMemoryStats() != null) {
+            String memoryUsage = formatMemory(stats.getMemoryStats().getUsage());
+            String memoryLimit = formatMemory(stats.getMemoryStats().getLimit());
+            containerStatus.setMemoryInfo(memoryUsage + " / " + memoryLimit);
+            containerStatus.setCpuInfo(formatCpu(containerStatus.getContainerName(), stats));
+        }
     }
 
     public void startListeners() {
@@ -276,10 +298,14 @@ public class DockerService {
     }
 
     public void deleteContainer(String name) {
+        long time = System.currentTimeMillis();
         List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
+        System.out.println("Get containers " + (System.currentTimeMillis() - time));
+        time = System.currentTimeMillis();
         if (containers.size() == 1) {
             Container container = containers.get(0);
-            getDockerClient().removeContainerCmd(container.getId()).exec();
+            getDockerClient().removeContainerCmd(container.getId()).withForce(true).exec();
+            System.out.println("removeContainerCmd " + (System.currentTimeMillis() - time));
         }
     }
 
@@ -335,7 +361,10 @@ public class DockerService {
     }
 
     private DockerClient getDockerClient() {
-        return DockerClientImpl.getInstance(getDockerClientConfig(), getDockerHttpClient());
+        if (dockerClient == null) {
+            dockerClient = DockerClientImpl.getInstance(getDockerClientConfig(), getDockerHttpClient());
+        }
+        return dockerClient;
     }
 
     private String formatMemory(Long memory) {
@@ -350,6 +379,42 @@ public class DockerService {
         }
     }
 
+    private ContainerStatus.ContainerType getContainerType(Map<String, String> labels) {
+        String type = labels.get(LABEL_TYPE);
+        if (Objects.equals(type, ContainerStatus.ContainerType.devmode.name())) {
+            return ContainerStatus.ContainerType.devmode;
+        } else if (Objects.equals(type, ContainerStatus.ContainerType.devservice.name())) {
+            return ContainerStatus.ContainerType.devservice;
+        } else if (Objects.equals(type, ContainerStatus.ContainerType.project.name())) {
+            return ContainerStatus.ContainerType.project;
+        } else if (Objects.equals(type, ContainerStatus.ContainerType.internal.name())) {
+            return ContainerStatus.ContainerType.internal;
+        }
+        return ContainerStatus.ContainerType.unknown;
+    }
+
+    private List<ContainerStatus.Command> getContainerCommand(String state) {
+        List<ContainerStatus.Command> result = new ArrayList<>();
+        if (Objects.equals(state, ContainerStatus.State.created.name())) {
+            result.add(ContainerStatus.Command.run);
+            result.add(ContainerStatus.Command.delete);
+        } else if (Objects.equals(state, ContainerStatus.State.exited.name())) {
+            result.add(ContainerStatus.Command.run);
+            result.add(ContainerStatus.Command.delete);
+        } else if (Objects.equals(state, ContainerStatus.State.running.name())) {
+            result.add(ContainerStatus.Command.pause);
+            result.add(ContainerStatus.Command.stop);
+            result.add(ContainerStatus.Command.delete);
+        } else if (Objects.equals(state, ContainerStatus.State.paused.name())) {
+            result.add(ContainerStatus.Command.run);
+            result.add(ContainerStatus.Command.stop);
+            result.add(ContainerStatus.Command.delete);
+        } else if (Objects.equals(state, ContainerStatus.State.dead.name())) {
+            result.add(ContainerStatus.Command.delete);
+        }
+        return result;
+    }
+
     private String formatCpu(String containerName, Statistics stats) {
         try {
             double cpuUsage = 0;
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 92971460..3be6850a 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
@@ -115,7 +115,7 @@ public class KubernetesService implements HealthCheck {
 
             SharedIndexInformer<Pod> podRunInformer = kubernetesClient().pods().inNamespace(getNamespace())
                     .withLabels(getRuntimeLabels()).inform();
-            podRunInformer.addEventHandlerWithResyncPeriod(new PodEventHandler(infinispanService, this), 30 * 1000L);
+            podRunInformer.addEventHandlerWithResyncPeriod(new PodEventHandler(infinispanService, this, eventBus), 30 * 1000L);
             informers.add(podRunInformer);
 
             LOGGER.info("Started Kubernetes Informers");
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 e60f354b..b46af28c 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
@@ -5,21 +5,28 @@ import io.fabric8.kubernetes.api.model.Pod;
 import io.fabric8.kubernetes.api.model.Quantity;
 import io.fabric8.kubernetes.api.model.ResourceRequirements;
 import io.fabric8.kubernetes.client.informers.ResourceEventHandler;
+import io.vertx.core.json.JsonObject;
+import io.vertx.mutiny.core.eventbus.EventBus;
 import org.apache.camel.karavan.infinispan.InfinispanService;
 import org.apache.camel.karavan.infinispan.model.ContainerStatus;
 import org.jboss.logging.Logger;
 
+import java.util.List;
+
 import static org.apache.camel.karavan.service.CodeService.DEFAULT_CONTAINER_RESOURCES;
+import static org.apache.camel.karavan.shared.EventType.CONTAINER_STATUS;
 
 public class PodEventHandler implements ResourceEventHandler<Pod> {
 
     private static final Logger LOGGER = Logger.getLogger(PodEventHandler.class.getName());
     private final InfinispanService infinispanService;
     private final KubernetesService kubernetesService;
+    private final EventBus eventBus;
 
-    public PodEventHandler(InfinispanService infinispanService, KubernetesService kubernetesService) {
+    public PodEventHandler(InfinispanService infinispanService, KubernetesService kubernetesService, EventBus eventBus) {
         this.infinispanService = infinispanService;
         this.kubernetesService = kubernetesService;
+        this.eventBus = eventBus;
     }
 
     @Override
@@ -28,7 +35,7 @@ public class PodEventHandler implements ResourceEventHandler<Pod> {
             LOGGER.info("onAdd " + pod.getMetadata().getName());
             ContainerStatus ps = getPodStatus(pod);
             if (ps != null) {
-                infinispanService.saveContainerStatus(ps);
+                eventBus.send(CONTAINER_STATUS, JsonObject.mapFrom(ps));
             }
         } catch (Exception e){
             LOGGER.error(e.getMessage(), e.getCause());
@@ -41,7 +48,7 @@ public class PodEventHandler implements ResourceEventHandler<Pod> {
             LOGGER.info("onUpdate " + newPod.getMetadata().getName());
             ContainerStatus ps = getPodStatus(newPod);
             if (ps != null) {
-                infinispanService.saveContainerStatus(ps);
+                eventBus.send(CONTAINER_STATUS, JsonObject.mapFrom(ps));
             }
         } catch (Exception e){
             LOGGER.error(e.getMessage(), e.getCause());
@@ -76,17 +83,22 @@ public class PodEventHandler implements ResourceEventHandler<Pod> {
             String requestCpu = resourceRequirements.getRequests().getOrDefault("cpu", new Quantity()).toString();
             String limitMemory = resourceRequirements.getLimits().getOrDefault("memory", new Quantity()).toString();
             String limitCpu = resourceRequirements.getLimits().getOrDefault("cpu", new Quantity()).toString();
-            return new ContainerStatus(
+            ContainerStatus status = new ContainerStatus(
                     pod.getMetadata().getName(),
-                    ContainerStatus.Lifecycle.ready,
+                    List.of(ContainerStatus.Command.delete),
                     projectId,
                     kubernetesService.environment,
-                    pod.getMetadata().getName().equals(projectId) ? ContainerStatus.CType.devmode : ContainerStatus.CType.project,
+                    pod.getMetadata().getName().equals(projectId) ? ContainerStatus.ContainerType.devmode : ContainerStatus.ContainerType.project,
                     requestMemory + " : " + limitMemory,
                     requestCpu + " : " + limitCpu,
-                    creationTimestamp
+                    creationTimestamp);
 
-            );
+            if (ready) {
+                status.setState(ContainerStatus.State.running.name());
+            } else {
+                status.setState(ContainerStatus.State.created.name());
+            }
+            return status;
         } catch (Exception ex) {
             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 ba3afb33..884ff168 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
@@ -37,6 +37,8 @@ import java.util.Arrays;
 import java.util.Objects;
 import java.util.concurrent.ExecutionException;
 
+import static org.apache.camel.karavan.shared.EventType.CONTAINER_STATUS;
+
 @ApplicationScoped
 public class CamelService {
 
@@ -71,12 +73,12 @@ public class CamelService {
     public void reloadProjectCode(String projectId) {
         LOGGER.info("Reload project code " + projectId);
         try {
-            ContainerStatus containerStatus = infinispanService.getDevModeContainerStatus(projectId, environment);
             infinispanService.getProjectFiles(projectId).forEach(projectFile ->
                     putRequest(projectId, projectFile.getName(), projectFile.getCode(), 1000));
             reloadRequest(projectId);
+            ContainerStatus containerStatus = infinispanService.getDevModeContainerStatus(projectId, environment);
             containerStatus.setCodeLoaded(true);
-            infinispanService.saveContainerStatus(containerStatus);
+            eventBus.send(CONTAINER_STATUS, JsonObject.mapFrom(containerStatus));
         } catch (Exception ex) {
             LOGGER.error(ex.getMessage());
         }
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/EventService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/EventService.java
index 4d6f0b7a..f3e011f4 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/EventService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/EventService.java
@@ -9,11 +9,13 @@ import org.apache.camel.karavan.infinispan.model.ContainerStatus;
 import org.apache.camel.karavan.kubernetes.KubernetesService;
 import org.apache.camel.karavan.shared.ConfigService;
 import org.apache.camel.karavan.shared.EventType;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.jboss.logging.Logger;
 
 import javax.enterprise.context.ApplicationScoped;
 import javax.inject.Inject;
 import java.io.IOException;
+import java.util.Objects;
 
 import static org.apache.camel.karavan.shared.EventType.*;
 
@@ -22,6 +24,9 @@ public class EventService {
 
     private static final Logger LOGGER = Logger.getLogger(EventService.class.getName());
 
+    @ConfigProperty(name = "karavan.environment")
+    String environment;
+
     @Inject
     InfinispanService infinispanService;
 
@@ -37,9 +42,6 @@ public class EventService {
     @Inject
     ProjectService projectService;
 
-    @Inject
-    ConfigService configService;
-
     @Inject
     EventBus bus;
 
@@ -73,29 +75,30 @@ public class EventService {
     }
 
     @ConsumeEvent(value = DEVMODE_CONTAINER_READY, blocking = true, ordered = true)
-    void receiveCommand(JsonObject message) {
-        LOGGER.info("received Status " + message);
-        ContainerStatus status = message.mapTo(ContainerStatus.class);
-        if (!status.getCodeLoaded() && status.getContainerId() != null && status.getLifeCycle().equals(ContainerStatus.Lifecycle.ready)) {
+    void receiveCommand(String projectId) {
+        LOGGER.info("DEVMODE_CONTAINER_READY " + projectId);
+        ContainerStatus status = infinispanService.getContainerStatus(projectId, environment, projectId);
+        if (!status.getCodeLoaded() && status.getContainerId() != null && status.getState().equals(ContainerStatus.State.running.name())) {
             if (ConfigService.inKubernetes()) {
-                camelService.reloadProjectCode(status.getProjectId());
+                camelService.reloadProjectCode(projectId);
             } else {
-                infinispanService.sendCodeReloadCommand(status.getProjectId());
+                infinispanService.sendCodeReloadCommand(projectId);
             }
         }
     }
 
-    @ConsumeEvent(value = CONTAINER_STATISTICS, blocking = true, ordered = true)
-    public void saveStats(JsonObject data) {
-        String projectId = data.getString("projectId");
-        String memory = data.getString("memory");
-        String cpu = data.getString("cpu");
+    @ConsumeEvent(value = CONTAINER_STATUS, blocking = true, ordered = true)
+    public void saveContainerStatus(JsonObject data) {
         if (infinispanService.isReady()) {
-            ContainerStatus containerStatus = infinispanService.getDevModeContainerStatus(projectId, configService.getConfiguration().getEnvironment());
-            if (containerStatus != null) {
-                containerStatus.setCpuInfo(cpu);
-                containerStatus.setMemoryInfo(memory);
-                infinispanService.saveContainerStatus(containerStatus);
+            ContainerStatus newStatus = data.mapTo(ContainerStatus.class);
+            System.out.println(newStatus);
+            ContainerStatus oldStatus = infinispanService.getContainerStatus(newStatus.getProjectId(), newStatus.getEnv(), newStatus.getContainerName());
+            if (oldStatus == null || Objects.equals(oldStatus.getInTransit(), Boolean.FALSE)) {
+                infinispanService.saveContainerStatus(newStatus);
+            } else if (Objects.equals(oldStatus.getInTransit(), Boolean.TRUE)){
+                if (!Objects.equals(oldStatus.getState(), newStatus.getState())) {
+                    infinispanService.saveContainerStatus(newStatus);
+                }
             }
         }
     }
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ScheduledService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ScheduledService.java
index 7886a86d..16fcbeb6 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ScheduledService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ScheduledService.java
@@ -17,19 +17,29 @@
 package org.apache.camel.karavan.service;
 
 import io.quarkus.scheduler.Scheduled;
+import io.vertx.core.eventbus.EventBus;
+import io.vertx.core.json.JsonObject;
 import org.apache.camel.karavan.docker.DockerService;
 import org.apache.camel.karavan.infinispan.InfinispanService;
+import org.apache.camel.karavan.infinispan.model.ContainerStatus;
 import org.apache.camel.karavan.shared.ConfigService;
+import org.apache.camel.karavan.shared.EventType;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.jboss.logging.Logger;
 
 import javax.enterprise.context.ApplicationScoped;
 import javax.inject.Inject;
+import java.util.List;
+import java.util.stream.Collectors;
 
 @ApplicationScoped
 public class ScheduledService {
 
     private static final Logger LOGGER = Logger.getLogger(ScheduledService.class.getName());
 
+    @ConfigProperty(name = "karavan.environment")
+    String environment;
+
     @Inject
     DockerService dockerService;
 
@@ -42,9 +52,25 @@ public class ScheduledService {
     @Inject
     InfinispanService infinispanService;
 
-    @Scheduled(every = "{karavan.container.statistics.interval}", concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
-    void collectContainersStats() {
-        dockerService.collectContainersStats();
+    @Inject
+    EventBus eventBus;
+
+    @Scheduled(every = "{karavan.container.status.interval}", concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
+    void collectContainersStatuses() {
+        if (infinispanService.isReady()) {
+            List<ContainerStatus> statusesInDocker = dockerService.collectContainersStatuses();
+            List<String> namesInDocker = statusesInDocker.stream().map(ContainerStatus::getContainerName).collect(Collectors.toList());
+            List<ContainerStatus> statusesInInfinispan = infinispanService.getContainerStatuses(environment);
+            // clean deleted
+            statusesInInfinispan.stream().filter(cs -> !namesInDocker.contains(cs.getContainerName())).forEach(containerStatus -> {
+                infinispanService.deleteContainerStatus(containerStatus);
+                infinispanService.deleteCamelStatuses(containerStatus.getProjectId(), containerStatus.getEnv());
+            });
+            // send statuses to save
+            statusesInDocker.forEach(containerStatus -> {
+                eventBus.send(EventType.CONTAINER_STATUS, JsonObject.mapFrom(containerStatus));
+            });
+        }
     }
 
     @Scheduled(every = "{karavan.container.infinispan.interval}", concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/EventType.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/EventType.java
index ac89a948..a1085fb9 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/EventType.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/EventType.java
@@ -10,7 +10,7 @@ public class EventType {
 
     public static final String INFINISPAN_STARTED = "INFINISPAN_STARTED";
 
-    public static final String CONTAINER_STATISTICS = "CONTAINER_STATISTICS";
+    public static final String CONTAINER_STATUS = "CONTAINER_STATUS";
     public static final String DEVMODE_CONTAINER_READY = "DEVMODE_STATUS";
 
 }
diff --git a/karavan-web/karavan-app/src/main/resources/application.properties b/karavan-web/karavan-app/src/main/resources/application.properties
index 37a78899..93f3db4b 100644
--- a/karavan-web/karavan-app/src/main/resources/application.properties
+++ b/karavan-web/karavan-app/src/main/resources/application.properties
@@ -4,7 +4,7 @@ karavan.environments=dev
 karavan.default-runtime=quarkus
 karavan.runtimes=quarkus,spring-boot
 karavan.camel.status.interval=off
-karavan.container.statistics.interval=3s
+karavan.container.status.interval=3s
 karavan.container.infinispan.interval=5s
 karavan.devmode.image=ghcr.io/apache/camel-karavan-runner:3.21.1-snapshot
 karavan.headless.image=entropy1/karavan-headless:3.21.1-SNAPSHOT
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 7a964268..fc0ed2e8 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
@@ -304,7 +304,7 @@ export class KaravanApi {
     }
 
     static async getDevModePodStatus(projectId: string, after: (res: AxiosResponse<ContainerStatus>) => void) {
-        instance.get('/api/devmode/pod/' + projectId)
+        instance.get('/api/devmode/container/' + projectId)
             .then(res => {
                 after(res);
             }).catch(err => {
@@ -312,7 +312,7 @@ export class KaravanApi {
         });
     }
 
-    static async reloadDevMode(projectId: string, after: (res: AxiosResponse<any>) => void) {
+    static async reloadDevModeCode(projectId: string, after: (res: AxiosResponse<any>) => void) {
         instance.get('/api/devmode/reload/' + projectId)
             .then(res => {
                 after(res);
@@ -330,7 +330,7 @@ export class KaravanApi {
         });
     }
 
-    static async runProject(project: Project, verbose: boolean, after: (res: AxiosResponse<string>) => void) {
+    static async startDevModeContainer(project: Project, verbose: boolean, after: (res: AxiosResponse<string>) => void) {
         instance.post('/api/devmode' + (verbose ? '/--verbose' : ''), project)
             .then(res => {
                 after(res);
@@ -339,7 +339,7 @@ export class KaravanApi {
         });
     }
 
-    static async deleteRunner(name: string, deletePVC: boolean, after: (res: AxiosResponse<any>) => void) {
+    static async deleteDevModeContainer(name: string, deletePVC: boolean, after: (res: AxiosResponse<any>) => void) {
         instance.delete('/api/devmode/' +  name + "/" + deletePVC)
             .then(res => {
                 after(res);
@@ -453,7 +453,7 @@ export class KaravanApi {
     }
 
     static async getProjectPodStatuses(project: string, env: string, after: (statuses: ContainerStatus[]) => void) {
-        instance.get('/api/infrastructure/pod/' + project + "/" + env)
+        instance.get('/api/infrastructure/container/' + project + "/" + env)
             .then(res => {
                 if (res.status === 200) {
                     after(res.data);
@@ -463,8 +463,17 @@ export class KaravanApi {
         });
     }
 
-    static async deletePod(environment: string, name: string, after: (res: AxiosResponse<any>) => void) {
-        instance.delete('/api/infrastructure/pod/' + environment + '/' + name)
+    static async manageContainer(environment: string, name: string, command: 'run' | 'pause' | 'stop', after: (res: AxiosResponse<any>) => void) {
+        instance.post('/api/infrastructure/container/' + environment + '/' + name, {command: command})
+            .then(res => {
+                after(res);
+            }).catch(err => {
+            after(err);
+        });
+    }
+
+    static async deleteContainer(environment: string, name: string, after: (res: AxiosResponse<any>) => void) {
+        instance.delete('/api/infrastructure/container/' + environment + '/' + name)
             .then(res => {
                 after(res);
             }).catch(err => {
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts b/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts
index fe0fb19e..3a4ced02 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts
@@ -71,6 +71,7 @@ export class ContainerStatus {
     containerName: string = '';
     containerId: string = '';
     lifeCycle: string = '';
+    state: string = '';
     deployment: string = '';
     projectId: string = '';
     env: string = '';
@@ -80,6 +81,8 @@ export class ContainerStatus {
     created: string = '';
     image: string = '';
     ports: [] = [];
+    commands: string [] = [];
+    inTransit: boolean = false;
 
     public constructor(init?: Partial<ContainerStatus>) {
         Object.assign(this, init);
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
index 4fbd9da7..28f6cf2b 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
@@ -14,43 +14,68 @@ import {ProjectEventBus} from "./ProjectEventBus";
 
 export class ProjectService {
 
-    public static startRunner(project: Project, verbose: boolean) {
-        useDevModeStore.setState({status: "starting"})
-        KaravanApi.runProject(project, verbose, res => {
+    public static startDevModeContainer(project: Project, verbose: boolean) {
+        useDevModeStore.setState({status: "wip"})
+        KaravanApi.startDevModeContainer(project, verbose, res => {
             if (res.status === 200 || res.status === 201) {
                 ProjectEventBus.sendLog("set", '');
                 useLogStore.setState({showLog: true, type: 'container', podName: res.data})
+                useDevModeStore.setState({status: "none"})
             } else {
                 // Todo notification
             }
         });
     }
 
-    public static reloadRunner(project: Project) {
-        useDevModeStore.setState({status: "reloading"})
-        KaravanApi.reloadDevMode(project.projectId, res => {
+    public static reloadDevModeCode(project: Project) {
+        useDevModeStore.setState({status: "wip"})
+        KaravanApi.reloadDevModeCode(project.projectId, res => {
             if (res.status === 200 || res.status === 201) {
                 // setIsReloadingPod(false);
             } else {
                 // Todo notification
                 // setIsReloadingPod(false);
             }
+            useDevModeStore.setState({status: "none"})
         });
     }
 
-    public static deleteRunner(project: Project) {
-        useDevModeStore.setState({status: "deleting"})
+    public static stopDevModeContainer(project: Project) {
+        useDevModeStore.setState({status: "wip"})
+        KaravanApi.manageContainer("dev", project.projectId, 'stop', res => {
+            if (res.status === 200) {
+                useLogStore.setState({showLog: false, type: 'container', isRunning: false})
+            } else {
+                ProjectEventBus.sendAlert(new ToastMessage("Error stopping DevMode container", res.statusText, 'warning'))
+            }
+        });
+    }
+
+    public static pauseDevModeContainer(project: Project) {
+        useDevModeStore.setState({status: "wip"})
+        KaravanApi.manageContainer("dev", project.projectId, 'pause', res => {
+            if (res.status === 200) {
+                useLogStore.setState({showLog: false, type: 'container', isRunning: false})
+            } else {
+                ProjectEventBus.sendAlert(new ToastMessage("Error stopping DevMode container", res.statusText, 'warning'))
+            }
+        });
+    }
+
+    public static deleteDevModeContainer(project: Project) {
+        useDevModeStore.setState({status: "wip"})
         ProjectEventBus.sendLog("set", '');
-        KaravanApi.deleteRunner(project.projectId, false, res => {
+        KaravanApi.deleteDevModeContainer(project.projectId, false, res => {
             if (res.status === 202) {
                 useLogStore.setState({showLog: false, type: 'container', isRunning: false})
             } else {
                 ProjectEventBus.sendAlert(new ToastMessage("Error delete runner", res.statusText, 'warning'))
             }
+            useDevModeStore.setState({status: "none"})
         });
     }
 
-    public static getDevModePodStatus(project: Project) {
+    public static getDevModeStatus(project: Project) {
         const projectId = project.projectId;
         KaravanApi.getDevModePodStatus(projectId, res => {
             if (res.status === 200) {
@@ -59,8 +84,8 @@ export class ProjectService {
                     if (useDevModeStore.getState().podName !== podStatus.containerName){
                         useDevModeStore.setState({podName: podStatus.containerName})
                     }
-                    if (useDevModeStore.getState().status !== "running"){
-                        useDevModeStore.setState({status: "running"})
+                    if (useDevModeStore.getState().status !== "wip"){
+                        useDevModeStore.setState({status: "wip"})
                         useLogStore.setState({isRunning: true})
                     }
                     useProjectStore.setState({containerStatus: res.data});
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts b/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts
index 418d1848..80c99593 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts
@@ -137,14 +137,14 @@ export const useFileStore = create<FileState>((set) => ({
 
 interface DevModeState {
     podName?: string,
-    status: "none" | "starting" | "deleting"| "reloading" | "running",
-    setStatus: (status: "none" | "starting" | "deleting"| "reloading" | "running") => void,
+    status: "none" | "wip",
+    setStatus: (status: "none" | "wip") => void,
 }
 
 export const useDevModeStore = create<DevModeState>((set) => ({
     podName: undefined,
     status: "none",
-    setStatus: (status: "none" | "starting" | "deleting"| "reloading" | "running") =>  {
+    setStatus: (status: "none" | "wip") =>  {
         set((state: DevModeState) => ({
             status: status,
         }));
diff --git a/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx b/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx
index 29241c44..660c2f0d 100644
--- a/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx
@@ -2,13 +2,16 @@ import React, {useState} from 'react';
 import {
     Button,
     Tooltip,
-    Flex, FlexItem, Label, Badge
+    Flex, FlexItem, Label, Badge, Spinner
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {ExpandableRowContent, Tbody, Td, Tr} from "@patternfly/react-table";
 import StopIcon from "@patternfly/react-icons/dist/js/icons/stop-icon";
 import PlayIcon from "@patternfly/react-icons/dist/esm/icons/play-icon";
 import {ContainerStatus} from "../api/ProjectModels";
+import PauseIcon from "@patternfly/react-icons/dist/esm/icons/pause-icon";
+import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
+import {KaravanApi} from "../api/KaravanApi";
 
 interface Props {
     index: number
@@ -18,14 +21,13 @@ interface Props {
 export const ContainerTableRow = (props: Props) => {
 
     const [isExpanded, setIsExpanded] = useState<boolean>(false);
-    const [running, setRunning] = useState<boolean>(false);
 
     const container = props.container;
-    const env = container.env;
+    const commands = container.commands;
     const ports = container.ports;
-    const icon = running ? <StopIcon/> : <PlayIcon/>;
-    const tooltip = running ? "Stop container" : "Start container";
-    const color = container.lifeCycle === 'ready' ? "green" : "grey";
+    const isRunning = container.state === 'running';
+    const inTransit = container.inTransit;
+    const color = container.state === 'running' ? "green" : "grey";
     return (
         <Tbody isExpanded={isExpanded}>
             <Tr key={container.containerName}>
@@ -48,23 +50,52 @@ export const ContainerTableRow = (props: Props) => {
                 </Td>
                 <Td>{container.image}</Td>
                 <Td>
-                    <Label color={color}>{container.cpuInfo}</Label>
+                    {isRunning && container.cpuInfo && <Label color={color}>{container.cpuInfo}</Label>}
                 </Td>
                 <Td>
-                    <Label color={color}>{container.memoryInfo}</Label>
+                    {isRunning && container.memoryInfo && <Label color={color}>{container.memoryInfo}</Label>}
+                </Td>
+                <Td>
+                    {!inTransit && <Label color={color}>{container.state}</Label>}
+                    {inTransit && <Spinner isSVG size="md" aria-label="spinner"/>}
                 </Td>
-                {/*<Td>{container.environment}</Td>*/}
                 <Td className="project-action-buttons">
-                    <Flex direction={{default: "row"}} justifyContent={{default: "justifyContentFlexEnd"}}
-                          spaceItems={{default: 'spaceItemsNone'}}>
-                        <FlexItem>
-                            <Tooltip content={tooltip} position={"bottom"}>
-                                <Button variant={"plain"} icon={icon} onClick={e => {
-                                    // setProject(project, "delete");
-                                }}></Button>
-                            </Tooltip>
-                        </FlexItem>
-                    </Flex>
+                    {container.type !== 'internal' &&
+                        <Flex direction={{default: "row"}} justifyContent={{default: "justifyContentFlexEnd"}}
+                              spaceItems={{default: 'spaceItemsNone'}}>
+                            <FlexItem>
+                                <Tooltip content={"Start container"} position={"bottom"}>
+                                    <Button variant={"plain"} icon={<PlayIcon/>} isDisabled={!commands.includes('run') || inTransit}
+                                            onClick={e => {
+                                                KaravanApi.manageContainer(container.env, container.containerName, 'run', res => {});
+                                            }}></Button>
+                                </Tooltip>
+                            </FlexItem>
+                            <FlexItem>
+                                <Tooltip content={"Pause container"} position={"bottom"}>
+                                    <Button variant={"plain"} icon={<PauseIcon/>} isDisabled={!commands.includes('pause') || inTransit}
+                                            onClick={e => {
+                                                KaravanApi.manageContainer(container.env, container.containerName, 'pause', res => {});
+                                            }}></Button>
+                                </Tooltip>
+                            </FlexItem>
+                            <FlexItem>
+                                <Tooltip content={"Stop container"} position={"bottom"}>
+                                    <Button variant={"plain"} icon={<StopIcon/>} isDisabled={!commands.includes('stop') || inTransit}
+                                            onClick={e => {
+                                                KaravanApi.manageContainer(container.env, container.containerName, 'stop', res => {});
+                                            }}></Button>
+                                </Tooltip>
+                            </FlexItem>
+                            <FlexItem>
+                                <Tooltip content={"Delete container"} position={"bottom"}>
+                                    <Button variant={"plain"} icon={<DeleteIcon/>} isDisabled={!commands.includes('delete') || inTransit}
+                                            onClick={e => {
+                                                KaravanApi.deleteContainer(container.env, container.containerName, res => {});
+                                            }}></Button>
+                                </Tooltip>
+                            </FlexItem>
+                        </Flex>}
                 </Td>
             </Tr>
             {<Tr isExpanded={isExpanded}>
@@ -84,7 +115,7 @@ export const ContainerTableRow = (props: Props) => {
                 <Td colSpan={2}>
                     <ExpandableRowContent>
                         <Flex direction={{default: "row"}} cellPadding={"0px"}>
-                            {ports.map(port => <FlexItem>{port}</FlexItem>)}
+                            {ports.map((port, index) => <FlexItem key={index}>{port}</FlexItem>)}
                         </Flex>
                     </ExpandableRowContent>
                 </Td>
diff --git a/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx b/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx
index 34d44ff7..9d1f6607 100644
--- a/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx
@@ -40,7 +40,7 @@ export const ContainersPage = () => {
     useEffect(() => {
         const interval = setInterval(() => {
             onGetProjects()
-        }, 2000);
+        }, 700);
         return () => {
             clearInterval(interval)
         };
@@ -111,21 +111,23 @@ export const ContainersPage = () => {
 
     function getEmptyState() {
         return (
-            <Tr>
-                <Td colSpan={8}>
-                    <Bullseye>
-                        {loading && <Spinner className="progress-stepper" isSVG diameter="80px" aria-label="Loading..."/>}
-                        {!loading &&
-                            <EmptyState variant={EmptyStateVariant.small}>
-                                <EmptyStateIcon icon={SearchIcon}/>
-                                <Title headingLevel="h2" size="lg">
-                                    No results found
-                                </Title>
-                            </EmptyState>
-                        }
-                    </Bullseye>
-                </Td>
-            </Tr>
+            <Tbody>
+                <Tr>
+                    <Td colSpan={8}>
+                        <Bullseye>
+                            {loading && <Spinner className="progress-stepper" isSVG diameter="80px" aria-label="Loading..."/>}
+                            {!loading &&
+                                <EmptyState variant={EmptyStateVariant.small}>
+                                    <EmptyStateIcon icon={SearchIcon}/>
+                                    <Title headingLevel="h2" size="lg">
+                                        No results found
+                                    </Title>
+                                </EmptyState>
+                            }
+                        </Bullseye>
+                    </Td>
+                </Tr>
+            </Tbody>
         )
     }
 
@@ -145,6 +147,7 @@ export const ContainersPage = () => {
                             <Th key='image'>Image</Th>
                             <Th key='cpuInfo'>CPU</Th>
                             <Th key='memoryInfo'>Memory</Th>
+                            <Th key='state'>State</Th>
                             <Th key='action'></Th>
                         </Tr>
                     </Thead>
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/DevModeToolbar.tsx b/karavan-web/karavan-app/src/main/webui/src/project/DevModeToolbar.tsx
index 4f202b92..510372e5 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/DevModeToolbar.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/DevModeToolbar.tsx
@@ -3,12 +3,13 @@ import {Button, Flex, FlexItem, Label, Switch, Tooltip, TooltipPosition} from '@
 import '../designer/karavan.css';
 import RocketIcon from "@patternfly/react-icons/dist/esm/icons/rocket-icon";
 import ReloadIcon from "@patternfly/react-icons/dist/esm/icons/bolt-icon";
-import DeleteIcon from "@patternfly/react-icons/dist/esm/icons/times-circle-icon";
+import DeleteIcon from "@patternfly/react-icons/dist/esm/icons/trash-icon";
 import {useDevModeStore, useLogStore, useProjectStore} from "../api/ProjectStore";
 import {ProjectService} from "../api/ProjectService";
 import {shallow} from "zustand/shallow";
 import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
 import DownIcon from "@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
+import StopIcon from "@patternfly/react-icons/dist/js/icons/stop-icon";
 
 
 interface Props {
@@ -18,29 +19,18 @@ interface Props {
 export const DevModeToolbar = (props: Props) => {
 
     const [status] = useDevModeStore((state) => [state.status], shallow)
-    const [project,containerStatus ] = useProjectStore((state) => [state.project, state.containerStatus], shallow)
+    const [project, containerStatus ] = useProjectStore((state) => [state.project, state.containerStatus], shallow)
     const [verbose, setVerbose] = useState(false);
 
-
-    function getColor() {
-        return getRunning() ? "green" : "grey";
-    }
-
-    function getRunning(): boolean {
-        return containerStatus.lifeCycle === 'ready';
-    }
-
-    function getIcon() {
-        return (getRunning() ? <UpIcon/> : <DownIcon/>)
-    }
-
-    const isRunning = status === "running";
-    const isStartingPod = status === "starting";
-    const isReloadingPod = status === "reloading";
-    const isDeletingPod = status === "deleting";
+    const commands = containerStatus.commands;
+    const ports = containerStatus.ports;
+    const isRunning = containerStatus.state === 'running';
+    const inTransit = containerStatus.inTransit;
+    const color = containerStatus.state === 'running' ? "green" : "grey";
+    const icon = isRunning ? <UpIcon/> : <DownIcon/>;
     return (<Flex className="toolbar" direction={{default: "row"}} alignItems={{default: "alignItemsCenter"}}>
-        {isRunning && <FlexItem>
-            <Label icon={getIcon()} color={getColor()}>
+        {<FlexItem>
+            <Label icon={icon} color={color}>
                 <Tooltip content={"Show log"} position={TooltipPosition.bottom}>
                     <Button variant="link"
                             onClick={e =>
@@ -50,19 +40,7 @@ export const DevModeToolbar = (props: Props) => {
                 </Tooltip>
             </Label>
         </FlexItem>}
-        {(isRunning || isDeletingPod) && !isReloadingPod && props.reloadOnly !== true && <FlexItem>
-            <Tooltip content="Stop devmode" position={TooltipPosition.bottom}>
-                <Button isLoading={isDeletingPod ? true : undefined}
-                        isSmall
-                        variant={"secondary"}
-                        className="project-button"
-                        icon={!isRunning ? <DeleteIcon/> : <div></div>}
-                        onClick={() => ProjectService.deleteRunner(project)}>
-                    {isDeletingPod ? "..." : "Stop"}
-                </Button>
-            </Tooltip>
-        </FlexItem>}
-        {!isRunning && !isReloadingPod && !isDeletingPod && props.reloadOnly !== true && <FlexItem>
+        <FlexItem>
             <Tooltip content="Verbose" position={TooltipPosition.bottom}>
                 <Switch aria-label="verbose"
                         id="verbose"
@@ -70,28 +48,50 @@ export const DevModeToolbar = (props: Props) => {
                         onChange={checked => setVerbose(checked)}
                 />
             </Tooltip>
-        </FlexItem>}
-        {!isRunning && !isReloadingPod && props.reloadOnly !== true && <FlexItem>
+        </FlexItem>
+        {!isRunning && <FlexItem>
             <Tooltip content="Run in developer mode" position={TooltipPosition.bottom}>
-                <Button isLoading={isStartingPod ? true : undefined}
+                <Button isLoading={status === 'wip'}
                         isSmall
+                        isDisabled={(!(commands.length === 0) && !commands.includes('run')) || inTransit}
                         variant={"primary"}
-                        className="project-button"
-                        icon={!isStartingPod ? <RocketIcon/> : <div></div>}
-                        onClick={() => ProjectService.startRunner(project, verbose)}>
-                    {isStartingPod ? "..." : "Run"}
+                        icon={<RocketIcon/>}
+                        onClick={() => ProjectService.startDevModeContainer(project, verbose)}>
+                    {"Run"}
                 </Button>
             </Tooltip>
         </FlexItem>}
-        {(isRunning || isReloadingPod) && <FlexItem>
+        {isRunning && <FlexItem>
             <Tooltip content="Reload" position={TooltipPosition.bottom}>
-                <Button isLoading={isReloadingPod ? true : undefined}
+                <Button isLoading={status === 'wip'}
                         isSmall
+                        isDisabled={inTransit}
                         variant={"primary"}
                         className="project-button"
-                        icon={!isReloadingPod ? <ReloadIcon/> : <div></div>}
-                        onClick={() => ProjectService.reloadRunner(project)}>
-                    {isReloadingPod ? "..." : "Reload"}
+                        icon={<ReloadIcon/>}
+                        onClick={() => ProjectService.reloadDevModeCode(project)}>Reload
+                </Button>
+            </Tooltip>
+        </FlexItem>}
+        {<FlexItem>
+            <Tooltip content="Stop container" position={TooltipPosition.bottom}>
+                <Button isLoading={status === 'wip'}
+                        isSmall
+                        isDisabled={!commands.includes('stop') || inTransit}
+                        variant={"control"}
+                        icon={<StopIcon/>}
+                        onClick={() => ProjectService.stopDevModeContainer(project)}>
+                </Button>
+            </Tooltip>
+        </FlexItem>}
+        {<FlexItem>
+            <Tooltip content="Delete container" position={TooltipPosition.bottom}>
+                <Button isLoading={status === 'wip'}
+                        isSmall
+                        isDisabled={!commands.includes('delete') || inTransit}
+                        variant={"control"}
+                        icon={<DeleteIcon/>}
+                        onClick={() => ProjectService.deleteDevModeContainer(project)}>
                 </Button>
             </Tooltip>
         </FlexItem>}
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 90b08ed3..cab863b6 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
@@ -26,7 +26,7 @@ export const ProjectPage = () => {
     useEffect(() => {
         // TODO: make status request only when started or just opened
         const interval = setInterval(() => {
-            ProjectService.getDevModePodStatus(project);
+            ProjectService.getDevModeStatus(project);
         }, 1000);
         return () => {
             clearInterval(interval)
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/dashboard/InfoContainer.tsx b/karavan-web/karavan-app/src/main/webui/src/project/dashboard/InfoContainer.tsx
index 03bc5f9e..29c7247d 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/dashboard/InfoContainer.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/dashboard/InfoContainer.tsx
@@ -35,7 +35,7 @@ export const InfoContainer = (props: Props) => {
     }
 
     function getRunning(): boolean {
-        return props.containerStatus.lifeCycle === 'ready';
+        return props.containerStatus.state === 'running';
     }
 
     const containerStatus = props.containerStatus;
@@ -50,7 +50,7 @@ export const InfoContainer = (props: Props) => {
             <DescriptionListGroup>
                 <DescriptionListTerm>Status</DescriptionListTerm>
                 <DescriptionListDescription>
-                    {getPodInfoLabel(containerStatus.lifeCycle)}
+                    {getPodInfoLabel(containerStatus.state)}
                 </DescriptionListDescription>
             </DescriptionListGroup>
             <DescriptionListGroup>
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx b/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
index 491a2b80..f32d070e 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
@@ -87,7 +87,7 @@ export class ProjectStatus extends React.Component<Props, State> {
                 });
                 break;
             case "pod":
-                KaravanApi.deletePod(environment, name, (res: any) => {
+                KaravanApi.deleteContainer(environment, name, (res: any) => {
                     // if (Array.isArray(res) && Array.from(res).length > 0)
                     // this.onRefresh();
                 });
diff --git a/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx b/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx
index 621f58a5..f6affafe 100644
--- a/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx
@@ -47,7 +47,7 @@ export const ServicesTableRow = (props: Props) => {
                 <Td>{service.image}</Td>
                 <Td>
                     <Flex direction={{default: "row"}}>
-                        {service.ports.map(port => <FlexItem>{port}</FlexItem>)}
+                        {service.ports.map(port => <FlexItem key={port}>{port}</FlexItem>)}
                     </Flex>
                 </Td>
                 {/*<Td>{service.environment}</Td>*/}
@@ -70,7 +70,7 @@ export const ServicesTableRow = (props: Props) => {
                 <Td colSpan={2}>
                     <ExpandableRowContent>
                         <Flex direction={{default: "column"}} cellPadding={"0px"}>
-                        {keys.map(key => <FlexItem>{key + ": " + env[key]}</FlexItem>)}
+                        {keys.map(key => <FlexItem key={key}>{key + ": " + env[key]}</FlexItem>)}
                         </Flex>
                     </ExpandableRowContent>
                 </Td>
diff --git a/karavan-web/karavan-headless/src/main/java/org/apache/camel/karavan/headless/CamelService.java b/karavan-web/karavan-headless/src/main/java/org/apache/camel/karavan/headless/CamelService.java
index 41aa5838..089ade28 100644
--- a/karavan-web/karavan-headless/src/main/java/org/apache/camel/karavan/headless/CamelService.java
+++ b/karavan-web/karavan-headless/src/main/java/org/apache/camel/karavan/headless/CamelService.java
@@ -66,10 +66,10 @@ public class CamelService {
     public void reloadProjectCode(String projectId) {
         LOGGER.info("Reload project code " + projectId);
         try {
-            ContainerStatus containerStatus = infinispanService.getDevModeContainerStatus(projectId, environment);
             infinispanService.getProjectFiles(projectId).forEach(projectFile ->
                     putRequest(projectId, projectFile.getName(), projectFile.getCode(), 1000));
             reloadRequest(projectId);
+            ContainerStatus containerStatus = infinispanService.getDevModeContainerStatus(projectId, environment);
             containerStatus.setCodeLoaded(true);
             infinispanService.saveContainerStatus(containerStatus);
         } catch (Exception ex) {
@@ -106,7 +106,7 @@ public class CamelService {
     public void collectCamelStatuses() {
         if (infinispanService.isReady()) {
             infinispanService.getContainerStatuses(environment).stream()
-                    .filter(status -> status.getType().equals(ContainerStatus.CType.devmode) || status.getType().equals(ContainerStatus.CType.project))
+                    .filter(status -> status.getType().equals(ContainerStatus.ContainerType.devmode) || status.getType().equals(ContainerStatus.ContainerType.project))
                     .forEach(status -> {
                         CamelStatusRequest csr = new CamelStatusRequest(status.getProjectId(), status.getContainerName());
                         eventBus.publish(CMD_COLLECT_CAMEL_STATUS, JsonObject.mapFrom(csr));
diff --git a/karavan-web/karavan-headless/src/main/java/org/apache/camel/karavan/headless/HeadlessService.java b/karavan-web/karavan-headless/src/main/java/org/apache/camel/karavan/headless/HeadlessService.java
index e6f8624f..ba2b805b 100644
--- a/karavan-web/karavan-headless/src/main/java/org/apache/camel/karavan/headless/HeadlessService.java
+++ b/karavan-web/karavan-headless/src/main/java/org/apache/camel/karavan/headless/HeadlessService.java
@@ -39,12 +39,11 @@ public class HeadlessService {
     void onStart(@Observes StartupEvent ev) {
         LOGGER.info("Starting Headless Karavan");
         infinispanService.start(true);
-        infinispanService.clearAllStatuses();
     }
 
     @Scheduled(every = "{karavan.camel.status.interval}", concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
     void collectCamelStatuses() {
-        LOGGER.info("Collect info statuses");
+        LOGGER.info("Collect Camel statuses");
         // collect Camel statuses
         camelService.collectCamelStatuses();
     }
diff --git a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
index efd1ee2c..ac905f64 100644
--- a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
+++ b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
@@ -67,6 +67,7 @@ public class InfinispanService {
     private RemoteCache<GroupedKey, PipelineStatus> pipelineStatuses;
     private RemoteCache<GroupedKey, DeploymentStatus> deploymentStatuses;
     private RemoteCache<GroupedKey, ContainerStatus> containerStatuses;
+    private RemoteCache<GroupedKey, Boolean> transits;
     private RemoteCache<GroupedKey, ServiceStatus> serviceStatuses;
     private RemoteCache<GroupedKey, CamelStatus> camelStatuses;
     private RemoteCache<String, String> commits;
@@ -107,6 +108,7 @@ public class InfinispanService {
         serviceStatuses = getOrCreateCache(ServiceStatus.CACHE, false);
         camelStatuses = getOrCreateCache(CamelStatus.CACHE, false);
         commits = getOrCreateCache("commits", false);
+        transits = getOrCreateCache("transits", false);
         deploymentStatuses = getOrCreateCache(DeploymentStatus.CACHE, false);
         codeReloadCommands = getOrCreateCache("code_reload_commands", true);
 
@@ -250,6 +252,18 @@ public class InfinispanService {
         return new ArrayList<>(serviceStatuses.values());
     }
 
+    public List<Boolean> getTransits() {
+        return new ArrayList<>(transits.values());
+    }
+
+    public Boolean getTransit(String projectId, String env, String containerName) {
+        return transits.get(GroupedKey.create(projectId, env, containerName));
+    }
+
+    public void setTransit(String projectId, String env, String containerName) {
+        transits.put(GroupedKey.create(projectId,env,containerName), true);
+    }
+
     public List<ContainerStatus> getContainerStatuses() {
         return new ArrayList<>(containerStatuses.values());
     }
@@ -262,6 +276,12 @@ public class InfinispanService {
                 .execute().list();
     }
 
+    public void setContainerStatusTransit(String projectId, String env, String containerName) {
+        ContainerStatus cs = getContainerStatus(projectId, env, containerName);
+        cs.setInTransit(true);
+        saveContainerStatus(cs);
+    }
+
     public ContainerStatus getContainerStatus(String projectId, String env, String containerName) {
         return containerStatuses.get(GroupedKey.create(projectId, env, containerName));
     }
@@ -353,14 +373,14 @@ public class InfinispanService {
     public List<ContainerStatus> getLoadedDevModeStatuses() {
         QueryFactory queryFactory = Search.getQueryFactory(containerStatuses);
         return queryFactory.<ContainerStatus>create("FROM karavan.ContainerStatus WHERE type = :type AND codeLoaded = true")
-                .setParameter("type", ContainerStatus.CType.devmode)
+                .setParameter("type", ContainerStatus.ContainerType.devmode)
                 .execute().list();
     }
 
     public List<ContainerStatus> getDevModeStatuses() {
         QueryFactory queryFactory = Search.getQueryFactory(containerStatuses);
         return queryFactory.<ContainerStatus>create("FROM karavan.ContainerStatus WHERE type = :type")
-                .setParameter("type", ContainerStatus.CType.devmode)
+                .setParameter("type", ContainerStatus.ContainerType.devmode)
                 .execute().list();
     }
 
diff --git a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
index de1fea4e..ef7ca779 100644
--- a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
+++ b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
@@ -9,7 +9,16 @@ import java.util.List;
 
 public class ContainerStatus {
 
-    public enum CType {
+    public enum State {
+        created,
+        running,
+        restarting,
+        paused,
+        exited,
+        dead
+    }
+
+    public enum ContainerType {
         @ProtoEnumValue(number = 0, name = "internal") internal,
         @ProtoEnumValue(number = 1, name = "devmode") devmode,
         @ProtoEnumValue(number = 2, name = "devservice") devservice,
@@ -17,10 +26,11 @@ public class ContainerStatus {
         @ProtoEnumValue(number = 5, name = "unknown") unknown,
     }
 
-    public enum Lifecycle {
-        @ProtoEnumValue(number = 0, name = "init") init,
-        @ProtoEnumValue(number = 1, name = "ready") ready,
-        @ProtoEnumValue(number = 2, name = "deleting") deleting,
+    public enum Command {
+        @ProtoEnumValue(number = 0, name = "run") run,
+        @ProtoEnumValue(number = 1, name = "pause") pause,
+        @ProtoEnumValue(number = 2, name = "stop") stop,
+        @ProtoEnumValue(number = 3, name = "delete") delete,
     }
 
     public static final String CACHE = "container_statuses";
@@ -37,7 +47,7 @@ public class ContainerStatus {
     @ProtoField(number = 6)
     String env;
     @ProtoField(number = 7)
-    CType type;
+    ContainerType type;
     @ProtoField(number = 8)
     String memoryInfo;
     @ProtoField(number = 9)
@@ -45,14 +55,16 @@ public class ContainerStatus {
     @ProtoField(number = 10)
     String created;
     @ProtoField(number = 11)
-    Lifecycle lifeCycle;
+    List<Command> commands;
     @ProtoField(number = 12)
-    Boolean codeLoaded;
+    String state;
     @ProtoField(number = 13)
-    Boolean logging;
+    Boolean codeLoaded;
+    @ProtoField(number = 14)
+    Boolean inTransit = false;
 
     @ProtoFactory
-    public ContainerStatus(String projectId, String containerName, String containerId, String image, List<Integer> ports, String env, CType type, String memoryInfo, String cpuInfo, String created, Lifecycle lifeCycle, Boolean codeLoaded, Boolean logging) {
+    public ContainerStatus(String projectId, String containerName, String containerId, String image, List<Integer> ports, String env, ContainerType type, String memoryInfo, String cpuInfo, String created, List<Command> commands, String state, Boolean codeLoaded, Boolean inTransit) {
         this.projectId = projectId;
         this.containerName = containerName;
         this.containerId = containerId;
@@ -63,14 +75,15 @@ public class ContainerStatus {
         this.memoryInfo = memoryInfo;
         this.cpuInfo = cpuInfo;
         this.created = created;
-        this.lifeCycle = lifeCycle;
+        this.commands = commands;
+        this.state = state;
         this.codeLoaded = codeLoaded;
-        this.logging = logging;
+        this.inTransit = inTransit;
     }
 
-    public ContainerStatus(String containerName, Lifecycle lifeCycle, String projectId, String env, CType type, String memoryInfo, String cpuInfo, String created) {
+    public ContainerStatus(String containerName, List<Command> commands, String projectId, String env, ContainerType type, String memoryInfo, String cpuInfo, String created) {
         this.containerName = containerName;
-        this.lifeCycle = lifeCycle;
+        this.commands = commands;
         this.projectId = projectId;
         this.env = env;
         this.type = type;
@@ -79,9 +92,9 @@ public class ContainerStatus {
         this.created = created;
     }
 
-    public ContainerStatus(String containerName, Lifecycle lifeCycle, String projectId, String env, CType type, String created) {
+    public ContainerStatus(String containerName, List<Command> commands, String projectId, String env, ContainerType type, String created) {
         this.containerName = containerName;
-        this.lifeCycle = lifeCycle;
+        this.commands = commands;
         this.projectId = projectId;
         this.env = env;
         this.created = created;
@@ -89,17 +102,18 @@ public class ContainerStatus {
     }
 
     public static ContainerStatus createDevMode(String projectId, String env) {
-        return new ContainerStatus(projectId, projectId, null, null, null, env, CType.devmode, null, null, null,  Lifecycle.init, false, false);
+        return new ContainerStatus(projectId, projectId, null, null, null, env, ContainerType.devmode, null, null, null, List.of(Command.run), null, false, false);
     }
 
-    public static ContainerStatus createWithId(String name, String env, String containerId, String image, List<Integer> ports, CType type, Lifecycle lifeCycle, String created) {
+    public static ContainerStatus createWithId(String name, String env, String containerId, String image, List<Integer> ports, ContainerType type, List<Command> commands, String status, String created) {
         return new ContainerStatus(name, name, containerId, image, ports, env, type,
-                null, null, created,  lifeCycle, false, false);
+                null, null, created,  commands, status, false, false);
     }
 
     public ContainerStatus() {
     }
 
+
     public String getProjectId() {
         return projectId;
     }
@@ -148,11 +162,11 @@ public class ContainerStatus {
         this.env = env;
     }
 
-    public CType getType() {
+    public ContainerType getType() {
         return type;
     }
 
-    public void setType(CType type) {
+    public void setType(ContainerType type) {
         this.type = type;
     }
 
@@ -180,12 +194,20 @@ public class ContainerStatus {
         this.created = created;
     }
 
-    public Lifecycle getLifeCycle() {
-        return lifeCycle;
+    public List<Command> getCommands() {
+        return commands;
     }
 
-    public void setLifeCycle(Lifecycle lifeCycle) {
-        this.lifeCycle = lifeCycle;
+    public void setCommands(List<Command> commands) {
+        this.commands = commands;
+    }
+
+    public String getState() {
+        return state;
+    }
+
+    public void setState(String state) {
+        this.state = state;
     }
 
     public Boolean getCodeLoaded() {
@@ -196,11 +218,31 @@ public class ContainerStatus {
         this.codeLoaded = codeLoaded;
     }
 
-    public Boolean getLogging() {
-        return logging;
-    }
-
-    public void setLogging(Boolean logging) {
-        this.logging = logging;
+    public Boolean getInTransit() {
+        return inTransit;
+    }
+
+    public void setInTransit(Boolean inTransit) {
+        this.inTransit = inTransit;
+    }
+
+    @Override
+    public String toString() {
+        return "ContainerStatus{" +
+                "projectId='" + projectId + '\'' +
+                ", containerName='" + containerName + '\'' +
+                ", containerId='" + containerId + '\'' +
+                ", image='" + image + '\'' +
+                ", ports=" + ports +
+                ", env='" + env + '\'' +
+                ", type=" + type +
+                ", memoryInfo='" + memoryInfo + '\'' +
+                ", cpuInfo='" + cpuInfo + '\'' +
+                ", created='" + created + '\'' +
+                ", commands=" + commands +
+                ", state='" + state + '\'' +
+                ", codeLoaded=" + codeLoaded +
+                ", logging=" + inTransit +
+                '}';
     }
 }
diff --git a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/KaravanSchema.java b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/KaravanSchema.java
index c20115b3..29731935 100644
--- a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/KaravanSchema.java
+++ b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/model/KaravanSchema.java
@@ -14,8 +14,8 @@ import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
                 CamelStatus.Name.class,
                 DeploymentStatus.class,
                 ContainerStatus.class,
-                ContainerStatus.CType.class,
-                ContainerStatus.Lifecycle.class,
+                ContainerStatus.ContainerType.class,
+                ContainerStatus.Command.class,
                 ServiceStatus.class
         },
         schemaFileName = "karavan.proto",


[camel-karavan] 02/02: DevMode improvements #817

Posted by ma...@apache.org.
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 c56e125bbe8c959ae197a815128c57d8fc4d5802
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Tue Jul 25 20:40:13 2023 -0400

    DevMode improvements #817
---
 .../apache/camel/karavan/api/DevModeResource.java  | 15 +++++---
 .../camel/karavan/docker/DockerEventListener.java  |  5 ++-
 .../apache/camel/karavan/docker/DockerService.java | 42 +++++++++++++---------
 .../karavan/kubernetes/KubernetesService.java      | 24 +++++++------
 .../camel/karavan/kubernetes/PodEventHandler.java  |  9 ++---
 .../apache/camel/karavan/service/AuthService.java  |  1 -
 .../apache/camel/karavan/service/EventService.java |  4 +--
 .../apache/camel/karavan/shared/Configuration.java | 16 +++++++++
 .../AuthService.java => shared/Constants.java}     | 23 +++---------
 .../org/apache/camel/karavan/shared/EventType.java | 16 +++++++++
 .../src/main/webui/src/api/ProjectService.ts       | 24 ++++++-------
 .../webui/src/containers/ContainerTableRow.tsx     |  2 +-
 .../src/main/webui/src/project/DevModeToolbar.tsx  | 21 ++++++-----
 .../src/main/webui/src/project/ProjectToolbar.tsx  |  1 -
 .../webui/src/project/pipeline/ProjectStatus.tsx   |  3 --
 15 files changed, 117 insertions(+), 89 deletions(-)

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 6379b058..d6d647c9 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
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.karavan.api;
 
+import io.vertx.core.json.JsonObject;
+import io.vertx.mutiny.core.eventbus.EventBus;
 import org.apache.camel.karavan.docker.DockerService;
 import org.apache.camel.karavan.infinispan.InfinispanService;
 import org.apache.camel.karavan.infinispan.model.CamelStatus;
@@ -24,6 +26,7 @@ import org.apache.camel.karavan.infinispan.model.Project;
 import org.apache.camel.karavan.kubernetes.KubernetesService;
 import org.apache.camel.karavan.service.CamelService;
 import org.apache.camel.karavan.shared.ConfigService;
+import org.apache.camel.karavan.shared.EventType;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 
 import javax.inject.Inject;
@@ -31,7 +34,6 @@ import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import java.util.Objects;
-import java.util.Optional;
 
 @Path("/api/devmode")
 public class DevModeResource {
@@ -51,6 +53,9 @@ public class DevModeResource {
     @Inject
     DockerService dockerService;
 
+    @Inject
+    EventBus eventBus;
+
     @POST
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
@@ -64,12 +69,12 @@ public class DevModeResource {
 
         if (!Objects.equals(status.getState(), ContainerStatus.State.running.name())){
             status.setInTransit(true);
-            infinispanService.saveContainerStatus(status);
+            eventBus.send(EventType.CONTAINER_STATUS, JsonObject.mapFrom(status));
 
             if (ConfigService.inKubernetes()) {
-                kubernetesService.runDevModeContainer(project, "");
+                kubernetesService.runDevModeContainer(project, jBangOptions);
             } else {
-                dockerService.runDevmodeContainer(project, "");
+                dockerService.runDevmodeContainer(project, jBangOptions);
             }
             return Response.ok(containerName).build();
         }
@@ -102,7 +107,7 @@ public class DevModeResource {
     public Response deleteDevMode(@PathParam("projectId") String projectId, @PathParam("deletePVC") boolean deletePVC) {
         infinispanService.setContainerStatusTransit(projectId, environment, projectId);
         if (ConfigService.inKubernetes()) {
-            kubernetesService.deleteRunner(projectId, deletePVC);
+            kubernetesService.deleteDevModePod(projectId, deletePVC);
         } else {
             dockerService.deleteContainer(projectId);
         }
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventListener.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventListener.java
index 86c40428..34a2905f 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventListener.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventListener.java
@@ -6,7 +6,6 @@ import com.github.dockerjava.api.model.ContainerPort;
 import com.github.dockerjava.api.model.Event;
 import com.github.dockerjava.api.model.EventType;
 import io.vertx.core.eventbus.EventBus;
-import io.vertx.core.json.JsonObject;
 import org.apache.camel.karavan.infinispan.InfinispanService;
 import org.apache.camel.karavan.infinispan.model.ContainerStatus;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
@@ -20,8 +19,8 @@ import java.time.Instant;
 import java.util.*;
 import java.util.stream.Collectors;
 
-import static org.apache.camel.karavan.docker.DockerService.LABEL_PROJECT_ID;
-import static org.apache.camel.karavan.docker.DockerService.LABEL_TYPE;
+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.EventType.DEVMODE_CONTAINER_READY;
 import static org.apache.camel.karavan.shared.EventType.INFINISPAN_STARTED;
 
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
index 84178d09..b7a6fa5e 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
@@ -1,3 +1,19 @@
+/*
+ * 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.docker;
 
 import com.github.dockerjava.api.DockerClient;
@@ -29,6 +45,7 @@ import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
+import static org.apache.camel.karavan.shared.Constants.*;
 import static org.apache.camel.karavan.shared.EventType.*;
 
 @ApplicationScoped
@@ -36,12 +53,10 @@ public class DockerService {
 
     private static final Logger LOGGER = Logger.getLogger(DockerService.class.getName());
 
-    public static final String INFINISPAN_CONTAINER_NAME = "infinispan";
-    public static final String KARAVAN_CONTAINER_NAME = "karavan-headless";
+    protected static final String INFINISPAN_CONTAINER_NAME = "infinispan";
+    protected static final String KARAVAN_CONTAINER_NAME = "karavan-headless";
 
-    public static final String NETWORK_NAME = "karavan";
-    public static final String LABEL_TYPE = "org.apache.camel.karavan.type";
-    public static final String LABEL_PROJECT_ID = "org.apache.camel.karavan.projectId";
+    protected static final String NETWORK_NAME = "karavan";
     private static final DecimalFormat formatCpu = new DecimalFormat("0.00");
     private static final DecimalFormat formatMiB = new DecimalFormat("0.0");
     private static final DecimalFormat formatGiB = new DecimalFormat("0.00");
@@ -59,8 +74,6 @@ public class DockerService {
 
     @ConfigProperty(name = "infinispan.image")
     String infinispanImage;
-    @ConfigProperty(name = "infinispan.hosts")
-    String infinispanHosts;
     @ConfigProperty(name = "infinispan.port")
     String infinispanPort;
     @ConfigProperty(name = "infinispan.username")
@@ -71,9 +84,6 @@ public class DockerService {
     @Inject
     DockerEventListener dockerEventListener;
 
-    @Inject
-    InfinispanService infinispanService;
-
     @Inject
     EventBus eventBus;
 
@@ -81,13 +91,17 @@ public class DockerService {
 
     public void runDevmodeContainer(Project project, String jBangOptions) throws InterruptedException {
         String projectId = project.getProjectId();
-        LOGGER.infof("DevMode starting for %s", projectId);
+        LOGGER.infof("DevMode starting for %s with JBANG_OPTIONS=%s", projectId, jBangOptions);
 
         HealthCheck healthCheck = new HealthCheck().withTest(List.of("CMD", "curl", "-f", "http://localhost:8080/q/dev/health"))
                 .withInterval(10000000000L).withTimeout(10000000000L).withStartPeriod(10000000000L).withRetries(30);
 
+        List<String> env = jBangOptions !=null && !jBangOptions.trim().isEmpty()
+                ? List.of(ENV_VAR_JBANG_OPTIONS + "=" + jBangOptions)
+                : List.of();
+
         createContainer(projectId, devmodeImage,
-                List.of(), null, false, false, healthCheck,
+                env, null, false, false, healthCheck,
                 Map.of(LABEL_TYPE, ContainerStatus.ContainerType.devmode.name(), LABEL_PROJECT_ID, projectId));
 
         startContainer(projectId);
@@ -298,14 +312,10 @@ public class DockerService {
     }
 
     public void deleteContainer(String name) {
-        long time = System.currentTimeMillis();
         List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
-        System.out.println("Get containers " + (System.currentTimeMillis() - time));
-        time = System.currentTimeMillis();
         if (containers.size() == 1) {
             Container container = containers.get(0);
             getDockerClient().removeContainerCmd(container.getId()).withForce(true).exec();
-            System.out.println("removeContainerCmd " + (System.currentTimeMillis() - time));
         }
     }
 
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 3be6850a..1e83cac7 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
@@ -29,6 +29,7 @@ import io.fabric8.tekton.client.DefaultTektonClient;
 import io.fabric8.tekton.pipeline.v1beta1.*;
 import io.vertx.mutiny.core.eventbus.EventBus;
 import org.apache.camel.karavan.infinispan.InfinispanService;
+import org.apache.camel.karavan.infinispan.model.ContainerStatus;
 import org.apache.camel.karavan.infinispan.model.Project;
 import org.apache.camel.karavan.infinispan.model.ProjectFile;
 import org.apache.camel.karavan.service.CodeService;
@@ -47,6 +48,7 @@ import java.util.*;
 import java.util.stream.Collectors;
 
 import static org.apache.camel.karavan.service.CodeService.APPLICATION_PROPERTIES_FILENAME;
+import static org.apache.camel.karavan.shared.Constants.*;
 
 @Default
 @Readiness
@@ -54,12 +56,12 @@ import static org.apache.camel.karavan.service.CodeService.APPLICATION_PROPERTIE
 public class KubernetesService implements HealthCheck {
 
     private static final Logger LOGGER = Logger.getLogger(KubernetesService.class.getName());
-    public static final int INFORMERS = 4;
+    protected static final int INFORMERS = 4;
     private static final String CAMEL_PREFIX = "camel";
     private static final String KARAVAN_PREFIX = "karavan";
     private static final String JBANG_CACHE_SUFFIX = "jbang-cache";
     private static final String M2_CACHE_SUFFIX = "m2-cache";
-    public static final String PVC_MAVEN_SETTINGS = "maven-settings";
+    protected static final String PVC_MAVEN_SETTINGS = "maven-settings";
 
     @Inject
     EventBus eventBus;
@@ -400,9 +402,9 @@ public class KubernetesService implements HealthCheck {
         createService(name);
     }
 
-    public void deleteRunner(String name, boolean deletePVC) {
+    public void deleteDevModePod(String name, boolean deletePVC) {
         try {
-            LOGGER.info("Delete runner: " + name + " in the namespace: " + getNamespace());
+            LOGGER.info("Delete devmode pod: " + name + " in the namespace: " + getNamespace());
             kubernetesClient().pods().inNamespace(getNamespace()).withName(name).delete();
             kubernetesClient().services().inNamespace(getNamespace()).withName(name).delete();
             if (deletePVC) {
@@ -426,7 +428,7 @@ public class KubernetesService implements HealthCheck {
         Map<String, String> labels = new HashMap<>();
         labels.putAll(getRuntimeLabels());
         labels.putAll(getKaravanRunnerLabels(name));
-        labels.put("karavan/projectId", projectId);
+        labels.put(LABEL_PROJECT_ID, projectId);
 
         ResourceRequirements resources = getResourceRequirements(containerResources);
 
@@ -448,7 +450,7 @@ public class KubernetesService implements HealthCheck {
                 .withPorts(port)
                 .withResources(resources)
                 .withImagePullPolicy("Always")
-                .withEnv(new EnvVarBuilder().withName("JBANG_OPTIONS").withValue(jbangOptions).build())
+                .withEnv(new EnvVarBuilder().withName(ENV_VAR_JBANG_OPTIONS).withValue(jbangOptions).build())
                 .withVolumeMounts(
                         new VolumeMountBuilder().withName(name).withMountPath("/karavan/.jbang/cache").build(),
                         new VolumeMountBuilder().withName("maven-settings").withSubPath("maven-settings.xml")
@@ -473,14 +475,14 @@ public class KubernetesService implements HealthCheck {
                 .build();
     }
 
-    private void createPVC(String runnerName) {
-        PersistentVolumeClaim old = kubernetesClient().persistentVolumeClaims().inNamespace(getNamespace()).withName(runnerName).get();
+    private void createPVC(String podName) {
+        PersistentVolumeClaim old = kubernetesClient().persistentVolumeClaims().inNamespace(getNamespace()).withName(podName).get();
         if (old == null) {
             PersistentVolumeClaim pvc = new PersistentVolumeClaimBuilder()
                     .withNewMetadata()
-                    .withName(runnerName)
+                    .withName(podName)
                     .withNamespace(getNamespace())
-                    .withLabels(getKaravanRunnerLabels(runnerName))
+                    .withLabels(getKaravanRunnerLabels(podName))
                     .endMetadata()
                     .withNewSpec()
                     .withResources(new ResourceRequirementsBuilder().withRequests(Map.of("storage", new Quantity("2Gi"))).build())
@@ -533,7 +535,7 @@ public class KubernetesService implements HealthCheck {
     }
 
     public static Map<String, String> getKaravanRunnerLabels(String name) {
-        return Map.of("karavan/type", "runner",
+        return Map.of(LABEL_TYPE, ContainerStatus.ContainerType.devmode.name(),
                 "app.kubernetes.io/name", name);
     }
 
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 b46af28c..5d9417ac 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
@@ -14,6 +14,7 @@ import org.jboss.logging.Logger;
 import java.util.List;
 
 import static org.apache.camel.karavan.service.CodeService.DEFAULT_CONTAINER_RESOURCES;
+import static org.apache.camel.karavan.shared.Constants.LABEL_PROJECT_ID;
 import static org.apache.camel.karavan.shared.EventType.CONTAINER_STATUS;
 
 public class PodEventHandler implements ResourceEventHandler<Pod> {
@@ -60,7 +61,7 @@ public class PodEventHandler implements ResourceEventHandler<Pod> {
         try {
             LOGGER.info("onDelete " + pod.getMetadata().getName());
             String deployment = pod.getMetadata().getLabels().get("app");
-            String projectId = deployment != null ? deployment : pod.getMetadata().getLabels().get("karavan/projectId");
+            String projectId = deployment != null ? deployment : pod.getMetadata().getLabels().get(LABEL_PROJECT_ID);
             infinispanService.deleteContainerStatus(projectId, kubernetesService.environment, pod.getMetadata().getName());
         } catch (Exception e){
             LOGGER.error(e.getMessage(), e.getCause());
@@ -70,7 +71,7 @@ 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("karavan/projectId");
+        String projectId = deployment != null ? deployment : pod.getMetadata().getLabels().get(LABEL_PROJECT_ID);
         try {
             boolean ready = pod.getStatus().getConditions().stream().anyMatch(c -> c.getType().equals("Ready"));
             String creationTimestamp = pod.getMetadata().getCreationTimestamp();
@@ -89,8 +90,8 @@ public class PodEventHandler implements ResourceEventHandler<Pod> {
                     projectId,
                     kubernetesService.environment,
                     pod.getMetadata().getName().equals(projectId) ? ContainerStatus.ContainerType.devmode : ContainerStatus.ContainerType.project,
-                    requestMemory + " : " + limitMemory,
-                    requestCpu + " : " + limitCpu,
+                    requestMemory + " / " + limitMemory,
+                    requestCpu + " / " + limitCpu,
                     creationTimestamp);
 
             if (ready) {
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/AuthService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/AuthService.java
index bcd8f90f..e3b489fe 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/AuthService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/AuthService.java
@@ -16,7 +16,6 @@
  */
 package org.apache.camel.karavan.service;
 
-import io.vertx.core.Vertx;
 import org.eclipse.microprofile.config.ConfigProvider;
 import org.jboss.logging.Logger;
 
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/EventService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/EventService.java
index f3e011f4..ea858d05 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/EventService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/EventService.java
@@ -78,7 +78,8 @@ public class EventService {
     void receiveCommand(String projectId) {
         LOGGER.info("DEVMODE_CONTAINER_READY " + projectId);
         ContainerStatus status = infinispanService.getContainerStatus(projectId, environment, projectId);
-        if (!status.getCodeLoaded() && status.getContainerId() != null && status.getState().equals(ContainerStatus.State.running.name())) {
+        System.out.println(status);
+        if (status != null && !status.getCodeLoaded() && status.getContainerId() != null && status.getState().equals(ContainerStatus.State.running.name())) {
             if (ConfigService.inKubernetes()) {
                 camelService.reloadProjectCode(projectId);
             } else {
@@ -91,7 +92,6 @@ public class EventService {
     public void saveContainerStatus(JsonObject data) {
         if (infinispanService.isReady()) {
             ContainerStatus newStatus = data.mapTo(ContainerStatus.class);
-            System.out.println(newStatus);
             ContainerStatus oldStatus = infinispanService.getContainerStatus(newStatus.getProjectId(), newStatus.getEnv(), newStatus.getContainerName());
             if (oldStatus == null || Objects.equals(oldStatus.getInTransit(), Boolean.FALSE)) {
                 infinispanService.saveContainerStatus(newStatus);
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Configuration.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Configuration.java
index 2e39bbb0..6ccabdf9 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Configuration.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Configuration.java
@@ -1,3 +1,19 @@
+/*
+ * 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.shared;
 
 import java.util.List;
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/AuthService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java
similarity index 52%
copy from karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/AuthService.java
copy to karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java
index bcd8f90f..07834b3b 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/AuthService.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java
@@ -14,26 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.karavan.service;
+package org.apache.camel.karavan.shared;
 
-import io.vertx.core.Vertx;
-import org.eclipse.microprofile.config.ConfigProvider;
-import org.jboss.logging.Logger;
+public class Constants {
 
-import javax.enterprise.context.ApplicationScoped;
-import java.net.MalformedURLException;
-import java.util.Map;
+    public static final String ENV_VAR_JBANG_OPTIONS = "JBANG_OPTIONS";
 
-@ApplicationScoped
-public class AuthService {
+    public static final String LABEL_TYPE = "org.apache.camel.karavan/type";
+    public static final String LABEL_PROJECT_ID = "org.apache.camel.karavan/projectId";
 
-    private static final Logger LOGGER = Logger.getLogger(AuthService.class.getName());
-
-    public String authType() {
-        return ConfigProvider.getConfig().getValue("karavan.auth", String.class);
-    }
-
-    public Map<String, String> getSsoConfig() throws MalformedURLException {
-        return Map.of("url", ConfigProvider.getConfig().getValue("karavan.frontend.auth-server-url", String.class));
-    }
 }
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/EventType.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/EventType.java
index a1085fb9..3a375e41 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/EventType.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/EventType.java
@@ -1,3 +1,19 @@
+/*
+ * 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.shared;
 
 public class EventType {
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
index 28f6cf2b..8b043d61 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
@@ -17,10 +17,10 @@ export class ProjectService {
     public static startDevModeContainer(project: Project, verbose: boolean) {
         useDevModeStore.setState({status: "wip"})
         KaravanApi.startDevModeContainer(project, verbose, res => {
+            useDevModeStore.setState({status: "none"})
             if (res.status === 200 || res.status === 201) {
                 ProjectEventBus.sendLog("set", '');
                 useLogStore.setState({showLog: true, type: 'container', podName: res.data})
-                useDevModeStore.setState({status: "none"})
             } else {
                 // Todo notification
             }
@@ -30,19 +30,20 @@ export class ProjectService {
     public static reloadDevModeCode(project: Project) {
         useDevModeStore.setState({status: "wip"})
         KaravanApi.reloadDevModeCode(project.projectId, res => {
+            useDevModeStore.setState({status: "none"})
             if (res.status === 200 || res.status === 201) {
                 // setIsReloadingPod(false);
             } else {
                 // Todo notification
                 // setIsReloadingPod(false);
             }
-            useDevModeStore.setState({status: "none"})
         });
     }
 
     public static stopDevModeContainer(project: Project) {
         useDevModeStore.setState({status: "wip"})
         KaravanApi.manageContainer("dev", project.projectId, 'stop', res => {
+            useDevModeStore.setState({status: "none"})
             if (res.status === 200) {
                 useLogStore.setState({showLog: false, type: 'container', isRunning: false})
             } else {
@@ -54,6 +55,7 @@ export class ProjectService {
     public static pauseDevModeContainer(project: Project) {
         useDevModeStore.setState({status: "wip"})
         KaravanApi.manageContainer("dev", project.projectId, 'pause', res => {
+            useDevModeStore.setState({status: "none"})
             if (res.status === 200) {
                 useLogStore.setState({showLog: false, type: 'container', isRunning: false})
             } else {
@@ -66,12 +68,12 @@ export class ProjectService {
         useDevModeStore.setState({status: "wip"})
         ProjectEventBus.sendLog("set", '');
         KaravanApi.deleteDevModeContainer(project.projectId, false, res => {
+            useDevModeStore.setState({status: "none"})
             if (res.status === 202) {
                 useLogStore.setState({showLog: false, type: 'container', isRunning: false})
             } else {
                 ProjectEventBus.sendAlert(new ToastMessage("Error delete runner", res.statusText, 'warning'))
             }
-            useDevModeStore.setState({status: "none"})
         });
     }
 
@@ -80,22 +82,19 @@ export class ProjectService {
         KaravanApi.getDevModePodStatus(projectId, res => {
             if (res.status === 200) {
                 unstable_batchedUpdates(() => {
-                    const podStatus = res.data;
-                    if (useDevModeStore.getState().podName !== podStatus.containerName){
-                        useDevModeStore.setState({podName: podStatus.containerName})
+                    const containerStatus = res.data;
+                    if (useDevModeStore.getState().podName !== containerStatus.containerName){
+                        useDevModeStore.setState({podName: containerStatus.containerName})
                     }
                     if (useDevModeStore.getState().status !== "wip"){
-                        useDevModeStore.setState({status: "wip"})
                         useLogStore.setState({isRunning: true})
                     }
-                    useProjectStore.setState({containerStatus: res.data});
+                    useProjectStore.setState({containerStatus: containerStatus});
                 })
             } else {
                 unstable_batchedUpdates(() => {
-                    if (useDevModeStore.getState().status !== 'none') {
-                        useDevModeStore.setState({status: "none", podName: undefined})
-                        useProjectStore.setState({containerStatus: new ContainerStatus()});
-                    }
+                    useDevModeStore.setState({status: "none", podName: undefined})
+                    useProjectStore.setState({containerStatus: new ContainerStatus({})});
                 })
             }
         });
@@ -178,7 +177,6 @@ export class ProjectService {
 
     public static createProject(project: Project) {
         KaravanApi.postProject(project, res => {
-            console.log(res.status)
             if (res.status === 200 || res.status === 201) {
                 ProjectService.refreshProjectData();
                 // this.props.toast?.call(this, "Success", "Project created", "success");
diff --git a/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx b/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx
index 660c2f0d..13d702d2 100644
--- a/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx
@@ -57,7 +57,7 @@ export const ContainerTableRow = (props: Props) => {
                 </Td>
                 <Td>
                     {!inTransit && <Label color={color}>{container.state}</Label>}
-                    {inTransit && <Spinner isSVG size="md" aria-label="spinner"/>}
+                    {inTransit && <Spinner isSVG size="lg" aria-label="spinner"/>}
                 </Td>
                 <Td className="project-action-buttons">
                     {container.type !== 'internal' &&
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/DevModeToolbar.tsx b/karavan-web/karavan-app/src/main/webui/src/project/DevModeToolbar.tsx
index 510372e5..58dae7ea 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/DevModeToolbar.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/DevModeToolbar.tsx
@@ -1,5 +1,5 @@
 import React, {useState} from 'react';
-import {Button, Flex, FlexItem, Label, Switch, Tooltip, TooltipPosition} from '@patternfly/react-core';
+import {Button, Flex, FlexItem, Label, Spinner, Switch, Tooltip, TooltipPosition} from '@patternfly/react-core';
 import '../designer/karavan.css';
 import RocketIcon from "@patternfly/react-icons/dist/esm/icons/rocket-icon";
 import ReloadIcon from "@patternfly/react-icons/dist/esm/icons/bolt-icon";
@@ -23,13 +23,16 @@ export const DevModeToolbar = (props: Props) => {
     const [verbose, setVerbose] = useState(false);
 
     const commands = containerStatus.commands;
-    const ports = containerStatus.ports;
     const isRunning = containerStatus.state === 'running';
     const inTransit = containerStatus.inTransit;
+    const isLoading= status === 'wip';
     const color = containerStatus.state === 'running' ? "green" : "grey";
     const icon = isRunning ? <UpIcon/> : <DownIcon/>;
     return (<Flex className="toolbar" direction={{default: "row"}} alignItems={{default: "alignItemsCenter"}}>
-        {<FlexItem>
+        <FlexItem>
+            {(inTransit || isLoading) && <Spinner isSVG size="lg" aria-label="spinner"/>}
+        </FlexItem>
+        {containerStatus.containerId && <FlexItem>
             <Label icon={icon} color={color}>
                 <Tooltip content={"Show log"} position={TooltipPosition.bottom}>
                     <Button variant="link"
@@ -51,8 +54,7 @@ export const DevModeToolbar = (props: Props) => {
         </FlexItem>
         {!isRunning && <FlexItem>
             <Tooltip content="Run in developer mode" position={TooltipPosition.bottom}>
-                <Button isLoading={status === 'wip'}
-                        isSmall
+                <Button isSmall
                         isDisabled={(!(commands.length === 0) && !commands.includes('run')) || inTransit}
                         variant={"primary"}
                         icon={<RocketIcon/>}
@@ -63,8 +65,7 @@ export const DevModeToolbar = (props: Props) => {
         </FlexItem>}
         {isRunning && <FlexItem>
             <Tooltip content="Reload" position={TooltipPosition.bottom}>
-                <Button isLoading={status === 'wip'}
-                        isSmall
+                <Button isSmall
                         isDisabled={inTransit}
                         variant={"primary"}
                         className="project-button"
@@ -75,8 +76,7 @@ export const DevModeToolbar = (props: Props) => {
         </FlexItem>}
         {<FlexItem>
             <Tooltip content="Stop container" position={TooltipPosition.bottom}>
-                <Button isLoading={status === 'wip'}
-                        isSmall
+                <Button isSmall
                         isDisabled={!commands.includes('stop') || inTransit}
                         variant={"control"}
                         icon={<StopIcon/>}
@@ -86,8 +86,7 @@ export const DevModeToolbar = (props: Props) => {
         </FlexItem>}
         {<FlexItem>
             <Tooltip content="Delete container" position={TooltipPosition.bottom}>
-                <Button isLoading={status === 'wip'}
-                        isSmall
+                <Button isSmall
                         isDisabled={!commands.includes('delete') || inTransit}
                         variant={"control"}
                         icon={<DeleteIcon/>}
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx b/karavan-web/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
index 2f639228..daed745b 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
@@ -34,7 +34,6 @@ export const ProjectToolbar = (props: Props) => {
         [state.file, state.editAdvancedProperties, state.setEditAdvancedProperties, state.setAddProperty], shallow )
 
     useEffect(() => {
-        console.log("ProjectToolbar useEffect", isPushing, project.lastCommitTimestamp);
     }, [project, file]);
 
     function isFile(): boolean {
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx b/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
index f32d070e..add844b9 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
@@ -65,15 +65,12 @@ export class ProjectStatus extends React.Component<Props, State> {
             });
             KaravanApi.getProjectDeploymentStatus(projectId, env, (status?: DeploymentStatus) => {
                 this.setState({deploymentStatus: status});
-                // console.log(status);
             });
             KaravanApi.getProjectPodStatuses(projectId, env, (statuses: ContainerStatus[]) => {
                 this.setState({podStatuses: statuses});
-                // console.log(status);
             });
             KaravanApi.getProjectCamelStatus(projectId, env, (status: CamelStatus) => {
                 this.setState({camelStatus: status});
-                // console.log(status);
             });
         }
     }