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 f7526d0dd74177bbfcad2d5953ba7cf3ce9d0f6d
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Tue Sep 5 15:10:30 2023 -0400

    Refactor Docker services for #817
---
 karavan-web/karavan-app/pom.xml                    | 13 ---
 .../apache/camel/karavan/api/DevModeResource.java  | 44 ++++------
 .../camel/karavan/api/InfrastructureResource.java  | 13 ++-
 .../apache/camel/karavan/api/ProjectResource.java  | 12 +--
 .../camel/karavan/docker/DockerEventListener.java  |  6 +-
 .../camel/karavan/docker/DockerForGitea.java       |  5 +-
 .../camel/karavan/docker/DockerForInfinispan.java  |  5 +-
 .../camel/karavan/docker/DockerForKaravan.java     | 11 ++-
 .../apache/camel/karavan/docker/DockerService.java | 76 ++++++++---------
 .../camel/karavan/docker/DockerServiceUtils.java   | 61 +-------------
 .../karavan/docker/model/DockerComposeService.java | 16 +++-
 .../apache/camel/karavan/service/CamelService.java | 35 +++++++-
 .../apache/camel/karavan/service/CodeService.java  | 97 +++++++++++++++++++++-
 .../apache/camel/karavan/service/DelayRoute.java   | 20 -----
 .../apache/camel/karavan/service/EventService.java | 59 +++++++------
 .../camel/karavan/service/KaravanService.java      |  2 +-
 .../camel/karavan/service/ProjectService.java      | 67 ++++++++++++++-
 .../org/apache/camel/karavan/shared/EventType.java |  3 +-
 .../main/resources/snippets/project-compose.yaml   |  8 ++
 .../src/main/webui/src/api/KaravanApi.tsx          |  4 +-
 .../src/main/webui/src/api/ProjectService.ts       | 17 ++--
 .../src/main/webui/src/main/MainDataPoller.tsx     |  2 -
 .../src/main/webui/src/project/ProjectPanel.tsx    |  6 +-
 .../src/main/webui/src/project/files/FilesTab.tsx  |  4 +-
 .../karavan/infinispan/InfinispanService.java      |  7 ++
 25 files changed, 348 insertions(+), 245 deletions(-)

diff --git a/karavan-web/karavan-app/pom.xml b/karavan-web/karavan-app/pom.xml
index 96c8e5ff..387faad1 100644
--- a/karavan-web/karavan-app/pom.xml
+++ b/karavan-web/karavan-app/pom.xml
@@ -128,19 +128,6 @@
             <artifactId>camel-openapi-rest-dsl-generator</artifactId>
             <version>${camel.version}</version>
         </dependency>
-        <dependency>
-            <groupId>org.apache.camel.quarkus</groupId>
-            <artifactId>camel-quarkus-core</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.camel.quarkus</groupId>
-            <artifactId>camel-quarkus-vertx</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.camel</groupId>
-            <artifactId>camel-endpointdsl</artifactId>
-            <version>${camel.version}</version>
-        </dependency>
         <dependency>
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-container-image-docker</artifactId>
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 5b10fa9b..3e2c879b 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
@@ -18,7 +18,10 @@ 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.DockerForKaravan;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.*;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
 import org.apache.camel.karavan.docker.DockerService;
 import org.apache.camel.karavan.infinispan.InfinispanService;
 import org.apache.camel.karavan.infinispan.model.CamelStatus;
@@ -26,16 +29,10 @@ import 
org.apache.camel.karavan.infinispan.model.ContainerStatus;
 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.service.ProjectService;
 import org.apache.camel.karavan.shared.ConfigService;
-import org.apache.camel.karavan.shared.EventType;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 
-import jakarta.inject.Inject;
-import jakarta.ws.rs.*;
-import jakarta.ws.rs.core.MediaType;
-import jakarta.ws.rs.core.Response;
-import java.util.Objects;
-
 import static org.apache.camel.karavan.shared.EventType.CONTAINER_STATUS;
 
 @Path("/api/devmode")
