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 2023/08/29 00:00:03 UTC
[camel-karavan] branch main updated: Forgotten files added
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 78a6de87 Forgotten files added
78a6de87 is described below
commit 78a6de87b9e042c6e2060bc4bca50463b7d2baaa
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Mon Aug 28 19:59:55 2023 -0400
Forgotten files added
---
.../main/webui/src/project/build/BuildStatus.tsx | 308 +++++++++++++++++++++
.../webui/src/project/build/ProjectBuildTab.tsx | 23 ++
2 files changed, 331 insertions(+)
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/build/BuildStatus.tsx b/karavan-web/karavan-app/src/main/webui/src/project/build/BuildStatus.tsx
new file mode 100644
index 00000000..c926c257
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/webui/src/project/build/BuildStatus.tsx
@@ -0,0 +1,308 @@
+import React, {useState} from 'react';
+import {
+ Button,
+ DescriptionList,
+ DescriptionListTerm,
+ DescriptionListGroup,
+ DescriptionListDescription, Spinner, Tooltip, Flex, FlexItem, LabelGroup, Label, Modal, Badge, CardBody, Card
+} from '@patternfly/react-core';
+import '../../designer/karavan.css';
+import {KaravanApi} from "../../api/KaravanApi";
+import BuildIcon from "@patternfly/react-icons/dist/esm/icons/build-icon";
+import RolloutIcon from "@patternfly/react-icons/dist/esm/icons/process-automation-icon";
+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";
+import ClockIcon from "@patternfly/react-icons/dist/esm/icons/clock-icon";
+import DeleteIcon from "@patternfly/react-icons/dist/esm/icons/times-circle-icon";
+import {ContainerStatus} from "../../api/ProjectModels";
+import {useLogStore, useProjectStore, useStatusesStore} from "../../api/ProjectStore";
+import {shallow} from "zustand/shallow";
+
+interface Props {
+ env: string,
+}
+
+export function BuildStatus (props: Props) {
+
+ const [project] = useProjectStore((s) => [s.project], shallow);
+ const [containers, deployments, camels, pipelineStatuses] =
+ useStatusesStore((s) => [s.containers, s.deployments, s.camels, s.pipelineStatuses], shallow);
+ const [isPushing, setIsPushing] = useState<boolean>(false);
+ const [isBuilding, setIsBuilding] = useState<boolean>(false);
+ const [isRolling, setIsRolling] = useState<boolean>(false);
+ const [showDeleteConfirmation, setShowDeleteConfirmation] = useState<boolean>(false);
+ const [deleteEntityType, setDeleteEntityType] = useState<'pod' | 'deployment' | 'pipelinerun'>('pod');
+ const [deleteEntityName, setDeleteEntityName] = useState<string>();
+ const [deleteEntityEnv, setDeleteEntityEnv] = useState<string>();
+
+ function deleteEntity(type: 'pod' | 'deployment' | 'pipelinerun', name: string, environment: string) {
+ switch (type) {
+ case "deployment":
+ KaravanApi.deleteDeployment(environment, name, (res: any) => {
+ // if (Array.isArray(res) && Array.from(res).length > 0)
+ // onRefresh();
+ });
+ break;
+ case "pod":
+ KaravanApi.deleteContainer(environment, 'project', name, (res: any) => {
+ // if (Array.isArray(res) && Array.from(res).length > 0)
+ // onRefresh();
+ });
+ break;
+ case "pipelinerun":
+ KaravanApi.stopPipelineRun(environment, name, (res: any) => {
+ // if (Array.isArray(res) && Array.from(res).length > 0)
+ // onRefresh();
+ });
+ break;
+ }
+ }
+
+ function build() {
+ setIsBuilding(true);
+ KaravanApi.pipelineRun(project, env, res => {
+ if (res.status === 200 || res.status === 201) {
+ setIsBuilding(false);
+ } else {
+ // Todo notification
+ }
+ });
+ }
+
+ function rollout() {
+ setIsRolling(true);
+ KaravanApi.rolloutDeployment(project.projectId, env, res => {
+ console.log(res)
+ if (res.status === 200 || res.status === 201) {
+ setIsRolling(false);
+ } else {
+ // Todo notification
+ }
+ });
+ }
+
+ function buildButton(env: string) {
+ const status = pipelineStatuses.filter(p => p.projectId === project.projectId).at(0);
+ const isRunning = status?.result === 'Running';
+ return (<Tooltip content="Start build pipeline" position={"left"}>
+ <Button isLoading={isBuilding ? true : undefined}
+ isDisabled={isBuilding || isRunning || isPushing}
+ size="sm"
+ variant="secondary"
+ className="project-button"
+ icon={!isBuilding ? <BuildIcon/> : <div></div>}
+ onClick={e => build()}>
+ {isBuilding ? "..." : "Build"}
+ </Button>
+ </Tooltip>)
+ }
+
+ function rolloutButton() {
+ return (<Tooltip content="Rollout deployment" position={"left"}>
+ <Button isLoading={isRolling ? true : undefined} size="sm" variant="secondary"
+ className="project-button"
+ icon={!isRolling ? <RolloutIcon/> : <div></div>}
+ onClick={e => rollout()}>
+ {isRolling ? "..." : "Rollout"}
+ </Button>
+ </Tooltip>)
+ }
+
+ function deleteDeploymentButton(env: string) {
+ return (<Tooltip content="Delete deployment" position={"left"}>
+ <Button size="sm" variant="secondary"
+ className="project-button"
+ icon={<DeleteIcon/>}
+ onClick={e => {
+ setShowDeleteConfirmation(true);
+ setDeleteEntityType("deployment");
+ setDeleteEntityEnv(env);
+ setDeleteEntityName(project?.projectId);
+ }}>
+ {"Delete"}
+ </Button>
+ </Tooltip>)
+ }
+
+ function getReplicasPanel(env: string) {
+ const deploymentStatus = deployments.find(d => d.name === project?.projectId);
+ const ok = (deploymentStatus && deploymentStatus?.readyReplicas > 0
+ && (deploymentStatus.unavailableReplicas === 0 || deploymentStatus.unavailableReplicas === undefined || deploymentStatus.unavailableReplicas === null)
+ && deploymentStatus?.replicas === deploymentStatus?.readyReplicas)
+ return (
+ <Flex justifyContent={{default: "justifyContentSpaceBetween"}} alignItems={{default: "alignItemsCenter"}}>
+ <FlexItem>
+ {deploymentStatus && <LabelGroup numLabels={3}>
+ <Tooltip content={"Ready Replicas / Replicas"} position={"left"}>
+ <Label icon={ok ? <UpIcon/> : <DownIcon/>}
+ color={ok ? "green" : "grey"}>{"Replicas: " + deploymentStatus.readyReplicas + " / " + deploymentStatus.replicas}</Label>
+ </Tooltip>
+ {deploymentStatus.unavailableReplicas > 0 &&
+ <Tooltip content={"Unavailable replicas"} position={"right"}>
+ <Label icon={<DownIcon/>} color={"red"}>{deploymentStatus.unavailableReplicas}</Label>
+ </Tooltip>
+ }
+ </LabelGroup>}
+ {deploymentStatus === undefined && <Label icon={<DownIcon/>} color={"grey"}>No deployments</Label>}
+ </FlexItem>
+ <FlexItem>{env === "dev" && deleteDeploymentButton(env)}</FlexItem>
+ </Flex>
+ )
+ }
+
+ function getPodsPanel(env: string) {
+ const podStatuses = containers.filter(d => d.projectId === project?.projectId);
+ return (
+ <Flex justifyContent={{default: "justifyContentSpaceBetween"}}
+ alignItems={{default: "alignItemsFlexStart"}}>
+ <FlexItem>
+ {podStatuses.length === 0 && <Label icon={<DownIcon/>} color={"grey"}>No pods</Label>}
+ <LabelGroup numLabels={2} isVertical>
+ {podStatuses.map((pod: ContainerStatus) => {
+ const ready = pod.state === 'running';
+ return (
+ <Tooltip key={pod.containerName} content={pod.state}>
+ <Label icon={ready ? <UpIcon/> : <DownIcon/>} color={ready ? "green" : "red"}>
+ <Button variant="link" className="labeled-button"
+ onClick={e => {
+ useLogStore.setState({
+ showLog: true,
+ type: 'container',
+ podName: pod.containerName
+ });
+ }}>
+ {pod.containerName}
+ </Button>
+ <Tooltip content={"Delete Pod"}>
+ <Button icon={<DeleteIcon/>}
+ className="labeled-button"
+ variant="link"
+ onClick={e => {
+ setShowDeleteConfirmation(true);
+ setDeleteEntityType("pod");
+ setDeleteEntityEnv(env);
+ setDeleteEntityName(pod.containerName);
+ }}></Button>
+ </Tooltip>
+ </Label>
+ </Tooltip>
+ )
+ }
+ )}
+ </LabelGroup>
+ </FlexItem>
+ <FlexItem>{env === "dev" && rolloutButton()}</FlexItem>
+ </Flex>
+ )
+ }
+
+ function getPipelineState(env: string) {
+ const status = pipelineStatuses.filter(p => p.projectId === project.projectId).at(0);
+ const pipeline = status?.pipelineName;
+ const pipelineResult = status?.result;
+ let lastPipelineRunTime = 0;
+ if (status?.startTime) {
+ const start: Date = new Date(status.startTime);
+ const finish: Date = status.completionTime !== undefined && status.completionTime !== null ? new Date(status.completionTime) : new Date();
+ lastPipelineRunTime = Math.round((finish.getTime() - start.getTime()) / 1000);
+ }
+ const showTime = lastPipelineRunTime && lastPipelineRunTime > 0;
+ const isRunning = pipelineResult === 'Running';
+ const isFailed = pipelineResult === 'Failed';
+ const isSucceeded = pipelineResult === 'Succeeded';
+ const color = isSucceeded ? "green" : (isFailed ? "red" : (isRunning ? "blue" : "grey"))
+ const icon = isSucceeded ? <UpIcon className="not-spinner"/> : <DownIcon className="not-spinner"/>
+ return (
+ <Flex justifyContent={{default: "justifyContentSpaceBetween"}} alignItems={{default: "alignItemsCenter"}}>
+ <FlexItem>
+ <Tooltip content={pipelineResult} position={"right"}>
+ <LabelGroup numLabels={2}>
+ <Label icon={isRunning ? <Spinner diameter="16px" className="spinner"/> : icon}
+ color={color}>
+ {pipeline
+ ? <Button className='labeled-button' variant="link" onClick={e =>
+ useLogStore.setState({showLog: true, type: 'pipeline', podName: pipeline})
+ }>
+ {pipeline}
+ </Button>
+ : "No pipeline"}
+ {isRunning && <Tooltip content={"Stop PipelineRun"}>
+ <Button
+ icon={<DeleteIcon/>}
+ className="labeled-button"
+ variant="link" onClick={e => {
+ setShowDeleteConfirmation(true);
+ setDeleteEntityType("pipelinerun");
+ setDeleteEntityEnv(env);
+ setDeleteEntityName(pipeline);
+ }}></Button>
+ </Tooltip>}
+ </Label>
+ {pipeline !== undefined && showTime === true && lastPipelineRunTime !== undefined &&
+ <Label icon={<ClockIcon className="not-spinner"/>}
+ color={color}>{lastPipelineRunTime + "s"}</Label>}
+ </LabelGroup>
+ </Tooltip>
+ </FlexItem>
+ <FlexItem>{env === "dev" && buildButton(env)}</FlexItem>
+ </Flex>
+ )
+ }
+
+ function getDeleteConfirmation() {
+ return (<Modal
+ className="modal-delete"
+ title="Confirmation"
+ isOpen={showDeleteConfirmation}
+ onClose={() => setShowDeleteConfirmation(false)}
+ actions={[
+ <Button key="confirm" variant="primary" onClick={e => {
+ if (deleteEntityEnv && deleteEntityName && deleteEntity) {
+ deleteEntity(deleteEntityType, deleteEntityName, deleteEntityEnv);
+ setShowDeleteConfirmation(false);
+ }
+ }}>Delete
+ </Button>,
+ <Button key="cancel" variant="link"
+ onClick={e => setShowDeleteConfirmation(false)}>Cancel</Button>
+ ]}
+ onEscapePress={e => setShowDeleteConfirmation(false)}>
+ <div>{"Delete " + deleteEntity + " " + deleteEntityName + "?"}</div>
+ </Modal>)
+ }
+
+ const env = props.env;
+ return (
+ <Card className="project-status">
+ <CardBody>
+ <DescriptionList isHorizontal>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Environment</DescriptionListTerm>
+ <DescriptionListDescription>
+ <Badge className="badge">{env}</Badge>
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Pipeline</DescriptionListTerm>
+ <DescriptionListDescription>
+ {getPipelineState(env)}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Deployment</DescriptionListTerm>
+ <DescriptionListDescription>
+ {getReplicasPanel(env)}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Pods</DescriptionListTerm>
+ <DescriptionListDescription>
+ {getPodsPanel(env)}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ </DescriptionList>
+ </CardBody>
+ {showDeleteConfirmation && getDeleteConfirmation()}
+ </Card>
+ )
+}
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/build/ProjectBuildTab.tsx b/karavan-web/karavan-app/src/main/webui/src/project/build/ProjectBuildTab.tsx
new file mode 100644
index 00000000..89d4bbbf
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/webui/src/project/build/ProjectBuildTab.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import '../../designer/karavan.css';
+import {BuildStatus} from "./BuildStatus";
+import {PageSection} from "@patternfly/react-core";
+import {useAppConfigStore, useProjectStore} from "../../api/ProjectStore";
+
+
+export function ProjectBuildTab () {
+
+ const {config} = useAppConfigStore();
+ const {project} = useProjectStore();
+
+ return (
+ <PageSection className="project-tab-panel" padding={{default: "padding"}}>
+ <div className="project-operations">
+ {/*{["dev", "test", "prod"].map(env =>*/}
+ {config.environments.map(env =>
+ <BuildStatus env={env}/>
+ )}
+ </div>
+ </PageSection>
+ )
+}