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 93e99f50bc7c5a4b9a18ff1e35a5af558b83e0eb
Author: Marat Gubaidullin <[email protected]>
AuthorDate: Wed Sep 6 10:14:29 2023 -0400

    Image builder for #817
---
 .../apache/camel/karavan/api/ProjectResource.java  |  45 +++++++++
 .../camel/karavan/docker/DockerEventListener.java  |   5 +-
 .../camel/karavan/docker/DockerForKaravan.java     | 103 +++------------------
 .../apache/camel/karavan/docker/DockerService.java |  63 +++++++++----
 .../camel/karavan/docker/DockerServiceUtils.java   |  21 ++---
 .../karavan/infinispan/InfinispanService.java      |  26 ------
 .../camel/karavan/infinispan/model/CommitInfo.java |  53 -----------
 .../karavan/infinispan/model/ContainerStatus.java  |  35 ++++---
 .../apache/camel/karavan/service/CodeService.java  |  19 +---
 .../apache/camel/karavan/service/EventService.java |  16 +++-
 .../apache/camel/karavan/service/GitService.java   |  38 --------
 .../camel/karavan/service/ProjectService.java      |  81 +++++++---------
 .../camel/karavan/service/ScheduledService.java    |  32 ++++---
 .../org/apache/camel/karavan/shared/Constants.java |   2 +-
 .../src/main/resources/application.properties      |   7 +-
 .../snippets/camel-main-builder-script-docker.sh   |   1 -
 .../src/main/webui/src/api/KaravanApi.tsx          |  15 ++-
 .../src/main/webui/src/api/ProjectModels.ts        |   1 +
 .../src/main/webui/src/api/ProjectStore.ts         |   6 +-
 .../webui/src/containers/ContainerTableRow.tsx     |   2 +-
 .../main/webui/src/project/build/BuildStatus.tsx   |  76 ++++++++++++---
 21 files changed, 294 insertions(+), 353 deletions(-)

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 b62ad193..b0bdf4cd 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
@@ -16,16 +16,22 @@
  */
 package org.apache.camel.karavan.api;
 
+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.ContainerStatus;
 import org.apache.camel.karavan.infinispan.model.GroupedKey;
 import org.apache.camel.karavan.infinispan.model.Project;
 import org.apache.camel.karavan.infinispan.model.ProjectFile;
+import org.apache.camel.karavan.kubernetes.KubernetesService;
 import org.apache.camel.karavan.service.CodeService;
 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 org.apache.camel.karavan.shared.ConfigService;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
 
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
@@ -35,12 +41,23 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.stream.Collectors;
 
