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/27 16:32:16 UTC
[camel-karavan] branch main updated: Saas feature16 (#395)
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 5cbaef6 Saas feature16 (#395)
5cbaef6 is described below
commit 5cbaef6a86491985d25b2248b57c0a6a81a00ea9
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Mon Jun 27 12:32:12 2022 -0400
Saas feature16 (#395)
* Status collect improvements
* Designer/editor switch
---
.../apache/camel/karavan/api/StatusResource.java | 91 +------
.../camel/karavan/model/KaravanConfiguration.java | 1 +
.../camel/karavan/service/StatusService.java | 127 ++++++++++
.../src/main/resources/application.properties | 5 +-
.../src/main/webapp/src/projects/ProjectHeader.tsx | 249 +++++++++++++++++++
.../src/main/webapp/src/projects/ProjectPage.tsx | 272 +++------------------
karavan-designer/src/designer/KaravanDesigner.tsx | 6 +-
7 files changed, 429 insertions(+), 322 deletions(-)
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 451786b..02d08c9 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,23 +16,12 @@
*/
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.core.eventbus.EventBus;
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.apache.camel.karavan.service.StatusService;
import org.jboss.logging.Logger;
import javax.inject.Inject;
@@ -42,11 +31,6 @@ 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.stream.Collectors;
@Path("/status")
@@ -57,79 +41,26 @@ public class StatusResource {
@Inject
InfinispanService infinispanService;
- @Inject
- KubernetesService kubernetesService;
-
@Inject
KaravanConfiguration configuration;
@Inject
- Vertx vertx;
-
- WebClient webClient;
-
- public WebClient getWebClient() {
- if (webClient == null) {
- webClient = WebClient.create(vertx);
- }
- return webClient;
- }
+ EventBus bus;
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/{projectId}")
- @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());
- List<ProjectEnvStatus> statuses = new ArrayList<>(configuration.environments().size());
- configuration.environments().forEach(e -> {
- 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.add(new ProjectEnvStatus(e.name(), ProjectEnvStatus.Status.UP));
- } else {
- statuses.add(new ProjectEnvStatus(e.name(), ProjectEnvStatus.Status.DOWN));
- }
- } catch (Exception ex) {
- statuses.add(new ProjectEnvStatus(e.name(), ProjectEnvStatus.Status.DOWN));
- LOGGER.error(ex);
- }
- });
- status.setStatuses(statuses);
-
- 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);
+ public ProjectStatus getStatus(@HeaderParam("username") String username, @PathParam("projectId") String projectId) throws Exception {
+ bus.publish(StatusService.CMD_COLLECT_STATUSES, projectId);
ProjectStatus status = infinispanService.getProjectStatus(projectId);
if (status != null){
- return Uni.createFrom().item(status);
+ return 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"
- ));
+ return 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/KaravanConfiguration.java b/karavan-app/src/main/java/org/apache/camel/karavan/model/KaravanConfiguration.java
index a843798..3cea180 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/model/KaravanConfiguration.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/model/KaravanConfiguration.java
@@ -11,6 +11,7 @@ public interface KaravanConfiguration {
String imageGroup();
String runtime();
String runtimeVersion();
+ Long statusThreshold();
List<Environment> environments();
interface Environment {
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/StatusService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/StatusService.java
new file mode 100644
index 0000000..074feb8
--- /dev/null
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/StatusService.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.karavan.service;
+
+import io.fabric8.tekton.pipeline.v1beta1.PipelineRun;
+import io.quarkus.runtime.configuration.ProfileManager;
+import io.quarkus.vertx.ConsumeEvent;
+import io.smallrye.mutiny.tuples.Tuple2;
+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 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.jboss.logging.Logger;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
+
+@ApplicationScoped
+public class StatusService {
+
+ private static final Logger LOGGER = Logger.getLogger(StatusService.class.getName());
+ public static final String CMD_COLLECT_STATUSES = "collect-statuses";
+
+ @Inject
+ InfinispanService infinispanService;
+
+ @Inject
+ KubernetesService kubernetesService;
+
+ @Inject
+ KaravanConfiguration configuration;
+
+ private long lastCollect = 0;
+
+ @Inject
+ Vertx vertx;
+
+ WebClient webClient;
+
+ public WebClient getWebClient() {
+ if (webClient == null) {
+ webClient = WebClient.create(vertx);
+ }
+ return webClient;
+ }
+
+ @ConsumeEvent(value = CMD_COLLECT_STATUSES, blocking = true)
+ public void collectStatuses(String projectId) throws Exception {
+ LOGGER.info("Event received to collect statuses for the project " + projectId);
+ if (System.currentTimeMillis() - lastCollect > configuration.statusThreshold()){
+ getStatuses(projectId);
+ }
+ }
+
+ private void getStatuses(String projectId) throws Exception {
+ LOGGER.info("Start to collect statuses for the project " + projectId);
+ ProjectStatus status = new ProjectStatus();
+ status.setProjectId(projectId);
+ status.setLastUpdate(System.currentTimeMillis());
+ List<ProjectEnvStatus> statuses = new ArrayList<>(configuration.environments().size());
+ configuration.environments().forEach(e -> {
+ 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());
+ Tuple2<Boolean, ProjectEnvStatus> s = getProjectEnvStatus(url, e.name());
+ if (s.getItem1()) statuses.add(s.getItem2());
+ });
+ status.setStatuses(statuses);
+
+ Project project = infinispanService.getProject(projectId);
+ Tuple2<Boolean, String> p = getProjectPipelineStatus(project.getLastPipelineRun());
+ if (p.getItem1()) status.setPipeline(p.getItem2());
+
+ LOGGER.info("Storing status in cache for " + projectId);
+ infinispanService.saveProjectStatus(status);
+ }
+
+ private Tuple2<Boolean, String> getProjectPipelineStatus(String lastPipelineRun) {
+ try {
+ PipelineRun pipelineRun = kubernetesService.getPipelineRun(lastPipelineRun, configuration.environments().get(0).namespace());
+ if (pipelineRun != null) {
+ return Tuple2.of(true, pipelineRun.getStatus().getConditions().get(0).getReason());
+ } else {
+ return Tuple2.of(true,"Undefined");
+ }
+ } catch (Exception ex) {
+ LOGGER.error(ex.getMessage());
+ return Tuple2.of(false, "Undefined");
+ }
+ }
+
+ private Tuple2<Boolean, ProjectEnvStatus> getProjectEnvStatus(String url, String env) {
+ // TODO: make it reactive
+ try {
+ HttpResponse<Buffer> result = getWebClient().getAbs(url).timeout(1000).send().subscribeAsCompletionStage().toCompletableFuture().get();
+ if (result.bodyAsJsonObject().getString("status").equals("UP")) {
+ return Tuple2.of(true, new ProjectEnvStatus(env, ProjectEnvStatus.Status.UP));
+ } else {
+ return Tuple2.of(true, new ProjectEnvStatus(env, ProjectEnvStatus.Status.DOWN));
+ }
+ } catch (Exception ex) {
+ LOGGER.error(ex.getMessage());
+ return Tuple2.of(false, new ProjectEnvStatus(env, ProjectEnvStatus.Status.DOWN));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/karavan-app/src/main/resources/application.properties b/karavan-app/src/main/resources/application.properties
index e942344..b230f87 100644
--- a/karavan-app/src/main/resources/application.properties
+++ b/karavan-app/src/main/resources/application.properties
@@ -20,10 +20,10 @@ karavan.config.group-id=org.camel.karavan.demo
karavan.config.image-group=karavan
karavan.config.runtime=QUARKUS
karavan.config.runtime-version=2.10.0.Final
+karavan.config.status-threshold=5000
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
@@ -31,6 +31,9 @@ karavan.config.environments[2].name=prod
karavan.config.environments[2].namespace=prod
karavan.config.environments[2].cluster=svc.cluster.local
+%dev.karavan.config.environments[0].cluster=apps.cluster-ztffv.ztffv.sandbox55.opentlc.com
+
+
# Infinispan Server address
#quarkus.infinispan-client.server-list=localhost:12345
quarkus.infinispan-client.devservices.enabled=false
diff --git a/karavan-app/src/main/webapp/src/projects/ProjectHeader.tsx b/karavan-app/src/main/webapp/src/projects/ProjectHeader.tsx
new file mode 100644
index 0000000..30a2ce0
--- /dev/null
+++ b/karavan-app/src/main/webapp/src/projects/ProjectHeader.tsx
@@ -0,0 +1,249 @@
+import React from 'react';
+import {
+ Badge,
+ Button,
+ Text,
+ DescriptionList,
+ DescriptionListTerm,
+ DescriptionListGroup,
+ DescriptionListDescription,
+ Card,
+ CardBody, Spinner, Tooltip, Flex, FlexItem
+} from '@patternfly/react-core';
+import '../designer/karavan.css';
+import {KaravanApi} from "../api/KaravanApi";
+import {Project, ProjectFileTypes, ProjectStatus} from "../models/ProjectModels";
+import BuildIcon from "@patternfly/react-icons/dist/esm/icons/build-icon";
+import PushIcon from "@patternfly/react-icons/dist/esm/icons/code-branch-icon";
+
+interface Props {
+ project: Project,
+ config: any,
+}
+
+interface State {
+ project?: Project,
+ status?: ProjectStatus,
+ isPushing: boolean,
+ isBuilding: boolean,
+ environments: string[],
+ environment: string,
+ key?: string,
+}
+
+export class ProjectHeader extends React.Component<Props, State> {
+
+ public state: State = {
+ project: this.props.project,
+ isPushing: false,
+ isBuilding: false,
+ environments: this.props.config.environments && Array.isArray(this.props.config.environments)
+ ? Array.from(this.props.config.environments) : [],
+ 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
+ })
+ });
+ }
+ }
+
+ onRefreshStatus = () => {
+ if (this.props.project) {
+ KaravanApi.getProjectStatus(this.props.project.projectId, (status: ProjectStatus) => {
+ this.setState({
+ key: Math.random().toString(),
+ status: status
+ });
+ // console.log(status);
+ });
+ }
+ }
+
+ push = (after?: () => void) => {
+ this.setState({isPushing: true});
+ KaravanApi.push(this.props.project, res => {
+ console.log(res)
+ if (res.status === 200 || res.status === 201) {
+ this.setState({isPushing: false});
+ after?.call(this);
+ this.onRefresh();
+ } else {
+ // Todo notification
+ }
+ });
+ }
+
+ build = () => {
+ this.setState({isBuilding: true});
+ KaravanApi.tekton(this.props.project, this.state.environment, res => {
+ console.log(res)
+ if (res.status === 200 || res.status === 201) {
+ this.setState({isBuilding: false});
+ this.onRefresh();
+ } else {
+ // Todo notification
+ }
+ });
+ }
+
+ getType = (name: string) => {
+ const extension = name.substring(name.lastIndexOf('.') + 1);
+ const type = ProjectFileTypes.filter(p => p.extension === extension).map(p => p.title)[0];
+ if (type) {
+ return type
+ } else {
+ return "Unknown"
+ }
+ }
+
+ pushButton = () => {
+ const isPushing = this.state.isPushing;
+ return (<Tooltip content="Commit and push to git" position={"left"}>
+ <Button isLoading={isPushing ? true : undefined} isSmall variant="secondary"
+ className="project-button"
+ icon={!isPushing ? <PushIcon/> : <div></div>}
+ onClick={e => this.push()}>
+ {isPushing ? "..." : "Commit"}
+ </Button>
+ </Tooltip>)
+ }
+
+ buildButton = () => {
+ const isDeploying = this.state.isBuilding;
+ return (<Tooltip content="Commit, push, build and deploy" position={"left"}>
+ <Button isLoading={isDeploying ? true : undefined} isSmall variant="secondary"
+ className="project-button"
+ icon={!isDeploying ? <BuildIcon/> : <div></div>}
+ onClick={e => {
+ this.push(() => this.build());
+ }}>
+ {isDeploying ? "..." : "Build"}
+ </Button>
+ </Tooltip>)
+ }
+
+ getCurrentStatus() {
+ 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;
+ }
+ }
+
+ render() {
+ const {project, environments, status} = this.state;
+ return (
+ <Card>
+ <CardBody isFilled>
+ <Flex direction={{default: "row"}} alignContent={{default: "alignContentSpaceBetween"}}
+ style={{width: "100%"}}>
+ <FlexItem flex={{default: "flex_1"}}>
+ <DescriptionList isHorizontal>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Project ID</DescriptionListTerm>
+ <DescriptionListDescription>{project?.projectId}</DescriptionListDescription>
+ </DescriptionListGroup>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Name</DescriptionListTerm>
+ <DescriptionListDescription>{project?.name}</DescriptionListDescription>
+ </DescriptionListGroup>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Description</DescriptionListTerm>
+ <DescriptionListDescription>{project?.description}</DescriptionListDescription>
+ </DescriptionListGroup>
+ </DescriptionList>
+ </FlexItem>
+ <FlexItem flex={{default: "flex_1"}}>
+ <DescriptionList isHorizontal>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Commit</DescriptionListTerm>
+ <DescriptionListDescription>
+ <Tooltip content={project?.lastCommit} position={"bottom"}>
+ <Badge>{project?.lastCommit ? project?.lastCommit?.substr(0, 7) : "-"}</Badge>
+ </Tooltip>
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Pipeline Run</DescriptionListTerm>
+ <DescriptionListDescription>
+ {this.getPipelineState()}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ <DescriptionListGroup key={this.state.key}>
+ <DescriptionListTerm>Status</DescriptionListTerm>
+ <DescriptionListDescription>
+ <Flex direction={{default: "row"}}>
+ {environments.filter(e => e !== undefined)
+ .map(e =>
+ <FlexItem key={e}>
+ <Tooltip content={"Last update: " + (status ? new Date(status.lastUpdate).toISOString() : "N/A")}
+ position={"bottom"}>
+ <Badge className={this.isUp(e) ? "badge-env-up" : ""} isRead>{e}</Badge>
+ </Tooltip>
+ </FlexItem>)}
+ </Flex>
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ </DescriptionList>
+ </FlexItem>
+ <FlexItem>
+ <Flex direction={{default: "column"}}>
+ <FlexItem>
+ {this.pushButton()}
+ </FlexItem>
+ <FlexItem>
+ {this.buildButton()}
+ </FlexItem>
+ <FlexItem>
+ <Button isSmall style={{visibility: "hidden"}}>Refresh</Button>
+ </FlexItem>
+ </Flex>
+ </FlexItem>
+ </Flex>
+ </CardBody>
+ </Card>
+ )
+ }
+}
diff --git a/karavan-app/src/main/webapp/src/projects/ProjectPage.tsx b/karavan-app/src/main/webapp/src/projects/ProjectPage.tsx
index 4e86ce9..022d02e 100644
--- a/karavan-app/src/main/webapp/src/projects/ProjectPage.tsx
+++ b/karavan-app/src/main/webapp/src/projects/ProjectPage.tsx
@@ -10,18 +10,12 @@ import {
Toolbar,
ToolbarContent,
ToolbarItem,
- DescriptionList,
- DescriptionListTerm,
- DescriptionListGroup,
- DescriptionListDescription,
- Card,
- CardBody,
Bullseye,
EmptyState,
EmptyStateVariant,
EmptyStateIcon,
Title,
- ModalVariant, Modal, Spinner, Tooltip, Flex, FlexItem, ProgressStep, ProgressStepper
+ ModalVariant, Modal, Spinner, Tooltip, Flex, FlexItem, ToggleGroup, ToggleGroupItem
} from '@patternfly/react-core';
import '../designer/karavan.css';
import {MainToolbar} from "../MainToolbar";
@@ -44,6 +38,7 @@ import {PropertiesEditor} from "./PropertiesEditor";
import PendingIcon from "@patternfly/react-icons/dist/esm/icons/pending-icon";
import CheckCircleIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
import ExclamationCircleIcon from "@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon";
+import {ProjectHeader} from "./ProjectHeader";
interface Props {
project: Project,
@@ -58,12 +53,8 @@ interface State {
isUploadModalOpen: boolean,
isDeleteModalOpen: boolean,
isCreateModalOpen: boolean,
- isPushing: boolean,
- isBuilding: boolean,
fileToDelete?: ProjectFile,
- environments: string[],
- environment: string,
- key?: string,
+ mode: "design" | "code";
}
export class ProjectPage extends React.Component<Props, State> {
@@ -73,23 +64,12 @@ export class ProjectPage extends React.Component<Props, State> {
isUploadModalOpen: false,
isCreateModalOpen: false,
isDeleteModalOpen: false,
- isPushing: false,
- isBuilding: false,
files: [],
- environments: this.props.config.environments && Array.isArray(this.props.config.environments)
- ? Array.from(this.props.config.environments) : [],
- environment: this.props.config.environments && Array.isArray(this.props.config.environments)
- ? this.props.config.environments[0] : ''
+ mode: "design"
};
- interval: any;
componentDidMount() {
this.onRefresh();
- this.interval = setInterval(() => this.onRefreshStatus(), 3000);
- }
-
- componentWillUnmount() {
- clearInterval(this.interval);
}
onRefresh = () => {
@@ -107,18 +87,6 @@ export class ProjectPage extends React.Component<Props, State> {
}
}
- 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) => {
KaravanApi.postProjectFile(file, res => {
if (res.status === 200) {
@@ -129,20 +97,10 @@ export class ProjectPage extends React.Component<Props, State> {
})
}
- publish = () => {
- }
-
- copy = () => {
- }
-
copyToClipboard = (data: string) => {
navigator.clipboard.writeText(data);
}
- changeView = (view: "design" | "code") => {
- // this.setState({view: view});
- }
-
save = (name: string, code: string) => {
const file = this.state.file;
if (file) {
@@ -162,23 +120,31 @@ export class ProjectPage extends React.Component<Props, State> {
}
tools = () => {
- const isFile = this.state.file !== undefined;
+ const {file, mode} = this.state;
+ const isFile = file !== undefined;
+ const isYaml = file !== undefined && file.name.endsWith("yaml");
return <Toolbar id="toolbar-group-types">
- {isFile && <ToolbarContent>
- <ToolbarItem>
- <Button variant="secondary" icon={<DownloadIcon/>} onClick={e => this.download()}>Download</Button>
- </ToolbarItem>
- </ToolbarContent>}
- {!isFile && <ToolbarContent>
- <ToolbarItem>
- <Button variant={"primary"} icon={<PlusIcon/>}
- onClick={e => this.setState({isCreateModalOpen: true})}>Create</Button>
- </ToolbarItem>
- <ToolbarItem>
- <Button variant="secondary" icon={<UploadIcon/>}
- onClick={e => this.setState({isUploadModalOpen: true})}>Upload</Button>
- </ToolbarItem>
- </ToolbarContent>}
+ <ToolbarContent>
+ <Flex className="toolbar" direction={{default: "row"}}>
+ {isYaml && <FlexItem>
+ <ToggleGroup>
+ <ToggleGroupItem text="Design" buttonId="design" isSelected={mode === "design"} onChange={s => this.setState({mode:"design"})} />
+ <ToggleGroupItem text="Code" buttonId="code" isSelected={mode === "code"} onChange={s => this.setState({mode:"code"})} />
+ </ToggleGroup>
+ </FlexItem>}
+ {isFile && <FlexItem>
+ <Button variant="secondary" icon={<DownloadIcon/>} onClick={e => this.download()}>Download</Button>
+ </FlexItem>}
+ {!isFile && <FlexItem>
+ <Button variant={"primary"} icon={<PlusIcon/>}
+ onClick={e => this.setState({isCreateModalOpen: true})}>Create</Button>
+ </FlexItem>}
+ {!isFile && <FlexItem>
+ <Button variant="secondary" icon={<UploadIcon/>}
+ onClick={e => this.setState({isUploadModalOpen: true})}>Upload</Button>
+ </FlexItem>}
+ </Flex>
+ </ToolbarContent>
</Toolbar>
};
@@ -208,7 +174,6 @@ export class ProjectPage extends React.Component<Props, State> {
this.setState({
isUploadModalOpen: false,
isCreateModalOpen: false,
- isPushing: isPushing
});
this.onRefresh();
}
@@ -233,32 +198,6 @@ export class ProjectPage extends React.Component<Props, State> {
}
}
- push = (after?: () => void) => {
- this.setState({isPushing: true});
- KaravanApi.push(this.props.project, res => {
- console.log(res)
- if (res.status === 200 || res.status === 201) {
- this.setState({isPushing: false});
- after?.call(this);
- this.onRefresh();
- } else {
- // Todo notification
- }
- });
- }
-
- build = () => {
- this.setState({isBuilding: true});
- KaravanApi.tekton(this.props.project, this.state.environment, res => {
- console.log(res)
- if (res.status === 200 || res.status === 201) {
- this.setState({isBuilding: false});
- this.onRefresh();
- } else {
- // Todo notification
- }
- });
- }
getType = (name: string) => {
const extension = name.substring(name.lastIndexOf('.') + 1);
@@ -270,151 +209,6 @@ export class ProjectPage extends React.Component<Props, State> {
}
}
- pushButton = () => {
- const isPushing = this.state.isPushing;
- return (<Tooltip content="Commit and push to git" position={"left"}>
- <Button isLoading={isPushing ? true : undefined} isSmall variant="secondary"
- className="project-button"
- icon={!isPushing ? <PushIcon/> : <div></div>}
- onClick={e => this.push()}>
- {isPushing ? "..." : "Commit"}
- </Button>
- </Tooltip>)
- }
-
- buildButton = () => {
- const isDeploying = this.state.isBuilding;
- return (<Tooltip content="Commit, push, build and deploy" position={"left"}>
- <Button isLoading={isDeploying ? true : undefined} isSmall variant="secondary"
- className="project-button"
- icon={!isDeploying ? <BuildIcon/> : <div></div>}
- onClick={e => {
- this.push(() => this.build());
- }}>
- {isDeploying ? "..." : "Build"}
- </Button>
- </Tooltip>)
- }
-
- getProgressIcon(status?: 'pending' | 'progress' | 'done' | 'error') {
- switch (status) {
- case "pending":
- return <PendingIcon color={"grey"}/>;
- case "progress":
- return <Spinner isSVG size="md"/>
- case "done":
- return <CheckCircleIcon color={"green"}/>;
- case "error":
- return <ExclamationCircleIcon color={"red"}/>;
- default:
- return undefined;
- }
- }
-
- getCurrentStatus() {
- 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} = this.state;
- return (
- <Card>
- <CardBody isFilled>
- <Flex direction={{default: "row"}} alignContent={{default: "alignContentSpaceBetween"}}
- style={{width: "100%"}}>
- <FlexItem flex={{default: "flex_1"}}>
- <DescriptionList isHorizontal>
- <DescriptionListGroup>
- <DescriptionListTerm>Project ID</DescriptionListTerm>
- <DescriptionListDescription>{project?.projectId}</DescriptionListDescription>
- </DescriptionListGroup>
- <DescriptionListGroup>
- <DescriptionListTerm>Name</DescriptionListTerm>
- <DescriptionListDescription>{project?.name}</DescriptionListDescription>
- </DescriptionListGroup>
- <DescriptionListGroup>
- <DescriptionListTerm>Description</DescriptionListTerm>
- <DescriptionListDescription>{project?.description}</DescriptionListDescription>
- </DescriptionListGroup>
- </DescriptionList>
- </FlexItem>
- <FlexItem flex={{default: "flex_1"}}>
- <DescriptionList isHorizontal>
- <DescriptionListGroup>
- <DescriptionListTerm>Commit</DescriptionListTerm>
- <DescriptionListDescription>
- <Tooltip content={project?.lastCommit} position={"bottom"}>
- <Badge>{project?.lastCommit ? project?.lastCommit?.substr(0, 7) : "-"}</Badge>
- </Tooltip>
- </DescriptionListDescription>
- </DescriptionListGroup>
- <DescriptionListGroup>
- <DescriptionListTerm>Pipeline Run</DescriptionListTerm>
- <DescriptionListDescription>
- {this.getPipelineState()}
- </DescriptionListDescription>
- </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
- className={this.isUp(e) ? "badge-env-up" : ""}
- isRead>{e}</Badge></FlexItem>)}
- </Flex>
- </DescriptionListDescription>
- </DescriptionListGroup>
- </DescriptionList>
- </FlexItem>
- <FlexItem>
- <Flex direction={{default: "column"}}>
- <FlexItem>
- {this.pushButton()}
- </FlexItem>
- <FlexItem>
- {this.buildButton()}
- </FlexItem>
- <FlexItem>
- <Button isSmall style={{visibility: "hidden"}}>Refresh</Button>
- </FlexItem>
- </Flex>
- </FlexItem>
- </Flex>
- </CardBody>
- </Card>
- )
- }
-
getProjectFiles = () => {
const files = this.state.files;
return (
@@ -514,10 +308,12 @@ export class ProjectPage extends React.Component<Props, State> {
}
render() {
- const file = this.state.file;
+ const {file, mode} = this.state;
const isYaml = file !== undefined && file.name.endsWith("yaml");
const isProperties = file !== undefined && file.name.endsWith("properties");
const isCode = file !== undefined && (file.name.endsWith("java") || file.name.endsWith("groovy"));
+ const showDesigner = isYaml && mode === 'design';
+ const showEditor = isCode || (isYaml && mode === 'code');
return (
<PageSection className="kamelet-section project-page" padding={{default: 'noPadding'}}>
<PageSection className="tools-section" padding={{default: 'noPadding'}}>
@@ -526,11 +322,11 @@ export class ProjectPage extends React.Component<Props, State> {
{file === undefined &&
<PageSection isFilled className="kamelets-page"
padding={{default: file !== undefined ? 'noPadding' : 'padding'}}>
- {this.getProjectForm()}
+ {<ProjectHeader project={this.props.project} config={this.props.config}/>}
{this.getProjectFiles()}
</PageSection>}
- {isYaml && this.getDesigner()}
- {isCode && this.getEditor()}
+ {showDesigner && this.getDesigner()}
+ {showEditor && this.getEditor()}
{isProperties && this.getPropertiesEditor()}
<CreateFileModal project={this.props.project} isOpen={this.state.isCreateModalOpen}
onClose={this.closeModal}/>
diff --git a/karavan-designer/src/designer/KaravanDesigner.tsx b/karavan-designer/src/designer/KaravanDesigner.tsx
index bc0c755..6eb2ff8 100644
--- a/karavan-designer/src/designer/KaravanDesigner.tsx
+++ b/karavan-designer/src/designer/KaravanDesigner.tsx
@@ -155,9 +155,9 @@ export class KaravanDesigner extends React.Component<Props, State> {
{tab === 'beans' && <BeansDesigner integration={this.state.integration}
onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)}
dark={this.props.dark}/>}
- {tab === 'dependencies' && <DependenciesDesigner integration={this.state.integration}
- onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)}
- dark={this.props.dark}/>}
+ {/*{tab === 'dependencies' && <DependenciesDesigner integration={this.state.integration}*/}
+ {/* onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)}*/}
+ {/* dark={this.props.dark}/>}*/}
{tab === 'error' && <ErrorDesigner integration={this.state.integration}
onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)}
dark={this.props.dark}/>}