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 <ma...@talismancloud.io>
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>
+    )
+}

Reply via email to