+import static org.apache.camel.karavan.shared.Constants.BUILDER_SUFFIX;
+
 @Path("/api/project")
 public class ProjectResource {
 
+    @ConfigProperty(name = "karavan.environment")
+    String environment;
+
     @Inject
     InfinispanService infinispanService;
 
+    @Inject
+    KubernetesService kubernetesService;
+
+    @Inject
+    DockerService dockerService;
+
     @Inject
     GitService gitService;
 
@@ -85,6 +102,34 @@ public class ProjectResource {
         infinispanService.deleteProject(projectId);
     }
 
+    @POST
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Path("/build")
+    public Response build(Project project) throws Exception {
+        try {
+            projectService.buildProject(project);
+            return Response.ok().entity(project).build();
+        } catch (Exception e) {
+            return Response.serverError().entity(e.getMessage()).build();
+        }
+    }
+
+    @DELETE
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/build/{env}/{buildName}")
+    public Response deleteBuild(@HeaderParam("username") String username,
+                       @PathParam("env") String env, @PathParam("buildName") 
String buildName) {
+        buildName = URLDecoder.decode(buildName, StandardCharsets.UTF_8);
+        if (ConfigService.inKubernetes()) {
+            kubernetesService.stopPipelineRun(buildName, 
kubernetesService.getNamespace());
+            return Response.ok().build();
+        } else {
+            dockerService.deleteContainer(buildName);
+            return Response.ok().build();
+        }
+    }
+
     @POST
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
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 8ed7b546..86195c79 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
@@ -85,9 +85,10 @@ public class DockerEventListener implements 
ResultCallback<Event> {
             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);
+            String projectId = 
container.getLabels().getOrDefault(LABEL_PROJECT_ID, name);
+            ContainerStatus ci = 
infinispanService.getContainerStatus(projectId, environment, name);
             if (ci == null) {
-                ci = ContainerStatus.createWithId(name, environment, 
container.getId(), container.getImage(), ports, type, commands, 
container.getState(), created);
+                ci = ContainerStatus.createWithId(projectId, name, 
environment, container.getId(), container.getImage(), ports, type, commands, 
container.getState(), created);
             } else {
                 ci.setContainerId(container.getId());
                 ci.setPorts(ports);
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 38a5a563..474265d2 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
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.karavan.docker;
 
+import com.github.dockerjava.api.model.Container;
 import com.github.dockerjava.api.model.HealthCheck;
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
@@ -46,12 +47,12 @@ public class DockerForKaravan {
     DockerService dockerService;
 
     public void runProjectInDevMode(String projectId, String jBangOptions, 
Map<Integer, Integer> ports, Map<String, String> files) throws Exception {
-        createDevmodeContainer(projectId, jBangOptions, ports);
+        Container c = createDevmodeContainer(projectId, jBangOptions, ports);
         dockerService.runContainer(projectId);
-        dockerService.copyFiles(projectId, "/code", files);
+        dockerService.copyFiles(c.getId(), "/code", files);
     }
 
-    protected void createDevmodeContainer(String projectId, String 
jBangOptions, Map<Integer, Integer> ports) throws InterruptedException {
+    protected Container 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,101 +62,27 @@ public class DockerForKaravan {
                 ? List.of(ENV_VAR_JBANG_OPTIONS + "=" + jBangOptions)
                 : List.of();
 
-        dockerService.createContainer(projectId, devmodeImage,
+        return dockerService.createContainer(projectId, devmodeImage,
                 env, ports, healthCheck,
                 Map.of(LABEL_TYPE, 
ContainerStatus.ContainerType.devmode.name(), LABEL_PROJECT_ID, projectId),
                 Map.of());
 
-        LOGGER.infof("DevMode started for %s", projectId);
     }
 
-    public void runBuildProject(String projectId, String script, Map<String, 
String> files) throws Exception {
-//        createBuildContainer(projectId, jBangOptions, ports);
-//        dockerService.runContainer(projectId);
-//        dockerService.copyFiles(projectId, "/code", files);
-
-
-        //        String scriptName = "camel-main-builder-script-docker.sh";
-//        String script = getResourceFile("/scripts/" + scriptName);
-//        try {
-//            CreateContainerResponse res = 
dockerClient.createContainerCmd("test")
-//                    .withName("xxx")
-//                    
.withImage("ghcr.io/apache/camel-karavan-devmode:4.0.0-RC2")
-//                    .withCmd("/karavan/"+scriptName)
-//                    .withEnv("GIT_BRANCH=main",
-//                            
"GIT_REPOSITORY=http://gitea:3000/karavan/karavan.git";,
-//                            "GIT_USERNAME=karavan",
-//                            "GIT_PASSWORD=karavan",
-//                            "PROJECT_ID=zzzzz",
-//                            "CAMEL_VERSION=4.0.0",
-//                            "IMAGE_REGISTRY=registry:5000",
-//                            "IMAGE_GROUP=karavan")
-//                    .withHostConfig(new 
HostConfig().withNetworkMode("karavan")).exec();
-//
-//        } catch (Exception e) {
-//            System.out.println(e.getMessage());
-//        }
-//
-//        Container c = 
dockerClient.listContainersCmd().withShowAll(true).withNameFilter(Collections.singleton("xxx")).exec().get(0);
-//
-//        String temp = 
Vertx.vertx().fileSystem().createTempDirectoryBlocking("xxx");
-//        String path = temp + File.separator + scriptName;
-//        Vertx.vertx().fileSystem().writeFileBlocking(path, 
Buffer.buffer(script));
-//
-//        try (ByteArrayOutputStream byteArrayOutputStream = new 
ByteArrayOutputStream();
-//                TarArchiveOutputStream tarArchive = new 
TarArchiveOutputStream(byteArrayOutputStream)) {
-//            
tarArchive.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
-//            
tarArchive.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
-//
-//            TarArchiveEntry tarEntry = new TarArchiveEntry(new File(path));
-//            tarEntry.setName(scriptName);
-//            tarEntry.setMode(0700); //
-//            tarArchive.putArchiveEntry(tarEntry);
-//            IOUtils.write(Files.readAllBytes(Paths.get(path)), tarArchive);
-//            tarArchive.closeArchiveEntry();
-//            tarArchive.finish();
-//
-//            dockerClient.copyArchiveToContainerCmd(c.getId())
-//                    .withTarInputStream(new 
ByteArrayInputStream(byteArrayOutputStream.toByteArray()))
-//                    .withRemotePath("/karavan").exec();
-//        } catch (Exception e) {
-//            e.printStackTrace();
-//        }
-
-//        dockerClient.startContainerCmd(c.getId()).exec();
+    public void runBuildProject(String projectId, String script, Map<String, 
String> files, List<String> env) throws Exception {
+        Container c = createBuildContainer(projectId, env);
+        dockerService.copyFiles(c.getId(), "/code", files);
+        dockerService.copyExecFile(c.getId(), "/karavan", "build.sh", script);
+        dockerService.runContainer(projectId);
     }
 
-
-    protected void createBuildContainer(String projectId, Map<Integer, 
Integer> ports) throws InterruptedException {
+    protected Container createBuildContainer(String projectId, List<String> 
env) throws InterruptedException {
         LOGGER.infof("Starting Build Container for %s ", projectId);
 
-        List<String> env = List.of(
-                "GIT_REPOSITORY=http://gitea:3000/karavan/karavan.git";,
-                            "GIT_USERNAME=karavan",
-                            "GIT_PASSWORD=karavan",
-                            "PROJECT_ID=" + projectId,
-                            "CAMEL_VERSION=4.0.0",
-                            "IMAGE_REGISTRY=registry:5000",
-                            "IMAGE_GROUP=karavan"
-        );
-
-//                    .withCmd("/karavan/"+scriptName)
-//                    .withEnv("GIT_BRANCH=main",
-//                            
"GIT_REPOSITORY=http://gitea:3000/karavan/karavan.git";,
-//                            "GIT_USERNAME=karavan",
-//                            "GIT_PASSWORD=karavan",
-//                            "PROJECT_ID=zzzzz",
-//                            "CAMEL_VERSION=4.0.0",
-//                            "IMAGE_REGISTRY=registry:5000",
-//                            "IMAGE_GROUP=karavan")
-//                    .withHostConfig(new 
HostConfig().withNetworkMode("karavan")).exec();
-
-        dockerService.createContainer(projectId + "-builder", devmodeImage,
-                env, ports, new HealthCheck(),
-                Map.of(LABEL_TYPE, 
ContainerStatus.ContainerType.devmode.name(), LABEL_PROJECT_ID, projectId),
-                Map.of());
-
-        LOGGER.infof("Build Container started for %s", projectId);
+        return dockerService.createContainer(projectId + BUILDER_SUFFIX, 
devmodeImage,
+                env, Map.of(), new HealthCheck(),
+                Map.of(LABEL_TYPE, ContainerStatus.ContainerType.build.name(), 
LABEL_PROJECT_ID, projectId),
+                Map.of(), "/karavan/build.sh");
     }
 
     public void createDevserviceContainer(DockerComposeService 
dockerComposeService) throws InterruptedException {
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 04ef2043..fcceccd2 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
@@ -24,7 +24,6 @@ import com.github.dockerjava.core.DefaultDockerClientConfig;
 import com.github.dockerjava.core.DockerClientConfig;
 import com.github.dockerjava.core.DockerClientImpl;
 import com.github.dockerjava.core.InvocationBuilder;
-import com.github.dockerjava.core.util.CompressArchiveUtil;
 import com.github.dockerjava.transport.DockerHttpClient;
 import com.github.dockerjava.zerodep.ZerodepDockerHttpClient;
 import io.vertx.core.Vertx;
@@ -33,16 +32,14 @@ import jakarta.enterprise.context.ApplicationScoped;
 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.apache.commons.io.IOUtils;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.jboss.logging.Logger;
 
 import java.io.*;
-import java.nio.file.LinkOption;
+import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.time.Instant;
 import java.util.*;
@@ -82,21 +79,21 @@ public class DockerService extends DockerServiceUtils {
     public List<ContainerStatus> collectContainersStatuses() {
         List<ContainerStatus> result = new ArrayList<>();
         
getDockerClient().listContainersCmd().withShowAll(true).exec().forEach(container
 -> {
-            ContainerStatus containerStatus = getContainerStatus(container);
-            Statistics stats = getContainerStats(container.getId());
-            updateStatistics(containerStatus, container, stats);
+            ContainerStatus containerStatus = getContainerStatus(container, 
environment);
             result.add(containerStatus);
         });
         return result;
     }
 
-    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);
+    public List<ContainerStatus> collectContainersStatistics() {
+        List<ContainerStatus> result = new ArrayList<>();
+        
getDockerClient().listContainersCmd().withShowAll(true).exec().forEach(container
 -> {
+            ContainerStatus containerStatus = getContainerStatus(container, 
environment);
+            Statistics stats = getContainerStats(container.getId());
+            updateStatistics(containerStatus, container, stats);
+            result.add(containerStatus);
+        });
+        return result;
     }
 
     public void startListeners() {
@@ -165,7 +162,7 @@ public class DockerService extends DockerServiceUtils {
 
     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 {
+                                     Map<String, String> volumes, String... 
command) throws InterruptedException {
         List<Container> containers = 
getDockerClient().listContainersCmd().withShowAll(true).withNameFilter(List.of(name)).exec();
         if (containers.size() == 0) {
             pullImage(image);
@@ -181,7 +178,10 @@ public class DockerService extends DockerServiceUtils {
                     mounts.add(new 
Mount().withType(MountType.BIND).withSource(hostPath).withTarget(containerPath));
                 });
             }
-//            createContainerCmd.withExposedPorts(exposedPorts);
+            if (command.length > 0) {
+                createContainerCmd.withCmd(command);
+            }
+
             createContainerCmd.withHostConfig(new HostConfig()
                     .withPortBindings(portBindings)
                             .withMounts(mounts)
@@ -244,13 +244,40 @@ public class DockerService extends DockerServiceUtils {
         dockerClient.execStartCmd(id).exec(callBack).awaitCompletion();
     }
 
-    public void copyFiles(String containerId, String containerPath, 
Map<String, String> files) throws IOException {
+    public void copyFiles(String containerId, String containerPath, 
Map<String, String> files) {
             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 copyExecFile(String containerId, String containerPath, String 
filename, String script) {
+        String temp = 
vertx.fileSystem().createTempDirectoryBlocking(containerId);
+        String path = temp + File.separator + filename;
+        vertx.fileSystem().writeFileBlocking(path, Buffer.buffer(script));
+
+        try (ByteArrayOutputStream byteArrayOutputStream = new 
ByteArrayOutputStream();
+                TarArchiveOutputStream tarArchive = new 
TarArchiveOutputStream(byteArrayOutputStream)) {
+            tarArchive.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
+            
tarArchive.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
+
+            TarArchiveEntry tarEntry = new TarArchiveEntry(new File(path));
+            tarEntry.setName(filename);
+            tarEntry.setMode(0700);
+            tarArchive.putArchiveEntry(tarEntry);
+            IOUtils.write(Files.readAllBytes(Paths.get(path)), tarArchive);
+            tarArchive.closeArchiveEntry();
+            tarArchive.finish();
+
+            dockerClient.copyArchiveToContainerCmd(containerId)
+                    .withTarInputStream(new 
ByteArrayInputStream(byteArrayOutputStream.toByteArray()))
+                    .withRemotePath(containerPath).exec();
+        } catch (Exception e) {
+            LOGGER.error(e.getMessage());
+            e.printStackTrace();
+        }
+    }
+
     private void addFile(String temp, String fileName, String code) {
         try {
             String path = temp + File.separator + fileName;
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 0b8b698f..91ae2cf9 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
@@ -18,18 +18,13 @@ package org.apache.camel.karavan.docker;
 
 import com.github.dockerjava.api.model.*;
 import io.smallrye.mutiny.tuples.Tuple2;
-import io.vertx.core.json.JsonArray;
-import io.vertx.core.json.JsonObject;
 import org.apache.camel.karavan.api.KameletResources;
-import org.apache.camel.karavan.docker.model.DockerComposeService;
 import org.apache.camel.karavan.docker.model.HealthCheckConfig;
 import org.apache.camel.karavan.infinispan.model.ContainerStatus;
-import org.apache.camel.karavan.service.CodeService;
-import org.yaml.snakeyaml.Yaml;
 
-import jakarta.inject.Inject;
-
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.text.DecimalFormat;
 import java.time.Duration;
 import java.time.Instant;
@@ -38,6 +33,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
+import static org.apache.camel.karavan.shared.Constants.LABEL_PROJECT_ID;
 import static org.apache.camel.karavan.shared.Constants.LABEL_TYPE;
 
 public class DockerServiceUtils {
@@ -47,17 +43,14 @@ public class DockerServiceUtils {
     protected static final DecimalFormat formatGiB = new DecimalFormat("0.00");
     protected static final Map<String, Tuple2<Long, Long>> previousStats = new 
ConcurrentHashMap<>();
 
-    @Inject
-    CodeService codeService;
-
-
     protected ContainerStatus getContainerStatus(Container container, String 
environment) {
         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);
+        String projectId = 
container.getLabels().getOrDefault(LABEL_PROJECT_ID, name);
+        return ContainerStatus.createWithId(projectId, name, environment, 
container.getId(), container.getImage(), ports, type, commands, 
container.getState(), created);
     }
 
     protected void updateStatistics(ContainerStatus containerStatus, Container 
container, Statistics stats) {
@@ -141,6 +134,8 @@ public class DockerServiceUtils {
             return ContainerStatus.ContainerType.project;
         } else if (Objects.equals(type, 
ContainerStatus.ContainerType.internal.name())) {
             return ContainerStatus.ContainerType.internal;
+        } else if (Objects.equals(type, 
ContainerStatus.ContainerType.build.name())) {
+            return ContainerStatus.ContainerType.build;
         }
         return ContainerStatus.ContainerType.unknown;
     }
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
index 5f48425d..cda9603d 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
@@ -17,9 +17,7 @@
 package org.apache.camel.karavan.infinispan;
 
 import io.smallrye.mutiny.tuples.Tuple2;
-import io.vertx.core.eventbus.EventBus;
 import jakarta.enterprise.inject.Default;
-import jakarta.inject.Inject;
 import jakarta.inject.Singleton;
 import org.apache.camel.karavan.infinispan.model.*;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
@@ -60,9 +58,6 @@ public class InfinispanService implements HealthCheck {
     @ConfigProperty(name = "karavan.infinispan.password")
     String infinispanPassword;
 
-    @Inject
-    EventBus eventBus;
-
     private RemoteCache<GroupedKey, Project> projects;
     private RemoteCache<GroupedKey, ProjectFile> files;
     private RemoteCache<GroupedKey, PipelineStatus> pipelineStatuses;
@@ -71,7 +66,6 @@ public class InfinispanService implements HealthCheck {
     private RemoteCache<GroupedKey, Boolean> transits;
     private RemoteCache<GroupedKey, ServiceStatus> serviceStatuses;
     private RemoteCache<GroupedKey, CamelStatus> camelStatuses;
-    private RemoteCache<String, String> commits;
     private final AtomicBoolean ready = new AtomicBoolean(false);
 
     private RemoteCacheManager cacheManager;
@@ -111,7 +105,6 @@ public class InfinispanService implements HealthCheck {
             deploymentStatuses = getOrCreateCache(DeploymentStatus.CACHE);
             serviceStatuses = getOrCreateCache(ServiceStatus.CACHE);
             camelStatuses = getOrCreateCache(CamelStatus.CACHE);
-            commits = getOrCreateCache("commits");
             transits = getOrCreateCache("transits");
             deploymentStatuses = getOrCreateCache(DeploymentStatus.CACHE);
 
@@ -349,25 +342,6 @@ public class InfinispanService implements HealthCheck {
         });
     }
 
-    public void saveCommit(String commitId, int time) {
-        commits.put(commitId, String.valueOf(time));
-    }
-
-    public void saveLastCommit(String commitId) {
-        commits.put("lastCommitId", commitId);
-    }
-
-    public Tuple2<String, Integer> getLastCommit() {
-        String lastCommitId = commits.get("lastCommitId");
-        String time = commits.get(lastCommitId);
-        return Tuple2.of(lastCommitId, Integer.parseInt(time));
-    }
-
-    public boolean hasCommit(String commitId) {
-        return commits.get(commitId) != null;
-    }
-
-
     public List<ContainerStatus> getLoadedDevModeStatuses() {
         QueryFactory queryFactory = Search.getQueryFactory(containerStatuses);
         return queryFactory.<ContainerStatus>create("FROM 
karavan.ContainerStatus WHERE type = :type AND codeLoaded = true")
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/model/CommitInfo.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/model/CommitInfo.java
deleted file mode 100644
index b7ee2075..00000000
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/model/CommitInfo.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.apache.camel.karavan.infinispan.model;
-
-import java.util.List;
-
-public class CommitInfo {
-    private String commitId;
-    private Integer time;
-    private List<GitRepo> repos;
-
-    public CommitInfo(String commitId, Integer time) {
-        this.commitId = commitId;
-        this.time = time;
-    }
-
-    public CommitInfo(String commitId, Integer time, List<GitRepo> repos) {
-        this.commitId = commitId;
-        this.time = time;
-        this.repos = repos;
-    }
-
-    public String getCommitId() {
-        return commitId;
-    }
-
-    public void setCommitId(String commitId) {
-        this.commitId = commitId;
-    }
-
-    public Integer getTime() {
-        return time;
-    }
-
-    public void setTime(Integer time) {
-        this.time = time;
-    }
-
-    public List<GitRepo> getRepos() {
-        return repos;
-    }
-
-    public void setRepos(List<GitRepo> repos) {
-        this.repos = repos;
-    }
-
-    @Override
-    public String toString() {
-        return "CommitInfo{" +
-                "commitId='" + commitId + '\'' +
-                ", time=" + time +
-                ", repos=" + repos +
-                '}';
-    }
-}
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
index c9336b8e..ab7c04b8 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
@@ -23,7 +23,8 @@ public class ContainerStatus {
         @ProtoEnumValue(number = 1, name = "devmode") devmode,
         @ProtoEnumValue(number = 2, name = "devservice") devservice,
         @ProtoEnumValue(number = 4, name = "project") project,
-        @ProtoEnumValue(number = 5, name = "unknown") unknown,
+        @ProtoEnumValue(number = 5, name = "build") build,
+        @ProtoEnumValue(number = 6, name = "unknown") unknown,
     }
 
     public enum Command {
@@ -55,16 +56,18 @@ public class ContainerStatus {
     @ProtoField(number = 10)
     String created;
     @ProtoField(number = 11)
-    List<Command> commands;
+    String finished;
     @ProtoField(number = 12)
-    String state;
+    List<Command> commands;
     @ProtoField(number = 13)
-    Boolean codeLoaded;
+    String state;
     @ProtoField(number = 14)
+    Boolean codeLoaded;
+    @ProtoField(number = 15)
     Boolean inTransit = false;
 
     @ProtoFactory
-    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) {
+    public ContainerStatus(String projectId, String containerName, String 
containerId, String image, List<Integer> ports, String env, ContainerType type, 
String memoryInfo, String cpuInfo, String created, String finished, 
List<Command> commands, String state, Boolean codeLoaded, Boolean inTransit) {
         this.projectId = projectId;
         this.containerName = containerName;
         this.containerId = containerId;
@@ -75,6 +78,7 @@ public class ContainerStatus {
         this.memoryInfo = memoryInfo;
         this.cpuInfo = cpuInfo;
         this.created = created;
+        this.finished = finished;
         this.commands = commands;
         this.state = state;
         this.codeLoaded = codeLoaded;
@@ -102,16 +106,16 @@ public class ContainerStatus {
     }
 
     public static ContainerStatus createDevMode(String projectId, String env) {
-        return new ContainerStatus(projectId, projectId, null, null, null, 
env, ContainerType.devmode, null, null, null, List.of(Command.run), null, 
false, false);
+        return new ContainerStatus(projectId, projectId, null, null, null, 
env, ContainerType.devmode, null, null, null, null, List.of(Command.run), null, 
false, false);
     }
 
     public static ContainerStatus createByType(String name, String env, 
ContainerType type) {
-        return new ContainerStatus(name, name, null, null, null, env, type, 
null, null, null, List.of(Command.run), null, false, false);
+        return new ContainerStatus(name, name, null, null, null, env, type, 
null, 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, ContainerType type, 
List<Command> commands, String status, String created) {
-        return new ContainerStatus(name, name, containerId, image, ports, env, 
type,
-                null, null, created,  commands, status, false, false);
+    public static ContainerStatus createWithId(String projectId, String 
containerName, String env, String containerId, String image, List<Integer> 
ports, ContainerType type, List<Command> commands, String status, String 
created) {
+        return new ContainerStatus(projectId, containerName, containerId, 
image, ports, env, type,
+                null, null, created, null,  commands, status, false, false);
     }
 
     public ContainerStatus() {
@@ -230,6 +234,14 @@ public class ContainerStatus {
         this.inTransit = inTransit;
     }
 
+    public String getFinished() {
+        return finished;
+    }
+
+    public void setFinished(String finished) {
+        this.finished = finished;
+    }
+
     @Override
     public String toString() {
         return "ContainerStatus{" +
@@ -243,10 +255,11 @@ public class ContainerStatus {
                 ", memoryInfo='" + memoryInfo + '\'' +
                 ", cpuInfo='" + cpuInfo + '\'' +
                 ", created='" + created + '\'' +
+                ", finished='" + finished + '\'' +
                 ", commands=" + commands +
                 ", state='" + state + '\'' +
                 ", codeLoaded=" + codeLoaded +
-                ", logging=" + inTransit +
+                ", inTransit=" + inTransit +
                 '}';
     }
 }
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 594be91b..f1e02e2a 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
@@ -58,6 +58,7 @@ 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 BUILDER_SCRIPT_FILE_SUFFIX = "builder-script-";
     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/";
@@ -123,6 +124,7 @@ public class CodeService {
 
         List<String> files = new ArrayList<>(interfaces);
         files.addAll(targets.stream().map(target -> target + "-" + 
APPLICATION_PROPERTIES_FILENAME).toList());
+        files.addAll(targets.stream().map(target -> BUILDER_SCRIPT_FILE_SUFFIX 
+ target + ".sh").toList());
 
         runtimes.forEach(runtime -> {
             files.forEach(file -> {
@@ -137,23 +139,6 @@ public class CodeService {
         return result;
     }
 
-    public Map<String, String> getPipelinesTemplates() {
-        Map<String, String> result = new HashMap<>();
-
-        List<String> files = new ArrayList<>(targets);
-        files.addAll(targets.stream().map(target -> target + 
".yaml").collect(Collectors.toList()));
-
-        runtimes.forEach(runtime -> {
-            files.forEach(file -> {
-                String templateName = runtime + "-" + file;
-                String templatePath = "/pipelines/" + templateName;
-                String templateText = getResourceFile(templatePath);
-                result.put(templateName, templateText);
-            });
-        });
-        return result;
-    }
-
     public Map<String, String> getServices() {
         Map<String, String> result = new HashMap<>();
         String templateText = getResourceFile("/services/" + 
DEV_SERVICES_FILENAME);
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 e873d25f..c74dde56 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
@@ -11,6 +11,7 @@ import 
org.apache.camel.karavan.infinispan.model.ContainerStatus;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.jboss.logging.Logger;
 
+import java.time.Instant;
 import java.util.Objects;
 
 import static org.apache.camel.karavan.shared.EventType.CONTAINER_STATUS;
@@ -79,10 +80,23 @@ public class EventService {
         if (infinispanService.isReady()) {
             ContainerStatus newStatus = data.mapTo(ContainerStatus.class);
             ContainerStatus oldStatus = 
infinispanService.getContainerStatus(newStatus.getProjectId(), 
newStatus.getEnv(), newStatus.getContainerName());
-            if (oldStatus == null || Objects.equals(oldStatus.getInTransit(), 
Boolean.FALSE)) {
+            if (oldStatus == null) {
+                infinispanService.saveContainerStatus(newStatus);
+            } else if (Objects.equals(oldStatus.getInTransit(), 
Boolean.FALSE)) {
+                if ("exited".equalsIgnoreCase(newStatus.getState()) && 
oldStatus.getFinished() == null) {
+                    newStatus.setFinished(Instant.now().toString());
+                }
+                if (newStatus.getCpuInfo() == null) {
+                    newStatus.setCpuInfo(oldStatus.getCpuInfo());
+                    newStatus.setMemoryInfo(oldStatus.getMemoryInfo());
+                }
                 infinispanService.saveContainerStatus(newStatus);
             } else if (Objects.equals(oldStatus.getInTransit(), Boolean.TRUE)) 
{
                 if (!Objects.equals(oldStatus.getState(), 
newStatus.getState())) {
+                    if (newStatus.getCpuInfo() == null) {
+                        newStatus.setCpuInfo(oldStatus.getCpuInfo());
+                        newStatus.setMemoryInfo(oldStatus.getMemoryInfo());
+                    }
                     infinispanService.saveContainerStatus(newStatus);
                 }
             }
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java
index 7cf3c243..399a52f7 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java
@@ -83,44 +83,6 @@ public class GitService {
         return gitForImport;
     }
 
-    public List<CommitInfo> getAllCommits() {
-        List<CommitInfo> result = new ArrayList<>();
-        try {
-            Git pollGit = getGitForImport();
-            if (pollGit != null) {
-                StreamSupport.stream(pollGit.log().all().call().spliterator(), 
false)
-                        
.sorted(Comparator.comparingInt(RevCommit::getCommitTime))
-                        .forEach(commit -> result.add(new 
CommitInfo(commit.getName(), commit.getCommitTime())));
-            }
-        } catch (Exception e) {
-            LOGGER.error(e.getMessage());
-        }
-        return result;
-    }
-
-    public List<CommitInfo> getCommitsAfterCommit(int commitTime) {
-        List<CommitInfo> result = new ArrayList<>();
-        try {
-            Git pollGit = getGitForImport();
-            if (pollGit != null) {
-                GitConfig gitConfig = getGitConfig();
-                CredentialsProvider cred = new 
UsernamePasswordCredentialsProvider(gitConfig.getUsername(), 
gitConfig.getPassword());
-                pull(pollGit, cred);
-                List<RevCommit> commits = 
StreamSupport.stream(pollGit.log().all().call().spliterator(), false)
-                        .filter(commit -> commit.getCommitTime() > commitTime)
-                        
.sorted(Comparator.comparingInt(RevCommit::getCommitTime)).collect(Collectors.toList());
-                for (RevCommit commit: commits) {
-                    List<String> projects = new 
ArrayList<>(getChangedProjects(commit));
-                    List<GitRepo> repo = readProjectsFromRepository(pollGit, 
projects.toArray(new String[projects.size()]));
-                    result.add(new CommitInfo(commit.getName(), 
commit.getCommitTime(), repo));
-                }
-            }
-        } catch (Exception e) {
-            LOGGER.error(e.getMessage());
-        }
-        return result;
-    }
-
     public GitConfig getGitConfig() {
         String propertiesPrefix = "karavan.";
         String branch = ConfigProvider.getConfig().getValue(propertiesPrefix + 
"git-branch", String.class);
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 8740d724..84f8ee0d 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
@@ -19,8 +19,6 @@ 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;
@@ -61,8 +59,20 @@ public class ProjectService implements HealthCheck {
     @ConfigProperty(name = "karavan.environment")
     String environment;
 
-    @ConfigProperty(name = "karavan.git-pull-interval")
-    String gitPullInterval;
+    @ConfigProperty(name = "karavan.git-install-gitea")
+    boolean installGitea;
+
+    @ConfigProperty(name = "karavan.image-registry-install-registry")
+    boolean installRegistry;
+
+    @ConfigProperty(name = "karavan.image-registry")
+    String registry;
+    @ConfigProperty(name = "karavan.image-group")
+    String group;
+    @ConfigProperty(name = "karavan.image-registry-username")
+    Optional<String> username;
+    @ConfigProperty(name = "karavan.image-registry-password")
+    Optional<String> password;
 
     @Inject
     InfinispanService infinispanService;
@@ -83,7 +93,6 @@ public class ProjectService implements HealthCheck {
     EventBus eventBus;
 
     private AtomicBoolean ready = new AtomicBoolean(false);
-    private AtomicBoolean readyToPull = new AtomicBoolean(false);
 
     @Override
     public HealthCheckResponse call() {
@@ -128,27 +137,33 @@ public class ProjectService implements HealthCheck {
             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());
 
             String templateName = project.getRuntime() + 
"-builder-script-docker.sh";
             String script = codeService.getTemplateText(templateName);
 
-            GitConfig gitConfig = gitService.getGitConfig();
-            List<String> env = List.of(
-                    "GIT_REPOSITORY=" + gitConfig.getUri(),
-                    "GIT_USERNAME=" + gitConfig.getUsername(),
-                    "GIT_PASSWORD=" + gitConfig.getPassword(),
-                    "PROJECT_ID=" + project.getProjectId(),
-                    "IMAGE_REGISTRY=registry:5000",
-                    "IMAGE_GROUP=karavan"
-            );
-
-            dockerForKaravan.runBuildProject(project.getProjectId(), script, 
files);
+            List<String> env = getEnvForBuild(project);
+
+            dockerForKaravan.runBuildProject(project.getProjectId(), script, 
files, env);
             return project.getProjectId();
         }
     }
 
+    private List<String> getEnvForBuild(Project project) {
+        GitConfig gitConfig = gitService.getGitConfig();
+        List<String> env = List.of(
+                "GIT_REPOSITORY=" + (installGitea ? 
gitConfig.getUri().replace("localhost", "gitea") : gitConfig.getUri()),
+                "GIT_USERNAME=" + gitConfig.getUsername(),
+                "GIT_PASSWORD=" + gitConfig.getPassword(),
+                "GIT_BRANCH=" + gitConfig.getBranch(),
+                "PROJECT_ID=" + project.getProjectId(),
+                "IMAGE_REGISTRY=" + (installRegistry ? "registry:5000" : 
registry),
+                "IMAGE_REGISTRY_USERNAME=" + username,
+                "IMAGE_REGISTRY_PASSWORD=" + password,
+                "IMAGE_GROUP=" + group
+        );
+        return env;
+    }
+
     public Project save(Project project) throws Exception {
         boolean isNew = infinispanService.getProject(project.getProjectId()) 
== null;
         infinispanService.saveProject(project);
@@ -168,33 +183,6 @@ public class ProjectService implements HealthCheck {
         return codeService.getProjectPort(composeFile);
     }
 
-    public void pullCommits() {
-        if (readyToPull.get()) {
-            LOGGER.info("Pull commits...");
-            Tuple2<String, Integer> lastCommit = 
infinispanService.getLastCommit();
-            
gitService.getCommitsAfterCommit(lastCommit.getItem2()).forEach(commitInfo -> {
-                if (!infinispanService.hasCommit(commitInfo.getCommitId())) {
-                    commitInfo.getRepos().forEach(repo -> {
-                        Project project = importProjectFromRepo(repo);
-                        kubernetesService.createPipelineRun(project);
-                    });
-                    infinispanService.saveCommit(commitInfo.getCommitId(), 
commitInfo.getTime());
-                }
-                infinispanService.saveLastCommit(commitInfo.getCommitId());
-            });
-        }
-    }
-
-    void importCommits() {
-        LOGGER.info("Import commits...");
-        gitService.getAllCommits().forEach(commitInfo -> {
-            System.out.println(commitInfo.getCommitId() + " " + 
Instant.ofEpochSecond(commitInfo.getTime()).toString());
-            infinispanService.saveCommit(commitInfo.getCommitId(), 
commitInfo.getTime());
-            infinispanService.saveLastCommit(commitInfo.getCommitId());
-        });
-        readyToPull.set(true);
-    }
-
     @Retry(maxRetries = 100, delay = 2000)
     public void tryStart() throws Exception {
         if (infinispanService.isReady() && gitService.checkGit()) {
@@ -204,9 +192,6 @@ public class ProjectService implements HealthCheck {
             addKameletsProject();
             addTemplatesProject();
             addServicesProject();
-            if (!Objects.equals("disabled", gitPullInterval.toLowerCase()) && 
!Objects.equals("off", gitPullInterval.toLowerCase())) {
-                importCommits();
-            }
             ready.set(true);
         } else {
             LOGGER.info("Projects are not ready");
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 b026d21d..c851a93c 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
@@ -59,11 +59,30 @@ public class ScheduledService {
     @Inject
     EventBus eventBus;
 
+    @Scheduled(every = "{karavan.container.statistics.interval}", 
concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
+    void collectContainersStatistics() {
+        if (infinispanService.isReady()) {
+            List<ContainerStatus> statusesInDocker = 
dockerService.collectContainersStatistics();
+            statusesInDocker.forEach(containerStatus -> {
+                eventBus.send(EventType.CONTAINER_STATUS, 
JsonObject.mapFrom(containerStatus));
+            });
+        }
+    }
+
     @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());
+            statusesInDocker.forEach(containerStatus -> {
+                eventBus.send(EventType.CONTAINER_STATUS, 
JsonObject.mapFrom(containerStatus));
+            });
+            cleanContainersStatuses(statusesInDocker);
+        }
+    }
+
+    void cleanContainersStatuses(List<ContainerStatus> statusesInDocker) {
+        if (infinispanService.isReady()) {
+            List<String> namesInDocker = 
statusesInDocker.stream().map(ContainerStatus::getContainerName).toList();
             List<ContainerStatus> statusesInInfinispan = 
infinispanService.getContainerStatuses(environment);
             // clean deleted
             statusesInInfinispan.stream()
@@ -73,23 +92,12 @@ public class ScheduledService {
                         
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.camel.status.interval}", concurrentExecution 
= Scheduled.ConcurrentExecution.SKIP)
     void collectCamelStatuses() {
-        LOGGER.info("Collect info statuses");
         camelService.collectCamelStatuses();
     }
-
-    @Scheduled(every = "{karavan.git-pull-interval}", concurrentExecution = 
Scheduled.ConcurrentExecution.SKIP)
-    void pullCommitsFromGit() {
-        projectService.pullCommits();
-    }
-
 }
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java
index 0d6871bd..3b77038f 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java
@@ -25,5 +25,5 @@ public class Constants {
 
     public static final String RELOAD_TRY_COUNT = "reloadTryCount";
 
-    public static final String HEALTHY_STATUS = "healthy";
+    public static final String BUILDER_SUFFIX = "-builder";
 }
diff --git a/karavan-web/karavan-app/src/main/resources/application.properties 
b/karavan-web/karavan-app/src/main/resources/application.properties
index 2aeb804a..d8b07ccf 100644
--- a/karavan-web/karavan-app/src/main/resources/application.properties
+++ b/karavan-web/karavan-app/src/main/resources/application.properties
@@ -3,9 +3,9 @@ karavan.environment=dev
 karavan.environments=dev
 karavan.default-runtime=camel-main
 karavan.runtimes=camel-main,quarkus,spring-boot
-karavan.camel.status.interval=3s
-karavan.container.status.interval=3s
-karavan.container.infinispan.interval=5s
+karavan.camel.status.interval=2s
+karavan.container.status.interval=2s
+karavan.container.statistics.interval=10s
 karavan.devmode.image=ghcr.io/apache/camel-karavan-devmode:4.0.0-RC2
 
 # Git repository Configuration
@@ -14,7 +14,6 @@ karavan.git-username=karavan
 karavan.git-password=karavan
 karavan.git-branch=main
 karavan.git-install-gitea=true
-karavan.git-pull-interval=disabled
 
 # Image registry configuration
 karavan.image-registry=localhost:5555
diff --git 
a/karavan-web/karavan-app/src/main/resources/snippets/camel-main-builder-script-docker.sh
 
b/karavan-web/karavan-app/src/main/resources/snippets/camel-main-builder-script-docker.sh
index e28483e1..62b72584 100644
--- 
a/karavan-web/karavan-app/src/main/resources/snippets/camel-main-builder-script-docker.sh
+++ 
b/karavan-web/karavan-app/src/main/resources/snippets/camel-main-builder-script-docker.sh
@@ -7,7 +7,6 @@ then
     replacer=https://${GIT_USERNAME}:${GIT_PASSWORD}@
     prefix=https://
     url="${GIT_REPOSITORY/$prefix/$replacer}"
-    echo url
     git clone --depth 1 --branch ${GIT_BRANCH} $url ${CHECKOUT_DIR}
 elif [[ ${GIT_REPOSITORY} == http* ]] ;
 then
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 a8ab3c75..259f5420 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,6 +257,15 @@ export class KaravanApi {
         });
     }
 
+    static async buildProject(project: Project, environment: string, after: 
(res: AxiosResponse<any>) => void) {
+        instance.post('/api/project/build', project)
+            .then(res => {
+                after(res);
+            }).catch(err => {
+            after(err);
+        });
+    }
+
     static async getFiles(projectId: string, after: (files: ProjectFile[]) => 
void) {
         instance.get('/api/file/' + projectId)
             .then(res => {
@@ -382,8 +391,8 @@ export class KaravanApi {
         });
     }
 
-    static async stopPipelineRun(environment: string, pipelineRunName: string, 
after: (res: AxiosResponse<any>) => void) {
-        instance.delete('/api/infrastructure/pipelinerun/' + environment + "/" 
+ pipelineRunName)
+    static async stopBuild(environment: string, buildName: string, after: 
(res: AxiosResponse<any>) => void) {
+        instance.delete('/api/project/build/' + environment + "/" + buildName)
             .then(res => {
                 if (res.status === 200) {
                     after(res.data);
@@ -603,7 +612,7 @@ export class KaravanApi {
         });
     }
 
-    static async fetchData(type: 'container' | 'pipeline' | 'none', podName: 
string, controller: AbortController) {
+    static async fetchData(type: 'container' | 'build' | 'none', podName: 
string, controller: AbortController) {
         const fetchData = async () => {
             await fetchEventSource("/api/logwatch/" + type + "/" + podName, {
                 method: "GET",
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 ce19158a..6888c0b0 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
@@ -78,6 +78,7 @@ export class ContainerStatus {
     memoryInfo: string = '';
     cpuInfo: string = '';
     created: string = '';
+    finished: string = '';
     image: string = '';
     ports: [] = [];
     commands: string [] = [];
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 8290cbde..b8c064a5 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
@@ -273,8 +273,8 @@ interface LogState {
     setCurrentLine: (currentLine: number) => void;
     showLog: boolean,
     setShowLog: (showLog: boolean) => void;
-    type: 'container' | 'pipeline' | 'none',
-    setType: (type: 'container' | 'pipeline' | 'none') => void,
+    type: 'container' | 'build' | 'none',
+    setType: (type: 'container' | 'build' | 'none') => void,
 }
 
 export const useLogStore = createWithEqualityFn<LogState>((set) => ({
@@ -304,7 +304,7 @@ export const useLogStore = 
createWithEqualityFn<LogState>((set) => ({
         set(() => ({showLog: showLog}));
     },
     type: "none",
-    setType: (type: 'container' | 'pipeline' | 'none') =>  {
+    setType: (type: 'container' | 'build' | 'none') =>  {
         set((state: LogState) => ({type: type}));
     },
 }), shallow)
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 97fe15cc..4cc43eb9 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
@@ -104,7 +104,7 @@ export function ContainerTableRow (props: Props) {
                 <Td colSpan={2}>
                     <ExpandableRowContent>
                         <Flex direction={{default: "column"}} 
cellPadding={"0px"}>
-                            {container.containerId}
+                            {container.containerId.substring(0, 10)+"..."}
                         </Flex>
                     </ExpandableRowContent>
                 </Td>
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/project/build/BuildStatus.tsx 
b/karavan-web/karavan-app/src/main/webui/src/project/build/BuildStatus.tsx
index c926c257..b757ac75 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/build/BuildStatus.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/build/BuildStatus.tsx
@@ -31,11 +31,11 @@ export function BuildStatus (props: Props) {
     const [isBuilding, setIsBuilding] = useState<boolean>(false);
     const [isRolling, setIsRolling] = useState<boolean>(false);
     const [showDeleteConfirmation, setShowDeleteConfirmation] = 
useState<boolean>(false);
-    const [deleteEntityType, setDeleteEntityType] = useState<'pod' | 
'deployment' | 'pipelinerun'>('pod');
+    const [deleteEntityType, setDeleteEntityType] = useState<'pod' | 
'deployment' | 'build'>('pod');
     const [deleteEntityName, setDeleteEntityName] = useState<string>();
     const [deleteEntityEnv, setDeleteEntityEnv] = useState<string>();
 
-    function deleteEntity(type: 'pod' | 'deployment' | 'pipelinerun', name: 
string, environment: string) {
+    function deleteEntity(type: 'pod' | 'deployment' | 'build', name: string, 
environment: string) {
         switch (type) {
             case "deployment":
                 KaravanApi.deleteDeployment(environment, name, (res: any) => {
@@ -49,8 +49,8 @@ export function BuildStatus (props: Props) {
                     // onRefresh();
                 });
                 break;
-            case "pipelinerun":
-                KaravanApi.stopPipelineRun(environment, name, (res: any) => {
+            case "build":
+                KaravanApi.stopBuild(environment, name, (res: any) => {
                     // if (Array.isArray(res) && Array.from(res).length > 0)
                     // onRefresh();
                 });
@@ -60,7 +60,7 @@ export function BuildStatus (props: Props) {
 
     function build() {
         setIsBuilding(true);
-        KaravanApi.pipelineRun(project, env, res => {
+        KaravanApi.buildProject(project, env, res => {
             if (res.status === 200 || res.status === 201) {
                 setIsBuilding(false);
             } else {
@@ -151,7 +151,7 @@ export function BuildStatus (props: Props) {
     }
 
     function getPodsPanel(env: string) {
-        const podStatuses = containers.filter(d => d.projectId === 
project?.projectId);
+        const podStatuses = containers.filter(d => d.projectId === 
project?.projectId && d.type === 'project');
         return (
             <Flex justifyContent={{default: "justifyContentSpaceBetween"}}
                   alignItems={{default: "alignItemsFlexStart"}}>
@@ -221,18 +221,18 @@ export function BuildStatus (props: Props) {
                                    color={color}>
                                 {pipeline
                                     ? <Button className='labeled-button' 
variant="link" onClick={e =>
-                                        useLogStore.setState({showLog: true, 
type: 'pipeline', podName: pipeline})
+                                        useLogStore.setState({showLog: true, 
type: 'build', podName: pipeline})
                                     }>
                                         {pipeline}
                                     </Button>
-                                    : "No pipeline"}
-                                {isRunning && <Tooltip content={"Stop 
PipelineRun"}>
+                                    : "No builder"}
+                                {isRunning && <Tooltip content={"Stop build"}>
                                     <Button
                                         icon={<DeleteIcon/>}
                                         className="labeled-button"
                                         variant="link" onClick={e => {
                                         setShowDeleteConfirmation(true);
-                                        setDeleteEntityType("pipelinerun");
+                                        setDeleteEntityType("build");
                                         setDeleteEntityEnv(env);
                                         setDeleteEntityName(pipeline);
                                     }}></Button>
@@ -249,6 +249,56 @@ export function BuildStatus (props: Props) {
         )
     }
 
+    function getBuildState(env: string) {
+        const status = containers.filter(c => c.projectId === 
project.projectId).at(0);
+        const buildName = status?.containerName;
+        const state = status?.state;
+        let buildTime = 0;
+        if (status?.created) {
+            const start: Date = new Date(status.created);
+            const finish: Date = status.finished !== undefined && 
status.finished !== null ? new Date(status.finished) : new Date();
+            buildTime = Math.round((finish.getTime() - start.getTime()) / 
1000);
+        }
+        const showTime = buildTime && buildTime > 0;
+        const isRunning = state === 'running';
+        const isExited = state === 'exited';
+        const color = isExited ? "grey" : (isRunning ? "blue" : "grey");
+        const icon = isExited ? <UpIcon className="not-spinner"/> : <DownIcon 
className="not-spinner"/>
+        return (
+            <Flex justifyContent={{default: "justifyContentSpaceBetween"}} 
alignItems={{default: "alignItemsCenter"}}>
+                <FlexItem>
+                    <LabelGroup numLabels={2}>
+                        <Label icon={isRunning ? <Spinner diameter="16px" 
className="spinner"/> : icon}
+                               color={color}>
+                            {buildName
+                                ? <Button className='labeled-button' 
variant="link" onClick={e =>
+                                    useLogStore.setState({showLog: true, type: 
'build', podName: buildName})
+                                }>
+                                    {buildName}
+                                </Button>
+                                : "No builder"}
+                            {status !== undefined && <Tooltip content={"Delete 
build"}>
+                                <Button
+                                    icon={<DeleteIcon/>}
+                                    className="labeled-button"
+                                    variant="link" onClick={e => {
+                                    setShowDeleteConfirmation(true);
+                                    setDeleteEntityType("build");
+                                    setDeleteEntityEnv(env);
+                                    setDeleteEntityName(buildName);
+                                }}></Button>
+                            </Tooltip>}
+                        </Label>
+                        {buildName !== undefined && showTime === true && 
buildTime !== undefined &&
+                            <Label icon={<ClockIcon className="not-spinner"/>}
+                                   color={color}>{buildTime + "s"}</Label>}
+                    </LabelGroup>
+                </FlexItem>
+                <FlexItem>{env === "dev" && buildButton(env)}</FlexItem>
+            </Flex>
+        )
+    }
+
     function getDeleteConfirmation() {
         return (<Modal
             className="modal-delete"
@@ -267,7 +317,7 @@ export function BuildStatus (props: Props) {
                         onClick={e => 
setShowDeleteConfirmation(false)}>Cancel</Button>
             ]}
             onEscapePress={e => setShowDeleteConfirmation(false)}>
-            <div>{"Delete " + deleteEntity + " " + deleteEntityName + 
"?"}</div>
+            <div>{"Delete " + deleteEntityType + " " + deleteEntityName + 
"?"}</div>
         </Modal>)
     }
 
@@ -283,9 +333,9 @@ export function BuildStatus (props: Props) {
                         </DescriptionListDescription>
                     </DescriptionListGroup>
                     <DescriptionListGroup>
-                        <DescriptionListTerm>Pipeline</DescriptionListTerm>
+                        <DescriptionListTerm>Build 
container</DescriptionListTerm>
                         <DescriptionListDescription>
-                            {getPipelineState(env)}
+                            {getBuildState(env)}
                         </DescriptionListDescription>
                     </DescriptionListGroup>
                     <DescriptionListGroup>

Reply via email to