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);