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 fae1fcbe7b2281276059a5e82aded7c21b300640 Author: Marat Gubaidullin <marat.gubaidul...@gmail.com> AuthorDate: Mon Jan 9 18:32:18 2023 -0500 RouteConfiguration in Route tab #596 --- .../designer/route/property/DslPropertyField.tsx | 727 --------------------- karavan-designer/src/designer/KaravanDesigner.tsx | 5 - .../configuration/RouteConfigurationCard.tsx | 58 -- .../configuration/RouteConfigurationDesigner.tsx | 174 ----- karavan-designer/src/designer/karavan.css | 1 + karavan-designer/src/designer/route/DslElement.tsx | 18 +- .../src/designer/route/RouteDesigner.tsx | 52 +- 7 files changed, 55 insertions(+), 980 deletions(-) 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 deleted file mode 100644 index f553cfe..0000000 --- a/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx +++ /dev/null @@ -1,727 +0,0 @@ -/* - * 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 { - FormGroup, - TextInput, - Popover, - Switch, - Select, - SelectVariant, - SelectDirection, - SelectOption, - ExpandableSection, - TextArea, - Chip, - TextInputGroup, - TextInputGroupMain, - TextInputGroupUtilities, - ChipGroup, - Button, - Text, - Tooltip, - Card, - InputGroup, -} from '@patternfly/react-core'; -import '../../karavan.css'; -import "@patternfly/patternfly/patternfly.css"; -import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; -import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-circle-icon"; -import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; -import {PropertyMeta} from "karavan-core/lib/model/CamelMetadata"; -import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt"; -import {ExpressionField} from "./ExpressionField"; -import {CamelUi, RouteToCreate} from "../../utils/CamelUi"; -import {ComponentParameterField} from "./ComponentParameterField"; -import {DataFormatDefinition} from "karavan-core/lib/model/CamelDefinition"; -import {Integration, CamelElement} from "karavan-core/lib/model/IntegrationDefinition"; -import {KameletPropertyField} from "./KameletPropertyField"; -import {ExpressionDefinition} from "karavan-core/lib/model/CamelDefinition"; -import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon"; -import {ObjectField} from "./ObjectField"; -import {CamelDefinitionApi} from "karavan-core/lib/api/CamelDefinitionApi"; -import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon"; -import {MediaTypes} from "../../utils/MediaTypes"; -import {ComponentProperty} from "karavan-core/lib/model/ComponentModels"; -import CompressIcon from "@patternfly/react-icons/dist/js/icons/compress-icon"; -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 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, - value: any, - onChange?: (fieldId: string, value: string | number | boolean | any, newRoute?: RouteToCreate) => void, - onExpressionChange?: (propertyName: string, exp: ExpressionDefinition) => void, - onDataFormatChange?: (value: DataFormatDefinition) => void, - onParameterChange?: (parameter: string, value: string | number | boolean | any, pathParameter?: boolean, newRoute?: RouteToCreate) => void, - element?: CamelElement - integration: Integration, - hideLabel?: boolean, - dslLanguage?: [string, string, string], - dark: boolean -} - -interface State { - selectStatus: Map<string, boolean>, - isShowAdvanced: Map<string, boolean>, - arrayValues: Map<string, string>, - showEditor: boolean - showKubernetesSelector: boolean - kubernetesSelectorProperty?: string - customCode?: string - ref: any -} - -export class DslPropertyField extends React.Component<Props, State> { - - public state: State = { - selectStatus: new Map<string, boolean>(), - arrayValues: new Map<string, string>(), - isShowAdvanced: new Map<string, boolean>(), - showEditor: false, - showKubernetesSelector: false, - ref: React.createRef(), - }; - - openSelect = (propertyName: string, isExpanded: boolean) => { - this.setState({selectStatus: new Map<string, boolean>([[propertyName, isExpanded]])}); - } - - clearSelection = (propertyName: string) => { - this.setState({selectStatus: new Map<string, boolean>([[propertyName, false]])}); - }; - - isSelectOpen = (propertyName: string): boolean => { - return this.state.selectStatus.has(propertyName) && this.state.selectStatus.get(propertyName) === true; - } - - propertyChanged = (fieldId: string, value: string | number | boolean | any, newRoute?: RouteToCreate) => { - this.props.onChange?.call(this, fieldId, value, newRoute); - this.setState({selectStatus: new Map<string, boolean>([[fieldId, false]])}); - } - - arrayChanged = (fieldId: string, value: string) => { - const tv = this.state.arrayValues; - tv.set(fieldId, value); - this.setState({arrayValues: tv}); - } - - arrayDeleteValue = (fieldId: string, element: string) => { - const property: PropertyMeta = this.props.property; - let value = this.props.value; - if (property.isArray && property.type === 'string') { - this.propertyChanged(fieldId, (value as any).filter((x: string) => x !== element)) - } - } - - arraySave = (fieldId: string) => { - const newValue = this.state.arrayValues.get(fieldId); - const property: PropertyMeta = this.props.property; - let value = this.props.value; - if (property.isArray && property.type === 'string') { - if (value) (value as any).push(newValue) - else value = [newValue]; - } - this.propertyChanged(fieldId, value); - this.arrayChanged(fieldId, ""); - } - - 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={"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> - </div> - ) - } else if (!["ExpressionDefinition"].includes(property.type)) { - return CamelUtil.capitalizeName(property.displayName); - } - } - - isUriReadOnly = (property: PropertyMeta): boolean => { - const dslName: string = this.props.element?.dslName || ''; - return property.name === 'uri' && !['ToDynamicDefinition', 'WireTapDefinition'].includes(dslName) - } - - selectKubernetes = (value: string) => { - // check if there is a selection - const textVal = this.state.ref.current; - const cursorStart = textVal.selectionStart; - const cursorEnd = textVal.selectionEnd; - if (cursorStart !== cursorEnd) { - const prevValue = this.props.value; - const selectedText = prevValue.substring(cursorStart, cursorEnd) - value = prevValue.replace(selectedText, value); - } - const propertyName = this.state.kubernetesSelectorProperty; - if (propertyName) { - if (value.startsWith("config") || value.startsWith("secret")) value = "{{" + value + "}}"; - this.propertyChanged(propertyName, value); - this.setState({showKubernetesSelector: false, kubernetesSelectorProperty: undefined}) - } - } - - openKubernetesSelector = (propertyName: string) => { - this.setState({kubernetesSelectorProperty: propertyName, showKubernetesSelector: true}); - } - - closeKubernetesSelector = () => { - this.setState({showKubernetesSelector: false}) - } - - getKubernetesSelectorModal() { - return ( - <KubernetesSelector - dark={false} - isOpen={this.state.showKubernetesSelector} - onClose={() => this.closeKubernetesSelector()} - onSelect={this.selectKubernetes}/>) - } - - getStringInput = (property: PropertyMeta, value: any) => { - const showEditor = this.state.showEditor; - const inKubernetes = KubernetesAPI.inKubernetes; - const noKubeSelectorButton = ["uri", "id", "description", "group"].includes(property.name); - return (<InputGroup> - {inKubernetes && !showEditor && !noKubeSelectorButton && - <Tooltip position="bottom-end" content="Select from Kubernetes"> - <Button variant="control" onClick={e => this.openKubernetesSelector(property.name)}> - <KubernetesIcon/> - </Button> - </Tooltip>} - {(!showEditor || property.secret) && <TextInput - ref={this.state.ref} - className="text-field" isRequired isReadOnly={this.isUriReadOnly(property)} - type={['integer', 'number'].includes(property.type) ? 'number' : (property.secret ? "password" : "text")} - id={property.name} name={property.name} - value={value?.toString()} - onChange={e => this.propertyChanged(property.name, ['integer', 'number'].includes(property.type) ? Number(e) : e)}/> - } - {showEditor && !property.secret && <TextArea - ref={this.state.ref} - autoResize={true} - 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, ['integer', 'number'].includes(property.type) ? Number(e) : e)}/> - } - {!property.secret && - <Tooltip position="bottom-end" content={showEditor ? "Change to TextField" : "Change to Text Area"}> - <Button variant="control" onClick={e => this.setState({showEditor: !showEditor})}> - {showEditor ? <CompressIcon/> : <ExpandIcon/>} - </Button> - </Tooltip> - } - </InputGroup>) - } - - showCode = (name: string, javaType: string) => { - const {property} = this.props; - KaravanInstance.getProps().onGetCustomCode.call(this, name, property.javaType).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, property.javaType)}> - <PlusIcon/> - </Button> - </Tooltip> - <ModalEditor property={property} - customCode={customCode} - showEditor={showEditor} - dark={dark} - dslLanguage={dslLanguage} - title="Java Class" - onClose={() => this.setState({showEditor: false})} - 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; - return ( - <InputGroup> - <TextArea - autoResize - className="text-field" isRequired - type={"text"} - id={property.name} name={property.name} - height={"100px"} - value={value?.toString()} - onChange={e => this.propertyChanged(property.name, e)}/> - <Tooltip position="bottom-end" content={"Show Editor"}> - <Button variant="control" onClick={e => this.setState({showEditor: !showEditor})}> - <EditorIcon/> - </Button> - </Tooltip> - <ModalEditor property={property} - customCode={value} - showEditor={showEditor} - dark={dark} - dslLanguage={dslLanguage} - title={`Expression (${dslLanguage?.[0]})`} - onClose={() => this.setState({showEditor: false})} - onSave={(fieldId, value1) => { - this.propertyChanged(fieldId, value1); - this.setState({showEditor: false}); - }}/> - </InputGroup> - ) - } - - getExpressionField = (property: PropertyMeta, value: any) => { - return ( - <div className="expression"> - <ExpressionField property={property} - value={value} - onExpressionChange={this.props.onExpressionChange} - integration={this.props.integration} - dark={this.props.dark}/> - </div> - ) - } - - getObjectField = (property: PropertyMeta, value: any) => { - return ( - <div className="object"> - {value && <ObjectField property={property} - value={value} - onPropertyUpdate={this.props.onChange} - integration={this.props.integration} - dark={this.props.dark}/>} - </div> - ) - } - - getSwitch = (property: PropertyMeta, value: any) => { - const isChecked = value !== undefined ? Boolean(value) : Boolean(property.defaultValue !== undefined && property.defaultValue === 'true'); - return ( - <Switch - id={property.name} name={property.name} - value={value?.toString()} - aria-label={property.name} - isChecked={isChecked} - onChange={e => this.propertyChanged(property.name, e)}/> - ) - } - - getSelectBean = (property: PropertyMeta, value: any) => { - const selectOptions: JSX.Element[] = []; - const beans = CamelUi.getBeans(this.props.integration); - if (beans) { - selectOptions.push(<SelectOption key={0} value={"Select..."} isPlaceholder/>); - selectOptions.push(...beans.map((bean) => <SelectOption key={bean.name} value={bean.name} description={bean.type}/>)); - } - return ( - <Select - variant={SelectVariant.single} - aria-label={property.name} - onToggle={isExpanded => { - this.openSelect(property.name, isExpanded) - }} - onSelect={(e, value, isPlaceholder) => this.propertyChanged(property.name, (!isPlaceholder ? value : undefined))} - selections={value} - isOpen={this.isSelectOpen(property.name)} - aria-labelledby={property.name} - direction={SelectDirection.down} - > - {selectOptions} - </Select> - ) - } - - getSelect = (property: PropertyMeta, value: any) => { - const selectOptions: JSX.Element[] = [] - if (property.enumVals && property.enumVals.length > 0) { - selectOptions.push(<SelectOption key={0} value={"Select " + property.name} isPlaceholder/>); - selectOptions.push(...property.enumVals.split(',').map((value: string) => - <SelectOption key={value} value={value.trim()}/>)); - } - return ( - <Select - variant={SelectVariant.single} - aria-label={property.name} - onToggle={isExpanded => { - this.openSelect(property.name, isExpanded) - }} - onSelect={(e, value, isPlaceholder) => this.propertyChanged(property.name, (!isPlaceholder ? value : undefined))} - selections={value} - isOpen={this.isSelectOpen(property.name)} - aria-labelledby={property.name} - direction={SelectDirection.down} - > - {selectOptions} - </Select> - ) - } - - getMediaTypeSelectOptions(filter?: string) { - return filter - ? MediaTypes.filter(mt => mt.includes(filter)).map((value: string) => <SelectOption key={value} value={value.trim()}/>) - : MediaTypes.map((value: string) => <SelectOption key={value} value={value.trim()}/>); - } - - getMediaTypeSelect = (property: PropertyMeta, value: any) => { - return ( - <Select - placeholderText="Select Media Type" - variant={SelectVariant.typeahead} - aria-label={property.name} - onToggle={isExpanded => { - this.openSelect(property.name, isExpanded) - }} - onSelect={(e, value, isPlaceholder) => this.propertyChanged(property.name, (!isPlaceholder ? value : undefined))} - selections={value} - isOpen={this.isSelectOpen(property.name)} - isCreatable={false} - isInputFilterPersisted={false} - onFilter={(e, text) => this.getMediaTypeSelectOptions(text)} - aria-labelledby={property.name} - direction={SelectDirection.down} - > - {this.getMediaTypeSelectOptions()} - </Select> - ) - } - - canBeInternalUri = (property: PropertyMeta, element?: CamelElement): boolean => { - if (element?.dslName === 'WireTapDefinition' && property.name === 'uri') { - return true; - } else if (element?.dslName === 'SagaDefinition' && ['compensation', 'completion'].includes(property.name)) { - return true; - } else if (element && ['GetDefinition', 'PostDefinition', 'PutDefinition', 'PatchDefinition', 'DeleteDefinition', 'HeadDefinition'].includes(element?.dslName) && property.name === 'to') { - return true; - } else { - return false; - } - } - - canBeMediaType = (property: PropertyMeta, element?: CamelElement): boolean => { - if (element - && ['RestDefinition', 'GetDefinition', 'PostDefinition', 'PutDefinition', 'PatchDefinition', 'DeleteDefinition', 'HeadDefinition'].includes(element?.dslName) - && ['consumes', 'produces'].includes(property.name)) { - return true; - } else { - return false; - } - } - - 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"); - urls.push(...CamelUi.getInternalRouteUris(this.props.integration, "seda")); - if (urls && urls.length > 0) { - selectOptions.push(...urls.map((value: string) => - <SelectOption key={value} value={value.trim()}/>)); - } - return ( - <Select - placeholderText="Select or type an URI" - variant={SelectVariant.typeahead} - aria-label={property.name} - onClear={event => this.clearSelection(property.name)} - onToggle={isExpanded => { - this.openSelect(property.name, isExpanded) - }} - onSelect={(e, value, isPlaceholder) => { - const url = value.toString().split(":"); - const newRoute = !urls.includes(value.toString()) && (['direct', 'seda'].includes(url[0])) ? new RouteToCreate(url[0], url[1]) : undefined; - this.propertyChanged(property.name, (!isPlaceholder ? value : undefined), newRoute) - }} - selections={value} - isOpen={this.isSelectOpen(property.name)} - isCreatable={true} - isInputFilterPersisted={true} - aria-labelledby={property.name} - direction={SelectDirection.down} - > - {selectOptions} - </Select> - ) - } - - onMultiValueObjectUpdate = (index: number, fieldId: string, value: CamelElement) => { - const mValue = [...this.props.value]; - mValue[index] = value; - this.props.onChange?.call(this, fieldId, mValue); - } - - isKeyValueObject(property: PropertyMeta) { - const props = CamelDefinitionApiExt.getElementProperties(property.type); - return props.length === 2 && props.filter(p => p.name === 'key').length === 1 && props.filter(p => p.name === 'value').length === 1; - } - - getMultiObjectFieldProps(property: PropertyMeta, value: any, v: any, index: number, hideLabel: boolean = false) { - return (<> - <div className="object"> - {value && <ObjectField property={property} - hideLabel={hideLabel} - value={v} - onPropertyUpdate={(f, v) => this.onMultiValueObjectUpdate(index, f, v)} - integration={this.props.integration} - dark={this.props.dark}/>} - </div> - <Button variant="link" className="delete-button" onClick={e => { - const v = Array.from(value); - v.splice(index, 1); - this.propertyChanged(property.name, v); - }}><DeleteIcon/></Button> - </>) - } - - getMultiValueObjectField = (property: PropertyMeta, value: any) => { - const isKeyValue = this.isKeyValueObject(property); - return ( - <div> - {value && Array.from(value).map((v: any, index: number) => { - if (isKeyValue) - return <div key={property + "-" + index} className="object-key-value"> - {this.getMultiObjectFieldProps(property, value, v, index, index > 0)} - </div> - else - return <Card key={property + "-" + index} className="object-value"> - {this.getMultiObjectFieldProps(property, value, v, index)} - </Card> - })} - <Button variant="link" className="add-button" - onClick={e => this.propertyChanged(property.name, [...value, CamelDefinitionApi.createStep(property.type, {})])}><AddIcon/>{"Add " + property.displayName} - </Button> - </div> - ) - } - - getMultiValueField = (property: PropertyMeta, value: any) => { - return ( - <div> - <TextInputGroup className="input-group"> - <TextInputGroupMain value={this.state.arrayValues.get(property.name)} onChange={e => this.arrayChanged(property.name, e)} onKeyUp={e => { - if (e.key === 'Enter') this.arraySave(property.name) - }}> - <ChipGroup> - {value && Array.from(value).map((v: any, index: number) => ( - <Chip key={"chip-" + index} className="chip" onClick={() => this.arrayDeleteValue(property.name, v)}>{v.toString()}</Chip>))} - </ChipGroup> - </TextInputGroupMain> - <TextInputGroupUtilities> - <Button variant="plain" onClick={e => this.arraySave(property.name)} aria-label="Add element"> - <PlusIcon/> - </Button> - </TextInputGroupUtilities> - </TextInputGroup> - </div> - ) - } - - getKameletParameters = () => { - const element = this.props.element; - const requiredParameters = CamelUtil.getKameletRequiredParameters(element); - return ( - <div className="parameters"> - {CamelUtil.getKameletProperties(element).map(property => - <KameletPropertyField - key={property.id} - property={property} - value={CamelDefinitionApiExt.getParametersValue(element, property.id)} - required={requiredParameters?.includes(property.id)} - onParameterChange={this.props.onParameterChange} - />)} - </div> - ) - } - - getMainComponentParameters = (properties: ComponentProperty[]) => { - return ( - <div className="parameters"> - {properties.map(kp => { - // console.log(kp); - // console.log(CamelDefinitionApiExt.getParametersValue(this.props.element, kp.name, kp.kind === 'path')); - return (<ComponentParameterField - key={kp.name} - property={kp} - element={this.props.element} - integration={this.props.integration} - value={CamelDefinitionApiExt.getParametersValue(this.props.element, kp.name, kp.kind === 'path')} - onParameterChange={this.props.onParameterChange} - />) - })} - </div> - ) - } - - getExpandableComponentParameters = (properties: ComponentProperty[], label: string) => { - return ( - <ExpandableSection - toggleText={label} - onToggle={isExpanded => { - this.setState(state => { - state.isShowAdvanced.set(label, !state.isShowAdvanced.get(label)); - return {isShowAdvanced: state.isShowAdvanced}; - }) - }} - isExpanded={this.state.isShowAdvanced.has(label) && this.state.isShowAdvanced.get(label)}> - <div className="parameters"> - {properties.map(kp => - <ComponentParameterField - key={kp.name} - property={kp} - integration={this.props.integration} - value={CamelDefinitionApiExt.getParametersValue(this.props.element, kp.name, kp.kind === 'path')} - onParameterChange={this.props.onParameterChange} - /> - )} - </div> - </ExpandableSection> - ) - } - - getLabelIcon = (property: PropertyMeta) => { - return ( - property.description - ? <Popover - position={"left"} - headerContent={property.displayName} - bodyContent={property.description} - footerContent={ - <div> - {property.defaultValue !== undefined && property.defaultValue.toString().trim().length > 0 && <div>{"Default: " + property.defaultValue}</div>} - {property.required && <b>Required</b>} - </div> - }> - <button type="button" aria-label="More info" onClick={e => { - e.preventDefault(); - e.stopPropagation(); - }} className="pf-c-form__group-label-help"> - <HelpIcon noVerticalAlign/> - </button> - </Popover> - : <div></div> - ) - } - - - isMultiValueField = (property: PropertyMeta): boolean => { - return ['string'].includes(property.type) && property.name !== 'expression' && property.isArray && !property.enumVals; - } - - getComponentParameters(property: PropertyMeta) { - const properties = CamelUtil.getComponentProperties(this.props.element); - const propertiesMain = properties.filter(p => !p.label.includes("advanced") && !p.label.includes("security") && !p.label.includes("scheduler")); - const propertiesAdvanced = properties.filter(p => p.label.includes("advanced")); - const propertiesScheduler = properties.filter(p => p.label.includes("scheduler")); - const propertiesSecurity = properties.filter(p => p.label.includes("security")); - return ( - <> - {property.name === 'parameters' && this.getMainComponentParameters(propertiesMain)} - {property.name === 'parameters' && this.props.element && propertiesScheduler.length > 0 - && this.getExpandableComponentParameters(propertiesScheduler, "Scheduler parameters")} - {property.name === 'parameters' && this.props.element && propertiesSecurity.length > 0 - && this.getExpandableComponentParameters(propertiesSecurity, "Security parameters")} - {property.name === 'parameters' && this.props.element && propertiesAdvanced.length > 0 - && this.getExpandableComponentParameters(propertiesAdvanced, "Advanced parameters")} - </> - ) - } - - render() { - const isKamelet = CamelUtil.isKameletComponent(this.props.element); - const property: PropertyMeta = this.props.property; - const value = this.props.value; - return ( - <div> - <FormGroup - label={this.props.hideLabel ? undefined : this.getLabel(property, value)} - isRequired={property.required} - fieldId={property.name} - labelIcon={this.getLabelIcon(property)}> - {value && ["ExpressionDefinition", "ExpressionSubElementDefinition"].includes(property.type) - && this.getExpressionField(property, value)} - {property.isObject && !property.isArray && !["ExpressionDefinition", "ExpressionSubElementDefinition"].includes(property.type) - && this.getObjectField(property, value)} - {property.isObject && property.isArray && !this.isMultiValueField(property) - && this.getMultiValueObjectField(property, value)} - {property.name === 'expression' && property.type === "string" && !property.isArray - && this.getTextArea(property, value)} - {this.canBeInternalUri(property, this.props.element) - && 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)} - {this.isMultiValueField(property) - && this.getMultiValueField(property, value)} - {property.type === 'boolean' - && this.getSwitch(property, value)} - {property.enumVals - && this.getSelect(property, value)} - {isKamelet && property.name === 'parameters' && this.getKameletParameters()} - {!isKamelet && property.name === 'parameters' && this.getComponentParameters(property)} - </FormGroup> - {this.getKubernetesSelectorModal()} - </div> - ) - } -} diff --git a/karavan-designer/src/designer/KaravanDesigner.tsx b/karavan-designer/src/designer/KaravanDesigner.tsx index b4ac345..e7f975b 100644 --- a/karavan-designer/src/designer/KaravanDesigner.tsx +++ b/karavan-designer/src/designer/KaravanDesigner.tsx @@ -27,7 +27,6 @@ import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; import {CamelUi} from "./utils/CamelUi"; import {BeansDesigner} from "./beans/BeansDesigner"; import {RestDesigner} from "./rest/RestDesigner"; -import {RouteConfigurationDesigner} from "./configuration/RouteConfigurationDesigner"; import {getDesignerIcon} from "./utils/KaravanIcons"; interface Props { @@ -132,7 +131,6 @@ 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='routeConfiguration' title={this.getTab("Configuration", "Route Configuration", "routeConfiguration")}></Tab> </Tabs> {tab === 'routes' && <RouteDesigner integration={this.state.integration} onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)} @@ -144,9 +142,6 @@ export class KaravanDesigner extends React.Component<Props, State> { {tab === 'beans' && <BeansDesigner integration={this.state.integration} onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)} dark={this.props.dark}/>} - {tab === 'routeConfiguration' && <RouteConfigurationDesigner integration={this.state.integration} - onSave={(integration, propertyOnly) => this.save(integration, propertyOnly)} - dark={this.props.dark}/>} </PageSection> ) } diff --git a/karavan-designer/src/designer/configuration/RouteConfigurationCard.tsx b/karavan-designer/src/designer/configuration/RouteConfigurationCard.tsx deleted file mode 100644 index 7b069e3..0000000 --- a/karavan-designer/src/designer/configuration/RouteConfigurationCard.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 {RouteConfigurationDefinition} from "karavan-core/lib/model/CamelDefinition"; -import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-circle-icon"; -import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition"; - -interface Props { - routeConfiguration: RouteConfigurationDefinition - selectedStep?: CamelElement - deleteElement: (element: RouteConfigurationDefinition) => void - selectElement: (element: RouteConfigurationDefinition) => void -} - -export class RouteConfigurationCard extends React.Component<Props, any> { - - selectElement = (evt: React.MouseEvent) => { - evt.stopPropagation(); - this.props.selectElement.call(this, this.props.routeConfiguration); - } - - delete = (evt: React.MouseEvent) => { - evt.stopPropagation(); - this.props.deleteElement.call(this, this.props.routeConfiguration); - } - - render() { - const {selectedStep, routeConfiguration} = this.props; - return ( - <div className={selectedStep?.uuid === routeConfiguration.uuid ? "rest-card rest-card-selected" : "rest-card rest-card-unselected"} - onClick={e => this.selectElement(e)}> - <div className="header"> - <div className="title">Route Configuration</div> - <div className="description">Route Configuration</div> - <Button variant="link" className="delete-button" onClick={e => this.delete(e)}><DeleteIcon/></Button> - </div> - </div> - ); - } -} diff --git a/karavan-designer/src/designer/configuration/RouteConfigurationDesigner.tsx b/karavan-designer/src/designer/configuration/RouteConfigurationDesigner.tsx deleted file mode 100644 index d7fdb61..0000000 --- a/karavan-designer/src/designer/configuration/RouteConfigurationDesigner.tsx +++ /dev/null @@ -1,174 +0,0 @@ -/* - * 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 {RouteConfigurationDefinition} 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 {RouteConfigurationCard} from "./RouteConfigurationCard"; -import {DslProperties} from "../route/DslProperties"; - -interface Props { - onSave?: (integration: Integration, propertyOnly: boolean) => void - integration: Integration - dark: boolean -} - -interface State { - integration: Integration - showDeleteConfirmation: boolean - routeConfigurations: RouteConfigurationDefinition[] - selectedRouteConfiguration?: RouteConfigurationDefinition - key: string - propertyOnly: boolean -} - -export class RouteConfigurationDesigner extends React.Component<Props, State> { - - public state: State = { - integration: this.props.integration, - routeConfigurations: [], - 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 = (routeConfiguration: RouteConfigurationDefinition) => { - this.setState({selectedRouteConfiguration: routeConfiguration, showDeleteConfirmation: true}); - } - - onIntegrationUpdate = (i: Integration) => { - this.setState({integration: i, propertyOnly: false, showDeleteConfirmation: false, key: Math.random().toString()}); - } - - deleteRouteConfiguration = () => { - const {selectedRouteConfiguration} = this.state; - if (selectedRouteConfiguration) { - const i = CamelDefinitionApiExt.deleteRouteConfigurationFromIntegration(this.state.integration, selectedRouteConfiguration); - this.setState({ - integration: i, - showDeleteConfirmation: false, - key: Math.random().toString(), - selectedRouteConfiguration: undefined, - propertyOnly: false - }); - } - } - - 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.deleteRouteConfiguration()}>Delete</Button>, - <Button key="cancel" variant="link" - onClick={e => this.setState({showDeleteConfirmation: false})}>Cancel</Button> - ]} - onEscapePress={e => this.setState({showDeleteConfirmation: false})}> - <div> - Delete Route Configuration from integration? - </div> - </Modal>) - } - - createRouteConfiguration = () => { - const clone = CamelUtil.cloneIntegration(this.state.integration); - const routeConfiguration = new RouteConfigurationDefinition(); - const i = CamelDefinitionApiExt.addRouteConfigurationToIntegration(clone, routeConfiguration); - this.setState({integration: i, propertyOnly: false, key: Math.random().toString(), selectedRouteConfiguration: routeConfiguration}); - } - - selectRouteConfiguration = (element: RouteConfigurationDefinition) => { - this.setState({selectedRouteConfiguration: element}) - } - - onPropertyUpdate = (element: CamelElement) => { - const clone = CamelUtil.cloneIntegration(this.state.integration); - const i = CamelDefinitionApiExt.updateRouteConfigurationToIntegration(clone, element); - this.setState({integration: i, propertyOnly: true, key: Math.random().toString()}); - } - - getPropertiesPanel() { - return ( - <DrawerPanelContent isResizable hasNoBorder defaultSize={'400px'} maxSize={'800px'} minSize={'300px'}> - <DslProperties - integration={this.props.integration} - step={this.state.selectedRouteConfiguration} - onIntegrationUpdate={this.onIntegrationUpdate} - onPropertyUpdate={this.onPropertyUpdate} - clipboardStep={undefined} - isRouteDesigner={false} - onClone={element => {}} - dark={this.props.dark}/> - </DrawerPanelContent> - ) - } - - render() { - const routeConfigurations = CamelUi.getRouteConfigurations(this.state.integration); - return ( - <PageSection className="rest-page" isFilled padding={{default: 'noPadding'}}> - <div className="rest-page-columns"> - <Drawer isExpanded isInline> - <DrawerContent panelContent={this.getPropertiesPanel()}> - <DrawerContentBody> - <div className="graph" data-click="REST"> - <div className="flows"> - {routeConfigurations?.map(routeConfiguration => - <RouteConfigurationCard key={routeConfiguration.uuid + this.state.key} - routeConfiguration={routeConfiguration} - selectedStep={this.state.selectedRouteConfiguration} - selectElement={this.selectRouteConfiguration} - deleteElement={this.showDeleteConfirmation}/> - )} - <div className="add-rest"> - <Button - variant="primary" - data-click="ADD_REST" - icon={<PlusIcon/>} - onClick={e => this.createRouteConfiguration()}>Create new configuration - </Button> - </div> - </div> - </div> - </DrawerContentBody> - </DrawerContent> - </Drawer> - </div> - {this.getDeleteConfirmation()} - </PageSection> - ) - } -} diff --git a/karavan-designer/src/designer/karavan.css b/karavan-designer/src/designer/karavan.css index 2ae90aa..3598b1a 100644 --- a/karavan-designer/src/designer/karavan.css +++ b/karavan-designer/src/designer/karavan.css @@ -525,6 +525,7 @@ margin-top: 16px; display: flex; justify-content: center; + gap: 6px; } /*connections*/ diff --git a/karavan-designer/src/designer/route/DslElement.tsx b/karavan-designer/src/designer/route/DslElement.tsx index 31ee222..ccf15e8 100644 --- a/karavan-designer/src/designer/route/DslElement.tsx +++ b/karavan-designer/src/designer/route/DslElement.tsx @@ -131,15 +131,19 @@ export class DslElement extends React.Component<Props, State> { hasBorder = (): boolean => { return (this.props.step?.hasSteps() && !['FromDefinition'].includes(this.props.step.dslName)) - || ['RouteDefinition', 'TryDefinition', 'ChoiceDefinition', 'SwitchDefinition'].includes(this.props.step.dslName); + || ['RouteConfigurationDefinition', + 'RouteDefinition', + 'TryDefinition', + 'ChoiceDefinition', + 'SwitchDefinition'].includes(this.props.step.dslName); } isNotDraggable = (): boolean => { - return ['FromDefinition', 'RouteDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(this.props.step.dslName); + return ['FromDefinition', 'RouteConfigurationDefinition', 'RouteDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(this.props.step.dslName); } isWide = (): boolean => { - return ['RouteDefinition', 'ChoiceDefinition', 'SwitchDefinition', 'MulticastDefinition', 'TryDefinition', 'CircuitBreakerDefinition'] + return ['RouteConfigurationDefinition', 'RouteDefinition', 'ChoiceDefinition', 'SwitchDefinition', 'MulticastDefinition', 'TryDefinition', 'CircuitBreakerDefinition'] .includes(this.props.step.dslName); } @@ -153,7 +157,7 @@ export class DslElement extends React.Component<Props, State> { } isRoot = (): boolean => { - return this.props.step?.dslName?.startsWith("RouteDefinition"); + return ['RouteConfigurationDefinition', 'RouteDefinition'].includes(this.props.step?.dslName); } isInStepWithChildren = () => { @@ -226,12 +230,12 @@ export class DslElement extends React.Component<Props, State> { const step: CamelElement = this.props.step; const availableModels = CamelUi.getSelectorModelsForParent(step.dslName, false); const showAddButton = !['CatchDefinition', 'RouteDefinition'].includes(step.dslName) && availableModels.length > 0; - const showInsertButton = !['FromDefinition', 'RouteDefinition', 'CatchDefinition', 'FinallyDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(step.dslName); - const headerClass = step.dslName === 'RouteDefinition' ? "header-route" : "header" + const showInsertButton = !['FromDefinition', 'RouteConfigurationDefinition', 'RouteDefinition', 'CatchDefinition', 'FinallyDefinition', 'WhenDefinition', 'OtherwiseDefinition'].includes(step.dslName); + const headerClass = ['RouteConfigurationDefinition', 'RouteDefinition'].includes(step.dslName) ? "header-route" : "header" const headerClasses = this.isSelected() ? headerClass + " selected" : headerClass; return ( <div className={headerClasses} style={this.getHeaderStyle()}> - {this.props.step.dslName !== 'RouteDefinition' && + {!['RouteConfigurationDefinition', 'RouteDefinition'].includes(this.props.step.dslName) && <div ref={el => this.sendPosition(el, this.isSelected())} className={"header-icon"} style={this.isWide() ? {width: ""} : {}}> diff --git a/karavan-designer/src/designer/route/RouteDesigner.tsx b/karavan-designer/src/designer/route/RouteDesigner.tsx index 6c976a9..01c02f7 100644 --- a/karavan-designer/src/designer/route/RouteDesigner.tsx +++ b/karavan-designer/src/designer/route/RouteDesigner.tsx @@ -28,7 +28,7 @@ import {DslSelector} from "./DslSelector"; import {DslMetaModel} from "../utils/DslMetaModel"; import {DslProperties} from "./DslProperties"; import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; -import {FromDefinition, RouteDefinition} from "karavan-core/lib/model/CamelDefinition"; +import {FromDefinition, RouteConfigurationDefinition, RouteDefinition} from "karavan-core/lib/model/CamelDefinition"; import {CamelElement, Integration} from "karavan-core/lib/model/IntegrationDefinition"; import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt"; import {CamelDefinitionApi} from "karavan-core/lib/api/CamelDefinitionApi"; @@ -40,7 +40,6 @@ 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 @@ -149,6 +148,7 @@ export class RouteDesigner extends React.Component<Props, State> { showDeleteConfirmation = (id: string) => { let message: string; let ce: CamelElement; + let isRouteConfiguration: boolean = false; ce = CamelDefinitionApiExt.findElementInIntegration(this.state.integration, id)!; if (ce.dslName === 'FromDefinition') { // Get the RouteDefinition for this. Use its uuid. let flows = this.state.integration.spec.flows!; @@ -164,6 +164,9 @@ export class RouteDesigner extends React.Component<Props, State> { message = 'Deleting the first element will delete the entire route!'; } else if (ce.dslName === 'RouteDefinition') { message = 'Delete route?'; + } else if (ce.dslName === 'RouteConfigurationDefinition') { + message = 'Delete route configuration?'; + isRouteConfiguration = true; } else { message = 'Delete element from route?'; } @@ -241,6 +244,19 @@ export class RouteDesigner extends React.Component<Props, State> { } } + createRouteConfiguration = () => { + const clone = CamelUtil.cloneIntegration(this.state.integration); + const routeConfiguration = new RouteConfigurationDefinition(); + const i = CamelDefinitionApiExt.addRouteConfigurationToIntegration(clone, routeConfiguration); + this.setState({ + integration: i, + propertyOnly: false, + key: Math.random().toString(), + selectedStep: routeConfiguration, + selectedUuid: routeConfiguration.uuid, + }); + } + addStep = (step: CamelElement, parentId: string, position?: number | undefined) => { const i = CamelDefinitionApiExt.addStepToIntegration(this.state.integration, step, parentId, position); const clone = CamelUtil.cloneIntegration(i); @@ -356,21 +372,35 @@ export class RouteDesigner extends React.Component<Props, State> { } getGraph() { - const routes = CamelUi.getRoutes(this.state.integration); + const {selectedUuid, integration, key, width, height, top, left} = this.state; + const routes = CamelUi.getRoutes(integration); + const routeConfigurations = CamelUi.getRouteConfigurations(integration); return ( <div ref={this.state.printerRef} className="graph"> - <DslConnections height={this.state.height} width={this.state.width} top={this.state.top} - left={this.state.left} integration={this.state.integration}/> + <DslConnections height={height} width={width} top={top} left={left} integration={integration}/> <div className="flows" data-click="FLOWS" onClick={event => this.unselectElement(event)} ref={el => this.onResizePage(el)}> + {routeConfigurations?.map((routeConfiguration , index: number) => ( + <DslElement key={routeConfiguration.uuid + key} + integration={integration} + openSelector={this.openSelector} + deleteElement={this.showDeleteConfirmation} + selectElement={this.selectElement} + moveElement={this.moveElement} + selectedUuid={selectedUuid} + inSteps={false} + position={index} + step={routeConfiguration} + parent={undefined}/> + ))} {routes?.map((route: any, index: number) => ( - <DslElement key={route.uuid + this.state.key} - integration={this.state.integration} + <DslElement key={route.uuid + key} + integration={integration} openSelector={this.openSelector} deleteElement={this.showDeleteConfirmation} selectElement={this.selectElement} moveElement={this.moveElement} - selectedUuid={this.state.selectedUuid} + selectedUuid={selectedUuid} inSteps={false} position={index} step={route} @@ -379,10 +409,14 @@ export class RouteDesigner extends React.Component<Props, State> { <div className="add-flow"> <Button variant={routes.length === 0 ? "primary" : "secondary"} - data-click="ADD_ROUTE" icon={<PlusIcon/>} onClick={e => this.openSelector(undefined, undefined)}>Create new route </Button> + <Button + variant="secondary" + icon={<PlusIcon/>} + onClick={e => this.createRouteConfiguration()}>Create new configuration + </Button> </div> </div> </div>)