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 6d4fd8f330567a09e093a74c298480774659cca7
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Tue Sep 12 11:34:03 2023 -0400

    Universal Containers UI for #885
---
 .../apache/camel/karavan/api/ImagesResource.java   |   4 +-
 .../apache/camel/karavan/docker/DockerService.java |   6 +-
 .../karavan/infinispan/model/ContainerStatus.java  |  19 ++-
 .../karavan/kubernetes/KubernetesService.java      |   8 +-
 .../camel/karavan/kubernetes/PodEventHandler.java  |   7 +-
 .../camel-main-docker-application.properties       |   2 +-
 .../camel-main-kubernetes-application.properties   |   2 +-
 .../camel-main-openshift-application.properties    |   2 +-
 .../spring-boot-kubernetes-application.properties  |   2 +-
 .../spring-boot-openshift-application.properties   |   2 +-
 .../src/main/webui/src/api/ProjectModels.ts        |   3 +-
 .../src/main/webui/src/dashboard/DashboardPage.tsx |   8 +-
 .../karavan-app/src/main/webui/src/main/Main.tsx   |   2 +-
 .../src/main/webui/src/project/ProjectPanel.tsx    |   5 +-
 .../main/webui/src/project/build/BuildPanel.tsx    |  50 +-------
 .../main/webui/src/project/build/ImagesPanel.tsx   |   1 -
 .../src/project/container/ContainerButtons.tsx     |   2 +-
 .../webui/src/project/container/ContainerPanel.tsx | 133 +++++++++++++--------
 .../src/project/container/DeploymentPanel.tsx      | 112 +++++++++++++++++
 .../src/project/container/ProjectContainerTab.tsx  |  28 ++++-
 .../main/webui/src/projects/ProjectsTableRow.tsx   |   2 +-
 .../main/webui/src/templates/TemplatesTableRow.tsx |  25 ----
 22 files changed, 274 insertions(+), 151 deletions(-)

diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ImagesResource.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ImagesResource.java
index 2c2fdf0e..4d613aaa 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ImagesResource.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ImagesResource.java
@@ -22,11 +22,13 @@ 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.registry.RegistryConfig;
 import org.apache.camel.karavan.service.ConfigService;
 import org.apache.camel.karavan.service.ProjectService;
 import org.apache.camel.karavan.registry.RegistryService;
 import org.jose4j.base64url.Base64;
 
+import java.io.IOException;
 import java.util.Comparator;
 import java.util.List;
 
