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/06/24 01:13:45 UTC

[camel-karavan] branch main updated: Saas feature15 (#393)

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


The following commit(s) were added to refs/heads/main by this push:
     new be4e23d  Saas feature15 (#393)
be4e23d is described below

commit be4e23d901220e9a81dda33080c7ef32f5e055c6
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Thu Jun 23 21:13:40 2022 -0400

    Saas feature15 (#393)
    
    * Status API
    
    * Pipeline demo
---
 karavan-app/pom.xml                                |   4 +
 .../apache/camel/karavan/api/StatusResource.java   |  71 +++++++++---
 .../camel/karavan/model/ProjectEnvStatus.java      |  41 +++++++
 .../apache/camel/karavan/model/ProjectStatus.java  |  31 ++++--
 .../camel/karavan/model/ProjectStoreSchema.java    |   8 +-
 .../camel/karavan/service/InfinispanService.java   |   9 +-
 .../src/main/resources/application.properties      |   1 +
 karavan-app/src/main/webapp/src/api/KaravanApi.tsx |  18 ++-
 karavan-app/src/main/webapp/src/index.css          |  34 ++++++
 .../src/main/webapp/src/models/ProjectModels.ts    |  17 +++
 .../src/main/webapp/src/projects/ProjectPage.tsx   | 122 ++++++++++++++-------
 karavan-builder/openshift/karavan-app.yaml         |  15 ---
 12 files changed, 284 insertions(+), 87 deletions(-)

diff --git a/karavan-app/pom.xml b/karavan-app/pom.xml
index 565af4f..939ab56 100644
--- a/karavan-app/pom.xml
+++ b/karavan-app/pom.xml
@@ -58,6 +58,10 @@
             <groupId>io.smallrye.reactive</groupId>
             <artifactId>smallrye-mutiny-vertx-web-client</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-smallrye-fault-tolerance</artifactId>
+        </dependency>
         <dependency>
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-arc</artifactId>
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 d5b9126..451786b 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
@@ -16,13 +16,23 @@
  */
 package org.apache.camel.karavan.api;
 
+import io.fabric8.tekton.pipeline.v1beta1.PipelineRun;
+import io.quarkus.runtime.configuration.ProfileManager;
+import io.smallrye.mutiny.Uni;
 import io.vertx.mutiny.core.Vertx;
 import io.vertx.mutiny.core.buffer.Buffer;
 import io.vertx.mutiny.ext.web.client.HttpResponse;
-import  io.vertx.mutiny.ext.web.client.WebClient;
+import io.vertx.mutiny.ext.web.client.WebClient;
 import org.apache.camel.karavan.model.KaravanConfiguration;
+import org.apache.camel.karavan.model.Project;
+import org.apache.camel.karavan.model.ProjectEnvStatus;
 import org.apache.camel.karavan.model.ProjectStatus;
 import org.apache.camel.karavan.service.InfinispanService;
+import org.apache.camel.karavan.service.KubernetesService;
+import org.eclipse.microprofile.faulttolerance.Asynchronous;
+import org.eclipse.microprofile.faulttolerance.Fallback;
+import org.eclipse.microprofile.faulttolerance.Retry;
+import org.eclipse.microprofile.faulttolerance.Timeout;
 import org.jboss.logging.Logger;
 
 import javax.inject.Inject;
@@ -32,10 +42,12 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
 
 @Path("/status")
 public class StatusResource {
@@ -45,6 +57,9 @@ public class StatusResource {
     @Inject
     InfinispanService infinispanService;
 
+    @Inject
+    KubernetesService kubernetesService;
+
     @Inject
     KaravanConfiguration configuration;
 
@@ -54,7 +69,7 @@ public class StatusResource {
     WebClient webClient;
 
     public WebClient getWebClient() {
-        if (webClient == null){
+        if (webClient == null) {
             webClient = WebClient.create(vertx);
         }
         return webClient;
@@ -63,28 +78,58 @@ public class StatusResource {
     @GET
     @Produces(MediaType.APPLICATION_JSON)
     @Path("/{projectId}")
-    public ProjectStatus getStatus(@HeaderParam("username") String username, @PathParam("projectId") String projectId) throws Exception {
+    @Timeout(value = 1000)
+    @Retry(maxRetries = 0)
+    @Asynchronous
+    @Fallback(fallbackMethod = "fallbackStatus")
+    public Uni<ProjectStatus> getStatus(@HeaderParam("username") String username, @PathParam("projectId") String projectId) throws Exception {
         ProjectStatus status = new ProjectStatus();
         status.setProjectId(projectId);
         status.setLastUpdate(System.currentTimeMillis());
-        Map<String, ProjectStatus.Status> statuses = new HashMap<>(configuration.environments().size());
+        List<ProjectEnvStatus> statuses = new ArrayList<>(configuration.environments().size());
         configuration.environments().forEach(e -> {
-            String url = String.format("http://%s.%s.%s/q/health", projectId, e.namespace(), e.cluster());
+            String url = ProfileManager.getActiveProfile().equals("dev")
+                    ? String.format("http://%s-%s.%s/q/health", projectId, e.namespace(), e.cluster())
+                    : String.format("http://%s.%s.%s/q/health", projectId, e.namespace(), e.cluster());
             // TODO: make it reactive
             try {
                 HttpResponse<Buffer> result = getWebClient().getAbs(url).timeout(1000).send().subscribeAsCompletionStage().toCompletableFuture().get();
-                if (result.bodyAsJsonObject().getString("status").equals("UP")){
-                    statuses.put(e.name(), ProjectStatus.Status.UP);
+                if (result.bodyAsJsonObject().getString("status").equals("UP")) {
+                    statuses.add(new ProjectEnvStatus(e.name(), ProjectEnvStatus.Status.UP));
                 } else {
-                    statuses.put(e.name(), ProjectStatus.Status.DOWN);
+                    statuses.add(new ProjectEnvStatus(e.name(), ProjectEnvStatus.Status.DOWN));
                 }
             } catch (Exception ex) {
-
-                statuses.put(e.name(), ProjectStatus.Status.DOWN);
+                statuses.add(new ProjectEnvStatus(e.name(), ProjectEnvStatus.Status.DOWN));
                 LOGGER.error(ex);
             }
         });
         status.setStatuses(statuses);
-        return status;
+
+        Project project = infinispanService.getProject(projectId);
+        PipelineRun pipelineRun = kubernetesService.getPipelineRun(project.getLastPipelineRun(), configuration.environments().get(0).namespace());
+        if (pipelineRun != null) {
+            status.setPipeline(pipelineRun.getStatus().getConditions().get(0).getReason());
+        } else {
+            status.setPipeline("Undefined");
+        }
+
+        LOGGER.info("Storing status in cache for " + projectId);
+        infinispanService.saveProjectStatus(status);
+        return Uni.createFrom().item(status);
+    }
+
+    public Uni<ProjectStatus> fallbackStatus(String username, String projectId) {
+        LOGGER.info("Return cached status for " + projectId);
+        ProjectStatus status = infinispanService.getProjectStatus(projectId);
+        if (status != null){
+            return Uni.createFrom().item(status);
+        } else {
+            return Uni.createFrom().item(new ProjectStatus(
+                    projectId,
+                    configuration.environments().stream().map(e -> new ProjectEnvStatus(e.name(), ProjectEnvStatus.Status.DOWN)).collect(Collectors.toList()),
+                    Long.valueOf(0), "Undefined"
+            ));
+        }
     }
 }
\ No newline at end of file
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectEnvStatus.java b/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectEnvStatus.java
new file mode 100644
index 0000000..346a0e6
--- /dev/null
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectEnvStatus.java
@@ -0,0 +1,41 @@
+package org.apache.camel.karavan.model;
+
+import org.infinispan.protostream.annotations.ProtoEnumValue;
+import org.infinispan.protostream.annotations.ProtoFactory;
+import org.infinispan.protostream.annotations.ProtoField;
+
+public class ProjectEnvStatus {
+    @ProtoField(number = 1)
+    String environment;
+    @ProtoField(number = 2)
+    Status status;
+
+    public enum Status {
+        @ProtoEnumValue(number = 0, name = "DOWN")
+        DOWN,
+        @ProtoEnumValue(number = 1, name = "UP")
+        UP
+    }
+
+    @ProtoFactory
+    public ProjectEnvStatus(String environment, Status status) {
+        this.environment = environment;
+        this.status = status;
+    }
+
+    public String getEnvironment() {
+        return environment;
+    }
+
+    public void setEnvironment(String environment) {
+        this.environment = environment;
+    }
+
+    public Status getStatus() {
+        return status;
+    }
+
+    public void setStatus(Status status) {
+        this.status = status;
+    }
+}
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectStatus.java b/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectStatus.java
index b153a22..4dd7efc 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectStatus.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectStatus.java
@@ -4,16 +4,19 @@ import org.infinispan.protostream.annotations.ProtoEnumValue;
 import org.infinispan.protostream.annotations.ProtoFactory;
 import org.infinispan.protostream.annotations.ProtoField;
 
-import java.util.Map;
+import java.util.ArrayList;
+import java.util.List;
 
 public class ProjectStatus {
     public static final String CACHE = "project_statuses";
     @ProtoField(number = 1)
     String projectId;
-    @ProtoField(number = 2)
-    Map<String, Status> statuses;
+    @ProtoField(number = 2, collectionImplementation = ArrayList.class)
+    List<ProjectEnvStatus> statuses;
     @ProtoField(number = 3)
-    long lastUpdate;
+    Long lastUpdate;
+    @ProtoField(number = 4)
+    String pipeline;
 
     public enum Status {
         @ProtoEnumValue(number = 0, name = "DOWN")
@@ -23,11 +26,11 @@ public class ProjectStatus {
     }
 
     @ProtoFactory
-
-    public ProjectStatus(String projectId, Map<String, Status> statuses, long lastUpdate) {
+    public ProjectStatus(String projectId, List<ProjectEnvStatus> statuses, Long lastUpdate, String pipeline) {
         this.projectId = projectId;
         this.statuses = statuses;
         this.lastUpdate = lastUpdate;
+        this.pipeline = pipeline;
     }
 
     public ProjectStatus() {
@@ -41,19 +44,27 @@ public class ProjectStatus {
         this.projectId = projectId;
     }
 
-    public Map<String, Status> getStatuses() {
+    public List<ProjectEnvStatus> getStatuses() {
         return statuses;
     }
 
-    public void setStatuses(Map<String, Status> statuses) {
+    public void setStatuses(List<ProjectEnvStatus> statuses) {
         this.statuses = statuses;
     }
 
-    public long getLastUpdate() {
+    public Long getLastUpdate() {
         return lastUpdate;
     }
 
-    public void setLastUpdate(long lastUpdate) {
+    public void setLastUpdate(Long lastUpdate) {
         this.lastUpdate = lastUpdate;
     }
+
+    public String getPipeline() {
+        return pipeline;
+    }
+
+    public void setPipeline(String pipeline) {
+        this.pipeline = pipeline;
+    }
 }
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 666f60c..cf01d06 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
@@ -3,6 +3,12 @@ package org.apache.camel.karavan.model;
 import org.infinispan.protostream.GeneratedSchema;
 import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
 
-@AutoProtoSchemaBuilder(includeClasses = { GroupedKey.class, Project.class, ProjectFile.class }, schemaPackageName = "karavan")
+import java.util.HashMap;
+
+@AutoProtoSchemaBuilder(
+        includeClasses = {
+                GroupedKey.class, Project.class, ProjectFile.class, ProjectStatus.class, ProjectEnvStatus.class
+        },
+        schemaPackageName = "karavan")
 public interface ProjectStoreSchema extends GeneratedSchema {
 }
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 7c4befb..e2d22cd 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
@@ -130,7 +130,6 @@ public class InfinispanService {
                     .execute().list();
         }
     }
-
     public void saveProjectFile(ProjectFile file) {
         files.put(GroupedKey.create(file.getProjectId(), file.getName()), file);
     }
@@ -151,8 +150,12 @@ public class InfinispanService {
         return projects.get(GroupedKey.create(project, project));
     }
 
-    public Project getProjectStatus(String projectId) {
-        return projects.get(GroupedKey.create(projectId, projectId));
+    public ProjectStatus getProjectStatus(String projectId) {
+        return statuses.get(GroupedKey.create(projectId, projectId));
+    }
+
+    public void saveProjectStatus(ProjectStatus status) {
+        statuses.put(GroupedKey.create(status.getProjectId(), status.getProjectId()), status);
     }
 
     private void generateDevProjects(){
diff --git a/karavan-app/src/main/resources/application.properties b/karavan-app/src/main/resources/application.properties
index 4f09b60..e942344 100644
--- a/karavan-app/src/main/resources/application.properties
+++ b/karavan-app/src/main/resources/application.properties
@@ -23,6 +23,7 @@ karavan.config.runtime-version=2.10.0.Final
 karavan.config.environments[0].name=dev
 karavan.config.environments[0].namespace=karavan
 karavan.config.environments[0].cluster=svc.cluster.local
+%dev.karavan.config.environments[0].cluster=apps.cluster-ztffv.ztffv.sandbox55.opentlc.com
 karavan.config.environments[1].name=test
 karavan.config.environments[1].namespace=test
 karavan.config.environments[1].cluster=svc.cluster.local
diff --git a/karavan-app/src/main/webapp/src/api/KaravanApi.tsx b/karavan-app/src/main/webapp/src/api/KaravanApi.tsx
index 46f3d84..e59bb2b 100644
--- a/karavan-app/src/main/webapp/src/api/KaravanApi.tsx
+++ b/karavan-app/src/main/webapp/src/api/KaravanApi.tsx
@@ -1,5 +1,5 @@
 import axios, {AxiosResponse} from "axios";
-import {Project, ProjectFile} from "../models/ProjectModels";
+import {Project, ProjectFile, ProjectStatus} from "../models/ProjectModels";
 
 export const KaravanApi = {
 
@@ -15,8 +15,20 @@ export const KaravanApi = {
         });
     },
 
-    getProject: async (name: string, after: (project: Project) => void) => {
-        axios.get('/project/' + name,
+    getProject: async (projectId: string, after: (project: Project) => void) => {
+        axios.get('/project/' + projectId,
+            {headers: {'Accept': 'application/json', 'username': 'cameleer'}})
+            .then(res => {
+                if (res.status === 200) {
+                    after(res.data);
+                }
+            }).catch(err => {
+            console.log(err);
+        });
+    },
+
+    getProjectStatus: async (projectId: string, after: (status: ProjectStatus) => void) => {
+        axios.get('/status/' + projectId,
             {headers: {'Accept': 'application/json', 'username': 'cameleer'}})
             .then(res => {
                 if (res.status === 200) {
diff --git a/karavan-app/src/main/webapp/src/index.css b/karavan-app/src/main/webapp/src/index.css
index 7f33f3c..f711011 100644
--- a/karavan-app/src/main/webapp/src/index.css
+++ b/karavan-app/src/main/webapp/src/index.css
@@ -99,10 +99,44 @@
 .karavan .project-page .table {
   margin-top: 16px;
 }
+
 .karavan .project-page .project-button {
   width: 100px;
 }
 
+.karavan .project-page .badge-env-up {
+  background-color: rgb(56, 129, 47);
+  color: white;
+}
+
+.karavan .project-page .pipeline {
+  background-color: #f5f5f5;
+  border-radius: 24px;
+  font-size: 12px;
+  font-weight: bold;
+  width: fit-content;
+  padding-right: 8px;
+  padding-left: 8px;
+}
+
+.karavan .project-page .pipeline-running {
+  background-color: rgb(139, 193, 247);
+}
+
+.karavan .project-page .pipeline-succeeded {
+  background-color: rgb(56, 129, 47);
+  color: white;
+}
+
+.karavan .project-page .pipeline-failed {
+  background-color: #C9190B;
+  color: white;
+}
+
+.karavan .project-page .pipeline .pf-c-progress-stepper__step-main {
+  display: none;
+}
+
 .karavan .action-cell {
   padding: 0;
 }
diff --git a/karavan-app/src/main/webapp/src/models/ProjectModels.ts b/karavan-app/src/main/webapp/src/models/ProjectModels.ts
index 51474f8..28496f1 100644
--- a/karavan-app/src/main/webapp/src/models/ProjectModels.ts
+++ b/karavan-app/src/main/webapp/src/models/ProjectModels.ts
@@ -22,6 +22,23 @@ export class Project {
     }
 }
 
+export class ProjectEnvStatus {
+    environment: string = '';
+    status: string = '';
+
+    constructor(environment: string, status: string) {
+        this.environment = environment;
+        this.status = status;
+    }
+}
+
+export class ProjectStatus {
+    projectId: string = '';
+    lastUpdate: number = 0;
+    statuses: ProjectEnvStatus[] = [];
+    pipeline: string = 'Running'
+}
+
 export class ProjectFile {
     name: string = '';
     projectId: string = '';
diff --git a/karavan-app/src/main/webapp/src/projects/ProjectPage.tsx b/karavan-app/src/main/webapp/src/projects/ProjectPage.tsx
index 3436d66..4e86ce9 100644
--- a/karavan-app/src/main/webapp/src/projects/ProjectPage.tsx
+++ b/karavan-app/src/main/webapp/src/projects/ProjectPage.tsx
@@ -21,12 +21,12 @@ import {
     EmptyStateVariant,
     EmptyStateIcon,
     Title,
-    ModalVariant, Modal, Spinner, Tooltip, Flex, FlexItem, ToggleGroup, ToggleGroupItem,
+    ModalVariant, Modal, Spinner, Tooltip, Flex, FlexItem, ProgressStep, ProgressStepper
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {MainToolbar} from "../MainToolbar";
 import {KaravanApi} from "../api/KaravanApi";
-import {Project, ProjectFile, ProjectFileTypes} from "../models/ProjectModels";
+import {Project, ProjectFile, ProjectFileTypes, ProjectStatus} from "../models/ProjectModels";
 import {CamelUi} from "../designer/utils/CamelUi";
 import UploadIcon from "@patternfly/react-icons/dist/esm/icons/upload-icon";
 import {TableComposable, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
@@ -39,7 +39,6 @@ import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
 import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
 import {CreateFileModal} from "./CreateFileModal";
 import BuildIcon from "@patternfly/react-icons/dist/esm/icons/build-icon";
-import DeployIcon from "@patternfly/react-icons/dist/esm/icons/process-automation-icon";
 import PushIcon from "@patternfly/react-icons/dist/esm/icons/code-branch-icon";
 import {PropertiesEditor} from "./PropertiesEditor";
 import PendingIcon from "@patternfly/react-icons/dist/esm/icons/pending-icon";
@@ -53,6 +52,7 @@ interface Props {
 
 interface State {
     project?: Project,
+    status?: ProjectStatus,
     file?: ProjectFile,
     files: ProjectFile[],
     isUploadModalOpen: boolean,
@@ -62,7 +62,8 @@ interface State {
     isBuilding: boolean,
     fileToDelete?: ProjectFile,
     environments: string[],
-    environment: string
+    environment: string,
+    key?: string,
 }
 
 export class ProjectPage extends React.Component<Props, State> {
@@ -80,9 +81,42 @@ export class ProjectPage extends React.Component<Props, State> {
         environment: this.props.config.environments && Array.isArray(this.props.config.environments)
             ? this.props.config.environments[0] : ''
     };
+    interval: any;
 
     componentDidMount() {
         this.onRefresh();
+        this.interval = setInterval(() => this.onRefreshStatus(), 3000);
+    }
+
+    componentWillUnmount() {
+        clearInterval(this.interval);
+    }
+
+    onRefresh = () => {
+        if (this.props.project) {
+            KaravanApi.getProject(this.props.project.projectId, (project: Project) => {
+                this.setState({
+                    project: project
+                })
+            });
+            KaravanApi.getFiles(this.props.project.projectId, (files: []) => {
+                this.setState({
+                    files: files
+                })
+            });
+        }
+    }
+
+    onRefreshStatus = () => {
+        if (this.props.project) {
+            KaravanApi.getProjectStatus(this.props.project.projectId, (status: ProjectStatus) => {
+                this.setState({
+                    key: Math.random().toString(),
+                    status: status
+                });
+                console.log(status);
+            });
+        }
     }
 
     post = (file: ProjectFile) => {
@@ -170,21 +204,6 @@ export class ProjectPage extends React.Component<Props, State> {
         </div>)
     };
 
-    onRefresh = () => {
-        if (this.props.project) {
-            KaravanApi.getProject(this.props.project.projectId, (project: Project) => {
-                this.setState({
-                    project: project
-                })
-            });
-            KaravanApi.getFiles(this.props.project.projectId, (files: []) => {
-                this.setState({
-                    files: files
-                })
-            });
-        }
-    }
-
     closeModal = (isPushing: boolean = false) => {
         this.setState({
             isUploadModalOpen: false,
@@ -272,7 +291,7 @@ export class ProjectPage extends React.Component<Props, State> {
                     onClick={e => {
                         this.push(() => this.build());
                     }}>
-                {isDeploying ? "..." : "Run"}
+                {isDeploying ? "..." : "Build"}
             </Button>
         </Tooltip>)
     }
@@ -296,8 +315,37 @@ export class ProjectPage extends React.Component<Props, State> {
         return (<Text>OK</Text>)
     }
 
+    getPipelineState() {
+        const {project, status} = this.state;
+        const isRunning = status?.pipeline === 'Running';
+        const isFailed = status?.pipeline === 'Failed';
+        const isSucceeded = status?.pipeline === 'Succeeded';
+        let classname = "pipeline"
+        if (isRunning) classname = classname + " pipeline-running";
+        if (isFailed) classname = classname + " pipeline-running";
+        if (isSucceeded) classname = classname + " pipeline-succeeded";
+        return (
+                <Flex spaceItems={{ default: 'spaceItemsNone' }} className={classname} direction={{default:"row"}} alignItems={{default: "alignItemsCenter"}}>
+                    <FlexItem style={{height:"18px"}}>
+                        {isRunning && <Spinner isSVG diameter="16px"/>}
+                    </FlexItem>
+                    <FlexItem style={{height:"18px"}}>
+                        {project?.lastPipelineRun ? project?.lastPipelineRun : "-"}
+                    </FlexItem>
+                </Flex>
+            )
+    }
+
+    isUp(env: string): boolean {
+        if (this.state.status) {
+            return this.state.status.statuses.find(s => s.environment === env)?.status === 'UP';
+        } else {
+            return false;
+        }
+    }
+
     getProjectForm = () => {
-        const {project, environments, environment, isBuilding} = this.state;
+        const {project, environments} = this.state;
         return (
             <Card>
                 <CardBody isFilled>
@@ -322,7 +370,7 @@ export class ProjectPage extends React.Component<Props, State> {
                         <FlexItem flex={{default: "flex_1"}}>
                             <DescriptionList isHorizontal>
                                 <DescriptionListGroup>
-                                    <DescriptionListTerm>Last Commit</DescriptionListTerm>
+                                    <DescriptionListTerm>Commit</DescriptionListTerm>
                                     <DescriptionListDescription>
                                         <Tooltip content={project?.lastCommit} position={"bottom"}>
                                             <Badge>{project?.lastCommit ? project?.lastCommit?.substr(0, 7) : "-"}</Badge>
@@ -330,36 +378,25 @@ export class ProjectPage extends React.Component<Props, State> {
                                     </DescriptionListDescription>
                                 </DescriptionListGroup>
                                 <DescriptionListGroup>
-                                    <DescriptionListTerm>Last Pipeline Run</DescriptionListTerm>
+                                    <DescriptionListTerm>Pipeline Run</DescriptionListTerm>
                                     <DescriptionListDescription>
-                                        <Tooltip content={project?.lastPipelineRun} position={"bottom"}>
-                                            <Badge>{project?.lastPipelineRun ? project?.lastPipelineRun : "-"}</Badge>
-                                        </Tooltip>
+                                        {this.getPipelineState()}
                                     </DescriptionListDescription>
                                 </DescriptionListGroup>
-                                <DescriptionListGroup>
+                                <DescriptionListGroup key={this.state.key}>
                                     <DescriptionListTerm>Status</DescriptionListTerm>
                                     <DescriptionListDescription>
                                         <Flex direction={{default: "row"}}>
                                             {environments.filter(e => e !== undefined)
-                                                .map(e => <FlexItem key={e}><Badge isRead>{e}</Badge></FlexItem>)}
+                                                .map(e => <FlexItem key={e}><Badge
+                                                    className={this.isUp(e) ? "badge-env-up" : ""}
+                                                    isRead>{e}</Badge></FlexItem>)}
                                         </Flex>
                                     </DescriptionListDescription>
                                 </DescriptionListGroup>
-                                {/*<DescriptionListGroup>*/}
-                                {/*    <DescriptionListTerm>Environment</DescriptionListTerm>*/}
-                                {/*    <DescriptionListDescription>*/}
-                                {/*        <ToggleGroup isCompact>*/}
-                                {/*            {environments.filter(e => e !== undefined)*/}
-                                {/*                .map(e => <ToggleGroupItem key={e} text={e} isSelected={environment === e}*/}
-                                {/*                                           onChange={s => this.setState({environment: e})}>*/}
-                                {/*                </ToggleGroupItem>)}*/}
-                                {/*        </ToggleGroup>*/}
-                                {/*    </DescriptionListDescription>*/}
-                                {/*</DescriptionListGroup>*/}
                             </DescriptionList>
                         </FlexItem>
-                        <FlexItem >
+                        <FlexItem>
                             <Flex direction={{default: "column"}}>
                                 <FlexItem>
                                     {this.pushButton()}
@@ -368,7 +405,7 @@ export class ProjectPage extends React.Component<Props, State> {
                                     {this.buildButton()}
                                 </FlexItem>
                                 <FlexItem>
-                                    <Button isSmall style={{visibility:"hidden"}}>Refresh</Button>
+                                    <Button isSmall style={{visibility: "hidden"}}>Refresh</Button>
                                 </FlexItem>
                             </Flex>
                         </FlexItem>
@@ -403,7 +440,8 @@ export class ProjectPage extends React.Component<Props, State> {
                                 </Button>
                             </Td>
                             <Td modifier={"fitContent"}>
-                                <Button style={{padding: '0'}} variant={"plain"} isDisabled={file.name === 'application.properties'}
+                                <Button style={{padding: '0'}} variant={"plain"}
+                                        isDisabled={file.name === 'application.properties'}
                                         onClick={e => this.openDeleteConfirmation(file)}>
                                     <DeleteIcon/>
                                 </Button>
diff --git a/karavan-builder/openshift/karavan-app.yaml b/karavan-builder/openshift/karavan-app.yaml
index 6f98794..a9acc04 100644
--- a/karavan-builder/openshift/karavan-app.yaml
+++ b/karavan-builder/openshift/karavan-app.yaml
@@ -40,21 +40,6 @@ spec:
               valueFrom:
                 fieldRef:
                   fieldPath: metadata.namespace
-            - name: GIT_TOKEN
-              valueFrom:
-                secretKeyRef:
-                  key: git-token
-                  name: karavan
-            - name: GIT_REPOSITORY
-              valueFrom:
-                secretKeyRef:
-                  key: git-repository
-                  name: karavan
-            - name: GIT_USERNAME
-              valueFrom:
-                secretKeyRef:
-                  key: git-username
-                  name: karavan
           image: ghcr.io/apache/camel-karavan:0.0.16
           imagePullPolicy: Always
           name: karavan