You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ma...@apache.org on 2022/10/28 00:16:58 UTC
[camel-karavan] 08/08: First version of Dashboard
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 <ma...@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>