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 cce1dd34b98e62a12afa7499ee8ff18ee6de4902 Author: Marat Gubaidullin <ma...@talismancloud.io> AuthorDate: Mon Oct 23 20:10:47 2023 -0400 Kamelet Editor in App for #315 --- .../src/core/model/IntegrationDefinition.ts | 10 ++-- .../main/webui/src/designer/KaravanDesigner.tsx | 2 - .../webui/src/designer/icons/ComponentIcons.tsx | 23 +++++++++- .../main/webui/src/designer/icons/KaravanIcons.tsx | 3 +- .../webui/src/designer/route/DslConnections.tsx | 2 + .../main/webui/src/designer/route/DslElement.tsx | 14 +++++- .../main/webui/src/designer/route/DslSelector.tsx | 3 +- .../webui/src/designer/route/RouteDesigner.tsx | 53 ++++++++++++++-------- .../src/designer/route/useRouteDesignerHook.tsx | 42 +++++++++++++++-- .../src/main/webui/src/designer/utils/CamelUi.tsx | 12 ++++- .../main/webui/src/knowledgebase/eip/EipModal.tsx | 35 ++++++-------- .../src/main/webui/src/project/file/FileEditor.tsx | 2 +- .../webui/src/project/files/CreateFileModal.tsx | 51 +++++++++++++++++---- .../src/main/webui/src/project/files/FilesTab.tsx | 2 +- .../src/project/topology/ProjectTopologyTab.tsx | 2 +- 15 files changed, 186 insertions(+), 70 deletions(-) diff --git a/karavan-core/src/core/model/IntegrationDefinition.ts b/karavan-core/src/core/model/IntegrationDefinition.ts index fb1c11f3..18d01dc8 100644 --- a/karavan-core/src/core/model/IntegrationDefinition.ts +++ b/karavan-core/src/core/model/IntegrationDefinition.ts @@ -73,8 +73,10 @@ export class Spec { } } +export type KameletTypes = "sink" | "source" | "action"; + export class MetadataLabels { - "camel.apache.org/kamelet.type": "sink" | "source" | "action" = 'source' + "camel.apache.org/kamelet.type": KameletTypes = 'source' public constructor(init?: Partial<MetadataLabels>) { Object.assign(this, init); @@ -82,10 +84,10 @@ export class MetadataLabels { } export class MetadataAnnotations { - "camel.apache.org/kamelet.support.level:": string = 'Preview'; + "camel.apache.org/kamelet.support.level": string = 'Preview'; "camel.apache.org/catalog.version": string = ''; - "camel.apache.org/kamelet.icon": string = ''; - "camel.apache.org/provider": string = ''; + "camel.apache.org/kamelet.icon": string = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23000000' viewBox='0 0 32 32' id='icon'%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill:none;%7D%3C/style%3E%3C/defs%3E%3Ctitle%3Eapplication%3C/title%3E%3Cpath 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)'/%3E%3Cpath d='M26,12v4H22V12h4m0-2H22a2,2,0,0,0-2,2v4a2,2,0,0,0,2,2h4a2,2,0,0,0,2-2V12a2,2,0,0,0-2-2Z' tran [...] + "camel.apache.org/provider": string = 'Custom'; "camel.apache.org/kamelet.group": string = ''; "camel.apache.org/kamelet.namespace": string = ''; diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx index 4b7b3c7a..b1af73c5 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx @@ -24,8 +24,6 @@ import { Tabs, TabTitleIcon, TabTitleText, - Tooltip, - TooltipPosition, } from '@patternfly/react-core'; import './karavan.css'; import {RouteDesigner} from "./route/RouteDesigner"; diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/icons/ComponentIcons.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/icons/ComponentIcons.tsx index a05f5e70..bda35d53 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/icons/ComponentIcons.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/icons/ComponentIcons.tsx @@ -1165,7 +1165,28 @@ export function ApiIcon() { ); } -export function MonitoringIcon() { +export function KameletIcon() { + return ( + <svg + className="icon" id="icon" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 32 32" + > + <title>{"application"}</title> + <path d="M16 18H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2ZM6 6v10h10V6ZM26 12v4h-4v-4h4m0-2h-4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2ZM26 22v4h-4v-4h4m0-2h-4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2ZM16 22v4h-4v-4h4m0-2h-4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2Z" /> + <path + d="M0 0h32v32H0z" + data-name="<Transparent Rectangle>" + style={{ + fill: "none", + }} + /> + </svg> + ) +} + + + export function MonitoringIcon() { return ( <svg xmlns="http://www.w3.org/2000/svg" diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/icons/KaravanIcons.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/icons/KaravanIcons.tsx index b7165b93..b0b8042f 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/icons/KaravanIcons.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/icons/KaravanIcons.tsx @@ -260,7 +260,7 @@ export function CamelIcon(props?: (JSX.IntrinsicAttributes & React.SVGProps<SVGS ); } -export function getDesignerIcon(icon: string) { +export function getDesignerIcon(icon: string): React.JSX.Element { if (icon === 'kamelet') return ( <svg className="top-icon" id="icon" @@ -429,6 +429,7 @@ export function getDesignerIcon(icon: string) { <rect id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>" className="cls-1" width="32" height="32" transform="translate(0 32) rotate(-90)"/> </svg>) + return <></>; } diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx index de6a0060..a636339e 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx @@ -63,6 +63,7 @@ export function DslConnections() { .filter(pos => ["FromDefinition"].includes(pos.step.dslName)) .filter(pos => !TopologyUtils.isElementInternalComponent(pos.step)) .filter(pos => !(pos.step.dslName === 'FromDefinition' && TopologyUtils.hasInternalUri(pos.step))) + .filter(pos => !(pos.step.dslName === 'FromDefinition' && (pos.step as any).uri === 'kamelet:source')) .sort((pos1: DslPosition, pos2: DslPosition) => { const y1 = pos1.headerRect.y + pos1.headerRect.height / 2; const y2 = pos2.headerRect.y + pos2.headerRect.height / 2; @@ -142,6 +143,7 @@ export function DslConnections() { .filter(pos => pos.step.dslName === 'ToDefinition' && !CamelUi.isActionKamelet(pos.step) && !TopologyUtils.isElementInternalComponent(pos.step)) .filter(pos => !(outgoingDefinitions.includes(pos.step.dslName) && TopologyUtils.hasInternalUri(pos.step))) .filter(pos => pos.step.dslName !== 'SagaDefinition') + .filter(pos => !CamelUi.isKameletSink(pos.step)) .sort((pos1: DslPosition, pos2: DslPosition) => { const y1 = pos1.headerRect.y + pos1.headerRect.height / 2; const y2 = pos2.headerRect.y + pos2.headerRect.height / 2; diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElement.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElement.tsx index 305b7859..9e4a8c70 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElement.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElement.tsx @@ -42,7 +42,7 @@ interface Props { export function DslElement(props: Props) { const headerRef = React.useRef<HTMLDivElement>(null); - const {selectElement, moveElement, onShowDeleteConfirmation, openSelector} = useRouteDesignerHook(); + const {selectElement, moveElement, onShowDeleteConfirmation, openSelector, isKamelet, isSourceKamelet, isActionKamelet} = useRouteDesignerHook(); const [integration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow) @@ -241,9 +241,19 @@ export function DslElement(props: Props) { ) } + function getHeaderText(step: CamelElement): string { + if (isKamelet() && step.dslName === 'ToDefinition' && (step as any).uri === 'kamelet:sink') { + return "Sink"; + } else if (isKamelet() && step.dslName === 'FromDefinition' && (step as any).uri === 'kamelet:source') { + return "Source"; + } else { + return (step as any).description ? (step as any).description : CamelUi.getElementTitle(props.step); + } + } + function getHeaderTextWithTooltip(step: CamelElement) { const checkRequired = CamelUtil.checkRequired(step); - const title = (step as any).description ? (step as any).description : CamelUi.getElementTitle(props.step); + const title = getHeaderText(step); let className = hasWideChildrenElement() ? "text text-right" : "text text-bottom"; if (!checkRequired[0]) className = className + " header-text-required"; if (checkRequired[0]) { diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslSelector.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslSelector.tsx index 8f815536..a80525ba 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslSelector.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslSelector.tsx @@ -24,7 +24,7 @@ import { import '../karavan.css'; import {CamelUi} from "../utils/CamelUi"; import {DslMetaModel} from "../utils/DslMetaModel"; -import {useDesignerStore, useSelectorStore} from "../DesignerStore"; +import {useDesignerStore, useIntegrationStore, useSelectorStore} from "../DesignerStore"; import {shallow} from "zustand/shallow"; import {useRouteDesignerHook} from "./useRouteDesignerHook"; @@ -40,7 +40,6 @@ export function DslSelector (props: Props) { [s.showSelector, s.showSteps, s.parentId, s.parentDsl, s.selectorTabIndex, s.setShowSelector, s.setSelectorTabIndex, s.selectedPosition, s.selectedLabels, s.addSelectedLabel, s.deleteSelectedLabel], shallow) - const [dark] = useDesignerStore((s) => [s.dark], shallow) const {onDslSelect} = useRouteDesignerHook(); diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx index 215f334b..c57260db 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx @@ -40,7 +40,8 @@ import {DslElementMoveModal} from "./DslElementMoveModal"; export function RouteDesigner() { - const {openSelector, createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement} = useRouteDesignerHook(); + const {openSelector, createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement, onDslSelect, + isSourceKamelet, isActionKamelet, isKamelet, isSinkKamelet} = useRouteDesignerHook(); const [integration] = useIntegrationStore((state) => [state.integration], shallow) const [showDeleteConfirmation, setPosition, width, height, top, left, hideLogDSL, showMoveConfirmation, setShowMoveConfirmation] = @@ -104,6 +105,37 @@ export function RouteDesigner() { ) } + function getGraphButtons() { + const routes = CamelUi.getRoutes(integration); + const showNewRoute = (isKamelet() && routes.length === 0) || !isKamelet(); + const showNewRouteConfiguration = !isKamelet(); + return ( + <div className="add-flow"> + {showNewRoute && <Button + variant={routes.length === 0 ? "primary" : "secondary"} + icon={<PlusIcon/>} + onClick={e => { + if (isSinkKamelet() || isActionKamelet()) { + const dsl = CamelUi.getDslMetaModel('FromDefinition'); + dsl.uri = 'kamelet:source'; + onDslSelect(dsl, '', undefined); + } else { + openSelector(undefined, undefined) + } + }} + > + Create route + </Button>} + {showNewRouteConfiguration && <Button + variant="secondary" + icon={<PlusIcon/>} + onClick={e => createRouteConfiguration()} + > + Create configuration + </Button>} + </div> + ) + } function getGraph() { const routes = CamelUi.getRoutes(integration); const routeConfigurations = CamelUi.getRouteConfigurations(integration); @@ -129,24 +161,7 @@ export function RouteDesigner() { step={route} parent={undefined}/> ))} - <div className="add-flow"> - <Button - variant={routes.length === 0 ? "primary" : "secondary"} - icon={<PlusIcon/>} - onClick={e => { - openSelector(undefined, undefined) - }} - > - Create route - </Button> - <Button - variant="secondary" - icon={<PlusIcon/>} - onClick={e => createRouteConfiguration()} - > - Create configuration - </Button> - </div> + {getGraphButtons()} </div> </div>) } diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx index fe61d410..f48fc850 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx @@ -19,7 +19,7 @@ import '../karavan.css'; import {DslMetaModel} from "../utils/DslMetaModel"; import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; import {ChoiceDefinition, FromDefinition, LogDefinition, RouteConfigurationDefinition, RouteDefinition} from "karavan-core/lib/model/CamelDefinition"; -import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition"; +import {CamelElement, MetadataLabels} from "karavan-core/lib/model/IntegrationDefinition"; import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt"; import {CamelDefinitionApi} from "karavan-core/lib/api/CamelDefinitionApi"; import {Command, EventBus} from "../utils/EventBus"; @@ -27,6 +27,7 @@ import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil"; import {toPng} from 'html-to-image'; import {useDesignerStore, useIntegrationStore, useSelectorStore} from "../DesignerStore"; import {shallow} from "zustand/shallow"; +import {v4 as uuidv4} from 'uuid'; export function useRouteDesignerHook () { @@ -46,6 +47,34 @@ export function useRouteDesignerHook () { } } + function isKamelet(): boolean { + return integration.type === 'kamelet'; + } + + function isSourceKamelet(): boolean { + if (isKamelet()){ + const m: MetadataLabels | undefined = integration.metadata.labels; + return m !== undefined && m["camel.apache.org/kamelet.type"] === 'source'; + } + return false; + } + + function isSinkKamelet(): boolean { + if (isKamelet()){ + const m: MetadataLabels | undefined = integration.metadata.labels; + return m !== undefined && m["camel.apache.org/kamelet.type"] === 'sink'; + } + return false; + } + + function isActionKamelet(): boolean { + if (isKamelet()){ + const m: MetadataLabels | undefined = integration.metadata.labels; + return m !== undefined && m["camel.apache.org/kamelet.type"] === 'action'; + } + return false; + } + const onShowDeleteConfirmation = (id: string) => { let message: string; const uuidsToDelete:string [] = [id]; @@ -193,13 +222,17 @@ export function useRouteDesignerHook () { setSelectorTabIndex((parentId === undefined && parentDsl === undefined) ? 'kamelet' : 'eip'); } - const onDslSelect = (dsl: DslMetaModel, parentId: string, position?: number | undefined) => { + function onDslSelect (dsl: DslMetaModel, parentId: string, position?: number | undefined) { switch (dsl.dsl) { case 'FromDefinition' : - const route = CamelDefinitionApi.createRouteDefinition({from: new FromDefinition({uri: dsl.uri})}); + const nodePrefixId = isKamelet() ? integration.metadata.name : 'route-' + uuidv4().substring(0,3); + const route = CamelDefinitionApi.createRouteDefinition({from: new FromDefinition({uri: dsl.uri}), nodePrefixId: nodePrefixId}); addStep(route, parentId, position) break; case 'ToDefinition' : + if (dsl.uri === undefined && isKamelet()) { + dsl.uri = 'kamelet:sink'; + } const to = CamelDefinitionApi.createStep(dsl.dsl, {uri: dsl.uri}); addStep(to, parentId, position) break; @@ -291,5 +324,6 @@ export function useRouteDesignerHook () { } return { deleteElement, selectElement, moveElement, onShowDeleteConfirmation, onDslSelect, openSelector, - createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement} + createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement, isKamelet, isSourceKamelet, + isActionKamelet, isSinkKamelet} } \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx index 6864361b..2b672a8d 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx @@ -51,7 +51,7 @@ import { IgniteIcon, InfinispanIcon, IotIcon, - KafkaIcon, + KafkaIcon, KameletIcon, KubernetesIcon, MachineLearningIcon, MailIcon, @@ -93,6 +93,7 @@ import { import React from "react"; import {TopologyUtils} from "karavan-core/lib/api/TopologyUtils"; import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil"; +import {getDesignerIcon} from "../icons/KaravanIcons"; const StepElements: string[] = [ "AggregateDefinition", @@ -107,6 +108,7 @@ const StepElements: string[] = [ // "ErrorHandlerDefinition", "FilterDefinition", "IdempotentConsumerDefinition", + "KameletDefinition", "LogDefinition", "LoopDefinition", "MarshalDefinition", @@ -306,6 +308,10 @@ export class CamelUi { else return false; } + static isKameletSink = (element: CamelElement): boolean => { + return element.dslName === 'ToDefinition' && (element as any).uri === 'kamelet:sink'; + } + static getInternalRouteUris = (integration: Integration, componentName: string, showComponentName: boolean = true): string[] => { const result: string[] = []; integration.spec.flows?.filter(f => f.dslName === 'RouteDefinition') @@ -519,7 +525,7 @@ export class CamelUi { } } - static getIconForDsl = (dsl: DslMetaModel): JSX.Element => { + static getIconForDsl = (dsl: DslMetaModel): React.JSX.Element => { if (dsl.dsl && (dsl.dsl === "KameletDefinition" || dsl.navigation === 'kamelet')) { return this.getIconFromSource(CamelUi.getKameletIconByName(dsl.name)); } else if ((dsl.dsl && dsl.dsl === "FromDefinition") @@ -687,6 +693,8 @@ export class CamelUi { return <ApiIcon/>; case 'HeadDefinition' : return <ApiIcon/>; + case 'KameletDefinition' : + return <KameletIcon/>; default: return this.getIconFromSource(CamelUi.getIconSrcForName(dslName)) } diff --git a/karavan-web/karavan-app/src/main/webui/src/knowledgebase/eip/EipModal.tsx b/karavan-web/karavan-app/src/main/webui/src/knowledgebase/eip/EipModal.tsx index d2100a25..902a3ada 100644 --- a/karavan-web/karavan-app/src/main/webui/src/knowledgebase/eip/EipModal.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/knowledgebase/eip/EipModal.tsx @@ -15,16 +15,9 @@ * limitations under the License. */ import React from 'react'; -import { - Button, - Modal, - ActionGroup, - Text, - CardHeader, - Badge, Flex, CardTitle, -} from '@patternfly/react-core'; +import {ActionGroup, Badge, Button, CardHeader, CardTitle, Flex, Modal, Text,} from '@patternfly/react-core'; import '../../designer/karavan.css'; -import {Table, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table"; +import {Table, TableText, Tbody, Td, Th, Thead, Tr, WrapModifier} from "@patternfly/react-table"; import {CamelUi} from "../../designer/utils/CamelUi"; import {PropertyMeta} from "karavan-core/lib/model/CamelMetadata"; import {useKnowledgebaseStore} from "../KnowledgebaseStore"; @@ -45,7 +38,7 @@ export function EipModal() { isOpen={isModalOpen} onClose={() => setModalOpen(false)} actions={[ - <div className="modal-footer"> + <div className="modal-footer" key="buttons"> <ActionGroup className="deploy-buttons"> <Button key="cancel" variant="primary" onClick={e => setModalOpen(false)}>Close</Button> @@ -53,8 +46,7 @@ export function EipModal() { </div> ]} > - <Flex direction={{default: 'column'}} key={element?.name} - className="kamelet-modal-card"> + <Flex direction={{default: 'column'}} key={element?.name} className="kamelet-modal-card"> <CardHeader actions={{ actions: <><Badge className="badge" isRead> {element?.labels}</Badge></>, hasNoOffset: false, className: undefined}} > {element && CamelUi.getIconForDslName(element?.className)} @@ -67,27 +59,30 @@ export function EipModal() { <Table aria-label="Simple table" variant='compact'> <Thead> <Tr> - <Th key='name'>Display Name / Name</Th> + <Th key='name' width={10}>Name</Th> + <Th key='label'>Label</Th> + <Th key='display' width={10}>Display Name</Th> <Th key='desc'>Description</Th> <Th key='type'>Type</Th> - <Th key='label'>Label</Th> </Tr> </Thead> <Tbody> {element?.properties.map((p: PropertyMeta, idx: number) => ( <Tr key={idx}> - <Td key={`${idx}_name`}> - <div> - <b>{p.displayName}</b> - <div>{p.name}</div> - </div> + <Td modifier={"fitContent"}> + {p.name} + </Td> + <Td modifier={"fitContent"}> + <Badge className="badge" isRead>{p.label}</Badge> + </Td> + <Td modifier={"fitContent"}> + {p.displayName} </Td> <Td key={`${idx}_desc`}><div> <div>{p.description}</div> {p.defaultValue && p.defaultValue.toString().length > 0 && <div>{"Default value: " + p.defaultValue}</div>} </div></Td> <Td key={`${idx}_type`}>{p.type}</Td> - <Td key={`${idx}_label`}>{p.label}</Td> </Tr> ))} </Tbody> diff --git a/karavan-web/karavan-app/src/main/webui/src/project/file/FileEditor.tsx b/karavan-web/karavan-app/src/main/webui/src/project/file/FileEditor.tsx index 073b3bc3..8271c19f 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/file/FileEditor.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/file/FileEditor.tsx @@ -88,7 +88,7 @@ export function FileEditor (props: Props) { const isKameletYaml = file !== undefined && file.name.endsWith(".kamelet.yaml"); const isIntegration = isCamelYaml && file?.code && CamelDefinitionYaml.yamlIsIntegration(file.code); const isProperties = file !== undefined && file.name.endsWith("properties"); - const showDesigner = isCamelYaml && isIntegration; + const showDesigner = (isCamelYaml && isIntegration) || isKameletYaml; const showEditor = !showDesigner && !isProperties; return ( <> diff --git a/karavan-web/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx b/karavan-web/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx index 36dc24ea..1221dc38 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/files/CreateFileModal.tsx @@ -25,16 +25,18 @@ import { ToggleGroupItem, ToggleGroup, FormHelperText, HelperText, HelperTextItem, TextInput } from '@patternfly/react-core'; import '../../designer/karavan.css'; -import {Integration} from "karavan-core/lib/model/IntegrationDefinition"; +import {Integration, KameletTypes, MetadataLabels} from "karavan-core/lib/model/IntegrationDefinition"; import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml"; import {useFileStore, useProjectStore} from "../../api/ProjectStore"; import {ProjectFile, ProjectFileTypes} from "../../api/ProjectModels"; import {CamelUi} from "../../designer/utils/CamelUi"; import {ProjectService} from "../../api/ProjectService"; import {shallow} from "zustand/shallow"; +import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; interface Props { - types: string[] + types: string[], + isKameletsProject: boolean } export function CreateFileModal (props: Props) { @@ -43,6 +45,7 @@ export function CreateFileModal (props: Props) { const [operation, setFile] = useFileStore((s) => [s.operation, s.setFile], shallow); const [name, setName] = useState<string>( ''); const [fileType, setFileType] = useState<string>(); + const [kameletType, setKameletType] = useState<KameletTypes>('source'); useEffect(() => { if (props.types.length > 0) { @@ -60,14 +63,28 @@ export function CreateFileModal (props: Props) { cleanValues(); } + function getCode(): string { + if (fileType === 'INTEGRATION') { + return CamelDefinitionYaml.integrationToYaml(Integration.createNew(name, 'plain')); + } else if (fileType === 'KAMELET') { + const kameletName = name + (isKamelet ? '-'+kameletType : ''); + const integration = Integration.createNew(kameletName, 'kamelet'); + const meta:MetadataLabels = new MetadataLabels({"camel.apache.org/kamelet.type": kameletType}); + integration.metadata.labels = meta; + console.log(integration); + return CamelDefinitionYaml.integrationToYaml(integration); + } else { + return ''; + } + } + function confirmAndCloseModal () { const extension = ProjectFileTypes.filter(value => value.name === fileType)[0].extension; const filename = (extension !== 'java') ? fileNameCheck(name) : CamelUi.javaNameFromTitle(name); - const code = fileType === 'INTEGRATION' - ? CamelDefinitionYaml.integrationToYaml(Integration.createNew(name, 'plain')) - : ''; + const code = getCode(); if (filename && extension) { - const file = new ProjectFile(filename + '.' + extension, project.projectId, code, Date.now()); + const fullFileName = filename + (isKamelet ? '-'+kameletType : '') + '.' + extension; + const file = new ProjectFile(fullFileName, project.projectId, code, Date.now()); ProjectService.createFile(file); cleanValues(); if (code) { @@ -82,10 +99,12 @@ export function CreateFileModal (props: Props) { return title.replace(/[^0-9a-zA-Z.]+/gi, "-").toLowerCase(); } + const isKamelet = props.isKameletsProject; const extension = ProjectFileTypes.filter(value => value.name === fileType)[0]?.extension; const filename = (extension !== 'java') ? fileNameCheck(name) - : CamelUi.javaNameFromTitle(name) + : CamelUi.javaNameFromTitle(name); + const fullFileName = filename + (isKamelet ? '-'+kameletType : '') + '.' + extension; return ( <Modal title="Create" @@ -98,7 +117,7 @@ export function CreateFileModal (props: Props) { ]} > <Form autoComplete="off" isHorizontal className="create-file-form"> - <FormGroup label="Type" fieldId="type" isRequired> + {!isKamelet && <FormGroup label="Type" fieldId="type" isRequired> <ToggleGroup aria-label="Type" isCompact> {ProjectFileTypes.filter(p => props.types.includes(p.name)) .map(p => { @@ -110,12 +129,24 @@ export function CreateFileModal (props: Props) { }}/> })} </ToggleGroup> - </FormGroup> + </FormGroup>} + {isKamelet && <FormGroup label="Kamelet Type" fieldId="kameletType" isRequired> + <ToggleGroup aria-label="Kamelet Type"> + {['source', 'action', 'sink'].map((type) => { + const title = CamelUtil.capitalizeName(type); + return <ToggleGroupItem key={type} text={title} buttonId={type} + isSelected={kameletType === type} + onChange={(_, selected) => { + setKameletType(type as KameletTypes); + }}/> + })} + </ToggleGroup> + </FormGroup>} <FormGroup label="Name" fieldId="name" isRequired> <TextInput id="name" aria-label="name" value={name} onChange={(_, value) => setName(value)}/> <FormHelperText > <HelperText id="helper-text1"> - <HelperTextItem variant={'default'}>{filename + '.' + extension}</HelperTextItem> + <HelperTextItem variant={'default'}>{fullFileName}</HelperTextItem> </HelperText> </FormHelperText> </FormGroup> diff --git a/karavan-web/karavan-app/src/main/webui/src/project/files/FilesTab.tsx b/karavan-web/karavan-app/src/main/webui/src/project/files/FilesTab.tsx index d0396f96..e9dda209 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/files/FilesTab.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/files/FilesTab.tsx @@ -159,7 +159,7 @@ export function FilesTab () { </Table> </div> <UploadFileModal projectId={project.projectId}/> - <CreateFileModal types={types}/> + <CreateFileModal types={types} isKameletsProject={isKameletsProject()}/> <DeleteFileModal /> </PageSection> ) diff --git a/karavan-web/karavan-app/src/main/webui/src/project/topology/ProjectTopologyTab.tsx b/karavan-web/karavan-app/src/main/webui/src/project/topology/ProjectTopologyTab.tsx index 935a91e8..8775353f 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/topology/ProjectTopologyTab.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/topology/ProjectTopologyTab.tsx @@ -49,7 +49,7 @@ export const ProjectTopologyTab: React.FC = () => { onClickCreateButton={() => setFile('create')} onSetFile={(fileName) => selectFile(fileName)} /> - <CreateFileModal types={['INTEGRATION']}/> + <CreateFileModal types={['INTEGRATION']} isKameletsProject={false}/> </> ); } \ No newline at end of file