@@ -57,7 +54,7 @@ public class DevModeResource {
     DockerService dockerService;
 
     @Inject
-    DockerForKaravan dockerForKaravan;
+    ProjectService projectService;
 
     @Inject
     EventBus eventBus;
@@ -66,32 +63,23 @@ public class DevModeResource {
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
     @Path("/{jBangOptions}")
-    public Response runProjectWithJBangOptions(Project project, 
@PathParam("jBangOptions") String jBangOptions) throws InterruptedException {
-        String containerName = project.getProjectId();
-        ContainerStatus status = 
infinispanService.getDevModeContainerStatus(project.getProjectId(), 
environment);
-        if (status == null) {
-            status = ContainerStatus.createDevMode(project.getProjectId(), 
environment);
-        }
-
-        if (!Objects.equals(status.getState(), 
ContainerStatus.State.running.name())){
-            status.setInTransit(true);
-            eventBus.send(EventType.CONTAINER_STATUS, 
JsonObject.mapFrom(status));
-
-            if (ConfigService.inKubernetes()) {
-                kubernetesService.runDevModeContainer(project, jBangOptions);
+    public Response runProjectWithJBangOptions(Project project, 
@PathParam("jBangOptions") String jBangOptions) {
+        try {
+            String containerName = 
projectService.runProjectWithJBangOptions(project, jBangOptions);
+            if (containerName != null) {
+                return Response.ok(containerName).build();
             } else {
-                
dockerForKaravan.createDevmodeContainer(project.getProjectId(), jBangOptions);
-                dockerService.runContainer(project.getProjectId());
+                return Response.notModified().build();
             }
-            return Response.ok(containerName).build();
+        } catch (Exception e) {
+            return Response.serverError().entity(e).build();
         }
-        return Response.notModified().build();
     }
 
     @POST
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
-    public Response runProject(Project project) throws InterruptedException {
+    public Response runProject(Project project) throws Exception {
         return runProjectWithJBangOptions(project, "");
     }
 
@@ -121,7 +109,7 @@ public class DevModeResource {
         return Response.accepted().build();
     }
 
-    private void setContainerStatusTransit(String name, String type){
+    private void setContainerStatusTransit(String name, String type) {
         ContainerStatus status = infinispanService.getContainerStatus(name, 
environment, name);
         if (status == null) {
             status = ContainerStatus.createByType(name, environment, 
ContainerStatus.ContainerType.valueOf(type));
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 5c5f9fae..8f94786a 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
@@ -29,6 +29,7 @@ import 
org.apache.camel.karavan.infinispan.model.DeploymentStatus;
 import org.apache.camel.karavan.infinispan.model.Project;
 import org.apache.camel.karavan.infinispan.model.ServiceStatus;
 import org.apache.camel.karavan.kubernetes.KubernetesService;
+import org.apache.camel.karavan.service.CodeService;
 import org.apache.camel.karavan.service.ProjectService;
 import org.apache.camel.karavan.shared.ConfigService;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
@@ -66,6 +67,9 @@ public class InfrastructureResource {
     @Inject
     ProjectService projectService;
 
+    @Inject
+    CodeService codeService;
+
     @ConfigProperty(name = "karavan.environment")
     String environment;
 
@@ -186,7 +190,7 @@ public class InfrastructureResource {
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
     @Path("/container/{env}/{type}/{name}")
-    public Response startContainer(@PathParam("env") String env, 
@PathParam("type") String type, @PathParam("name") String name, JsonObject 
command) throws Exception {
+    public Response manageContainer(@PathParam("env") String env, 
@PathParam("type") String type, @PathParam("name") String name, JsonObject 
command) throws Exception {
         if (infinispanService.isReady()) {
             // set container statuses
             setContainerStatusTransit(name, type);
@@ -195,14 +199,15 @@ public class InfrastructureResource {
                 if (command.getString("command").equalsIgnoreCase("run")) {
                     if (Objects.equals(type, 
ContainerStatus.ContainerType.devservice.name())) {
                         String code = projectService.getDevServiceCode();
-                        DockerComposeService dockerComposeService = 
dockerService.convertToDockerComposeService(code, name);
+                        DockerComposeService dockerComposeService = 
codeService.convertToDockerComposeService(code, name);
                         if (dockerComposeService != null) {
                             
dockerForKaravan.createDevserviceContainer(dockerComposeService);
                             
dockerService.runContainer(dockerComposeService.getContainer_name());
                         }
                     } else if (Objects.equals(type, 
ContainerStatus.ContainerType.devmode.name())) {
-                        dockerForKaravan.createDevmodeContainer(name, "");
-                        dockerService.runContainer(name);
+//                        TODO: merge with DevMode service
+//                        dockerForKaravan.createDevmodeContainer(name, "");
+//                        dockerService.runContainer(name);
                     }
                     return Response.ok().build();
                 } else if 
(command.getString("command").equalsIgnoreCase("stop")) {
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java
index cc73eae9..b62ad193 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java
@@ -25,6 +25,8 @@ import org.apache.camel.karavan.service.GitService;
 import jakarta.inject.Inject;
 import jakarta.ws.rs.*;
 import jakarta.ws.rs.core.MediaType;
+import org.apache.camel.karavan.service.ProjectService;
+
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
 import java.util.Comparator;
@@ -43,7 +45,7 @@ public class ProjectResource {
     GitService gitService;
 
     @Inject
-    CodeService codeService;
+    ProjectService projectService;
 
     @GET
     @Produces(MediaType.APPLICATION_JSON)
@@ -69,13 +71,7 @@ public class ProjectResource {
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
     public Project save(Project project) throws Exception {
-        boolean isNew = infinispanService.getProject(project.getProjectId()) 
== null;
-        infinispanService.saveProject(project);
-        if (isNew){
-            ProjectFile appProp = 
codeService.getApplicationProperties(project);
-            infinispanService.saveProjectFile(appProp);
-        }
-        return project;
+        return projectService.save(project);
     }
 
     @DELETE
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 e9a7b746..8ed7b546 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
@@ -50,10 +50,12 @@ public class DockerEventListener implements 
ResultCallback<Event> {
         try {
             if (Objects.equals(event.getType(), EventType.CONTAINER)) {
                 Container container = 
dockerService.getContainer(event.getId());
-                onContainerEvent(event, container);
+                if (container != null) {
+                    onContainerEvent(event, container);
+                }
             }
         } catch (Exception exception) {
-            LOGGER.error(exception.getMessage());
+            exception.printStackTrace();
         }
     }
 
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForGitea.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForGitea.java
index 38397495..da52c520 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForGitea.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForGitea.java
@@ -26,6 +26,7 @@ import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 import org.apache.camel.karavan.infinispan.model.ContainerStatus;
 import org.apache.camel.karavan.infinispan.model.GitConfig;
+import org.apache.camel.karavan.service.CodeService;
 import org.apache.camel.karavan.service.GitService;
 import org.apache.camel.karavan.service.GiteaService;
 import org.jboss.logging.Logger;
@@ -56,12 +57,12 @@ public class DockerForGitea {
     GitService gitService;
 
     @Inject
-    EventBus eventBus;
+    CodeService codeService;
 
     public void startGitea() {
         try {
             LOGGER.info("Gitea container is starting...");
-            var compose = 
dockerService.getInternalDockerComposeService(GITEA_CONTAINER_NAME);
+            var compose = 
codeService.getInternalDockerComposeService(GITEA_CONTAINER_NAME);
             Container c = dockerService.createContainerFromCompose(compose, 
ContainerStatus.ContainerType.internal);
             dockerService.runContainer(GITEA_CONTAINER_NAME);
             LOGGER.info("Gitea container is started");
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForInfinispan.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForInfinispan.java
index b7c8c197..54774463 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForInfinispan.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForInfinispan.java
@@ -20,6 +20,7 @@ import io.vertx.core.eventbus.EventBus;
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 import org.apache.camel.karavan.infinispan.model.ContainerStatus;
+import org.apache.camel.karavan.service.CodeService;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.jboss.logging.Logger;
 
@@ -42,12 +43,12 @@ public class DockerForInfinispan {
     DockerService dockerService;
 
     @Inject
-    EventBus eventBus;
+    CodeService codeService;
 
     public void startInfinispan() {
         try {
             LOGGER.info("Infinispan is starting...");
-            var compose = 
dockerService.getInternalDockerComposeService(INFINISPAN_CONTAINER_NAME);
+            var compose = 
codeService.getInternalDockerComposeService(INFINISPAN_CONTAINER_NAME);
             compose.addEnvironment("USER", infinispanUsername);
             compose.addEnvironment("PASS", infinispanPassword);
             dockerService.createContainerFromCompose(compose, 
ContainerStatus.ContainerType.internal);
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForKaravan.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForKaravan.java
index c6d2b728..9ae18fa4 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForKaravan.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForKaravan.java
@@ -50,7 +50,12 @@ public class DockerForKaravan {
     @Inject
     DockerService dockerService;
 
-    public void createDevmodeContainer(String projectId, String jBangOptions) 
throws InterruptedException {
+    public void runProjectInDevMode(String projectId, String jBangOptions, 
Map<Integer, Integer> ports, Map<String, String> files) throws Exception {
+        createDevmodeContainer(projectId, jBangOptions, ports);
+        dockerService.runContainer(projectId);
+        dockerService.copyFiles(projectId, "/code", files);
+    }
+    protected void createDevmodeContainer(String projectId, String 
jBangOptions, Map<Integer, Integer> ports) throws InterruptedException {
         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";))
@@ -61,7 +66,7 @@ public class DockerForKaravan {
                 : List.of();
 
         dockerService.createContainer(projectId, devmodeImage,
-                env, null, false, List.of(), healthCheck,
+                env, ports, healthCheck,
                 Map.of(LABEL_TYPE, 
ContainerStatus.ContainerType.devmode.name(), LABEL_PROJECT_ID, projectId),
                 Map.of());
 
@@ -83,7 +88,7 @@ public class DockerForKaravan {
                             "INFINISPAN_USERNAME=" + infinispanUsername,
                             "INFINISPAN_PASSWORD=" + infinispanPassword
                     ),
-                    null, false, List.of(), new HealthCheck(),
+                    null, new HealthCheck(),
                     Map.of(LABEL_TYPE, 
ContainerStatus.ContainerType.internal.name()),
                     Map.of());
 
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 e8b22d2c..5a2932c4 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
@@ -34,8 +34,10 @@ import jakarta.inject.Inject;
 import org.apache.camel.karavan.docker.model.DockerComposeService;
 import org.apache.camel.karavan.infinispan.model.ContainerStatus;
 import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
 import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
 import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
+import org.apache.commons.compress.utils.IOUtils;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.jboss.logging.Logger;
 
@@ -121,7 +123,7 @@ public class DockerService extends DockerServiceUtils {
 
     public Container getContainer(String id) {
         List<Container> containers = 
getDockerClient().listContainersCmd().withShowAll(true).withIdFilter(List.of(id)).exec();
-        return containers.get(0);
+        return containers.isEmpty() ? null : containers.get(0);
     }
 
     public Container getContainerByName(String name) {
@@ -149,14 +151,11 @@ public class DockerService extends DockerServiceUtils {
 
             HealthCheck healthCheck = getHealthCheck(compose.getHealthcheck());
             List<String> env = compose.getEnvironment() != null ? 
compose.getEnvironmentList() : List.of();
-            String ports = String.join(",", compose.getPorts());
 
             LOGGER.infof("Compose Service started for %s", 
compose.getContainer_name());
 
             return createContainer(compose.getContainer_name(), 
compose.getImage(),
-                    env, ports, false, compose.getExpose(), healthCheck,
-                    Map.of(LABEL_TYPE, type.name()),
-                    Map.of());
+                    env, compose.getPortsMap(), healthCheck, 
Map.of(LABEL_TYPE, type.name()), Map.of());
 
         } else {
             LOGGER.info("Compose Service already exists: " + 
containers.get(0).getId());
@@ -164,17 +163,8 @@ public class DockerService extends DockerServiceUtils {
         }
     }
 
-    public Container createContainerFromCompose(String yaml, String name, 
ContainerStatus.ContainerType type) throws Exception {
-        var compose = convertToDockerComposeService(yaml, name);
-        if (compose != null) {
-            return createContainerFromCompose(compose, type);
-        } else {
-            throw new Exception("Service not found in compose YAML!");
-        }
-    }
-
-    public Container createContainer(String name, String image, List<String> 
env, String ports, boolean inRange,
-                                     List<String> exposed, HealthCheck 
healthCheck, Map<String, String> labels,
+    public Container createContainer(String name, String image, List<String> 
env, Map<Integer, Integer> ports,
+                                     HealthCheck healthCheck, Map<String, 
String> labels,
                                      Map<String, String> volumes) throws 
InterruptedException {
         List<Container> containers = 
getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
         if (containers.size() == 0) {
@@ -183,14 +173,7 @@ public class DockerService extends DockerServiceUtils {
             CreateContainerCmd createContainerCmd = 
getDockerClient().createContainerCmd(image)
                     
.withName(name).withLabels(labels).withEnv(env).withHostName(name).withHealthcheck(healthCheck);
 
-            Ports portBindings;
-            List<ExposedPort> exposedPorts = new ArrayList<>();
-            if (exposed != null) {
-                exposedPorts.addAll(exposed.stream().map(i -> 
ExposedPort.tcp(Integer.parseInt(i))).toList());
-                portBindings = getPortBindings(ports,exposedPorts, inRange);
-            } else {
-                portBindings = getPortBindings(ports,exposedPorts, inRange);
-            }
+            Ports portBindings = getPortBindings(ports);
 
             List<Mount> mounts = new ArrayList<>();
             if (volumes != null && !volumes.isEmpty()) {
@@ -198,8 +181,7 @@ public class DockerService extends DockerServiceUtils {
                     mounts.add(new 
Mount().withType(MountType.BIND).withSource(hostPath).withTarget(containerPath));
                 });
             }
-
-            createContainerCmd.withExposedPorts(exposedPorts);
+//            createContainerCmd.withExposedPorts(exposedPorts);
             createContainerCmd.withHostConfig(new HostConfig()
                     .withPortBindings(portBindings)
                             .withMounts(mounts)
@@ -260,25 +242,23 @@ public class DockerService extends DockerServiceUtils {
     }
 
     public void execStart(String id, ResultCallback.Adapter<Frame> callBack) 
throws InterruptedException {
-        getDockerClient().execStartCmd(id).exec(callBack).awaitCompletion();
+        dockerClient.execStartCmd(id).exec(callBack).awaitCompletion();
+    }
+
+    public void copyFiles(String containerId, String containerPath, 
Map<String, String> files) throws IOException {
+            String temp = 
vertx.fileSystem().createTempDirectoryBlocking(containerId);
+            files.forEach((fileName, code) -> addFile(temp, fileName, code));
+            
dockerClient.copyArchiveToContainerCmd(containerId).withRemotePath(containerPath)
+                    .withDirChildrenOnly(true).withHostResource(temp).exec();
     }
 
-    public void copyFile(String id, String containerPath, String filename, 
String text) throws IOException {
-//        try (ByteArrayOutputStream byteArrayOutputStream = new 
ByteArrayOutputStream();
-//                TarArchiveOutputStream tarArchive = new 
TarArchiveOutputStream(byteArrayOutputStream)) {
-//            
tarArchive.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
-//            
tarArchive.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
-//
-//            String temp = 
vertx.fileSystem().createTempDirectoryBlocking("x");
-//            String path = temp + File.separator + filename;
-//            vertx.fileSystem().writeFileBlocking(path, Buffer.buffer(text));
-//
-//            ArchiveEntry archive = 
tarArchive.createArchiveEntry(Paths.get(path), "app.ini");
-//            tarArchive.putArchiveEntry(archive);;
-//            tarArchive.finish();
-            
getDockerClient().copyArchiveToContainerCmd(id).withRemotePath("/data")
-                    
.withHostResource("/Users/marat/projects/camel-karavan/karavan-web/karavan-app/src/main/resources/gitea").exec();
-//        }
+    private void addFile(String temp, String fileName, String code) {
+        try {
+            String path = temp + File.separator + fileName;
+            vertx.fileSystem().writeFileBlocking(path, Buffer.buffer(code));
+        } catch (Exception e) {
+            LOGGER.error(e.getMessage());
+        }
     }
 
     public void logContainer(String containerName, LogCallback callback) {
@@ -359,4 +339,14 @@ public class DockerService extends DockerServiceUtils {
         }
         return dockerClient;
     }
+
+    public int getMaxPortMapped(int port) {
+        return 
getDockerClient().listContainersCmd().withShowAll(true).exec().stream()
+                .map(c -> List.of(c.ports))
+                .flatMap(java.util.List::stream)
+                .filter(p -> Objects.equals(p.getPrivatePort(), port))
+                .map(ContainerPort::getPublicPort).filter(Objects::nonNull)
+                .mapToInt(Integer::intValue)
+                .max().orElse(port);
+    }
 }
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerServiceUtils.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerServiceUtils.java
index 703f33ae..0b8b698f 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerServiceUtils.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerServiceUtils.java
@@ -42,8 +42,6 @@ import static 
org.apache.camel.karavan.shared.Constants.LABEL_TYPE;
 
 public class DockerServiceUtils {
 
-    protected static final String ENVIRONMENT = "environment";
-
     protected static final DecimalFormat formatCpu = new DecimalFormat("0.00");
     protected static final DecimalFormat formatMiB = new DecimalFormat("0.0");
     protected static final DecimalFormat formatGiB = new DecimalFormat("0.00");
@@ -71,46 +69,6 @@ public class DockerServiceUtils {
         }
     }
 
-    public DockerComposeService getInternalDockerComposeService (String name) {
-        String composeText = getResourceFile("/services/internal.yaml");
-        return convertToDockerComposeService(composeText, name);
-    }
-
-    public DockerComposeService convertToDockerComposeService(String code, 
String name) {
-        Yaml yaml = new Yaml();
-        Map<String, Object> obj = yaml.load(code);
-        JsonObject json = JsonObject.mapFrom(obj);
-        JsonObject services = json.getJsonObject("services");
-        if (services.containsKey(name)) {
-            JsonObject service = services.getJsonObject(name);
-            return convertToDockerComposeService(name, service);
-        } else {
-            Optional<JsonObject> j = services.fieldNames().stream()
-                    .map(services::getJsonObject)
-                    .filter(s -> 
s.getString("container_name").equalsIgnoreCase(name)).findFirst();
-            if (j.isPresent()) {
-                return convertToDockerComposeService(name, j.get());
-            }
-        }
-        return null;
-    }
-
-    public DockerComposeService convertToDockerComposeService(String name, 
JsonObject service) {
-        if (service.containsKey(ENVIRONMENT) && service.getValue(ENVIRONMENT) 
instanceof JsonArray) {
-            JsonObject env = new JsonObject();
-            service.getJsonArray(ENVIRONMENT).forEach(o -> {
-                String[] kv = o.toString().split("=");
-                env.put(kv[0], kv[1]);
-            });
-            service.put(ENVIRONMENT, env);
-        }
-        DockerComposeService ds = service.mapTo(DockerComposeService.class);
-        if (ds.getContainer_name() == null) {
-            ds.setContainer_name(name);
-        }
-        return ds;
-    }
-
     protected HealthCheck getHealthCheck(HealthCheckConfig config) {
         if (config != null) {
             HealthCheck healthCheck = new 
HealthCheck().withTest(config.getTest());
@@ -151,29 +109,16 @@ public class DockerServiceUtils {
         return Duration.parse(s).toMillis() * 1000000L;
     }
 
-    protected Ports getPortBindings(String ports, List<ExposedPort> 
exposedPorts, boolean inRange) {
+    protected Ports getPortBindings(Map<Integer, Integer> ports) {
         Ports portBindings = new Ports();
 
-        getPortsFromString(ports).forEach((hostPort, containerPort) -> {
-            Ports.Binding binding = (exposedPorts.stream().anyMatch(e -> 
e.getPort() == containerPort))
-                    ? (inRange ? Ports.Binding.bindPortRange(hostPort, 
hostPort + 1000) : Ports.Binding.bindPort(hostPort))
-                    : Ports.Binding.bindPort(hostPort);
+        ports.forEach((hostPort, containerPort) -> {
+            Ports.Binding binding = Ports.Binding.bindPort(hostPort);
             portBindings.bind(ExposedPort.tcp(containerPort), binding);
         });
         return portBindings;
     }
 
-    protected Map<Integer, Integer> getPortsFromString(String ports) {
-        Map<Integer, Integer> p = new HashMap<>();
-        if (ports != null && !ports.isEmpty()) {
-            Arrays.stream(ports.split(",")).forEach(s -> {
-                String[] values = s.split(":");
-                p.put(Integer.parseInt(values[0]), 
Integer.parseInt(values[1]));
-            });
-        }
-        return p;
-    }
-
     protected String formatMemory(Long memory) {
         try {
             if (memory < (1073741824)) {
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/model/DockerComposeService.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/model/DockerComposeService.java
index a348c706..629153e4 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/model/DockerComposeService.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/model/DockerComposeService.java
@@ -1,9 +1,6 @@
 package org.apache.camel.karavan.docker.model;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.stream.Collectors;
 
 public class DockerComposeService {
@@ -52,6 +49,17 @@ public class DockerComposeService {
         this.ports = ports;
     }
 
+    public Map<Integer, Integer> getPortsMap() {
+        Map<Integer, Integer> p = new HashMap<>();
+        if (ports != null && !ports.isEmpty()) {
+            ports.forEach(s -> {
+                String[] values = s.split(":");
+                p.put(Integer.parseInt(values[0]), 
Integer.parseInt(values[1]));
+            });
+        }
+        return p;
+    }
+
     public List<String> getExpose() {
         return expose;
     }
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 e76fcb30..73557d4d 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
@@ -27,6 +27,7 @@ import org.apache.camel.karavan.infinispan.InfinispanService;
 import org.apache.camel.karavan.infinispan.model.CamelStatus;
 import org.apache.camel.karavan.infinispan.model.ContainerStatus;
 import org.apache.camel.karavan.kubernetes.KubernetesService;
+import org.apache.camel.karavan.shared.ConfigService;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
 import org.jboss.logging.Logger;
@@ -40,8 +41,7 @@ import java.util.concurrent.ExecutionException;
 
 import static org.apache.camel.karavan.shared.Constants.LABEL_PROJECT_ID;
 import static org.apache.camel.karavan.shared.Constants.RELOAD_TRY_COUNT;
-import static org.apache.camel.karavan.shared.EventType.CONTAINER_STATUS;
-import static 
org.apache.camel.karavan.shared.EventType.DEVMODE_CONTAINER_READY;
+import static org.apache.camel.karavan.shared.EventType.*;
 
 @ApplicationScoped
 public class CamelService {
@@ -74,6 +74,37 @@ public class CamelService {
         return webClient;
     }
 
+
+    public void loadCodeToDevMode(String projectId) {
+        LOGGER.info("DevMode reload code " + projectId);
+        ContainerStatus status = 
infinispanService.getContainerStatus(projectId, environment, projectId);
+        CamelStatus cs = infinispanService.getCamelStatus(projectId, 
environment, CamelStatus.Name.context.name());
+        if (status != null
+                && !Objects.equals(status.getCodeLoaded(), Boolean.TRUE)
+                && status.getContainerId() != null
+                && 
status.getState().equals(ContainerStatus.State.running.name())
+                && camelIsStarted(cs)) {
+            LOGGER.info("CAMEL STARTED -> SEND RELOAD");
+            if (ConfigService.inKubernetes()) {
+                reloadProjectCode(projectId);
+            } else {
+                infinispanService.sendCodeReloadCommand(projectId);
+            }
+        } else {
+
+        }
+    }
+
+    private boolean camelIsStarted(CamelStatus camelStatus) {
+        try {
+            String status = camelStatus.getStatus();
+            JsonObject obj = new JsonObject(status);
+            return Objects.equals("Started", 
obj.getJsonObject("context").getString("state"));
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
     public void reloadProjectCode(String projectId) {
         LOGGER.info("Reload project code " + projectId);
         try {
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
index f961d968..4e243bc2 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
@@ -23,12 +23,16 @@ import io.apicurio.datamodels.openapi.models.OasDocument;
 import io.quarkus.qute.Engine;
 import io.quarkus.qute.Template;
 import io.quarkus.qute.TemplateInstance;
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 import org.apache.camel.CamelContext;
 import org.apache.camel.generator.openapi.RestDslGenerator;
 import org.apache.camel.impl.DefaultCamelContext;
 import org.apache.camel.karavan.api.KameletResources;
+import org.apache.camel.karavan.docker.DockerService;
+import org.apache.camel.karavan.docker.model.DockerComposeService;
 import org.apache.camel.karavan.infinispan.InfinispanService;
 import org.apache.camel.karavan.infinispan.model.GitRepo;
 import org.apache.camel.karavan.infinispan.model.GitRepoFile;
@@ -55,10 +59,18 @@ public class CodeService {
     private static final Logger LOGGER = 
Logger.getLogger(CodeService.class.getName());
     public static final String APPLICATION_PROPERTIES_FILENAME = 
"application.properties";
     public static final String DEV_SERVICES_FILENAME = "devservices.yaml";
+    public static final String PROJECT_COMPOSE_FILENAME = 
"project-compose.yaml";
+    private static final String SNIPPETS_PATH = "/snippets/";
+    private static final int INTERNAL_PORT = 8080;
+
+    protected static final String ENVIRONMENT = "environment";
 
     @Inject
     KubernetesService kubernetesService;
 
+    @Inject
+    DockerService dockerService;
+
     @Inject
     InfinispanService infinispanService;
 
@@ -106,20 +118,22 @@ public class CodeService {
         return null;
     }
 
-    public Map<String, String> getApplicationPropertiesTemplates() {
+    public Map<String, String> getTemplates() {
         Map<String, String> result = new HashMap<>();
 
         List<String> files = new ArrayList<>(interfaces);
-        files.addAll(targets.stream().map(target -> target + "-" + 
APPLICATION_PROPERTIES_FILENAME).collect(Collectors.toList()));
+        files.addAll(targets.stream().map(target -> target + "-" + 
APPLICATION_PROPERTIES_FILENAME).toList());
 
         runtimes.forEach(runtime -> {
             files.forEach(file -> {
                 String templateName = runtime + "-" + file;
-                String templatePath = "/snippets/" + templateName;
+                String templatePath = SNIPPETS_PATH + templateName;
                 String templateText = getResourceFile(templatePath);
                 result.put(templateName, templateText);
             });
         });
+
+        result.put(PROJECT_COMPOSE_FILENAME, getResourceFile(SNIPPETS_PATH + 
PROJECT_COMPOSE_FILENAME));
         return result;
     }
 
@@ -254,4 +268,81 @@ public class CodeService {
     public String getProjectRuntime(String file) {
         return getProperty(file, "camel.jbang.runtime");
     }
+
+    public ProjectFile createInitialProjectCompose(Project project) {
+        int port = getNextAvailablePort();
+        String templateText = getTemplateText(PROJECT_COMPOSE_FILENAME);
+        Template result = engine.parse(templateText);
+        TemplateInstance instance = result
+                .data("projectId", project.getProjectId())
+                .data("projectPort", port)
+                .data("projectImage", project.getProjectId());
+        String code =  instance.render();
+        return new ProjectFile(PROJECT_COMPOSE_FILENAME, code, 
project.getProjectId(), Instant.now().toEpochMilli());
+    }
+
+    private int getNextAvailablePort() {
+        int dockerPort = dockerService.getMaxPortMapped(INTERNAL_PORT);
+        int projectPort = getMaxPortMappedInProjects();
+        return Math.max(projectPort, dockerPort) + 1;
+    }
+
+
+    private int getMaxPortMappedInProjects() {
+        List<ProjectFile> files =  
infinispanService.getProjectFilesByName(PROJECT_COMPOSE_FILENAME).stream()
+                .filter(f -> !Objects.equals(f.getProjectId(), 
Project.Type.templates.name())).toList();
+        if (!files.isEmpty()) {
+            return files.stream().map(f -> 
convertToDockerComposeService(f.getCode(), f.getProjectId()))
+                    .map(dcs -> {
+                        Optional<Integer> port = 
dcs.getPortsMap().entrySet().stream()
+                                .filter(e -> Objects.equals(e.getValue(), 
INTERNAL_PORT)).map(Map.Entry::getKey).findFirst();
+                        return port.orElse(INTERNAL_PORT);
+                    })
+                    .mapToInt(Integer::intValue)
+                    .max().orElse(INTERNAL_PORT);
+        } else {
+            return INTERNAL_PORT;
+        }
+    }
+
+
+    public DockerComposeService getInternalDockerComposeService (String name) {
+        String composeText = getResourceFile("/services/internal.yaml");
+        return convertToDockerComposeService(composeText, name);
+    }
+
+    public DockerComposeService convertToDockerComposeService(String code, 
String name) {
+        Yaml yaml = new Yaml();
+        Map<String, Object> obj = yaml.load(code);
+        JsonObject json = JsonObject.mapFrom(obj);
+        JsonObject services = json.getJsonObject("services");
+        if (services.containsKey(name)) {
+            JsonObject service = services.getJsonObject(name);
+            return convertToDockerComposeService(name, service);
+        } else {
+            Optional<JsonObject> j = services.fieldNames().stream()
+                    .map(services::getJsonObject)
+                    .filter(s -> 
s.getString("container_name").equalsIgnoreCase(name)).findFirst();
+            if (j.isPresent()) {
+                return convertToDockerComposeService(name, j.get());
+            }
+        }
+        return null;
+    }
+
+    public DockerComposeService convertToDockerComposeService(String name, 
JsonObject service) {
+        if (service.containsKey(ENVIRONMENT) && service.getValue(ENVIRONMENT) 
instanceof JsonArray) {
+            JsonObject env = new JsonObject();
+            service.getJsonArray(ENVIRONMENT).forEach(o -> {
+                String[] kv = o.toString().split("=");
+                env.put(kv[0], kv[1]);
+            });
+            service.put(ENVIRONMENT, env);
+        }
+        DockerComposeService ds = service.mapTo(DockerComposeService.class);
+        if (ds.getContainer_name() == null) {
+            ds.setContainer_name(name);
+        }
+        return ds;
+    }
 }
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/DelayRoute.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/DelayRoute.java
deleted file mode 100644
index 8e594246..00000000
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/DelayRoute.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.apache.camel.karavan.service;
-
-import org.apache.camel.builder.endpoint.EndpointRouteBuilder;
-import org.apache.camel.karavan.infinispan.InfinispanService;
-
-import static org.apache.camel.karavan.shared.EventType.*;
-
-public class DelayRoute extends EndpointRouteBuilder {
-
-    @Override
-    public void configure() throws Exception {
-        from(vertx(DEVMODE_DELAY_MESSAGE))
-                .delay(500)
-                .to(vertx(DEVMODE_CONTAINER_READY));
-
-//        from(vertx(InfinispanService.INFINISPAN_START_DELAY))
-//                .delay(1000)
-//                .toD(vertx(InfinispanService.INFINISPAN_START));
-    }
-}
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 326946dd..e873d25f 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
@@ -8,16 +8,13 @@ import jakarta.inject.Inject;
 import org.apache.camel.karavan.infinispan.InfinispanService;
 import org.apache.camel.karavan.infinispan.model.CamelStatus;
 import org.apache.camel.karavan.infinispan.model.ContainerStatus;
-import org.apache.camel.karavan.shared.ConfigService;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.jboss.logging.Logger;
 
-import java.util.Map;
 import java.util.Objects;
 
-import static org.apache.camel.karavan.shared.Constants.LABEL_PROJECT_ID;
-import static org.apache.camel.karavan.shared.Constants.RELOAD_TRY_COUNT;
-import static org.apache.camel.karavan.shared.EventType.*;
+import static org.apache.camel.karavan.shared.EventType.CONTAINER_STATUS;
+import static 
org.apache.camel.karavan.shared.EventType.DEVMODE_CONTAINER_READY;
 
 @ApplicationScoped
 public class EventService {
@@ -38,31 +35,31 @@ public class EventService {
 
     @ConsumeEvent(value = DEVMODE_CONTAINER_READY, blocking = true, ordered = 
true)
     void receiveCommand(JsonObject json) {
-        String projectId = json.getString(LABEL_PROJECT_ID);
-        Integer reloadCount = json.getInteger(RELOAD_TRY_COUNT);
-        LOGGER.info("DEVMODE_CONTAINER_READY " + projectId + " : " + 
reloadCount);
-        ContainerStatus status = 
infinispanService.getContainerStatus(projectId, environment, projectId);
-        CamelStatus cs = infinispanService.getCamelStatus(projectId, 
environment, CamelStatus.Name.context.name());
-        if (status != null
-                && !Objects.equals(status.getCodeLoaded(), Boolean.TRUE)
-                && status.getContainerId() != null
-                && 
status.getState().equals(ContainerStatus.State.running.name())
-                && camelIsStarted(cs)) {
-            LOGGER.info("CAMEL STARTED -> SEND RELOAD");
-            if (ConfigService.inKubernetes()) {
-                camelService.reloadProjectCode(projectId);
-            } else {
-                infinispanService.sendCodeReloadCommand(projectId);
-            }
-        } else if (reloadCount < 30) {
-            LOGGER.info("CAMEL NOT STARTED -> SEND DEVMODE_CONTAINER_READY");
-            // retry again
-            Map<String, Object> message = Map.of(
-                    LABEL_PROJECT_ID, projectId,
-                    RELOAD_TRY_COUNT, ++reloadCount
-            );
-            eventBus.publish(DEVMODE_DELAY_MESSAGE, 
JsonObject.mapFrom(message));
-        }
+//        String projectId = json.getString(LABEL_PROJECT_ID);
+//        Integer reloadCount = json.getInteger(RELOAD_TRY_COUNT);
+//        LOGGER.info("DEVMODE_CONTAINER_READY " + projectId + " : " + 
reloadCount);
+//        ContainerStatus status = 
infinispanService.getContainerStatus(projectId, environment, projectId);
+//        CamelStatus cs = infinispanService.getCamelStatus(projectId, 
environment, CamelStatus.Name.context.name());
+//        if (status != null
+//                && !Objects.equals(status.getCodeLoaded(), Boolean.TRUE)
+//                && status.getContainerId() != null
+//                && 
status.getState().equals(ContainerStatus.State.running.name())
+//                && camelIsStarted(cs)) {
+//            LOGGER.info("CAMEL STARTED -> SEND RELOAD");
+//            if (ConfigService.inKubernetes()) {
+//                camelService.reloadProjectCode(projectId);
+//            } else {
+//                infinispanService.sendCodeReloadCommand(projectId);
+//            }
+//        } else if (reloadCount < 30) {
+//            LOGGER.info("CAMEL NOT STARTED -> SEND DEVMODE_CONTAINER_READY");
+//            // retry again
+//            Map<String, Object> message = Map.of(
+//                    LABEL_PROJECT_ID, projectId,
+//                    RELOAD_TRY_COUNT, ++reloadCount
+//            );
+//            eventBus.publish(DEVMODE_DELAY_MESSAGE, 
JsonObject.mapFrom(message));
+//        }
     }
 
     private boolean camelIsStarted(CamelStatus camelStatus) {
@@ -75,6 +72,8 @@ public class EventService {
         }
     }
 
+
+
     @ConsumeEvent(value = CONTAINER_STATUS, blocking = true, ordered = true)
     public void saveContainerStatus(JsonObject data) {
         if (infinispanService.isReady()) {
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java
index 7e91bed2..6a5304a9 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java
@@ -93,7 +93,7 @@ public class KaravanService {
             dockerService.startListeners();
 
             dockerForInfinispan.startInfinispan();
-            dockerForKaravan.startKaravanHeadlessContainer();
+//            dockerForKaravan.startKaravanHeadlessContainer();
             if (giteaInstall) {
                 dockerForGitea.startGitea();
                 giteaService.install();
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
index 7dbdd75a..1ec31c08 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
@@ -17,17 +17,25 @@
 package org.apache.camel.karavan.service;
 
 import io.smallrye.mutiny.tuples.Tuple2;
+import io.vertx.core.json.JsonObject;
+import io.vertx.mutiny.core.eventbus.EventBus;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.core.Response;
+import org.apache.camel.karavan.docker.DockerForKaravan;
+import org.apache.camel.karavan.docker.model.DockerComposeService;
 import org.apache.camel.karavan.infinispan.InfinispanService;
+import org.apache.camel.karavan.infinispan.model.ContainerStatus;
 import org.apache.camel.karavan.infinispan.model.GitRepo;
 import org.apache.camel.karavan.infinispan.model.Project;
 import org.apache.camel.karavan.infinispan.model.ProjectFile;
 import org.apache.camel.karavan.kubernetes.KubernetesService;
+import org.apache.camel.karavan.shared.ConfigService;
+import org.apache.camel.karavan.shared.EventType;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.eclipse.microprofile.faulttolerance.Retry;
 import org.eclipse.microprofile.health.HealthCheck;
 import org.eclipse.microprofile.health.HealthCheckResponse;
-import org.eclipse.microprofile.health.HealthCheckResponseBuilder;
 import org.eclipse.microprofile.health.Readiness;
 import org.jboss.logging.Logger;
 
@@ -36,11 +44,14 @@ import jakarta.enterprise.inject.Default;
 import jakarta.inject.Inject;
 import java.time.Instant;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
 
 import static 
org.apache.camel.karavan.service.CodeService.DEV_SERVICES_FILENAME;
+import static 
org.apache.camel.karavan.service.CodeService.PROJECT_COMPOSE_FILENAME;
 
 @Default
 @Readiness
@@ -49,6 +60,9 @@ public class ProjectService implements HealthCheck{
 
     private static final Logger LOGGER = 
Logger.getLogger(ProjectService.class.getName());
 
+    @ConfigProperty(name = "karavan.environment")
+    String environment;
+
     @ConfigProperty(name = "karavan.git-pull-interval")
     String gitPullInterval;
 
@@ -58,12 +72,18 @@ public class ProjectService implements HealthCheck{
     @Inject
     KubernetesService kubernetesService;
 
+    @Inject
+    DockerForKaravan dockerForKaravan;
+
     @Inject
     GitService gitService;
 
     @Inject
     CodeService codeService;
 
+    @Inject
+    EventBus eventBus;
+
     private AtomicBoolean ready = new AtomicBoolean(false);
     private AtomicBoolean readyToPull = new AtomicBoolean(false);
 
@@ -77,6 +97,47 @@ public class ProjectService implements HealthCheck{
         }
     }
 
+    public String runProjectWithJBangOptions(Project project, 
@PathParam("jBangOptions") String jBangOptions) throws Exception {
+        String containerName = project.getProjectId();
+        ContainerStatus status = 
infinispanService.getDevModeContainerStatus(project.getProjectId(), 
environment);
+        if (status == null) {
+            status = ContainerStatus.createDevMode(project.getProjectId(), 
environment);
+        }
+
+        if (!Objects.equals(status.getState(), 
ContainerStatus.State.running.name())) {
+            status.setInTransit(true);
+            eventBus.send(EventType.CONTAINER_STATUS, 
JsonObject.mapFrom(status));
+
+            if (ConfigService.inKubernetes()) {
+                kubernetesService.runDevModeContainer(project, jBangOptions);
+            } else {
+                Map<String, String> files  = 
infinispanService.getProjectFiles(project.getProjectId()).stream()
+                        .filter(f -> !Objects.equals(f.getName(), 
PROJECT_COMPOSE_FILENAME))
+                        .collect(Collectors.toMap(ProjectFile::getName, 
ProjectFile::getCode));
+                ProjectFile compose = 
infinispanService.getProjectFile(project.getProjectId(), 
PROJECT_COMPOSE_FILENAME);
+                DockerComposeService dcs = 
codeService.convertToDockerComposeService(compose.getCode(), 
project.getProjectId());
+                dockerForKaravan.runProjectInDevMode(project.getProjectId(), 
jBangOptions, dcs.getPortsMap(), files);
+            }
+            return containerName;
+        } else {
+            return null;
+        }
+    }
+
+    public Project save(Project project) throws Exception {
+        boolean isNew = infinispanService.getProject(project.getProjectId()) 
== null;
+        infinispanService.saveProject(project);
+        if (isNew){
+            ProjectFile appProp = 
codeService.getApplicationProperties(project);
+            infinispanService.saveProjectFile(appProp);
+            if (!ConfigService.inKubernetes()) {
+                ProjectFile projectCompose = 
codeService.createInitialProjectCompose(project);
+                infinispanService.saveProjectFile(projectCompose);
+            }
+        }
+        return project;
+    }
+
     public void pullCommits() {
         if (readyToPull.get()) {
             LOGGER.info("Pull commits...");
@@ -224,7 +285,7 @@ public class ProjectService implements HealthCheck{
                 templates = new Project(Project.Type.templates.name(), 
"Templates", "Templates", "", "", Instant.now().toEpochMilli(), 
Project.Type.templates);
                 infinispanService.saveProject(templates);
 
-                codeService.getApplicationPropertiesTemplates().forEach((name, 
value) -> {
+                codeService.getTemplates().forEach((name, value) -> {
                     ProjectFile file = new ProjectFile(name, value, 
Project.Type.templates.name(), Instant.now().toEpochMilli());
                     infinispanService.saveProjectFile(file);
                 });
@@ -262,7 +323,7 @@ public class ProjectService implements HealthCheck{
                 pipelines = new Project(Project.Type.pipelines.name(), 
"Pipelines", "CI/CD Pipelines", "", "", Instant.now().toEpochMilli(), 
Project.Type.pipelines);
                 infinispanService.saveProject(pipelines);
 
-                codeService.getApplicationPropertiesTemplates().forEach((name, 
value) -> {
+                codeService.getTemplates().forEach((name, value) -> {
                     ProjectFile file = new ProjectFile(name, value, 
Project.Type.pipelines.name(), Instant.now().toEpochMilli());
                     infinispanService.saveProjectFile(file);
                 });
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 0f6ba504..480185ab 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
@@ -19,7 +19,6 @@ package org.apache.camel.karavan.shared;
 public class EventType {
 
     public static final String CONTAINER_STATUS = "CONTAINER_STATUS";
-    public static final String DEVMODE_CONTAINER_READY = "DEVMODE_STATUS";
-    public static final String DEVMODE_DELAY_MESSAGE = "DEVMODE_DELAY_MESSAGE";
+    public static final String DEVMODE_CONTAINER_READY = 
"DEVMODE_CONTAINER_READY";
 
 }
diff --git 
a/karavan-web/karavan-app/src/main/resources/snippets/project-compose.yaml 
b/karavan-web/karavan-app/src/main/resources/snippets/project-compose.yaml
new file mode 100644
index 00000000..36c668af
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/resources/snippets/project-compose.yaml
@@ -0,0 +1,8 @@
+services:
+  {projectId}:
+    image: {projectImage}
+    restart: always
+    ports:
+      - "{projectPort}:8080"
+    networks:
+      - karavan
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 9237fba3..a8ab3c75 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
@@ -257,8 +257,8 @@ export class KaravanApi {
         });
     }
 
-    static async getFiles(project: string, after: (files: ProjectFile[]) => 
void) {
-        instance.get('/api/file/' + project)
+    static async getFiles(projectId: string, after: (files: ProjectFile[]) => 
void) {
+        instance.get('/api/file/' + projectId)
             .then(res => {
                 if (res.status === 200) {
                     after(res.data);
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 9ebc6eda..7bef52d9 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
@@ -111,7 +111,7 @@ export class ProjectService {
             if (res.status === 200 || res.status === 201) {
                 useProjectStore.setState({isPushing: false})
                 ProjectService.refreshProject(project.projectId);
-                ProjectService.refreshProjectData();
+                ProjectService.refreshProjectData(project.projectId);
             } else {
                 // Todo notification
             }
@@ -169,7 +169,7 @@ export class ProjectService {
         KaravanApi.deleteProject(project, res => {
             if (res.status === 204) {
                 // this.props.toast?.call(this, 'Success', 'Project deleted', 
'success');
-                ProjectService.refreshProjectData();
+                ProjectService.refreshProjectData(project.projectId);
             } else {
                 // this.props.toast?.call(this, 'Error', res.statusText, 
'danger');
             }
@@ -179,7 +179,7 @@ export class ProjectService {
     public static createProject(project: Project) {
         KaravanApi.postProject(project, res => {
             if (res.status === 200 || res.status === 201) {
-                ProjectService.refreshProjectData();
+                ProjectService.refreshProjectData(project.projectId);
                 // this.props.toast?.call(this, 'Success', 'Project created', 
'success');
             } else {
                 // this.props.toast?.call(this, 'Error', res.status + ', ' + 
res.statusText, 'danger');
@@ -191,7 +191,7 @@ export class ProjectService {
         KaravanApi.postProjectFile(file, res => {
             if (res.status === 200) {
                 // console.log(res) //TODO show notification
-                ProjectService.refreshProjectData();
+                ProjectService.refreshProjectData(file.projectId);
             } else {
                 // console.log(res) //TODO show notification
             }
@@ -201,15 +201,14 @@ export class ProjectService {
     public static deleteFile(file: ProjectFile) {
         KaravanApi.deleteProjectFile(file, res => {
             if (res.status === 204) {
-                ProjectService.refreshProjectData();
+                ProjectService.refreshProjectData(file.projectId);
             } else {
             }
         });
     }
 
-    public static refreshProjectData() {
-        const project = useProjectStore.getState().project;
-        KaravanApi.getProject(project.projectId, (project: Project) => {
+    public static refreshProjectData(projectId: string) {
+        KaravanApi.getProject(projectId, (project: Project) => {
             // ProjectEventBus.selectProject(project);
             KaravanApi.getTemplatesFiles((files: ProjectFile[]) => {
                 files.filter(f => f.name.endsWith('java'))
@@ -220,7 +219,7 @@ export class ProjectService {
                     })
             });
         });
-        KaravanApi.getFiles(project.projectId, (files: ProjectFile[]) => {
+        KaravanApi.getFiles(projectId, (files: ProjectFile[]) => {
             useFilesStore.setState({files: files});
         });
 
diff --git a/karavan-web/karavan-app/src/main/webui/src/main/MainDataPoller.tsx 
b/karavan-web/karavan-app/src/main/webui/src/main/MainDataPoller.tsx
index 6ff24777..8d7de7ab 100644
--- a/karavan-web/karavan-app/src/main/webui/src/main/MainDataPoller.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/main/MainDataPoller.tsx
@@ -33,12 +33,10 @@ export function MainDataPoller () {
     }, [project, readiness]);
 
     function getData() {
-        console.log(readiness);
         KaravanApi.getReadiness((r: any) => {
             setReadiness(r);
         })
         if (readiness) {
-            console.log("getData");
             setLoading(true);
             KaravanApi.getConfiguration((config: AppConfig) => {
                 if (project.projectId === undefined) {
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/project/ProjectPanel.tsx 
b/karavan-web/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
index 2240c8a8..94fb1d28 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
@@ -19,10 +19,12 @@ export function ProjectPanel () {
 
     useEffect(() => {
         onRefresh();
-    });
+    }, [project]);
 
     function onRefresh () {
-        ProjectService.refreshProjectData();
+        if (project.projectId) {
+            ProjectService.refreshProjectData(project.projectId);
+        }
     }
 
     function isBuildIn(): boolean {
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/project/files/FilesTab.tsx 
b/karavan-web/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
index 6c444bf5..2e2d0bd8 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, {useEffect} from 'react';
 import {
     Badge,
     Button,
@@ -115,7 +115,7 @@ export function FilesTab () {
                             <Td modifier={"fitContent"}>
                                 {canDeleteFiles() &&
                                     <Button style={{padding: '0'}} 
variant={"plain"}
-                                            isDisabled={file.name === 
'application.properties'}
+                                            
isDisabled={['application.properties', 
'project-compose.yaml'].includes(file.name)}
                                             onClick={e =>
                                                 useFileStore.setState({file: 
file, operation: "delete"})
                                     }>
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 3ea2c584..eae0311f 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
@@ -196,6 +196,13 @@ public class InfinispanService implements HealthCheck {
         return list.size() > 0 ? list.get(0) : null;
     }
 
+    public List<ProjectFile> getProjectFilesByName(String filename) {
+        QueryFactory queryFactory = Search.getQueryFactory(files);
+        return queryFactory.<ProjectFile>create("FROM karavan.ProjectFile 
WHERE name = :name")
+                .setParameter("name", filename)
+                .execute().list();
+    }
+
     public void saveProjectFile(ProjectFile file) {
         files.put(GroupedKey.create(file.getProjectId(), DEFAULT_ENVIRONMENT, 
file.getName()), file);
     }

Reply via email to