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 275bc3a1 Added missed files
275bc3a1 is described below
commit 275bc3a16ea941f232513c327e22ecfad30c4aa7
Author: Marat Gubaidullin <[email protected]>
AuthorDate: Mon Nov 27 16:55:36 2023 -0500
Added missed files
---
.../src/main/webui/src/project/ProjectPanel.tsx | 4 +-
.../main/webui/src/project/builder/BuildPanel.tsx | 207 ++++++++++++++++++
.../main/webui/src/project/builder/ImagesPanel.tsx | 238 +++++++++++++++++++++
.../webui/src/project/builder/ProjectBuildTab.tsx | 32 +++
4 files changed, 479 insertions(+), 2 deletions(-)
diff --git
a/karavan-web/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
b/karavan-web/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
index 577b400c..d9b503b7 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
@@ -25,10 +25,10 @@ import {FilesTab} from "./files/FilesTab";
import {useAppConfigStore, useProjectStore} from "../api/ProjectStore";
import {DashboardTab} from "./dashboard/DashboardTab";
import {TraceTab} from "./trace/TraceTab";
-import {ProjectBuildTab} from "./build/ProjectBuildTab";
+import {ProjectBuildTab} from "./builder/ProjectBuildTab";
import {ProjectService} from "../api/ProjectService";
import {shallow} from "zustand/shallow";
-import {ImagesPanel} from "./build/ImagesPanel";
+import {ImagesPanel} from "./builder/ImagesPanel";
import {ProjectContainerTab} from "./container/ProjectContainerTab";
import {ProjectTopologyTab} from "./topology/ProjectTopologyTab";
diff --git
a/karavan-web/karavan-app/src/main/webui/src/project/builder/BuildPanel.tsx
b/karavan-web/karavan-app/src/main/webui/src/project/builder/BuildPanel.tsx
new file mode 100644
index 00000000..f1dedba6
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/webui/src/project/builder/BuildPanel.tsx
@@ -0,0 +1,207 @@
+/*
+ * 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.
+ */
+
+import React, {useEffect, 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 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 TagIcon from "@patternfly/react-icons/dist/esm/icons/tag-icon";
+import DeleteIcon from
"@patternfly/react-icons/dist/esm/icons/times-circle-icon";
+import {useAppConfigStore, useLogStore, useProjectStore, useStatusesStore}
from "../../api/ProjectStore";
+import {shallow} from "zustand/shallow";
+import {EventBus} from "../../designer/utils/EventBus";
+import {ProjectService} from "../../api/ProjectService";
+
+export function BuildPanel () {
+
+ const [config] = useAppConfigStore((state) => [state.config], shallow)
+ const [project] = useProjectStore((s) => [s.project], shallow);
+ const [setShowLog] = useLogStore((s) => [s.setShowLog], shallow);
+ const [containers, deployments, camels] =
+ useStatusesStore((s) => [s.containers, s.deployments, s.camels],
shallow);
+ const [isPushing, setIsPushing] = useState<boolean>(false);
+ const [isBuilding, setIsBuilding] = useState<boolean>(false);
+ const [showDeleteConfirmation, setShowDeleteConfirmation] =
useState<boolean>(false);
+ const [deleteEntityName, setDeleteEntityName] = useState<string>();
+ const [tag, setTag] = useState<string>(
+ new Date().toISOString().substring(0,19).replaceAll(':',
'').replaceAll('-', '')
+ );
+
+ function deleteEntity() {
+ const buildName = getBuildName();
+ if (buildName) {
+ KaravanApi.manageContainer(config.environment, 'build', buildName,
'delete', res => {
+ EventBus.sendAlert("Container deleted", "Container " +
buildName + " deleted", 'info')
+ setShowLog(false, 'container', undefined)
+ });
+ }
+ }
+
+ function build() {
+ setIsBuilding(true);
+ setShowLog(false,'none')
+ KaravanApi.buildProject(project, tag, res => {
+ if (res.status === 200 || res.status === 201) {
+ setIsBuilding(false);
+ } else {
+ console.log(res);
+ EventBus.sendAlert("Error", (res as any)?.response?.data,
'danger')
+ }
+ });
+ }
+
+ function buildButton() {
+ const status = containers.filter(c => c.projectId ===
project.projectId && c.type === 'build').at(0);
+ const isRunning = status?.state === 'running';
+ return (<Tooltip content="Start build" 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 getContainerStatus() {
+ return containers.filter(c => c.projectId === project.projectId &&
c.type === 'build').at(0);
+ }
+
+ function getBuildName() {
+ const status = getContainerStatus();
+ return status?.containerName;
+ }
+
+ function getBuildState() {
+ const status = getContainerStatus();
+ const buildName = getBuildName();
+ const state = status?.state;
+ let buildTime = 0;
+ if (status?.created) {
+ const start: Date = new Date(status.created);
+ const finish: Date = status.finished !== undefined &&
status.finished !== null ? new Date(status.finished) : new Date();
+ buildTime = Math.round((finish.getTime() - start.getTime()) /
1000);
+ }
+ const showTime = buildTime && buildTime > 0;
+ const isRunning = state === 'running';
+ const isExited = state === 'exited';
+ const isFailed = state === 'failed';
+ const color = (isRunning ? "blue" : (isFailed ? "red" : "grey"));
+ const icon = isExited ? <UpIcon className="not-spinner"/> : <DownIcon
className="not-spinner"/>
+ return (
+ <Flex justifyContent={{default: "justifyContentSpaceBetween"}}
alignItems={{default: "alignItemsCenter"}}>
+ <FlexItem>
+ <LabelGroup numLabels={3}>
+ <Label icon={isRunning ? <Spinner diameter="16px"
className="spinner"/> : icon}
+ color={color}>
+ {buildName
+ ? <Button className='labeled-button'
variant="link" onClick={e =>
+ useLogStore.setState({showLog: true, type:
'build', podName: buildName})
+ }>
+ {buildName}
+ </Button>
+ : "No builder"}
+ {status !== undefined && <Tooltip content={"Delete
build"}>
+ <Button
+ icon={<DeleteIcon/>}
+ className="labeled-button"
+ variant="link" onClick={e => {
+ setShowDeleteConfirmation(true);
+ setDeleteEntityName(buildName);
+ }}></Button>
+ </Tooltip>}
+ </Label>
+ {buildName !== undefined && showTime === true &&
buildTime !== undefined &&
+ <Label icon={<ClockIcon className="not-spinner"/>}
+ color={color}>{buildTime + "s"}</Label>}
+ </LabelGroup>
+ </FlexItem>
+ <FlexItem>{buildButton()}</FlexItem>
+ </Flex>
+ )
+ }
+
+ function getBuildTag() {
+ const status = containers.filter(c => c.projectId ===
project.projectId && c.type === 'build').at(0);
+ const state = status?.state;
+ const isRunning = state === 'running';
+ const isExited = state === 'exited';
+ const color = isExited ? "grey" : (isRunning ? "blue" : "grey");
+ return (
+ <Label isEditable={!isRunning} onEditComplete={(_, v) => setTag(v)}
+ icon={<TagIcon className="not-spinner"/>}
+ color={color}>{tag}</Label>
+ )
+ }
+
+ function getDeleteConfirmation() {
+ return (<Modal
+ className="modal-delete"
+ title="Confirmation"
+ isOpen={showDeleteConfirmation}
+ onClose={() => setShowDeleteConfirmation(false)}
+ actions={[
+ <Button key="confirm" variant="primary" onClick={e => {
+ if (deleteEntityName && deleteEntity) {
+ deleteEntity();
+ setShowDeleteConfirmation(false);
+ }
+ }}>Delete
+ </Button>,
+ <Button key="cancel" variant="link"
+ onClick={e =>
setShowDeleteConfirmation(false)}>Cancel</Button>
+ ]}
+ onEscapePress={e => setShowDeleteConfirmation(false)}>
+ <div>{"Delete build " + deleteEntityName + "?"}</div>
+ </Modal>)
+ }
+
+ return (
+ <Card className="project-status">
+ <CardBody>
+ <DescriptionList isHorizontal
horizontalTermWidthModifier={{default: '20ch'}}>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Tag</DescriptionListTerm>
+ <DescriptionListDescription>
+ {getBuildTag()}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Build
container</DescriptionListTerm>
+ <DescriptionListDescription>
+ {getBuildState()}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ </DescriptionList>
+ </CardBody>
+ {showDeleteConfirmation && getDeleteConfirmation()}
+ </Card>
+ )
+}
diff --git
a/karavan-web/karavan-app/src/main/webui/src/project/builder/ImagesPanel.tsx
b/karavan-web/karavan-app/src/main/webui/src/project/builder/ImagesPanel.tsx
new file mode 100644
index 00000000..22c22a93
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/webui/src/project/builder/ImagesPanel.tsx
@@ -0,0 +1,238 @@
+/*
+ * 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.
+ */
+
+import React, {useState} from 'react';
+import {
+ Button,
+ Tooltip,
+ Flex,
+ FlexItem,
+ Modal,
+ Panel,
+ PanelHeader,
+ TextContent,
+ Text,
+ TextVariants,
+ Bullseye, EmptyState, EmptyStateVariant, EmptyStateHeader, EmptyStateIcon,
PageSection, Switch, TextInput
+} from '@patternfly/react-core';
+import '../../designer/karavan.css';
+import {useFilesStore, useProjectStore} from "../../api/ProjectStore";
+import {shallow} from "zustand/shallow";
+import {Table} from "@patternfly/react-table/deprecated";
+import {Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
+import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon";
+import SetIcon from "@patternfly/react-icons/dist/esm/icons/check-icon";
+import {KaravanApi} from "../../api/KaravanApi";
+import {ProjectService} from "../../api/ProjectService";
+import {ServicesYaml} from "../../api/ServiceModels";
+import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
+import {EventBus} from "../../designer/utils/EventBus";
+
+export function ImagesPanel () {
+
+ const [project, images] = useProjectStore((s) => [s.project, s.images],
shallow);
+ const [files] = useFilesStore((s) => [s.files], shallow);
+ const [showSetConfirmation, setShowSetConfirmation] =
useState<boolean>(false);
+ const [showDeleteConfirmation, setShowDeleteConfirmation] =
useState<boolean>(false);
+ const [imageName, setImageName] = useState<string>();
+ const [commitChanges, setCommitChanges] = useState<boolean>(false);
+ const [commitMessage, setCommitMessage] = useState('');
+
+ function setProjectImage() {
+ if (imageName) {
+ KaravanApi.setProjectImage(project.projectId, imageName,
commitChanges, commitMessage, (res: any) => {
+ ProjectService.refreshProjectData(project.projectId);
+ });
+ }
+ }
+
+ function getProjectImage(): string | undefined {
+ const file = files.filter(f => f.name === 'docker-compose.yaml').at(0);
+ if (file) {
+ const dc = ServicesYaml.yamlToServices(file.code);
+ const dcs = dc.services.filter(s => s.container_name ===
project.projectId).at(0);
+ return dcs?.image;
+ }
+ return undefined;
+ }
+
+ function getSetConfirmation() {
+ const index = imageName?.lastIndexOf(":");
+ const name = imageName?.substring(0, index);
+ const tag = index ? imageName?.substring(index+1) : "";
+ return (<Modal
+ className="modal-delete"
+ title="Confirmation"
+ isOpen={showSetConfirmation}
+ onClose={() => setShowSetConfirmation(false)}
+ actions={[
+ <Button key="confirm" variant="primary" onClick={e => {
+ if (imageName) {
+ setProjectImage();
+ setShowSetConfirmation(false);
+ setCommitChanges(false);
+ }
+ }}>Set
+ </Button>,
+ <Button key="cancel" variant="link"
+ onClick={e => {
+ setShowSetConfirmation(false);
+ setCommitChanges(false);
+ }}>Cancel</Button>
+ ]}
+ onEscapePress={e => setShowSetConfirmation(false)}>
+ <Flex direction={{default:"column"}}
justifyContent={{default:"justifyContentFlexStart"}}>
+ <FlexItem>
+ <div>{"Set image for project " + project.projectId +
":"}</div>
+ <div>{"Name: " + name}</div>
+ <div>{"Tag: " + tag}</div>
+ </FlexItem>
+ <FlexItem>
+ <Switch
+ id="commit-switch"
+ label="Commit changes"
+ isChecked={commitChanges}
+ onChange={(event, checked) =>
setCommitChanges(checked)}
+ isReversed
+ />
+ </FlexItem>
+ {commitChanges && <FlexItem>
+ <TextInput value={commitMessage} type="text"
+ onChange={(_, value) => setCommitMessage(value)}
+ aria-label="commit message"/>
+ </FlexItem>}
+ </Flex>
+ </Modal>)
+ }
+
+ function getDeleteConfirmation() {
+ return (<Modal
+ className="modal-delete"
+ title="Confirmation"
+ isOpen={showDeleteConfirmation}
+ onClose={() => setShowDeleteConfirmation(false)}
+ actions={[
+ <Button key="confirm" variant="primary" onClick={e => {
+ if (imageName) {
+ KaravanApi.deleteImage(imageName, () => {
+ EventBus.sendAlert("Image deleted", "Image " +
imageName + " deleted", 'info');
+ setShowDeleteConfirmation(false);
+ });
+ }
+ }}>Delete
+ </Button>,
+ <Button key="cancel" variant="link"
+ onClick={e =>
setShowDeleteConfirmation(false)}>Cancel</Button>
+ ]}
+ onEscapePress={e => setShowDeleteConfirmation(false)}>
+ <div>{"Delete image:"}</div>
+ <div>{imageName}</div>
+ </Modal>)
+ }
+
+ const projectImage = getProjectImage();
+ return (
+ <PageSection className="project-tab-panel project-images-panel"
padding={{default: "padding"}}>
+ <Panel>
+ <PanelHeader>
+ <Flex direction={{default: "row"}}
justifyContent={{default:"justifyContentFlexStart"}}>
+ <FlexItem>
+ <TextContent>
+ <Text component={TextVariants.h6}>Images</Text>
+ </TextContent>
+ </FlexItem>
+ <FlexItem>
+
+ </FlexItem>
+ </Flex>
+ </PanelHeader>
+ </Panel>
+ <Table aria-label="Images" variant={"compact"} className={"table"}>
+ <Thead>
+ <Tr>
+ <Th key='status' width={10}></Th>
+ <Th key='image' width={20}>Image</Th>
+ <Th key='tag' width={10}>Tag</Th>
+ <Th key='actions' width={10}></Th>
+ </Tr>
+ </Thead>
+ <Tbody>
+ {images.map(image => {
+ const index = image.lastIndexOf(":");
+ const name = image.substring(0, index);
+ const tag = image.substring(index+1);
+ return <Tr key={image}>
+ <Td modifier={"fitContent"} >
+ {image === projectImage ? <SetIcon/> : <div/>}
+ </Td>
+ <Td>
+ {name}
+ </Td>
+ <Td>
+ {tag}
+ </Td>
+ <Td modifier={"fitContent"} isActionCell>
+ <Flex direction={{default: "row"}}
justifyContent={{default: "justifyContentFlexEnd"}}
+ spaceItems={{default: 'spaceItemsNone'}}>
+ <FlexItem>
+ <Tooltip content={"Delete image"}
position={"bottom"}>
+ <Button variant={"plain"}
+ icon={<DeleteIcon/>}
+ isDisabled={image ===
projectImage}
+ onClick={e => {
+ setImageName(image);
+
setShowDeleteConfirmation(true);
+ }}>
+ </Button>
+ </Tooltip>
+ </FlexItem>
+ <FlexItem>
+ <Tooltip content="Set project image"
position={"bottom"}>
+ <Button style={{padding: '0'}}
+ variant={"plain"}
+ isDisabled={image ===
projectImage}
+ onClick={e => {
+ setImageName(image);
+
setCommitMessage(commitMessage === '' ? new Date().toLocaleString() :
commitMessage);
+
setShowSetConfirmation(true);
+ }}>
+ <SetIcon/>
+ </Button>
+ </Tooltip>
+ </FlexItem>
+ </Flex>
+ </Td>
+ </Tr>
+ })}
+ {images.length === 0 &&
+ <Tr>
+ <Td colSpan={8}>
+ <Bullseye>
+ <EmptyState variant={EmptyStateVariant.sm}>
+ <EmptyStateHeader titleText="No
results found" icon={<EmptyStateIcon icon={SearchIcon}/>} headingLevel="h2" />
+ </EmptyState>
+ </Bullseye>
+ </Td>
+ </Tr>
+ }
+ </Tbody>
+ </Table>
+ {showSetConfirmation && getSetConfirmation()}
+ {showDeleteConfirmation && getDeleteConfirmation()}
+ </PageSection>
+ )
+}
diff --git
a/karavan-web/karavan-app/src/main/webui/src/project/builder/ProjectBuildTab.tsx
b/karavan-web/karavan-app/src/main/webui/src/project/builder/ProjectBuildTab.tsx
new file mode 100644
index 00000000..31e6b19a
--- /dev/null
+++
b/karavan-web/karavan-app/src/main/webui/src/project/builder/ProjectBuildTab.tsx
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+import React from 'react';
+import '../../designer/karavan.css';
+import {BuildPanel} from "./BuildPanel";
+import {PageSection} from "@patternfly/react-core";
+
+export function ProjectBuildTab () {
+
+ return (
+ <PageSection className="project-tab-panel project-build-panel"
padding={{default: "padding"}}>
+ <div>
+ <BuildPanel/>
+ </div>
+ </PageSection>
+ )
+}