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 39c7e89e9694538b841a866c35d421da0c59712c Author: Marat Gubaidullin <[email protected]> AuthorDate: Mon Nov 28 11:09:03 2022 -0500 Code templates in application --- .../camel/karavan/api/ProjectFileResource.java | 6 +- .../apache/camel/karavan/model/ProjectFile.java | 10 ++ .../apache/camel/karavan/service/CodeService.java | 17 ++- ...arkus-org.apache.camel.AggregationStrategy.java | 23 +++ .../quarkus-org.apache.camel.Processor.java | 14 ++ ...-boot-org.apache.camel.AggregationStrategy.java | 20 +++ .../spring-boot-org.apache.camel.Processor.java | 12 ++ .../main/webui/src/designer/KaravanDesigner.tsx | 56 ++++--- .../webui/src/designer/error/ErrorHandlerCard.tsx | 48 ++++++ .../src/designer/error/ErrorHandlerDesigner.tsx | 164 +++++++++++++++++++++ .../src/main/webui/src/designer/karavan.css | 12 +- .../webui/src/designer/route/RouteDesigner.tsx | 1 + .../designer/route/property/DslPropertyField.tsx | 104 ++++++++----- .../src/designer/route/property/ModalEditor.tsx | 98 ++++++++++++ .../src/main/webui/src/designer/utils/CamelUi.tsx | 42 +++++- .../src/main/webui/src/projects/ProjectPage.tsx | 24 ++- .../main/webui/src/projects/ProjectPageToolbar.tsx | 2 +- .../designer/route/property/DslPropertyField.tsx | 22 +-- 18 files changed, 589 insertions(+), 86 deletions(-) diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java index 5069200..747e34d 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java @@ -33,7 +33,9 @@ import javax.ws.rs.core.MediaType; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.time.Instant; +import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; @Path("/api/file") public class ProjectFileResource { @@ -49,7 +51,9 @@ public class ProjectFileResource { @Path("/{projectId}") public List<ProjectFile> get(@HeaderParam("username") String username, @PathParam("projectId") String projectId) throws Exception { - return infinispanService.getProjectFiles(projectId); + return infinispanService.getProjectFiles(projectId).stream() + .sorted(Comparator.comparing(ProjectFile::getName)) + .collect(Collectors.toList()); } @POST diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectFile.java b/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectFile.java index 6732b91..ad1d6c6 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectFile.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectFile.java @@ -60,4 +60,14 @@ public class ProjectFile { public void setLastUpdate(Long lastUpdate) { this.lastUpdate = lastUpdate; } + + @Override + public String toString() { + return "ProjectFile{" + + "name='" + name + '\'' + + ", code='" + code + '\'' + + ", projectId='" + projectId + '\'' + + ", lastUpdate=" + lastUpdate + + '}'; + } } diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java index ee7c57c..4fb5e67 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java @@ -38,6 +38,7 @@ import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -83,10 +84,18 @@ public class CodeService { } public Map<String,String> getApplicationPropertiesTemplates() { - Map<String, String> result = new HashMap<>(4); - List.of("quarkus", "spring-boot").forEach(runtime -> { - List.of("openshift", "kubernetes").forEach(target -> { - String templateName = runtime + "-" + target + "-application.properties"; + Map<String, String> result = new HashMap<>(); + + List<String> runtimes = List.of("quarkus", "spring-boot"); + List<String> targets = List.of("openshift", "kubernetes"); + List<String> interfaces = List.of("org.apache.camel.AggregationStrategy.java", "org.apache.camel.Processor.java"); + + List<String> files = new ArrayList<>(interfaces); + files.addAll(targets.stream().map(target -> target + "-application.properties").collect(Collectors.toList())); + + runtimes.forEach(runtime -> { + files.forEach(file -> { + String templateName = runtime + "-" + file; String templatePath = "/snippets/" + templateName; String templateText = getResourceFile(templatePath); result.put(templateName, templateText); diff --git a/karavan-app/src/main/resources/snippets/quarkus-org.apache.camel.AggregationStrategy.java b/karavan-app/src/main/resources/snippets/quarkus-org.apache.camel.AggregationStrategy.java new file mode 100644 index 0000000..4e45fcd --- /dev/null +++ b/karavan-app/src/main/resources/snippets/quarkus-org.apache.camel.AggregationStrategy.java @@ -0,0 +1,23 @@ +import org.apache.camel.AggregationStrategy +; +import org.apache.camel.Exchange; + +import javax.inject.Named; +import javax.inject.Singleton; + +@Singleton +@Named("NAME") +public class NAME implements AggregationStrategy { + @Override + public Exchange aggregate(Exchange oldExchange, Exchange newExchange) { + + if (oldExchange == null) { + return newExchange; + } + + String oldBody = oldExchange.getIn().getBody(String.class); + String newBody = newExchange.getIn().getBody(String.class); + oldExchange.getIn().setBody(oldBody + "+" + newBody); + return oldExchange; + } +} \ No newline at end of file diff --git a/karavan-app/src/main/resources/snippets/quarkus-org.apache.camel.Processor.java b/karavan-app/src/main/resources/snippets/quarkus-org.apache.camel.Processor.java new file mode 100644 index 0000000..6b81323 --- /dev/null +++ b/karavan-app/src/main/resources/snippets/quarkus-org.apache.camel.Processor.java @@ -0,0 +1,14 @@ +import org.apache.camel.Exchange; +import org.apache.camel.Processor; + +import javax.inject.Named; +import javax.inject.Singleton; + +@Singleton +@Named("NAME") +public class NAME implements Processor { + + public void process(Exchange exchange) throws Exception { + exchange.getIn().setBody("Hello World"); + } +} \ No newline at end of file diff --git a/karavan-app/src/main/resources/snippets/spring-boot-org.apache.camel.AggregationStrategy.java b/karavan-app/src/main/resources/snippets/spring-boot-org.apache.camel.AggregationStrategy.java new file mode 100644 index 0000000..1b04d76 --- /dev/null +++ b/karavan-app/src/main/resources/snippets/spring-boot-org.apache.camel.AggregationStrategy.java @@ -0,0 +1,20 @@ +import org.apache.camel.AggregationStrategy; +import org.apache.camel.Exchange; + +import org.springframework.stereotype.Component; + +@Component("NAME") +public class NAME implements AggregationStrategy { + @Override + public Exchange aggregate(Exchange oldExchange, Exchange newExchange) { + + if (oldExchange == null) { + return newExchange; + } + + String oldBody = oldExchange.getIn().getBody(String.class); + String newBody = newExchange.getIn().getBody(String.class); + oldExchange.getIn().setBody(oldBody + "+" + newBody); + return oldExchange; + } +} \ No newline at end of file diff --git a/karavan-app/src/main/resources/snippets/spring-boot-org.apache.camel.Processor.java b/karavan-app/src/main/resources/snippets/spring-boot-org.apache.camel.Processor.java new file mode 100644 index 0000000..4593b74 --- /dev/null +++ b/karavan-app/src/main/resources/snippets/spring-boot-org.apache.camel.Processor.java @@ -0,0 +1,12 @@ +import org.apache.camel.Exchange; +import org.apache.camel.Processor; + +import org.springframework.stereotype.Component; + +@Component("NAME") +public class NAME implements Processor { + + public void process(Exchange exchange) throws Exception { + exchange.getIn().setBody("Hello World"); + } +} \ No newline at end of file diff --git a/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx b/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx index 6333b37..4b43ff4 100644 --- a/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx +++ b/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx @@ -27,13 +27,13 @@ import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; import {CamelUi} from "./utils/CamelUi"; import {BeansDesigner} from "./beans/BeansDesigner"; import {RestDesigner} from "./rest/RestDesigner"; -import {ErrorDesigner} from "./error/ErrorDesigner"; -import {ExceptionDesigner} from "./exception/ExceptionDesigner"; +import {ErrorHandlerDesigner} from "./error/ErrorHandlerDesigner"; import {getDesignerIcon} from "./utils/KaravanIcons"; interface Props { - onSave?: (filename: string, yaml: string, propertyOnly: boolean) => void - onDisableHelp?: () => void + onSave: (filename: string, yaml: string, propertyOnly: boolean) => void + onSaveCustomCode: (name: string, code: string) => void + onGetCustomCode: (name: string) => Promise<string | undefined> filename: string yaml: string dark: boolean @@ -48,6 +48,22 @@ interface State { routeDesignerRef?: any } +export class KaravanInstance { + static designer: KaravanDesigner; + + static set(designer: KaravanDesigner): void { + KaravanInstance.designer = designer; + } + + static get(): KaravanDesigner { + return KaravanInstance.designer; + } + + static getProps(): Props { + return KaravanInstance.designer?.props; + } +} + export class KaravanDesigner extends React.Component<Props, State> { getIntegration = (yaml: string, filename: string): Integration => { @@ -66,6 +82,10 @@ export class KaravanDesigner extends React.Component<Props, State> { routeDesignerRef: React.createRef(), } + componentDidMount() { + KaravanInstance.set(this); + } + componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => { if (prevState.key !== this.state.key) { this.props.onSave?.call(this, this.props.filename, this.getCode(this.state.integration), this.state.propertyOnly); @@ -112,21 +132,21 @@ export class KaravanDesigner extends React.Component<Props, State> { <Tab eventKey='routes' title={this.getTab("Routes", "Integration flows", "routes")}></Tab> <Tab eventKey='rest' title={this.getTab("REST", "REST services", "rest")}></Tab> <Tab eventKey='beans' title={this.getTab("Beans", "Beans Configuration", "beans")}></Tab> - <Tab eventKey='error' title={this.getTab("Error", "Error Handler", "error")}></Tab> + <Tab eventKey='error' title={this.getTab("Error Handler", "Global Error Handler", "error")}></Tab> </Tabs> - {tab === 'routes' && <RouteDesigner integration={this.state.integration} - onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)} - dark={this.props.dark} - ref={this.state.routeDesignerRef}/>} - {tab === 'rest' && <RestDesigner integration={this.state.integration} - onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)} - dark={this.props.dark}/>} - {tab === 'beans' && <BeansDesigner integration={this.state.integration} - onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)} - dark={this.props.dark}/>} - {tab === 'error' && <ErrorDesigner integration={this.state.integration} - onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)} - dark={this.props.dark}/>} + {tab === 'routes' && <RouteDesigner integration={this.state.integration} + onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)} + dark={this.props.dark} + ref={this.state.routeDesignerRef}/>} + {tab === 'rest' && <RestDesigner integration={this.state.integration} + onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)} + dark={this.props.dark}/>} + {tab === 'beans' && <BeansDesigner integration={this.state.integration} + onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)} + dark={this.props.dark}/>} + {tab === 'error' && <ErrorHandlerDesigner integration={this.state.integration} + onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)} + dark={this.props.dark}/>} </PageSection> ) } diff --git a/karavan-app/src/main/webui/src/designer/error/ErrorHandlerCard.tsx b/karavan-app/src/main/webui/src/designer/error/ErrorHandlerCard.tsx new file mode 100644 index 0000000..f0d534c --- /dev/null +++ b/karavan-app/src/main/webui/src/designer/error/ErrorHandlerCard.tsx @@ -0,0 +1,48 @@ +/* + * 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 { + Button +} from '@patternfly/react-core'; +import '../karavan.css'; +import {ErrorHandlerDefinition} from "karavan-core/lib/model/CamelDefinition"; +import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-circle-icon"; + +interface Props { + errorHandler: ErrorHandlerDefinition + deleteElement: (element: ErrorHandlerDefinition) => void +} + +export class ErrorHandlerCard extends React.Component<Props, any> { + + delete = (evt: React.MouseEvent) => { + evt.stopPropagation(); + this.props.deleteElement.call(this, this.props.errorHandler); + } + + render() { + return ( + <div className="rest-card rest-card-selected"> + <div className="header"> + <div className="title">Error Handler</div> + <div className="description">Global error handler for the RouteBuilder</div> + <Button variant="link" className="delete-button" onClick={e => this.delete(e)}><DeleteIcon/></Button> + </div> + </div> + ); + } +} diff --git a/karavan-app/src/main/webui/src/designer/error/ErrorHandlerDesigner.tsx b/karavan-app/src/main/webui/src/designer/error/ErrorHandlerDesigner.tsx new file mode 100644 index 0000000..ba35ccc --- /dev/null +++ b/karavan-app/src/main/webui/src/designer/error/ErrorHandlerDesigner.tsx @@ -0,0 +1,164 @@ +/* + * 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 { + Button, Drawer, DrawerContent, DrawerContentBody, DrawerPanelContent, Modal, PageSection +} from '@patternfly/react-core'; +import '../karavan.css'; +import {ErrorHandlerDefinition} from "karavan-core/lib/model/CamelDefinition"; +import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition"; +import {CamelUi} from "../utils/CamelUi"; +import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon"; +import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt"; +import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; +import {ErrorHandlerCard} from "./ErrorHandlerCard"; +import {DslProperties} from "../route/DslProperties"; + +interface Props { + onSave?: (integration: Integration, propertyOnly: boolean) => void + integration: Integration + dark: boolean +} + +interface State { + integration: Integration + showDeleteConfirmation: boolean + errorHandler?: ErrorHandlerDefinition + key: string + propertyOnly: boolean +} + +export class ErrorHandlerDesigner extends React.Component<Props, State> { + + public state: State = { + integration: this.props.integration, + showDeleteConfirmation: false, + key: "", + propertyOnly: false + } + + componentDidMount() { + this.setState({key: Math.random().toString()}) + } + + componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => { + if (prevState.key !== this.state.key) { + this.props.onSave?.call(this, this.state.integration, this.state.propertyOnly); + } + } + + showDeleteConfirmation = (errorHandler: ErrorHandlerDefinition) => { + this.setState({errorHandler: errorHandler, showDeleteConfirmation: true}); + } + + onIntegrationUpdate = (i: Integration) => { + this.setState({integration: i, propertyOnly: false, showDeleteConfirmation: false, key: Math.random().toString()}); + } + + deleteErrorHandler = () => { + const i = CamelDefinitionApiExt.deleteErrorHandlerFromIntegration(this.state.integration); + this.setState({ + integration: i, + showDeleteConfirmation: false, + key: Math.random().toString(), + errorHandler: undefined, + propertyOnly: false + }); + } + + changeErrorHandler = (errorHandler: ErrorHandlerDefinition) => { + const clone = CamelUtil.cloneIntegration(this.state.integration); + const i = CamelDefinitionApiExt.addErrorHandlerToIntegration(clone, errorHandler); + this.setState({integration: i, propertyOnly: false, key: Math.random().toString(), errorHandler: errorHandler}); + } + + getDeleteConfirmation() { + return (<Modal + className="modal-delete" + title="Confirmation" + isOpen={this.state.showDeleteConfirmation} + onClose={() => this.setState({showDeleteConfirmation: false})} + actions={[ + <Button key="confirm" variant="primary" onClick={e => this.deleteErrorHandler()}>Delete</Button>, + <Button key="cancel" variant="link" + onClick={e => this.setState({showDeleteConfirmation: false})}>Cancel</Button> + ]} + onEscapePress={e => this.setState({showDeleteConfirmation: false})}> + <div> + Delete Global Error Handler from integration? + </div> + </Modal>) + } + + createErrorHandlerErrorHandle = () => { + this.changeErrorHandler(new ErrorHandlerDefinition()); + } + + onPropertyUpdate = (element: CamelElement) => { + const clone = CamelUtil.cloneIntegration(this.state.integration); + const i = CamelDefinitionApiExt.addErrorHandlerToIntegration(clone, element); + this.setState({integration: i, propertyOnly: true, key: Math.random().toString()}); + } + + getPropertiesPanel(errorHandler?: ErrorHandlerDefinition) { + return ( + <DrawerPanelContent isResizable hasNoBorder defaultSize={'400px'} maxSize={'800px'} minSize={'300px'}> + <DslProperties + integration={this.props.integration} + step={errorHandler} + onIntegrationUpdate={this.onIntegrationUpdate} + onPropertyUpdate={this.onPropertyUpdate} + clipboardStep={undefined} + isRouteDesigner={false} + onClone={element => {}} + dark={this.props.dark}/> + </DrawerPanelContent> + ) + } + + render() { + const errorHandler = CamelUi.getErrorHandler(this.state.integration); + return ( + <PageSection className="rest-page" isFilled padding={{default: 'noPadding'}}> + <div className="rest-page-columns"> + <Drawer isExpanded isInline> + <DrawerContent panelContent={this.getPropertiesPanel(errorHandler)}> + <DrawerContentBody> + <div className="graph" data-click="REST"> + <div className="flows"> + {errorHandler && <ErrorHandlerCard key={errorHandler.uuid + this.state.key} + errorHandler={errorHandler} + deleteElement={this.showDeleteConfirmation}/>} + <div className="add-rest"> + {errorHandler === undefined && <Button + variant="primary" + data-click="ADD_REST" + icon={<PlusIcon/>} + onClick={e => this.createErrorHandlerErrorHandle()}>Create new error handler + </Button>} + </div> + </div> + </div> + </DrawerContentBody> + </DrawerContent> + </Drawer> + </div> + {this.getDeleteConfirmation()} + </PageSection> + ) + } +} diff --git a/karavan-app/src/main/webui/src/designer/karavan.css b/karavan-app/src/main/webui/src/designer/karavan.css index 17b9f6b..2ae90aa 100644 --- a/karavan-app/src/main/webui/src/designer/karavan.css +++ b/karavan-app/src/main/webui/src/designer/karavan.css @@ -468,7 +468,7 @@ width: 350px; } -.karavan .properties .add-button { +.karavan .properties .change-button { font-size: 15px; height: 15px; line-height: 1; @@ -478,10 +478,18 @@ background: transparent; } -.karavan .properties .add-button svg { +.karavan .properties .change-button svg { margin-right: 6px; } +.karavan .properties .add-button { + color: var(--pf-global--active-color--100); +} + +.karavan .properties .delete-button { + color: #909090; +} + .karavan .properties .pf-c-expandable-section__toggle { margin: 0; padding: 0; diff --git a/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx b/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx index 14ded06..6c976a9 100644 --- a/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx +++ b/karavan-app/src/main/webui/src/designer/route/RouteDesigner.tsx @@ -40,6 +40,7 @@ import {CamelUi, RouteToCreate} from "../utils/CamelUi"; import {findDOMNode} from "react-dom"; import {CamelDisplayUtil} from "karavan-core/lib/api/CamelDisplayUtil"; import {toPng} from 'html-to-image'; +import {KaravanDesigner} from "../KaravanDesigner"; interface Props { onSave?: (integration: Integration, propertyOnly: boolean) => void diff --git a/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx b/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx index 9f4504e..369f4ee 100644 --- a/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx +++ b/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx @@ -36,8 +36,6 @@ import { Tooltip, Card, InputGroup, - Modal, - ModalVariant, Title, TitleSizes } from '@patternfly/react-core'; import '../../karavan.css'; import "@patternfly/patternfly/patternfly.css"; @@ -64,8 +62,10 @@ import ExpandIcon from "@patternfly/react-icons/dist/js/icons/expand-icon"; import KubernetesIcon from "@patternfly/react-icons/dist/js/icons/openshift-icon"; import {KubernetesSelector} from "./KubernetesSelector"; import {KubernetesAPI} from "../../utils/KubernetesAPI"; -import Editor from "@monaco-editor/react"; import EditorIcon from "@patternfly/react-icons/dist/js/icons/code-icon"; +import {TemplateApi} from "karavan-core/lib/api/TemplateApi"; +import {ModalEditor} from "./ModalEditor"; +import {KaravanInstance} from "../../KaravanDesigner"; interface Props { property: PropertyMeta, @@ -88,6 +88,7 @@ interface State { showEditor: boolean showKubernetesSelector: boolean kubernetesSelectorProperty?: string + customCode?: string ref: any } @@ -148,13 +149,14 @@ export class DslPropertyField extends React.Component<Props, State> { getLabel = (property: PropertyMeta, value: any) => { if (!this.isMultiValueField(property) && property.isObject && !property.isArray && !["ExpressionDefinition"].includes(property.type)) { const tooltip = value ? "Delete " + property.name : "Add " + property.name; + const className = value ? "change-button delete-button" : "change-button add-button"; const x = value ? undefined : CamelDefinitionApi.createStep(property.type, {}); const icon = value ? (<DeleteIcon noVerticalAlign/>) : (<AddIcon noVerticalAlign/>); return ( <div style={{display: "flex"}}> <Text>{property.displayName} </Text> - <Tooltip position={"bottom"} content={<div>{tooltip}</div>}> - <button className="add-button" onClick={e => this.props.onChange?.call(this, property.name, x)} aria-label="Add element"> + <Tooltip position={"top"} content={<div>{tooltip}</div>}> + <button className={className} onClick={e => this.props.onChange?.call(this, property.name, x)} aria-label="Add element"> {icon} </button> </Tooltip> @@ -243,9 +245,51 @@ export class DslPropertyField extends React.Component<Props, State> { </InputGroup>) } + showCode = (name: string) => { + const {property} = this.props; + KaravanInstance.getProps().onGetCustomCode.call(this, name).then(value => { + if (value === undefined) { + const code = TemplateApi.generateCode(property.javaType, name); + this.setState({customCode: code, showEditor: true}) + } else { + this.setState({customCode: value, showEditor: true}) + } + }).catch(reason => console.log(reason)) + } + + getJavaTypeGeneratedInput = (property: PropertyMeta, value: any) => { + const {dslLanguage, dark} = this.props; + const {showEditor, customCode} = this.state; + return (<InputGroup> + <TextInput + ref={this.state.ref} + className="text-field" isRequired isReadOnly={this.isUriReadOnly(property)} + type="text" + id={property.name} name={property.name} + value={value?.toString()} + onChange={e => this.propertyChanged(property.name, CamelUtil.capitalizeName(e?.replace(/\s/g, '')))}/> + <Tooltip position="bottom-end" content={"Create Java Class"}> + <Button variant="control" onClick={e => this.showCode(value)}> + <PlusIcon/> + </Button> + </Tooltip> + <ModalEditor property={property} + value={customCode} + showEditor={showEditor} + dark={dark} + dslLanguage={dslLanguage} + title="Java Class" + onSave={(fieldId, value1) => { + this.propertyChanged(fieldId, value); + KaravanInstance.getProps().onSaveCustomCode?.call(this, value, value1); + this.setState({showEditor: false}); + }}/> + </InputGroup>) + } + getTextArea = (property: PropertyMeta, value: any) => { const {dslLanguage, dark} = this.props; - const showEditor = this.state.showEditor; + const {showEditor} = this.state; return ( <InputGroup> <TextArea @@ -261,37 +305,16 @@ export class DslPropertyField extends React.Component<Props, State> { <EditorIcon/> </Button> </Tooltip> - <Modal - variant={ModalVariant.large} - header={<React.Fragment> - <Title id="modal-custom-header-label" headingLevel="h1" size={TitleSizes['2xl']}> - {`Expression (${dslLanguage?.[0]})`} - </Title> - <p className="pf-u-pt-sm">{dslLanguage?.[2]}</p> - </React.Fragment>} - isOpen={this.state.showEditor} - onClose={() => this.setState({showEditor: false})} - actions={[ - <Button key="cancel" variant="primary" isSmall - onClick={e => this.setState({showEditor: false})}>Close</Button> - ]} - onEscapePress={e => this.setState({showEditor: false})}> - <Editor - height="400px" - width="100%" - defaultLanguage={'java'} - language={'java'} - theme={dark ? 'vs-dark' : 'light'} - options={{lineNumbers:"off", folding:false, lineNumbersMinChars:10, showUnused:false, fontSize:12, minimap:{enabled:false}}} - value={value?.toString()} - className={'code-editor'} - onChange={(value: any, ev: any) => { - if (value) { - this.propertyChanged(property.name, value) - } - }} - /> - </Modal> + <ModalEditor property={property} + value={value} + showEditor={showEditor} + dark={dark} + dslLanguage={dslLanguage} + title={`Expression (${dslLanguage?.[0]})`} + onSave={(fieldId, value1) => { + this.propertyChanged(fieldId, value1); + this.setState({showEditor: false}); + }}/> </InputGroup> ) } @@ -433,6 +456,10 @@ export class DslPropertyField extends React.Component<Props, State> { } } + javaTypeGenerated = (property: PropertyMeta): boolean => { + return property.javaType.length !== 0; + } + getInternalUriSelect = (property: PropertyMeta, value: any) => { const selectOptions: JSX.Element[] = []; const urls = CamelUi.getInternalRouteUris(this.props.integration, "direct"); @@ -672,10 +699,13 @@ export class DslPropertyField extends React.Component<Props, State> { && this.getInternalUriSelect(property, value)} {this.canBeMediaType(property, this.props.element) && this.getMediaTypeSelect(property, value)} + {this.javaTypeGenerated(property) + && this.getJavaTypeGeneratedInput(property, value)} {['string', 'duration', 'integer', 'number'].includes(property.type) && property.name !== 'expression' && !property.name.endsWith("Ref") && !property.isArray && !property.enumVals && !this.canBeInternalUri(property, this.props.element) && !this.canBeMediaType(property, this.props.element) + && !this.javaTypeGenerated(property) && this.getStringInput(property, value)} {['string'].includes(property.type) && property.name.endsWith("Ref") && !property.isArray && !property.enumVals && this.getSelectBean(property, value)} diff --git a/karavan-app/src/main/webui/src/designer/route/property/ModalEditor.tsx b/karavan-app/src/main/webui/src/designer/route/property/ModalEditor.tsx new file mode 100644 index 0000000..a12242a --- /dev/null +++ b/karavan-app/src/main/webui/src/designer/route/property/ModalEditor.tsx @@ -0,0 +1,98 @@ +/* + * 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 { + Button, + Modal, + ModalVariant, Title, TitleSizes +} from '@patternfly/react-core'; +import '../../karavan.css'; +import "@patternfly/patternfly/patternfly.css"; +import {PropertyMeta} from "karavan-core/lib/model/CamelMetadata"; +import Editor from "@monaco-editor/react"; + +interface Props { + property: PropertyMeta, + value: any, + onSave?: (fieldId: string, value: string | number | boolean | any) => void, + title: string, + dslLanguage?: [string, string, string], + dark: boolean + showEditor: boolean +} + +interface State { + value: any, +} + +export class ModalEditor extends React.Component<Props, State> { + + public state: State = { + value: this.props.value, + } + + componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => { + if (prevProps.showEditor !== this.props.showEditor) { + this.setState({value: this.props.value}) + } + } + + close(){ + this.props.onSave?.call(this, this.props.property.name, this.props.value); + } + + closeAndSave(){ + this.props.onSave?.call(this, this.props.property.name, this.state.value); + } + + render() { + const {dark, dslLanguage, title, showEditor} = this.props; + const {value} = this.state; + return ( + <Modal + aria-label={"expression"} + variant={ModalVariant.large} + header={<React.Fragment> + <Title id="modal-custom-header-label" headingLevel="h1" size={TitleSizes['2xl']}> + {title} + </Title> + <p className="pf-u-pt-sm">{dslLanguage?.[2]}</p> + </React.Fragment>} + isOpen={showEditor} + onClose={() => this.close()} + actions={[ + <Button key="save" variant="primary" isSmall + onClick={e => this.closeAndSave()}>Save</Button>, + <Button key="cancel" variant="secondary" isSmall + onClick={e => this.close()}>Close</Button> + ]} + onEscapePress={e => this.close()}> + <Editor + height="400px" + width="100%" + defaultLanguage={'java'} + language={'java'} + theme={dark ? 'vs-dark' : 'light'} + options={{lineNumbers: "off", folding: false, lineNumbersMinChars: 10, showUnused: false, fontSize: 12, minimap: {enabled: false}}} + value={value?.toString()} + className={'code-editor'} + onChange={(value: any, ev: any) => this.setState({value: value})} + /> + </Modal> + ) + } +} diff --git a/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx b/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx index 5c98f13..51f8517 100644 --- a/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx +++ b/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx @@ -21,9 +21,9 @@ import {ComponentApi} from "karavan-core/lib/api/ComponentApi"; import {CamelMetadataApi} from "karavan-core/lib/model/CamelMetadata"; import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt"; -import {NamedBeanDefinition, RouteDefinition, SagaDefinition, ToDefinition} from "karavan-core/lib/model/CamelDefinition"; +import {ErrorHandlerDefinition, NamedBeanDefinition, RouteDefinition, SagaDefinition, ToDefinition} from "karavan-core/lib/model/CamelDefinition"; import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition"; -import {AggregateIcon, ChoiceIcon, FilterIcon, SagaIcon, SortIcon, SplitIcon, TransformIcon} from "./KaravanIcons"; +import {AggregateIcon, ChoiceIcon, FilterIcon, SagaIcon, SortIcon, SplitIcon} from "./KaravanIcons"; import React from "react"; const StepElements: string[] = [ @@ -35,27 +35,34 @@ const StepElements: string[] = [ "ConvertBodyDefinition", "DynamicRouterDefinition", "EnrichDefinition", - // "ErrorHandlerBuilderDeserializer", // "ErrorHandlerDefinition", "FilterDefinition", + "IdempotentConsumerDefinition", "LogDefinition", "LoopDefinition", "MarshalDefinition", "MulticastDefinition", + "PausableDefinition", "PollEnrichDefinition", "ProcessDefinition", "RecipientListDefinition", "RemoveHeaderDefinition", "RemoveHeadersDefinition", + "RemovePropertiesDefinition", "RemovePropertyDefinition", + "ResumableDefinition", "ResequenceDefinition", + "RoutingSlipDefinition", + "SamplingDefinition", "SagaDefinition", "SetBodyDefinition", "SetHeaderDefinition", "SetPropertyDefinition", "SortDefinition", + "ScriptDefinition", "SplitDefinition", "StepDefinition", + "StopDefinition", "ThreadsDefinition", "ThrottleDefinition", "ThrowExceptionDefinition", @@ -380,6 +387,8 @@ export class CamelUi { return "data:image/svg+xml,%3Csvg width='32' height='32' xmlns='http://www.w3.org/2000/svg' xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1 %7B fill: none; %7D%3C/style%3E%3C/defs%3E%3Cg class='layer'%3E%3Ctitle%3ELayer 1%3C/title%3E%3Cpath d='m24,30l-20,0a2.0021,2.0021 0 0 1 -2,-2l0,-6a2.0021,2.0021 0 0 1 2,-2l20,0a2.0021,2.0021 0 0 1 2,2l0,6a2.0021,2.0021 0 0 1 -2,2zm-20,-8l-0.0015,0l0.0015,6l20,0l0,-6l-20,0z' id='svg_1'/%3E%3Cpolygon id='svg_2' poi [...] case "RemoveHeadersDefinition": return "data:image/svg+xml,%3Csvg width='32' height='32' xmlns='http://www.w3.org/2000/svg' xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1 %7B fill: none; %7D%3C/style%3E%3C/defs%3E%3Cg class='layer'%3E%3Ctitle%3ELayer 1%3C/title%3E%3Cpolygon id='svg_2' points='32.12467002868652,6.015733242034912 32.12467002868652,4.021692276000977 27.047643661499023,4.021692276000977 27.047643661499023,-1.0553351640701294 25.05360221862793,-1.0553350448608398 25.053 [...] + case "RemovePropertiesDefinition": + return "data:image/svg+xml,%3Csvg width='32' height='32' xmlns='http://www.w3.org/2000/svg' xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1 %7B fill: none; %7D%3C/style%3E%3C/defs%3E%3Cg class='layer'%3E%3Ctitle%3ELayer 1%3C/title%3E%3Cpolygon id='svg_2' points='32.12467002868652,6.015733242034912 32.12467002868652,4.021692276000977 27.047643661499023,4.021692276000977 27.047643661499023,-1.0553351640701294 25.05360221862793,-1.0553350448608398 25.053 [...] case "SetHeaderDefinition": return "data:image/svg+xml,%3Csvg width='32' height='32' xmlns='http://www.w3.org/2000/svg' xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1 %7B fill: none; %7D%3C/style%3E%3C/defs%3E%3Cg class='layer'%3E%3Ctitle%3ELayer 1%3C/title%3E%3Cpath d='m24,30l-20,0a2.0021,2.0021 0 0 1 -2,-2l0,-6a2.0021,2.0021 0 0 1 2,-2l20,0a2.0021,2.0021 0 0 1 2,2l0,6a2.0021,2.0021 0 0 1 -2,2zm-20,-8l-0.0015,0l0.0015,6l20,0l0,-6l-20,0z' id='svg_1'/%3E%3Cpolygon id='svg_2' poi [...] case "SetPropertyDefinition": @@ -395,7 +404,7 @@ export class CamelUi { case "ConvertBodyDefinition": return "data:image/svg+xml,%3Csvg width='32' height='32' xmlns='http://www.w3.org/2000/svg' xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1 %7B fill: none; %7D%3C/style%3E%3C/defs%3E%3Cg class='layer'%3E%3Ctitle%3ELayer 1%3C/title%3E%3Cpath d='m16,28l-8,0l0,-24l8,0l0,6a2.0058,2.0058 0 0 0 2,2l6,0l0,4l2,0l0,-6a0.9092,0.9092 0 0 0 -0.3,-0.7l-7,-7a0.9087,0.9087 0 0 0 -0.7,-0.3l-10,0a2.0058,2.0058 0 0 0 -2,2l0,24a2.0058,2.0058 0 0 0 2,2l8,0l0,-2zm2,-23.6l [...] case "TransformDefinition": - return "data:image/svg+xml,%0A%3Csvg width='32px' height='32px' viewBox='0 0 32 32' id='icon' xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill:none;%7D%3C/style%3E%3C/defs%3E%3Ctitle%3Escript%3C/title%3E%3Cpolygon points='18.83 26 21.41 23.42 20 22 16 26 20 30 21.42 28.59 18.83 26'/%3E%3Cpolygon points='27.17 26 24.59 28.58 26 30 30 26 26 22 24.58 23.41 27.17 26'/%3E%3Cpath d='M14,28H8V4h8v6a2.0058,2.0058,0,0,0,2,2h6v6h2V10a.9092.9092,0,0,0-.3-.7l-7 [...] + return "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGlkPSJpY29uIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO308L3N0eWxlPjwvZGVmcz48dGl0bGU+ZGF0YS1zaGFyZTwvdGl0bGU+PHBhdGggZD0iTTUsMjVWMTUuODI4MWwtMy41ODU5LDMuNTg2TDAsMThsNi02LDYsNi0xLjQxNDEsMS40MTQxTDcsMTUuODI4MVYyNUgxOXYySDdBMi4wMDI0LDIuMDAyNCwwLDAsMSw1LDI1WiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAwKSIvPjxwYXRoIGQ9Ik0yNCwyMmg0YTIuMDAyLD [...] case "EnrichDefinition": return "data:image/svg+xml,%3Csvg width='32' height='32' xmlns='http://www.w3.org/2000/svg' xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cstyle%3E.cls-1 %7B fill: none; %7D%3C/style%3E%3C/defs%3E%3Cg class='layer'%3E%3Ctitle%3ELayer 1%3C/title%3E%3Cpolygon id='svg_1' points='4,20 4,22 8.586000442504883,22 2,28.586000442504883 3.4140000343322754,30 10,23.413999557495117 10,28 12,28 12,20 4,20 '/%3E%3Cpath d='m25.7,9.3l-7,-7a0.9087,0.9087 0 0 0 -0.7,-0.3l-10,0a2.005 [...] case "PollEnrichDefinition": @@ -426,6 +435,24 @@ export class CamelUi { return "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' id='Layer_1' x='0px' y='0px' viewBox='0 0 305.001 305.001' style='enable-background:new 0 0 305.001 305.001;' xml:space='preserve'%3E%3Cg id='XMLID_7_'%3E%3Cpath id='XMLID_8_' d='M150.99,56.513c-14.093,9.912-30.066,21.147-38.624,39.734c-14.865,32.426,30.418,67.798,32.353,69.288 c0.45,0.347,0.988,0.519,1.525,0.519c0.57,0,1.141-0.195,1.605-0.583c0.89 [...] case "ProcessDefinition": return "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' id='Capa_1' x='0px' y='0px' width='235.506px' height='235.506px' viewBox='0 0 235.506 235.506' style='enable-background:new 0 0 235.506 235.506;' xml:space='preserve'%3E%3Cg%3E%3Cpath d='M234.099,29.368c-3.025-4.861-10.303-7.123-22.915-7.123c-13.492,0-28.304,2.661-28.625,2.733 c-20.453,2.098-27.254,26.675-32.736,46.436c-1.924,6.969-3.755,13.549-5.8 [...] + case "ErrorHandlerDefinition": + return "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGlkPSJpY29uIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO308L3N0eWxlPjwvZGVmcz48dGl0bGU+d2FybmluZzwvdGl0bGU+PHBhdGggZD0iTTE2LDJBMTQsMTQsMCwxLDAsMzAsMTYsMTQsMTQsMCwwLDAsMTYsMlptMCwyNkExMiwxMiwwLDEsMSwyOCwxNiwxMiwxMiwwLDAsMSwxNiwyOFoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgMCkiLz48cmVjdCB4PSIxNSIgeT0iOCIgd2lkdGg9IjIiIGhlaWdodD0iMTEiLz48cG [...] + case "ScriptDefinition": + return "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjU2cHgiIGhlaWdodD0iMjU2cHgiIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBpZD0iRmxhdCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cGF0aCBkPSJNNDMuMTc1MjksMTI4YTI5Ljc4NTIsMjkuNzg1MiwwLDAsMSw4LjAyMywxMC4yNTk3N0M1NiwxNDguMTYzMDksNTYsMTYwLjI4MTI1LDU2LDE3MmMwLDI0LjMxMzQ4LDEuMDE5NTMsMzYsMjQsMzZhOCw4LDAsMCwxLDAsMTZjLTE3LjQ4MTQ1LDAtMjkuMzI0MjItNi4xNDM1NS0zNS4xOTgyNC0xOC4yNTk3N0M0MCwxOTUuODM2OTEsNDAsMTgzLjcxODc1LDQwLDE3MmMwLTI0LjMxMzQ4LT [...] + case "PausableDefinition": + return "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGlkPSJpY29uIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO308L3N0eWxlPjwvZGVmcz48dGl0bGU+cGF1c2UtLWZpbGxlZDwvdGl0bGU+PHBhdGggZD0iTTEyLDZIMTBBMiwyLDAsMCwwLDgsOFYyNGEyLDIsMCwwLDAsMiwyaDJhMiwyLDAsMCwwLDItMlY4YTIsMiwwLDAsMC0yLTJaIi8+PHBhdGggZD0iTTIyLDZIMjBhMiwyLDAsMCwwLTIsMlYyNGEyLDIsMCwwLDAsMiwyaDJhMiwyLDAsMCwwLDItMlY4YTIsMiwwLDAsMC [...] + case "StopDefinition": + return "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGlkPSJpY29uIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO308L3N0eWxlPjwvZGVmcz48dGl0bGU+c3RvcC0tZmlsbGVkPC90aXRsZT48cGF0aCBkPSJNMjQsNkg4QTIsMiwwLDAsMCw2LDhWMjRhMiwyLDAsMCwwLDIsMkgyNGEyLDIsMCwwLDAsMi0yVjhhMiwyLDAsMCwwLTItMloiLz48cmVjdCBpZD0iX1RyYW5zcGFyZW50X1JlY3RhbmdsZV8iIGRhdGEtbmFtZT0iJmx0O1RyYW5zcGFyZW50IFJlY3RhbmdsZSZndDsiIG [...] + case "ResumableDefinition": + return "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTVweCIgaGVpZ2h0PSIxNXB4IiB2aWV3Qm94PSIwIDAgMTUgMTUiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGgKICAgIGZpbGwtcnVsZT0iZXZlbm9kZCIKICAgIGNsaXAtcnVsZT0iZXZlbm9kZCIKICAgIGQ9Ik0zLjA0OTk1IDIuNzQ5OTVDMy4wNDk5NSAyLjQ0NjE5IDIuODAzNzEgMi4xOTk5NSAyLjQ5OTk1IDIuMTk5OTVDMi4xOTYxOSAyLjE5OTk1IDEuOTQ5OTUgMi40NDYxOSAxLjk0OTk1IDIuNzQ5OTVWMTIuMjVDMS45NDk5NSAxMi41NTM3IDIuMTk2MTkgMTIuOCAyLjQ5OTk1IDEyLjhDMi44MDM3MS [...] + case "RoutingSlipDefinition": + return "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGlkPSJpY29uIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO308L3N0eWxlPjwvZGVmcz48dGl0bGU+c2NoZW1hdGljczwvdGl0bGU+PHBhdGggZD0iTTI3LDE5LjAwMUE0LjAwNTYsNC4wMDU2LDAsMCwwLDIyLjk5OTEsMTVIOS4wMDExQTIuMDAzMSwyLjAwMzEsMCwwLDEsNywxMi45OTkxVjkuODU4QTMuOTk0OSwzLjk5NDksMCwwLDAsOS44NTgxLDdoMTIuMjg0YTQsNCwwLDEsMCwwLTJIOS44NTgxQTMuOTkxNiwzLjk5MT [...] + case "ClaimCheckDefinition": + return "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGlkPSJpY29uIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO308L3N0eWxlPjwvZGVmcz48dGl0bGU+Y2VydGlmaWNhdGUtLWNoZWNrPC90aXRsZT48cmVjdCB4PSI2IiB5PSIxNiIgd2lkdGg9IjYiIGhlaWdodD0iMiIvPjxyZWN0IHg9IjYiIHk9IjEyIiB3aWR0aD0iMTAiIGhlaWdodD0iMiIvPjxyZWN0IHg9IjYiIHk9IjgiIHdpZHRoPSIxMCIgaGVpZ2h0PSIyIi8+PHBhdGggZD0iTTE0LDI2SDRWNkgyOFYxNmgyVjZhMi [...] + case "SamplingDefinition": + return "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgdmlld0JveD0iMCAwIDUxMiA1MTIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDUxMiA1MT [...] + case "IdempotentConsumerDefinition": + return "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiIGlkPSJpY29uIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO308L3N0eWxlPjwvZGVmcz48dGl0bGU+cmVzZWFyY2gtLW1hdHJpeDwvdGl0bGU+PHBvbHlnb24gcG9pbnRzPSIxOCAxMyAxOCA0IDE2IDQgMTYgNiAxMyA2IDEzIDggMTYgOCAxNiAxMyAxMyAxMyAxMyAxNSAyMSAxNSAyMSAxMyAxOCAxMyIvPjxwYXRoIGQ9Ik0xNi41LDIwQTMuNSwzLjUsMCwxLDEsMTMsMjMuNSwzLjUsMy41LDAsMCwxLDE2LjUsMjBtMC0yQT [...] default: return camelIcon; } @@ -460,7 +487,6 @@ export class CamelUi { case 'ChoiceDefinition' :return <ChoiceIcon/>; case 'SplitDefinition' :return <SplitIcon/>; case 'SagaDefinition' :return <SagaIcon/>; - case 'TransformDefinition' :return <TransformIcon/>; case 'FilterDefinition' :return <FilterIcon/>; case 'SortDefinition' :return <SortIcon/>; default: return this.getIconFromSource(CamelUi.getIconSrcForName(dslName)) @@ -486,6 +512,7 @@ export class CamelUi { const result = new Map<string, number>(); result.set('routes', i.spec.flows?.filter((e: any) => e.dslName === 'RouteDefinition').length || 0); result.set('rest', i.spec.flows?.filter((e: any) => e.dslName === 'RestDefinition').length || 0); + result.set('error', i.spec.flows?.filter((e: any) => e.dslName === 'ErrorHandlerDefinition').length || 0); const beans = i.spec.flows?.filter((e: any) => e.dslName === 'Beans'); if (beans && beans.length > 0 && beans[0].beans && beans[0].beans.length > 0){ result.set('beans', Array.from(beans[0].beans).length); @@ -508,4 +535,9 @@ export class CamelUi { } return result; } + + static getErrorHandler = (integration: Integration): ErrorHandlerDefinition | undefined => { + const errorHandler = integration.spec.flows?.filter((e: any) => e.dslName === 'ErrorHandlerDefinition').at(0); + return errorHandler; + } } \ No newline at end of file diff --git a/karavan-app/src/main/webui/src/projects/ProjectPage.tsx b/karavan-app/src/main/webui/src/projects/ProjectPage.tsx index faf8d5c..f998586 100644 --- a/karavan-app/src/main/webui/src/projects/ProjectPage.tsx +++ b/karavan-app/src/main/webui/src/projects/ProjectPage.tsx @@ -108,14 +108,16 @@ export class ProjectPage extends React.Component<Props, State> { return this.props.project.projectId === 'kamelets'; } - isTemplatesProject():boolean { - return this.props.project.projectId === 'templates'; - } - post = (file: ProjectFile) => { KaravanApi.postProjectFile(file, res => { if (res.status === 200) { - // console.log(res) //TODO show notification + const newFile = res.data; + this.setState((state => { + const index = state.files.findIndex(f => f.name === newFile.name); + if (index !== -1) state.files.splice(index, 1, newFile) + else state.files.push(newFile); + return state + })) } else { // console.log(res) //TODO show notification } @@ -191,7 +193,10 @@ export class ProjectPage extends React.Component<Props, State> { {isFile && <div> <Breadcrumb> - <BreadcrumbItem to="#" onClick={event => this.setState({file: undefined})}> + <BreadcrumbItem to="#" onClick={event => { + this.setState({file: undefined}) + this.onRefresh(); + }}> {"Project: " + project?.projectId} </BreadcrumbItem> <Badge>{getProjectFileType(file)}</Badge> @@ -236,7 +241,8 @@ export class ProjectPage extends React.Component<Props, State> { } getDesigner = () => { - const file = this.state.file; + const {file, files} = this.state; + const {project} = this.props; return ( file !== undefined && <KaravanDesigner @@ -246,6 +252,10 @@ export class ProjectPage extends React.Component<Props, State> { filename={file.name} yaml={file.code} onSave={(name, yaml) => this.save(name, yaml)} + onSaveCustomCode={(name, code) => this.post(new ProjectFile(name+".java", project.projectId, code, Date.now()))} + onGetCustomCode={name => { + return new Promise<string | undefined>(resolve => resolve(files.filter(f => f.name === name + ".java")?.at(0)?.code)) + }} /> ) } diff --git a/karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx b/karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx index 19550c4..6314a50 100644 --- a/karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx +++ b/karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx @@ -62,7 +62,7 @@ export class ProjectPageToolbar extends React.Component<Props> { } getTemplatesToolbar() { - const {project, file, editAdvancedProperties} = this.props; + const {file, editAdvancedProperties} = this.props; const {isPushing} = this.state; const isProperties = file !== undefined && file.name.endsWith("properties"); return <Toolbar id="toolbar-group-types"> diff --git a/karavan-designer/src/designer/route/property/DslPropertyField.tsx b/karavan-designer/src/designer/route/property/DslPropertyField.tsx index 369f4ee..773b36d 100644 --- a/karavan-designer/src/designer/route/property/DslPropertyField.tsx +++ b/karavan-designer/src/designer/route/property/DslPropertyField.tsx @@ -273,17 +273,17 @@ export class DslPropertyField extends React.Component<Props, State> { <PlusIcon/> </Button> </Tooltip> - <ModalEditor property={property} - value={customCode} - showEditor={showEditor} - dark={dark} - dslLanguage={dslLanguage} - title="Java Class" - onSave={(fieldId, value1) => { - this.propertyChanged(fieldId, value); - KaravanInstance.getProps().onSaveCustomCode?.call(this, value, value1); - this.setState({showEditor: false}); - }}/> + <ModalEditor property={property} + value={customCode} + showEditor={showEditor} + dark={dark} + dslLanguage={dslLanguage} + title="Java Class" + onSave={(fieldId, value1) => { + this.propertyChanged(fieldId, value); + KaravanInstance.getProps().onSaveCustomCode?.call(this, value, value1); + this.setState({showEditor: false}); + }}/> </InputGroup>) }