@@ -46,7 +48,7 @@ public class ImagesResource {
     @Produces(MediaType.APPLICATION_JSON)
     @Path("/{projectId}")
     public List<String> getImagesForProject(@HeaderParam("username") String 
username,
-                                 @PathParam("projectId") String projectId) {
+                                 @PathParam("projectId") String projectId) 
throws IOException {
         String pattern = registryService.getRegistryWithGroup() + "/" + 
projectId;
         if (ConfigService.inKubernetes()) {
             return List.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 49398add..0afe21ea 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
@@ -402,10 +402,10 @@ public class DockerService extends DockerServiceUtils {
                 .map(image -> image.getRepoTags()[0]).toList();
     }
 
-    public List<String> getImages(String registryUrl, String registryUsername, 
String registryPassword, String pattern) throws IOException {
+    public List<String> getImages(String registryUrl, String registryUsername, 
String registryPassword) throws IOException {
         List<String> result = new ArrayList<>();
-        DockerClient client =  getDockerClient(registryUrl, registryUsername, 
registryPassword);
-        
getDockerClient().searchImagesCmd(pattern).exec().stream().map(searchItem -> 
searchItem.getName());
+        DockerClient client = getDockerClient(registryUrl, registryUsername, 
registryPassword);
+        client.listImagesCmd().withShowAll(true).exec().forEach(image -> 
result.add(image.getRepoTags()[0]));
         client.close();
         return result;
     }
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 e734abc4..4244db7a 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
@@ -63,14 +63,16 @@ public class ContainerStatus {
     @ProtoField(number = 13)
     String state;
     @ProtoField(number = 14)
-    Boolean codeLoaded;
+    String phase;
     @ProtoField(number = 15)
-    Boolean inTransit = false;
+    Boolean codeLoaded;
     @ProtoField(number = 16)
+    Boolean inTransit = false;
+    @ProtoField(number = 17)
     String initDate;
 
     @ProtoFactory
-    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, 
String initDate) {
+    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, String phase, Boolean codeLoaded, Boolean 
inTransit, String initDate) {
         this.projectId = projectId;
         this.containerName = containerName;
         this.containerId = containerId;
@@ -84,6 +86,7 @@ public class ContainerStatus {
         this.finished = finished;
         this.commands = commands;
         this.state = state;
+        this.phase = phase;
         this.codeLoaded = codeLoaded;
         this.inTransit = inTransit;
         this.initDate = initDate;
@@ -275,6 +278,14 @@ public class ContainerStatus {
         this.initDate = initDate;
     }
 
+    public String getPhase() {
+        return phase;
+    }
+
+    public void setPhase(String phase) {
+        this.phase = phase;
+    }
+
     @Override
     public String toString() {
         return "ContainerStatus{" +
@@ -291,8 +302,10 @@ public class ContainerStatus {
                 ", finished='" + finished + '\'' +
                 ", commands=" + commands +
                 ", state='" + state + '\'' +
+                ", status='" + phase + '\'' +
                 ", codeLoaded=" + codeLoaded +
                 ", inTransit=" + inTransit +
+                ", initDate='" + initDate + '\'' +
                 '}';
     }
 }
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/KubernetesService.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/KubernetesService.java
index b02bc045..ffd24a68 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/KubernetesService.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/KubernetesService.java
@@ -159,6 +159,7 @@ public class KubernetesService implements HealthCheck {
     private Map<String, String> getLabels(String name, Project project, 
ContainerStatus.ContainerType type) {
         Map<String, String> labels = new HashMap<>();
         labels.putAll(getRuntimeLabels());
+        labels.putAll(getPartOfLabels());
         labels.put("app.kubernetes.io/name", name);
         labels.put(LABEL_PROJECT_ID, project.getProjectId());
         labels.put(LABEL_PROJECT_RUNTIME, project.getRuntime());
@@ -170,11 +171,16 @@ public class KubernetesService implements HealthCheck {
 
     private Map<String, String> getRuntimeLabels() {
         Map<String, String> labels = new HashMap<>();
-        labels.put(LABEL_PART_OF, KARAVAN_PREFIX);
         labels.put(isOpenshift() ? "app.openshift.io/runtime" : 
"app.kubernetes.io/runtime", CAMEL_PREFIX);
         return labels;
     }
 
+    private Map<String, String> getPartOfLabels() {
+        Map<String, String> labels = new HashMap<>();
+        labels.put(LABEL_PART_OF, KARAVAN_PREFIX);
+        return labels;
+    }
+
 
     private ConfigMap getConfigMapForBuilder(String name, Map<String, String> 
labels) {
         return new ConfigMapBuilder()
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/PodEventHandler.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/PodEventHandler.java
index c94d14c1..fa03e433 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/PodEventHandler.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/PodEventHandler.java
@@ -78,8 +78,10 @@ public class PodEventHandler implements 
ResourceEventHandler<Pod> {
     public ContainerStatus getPodStatus(Pod pod) {
         String deployment = pod.getMetadata().getLabels().get("app");
         String projectId = deployment != null ? deployment : 
pod.getMetadata().getLabels().get(LABEL_PROJECT_ID);
-        String type = deployment != null ? deployment : 
pod.getMetadata().getLabels().get(LABEL_TYPE);
-        ContainerStatus.ContainerType containerType = type != null ? 
ContainerStatus.ContainerType.valueOf(type) : 
ContainerStatus.ContainerType.unknown;
+        String type = pod.getMetadata().getLabels().get(LABEL_TYPE);
+        ContainerStatus.ContainerType containerType = deployment != null
+                ? ContainerStatus.ContainerType.project
+                : (type != null ? ContainerStatus.ContainerType.valueOf(type) 
: ContainerStatus.ContainerType.unknown);
         try {
             boolean ready = 
pod.getStatus().getConditions().stream().anyMatch(c -> 
c.getType().equals("Ready") && c.getStatus().equals("True"));
             boolean running = Objects.equals(pod.getStatus().getPhase(), 
"Running");
@@ -105,6 +107,7 @@ public class PodEventHandler implements 
ResourceEventHandler<Pod> {
                     requestCpu + " / " + limitCpu,
                     creationTimestamp);
             status.setContainerId(pod.getMetadata().getName());
+            status.setPhase(pod.getStatus().getPhase());
             if (ready) {
                 status.setState(ContainerStatus.State.running.name());
             } else if (failed) {
diff --git 
a/karavan-web/karavan-app/src/main/resources/snippets/camel-main-docker-application.properties
 
b/karavan-web/karavan-app/src/main/resources/snippets/camel-main-docker-application.properties
index 2ce5adb2..48b4fb35 100644
--- 
a/karavan-web/karavan-app/src/main/resources/snippets/camel-main-docker-application.properties
+++ 
b/karavan-web/karavan-app/src/main/resources/snippets/camel-main-docker-application.properties
@@ -13,5 +13,5 @@ camel.health.exposure-level=full
 camel.server.enabled=true
 camel.server.healthCheckEnabled=true
 camel.server.devConsoleEnabled=true
-jkube.version=1.13.1
+jkube.version=1.14.0
 
jib.from.image=gcr.io/distroless/java17@sha256:3a4ea21bd7b412b8b6ae61313b39337d8f03bb6844013810e8e4625d8c765edb
diff --git 
a/karavan-web/karavan-app/src/main/resources/snippets/camel-main-kubernetes-application.properties
 
b/karavan-web/karavan-app/src/main/resources/snippets/camel-main-kubernetes-application.properties
index 6f6a9a91..467d45e0 100644
--- 
a/karavan-web/karavan-app/src/main/resources/snippets/camel-main-kubernetes-application.properties
+++ 
b/karavan-web/karavan-app/src/main/resources/snippets/camel-main-kubernetes-application.properties
@@ -12,7 +12,7 @@ camel.server.healthCheckEnabled=true
 camel.server.devConsoleEnabled=true
 label.runtime=app.kubernetes.io/runtime
 
jib.from.image=gcr.io/distroless/java17@sha256:3a4ea21bd7b412b8b6ae61313b39337d8f03bb6844013810e8e4625d8c765edb
-jkube.version=1.13.1
+jkube.version=1.14.0
 jkube.skip.build=true
 jkube.namespace=default
 jkube.imagePullPolicy=IfNotPresent
diff --git 
a/karavan-web/karavan-app/src/main/resources/snippets/camel-main-openshift-application.properties
 
b/karavan-web/karavan-app/src/main/resources/snippets/camel-main-openshift-application.properties
index 6f6a9a91..467d45e0 100644
--- 
a/karavan-web/karavan-app/src/main/resources/snippets/camel-main-openshift-application.properties
+++ 
b/karavan-web/karavan-app/src/main/resources/snippets/camel-main-openshift-application.properties
@@ -12,7 +12,7 @@ camel.server.healthCheckEnabled=true
 camel.server.devConsoleEnabled=true
 label.runtime=app.kubernetes.io/runtime
 
jib.from.image=gcr.io/distroless/java17@sha256:3a4ea21bd7b412b8b6ae61313b39337d8f03bb6844013810e8e4625d8c765edb
-jkube.version=1.13.1
+jkube.version=1.14.0
 jkube.skip.build=true
 jkube.namespace=default
 jkube.imagePullPolicy=IfNotPresent
diff --git 
a/karavan-web/karavan-app/src/main/resources/snippets/spring-boot-kubernetes-application.properties
 
b/karavan-web/karavan-app/src/main/resources/snippets/spring-boot-kubernetes-application.properties
index 4035a1dc..21839b9d 100644
--- 
a/karavan-web/karavan-app/src/main/resources/snippets/spring-boot-kubernetes-application.properties
+++ 
b/karavan-web/karavan-app/src/main/resources/snippets/spring-boot-kubernetes-application.properties
@@ -13,7 +13,7 @@ management.health.probes.enabled=true
 management.health.livenessState.enabled=true
 management.health.readinessState.enabled=true
 management.endpoint.health.show-details=always
-jkube.version=1.13.1
+jkube.version=1.14.0
 jkube.build.strategy=jib
 jkube.imagePullPolicy=IfNotPresent
 jkube.enricher.jkube-controller.replicaCount=1
diff --git 
a/karavan-web/karavan-app/src/main/resources/snippets/spring-boot-openshift-application.properties
 
b/karavan-web/karavan-app/src/main/resources/snippets/spring-boot-openshift-application.properties
index 78aa6907..ebd7e368 100644
--- 
a/karavan-web/karavan-app/src/main/resources/snippets/spring-boot-openshift-application.properties
+++ 
b/karavan-web/karavan-app/src/main/resources/snippets/spring-boot-openshift-application.properties
@@ -13,7 +13,7 @@ management.health.probes.enabled=true
 management.health.livenessState.enabled=true
 management.health.readinessState.enabled=true
 management.endpoint.health.show-details=always
-jkube.version=1.13.1
+jkube.version=1.14.0
 jkube.build.strategy=jib
 jkube.imagePullPolicy=IfNotPresent
 jkube.enricher.jkube-controller.type=Deployment
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 e81b4202..61ad544a 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
@@ -44,7 +44,7 @@ export class Project {
 }
 
 export class DeploymentStatus {
-    name: string = '';
+    projectId: string = '';
     env: string = '';
     namespace: string = '';
     cluster: string = '';
@@ -69,6 +69,7 @@ export class ContainerStatus {
     containerName: string = '';
     containerId: string = '';
     state: string = '';
+    phase: string = '';
     deployment: string = '';
     projectId: string = '';
     env: string = '';
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/dashboard/DashboardPage.tsx 
b/karavan-web/karavan-app/src/main/webui/src/dashboard/DashboardPage.tsx
index 397758d2..0745dbaa 100644
--- a/karavan-web/karavan-app/src/main/webui/src/dashboard/DashboardPage.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/dashboard/DashboardPage.tsx
@@ -94,7 +94,7 @@ export function DashboardPage () {
     function getDeploymentEnvironments(name: string): [string, boolean] [] {
         return selectedEnv.map(e => {
             const env: string = e as string;
-            const dep = deployments.find(d => d.name === name && d.env === 
env);
+            const dep = deployments.find(d => d.projectId === name && d.env 
=== env);
             const deployed: boolean = dep !== undefined && dep.replicas > 0 && 
dep.replicas === dep.readyReplicas;
             return [env, deployed];
         });
@@ -103,7 +103,7 @@ export function DashboardPage () {
     function getDeploymentByEnvironments(name: string): [string, 
DeploymentStatus | undefined] [] {
         return selectedEnv.map(e => {
             const env: string = e as string;
-            const dep = deployments.find(d => d.name === name && d.env === 
env);
+            const dep = deployments.find(d => d.projectId === name && d.env 
=== env);
             return [env, dep];
         });
     }
@@ -187,7 +187,9 @@ export function DashboardPage () {
     }
 
     function getKubernetesTable() {
-        const deps = Array.from(new Set(deployments.filter(d => 
d.name.toLowerCase().includes(filter)).map(d => d.name)));
+        const deps = Array.from(new Set(deployments
+            .filter(d => d?.projectId?.toLowerCase().includes(filter))
+            .map(d => d.projectId)));
         return (
             <Table aria-label="Projects" variant={TableVariant.compact}>
                 <Thead>
diff --git a/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx 
b/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx
index 7ae54de6..36a0a734 100644
--- a/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx
@@ -34,7 +34,7 @@ import CheckIcon from 
"@patternfly/react-icons/dist/esm/icons/check-icon";
 
 export function Main() {
 
-    const [config, readiness] = useAppConfigStore((s) => [s.config, 
s.readiness], shallow)
+    const [readiness] = useAppConfigStore((s) => [s.readiness], shallow)
     const {getData, getStatuses} = useMainHook();
 
     const initialized = useRef(false)
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 6838dbf1..437ce91b 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
@@ -5,7 +5,7 @@ import {
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {FilesTab} from "./files/FilesTab";
-import {useProjectStore} from "../api/ProjectStore";
+import {useAppConfigStore, useProjectStore} from "../api/ProjectStore";
 import {DashboardTab} from "./dashboard/DashboardTab";
 import {TraceTab} from "./trace/TraceTab";
 import {ProjectBuildTab} from "./build/ProjectBuildTab";
@@ -17,6 +17,7 @@ import {ProjectContainerTab} from 
"./container/ProjectContainerTab";
 
 export function ProjectPanel () {
 
+    const [config] = useAppConfigStore((state) => [state.config], shallow)
     const [project,tab, setTab] = useProjectStore((s) => [s.project, 
s.tabIndex, s.setTabIndex], shallow );
 
     useEffect(() => {
@@ -53,7 +54,7 @@ export function ProjectPanel () {
                         {tab === 'dashboard' && project && <DashboardTab/>}
                         {tab === 'trace' && project && <TraceTab/>}
                         {tab === 'build' && <ProjectBuildTab/>}
-                        {tab === 'build' && <ImagesPanel/>}
+                        {tab === 'build' && config.infrastructure !== 
'kubernetes' && <ImagesPanel/>}
                         {tab === 'container' && <ProjectContainerTab/>}
                     </>
                 }
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/project/build/BuildPanel.tsx 
b/karavan-web/karavan-app/src/main/webui/src/project/build/BuildPanel.tsx
index 409ad63c..e950ff00 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/build/BuildPanel.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/build/BuildPanel.tsx
@@ -34,13 +34,9 @@ export function BuildPanel () {
     );
 
     function deleteEntity() {
-        if (deleteEntityName) {
-            KaravanApi.stopBuild('dev', deleteEntityName, (res: any) => {
-                if (res.status === 200) {
-                    EventBus.sendAlert("Build deleted", "Build deleted: " + 
deleteEntityName, 'info');
-                }
-            });
-        }
+        KaravanApi.manageContainer(config.environment, 'project', 
project.projectId, 'delete', res => {
+            setShowLog(false, 'container', undefined)
+        });
     }
 
     function build() {
@@ -71,46 +67,6 @@ export function BuildPanel () {
         </Tooltip>)
     }
 
-    function deleteDeploymentButton(env: string) {
-        return (<Tooltip content="Delete deployment" position={"left"}>
-            <Button size="sm" variant="secondary"
-                    className="project-button"
-                    icon={<DeleteIcon/>}
-                    onClick={e => {
-                        setShowDeleteConfirmation(true);
-                        setDeleteEntityName(project?.projectId);
-                    }}>
-                {"Delete"}
-            </Button>
-        </Tooltip>)
-    }
-
-    function getReplicasPanel(env: string) {
-        const deploymentStatus = deployments.find(d => d.name === 
project?.projectId);
-        const ok = (deploymentStatus && deploymentStatus?.readyReplicas > 0
-            && (deploymentStatus.unavailableReplicas === 0 || 
deploymentStatus.unavailableReplicas === undefined || 
deploymentStatus.unavailableReplicas === null)
-            && deploymentStatus?.replicas === deploymentStatus?.readyReplicas)
-        return (
-            <Flex justifyContent={{default: "justifyContentSpaceBetween"}} 
alignItems={{default: "alignItemsCenter"}}>
-                <FlexItem>
-                    {deploymentStatus && <LabelGroup numLabels={3}>
-                        <Tooltip content={"Ready Replicas / Replicas"} 
position={"left"}>
-                            <Label icon={ok ? <UpIcon/> : <DownIcon/>}
-                                   color={ok ? "green" : "grey"}>{"Replicas: " 
+ deploymentStatus.readyReplicas + " / " + deploymentStatus.replicas}</Label>
-                        </Tooltip>
-                        {deploymentStatus.unavailableReplicas > 0 &&
-                            <Tooltip content={"Unavailable replicas"} 
position={"right"}>
-                                <Label icon={<DownIcon/>} 
color={"red"}>{deploymentStatus.unavailableReplicas}</Label>
-                            </Tooltip>
-                        }
-                    </LabelGroup>}
-                    {deploymentStatus === undefined && <Label 
icon={<DownIcon/>} color={"grey"}>No deployments</Label>}
-                </FlexItem>
-                <FlexItem>{env === "dev" && 
deleteDeploymentButton(env)}</FlexItem>
-            </Flex>
-        )
-    }
-
     function getBuildState() {
         const status = containers.filter(c => c.projectId === 
project.projectId && c.type === 'build').at(0);
         const buildName = status?.containerName;
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/project/build/ImagesPanel.tsx 
b/karavan-web/karavan-app/src/main/webui/src/project/build/ImagesPanel.tsx
index 31fc581d..f16752a5 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/build/ImagesPanel.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/build/ImagesPanel.tsx
@@ -22,7 +22,6 @@ import SetIcon from 
"@patternfly/react-icons/dist/esm/icons/check-icon";
 import {KaravanApi} from "../../api/KaravanApi";
 import {ProjectService} from "../../api/ProjectService";
 import {ServicesYaml} from "../../api/ServiceModels";
-import CopyIcon from "@patternfly/react-icons/dist/esm/icons/copy-icon";
 import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
 import {EventBus} from "../../designer/utils/EventBus";
 
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/project/container/ContainerButtons.tsx
 
b/karavan-web/karavan-app/src/main/webui/src/project/container/ContainerButtons.tsx
index 1fafb4d8..ed844ddc 100644
--- 
a/karavan-web/karavan-app/src/main/webui/src/project/container/ContainerButtons.tsx
+++ 
b/karavan-web/karavan-app/src/main/webui/src/project/container/ContainerButtons.tsx
@@ -76,7 +76,7 @@ export function ContainerButtons (props: Props) {
         <FlexItem>
             {(inTransit || isLoading) && <Spinner size="lg" 
aria-label="spinner"/>}
         </FlexItem>
-        {!isRunning && <FlexItem>
+        {!isRunning && config.infrastructure !== 'kubernetes' && <FlexItem>
             <Tooltip content="Run container" position={TooltipPosition.bottom}>
                 <Button size="sm"
                         isDisabled={(!(commands.length === 0) && 
!commands.includes('run')) || inTransit}
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/project/container/ContainerPanel.tsx
 
b/karavan-web/karavan-app/src/main/webui/src/project/container/ContainerPanel.tsx
index ec1fb5ab..9a4c2848 100644
--- 
a/karavan-web/karavan-app/src/main/webui/src/project/container/ContainerPanel.tsx
+++ 
b/karavan-web/karavan-app/src/main/webui/src/project/container/ContainerPanel.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, {useState} from 'react';
 import {
     Badge,
     Button,
@@ -11,17 +11,20 @@ import {
     Flex,
     FlexItem,
     Label,
-    LabelGroup,
+    LabelGroup, Modal,
     Tooltip,
     TooltipPosition
 } from '@patternfly/react-core';
 import '../../designer/karavan.css';
 import UpIcon from "@patternfly/react-icons/dist/esm/icons/running-icon";
 import DownIcon from 
"@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
-import {useLogStore, useProjectStore, useStatusesStore} from 
"../../api/ProjectStore";
+import {useAppConfigStore, useLogStore, useProjectStore, useStatusesStore} 
from "../../api/ProjectStore";
 import {shallow} from "zustand/shallow";
 import {ContainerStatus} from "../../api/ProjectModels";
 import {ContainerButtons} from "./ContainerButtons";
+import DeleteIcon from 
"@patternfly/react-icons/dist/esm/icons/times-circle-icon";
+import {KaravanApi} from "../../api/KaravanApi";
+import {EventBus} from "../../designer/utils/EventBus";
 
 interface Props {
     env: string,
@@ -29,62 +32,86 @@ interface Props {
 
 export function ContainerPanel (props: Props) {
 
+    const {config} = useAppConfigStore();
     const [project] = useProjectStore((s) => [s.project], shallow);
     const [setShowLog] = useLogStore((s) => [s.setShowLog], shallow);
-    const [containers, deployments, camels] =
-        useStatusesStore((s) => [s.containers, s.deployments, s.camels], 
shallow);
+    const [containers] = useStatusesStore((s) => [s.containers], shallow);
+    const [showDeleteConfirmation, setShowDeleteConfirmation] = 
useState<boolean>(false);
+    const [deleteEntityName, setDeleteEntityName] = useState<string>();
 
-    function getButtons() {
-        const env = props.env;
-        const conts = containers.filter(d => d.projectId === 
project?.projectId && d.type === 'project');
-        return (
-            <Flex justifyContent={{default: "justifyContentSpaceBetween"}}
-                  alignItems={{default: "alignItemsFlexStart"}}>
-                <FlexItem>
-                    {conts.length === 0 && <Label icon={<DownIcon/>} 
color={"grey"}>No pods</Label>}
-                    <LabelGroup numLabels={2} isVertical>
-                        {conts.map((pod: ContainerStatus) => {
-                                const ready = pod.state === 'running';
-                                return (
-                                    <Tooltip key={pod.containerName} 
content={pod.state} position={TooltipPosition.left}>
-                                        <Label icon={ready ? <UpIcon/> : 
<DownIcon/>} color={ready ? "green" : "grey"}>
-                                            <Button variant="link" 
className="labeled-button"
-                                                    onClick={e => {
-                                                        setShowLog(true, 
'container', pod.containerName);
-                                                    }}>
-                                                {pod.containerName}
-                                            </Button>
-                                        </Label>
-                                    </Tooltip>
-                                )
-                            }
-                        )}
-                    </LabelGroup>
-                </FlexItem>
-                <FlexItem>{env === "dev" && <ContainerButtons 
env={env}/>}</FlexItem>
-            </Flex>
-        )
+
+    function deleteEntity() {
+        if (deleteEntityName) {
+            KaravanApi.stopBuild('dev', deleteEntityName, (res: any) => {
+                if (res.status === 200) {
+                    EventBus.sendAlert("Container deleted", "Container 
deleted: " + deleteEntityName, 'info');
+                }
+            });
+        }
+    }
+
+    function getDeleteConfirmation() {
+        return (<Modal
+            className="modal-delete"
+            title="Confirmation"
+            isOpen={showDeleteConfirmation}
+            onClose={() => setShowDeleteConfirmation(false)}
+            actions={[
+                <Button key="confirm" variant="primary" onClick={e => {
+                    if (deleteEntityName) {
+                        deleteEntity();
+                        setShowDeleteConfirmation(false);
+                    }
+                }}>Delete
+                </Button>,
+                <Button key="cancel" variant="link"
+                        onClick={e => 
setShowDeleteConfirmation(false)}>Cancel</Button>
+            ]}
+            onEscapePress={e => setShowDeleteConfirmation(false)}>
+            <div>{"Delete container " + deleteEntityName + "?"}</div>
+        </Modal>)
+    }
+
+    function getBadge(cs: ContainerStatus) {
+        return config.infrastructure === 'kubernetes'
+            ? <Badge isRead>{cs.phase}</Badge>
+            : <Badge isRead>{cs.state}</Badge>
     }
 
     const env = props.env;
+    const conts = containers.filter(d => d.projectId === project?.projectId && 
d.type === 'project');
     return (
-        <Card className="project-status">
-            <CardBody>
-                <DescriptionList isHorizontal 
horizontalTermWidthModifier={{default: '20ch'}}>
-                    <DescriptionListGroup>
-                        <DescriptionListTerm>Environment</DescriptionListTerm>
-                        <DescriptionListDescription>
-                            <Badge className="badge">{env}</Badge>
-                        </DescriptionListDescription>
-                    </DescriptionListGroup>
-                    <DescriptionListGroup>
-                        <DescriptionListTerm>Containers</DescriptionListTerm>
-                        <DescriptionListDescription>
-                            {getButtons()}
-                        </DescriptionListDescription>
-                    </DescriptionListGroup>
-                </DescriptionList>
-            </CardBody>
-        </Card>
+        <Flex justifyContent={{default: "justifyContentSpaceBetween"}}
+              alignItems={{default: "alignItemsFlexStart"}}>
+            <FlexItem>
+                {conts.length === 0 && <Label icon={<DownIcon/>} 
color={"grey"}>No pods</Label>}
+                <LabelGroup numLabels={10} isVertical>
+                    {conts.map((cs: ContainerStatus) => {
+                            const ready = cs.state === 'running';
+                            return (
+                                <Label icon={ready ? <UpIcon/> : <DownIcon/>} 
color={ready ? "green" : "grey"}>
+                                    <Button variant="link" 
className="labeled-button"
+                                            onClick={e => {
+                                                setShowLog(true, 'container', 
cs.containerName);
+                                            }}>
+                                        {cs.containerName}
+                                    </Button>
+                                    {getBadge(cs)}
+                                    <Button
+                                        icon={<DeleteIcon/>}
+                                        className="labeled-button"
+                                        variant="link" onClick={e => {
+                                        setShowDeleteConfirmation(true);
+                                        setDeleteEntityName(cs.containerName);
+                                    }}></Button>
+                                </Label>
+                            )
+                        }
+                    )}
+                </LabelGroup>
+            </FlexItem>
+            <FlexItem>{env === "dev" && config.infrastructure !== 'kubernetes' 
&& <ContainerButtons env={env}/>}</FlexItem>
+            {showDeleteConfirmation && getDeleteConfirmation()}
+        </Flex>
     )
 }
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/project/container/DeploymentPanel.tsx
 
b/karavan-web/karavan-app/src/main/webui/src/project/container/DeploymentPanel.tsx
new file mode 100644
index 00000000..74f6ea59
--- /dev/null
+++ 
b/karavan-web/karavan-app/src/main/webui/src/project/container/DeploymentPanel.tsx
@@ -0,0 +1,112 @@
+import React, {useState} from 'react';
+import {
+    Badge,
+    Button,
+    Card,
+    CardBody,
+    DescriptionList,
+    DescriptionListDescription,
+    DescriptionListGroup,
+    DescriptionListTerm,
+    Flex,
+    FlexItem,
+    Label,
+    LabelGroup, Modal,
+    Tooltip,
+    TooltipPosition
+} from '@patternfly/react-core';
+import '../../designer/karavan.css';
+import UpIcon from "@patternfly/react-icons/dist/esm/icons/running-icon";
+import DownIcon from 
"@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
+import {useAppConfigStore, useLogStore, useProjectStore, useStatusesStore} 
from "../../api/ProjectStore";
+import {shallow} from "zustand/shallow";
+import {ContainerStatus} from "../../api/ProjectModels";
+import {ContainerButtons} from "./ContainerButtons";
+import DeleteIcon from 
"@patternfly/react-icons/dist/esm/icons/times-circle-icon";
+import {KaravanApi} from "../../api/KaravanApi";
+import {EventBus} from "../../designer/utils/EventBus";
+
+interface Props {
+    env: string,
+}
+
+export function DeploymentPanel (props: Props) {
+
+    const {config} = useAppConfigStore();
+    const [project] = useProjectStore((s) => [s.project], shallow);
+    const [ deployments] =
+        useStatusesStore((s) => [s.deployments], shallow);
+    const [showDeleteConfirmation, setShowDeleteConfirmation] = 
useState<boolean>(false);
+    const [deleteEntityName, setDeleteEntityName] = useState<string>();
+
+    function deleteEntity() {
+        if (deleteEntityName) {
+            KaravanApi.deleteDeployment(props.env, deleteEntityName, (res: 
any) => {
+                if (res.status === 200) {
+                    EventBus.sendAlert("Deployment deleted", "Deployment 
deleted: " + deleteEntityName, 'info');
+                }
+            });
+        }
+    }
+
+    function getDeleteConfirmation() {
+        return (<Modal
+            className="modal-delete"
+            title="Confirmation"
+            isOpen={showDeleteConfirmation}
+            onClose={() => setShowDeleteConfirmation(false)}
+            actions={[
+                <Button key="confirm" variant="primary" onClick={e => {
+                    if (deleteEntityName) {
+                        deleteEntity();
+                        setShowDeleteConfirmation(false);
+                    }
+                }}>Delete
+                </Button>,
+                <Button key="cancel" variant="link"
+                        onClick={e => 
setShowDeleteConfirmation(false)}>Cancel</Button>
+            ]}
+            onEscapePress={e => setShowDeleteConfirmation(false)}>
+            <div>{"Delete deployment " + deleteEntityName + "?"}</div>
+        </Modal>)
+    }
+
+    function deleteDeploymentButton() {
+        return (<Tooltip content="Delete deployment" position={"left"}>
+            <Button size="sm" variant="secondary"
+                    className="project-button"
+                    icon={<DeleteIcon/>}
+                    onClick={e => {
+                        setShowDeleteConfirmation(true);
+                        setDeleteEntityName(project?.projectId);
+                    }}>
+                {"Delete"}
+            </Button>
+        </Tooltip>)
+    }
+
+    const deploymentStatus = deployments.find(d => d.projectId === 
project?.projectId);
+    const ok = (deploymentStatus && deploymentStatus?.readyReplicas > 0
+        && (deploymentStatus.unavailableReplicas === 0 || 
deploymentStatus.unavailableReplicas === undefined || 
deploymentStatus.unavailableReplicas === null)
+        && deploymentStatus?.replicas === deploymentStatus?.readyReplicas)
+    return (
+        <Flex justifyContent={{default: "justifyContentSpaceBetween"}} 
alignItems={{default: "alignItemsCenter"}}>
+            <FlexItem>
+                {deploymentStatus && <LabelGroup numLabels={3}>
+                    <Tooltip content={"Ready Replicas / Replicas"} 
position={"left"}>
+                        <Label icon={ok ? <UpIcon/> : <DownIcon/>}
+                               color={ok ? "green" : "grey"}>{"Replicas: " + 
deploymentStatus.readyReplicas + " / " + deploymentStatus.replicas}</Label>
+                    </Tooltip>
+                    {deploymentStatus.unavailableReplicas > 0 &&
+                        <Tooltip content={"Unavailable replicas"} 
position={"right"}>
+                            <Label icon={<DownIcon/>} 
color={"red"}>{deploymentStatus.unavailableReplicas}</Label>
+                        </Tooltip>
+                    }
+                </LabelGroup>}
+                {deploymentStatus === undefined && <Label icon={<DownIcon/>} 
color={"grey"}>No deployments</Label>}
+            </FlexItem>
+            <FlexItem>{props.env === "dev" && 
deleteDeploymentButton()}</FlexItem>
+            {showDeleteConfirmation && getDeleteConfirmation()}
+        </Flex>
+    )
+}
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/project/container/ProjectContainerTab.tsx
 
b/karavan-web/karavan-app/src/main/webui/src/project/container/ProjectContainerTab.tsx
index 08eb587a..649f77cc 100644
--- 
a/karavan-web/karavan-app/src/main/webui/src/project/container/ProjectContainerTab.tsx
+++ 
b/karavan-web/karavan-app/src/main/webui/src/project/container/ProjectContainerTab.tsx
@@ -1,10 +1,13 @@
 import React from 'react';
 import {
+    Badge, Card,
+    CardBody, DescriptionList, DescriptionListDescription, 
DescriptionListGroup, DescriptionListTerm,
     PageSection
 } from '@patternfly/react-core';
 import '../../designer/karavan.css';
 import {useAppConfigStore} from "../../api/ProjectStore";
 import {ContainerPanel} from "./ContainerPanel";
+import {DeploymentPanel} from "./DeploymentPanel";
 
 export function ProjectContainerTab() {
 
@@ -14,7 +17,30 @@ export function ProjectContainerTab() {
         <PageSection className="project-tab-panel project-build-panel" 
padding={{default: "padding"}}>
             <div>
                 {config.environments.map(env =>
-                    <ContainerPanel key={env} env={env}/>
+                    <Card className="project-status">
+                        <CardBody>
+                            <DescriptionList isHorizontal 
horizontalTermWidthModifier={{default: '20ch'}}>
+                                <DescriptionListGroup>
+                                    
<DescriptionListTerm>Environment</DescriptionListTerm>
+                                    <DescriptionListDescription>
+                                        <Badge className="badge">{env}</Badge>
+                                    </DescriptionListDescription>
+                                </DescriptionListGroup>
+                                <DescriptionListGroup>
+                                    
<DescriptionListTerm>Containers</DescriptionListTerm>
+                                    <DescriptionListDescription>
+                                        <DeploymentPanel key={env} env={env}/>
+                                    </DescriptionListDescription>
+                                </DescriptionListGroup>
+                                <DescriptionListGroup>
+                                    
<DescriptionListTerm>Containers</DescriptionListTerm>
+                                    <DescriptionListDescription>
+                                        <ContainerPanel key={env} env={env}/>
+                                    </DescriptionListDescription>
+                                </DescriptionListGroup>
+                            </DescriptionList>
+                        </CardBody>
+                    </Card>
                 )}
             </div>
         </PageSection>
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx 
b/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
index 3944c6f7..c5bf9f9a 100644
--- a/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
@@ -39,7 +39,7 @@ export function ProjectsTableRow (props: Props) {
         return getEnvironments().map(e => {
             const env: string = e as string;
             const status = config.infrastructure === 'kubernetes'
-                ? deployments.find(d => d.name === name && d.env === env)
+                ? deployments.find(d => d.projectId === name && d.env === env)
                 : containers.find(d => d.containerName === name && d.env === 
env);
             return [env, status];
         });
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/templates/TemplatesTableRow.tsx 
b/karavan-web/karavan-app/src/main/webui/src/templates/TemplatesTableRow.tsx
index f288dc16..0f15f260 100644
--- a/karavan-web/karavan-app/src/main/webui/src/templates/TemplatesTableRow.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/templates/TemplatesTableRow.tsx
@@ -8,12 +8,9 @@ import '../designer/karavan.css';
 import { Td, Tr} from "@patternfly/react-table";
 import {Project} from '../api/ProjectModels';
 import {
-    useAppConfigStore,
     useLogStore,
-    useProjectStore, useStatusesStore,
 } from "../api/ProjectStore";
 import {shallow} from "zustand/shallow";
-import {CamelIcon, QuarkusIcon, SpringIcon} from 
"../designer/utils/KaravanIcons";
 import {useNavigate} from "react-router-dom";
 
 interface Props {
@@ -22,31 +19,9 @@ interface Props {
 
 export function TemplatesTableRow (props: Props) {
 
-    const [deployments, containers] = useStatusesStore((state) => 
[state.deployments, state.containers], shallow)
-    const {config} = useAppConfigStore();
-    const [setProject] = useProjectStore((state) => [state.setProject, 
state.setOperation], shallow);
     const [setShowLog] = useLogStore((state) => [state.setShowLog], shallow);
     const navigate = useNavigate();
 
-    function getEnvironments(): string [] {
-        return config.environments && Array.isArray(config.environments) ? 
Array.from(config.environments) : [];
-    }
-
-    function getStatusByEnvironments(name: string): [string, any] [] {
-        return getEnvironments().map(e => {
-            const env: string = e as string;
-            const status = config.infrastructure === 'kubernetes'
-                ? deployments.find(d => d.name === name && d.env === env)
-                : containers.find(d => d.containerName === name && d.env === 
env);
-            return [env, status];
-        });
-    }
-
-    function getIcon(runtime: string) {
-        if (runtime === 'quarkus') return QuarkusIcon();
-        else if (runtime === 'spring-boot') return SpringIcon();
-        else if (runtime === 'camel-main') return CamelIcon();
-    }
 
     const project = props.project;
     const isBuildIn = ['kamelets', 'templates'].includes(project.projectId);


Reply via email to