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
commit d20456971b887a99f266d6b1176ab3de0ef169a6 Author: Marat Gubaidullin <ma...@talismancloud.io> AuthorDate: Tue Feb 6 18:18:02 2024 -0500 Fix #1108 --- karavan-designer/public/example/demo.camel.yaml | 25 +++++ karavan-designer/src/App.tsx | 2 +- .../src/designer/property/DslProperties.tsx | 1 - .../src/topology/TopologyPropertiesPanel.tsx | 103 ++++++++++++++++----- karavan-designer/src/topology/TopologyStore.ts | 8 ++ karavan-designer/src/topology/TopologyTab.tsx | 5 +- karavan-designer/src/topology/topology.css | 12 ++- karavan-space/src/designer/icons/KaravanIcons.tsx | 47 ++-------- karavan-space/src/designer/karavan.css | 3 + .../src/designer/property/DslProperties.tsx | 1 - karavan-space/src/knowledgebase/eip/EipTab.tsx | 43 +++++++-- .../src/topology/TopologyPropertiesPanel.tsx | 103 ++++++++++++++++----- karavan-space/src/topology/TopologyStore.ts | 8 ++ karavan-space/src/topology/TopologyTab.tsx | 5 +- karavan-space/src/topology/topology.css | 16 +++- .../webui/src/designer/property/DslProperties.tsx | 1 - .../webui/src/topology/TopologyPropertiesPanel.tsx | 103 ++++++++++++++++----- .../src/main/webui/src/topology/TopologyStore.ts | 8 ++ .../src/main/webui/src/topology/TopologyTab.tsx | 5 +- .../src/main/webui/src/topology/topology.css | 12 ++- 20 files changed, 387 insertions(+), 124 deletions(-) diff --git a/karavan-designer/public/example/demo.camel.yaml b/karavan-designer/public/example/demo.camel.yaml index 9cb87f08..9d74d5d4 100644 --- a/karavan-designer/public/example/demo.camel.yaml +++ b/karavan-designer/public/example/demo.camel.yaml @@ -39,6 +39,31 @@ from: id: from-6aee uri: timer + steps: + - to: + id: to-2df4 + uri: direct + parameters: + name: first-firect + - to: + id: to-e017 + uri: direct + parameters: + name: second_direct +- route: + id: first-firect + from: + id: from-f155 + uri: direct + parameters: + name: first-firect +- route: + id: second_direct + from: + id: from-7ce0 + uri: direct + parameters: + name: second_direct # steps: # - marshal: diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx index a6613131..c7a7e461 100644 --- a/karavan-designer/src/App.tsx +++ b/karavan-designer/src/App.tsx @@ -69,7 +69,7 @@ interface State { class App extends React.Component<Props, State> { public state: State = { - pageId: "designer", + pageId: "topology", name: 'example.yaml', key: '', yaml: '' diff --git a/karavan-designer/src/designer/property/DslProperties.tsx b/karavan-designer/src/designer/property/DslProperties.tsx index ed56d2eb..656d816c 100644 --- a/karavan-designer/src/designer/property/DslProperties.tsx +++ b/karavan-designer/src/designer/property/DslProperties.tsx @@ -44,7 +44,6 @@ import {shallow} from "zustand/shallow"; import {usePropertiesHook} from "./usePropertiesHook"; import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil"; import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; -import QuestionIcon from '@patternfly/react-icons/dist/esm/icons/question-icon'; import {ComponentApi} from "karavan-core/lib/api/ComponentApi"; import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; diff --git a/karavan-designer/src/topology/TopologyPropertiesPanel.tsx b/karavan-designer/src/topology/TopologyPropertiesPanel.tsx index 8387ce1f..67edb46c 100644 --- a/karavan-designer/src/topology/TopologyPropertiesPanel.tsx +++ b/karavan-designer/src/topology/TopologyPropertiesPanel.tsx @@ -20,32 +20,93 @@ import {shallow} from "zustand/shallow"; import {TopologySideBar} from "@patternfly/react-topology"; import {useTopologyStore} from "./TopologyStore"; import {DslProperties} from "../designer/property/DslProperties"; -import {Button, Flex, FlexItem, Text, Tooltip, TooltipPosition} from "@patternfly/react-core"; +import { + Button, DescriptionList, + DescriptionListDescription, DescriptionListGroup, DescriptionListTerm, + Flex, + FlexItem, Panel, PanelHeader, PanelMain, PanelMainBody, + Text, TextContent, TextVariants, + Tooltip, + TooltipPosition +} from "@patternfly/react-core"; import CloseIcon from "@patternfly/react-icons/dist/esm/icons/times-icon"; interface Props { onSetFile: (fileName: string) => void } -export function TopologyPropertiesPanel (props: Props) { +export function TopologyPropertiesPanel(props: Props) { - const [selectedIds, setSelectedIds, fileName] = useTopologyStore((s) => - [s.selectedIds, s.setSelectedIds, s.fileName], shallow); + const [selectedIds, setSelectedIds, fileName, nodeData] = useTopologyStore((s) => + [s.selectedIds, s.setSelectedIds, s.fileName, s.nodeData], shallow); + + console.log(nodeData) + + function isRoute() { + if (nodeData && nodeData.type === 'route') { + const uri: string = nodeData?.step?.from.uri || ''; + return uri !== undefined; + } + return false; + } + + function isKamelet() { + if (nodeData && nodeData.type === 'step') { + const uri: string = nodeData?.step?.uri || ''; + return uri.startsWith("kamelet"); + } + return false; + } + + function getFromInfo() { + if (isRoute()) { + const uri: string = nodeData?.step?.from.uri || ''; + const name: string = nodeData?.step?.from.parameters?.name || ''; + if (['direct','seda'].includes(uri)) { + return uri.concat(":").concat(name); + } else { + return uri; + } + } + return "" + } + + function getTitle () { + return isRoute() ? "Route" : (isKamelet() ? "Kamelet" : "Component"); + } function getHeader() { return ( - <Flex className="properties-header" direction={{default: "row"}} justifyContent={{default: "justifyContentFlexStart"}}> + <Flex direction={{default: "row"}} justifyContent={{default: "justifyContentFlexStart"}}> <FlexItem spacer={{ default: 'spacerNone' }}> - <Text>Filename:</Text> - </FlexItem> - <FlexItem> - <Button variant="link" onClick={event => { - if (fileName) { - props.onSetFile(fileName); - } - }} - >{fileName} - </Button> + <Panel> + <PanelHeader> + <TextContent> + <Text component={TextVariants.h3}>{getTitle()}</Text> + </TextContent> + </PanelHeader> + <PanelMain> + <PanelMainBody> + <DescriptionList isHorizontal> + <DescriptionListGroup> + <DescriptionListTerm>File</DescriptionListTerm> + <DescriptionListDescription> + <Button className="file-button" variant="link" onClick={_ => { + if (fileName) { + props.onSetFile(fileName); + } + }}>{fileName} + </Button> + </DescriptionListDescription> + </DescriptionListGroup> + {isRoute() && <DescriptionListGroup> + <DescriptionListTerm>From</DescriptionListTerm> + <DescriptionListDescription>{getFromInfo()}</DescriptionListDescription> + </DescriptionListGroup>} + </DescriptionList> + </PanelMainBody> + </PanelMain> + </Panel> </FlexItem> <FlexItem align={{ default: 'alignRight' }}> <Tooltip content={"Close"} position={TooltipPosition.top}> @@ -58,11 +119,11 @@ export function TopologyPropertiesPanel (props: Props) { return ( <TopologySideBar - className="topology-sidebar" - show={selectedIds.length > 0} - header={getHeader()} - > - <DslProperties designerType={'routes'}/> - </TopologySideBar> + className="topology-sidebar" + show={selectedIds.length > 0} + header={getHeader()} + > + <DslProperties designerType={'routes'}/> + </TopologySideBar> ) } diff --git a/karavan-designer/src/topology/TopologyStore.ts b/karavan-designer/src/topology/TopologyStore.ts index 73aead9f..4e9ee643 100644 --- a/karavan-designer/src/topology/TopologyStore.ts +++ b/karavan-designer/src/topology/TopologyStore.ts @@ -35,6 +35,8 @@ interface TopologyState { setFileName: (fileName?: string) => void ranker: string setRanker: (ranker: string) => void + nodeData: any + setNodeData: (nodeData: any) => void } export const useTopologyStore = createWithEqualityFn<TopologyState>((set) => ({ @@ -55,4 +57,10 @@ export const useTopologyStore = createWithEqualityFn<TopologyState>((set) => ({ return {ranker: ranker}; }); }, + nodeData: undefined, + setNodeData: (nodeData: any) => { + set((state: TopologyState) => { + return {nodeData: nodeData}; + }); +}, }), shallow) diff --git a/karavan-designer/src/topology/TopologyTab.tsx b/karavan-designer/src/topology/TopologyTab.tsx index 727905cc..1f2d165e 100644 --- a/karavan-designer/src/topology/TopologyTab.tsx +++ b/karavan-designer/src/topology/TopologyTab.tsx @@ -47,8 +47,8 @@ interface Props { export function TopologyTab(props: Props) { - const [selectedIds, setSelectedIds, setFileName, ranker, setRanker] = useTopologyStore((s) => - [s.selectedIds, s.setSelectedIds, s.setFileName, s.ranker, s.setRanker], shallow); + const [selectedIds, setSelectedIds, setFileName, ranker, setRanker, setNodeData] = useTopologyStore((s) => + [s.selectedIds, s.setSelectedIds, s.setFileName, s.ranker, s.setRanker, s.setNodeData], shallow); const [setSelectedStep] = useDesignerStore((s) => [s.setSelectedStep], shallow) function setTopologySelected(model: Model, ids: string []) { @@ -57,6 +57,7 @@ export function TopologyTab(props: Props) { const node = model.nodes?.filter(node => node.id === ids[0]); if (node && node.length > 0) { const data = node[0].data; + setNodeData(data); setFileName(data.fileName) if (data.step) { setSelectedStep(data.step) diff --git a/karavan-designer/src/topology/topology.css b/karavan-designer/src/topology/topology.css index c67f1f6b..24fe7679 100644 --- a/karavan-designer/src/topology/topology.css +++ b/karavan-designer/src/topology/topology.css @@ -41,8 +41,8 @@ overflow: auto; } -.karavan .topology-panel .properties-header { - padding: 10px; +.karavan .topology-panel .properties .headers { + display: none; } .karavan .topology-panel .common-node .icon { @@ -50,6 +50,14 @@ width: 32px; } +.karavan .topology-panel .pf-v5-c-panel__header { + padding-bottom: 0; +} + +.karavan .topology-panel .file-button { + padding: 0; +} + .karavan .topology-sidebar .pf-topology-side-bar__header { margin-right: 0; } diff --git a/karavan-space/src/designer/icons/KaravanIcons.tsx b/karavan-space/src/designer/icons/KaravanIcons.tsx index b0b8042f..d1d06505 100644 --- a/karavan-space/src/designer/icons/KaravanIcons.tsx +++ b/karavan-space/src/designer/icons/KaravanIcons.tsx @@ -295,16 +295,13 @@ export function getDesignerIcon(icon: string): React.JSX.Element { ) if (icon === 'routes') return ( <svg className="top-icon" width="32px" height="32px" viewBox="0 0 32 32" id="icon"> - <defs> - <style>{".cls-1{fill:none;}"}</style> - </defs> <path d="M29,10H24v2h5v6H22v2h3v2.142a4,4,0,1,0,2,0V20h2a2.0027,2.0027,0,0,0,2-2V12A2.0023,2.0023,0,0,0,29,10ZM28,26a2,2,0,1,1-2-2A2.0027,2.0027,0,0,1,28,26Z"/> <path d="M19,6H14V8h5v6H12v2h3v6.142a4,4,0,1,0,2,0V16h2a2.0023,2.0023,0,0,0,2-2V8A2.0023,2.0023,0,0,0,19,6ZM18,26a2,2,0,1,1-2-2A2.0027,2.0027,0,0,1,18,26Z"/> <path d="M9,2H3A2.002,2.002,0,0,0,1,4v6a2.002,2.002,0,0,0,2,2H5V22.142a4,4,0,1,0,2,0V12H9a2.002,2.002,0,0,0,2-2V4A2.002,2.002,0,0,0,9,2ZM8,26a2,2,0,1,1-2-2A2.0023,2.0023,0,0,1,8,26ZM3,10V4H9l.0015,6Z"/> - <rect id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>" className="cls-1" width="32" + <rect id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>" fill='none' width="32" height="32"/> </svg>) if (icon === 'route') return ( @@ -335,9 +332,6 @@ export function getDesignerIcon(icon: string): React.JSX.Element { ) if (icon === 'beans') return ( <svg className="top-icon" width="32px" height="32px" viewBox="0 0 32 32" id="icon"> - <defs> - <style>{".cls-1 {fill: none;}"}</style> - </defs> <title>data--1</title> <rect x="15" y="6" width="13" height="2"/> <rect x="15" y="24" width="13" height="2"/> @@ -347,15 +341,12 @@ export function getDesignerIcon(icon: string): React.JSX.Element { <path d="M25,20a4,4,0,1,1,4-4A4,4,0,0,1,25,20Zm0-6a2,2,0,1,0,2,2A2,2,0,0,0,25,14Z" transform="translate(0 0)"/> <g id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>"> - <rect className="cls-1" width="32" height="32"/> + <rect fill='none' width="32" height="32"/> </g> </svg> ) if (icon === 'dependencies') return ( <svg className="top-icon" width="32px" height="32px" viewBox="0 0 32 32" id="icon"> - <defs> - <style>{".cls-1 {fill: none;}"}</style> - </defs> <title>application</title> <path d="M16,18H6a2,2,0,0,1-2-2V6A2,2,0,0,1,6,4H16a2,2,0,0,1,2,2V16A2,2,0,0,1,16,18ZM6,6V16H16V6Z" transform="translate(0 0)"/> @@ -366,7 +357,7 @@ export function getDesignerIcon(icon: string): React.JSX.Element { <path d="M16,22v4H12V22h4m0-2H12a2,2,0,0,0-2,2v4a2,2,0,0,0,2,2h4a2,2,0,0,0,2-2V22a2,2,0,0,0-2-2Z" transform="translate(0 0)"/> <g id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>"> - <rect className="cls-1" width="32" height="32"/> + <rect fill='none' width="32" height="32"/> </g> </svg> ) @@ -382,27 +373,21 @@ export function getDesignerIcon(icon: string): React.JSX.Element { </svg>) if (icon === 'exception') return ( <svg className="top-icon" width="32px" height="32px" viewBox="0 0 32 32" id="icon"> - <defs> - <style>{".cls-1{fill:none;}"}</style> - </defs> <title>misuse--alt</title> <polygon points="21.41 23 16 17.591 10.59 23 9 21.41 14.409 16 9 10.591 10.591 9 16 14.409 21.409 9 23 10.591 17.591 16 23 21.41 21.41 23"/> <path d="M16,4A12,12,0,1,1,4,16,12.0136,12.0136,0,0,1,16,4m0-2A14,14,0,1,0,30,16,14,14,0,0,0,16,2Z" transform="translate(0)"/> - <rect id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>" className="cls-1" width="32" + <rect id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>" fill='none' width="32" height="32"/> </svg>) if (icon === 'routeConfiguration') return ( - <svg className="top-icon" width="32" height="32" viewBox="0 0 32 32"> - <defs> - <style>{".cls-1{fill:none;}"}</style> - </defs> + <svg className="top-icon" width="32" height="32" viewBox="0 0 32 32"> <path d="M28.83 21.17L25 17.37l.67-.67a1 1 0 000-1.41l-6-6a1 1 0 00-1.41 0l-.79.79-6.76-6.79a1 1 0 00-1.41 0l-4 4-.12.15-4 6a1 1 0 00.12 1.26l3 3a1 1 0 001.42 0L10 13.41l2.09 2.09-4.8 4.79a1 1 0 000 1.41l2 2a1 1 0 00.71.3 1 1 0 00.52-.15l4.33-2.6 2.44 2.45a1 1 0 001.41 0l.67-.7 3.79 3.83a4 4 0 005.66-5.66zM10 10.58l-5 5-1.71-1.71 3.49-5.24L10 5.41l6.09 6.09-2.59 2.58zm8 11l-2.84-2.84-5 3-.74-.74L19 11.41 23.59 16zm9.42 3.83a2 2 0 01-2.83 0l-3.8-3.79 2.83-2.83 3.8 3.79a2 2 0 010 [...] <path d="M0 0H32V32H0z" - className="cls-1" + fill='none' data-name="<Transparent Rectangle>" ></path> </svg>) @@ -419,14 +404,11 @@ export function getDesignerIcon(icon: string): React.JSX.Element { </svg>) if (icon === 'code') return ( <svg className="top-icon" width="32px" height="32px" viewBox="0 0 32 32" id="icon"> - <defs> - <style>{".cls-1{fill:none;}"}</style> - </defs> <title>code</title> <polygon points="31 16 24 23 22.59 21.59 28.17 16 22.59 10.41 24 9 31 16"/> <polygon points="1 16 8 9 9.41 10.41 3.83 16 9.41 21.59 8 23 1 16"/> <rect x="5.91" y="15" width="20.17" height="2" transform="translate(-3.6 27.31) rotate(-75)"/> - <rect id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>" className="cls-1" width="32" + <rect id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>" fill='none' width="32" height="32" transform="translate(0 32) rotate(-90)"/> </svg>) return <></>; @@ -438,9 +420,6 @@ export class BeanIcon extends React.Component<any> { render() { return ( <svg className="icon" width="32px" height="32px" viewBox="0 0 32 32" id="icon"> - <defs> - <style>{".cls-1 {fill: none;}"}</style> - </defs> <title>data--1</title> <rect x="15" y="6" width="13" height="2"/> <rect x="15" y="24" width="13" height="2"/> @@ -452,7 +431,7 @@ export class BeanIcon extends React.Component<any> { <path d="M25,20a4,4,0,1,1,4-4A4,4,0,0,1,25,20Zm0-6a2,2,0,1,0,2,2A2,2,0,0,0,25,14Z" transform="translate(0 0)"/> <g id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>"> - <rect className="cls-1" width="32" height="32"/> + <rect fill='none' width="32" height="32"/> </g> </svg> ) @@ -464,9 +443,6 @@ export class DependencyIcon extends React.Component<any> { render() { return ( <svg className="icon" width="32px" height="32px" viewBox="0 0 32 32" id="icon"> - <defs> - <style>{".cls-1 {fill: none;}"}</style> - </defs> <title>application</title> <path d="M16,18H6a2,2,0,0,1-2-2V6A2,2,0,0,1,6,4H16a2,2,0,0,1,2,2V16A2,2,0,0,1,16,18ZM6,6V16H16V6Z" transform="translate(0 0)"/> @@ -477,7 +453,7 @@ export class DependencyIcon extends React.Component<any> { <path d="M16,22v4H12V22h4m0-2H12a2,2,0,0,0-2,2v4a2,2,0,0,0,2,2h4a2,2,0,0,0,2-2V22a2,2,0,0,0-2-2Z" transform="translate(0 0)"/> <g id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>"> - <rect className="cls-1" width="32" height="32"/> + <rect fill='none' width="32" height="32"/> </g> </svg> ) @@ -508,13 +484,10 @@ export class ConceptIcon extends React.Component<any> { render() { return ( <svg className="icon" width="32px" height="32px" viewBox="0 0 32 32"> - <defs> - <style>{".cls-1 {fill: none;}"}</style> - </defs> <title>concept</title> <path d="M20.8851,19.4711a5.9609,5.9609,0,0,0,0-6.9422L23,10.4141l1.293,1.2929a.9995.9995,0,0,0,1.414,0l4-4a.9994.9994,0,0,0,0-1.414l-4-4a.9994.9994,0,0,0-1.414,0l-4,4a.9994.9994,0,0,0,0,1.414L21.5859,9l-2.1148,2.1149a5.9609,5.9609,0,0,0-6.9422,0L10,8.5859V2H2v8H8.5859l2.529,2.5289a5.9609,5.9609,0,0,0,0,6.9422L9,21.5859,7.707,20.293a.9994.9994,0,0,0-1.414,0l-4,4a.9994.9994,0,0,0,0,1.414l4,4a.9995.9995,0,0,0,1.414,0l4-4a.9994.9994,0,0,0,0-1.414L10.4141,23l2.1148-2.1149a5.960 [...] - <rect id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>" className="cls-1" + <rect id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>" fill='none' width="32" height="32"/> </svg> ) diff --git a/karavan-space/src/designer/karavan.css b/karavan-space/src/designer/karavan.css index 5e558152..4d9a7162 100644 --- a/karavan-space/src/designer/karavan.css +++ b/karavan-space/src/designer/karavan.css @@ -811,3 +811,6 @@ background-color: var(--pf-v5-global--BackgroundColor--light-300); margin-bottom: 100px; } +.karavan .knowledbase-eip-section .pf-v5-c-toggle-group{ + margin:16px; +} \ No newline at end of file diff --git a/karavan-space/src/designer/property/DslProperties.tsx b/karavan-space/src/designer/property/DslProperties.tsx index ed56d2eb..656d816c 100644 --- a/karavan-space/src/designer/property/DslProperties.tsx +++ b/karavan-space/src/designer/property/DslProperties.tsx @@ -44,7 +44,6 @@ import {shallow} from "zustand/shallow"; import {usePropertiesHook} from "./usePropertiesHook"; import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil"; import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; -import QuestionIcon from '@patternfly/react-icons/dist/esm/icons/question-icon'; import {ComponentApi} from "karavan-core/lib/api/ComponentApi"; import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; diff --git a/karavan-space/src/knowledgebase/eip/EipTab.tsx b/karavan-space/src/knowledgebase/eip/EipTab.tsx index e2757f9d..35cc9741 100644 --- a/karavan-space/src/knowledgebase/eip/EipTab.tsx +++ b/karavan-space/src/knowledgebase/eip/EipTab.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { Gallery, - PageSection, PageSectionVariants + PageSection, PageSectionVariants,ToggleGroup,ToggleGroupItem } from '@patternfly/react-core'; import '../../designer/karavan.css'; import {EipCard} from "./EipCard"; @@ -25,6 +25,7 @@ import {EipModal} from "./EipModal"; import {CamelModelMetadata, ElementMeta} from "karavan-core/lib/model/CamelMetadata"; import {useKnowledgebaseStore} from "../KnowledgebaseStore"; import {shallow} from "zustand/shallow"; +import { useSelectorStore } from '../../designer/DesignerStore'; interface Props { dark: boolean, @@ -36,20 +37,46 @@ export function EipTab(props: Props) { const [isModalOpen] = useKnowledgebaseStore((s) => [s.isModalOpen], shallow) - - const {filter} = props; - const elements = CamelModelMetadata - .filter(c => c.name.toLowerCase().includes(filter.toLowerCase())) + const [ selectedLabels, addSelectedLabel, deleteSelectedLabel] = + useSelectorStore((s) => + [s.selectedLabels, s.addSelectedLabel, s.deleteSelectedLabel], shallow) + const { filter } = props; + const elements = CamelModelMetadata; + const filteredElements=CamelModelMetadata + .filter(c => c.name.toLowerCase().includes(filter.toLowerCase())).filter((dsl: ElementMeta) => { + if (selectedLabels.length === 0) { + return true; + } else { + return dsl.labels.split(",").some(r => selectedLabels.includes(r)); + } + }) .sort((a: ElementMeta, b: ElementMeta) => a.name > b.name ? 1 : -1); - + const eipLabels = [...new Set(elements.map(e => e.labels).join(",").split(",").filter(e => e !== 'eip'))]; + function selectLabel(eipLabel: string) { + if (!selectedLabels.includes(eipLabel)) { + addSelectedLabel(eipLabel); + } else { + deleteSelectedLabel(eipLabel); + } + } return ( <PageSection variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light} - padding={{default: 'noPadding'}} className="kamelet-section"> + padding={{ default: 'noPadding' }} className="kamelet-section knowledbase-eip-section"> + <ToggleGroup aria-label="Labels" isCompact > + {eipLabels.map(eipLabel => <ToggleGroupItem + key={eipLabel} + text={eipLabel} + buttonId={eipLabel} + isSelected={selectedLabels.includes(eipLabel)} + onChange={selected => selectLabel(eipLabel)} + />)} + </ToggleGroup> + {isModalOpen && <EipModal/>} <PageSection isFilled className="kamelets-page" variant={props.dark ? PageSectionVariants.darker : PageSectionVariants.light}> <Gallery hasGutter> - {elements.map(c => ( + {filteredElements.map(c => ( <EipCard key={c.name} element={c}/> ))} </Gallery> diff --git a/karavan-space/src/topology/TopologyPropertiesPanel.tsx b/karavan-space/src/topology/TopologyPropertiesPanel.tsx index 8387ce1f..67edb46c 100644 --- a/karavan-space/src/topology/TopologyPropertiesPanel.tsx +++ b/karavan-space/src/topology/TopologyPropertiesPanel.tsx @@ -20,32 +20,93 @@ import {shallow} from "zustand/shallow"; import {TopologySideBar} from "@patternfly/react-topology"; import {useTopologyStore} from "./TopologyStore"; import {DslProperties} from "../designer/property/DslProperties"; -import {Button, Flex, FlexItem, Text, Tooltip, TooltipPosition} from "@patternfly/react-core"; +import { + Button, DescriptionList, + DescriptionListDescription, DescriptionListGroup, DescriptionListTerm, + Flex, + FlexItem, Panel, PanelHeader, PanelMain, PanelMainBody, + Text, TextContent, TextVariants, + Tooltip, + TooltipPosition +} from "@patternfly/react-core"; import CloseIcon from "@patternfly/react-icons/dist/esm/icons/times-icon"; interface Props { onSetFile: (fileName: string) => void } -export function TopologyPropertiesPanel (props: Props) { +export function TopologyPropertiesPanel(props: Props) { - const [selectedIds, setSelectedIds, fileName] = useTopologyStore((s) => - [s.selectedIds, s.setSelectedIds, s.fileName], shallow); + const [selectedIds, setSelectedIds, fileName, nodeData] = useTopologyStore((s) => + [s.selectedIds, s.setSelectedIds, s.fileName, s.nodeData], shallow); + + console.log(nodeData) + + function isRoute() { + if (nodeData && nodeData.type === 'route') { + const uri: string = nodeData?.step?.from.uri || ''; + return uri !== undefined; + } + return false; + } + + function isKamelet() { + if (nodeData && nodeData.type === 'step') { + const uri: string = nodeData?.step?.uri || ''; + return uri.startsWith("kamelet"); + } + return false; + } + + function getFromInfo() { + if (isRoute()) { + const uri: string = nodeData?.step?.from.uri || ''; + const name: string = nodeData?.step?.from.parameters?.name || ''; + if (['direct','seda'].includes(uri)) { + return uri.concat(":").concat(name); + } else { + return uri; + } + } + return "" + } + + function getTitle () { + return isRoute() ? "Route" : (isKamelet() ? "Kamelet" : "Component"); + } function getHeader() { return ( - <Flex className="properties-header" direction={{default: "row"}} justifyContent={{default: "justifyContentFlexStart"}}> + <Flex direction={{default: "row"}} justifyContent={{default: "justifyContentFlexStart"}}> <FlexItem spacer={{ default: 'spacerNone' }}> - <Text>Filename:</Text> - </FlexItem> - <FlexItem> - <Button variant="link" onClick={event => { - if (fileName) { - props.onSetFile(fileName); - } - }} - >{fileName} - </Button> + <Panel> + <PanelHeader> + <TextContent> + <Text component={TextVariants.h3}>{getTitle()}</Text> + </TextContent> + </PanelHeader> + <PanelMain> + <PanelMainBody> + <DescriptionList isHorizontal> + <DescriptionListGroup> + <DescriptionListTerm>File</DescriptionListTerm> + <DescriptionListDescription> + <Button className="file-button" variant="link" onClick={_ => { + if (fileName) { + props.onSetFile(fileName); + } + }}>{fileName} + </Button> + </DescriptionListDescription> + </DescriptionListGroup> + {isRoute() && <DescriptionListGroup> + <DescriptionListTerm>From</DescriptionListTerm> + <DescriptionListDescription>{getFromInfo()}</DescriptionListDescription> + </DescriptionListGroup>} + </DescriptionList> + </PanelMainBody> + </PanelMain> + </Panel> </FlexItem> <FlexItem align={{ default: 'alignRight' }}> <Tooltip content={"Close"} position={TooltipPosition.top}> @@ -58,11 +119,11 @@ export function TopologyPropertiesPanel (props: Props) { return ( <TopologySideBar - className="topology-sidebar" - show={selectedIds.length > 0} - header={getHeader()} - > - <DslProperties designerType={'routes'}/> - </TopologySideBar> + className="topology-sidebar" + show={selectedIds.length > 0} + header={getHeader()} + > + <DslProperties designerType={'routes'}/> + </TopologySideBar> ) } diff --git a/karavan-space/src/topology/TopologyStore.ts b/karavan-space/src/topology/TopologyStore.ts index 73aead9f..4e9ee643 100644 --- a/karavan-space/src/topology/TopologyStore.ts +++ b/karavan-space/src/topology/TopologyStore.ts @@ -35,6 +35,8 @@ interface TopologyState { setFileName: (fileName?: string) => void ranker: string setRanker: (ranker: string) => void + nodeData: any + setNodeData: (nodeData: any) => void } export const useTopologyStore = createWithEqualityFn<TopologyState>((set) => ({ @@ -55,4 +57,10 @@ export const useTopologyStore = createWithEqualityFn<TopologyState>((set) => ({ return {ranker: ranker}; }); }, + nodeData: undefined, + setNodeData: (nodeData: any) => { + set((state: TopologyState) => { + return {nodeData: nodeData}; + }); +}, }), shallow) diff --git a/karavan-space/src/topology/TopologyTab.tsx b/karavan-space/src/topology/TopologyTab.tsx index 727905cc..1f2d165e 100644 --- a/karavan-space/src/topology/TopologyTab.tsx +++ b/karavan-space/src/topology/TopologyTab.tsx @@ -47,8 +47,8 @@ interface Props { export function TopologyTab(props: Props) { - const [selectedIds, setSelectedIds, setFileName, ranker, setRanker] = useTopologyStore((s) => - [s.selectedIds, s.setSelectedIds, s.setFileName, s.ranker, s.setRanker], shallow); + const [selectedIds, setSelectedIds, setFileName, ranker, setRanker, setNodeData] = useTopologyStore((s) => + [s.selectedIds, s.setSelectedIds, s.setFileName, s.ranker, s.setRanker, s.setNodeData], shallow); const [setSelectedStep] = useDesignerStore((s) => [s.setSelectedStep], shallow) function setTopologySelected(model: Model, ids: string []) { @@ -57,6 +57,7 @@ export function TopologyTab(props: Props) { const node = model.nodes?.filter(node => node.id === ids[0]); if (node && node.length > 0) { const data = node[0].data; + setNodeData(data); setFileName(data.fileName) if (data.step) { setSelectedStep(data.step) diff --git a/karavan-space/src/topology/topology.css b/karavan-space/src/topology/topology.css index cb261e21..24fe7679 100644 --- a/karavan-space/src/topology/topology.css +++ b/karavan-space/src/topology/topology.css @@ -41,8 +41,8 @@ overflow: auto; } -.karavan .topology-panel .properties-header { - padding: 10px; +.karavan .topology-panel .properties .headers { + display: none; } .karavan .topology-panel .common-node .icon { @@ -50,6 +50,14 @@ width: 32px; } +.karavan .topology-panel .pf-v5-c-panel__header { + padding-bottom: 0; +} + +.karavan .topology-panel .file-button { + padding: 0; +} + .karavan .topology-sidebar .pf-topology-side-bar__header { margin-right: 0; } @@ -72,4 +80,8 @@ .karavan .topology-sidebar .properties .pf-v5-c-form { pointer-events: none; opacity: 0.7; +} +.karavan .topology-sidebar .properties .pf-v5-c-form .pf-v5-c-form__group-label-help { + pointer-events: auto; + opacity: 1; } \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx index ed56d2eb..656d816c 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/property/DslProperties.tsx @@ -44,7 +44,6 @@ import {shallow} from "zustand/shallow"; import {usePropertiesHook} from "./usePropertiesHook"; import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil"; import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; -import QuestionIcon from '@patternfly/react-icons/dist/esm/icons/question-icon'; import {ComponentApi} from "karavan-core/lib/api/ComponentApi"; import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; diff --git a/karavan-web/karavan-app/src/main/webui/src/topology/TopologyPropertiesPanel.tsx b/karavan-web/karavan-app/src/main/webui/src/topology/TopologyPropertiesPanel.tsx index 8387ce1f..67edb46c 100644 --- a/karavan-web/karavan-app/src/main/webui/src/topology/TopologyPropertiesPanel.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/topology/TopologyPropertiesPanel.tsx @@ -20,32 +20,93 @@ import {shallow} from "zustand/shallow"; import {TopologySideBar} from "@patternfly/react-topology"; import {useTopologyStore} from "./TopologyStore"; import {DslProperties} from "../designer/property/DslProperties"; -import {Button, Flex, FlexItem, Text, Tooltip, TooltipPosition} from "@patternfly/react-core"; +import { + Button, DescriptionList, + DescriptionListDescription, DescriptionListGroup, DescriptionListTerm, + Flex, + FlexItem, Panel, PanelHeader, PanelMain, PanelMainBody, + Text, TextContent, TextVariants, + Tooltip, + TooltipPosition +} from "@patternfly/react-core"; import CloseIcon from "@patternfly/react-icons/dist/esm/icons/times-icon"; interface Props { onSetFile: (fileName: string) => void } -export function TopologyPropertiesPanel (props: Props) { +export function TopologyPropertiesPanel(props: Props) { - const [selectedIds, setSelectedIds, fileName] = useTopologyStore((s) => - [s.selectedIds, s.setSelectedIds, s.fileName], shallow); + const [selectedIds, setSelectedIds, fileName, nodeData] = useTopologyStore((s) => + [s.selectedIds, s.setSelectedIds, s.fileName, s.nodeData], shallow); + + console.log(nodeData) + + function isRoute() { + if (nodeData && nodeData.type === 'route') { + const uri: string = nodeData?.step?.from.uri || ''; + return uri !== undefined; + } + return false; + } + + function isKamelet() { + if (nodeData && nodeData.type === 'step') { + const uri: string = nodeData?.step?.uri || ''; + return uri.startsWith("kamelet"); + } + return false; + } + + function getFromInfo() { + if (isRoute()) { + const uri: string = nodeData?.step?.from.uri || ''; + const name: string = nodeData?.step?.from.parameters?.name || ''; + if (['direct','seda'].includes(uri)) { + return uri.concat(":").concat(name); + } else { + return uri; + } + } + return "" + } + + function getTitle () { + return isRoute() ? "Route" : (isKamelet() ? "Kamelet" : "Component"); + } function getHeader() { return ( - <Flex className="properties-header" direction={{default: "row"}} justifyContent={{default: "justifyContentFlexStart"}}> + <Flex direction={{default: "row"}} justifyContent={{default: "justifyContentFlexStart"}}> <FlexItem spacer={{ default: 'spacerNone' }}> - <Text>Filename:</Text> - </FlexItem> - <FlexItem> - <Button variant="link" onClick={event => { - if (fileName) { - props.onSetFile(fileName); - } - }} - >{fileName} - </Button> + <Panel> + <PanelHeader> + <TextContent> + <Text component={TextVariants.h3}>{getTitle()}</Text> + </TextContent> + </PanelHeader> + <PanelMain> + <PanelMainBody> + <DescriptionList isHorizontal> + <DescriptionListGroup> + <DescriptionListTerm>File</DescriptionListTerm> + <DescriptionListDescription> + <Button className="file-button" variant="link" onClick={_ => { + if (fileName) { + props.onSetFile(fileName); + } + }}>{fileName} + </Button> + </DescriptionListDescription> + </DescriptionListGroup> + {isRoute() && <DescriptionListGroup> + <DescriptionListTerm>From</DescriptionListTerm> + <DescriptionListDescription>{getFromInfo()}</DescriptionListDescription> + </DescriptionListGroup>} + </DescriptionList> + </PanelMainBody> + </PanelMain> + </Panel> </FlexItem> <FlexItem align={{ default: 'alignRight' }}> <Tooltip content={"Close"} position={TooltipPosition.top}> @@ -58,11 +119,11 @@ export function TopologyPropertiesPanel (props: Props) { return ( <TopologySideBar - className="topology-sidebar" - show={selectedIds.length > 0} - header={getHeader()} - > - <DslProperties designerType={'routes'}/> - </TopologySideBar> + className="topology-sidebar" + show={selectedIds.length > 0} + header={getHeader()} + > + <DslProperties designerType={'routes'}/> + </TopologySideBar> ) } diff --git a/karavan-web/karavan-app/src/main/webui/src/topology/TopologyStore.ts b/karavan-web/karavan-app/src/main/webui/src/topology/TopologyStore.ts index 73aead9f..4e9ee643 100644 --- a/karavan-web/karavan-app/src/main/webui/src/topology/TopologyStore.ts +++ b/karavan-web/karavan-app/src/main/webui/src/topology/TopologyStore.ts @@ -35,6 +35,8 @@ interface TopologyState { setFileName: (fileName?: string) => void ranker: string setRanker: (ranker: string) => void + nodeData: any + setNodeData: (nodeData: any) => void } export const useTopologyStore = createWithEqualityFn<TopologyState>((set) => ({ @@ -55,4 +57,10 @@ export const useTopologyStore = createWithEqualityFn<TopologyState>((set) => ({ return {ranker: ranker}; }); }, + nodeData: undefined, + setNodeData: (nodeData: any) => { + set((state: TopologyState) => { + return {nodeData: nodeData}; + }); +}, }), shallow) diff --git a/karavan-web/karavan-app/src/main/webui/src/topology/TopologyTab.tsx b/karavan-web/karavan-app/src/main/webui/src/topology/TopologyTab.tsx index 727905cc..1f2d165e 100644 --- a/karavan-web/karavan-app/src/main/webui/src/topology/TopologyTab.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/topology/TopologyTab.tsx @@ -47,8 +47,8 @@ interface Props { export function TopologyTab(props: Props) { - const [selectedIds, setSelectedIds, setFileName, ranker, setRanker] = useTopologyStore((s) => - [s.selectedIds, s.setSelectedIds, s.setFileName, s.ranker, s.setRanker], shallow); + const [selectedIds, setSelectedIds, setFileName, ranker, setRanker, setNodeData] = useTopologyStore((s) => + [s.selectedIds, s.setSelectedIds, s.setFileName, s.ranker, s.setRanker, s.setNodeData], shallow); const [setSelectedStep] = useDesignerStore((s) => [s.setSelectedStep], shallow) function setTopologySelected(model: Model, ids: string []) { @@ -57,6 +57,7 @@ export function TopologyTab(props: Props) { const node = model.nodes?.filter(node => node.id === ids[0]); if (node && node.length > 0) { const data = node[0].data; + setNodeData(data); setFileName(data.fileName) if (data.step) { setSelectedStep(data.step) diff --git a/karavan-web/karavan-app/src/main/webui/src/topology/topology.css b/karavan-web/karavan-app/src/main/webui/src/topology/topology.css index c67f1f6b..24fe7679 100644 --- a/karavan-web/karavan-app/src/main/webui/src/topology/topology.css +++ b/karavan-web/karavan-app/src/main/webui/src/topology/topology.css @@ -41,8 +41,8 @@ overflow: auto; } -.karavan .topology-panel .properties-header { - padding: 10px; +.karavan .topology-panel .properties .headers { + display: none; } .karavan .topology-panel .common-node .icon { @@ -50,6 +50,14 @@ width: 32px; } +.karavan .topology-panel .pf-v5-c-panel__header { + padding-bottom: 0; +} + +.karavan .topology-panel .file-button { + padding: 0; +} + .karavan .topology-sidebar .pf-topology-side-bar__header { margin-right: 0; }