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 17e5855590bafd18dd80604d25389d2b852513b6 Author: Marat Gubaidullin <marat.gubaidul...@gmail.com> AuthorDate: Thu Oct 27 20:16:37 2022 -0400 First version of Dashboard --- .../camel/karavan/api/ConfigurationResource.java | 11 +- .../camel/karavan/api/KubernetesResource.java | 19 ++ .../apache/camel/karavan/api/StatusResource.java | 14 +- .../DeploymentEventHandler.java | 19 +- .../PipelineRunEventHandler.java | 2 +- .../{watcher => informer}/PodEventHandler.java | 8 +- .../karavan/informer/ServiceEventHandler.java | 91 +++++++ .../camel/karavan/model/DeploymentStatus.java | 29 ++- .../camel/karavan/model/ProjectStoreSchema.java | 2 +- .../apache/camel/karavan/model/ServiceStatus.java | 111 +++++++++ .../camel/karavan/service/InfinispanService.java | 25 +- .../camel/karavan/service/KubernetesService.java | 18 +- karavan-app/src/main/webapp/src/Main.tsx | 47 ++-- karavan-app/src/main/webapp/src/api/KaravanApi.tsx | 24 +- .../main/webapp/src/dashboard/DashboardPage.tsx | 268 +++++++++++++++++++++ karavan-app/src/main/webapp/src/index.css | 50 +++- .../main/webapp/src/projects/ProjectDashboard.tsx | 1 - .../src/main/webapp/src/projects/ProjectInfo.tsx | 3 - .../src/main/webapp/src/projects/ProjectModels.ts | 12 + .../src/main/webapp/src/projects/ProjectsPage.tsx | 21 +- 20 files changed, 712 insertions(+), 63 deletions(-) diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/api/ConfigurationResource.java b/karavan-app/src/main/java/org/apache/camel/karavan/api/ConfigurationResource.java index 7980e61..0c34dad 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/api/ConfigurationResource.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/ConfigurationResource.java @@ -50,7 +50,16 @@ public class ConfigurationResource { Map.of( "version", version, "environment", environment, - "environments", infinispanService.getEnvironments().stream().map(e -> e.getName()).collect(Collectors.toList()), + "environments", infinispanService.getEnvironments().stream() + .map(e -> e.getName()) + .sorted((o1, o2) -> { + if (o1.startsWith("dev") && o2.startsWith("test")) return -1; + if (o1.startsWith("test") && o2.startsWith("dev")) return 1; + if (o1.startsWith("test") && o2.startsWith("prod")) return -1; + if (o1.startsWith("prod") && o2.startsWith("test")) return 1; + return o1.compareTo(o2); + }) + .collect(Collectors.toList()), "runtime", runtime ) ).build(); diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/api/KubernetesResource.java b/karavan-app/src/main/java/org/apache/camel/karavan/api/KubernetesResource.java index ebe0cc1..d1553cb 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/api/KubernetesResource.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/KubernetesResource.java @@ -22,6 +22,7 @@ import io.vertx.mutiny.core.eventbus.Message; import org.apache.camel.karavan.model.DeploymentStatus; import org.apache.camel.karavan.model.PodStatus; import org.apache.camel.karavan.model.Project; +import org.apache.camel.karavan.model.ServiceStatus; import org.apache.camel.karavan.service.InfinispanService; import org.apache.camel.karavan.service.KubernetesService; import org.eclipse.microprofile.config.inject.ConfigProperty; @@ -98,6 +99,15 @@ public class KubernetesResource { return Response.ok(kubernetesService.getContainerLog(name, kubernetesService.getNamespace())).build(); } + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/deployment") + public List<DeploymentStatus> getAllDeploymentStatuses() throws Exception { + return infinispanService.getDeploymentStatuses().stream() + .sorted(Comparator.comparing(DeploymentStatus::getName)) + .collect(Collectors.toList()); + } + @GET @Produces(MediaType.APPLICATION_JSON) @Path("/deployment/{env}") @@ -128,6 +138,15 @@ public class KubernetesResource { return Response.ok().build(); } + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/service") + public List<ServiceStatus> getAllServiceStatuses() throws Exception { + return infinispanService.getServiceStatuses().stream() + .sorted(Comparator.comparing(ServiceStatus::getName)) + .collect(Collectors.toList()); + } + @GET @Produces(MediaType.APPLICATION_JSON) @Path("/pod/{env}") diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/api/StatusResource.java b/karavan-app/src/main/java/org/apache/camel/karavan/api/StatusResource.java index f869b26..5f88aec 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/api/StatusResource.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/StatusResource.java @@ -19,6 +19,7 @@ package org.apache.camel.karavan.api; import io.vertx.core.eventbus.EventBus; import org.apache.camel.karavan.model.CamelStatus; import org.apache.camel.karavan.model.DeploymentStatus; +import org.apache.camel.karavan.model.Environment; import org.apache.camel.karavan.model.PipelineStatus; import org.apache.camel.karavan.service.InfinispanService; import org.apache.camel.karavan.service.StatusService; @@ -31,6 +32,7 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import java.util.Optional; @Path("/api/status") public class StatusResource { @@ -59,12 +61,14 @@ public class StatusResource { @Produces(MediaType.APPLICATION_JSON) @Path("/deployment/{name}/{env}") public Response getDeploymentStatus(@PathParam("name") String name, @PathParam("env") String env) { - DeploymentStatus status = infinispanService.getDeploymentStatus(name, env); - if (status != null) { - return Response.ok(status).build(); - } else { - return Response.noContent().build(); + Optional<Environment> environment = infinispanService.getEnvironments().stream().filter(e -> e.getName().equals(env)).findFirst(); + if (environment.isPresent()){ + DeploymentStatus status = infinispanService.getDeploymentStatus(name, environment.get().getNamespace(), environment.get().getCluster()); + if (status != null) { + return Response.ok(status).build(); + } } + return Response.noContent().build(); } @GET diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/watcher/DeploymentEventHandler.java b/karavan-app/src/main/java/org/apache/camel/karavan/informer/DeploymentEventHandler.java similarity index 76% rename from karavan-app/src/main/java/org/apache/camel/karavan/watcher/DeploymentEventHandler.java rename to karavan-app/src/main/java/org/apache/camel/karavan/informer/DeploymentEventHandler.java index b4f4323..7ba4793 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/watcher/DeploymentEventHandler.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/informer/DeploymentEventHandler.java @@ -1,8 +1,9 @@ -package org.apache.camel.karavan.watcher; +package org.apache.camel.karavan.informer; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.client.informers.ResourceEventHandler; import org.apache.camel.karavan.model.DeploymentStatus; +import org.apache.camel.karavan.model.Environment; import org.apache.camel.karavan.service.InfinispanService; import org.apache.camel.karavan.service.KubernetesService; import org.jboss.logging.Logger; @@ -24,6 +25,14 @@ public class DeploymentEventHandler implements ResourceEventHandler<Deployment> LOGGER.info("onAdd " + deployment.getMetadata().getName()); DeploymentStatus ds = getDeploymentStatus(deployment); infinispanService.saveDeploymentStatus(ds); + // TODO: Delete after UI design + infinispanService.saveEnvironment(new Environment("test", "test", "karavan-test", "test-pipeline")); + infinispanService.saveEnvironment(new Environment("prod", "prod", "karavan-prod", "prod-pipeline")); + DeploymentStatus ds1 = getDeploymentStatus(deployment); + ds1.setEnv("test"); + ds1.setNamespace("karavan-test"); + ds1.setCluster("demo.cluster"); + infinispanService.saveDeploymentStatus(ds1); } catch (Exception e){ LOGGER.error(e.getMessage()); } @@ -35,6 +44,11 @@ public class DeploymentEventHandler implements ResourceEventHandler<Deployment> LOGGER.info("onUpdate " + newDeployment.getMetadata().getName()); DeploymentStatus ds = getDeploymentStatus(newDeployment); infinispanService.saveDeploymentStatus(ds); + // TODO: Delete after UI design + DeploymentStatus ds1 = getDeploymentStatus(newDeployment); + ds1.setEnv("test"); + ds1.setCluster("demo.cluster"); + infinispanService.saveDeploymentStatus(ds1); } catch (Exception e){ LOGGER.error(e.getMessage()); } @@ -47,6 +61,7 @@ public class DeploymentEventHandler implements ResourceEventHandler<Deployment> DeploymentStatus ds = new DeploymentStatus( deployment.getMetadata().getName(), deployment.getMetadata().getNamespace(), + kubernetesService.getCluster(), kubernetesService.environment); infinispanService.deleteDeploymentStatus(ds); } catch (Exception e){ @@ -65,6 +80,7 @@ public class DeploymentEventHandler implements ResourceEventHandler<Deployment> deployment.getMetadata().getName(), deployment.getMetadata().getNamespace(), kubernetesService.environment, + kubernetesService.getCluster(), imageName, deployment.getSpec().getReplicas(), deployment.getStatus().getReadyReplicas(), @@ -75,6 +91,7 @@ public class DeploymentEventHandler implements ResourceEventHandler<Deployment> return new DeploymentStatus( deployment.getMetadata().getName(), deployment.getMetadata().getNamespace(), + kubernetesService.getCluster(), kubernetesService.environment); } } diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/watcher/PipelineRunEventHandler.java b/karavan-app/src/main/java/org/apache/camel/karavan/informer/PipelineRunEventHandler.java similarity index 99% rename from karavan-app/src/main/java/org/apache/camel/karavan/watcher/PipelineRunEventHandler.java rename to karavan-app/src/main/java/org/apache/camel/karavan/informer/PipelineRunEventHandler.java index b5d59e9..3254e26 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/watcher/PipelineRunEventHandler.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/informer/PipelineRunEventHandler.java @@ -1,4 +1,4 @@ -package org.apache.camel.karavan.watcher; +package org.apache.camel.karavan.informer; import io.fabric8.kubernetes.client.informers.ResourceEventHandler; import io.fabric8.tekton.pipeline.v1beta1.PipelineRun; diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/watcher/PodEventHandler.java b/karavan-app/src/main/java/org/apache/camel/karavan/informer/PodEventHandler.java similarity index 91% rename from karavan-app/src/main/java/org/apache/camel/karavan/watcher/PodEventHandler.java rename to karavan-app/src/main/java/org/apache/camel/karavan/informer/PodEventHandler.java index 8d3af7c..4e68379 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/watcher/PodEventHandler.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/informer/PodEventHandler.java @@ -1,4 +1,4 @@ -package org.apache.camel.karavan.watcher; +package org.apache.camel.karavan.informer; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodCondition; @@ -27,6 +27,9 @@ public class PodEventHandler implements ResourceEventHandler<Pod> { LOGGER.info("onAdd " + pod.getMetadata().getName()); PodStatus ps = getPodStatus(pod); infinispanService.savePodStatus(ps); + // TODO: Delete after UI design + ps.setEnv("test"); + infinispanService.savePodStatus(ps); } catch (Exception e){ LOGGER.error(e.getMessage()); } @@ -38,6 +41,9 @@ public class PodEventHandler implements ResourceEventHandler<Pod> { LOGGER.info("onUpdate " + newPod.getMetadata().getName()); PodStatus ps = getPodStatus(newPod); infinispanService.savePodStatus(ps); + // TODO: Delete after UI design + ps.setEnv("test"); + infinispanService.savePodStatus(ps); } catch (Exception e){ LOGGER.error(e.getMessage()); } diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/informer/ServiceEventHandler.java b/karavan-app/src/main/java/org/apache/camel/karavan/informer/ServiceEventHandler.java new file mode 100644 index 0000000..dc7382a --- /dev/null +++ b/karavan-app/src/main/java/org/apache/camel/karavan/informer/ServiceEventHandler.java @@ -0,0 +1,91 @@ +package org.apache.camel.karavan.informer; + +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.client.informers.ResourceEventHandler; +import org.apache.camel.karavan.model.ServiceStatus; +import org.apache.camel.karavan.model.Environment; +import org.apache.camel.karavan.service.InfinispanService; +import org.apache.camel.karavan.service.KubernetesService; +import org.jboss.logging.Logger; + +public class ServiceEventHandler implements ResourceEventHandler<Service> { + + private static final Logger LOGGER = Logger.getLogger(ServiceEventHandler.class.getName()); + private InfinispanService infinispanService; + private KubernetesService kubernetesService; + + public ServiceEventHandler(InfinispanService infinispanService, KubernetesService kubernetesService) { + this.infinispanService = infinispanService; + this.kubernetesService = kubernetesService; + } + + @Override + public void onAdd(Service service) { + try { + LOGGER.info("onAdd " + service.getMetadata().getName()); + ServiceStatus ds = getServiceStatus(service); + infinispanService.saveServiceStatus(ds); + // TODO: Delete after UI design + infinispanService.saveEnvironment(new Environment("test", "demo", "karavan-test", "test-pipeline")); + ServiceStatus ds1 = getServiceStatus(service); + ds1.setEnv("test"); + ds1.setCluster("demo.cluster"); + infinispanService.saveServiceStatus(ds1); + } catch (Exception e){ + LOGGER.error(e.getMessage()); + } + } + + @Override + public void onUpdate(Service oldService, Service newService) { + try { + LOGGER.info("onUpdate " + newService.getMetadata().getName()); + ServiceStatus ds = getServiceStatus(newService); + infinispanService.saveServiceStatus(ds); + // TODO: Delete after UI design + ServiceStatus ds1 = getServiceStatus(newService); + ds1.setEnv("test"); + ds1.setCluster("demo.cluster"); + infinispanService.saveServiceStatus(ds1); + } catch (Exception e){ + LOGGER.error(e.getMessage()); + } + } + + @Override + public void onDelete(Service service, boolean deletedFinalStateUnknown) { + try { + LOGGER.info("onDelete " + service.getMetadata().getName()); + ServiceStatus ds = new ServiceStatus( + service.getMetadata().getName(), + service.getMetadata().getNamespace(), + kubernetesService.getCluster(), + kubernetesService.environment); + infinispanService.deleteServiceStatus(ds); + } catch (Exception e){ + LOGGER.error(e.getMessage()); + } + } + + public ServiceStatus getServiceStatus(Service service) { + try { + return new ServiceStatus( + service.getMetadata().getName(), + service.getMetadata().getNamespace(), + kubernetesService.environment, + kubernetesService.getCluster(), + service.getSpec().getPorts().get(0).getPort(), + service.getSpec().getPorts().get(0).getTargetPort().getIntVal(), + service.getSpec().getClusterIP(), + service.getSpec().getType() + ); + } catch (Exception ex) { + LOGGER.error(ex.getMessage()); + return new ServiceStatus( + service.getMetadata().getName(), + service.getMetadata().getNamespace(), + kubernetesService.getCluster(), + kubernetesService.environment); + } + } +} \ No newline at end of file diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/model/DeploymentStatus.java b/karavan-app/src/main/java/org/apache/camel/karavan/model/DeploymentStatus.java index f0baa9e..fe50512 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/model/DeploymentStatus.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/model/DeploymentStatus.java @@ -1,6 +1,5 @@ package org.apache.camel.karavan.model; -import org.infinispan.protostream.annotations.ProtoDoc; import org.infinispan.protostream.annotations.ProtoFactory; import org.infinispan.protostream.annotations.ProtoField; @@ -13,17 +12,20 @@ public class DeploymentStatus { @ProtoField(number = 3) String env; @ProtoField(number = 4) - String image; + String cluster; @ProtoField(number = 5) - Integer replicas; + String image; @ProtoField(number = 6) - Integer readyReplicas; + Integer replicas; @ProtoField(number = 7) + Integer readyReplicas; + @ProtoField(number = 8) Integer unavailableReplicas; - public DeploymentStatus(String name, String namespace, String env) { + public DeploymentStatus(String name, String namespace, String cluster, String env) { this.name = name; this.namespace = namespace; + this.cluster = cluster; this.env = env; this.image = ""; this.replicas = 0; @@ -32,16 +34,21 @@ public class DeploymentStatus { } @ProtoFactory - public DeploymentStatus(String name, String namespace, String env, String image, Integer replicas, Integer readyReplicas, Integer unavailableReplicas) { + public DeploymentStatus(String name, String namespace, String env, String cluster, String image, Integer replicas, Integer readyReplicas, Integer unavailableReplicas) { this.name = name; - this.env = env; this.namespace = namespace; + this.env = env; + this.cluster = cluster; this.image = image; this.replicas = replicas; this.readyReplicas = readyReplicas; this.unavailableReplicas = unavailableReplicas; } + public String getId() { + return name + ":" + namespace + ":" + cluster; + } + public String getName() { return name; } @@ -97,4 +104,12 @@ public class DeploymentStatus { public void setUnavailableReplicas(Integer unavailableReplicas) { this.unavailableReplicas = unavailableReplicas; } + + public String getCluster() { + return cluster; + } + + public void setCluster(String cluster) { + this.cluster = cluster; + } } diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectStoreSchema.java b/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectStoreSchema.java index 6a94007..c9b1e7a 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectStoreSchema.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectStoreSchema.java @@ -6,7 +6,7 @@ import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder; @AutoProtoSchemaBuilder( includeClasses = { GroupedKey.class, Project.class, ProjectFile.class, PipelineStatus.class, CamelStatus.class, DeploymentStatus.class, - PodStatus.class, Environment.class + PodStatus.class, Environment.class, ServiceStatus.class }, schemaPackageName = "karavan") public interface ProjectStoreSchema extends GeneratedSchema { diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/model/ServiceStatus.java b/karavan-app/src/main/java/org/apache/camel/karavan/model/ServiceStatus.java new file mode 100644 index 0000000..38dbde5 --- /dev/null +++ b/karavan-app/src/main/java/org/apache/camel/karavan/model/ServiceStatus.java @@ -0,0 +1,111 @@ +package org.apache.camel.karavan.model; + +import org.infinispan.protostream.annotations.ProtoFactory; +import org.infinispan.protostream.annotations.ProtoField; + +public class ServiceStatus { + public static final String CACHE = "service_statuses"; + @ProtoField(number = 1) + String name; + @ProtoField(number = 2) + String namespace; + @ProtoField(number = 3) + String env; + @ProtoField(number = 4) + String cluster; + @ProtoField(number = 5) + Integer port; + @ProtoField(number = 6) + Integer targetPort; + @ProtoField(number = 7) + String clusterIP; + @ProtoField(number = 8) + String type; + + @ProtoFactory + public ServiceStatus(String name, String namespace, String env, String cluster, Integer port, Integer targetPort, String clusterIP, String type) { + this.name = name; + this.namespace = namespace; + this.env = env; + this.cluster = cluster; + this.port = port; + this.targetPort = targetPort; + this.clusterIP = clusterIP; + this.type = type; + } + + public ServiceStatus(String name, String namespace, String cluster, String env) { + this.name = name; + this.namespace = namespace; + this.env = env; + this.cluster = cluster; + } + + public String getId() { + return name + ":" + namespace + ":" + cluster; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public String getEnv() { + return env; + } + + public void setEnv(String env) { + this.env = env; + } + + public String getCluster() { + return cluster; + } + + public void setCluster(String cluster) { + this.cluster = cluster; + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + public Integer getTargetPort() { + return targetPort; + } + + public void setTargetPort(Integer targetPort) { + this.targetPort = targetPort; + } + + public String getClusterIP() { + return clusterIP; + } + + public void setClusterIP(String clusterIP) { + this.clusterIP = clusterIP; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java index 3fddb8f..aa4026a 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java @@ -25,6 +25,7 @@ import org.apache.camel.karavan.model.PipelineStatus; import org.apache.camel.karavan.model.PodStatus; import org.apache.camel.karavan.model.Project; import org.apache.camel.karavan.model.ProjectFile; +import org.apache.camel.karavan.model.ServiceStatus; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.infinispan.Cache; import org.infinispan.client.hotrod.RemoteCache; @@ -56,6 +57,7 @@ public class InfinispanService { BasicCache<GroupedKey, DeploymentStatus> deploymentStatuses; BasicCache<GroupedKey, PodStatus> podStatuses; BasicCache<GroupedKey, CamelStatus> camelStatuses; + BasicCache<GroupedKey, ServiceStatus> serviceStatuses; BasicCache<String, String> kamelets; BasicCache<String, Environment> environments; @@ -95,6 +97,7 @@ public class InfinispanService { pipelineStatuses = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(PipelineStatus.CACHE, builder.build()); deploymentStatuses = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(DeploymentStatus.CACHE, builder.build()); podStatuses = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(PodStatus.CACHE, builder.build()); + serviceStatuses = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(ServiceStatus.CACHE, builder.build()); camelStatuses = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(CamelStatus.CACHE, builder.build()); kamelets = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE).getOrCreateCache(Kamelet.CACHE, builder.build()); @@ -107,6 +110,7 @@ public class InfinispanService { pipelineStatuses = cacheManager.administration().getOrCreateCache(PipelineStatus.CACHE, new XMLStringConfiguration(String.format(CACHE_CONFIG, PipelineStatus.CACHE))); deploymentStatuses = cacheManager.administration().getOrCreateCache(DeploymentStatus.CACHE, new XMLStringConfiguration(String.format(CACHE_CONFIG, DeploymentStatus.CACHE))); podStatuses = cacheManager.administration().getOrCreateCache(PodStatus.CACHE, new XMLStringConfiguration(String.format(CACHE_CONFIG, PodStatus.CACHE))); + serviceStatuses = cacheManager.administration().getOrCreateCache(ServiceStatus.CACHE, new XMLStringConfiguration(String.format(CACHE_CONFIG, ServiceStatus.CACHE))); camelStatuses = cacheManager.administration().getOrCreateCache(CamelStatus.CACHE, new XMLStringConfiguration(String.format(CACHE_CONFIG, CamelStatus.CACHE))); kamelets = cacheManager.administration().getOrCreateCache(Kamelet.CACHE, new XMLStringConfiguration(String.format(CACHE_CONFIG, Kamelet.CACHE))); } @@ -180,16 +184,17 @@ public class InfinispanService { pipelineStatuses.remove(GroupedKey.create(status.getProjectId(), status.getEnv())); } - public DeploymentStatus getDeploymentStatus(String name, String env) { - return deploymentStatuses.get(GroupedKey.create(name, env)); + public DeploymentStatus getDeploymentStatus(String name, String namespace, String cluster) { + String deploymentId = name + ":" + namespace + ":" + cluster; + return deploymentStatuses.get(GroupedKey.create(name, deploymentId)); } public void saveDeploymentStatus(DeploymentStatus status) { - deploymentStatuses.put(GroupedKey.create(status.getName(), status.getEnv()), status); + deploymentStatuses.put(GroupedKey.create(status.getName(), status.getId()), status); } public void deleteDeploymentStatus(DeploymentStatus status) { - deploymentStatuses.remove(GroupedKey.create(status.getName(), status.getEnv())); + deploymentStatuses.remove(GroupedKey.create(status.getName(), status.getId())); } public List<DeploymentStatus> getDeploymentStatuses() { @@ -210,6 +215,18 @@ public class InfinispanService { } } + public void saveServiceStatus(ServiceStatus status) { + serviceStatuses.put(GroupedKey.create(status.getName(), status.getId()), status); + } + + public void deleteServiceStatus(ServiceStatus status) { + serviceStatuses.remove(GroupedKey.create(status.getName(), status.getId())); + } + + public List<ServiceStatus> getServiceStatuses() { + return serviceStatuses.values().stream().collect(Collectors.toList()); + } + public List<PodStatus> getPodStatuses(String projectId, String env) { if (cacheManager == null) { QueryFactory queryFactory = org.infinispan.query.Search.getQueryFactory((Cache<?, ?>) podStatuses); diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java index b896013..c6fdea4 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java @@ -20,15 +20,12 @@ import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.client.DefaultKubernetesClient; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.Watch; -import io.fabric8.kubernetes.client.dsl.Informable; import io.fabric8.kubernetes.client.dsl.LogWatch; import io.fabric8.kubernetes.client.informers.SharedIndexInformer; -import io.fabric8.kubernetes.client.informers.SharedInformerEventListener; -import io.fabric8.kubernetes.client.informers.SharedInformerFactory; import io.fabric8.openshift.api.model.ImageStream; import io.fabric8.openshift.client.OpenShiftClient; import io.fabric8.tekton.client.DefaultTektonClient; @@ -40,19 +37,18 @@ import io.fabric8.tekton.pipeline.v1beta1.PipelineRunBuilder; import io.fabric8.tekton.pipeline.v1beta1.PipelineRunSpec; import io.fabric8.tekton.pipeline.v1beta1.PipelineRunSpecBuilder; import io.fabric8.tekton.pipeline.v1beta1.WorkspaceBindingBuilder; -import io.quarkus.runtime.ShutdownEvent; import io.quarkus.vertx.ConsumeEvent; import io.vertx.mutiny.core.eventbus.EventBus; +import org.apache.camel.karavan.informer.ServiceEventHandler; import org.apache.camel.karavan.model.PipelineRunLog; import org.apache.camel.karavan.model.Project; -import org.apache.camel.karavan.watcher.DeploymentEventHandler; -import org.apache.camel.karavan.watcher.PipelineRunEventHandler; -import org.apache.camel.karavan.watcher.PodEventHandler; +import org.apache.camel.karavan.informer.DeploymentEventHandler; +import org.apache.camel.karavan.informer.PipelineRunEventHandler; +import org.apache.camel.karavan.informer.PodEventHandler; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.logging.Logger; import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.event.Observes; import javax.enterprise.inject.Produces; import javax.inject.Inject; import java.io.IOException; @@ -110,6 +106,10 @@ public class KubernetesService { deploymentInformer.addEventHandlerWithResyncPeriod(new DeploymentEventHandler(infinispanService, this),30 * 1000L); informers.add(deploymentInformer); + SharedIndexInformer<Service> serviceInformer = kubernetesClient().services().inNamespace(getNamespace()).withLabel(runtimeLabel, "camel").inform(); + serviceInformer.addEventHandlerWithResyncPeriod(new ServiceEventHandler(infinispanService, this),30 * 1000L); + informers.add(serviceInformer); + SharedIndexInformer<PipelineRun> pipelineRunInformer = tektonClient().v1beta1().pipelineRuns().inNamespace(getNamespace()).withLabel(runtimeLabel, "camel").inform(); pipelineRunInformer.addEventHandlerWithResyncPeriod(new PipelineRunEventHandler(infinispanService, this),30 * 1000L); informers.add(pipelineRunInformer); diff --git a/karavan-app/src/main/webapp/src/Main.tsx b/karavan-app/src/main/webapp/src/Main.tsx index 4d7acb5..f69f8d8 100644 --- a/karavan-app/src/main/webapp/src/Main.tsx +++ b/karavan-app/src/main/webapp/src/Main.tsx @@ -1,9 +1,7 @@ import React from 'react'; import { Page, - ModalVariant, Button, - Modal, Alert, AlertActionCloseButton, Flex, @@ -13,7 +11,7 @@ import { } from '@patternfly/react-core'; import {KaravanApi} from "./api/KaravanApi"; import {SsoApi} from "./api/SsoApi"; -import {KameletApi, Kamelets} from "karavan-core/lib/api/KameletApi"; +import {KameletApi} from "karavan-core/lib/api/KameletApi"; import './designer/karavan.css'; import {ConfigurationPage} from "./config/ConfigurationPage"; import {KameletsPage} from "./kamelets/KameletsPage"; @@ -28,10 +26,12 @@ import {ProjectPage} from "./projects/ProjectPage"; import UserIcon from "@patternfly/react-icons/dist/js/icons/user-icon"; import ProjectsIcon from "@patternfly/react-icons/dist/js/icons/repository-icon"; import KameletsIcon from "@patternfly/react-icons/dist/js/icons/registry-icon"; +import DashboardIcon from "@patternfly/react-icons/dist/js/icons/tachometer-alt-icon"; import EipIcon from "@patternfly/react-icons/dist/js/icons/topology-icon"; import ComponentsIcon from "@patternfly/react-icons/dist/js/icons/module-icon"; import ConfigurationIcon from "@patternfly/react-icons/dist/js/icons/cogs-icon"; import {MainLogin} from "./MainLogin"; +import {DashboardPage} from "./dashboard/DashboardPage"; class ToastMessage { id: string = '' @@ -124,7 +124,7 @@ export class Main extends React.Component<Props, State> { getData() { KaravanApi.getConfiguration((config: any) => { - this.setState({ config: config }); + this.setState({config: config}); }); this.updateKamelets(); this.updateComponents(); @@ -157,16 +157,18 @@ export class Main extends React.Component<Props, State> { pageNav = () => { const pages: MenuItem[] = [ - // new MenuItem("dashboard", "Dashboard", <TachometerAltIcon/>), + new MenuItem("dashboard", "Dashboard", <DashboardIcon/>), new MenuItem("projects", "Projects", <ProjectsIcon/>), new MenuItem("eip", "Enterprise Integration Patterns", <EipIcon/>), new MenuItem("kamelets", "Kamelets", <KameletsIcon/>), new MenuItem("components", "Components", <ComponentsIcon/>), new MenuItem("configuration", "Configuration", <ConfigurationIcon/>) ] - return (<Flex className="nav-buttons" direction={{default: "column"}} style={{height:"100%"}} spaceItems={{default:"spaceItemsNone"}}> - <FlexItem alignSelf={{default:"alignSelfCenter"}}> - <Tooltip className="logo-tooltip" content={"Apache Camel Karavan " + this.state.config.version} position={"right"}> + return (<Flex className="nav-buttons" direction={{default: "column"}} style={{height: "100%"}} + spaceItems={{default: "spaceItemsNone"}}> + <FlexItem alignSelf={{default: "alignSelfCenter"}}> + <Tooltip className="logo-tooltip" content={"Apache Camel Karavan " + this.state.config.version} + position={"right"}> {Icon()} </Tooltip> </FlexItem> @@ -180,11 +182,11 @@ export class Main extends React.Component<Props, State> { </Tooltip> </FlexItem> )} - <FlexItem flex={{default:"flex_2"}} alignSelf={{default:"alignSelfCenter"}}> + <FlexItem flex={{default: "flex_2"}} alignSelf={{default: "alignSelfCenter"}}> <Divider/> </FlexItem> {KaravanApi.authType !== 'public' && - <FlexItem alignSelf={{default:"alignSelfCenter"}}> + <FlexItem alignSelf={{default: "alignSelfCenter"}}> <Popover aria-label="Current user" position={"right-end"} @@ -194,7 +196,7 @@ export class Main extends React.Component<Props, State> { shouldOpen={tip => this.setState({showUser: true})} headerContent={<div>{KaravanApi.me.userName}</div>} bodyContent={ - <Flex direction={{default:"row"}}> + <Flex direction={{default: "row"}}> {KaravanApi.me.roles && Array.isArray(KaravanApi.me.roles) && KaravanApi.me.roles .filter((r: string) => ['administrator', 'developer', 'viewer'].includes(r)) @@ -221,20 +223,28 @@ export class Main extends React.Component<Props, State> { getMain() { return ( <> - <Flex direction={{default:"row"}} style={{width: "100%", height:"100%"}} alignItems={{default:"alignItemsStretch"}} spaceItems={{ default: 'spaceItemsNone' }}> + <Flex direction={{default: "row"}} style={{width: "100%", height: "100%"}} + alignItems={{default: "alignItemsStretch"}} spaceItems={{default: 'spaceItemsNone'}}> <FlexItem> {this.pageNav()} </FlexItem> - <FlexItem flex={{default:"flex_2"}} style={{height:"100%"}}> + <FlexItem flex={{default: "flex_2"}} style={{height: "100%"}}> {this.state.pageId === 'projects' && <ProjectsPage key={this.state.request} onSelect={this.onProjectSelect} toast={this.toast} config={this.state.config}/>} - {this.state.pageId === 'project' && this.state.project && <ProjectPage project={this.state.project} config={this.state.config}/>} + {this.state.pageId === 'project' && this.state.project && + <ProjectPage project={this.state.project} config={this.state.config}/>} + {this.state.pageId === 'dashboard' && <DashboardPage key={this.state.request} + onSelect={this.onProjectSelect} + toast={this.toast} + config={this.state.config}/>} {this.state.pageId === 'configuration' && <ConfigurationPage/>} - {this.state.pageId === 'kamelets' && <KameletsPage dark={false} onRefresh={this.updateKamelets}/>} - {this.state.pageId === 'components' && <ComponentsPage dark={false} onRefresh={this.updateComponents}/>} + {this.state.pageId === 'kamelets' && + <KameletsPage dark={false} onRefresh={this.updateKamelets}/>} + {this.state.pageId === 'components' && + <ComponentsPage dark={false} onRefresh={this.updateComponents}/>} {this.state.pageId === 'eip' && <EipPage dark={false}/>} </FlexItem> </Flex> @@ -246,11 +256,12 @@ export class Main extends React.Component<Props, State> { return ( <Page className="karavan"> {KaravanApi.authType === undefined && <Bullseye className="loading-page"> - <Spinner className="spinner" isSVG diameter="140px" aria-label="Loading..." /> + <Spinner className="spinner" isSVG diameter="140px" aria-label="Loading..."/> <div className="logo-placeholder">{Icon()}</div> </Bullseye>} {(KaravanApi.isAuthorized || KaravanApi.authType === 'public') && this.getMain()} - {!KaravanApi.isAuthorized && KaravanApi.authType === 'basic' && <MainLogin config={this.state.config} onLogin={this.onLogin}/>} + {!KaravanApi.isAuthorized && KaravanApi.authType === 'basic' && + <MainLogin config={this.state.config} onLogin={this.onLogin}/>} {this.state.alerts.map((e: ToastMessage) => ( <Alert key={e.id} className="main-alert" variant={e.variant} title={e.title} timeout={e.variant === "success" ? 1000 : 2000} diff --git a/karavan-app/src/main/webapp/src/api/KaravanApi.tsx b/karavan-app/src/main/webapp/src/api/KaravanApi.tsx index ad3b9a6..b2cb1af 100644 --- a/karavan-app/src/main/webapp/src/api/KaravanApi.tsx +++ b/karavan-app/src/main/webapp/src/api/KaravanApi.tsx @@ -5,7 +5,7 @@ import { PipelineStatus, PodStatus, Project, - ProjectFile + ProjectFile, ServiceStatus } from "../projects/ProjectModels"; import {Buffer} from 'buffer'; import {SsoApi} from "./SsoApi"; @@ -297,6 +297,28 @@ export class KaravanApi { }); } + static async getAllServiceStatuses(after: (statuses: ServiceStatus[]) => void) { + instance.get('/api/kubernetes/service') + .then(res => { + if (res.status === 200) { + after(res.data); + } + }).catch(err => { + console.log(err); + }); + } + + static async getAllDeploymentStatuses(after: (statuses: DeploymentStatus[]) => void) { + instance.get('/api/kubernetes/deployment') + .then(res => { + if (res.status === 200) { + after(res.data); + } + }).catch(err => { + console.log(err); + }); + } + static async getDeploymentStatuses(env: string, after: (statuses: DeploymentStatus[]) => void) { instance.get('/api/kubernetes/deployment/' + env) .then(res => { diff --git a/karavan-app/src/main/webapp/src/dashboard/DashboardPage.tsx b/karavan-app/src/main/webapp/src/dashboard/DashboardPage.tsx new file mode 100644 index 0000000..d893bf7 --- /dev/null +++ b/karavan-app/src/main/webapp/src/dashboard/DashboardPage.tsx @@ -0,0 +1,268 @@ +import React from 'react'; +import { + Badge, + Button, + Flex, + FlexItem, HelperText, HelperTextItem, Label, LabelGroup, + OverflowMenu, + OverflowMenuContent, + OverflowMenuGroup, + OverflowMenuItem, + PageSection, + Text, + TextContent, + TextInput, + Toolbar, + ToolbarContent, + ToolbarItem, Tooltip +} from '@patternfly/react-core'; +import '../designer/karavan.css'; +import {MainToolbar} from "../MainToolbar"; +import RefreshIcon from '@patternfly/react-icons/dist/esm/icons/sync-alt-icon'; +import {DeploymentStatus, Project, ServiceStatus} from "../projects/ProjectModels"; +import {TableComposable, TableVariant, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table"; +import {camelIcon, CamelUi} from "../designer/utils/CamelUi"; +import {KaravanApi} from "../api/KaravanApi"; +import Icon from "../Logo"; +import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon"; +import DownIcon from "@patternfly/react-icons/dist/esm/icons/error-circle-o-icon"; + +interface Props { + config: any, + onSelect: (project: Project) => void + toast: (title: string, text: string, variant: 'success' | 'danger' | 'warning' | 'info' | 'default') => void +} + +interface State { + projects: Project[], + deploymentStatuses: DeploymentStatus[], + serviceStatuses: ServiceStatus[], + isCreateModalOpen: boolean, + isDeleteModalOpen: boolean, + isCopy: boolean, + projectToCopy?: Project, + projectToDelete?: Project, + filter: string, + name: string, + description: string, + projectId: string, +} + +export class DashboardPage extends React.Component<Props, State> { + + public state: State = { + projects: [], + deploymentStatuses: [], + serviceStatuses: [], + isCreateModalOpen: false, + isDeleteModalOpen: false, + isCopy: false, + filter: '', + name: '', + description: '', + projectId: '', + }; + interval: any; + + componentDidMount() { + this.interval = setInterval(() => this.onGetProjects(), 1300); + } + + componentWillUnmount() { + clearInterval(this.interval); + } + + onGetProjects = () => { + KaravanApi.getConfiguration((config: any) => { + KaravanApi.getProjects((projects: Project[]) => { + this.setState({projects: projects}) + }); + KaravanApi.getAllDeploymentStatuses((statuses: DeploymentStatus[]) => { + this.setState({deploymentStatuses: statuses}); + }); + KaravanApi.getAllServiceStatuses((statuses: ServiceStatus[]) => { + this.setState({serviceStatuses: statuses}); + }); + }); + + } + + tools = () => (<Toolbar id="toolbar-group-types"> + <ToolbarContent> + <ToolbarItem> + <Button variant="link" icon={<RefreshIcon/>} onClick={e => this.onGetProjects()}/> + </ToolbarItem> + <ToolbarItem> + <TextInput className="text-field" type="search" id="search" name="search" + autoComplete="off" placeholder="Search by name" + value={this.state.filter} + onChange={e => this.setState({filter: e})}/> + </ToolbarItem> + </ToolbarContent> + </Toolbar>); + + title = () => (<TextContent> + <Text component="h1">Dashboard</Text> + </TextContent>); + + getEnvironments(): string []{ + return this.props.config.environments && Array.isArray(this.props.config.environments) ? Array.from(this.props.config.environments) : []; + } + + getDeploymentEnvironments(name: string): [string, boolean] [] { + const deps = this.state.deploymentStatuses; + return this.getEnvironments().map(e => { + const env: string = e as string; + const dep = deps.find(d => d.name === name && d.env === env); + const deployed: boolean = dep !== undefined && dep.replicas > 0 && dep.replicas === dep.readyReplicas; + return [env, deployed]; + }); + } + + getDeploymentByEnvironments(name: string): [string, DeploymentStatus | undefined] [] { + const deps = this.state.deploymentStatuses; + return this.getEnvironments().map(e => { + const env: string = e as string; + const dep = deps.find(d => d.name === name && d.env === env); + return [env, dep]; + }); + } + + getServiceByEnvironments(name: string): [string, ServiceStatus | undefined] [] { + const services = this.state.serviceStatuses; + return this.getEnvironments().map(e => { + const env: string = e as string; + const service = services.find(d => d.name === name && d.env === env); + return [env, service]; + }); + } + + getProject(name: string): Project | undefined { + return this.state.projects.filter(p => p.name === name)?.at(0); + } + + isKaravan(name: string): boolean { + return this.state.projects.findIndex(p => p.projectId === name) > 0; + } + + getReplicasPanel(deploymentStatus?: DeploymentStatus) { + if (deploymentStatus) { + const readyReplicas = deploymentStatus.readyReplicas ? deploymentStatus.readyReplicas : 0; + const ok = (deploymentStatus && readyReplicas > 0 + && (deploymentStatus.unavailableReplicas === 0 || deploymentStatus.unavailableReplicas === undefined || deploymentStatus.unavailableReplicas === null) + && deploymentStatus?.replicas === readyReplicas); + return ( + <Flex justifyContent={{default: "justifyContentSpaceBetween"}} alignItems={{default: "alignItemsCenter"}}> + <FlexItem> + <LabelGroup numLabels={3}> + <Tooltip content={"Ready Replicas / Replicas"} position={"left"}> + <Label className="table-label" icon={ok ? <UpIcon/> : <DownIcon/>} + color={ok ? "green" : "grey"}>{"Replicas: " + readyReplicas + " / " + deploymentStatus.replicas}</Label> + </Tooltip> + {deploymentStatus.unavailableReplicas > 0 && + <Tooltip content={"Unavailable replicas"} position={"right"}> + <Label icon={<DownIcon/>} color={"red"}>{deploymentStatus.unavailableReplicas}</Label> + </Tooltip> + } + </LabelGroup> + </FlexItem> + </Flex> + ) + } else { + return (<Label icon={<DownIcon/>} color={"grey"}>n/a</Label>); + } + } + + render() { + const deployments = Array.from(new Set(this.state.deploymentStatuses.filter(d => d.name.toLowerCase().includes(this.state.filter)).map(d => d.name))); + return ( + <PageSection className="kamelet-section dashboard-page" padding={{default: 'noPadding'}}> + <PageSection className="tools-section" padding={{default: 'noPadding'}}> + <MainToolbar title={this.title()} tools={this.tools()}/> + </PageSection> + <PageSection isFilled className="kamelets-page"> + <TableComposable aria-label="Projects" variant={TableVariant.compact}> + <Thead> + <Tr> + <Th key='type'>Type</Th> + <Th key='name'>Deployment</Th> + <Th key='description'>Project/Description</Th> + <Th key='environment'>Environment</Th> + <Th key='namespace'>Namespace</Th> + <Th key='replicas'>Replicas</Th> + <Th key='services'>Services</Th> + <Th key='camel'>Camel Health</Th> + {/*<Th key='action'></Th>*/} + </Tr> + </Thead> + <Tbody> + {deployments.map(deployment => ( + <Tr key={deployment}> + <Td style={{verticalAlign:"middle"}}> + {this.isKaravan(deployment) ? Icon("icon") : CamelUi.getIconFromSource(camelIcon)} + </Td> + <Td style={{ verticalAlign:"middle"}}> + <Button style={{padding: '6px'}} variant={"link"}>{deployment}</Button> + </Td> + <Td style={{verticalAlign:"middle"}}> + <HelperText> + <HelperTextItem>{this.getProject(deployment)?.name || ""}</HelperTextItem> + <HelperTextItem>{this.getProject(deployment)?.description || ""}</HelperTextItem> + </HelperText> + </Td> + <Td > + <Flex direction={{default: "column"}}> + {this.getDeploymentEnvironments(deployment).map(value => ( + <FlexItem className="badge-flex-item" key={value[0]}><Badge className="badge" + isRead={!value[1]}>{value[0]}</Badge></FlexItem> + ))} + </Flex> + </Td> + <Td > + <Flex direction={{default: "column"}}> + {this.getDeploymentByEnvironments(deployment).map(value => ( + <FlexItem className="badge-flex-item" key={value[0]}> + <Label variant={"outline"}>{value[1]?.namespace || "n/a"}</Label> + </FlexItem> + ))} + </Flex> + </Td> + <Td > + <Flex direction={{default: "column"}}> + {this.getDeploymentByEnvironments(deployment).map(value => ( + <FlexItem className="badge-flex-item" key={value[0]}>{this.getReplicasPanel(value[1])}</FlexItem> + ))} + </Flex> + </Td> + <Td> + <Flex direction={{default: "column"}}> + {this.getServiceByEnvironments(deployment).map(value => ( + <FlexItem className="badge-flex-item" key={value[0]}> + <Label variant={"outline"}>{value[1] ? (value[1]?.port + " -> " + value[1]?.targetPort) : "n/a"}</Label> + </FlexItem> + ))} + </Flex> + </Td> + <Td modifier={"fitContent"}> + <Flex direction={{default: "column"}}> + {this.getServiceByEnvironments(deployment).map(value => ( + <FlexItem key={value[0]}> + <LabelGroup numLabels={4} className="camel-label-group"> + <Label className="table-label" icon={false ? <UpIcon/> : <DownIcon/>}>{"Context"}</Label> + <Label className="table-label" icon={false ? <UpIcon/> : <DownIcon/>}>{"Consumer"}</Label> + <Label className="table-label" icon={false ? <UpIcon/> : <DownIcon/>}>{"Routes"}</Label> + <Label className="table-label" icon={false ? <UpIcon/> : <DownIcon/>}>{"Registry"}</Label> + </LabelGroup> + </FlexItem> + ))} + </Flex> + </Td> + </Tr> + ))} + </Tbody> + </TableComposable> + </PageSection> + </PageSection> + ) + } +} \ No newline at end of file diff --git a/karavan-app/src/main/webapp/src/index.css b/karavan-app/src/main/webapp/src/index.css index 9ccffd9..f170c70 100644 --- a/karavan-app/src/main/webapp/src/index.css +++ b/karavan-app/src/main/webapp/src/index.css @@ -111,6 +111,19 @@ margin: auto; } +.karavan .projects-page .badge { + font-size: 14px; + font-weight: 400; + padding: 4px 8px 4px 8px; +} + +.karavan .projects-page .runtime-badge { + min-width: 18px; + font-size: 14px; + font-weight: 400; + padding: 2px 7px 2px 7px; +} + .karavan .projects-page .pf-m-link { font-size: 14px; } @@ -205,11 +218,6 @@ padding: 0; } -.karavan .runtime-badge { - min-width: 18px; - padding: 0; -} - .create-file-form .pf-c-form__group { grid-template-columns: 80px 1fr !important; } @@ -222,6 +230,38 @@ overflow-wrap: anywhere; } +/*Dashboard*/ +.karavan .dashboard-page .pf-m-link { + font-size: 14px; +} + +.karavan .dashboard-page .icon { + height: 28px; + width: 28px; + margin: auto; +} + +.karavan .dashboard-page .badge { + font-size: 14px; + font-weight: 400; + padding: 4px 8px 4px 8px; +} + +.karavan .dashboard-page .table-label { + font-size: 14px; + padding: 2px 6px 2px 4px; +} + +.karavan .dashboard-page .badge-flex-item { + margin-bottom: 3px; + margin-top: 3px; +} + +.karavan .dashboard-page .camel-label-group .pf-c-label-group__list { + flex-wrap: nowrap; +} + + .karavan .loading-page .spinner { position: absolute; } diff --git a/karavan-app/src/main/webapp/src/projects/ProjectDashboard.tsx b/karavan-app/src/main/webapp/src/projects/ProjectDashboard.tsx index b3dffe3..0fb9f80 100644 --- a/karavan-app/src/main/webapp/src/projects/ProjectDashboard.tsx +++ b/karavan-app/src/main/webapp/src/projects/ProjectDashboard.tsx @@ -8,7 +8,6 @@ import { import '../designer/karavan.css'; import {KaravanApi} from "../api/KaravanApi"; import {DeploymentStatus, Project, ProjectFileTypes} from "./ProjectModels"; -import {ChartDonutThreshold} from "@patternfly/react-charts"; interface Props { project: Project, diff --git a/karavan-app/src/main/webapp/src/projects/ProjectInfo.tsx b/karavan-app/src/main/webapp/src/projects/ProjectInfo.tsx index acbb7b2..d8e25fd 100644 --- a/karavan-app/src/main/webapp/src/projects/ProjectInfo.tsx +++ b/karavan-app/src/main/webapp/src/projects/ProjectInfo.tsx @@ -40,7 +40,6 @@ interface State { deleteEntity?: 'pod' | 'deployment', deleteEntityName?: string, deleteEntityEnv?: string, - environments: string[], environment: string, key?: string, } @@ -54,8 +53,6 @@ export class ProjectInfo extends React.Component<Props, State> { isBuilding: false, isRolling: false, showDeleteConfirmation: false, - environments: this.props.config.environments && Array.isArray(this.props.config.environments) - ? Array.from(this.props.config.environments) : [], environment: this.props.config.environment }; interval: any; diff --git a/karavan-app/src/main/webapp/src/projects/ProjectModels.ts b/karavan-app/src/main/webapp/src/projects/ProjectModels.ts index 2f6eeb9..455182e 100644 --- a/karavan-app/src/main/webapp/src/projects/ProjectModels.ts +++ b/karavan-app/src/main/webapp/src/projects/ProjectModels.ts @@ -24,12 +24,24 @@ export class DeploymentStatus { name: string = ''; env: string = ''; namespace: string = ''; + cluster: string = ''; image: string = ''; replicas: number = 0; readyReplicas: number = 0; unavailableReplicas: number = 0; } +export class ServiceStatus { + name: string = ''; + env: string = ''; + namespace: string = ''; + cluster: string = ''; + port: string = ''; + targetPort: string = ''; + clusterIP: string = ''; + type: string = ''; +} + export class PodStatus { name: string = ''; phase: string = ''; diff --git a/karavan-app/src/main/webapp/src/projects/ProjectsPage.tsx b/karavan-app/src/main/webapp/src/projects/ProjectsPage.tsx index 744c19f..3f7560c 100644 --- a/karavan-app/src/main/webapp/src/projects/ProjectsPage.tsx +++ b/karavan-app/src/main/webapp/src/projects/ProjectsPage.tsx @@ -214,15 +214,22 @@ export class ProjectsPage extends React.Component<Props, State> { ) } - isDeployed(projectId: string): boolean{ - const ds = this.state.deploymentStatuses.find(ds => ds.name === projectId); - return ds ? (ds.replicas > 0 && ds.replicas === ds.readyReplicas) : false; + getEnvironments(): string []{ + return this.props.config.environments && Array.isArray(this.props.config.environments) ? Array.from(this.props.config.environments) : []; + } + + getDeploymentByEnvironments(name: string): [string, DeploymentStatus | undefined] [] { + const deps = this.state.deploymentStatuses; + return this.getEnvironments().map(e => { + const env: string = e as string; + const dep = deps.find(d => d.name === name && d.env === env); + return [env, dep]; + }); } render() { const runtime = this.props.config?.runtime ? this.props.config.runtime : "QUARKUS"; const projects = this.state.projects.filter(p => p.name.toLowerCase().includes(this.state.filter) || p.description.toLowerCase().includes(this.state.filter)); - const environment: string = this.props.config.environment; return ( <PageSection className="kamelet-section projects-page" padding={{default: 'noPadding'}}> <PageSection className="tools-section" padding={{default: 'noPadding'}}> @@ -263,7 +270,11 @@ export class ProjectsPage extends React.Component<Props, State> { </Td> <Td noPadding style={{width:"180px"}}> <Flex direction={{default: "row"}}> - <FlexItem key={"dev"}><Badge isRead={!this.isDeployed(project.projectId)}>{"dev"}</Badge></FlexItem> + {this.getDeploymentByEnvironments(project.projectId).map(value => ( + <FlexItem className="badge-flex-item" key={value[0]}> + <Badge className="badge"isRead={!value[1]}>{value[0]}</Badge> + </FlexItem> + ))} </Flex> </Td> <Td isActionCell>