diff --git a/karavan-core/src/core/api/CamelDefinitionYaml.ts b/karavan-core/src/core/api/CamelDefinitionYaml.ts index 44feabdd64a..aa6f425bd87 100644 --- a/karavan-core/src/core/api/CamelDefinitionYaml.ts +++ b/karavan-core/src/core/api/CamelDefinitionYaml.ts @@ -237,25 +237,24 @@ export class CamelDefinitionYaml { return integration; }; - static yamlIsIntegration = (text: string): boolean => { + static yamlIsIntegration = (text: string): 'crd' | 'plain' | 'kamelet' | 'none' => { try { const fromYaml: any = yaml.load(text); const camelized: any = CamelUtil.camelizeObject(fromYaml); - if ( - camelized?.apiVersion && - camelized.apiVersion.startsWith('camel.apache.org') && - camelized.kind && - camelized.kind === 'Integration' - ) { - return true; + if (camelized?.apiVersion && camelized.apiVersion.startsWith('camel.apache.org') && camelized.kind) { + if (camelized.kind === 'Integration') { + return 'crd'; + } else if (camelized.kind === 'Kamelet') { + return 'kamelet'; + } } else if (Array.isArray(camelized)) { - return true; + return 'plain'; } else { - return false; + return 'none'; } } catch (e) { - return false; } + return 'none'; }; static flowsToCamelElements = (flows: any[]): any[] => { const rules: { [key: string]: (flow: any) => any } = { diff --git a/karavan-core/src/core/model/IntegrationDefinition.ts b/karavan-core/src/core/model/IntegrationDefinition.ts index c6c53d46a30..696e2da17c1 100644 --- a/karavan-core/src/core/model/IntegrationDefinition.ts +++ b/karavan-core/src/core/model/IntegrationDefinition.ts @@ -17,7 +17,7 @@ import { v4 as uuidv4 } from 'uuid'; import { RegistryBeanDefinition } from './CamelDefinition'; -export class KameletDefinitionProperty { +export class DefinitionProperty { title: string = ''; description: string = ''; type: 'string' | 'integer' | 'boolean' = 'string'; @@ -25,27 +25,26 @@ export class KameletDefinitionProperty { example?: any; format?: string; "x-descriptors"?: string[]; - properties: any = {}; - public constructor(init?: Partial) { + public constructor(init?: Partial) { Object.assign(this, init); } } -export class KameletDefinition { +export class Definition { title: string = ''; description: string = ''; required: string[] = []; type: string = 'object'; properties: any = {}; - public constructor(init?: Partial) { + public constructor(init?: Partial) { Object.assign(this, init); } } export class Spec { - definition?: KameletDefinition; + definition?: Definition; types?: any; flows?: any[] = []; template?: any; @@ -56,30 +55,31 @@ export class Spec { } } -export class MetadataLabel { - "camel.apache.org/kamelet.type": "sink" | "source" | "action" +export class MetadataLabels { + "camel.apache.org/kamelet.type": "sink" | "source" | "action" = 'source' - public constructor(init?: Partial) { + public constructor(init?: Partial) { Object.assign(this, init); } } -export class MetadataAnnotation { - "camel.apache.org/catalog.version"?: string; - "camel.apache.org/kamelet.icon"?: string; - "camel.apache.org/provider"?: string; - "camel.apache.org/kamelet.group"?: string; - "camel.apache.org/kamelet.namespace"?: string; +export class MetadataAnnotations { + "camel.apache.org/kamelet.support.level:": string = 'Preview'; + "camel.apache.org/catalog.version": string = ''; + "camel.apache.org/kamelet.icon": string = ''; + "camel.apache.org/provider": string = ''; + "camel.apache.org/kamelet.group": string = ''; + "camel.apache.org/kamelet.namespace": string = ''; - public constructor(init?: Partial) { + public constructor(init?: Partial) { Object.assign(this, init); } } export class Metadata { name: string = ''; - annotations?: MetadataAnnotation; - labels?: MetadataLabel[]; + annotations?: MetadataAnnotations; + labels?: MetadataLabels; public constructor(init?: Partial) { Object.assign(this, init); @@ -98,10 +98,18 @@ export class Integration { } static createNew(name?: string, type: 'crd' | 'plain' | 'kamelet' = 'plain'): Integration { - return new Integration({ type: type, + const i = new Integration({ type: type, metadata: new Metadata({ name: name }), kind : type === 'kamelet' ? 'Kamelet' : 'Integration', spec: new Spec({ flows: [] }) }); + + if (type === 'kamelet') { + i.metadata.annotations = new MetadataAnnotations({}) + i.spec.definition = new Definition({}) + i.spec.types = {} + } + + return i; } } diff --git a/karavan-core/test/isIntegration.spec.ts b/karavan-core/test/isIntegration.spec.ts index a14ed140638..61f3c218e21 100644 --- a/karavan-core/test/isIntegration.spec.ts +++ b/karavan-core/test/isIntegration.spec.ts @@ -27,13 +27,19 @@ describe('Is Integration', () => { it('Is not integration', () => { const yaml = fs.readFileSync('test/is-not-integration.yaml',{encoding:'utf8', flag:'r'}); const i = CamelDefinitionYaml.yamlIsIntegration(yaml); - expect(i).to.equal(false); + expect(i).to.equal('none'); }); - it('Is integration', () => { + it('Is integration CRD', () => { const yaml = fs.readFileSync('test/integration1.yaml',{encoding:'utf8', flag:'r'}); const i = CamelDefinitionYaml.yamlIsIntegration(yaml); - expect(i).to.equal(true); + expect(i).to.equal('crd'); + }); + + it('Is integration plain', () => { + const yaml = fs.readFileSync('test/plain1.yaml',{encoding:'utf8', flag:'r'}); + const i = CamelDefinitionYaml.yamlIsIntegration(yaml); + expect(i).to.equal('plain'); }); }); \ No newline at end of file diff --git a/karavan-core/test/kamelet.spec.ts b/karavan-core/test/kamelet.spec.ts index 691bf48ed1c..65e9498700b 100644 --- a/karavan-core/test/kamelet.spec.ts +++ b/karavan-core/test/kamelet.spec.ts @@ -14,29 +14,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {expect} from 'chai'; import * as fs from 'fs'; import 'mocha'; import {CamelDefinitionYaml} from "../src/core/api/CamelDefinitionYaml"; -import { - ChoiceDefinition, - ExpressionDefinition, - FilterDefinition, FromDefinition, LogDefinition, - ToDefinition, - WhenDefinition, -} from '../src/core/model/CamelDefinition'; +import { FromDefinition, LogDefinition, } from '../src/core/model/CamelDefinition'; import { RouteDefinition} from "../src/core/model/CamelDefinition"; -import { Beans, Integration, MetadataAnnotation } from '../src/core/model/IntegrationDefinition'; -import { KameletMetadata } from '../lib/model/KameletModels'; -import { RegistryBeanDefinition } from '../lib/model/CamelDefinition'; +import { Beans, Definition, Integration } from '../src/core/model/IntegrationDefinition'; +import { RegistryBeanDefinition } from '../src/core/model/CamelDefinition'; +import { MetadataAnnotations } from '../src/core/model/IntegrationDefinition'; describe('Kamelet <=> YAML', () => { it('Yaml to Kamelet', () => { const yaml = fs.readFileSync('test/postgresql-source.kamelet.yaml',{encoding:'utf8', flag:'r'}); const i = CamelDefinitionYaml.yamlToIntegration("postgresql-source.kamelet.yaml", yaml); - console.log(i) - console.log(CamelDefinitionYaml.integrationToYaml(i)) + // console.log(i) }); it('Kamelet to YAML with beans', () => { @@ -50,7 +42,7 @@ describe('Kamelet <=> YAML', () => { b.beans.push(new RegistryBeanDefinition({name: "beanDS1", type: "String.class"})); b.beans.push(new RegistryBeanDefinition({name: "beanDS2", type: "String.class"})); i.spec.flows?.push(b); - const a = new MetadataAnnotation({"camel.apache.org/kamelet.group" : "hello world"}) + const a = new MetadataAnnotations({"camel.apache.org/kamelet.group" : "hello world"}) i.metadata.annotations = a }); @@ -60,6 +52,8 @@ describe('Kamelet <=> YAML', () => { const flow1 = new FromDefinition({uri: "direct1"}); flow1.steps?.push(new LogDefinition({logName: 'log11', message: "hello11"})); i.spec.flows?.push(new RouteDefinition({from:flow1})); + + console.log(CamelDefinitionYaml.integrationToYaml(i)) }); diff --git a/karavan-core/test/topology.spec.ts b/karavan-core/test/topology.spec.ts index e76ead2bd43..db689f210e7 100644 --- a/karavan-core/test/topology.spec.ts +++ b/karavan-core/test/topology.spec.ts @@ -30,7 +30,6 @@ describe('Topology functions', () => { const tin = TopologyUtils.findTopologyIncomingNodes([i1, i2]); const trn = TopologyUtils.findTopologyRestNodes([i1, i2]); const ton = TopologyUtils.findTopologyOutgoingNodes([i1, i2]); - console.log(tin) }); }); diff --git a/karavan-designer/public/example/postgresql-source.kamelet.yaml b/karavan-designer/public/example/postgresql-source.kamelet.yaml new file mode 100644 index 00000000000..2fcd541ed32 --- /dev/null +++ b/karavan-designer/public/example/postgresql-source.kamelet.yaml @@ -0,0 +1,114 @@ +# --------------------------------------------------------------------------- +# 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. +# --------------------------------------------------------------------------- +apiVersion: camel.apache.org/v1 +kind: Kamelet +metadata: + name: postgresql-source + annotations: + camel.apache.org/kamelet.support.level: "Stable" + camel.apache.org/catalog.version: "4.1.0-SNAPSHOT" + camel.apache.org/kamelet.icon: "data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+UG9zdGdyZVNRTCBpY29uPC90aXRsZT48cGF0aCBkPSJNMTcuMTI4IDBhMTAuMTM0IDEwLjEzNCAwIDAgMC0yLjc1NS40MDNsLS4wNjMuMDJBMTAuOTIyIDEwLjkyMiAwIDAgMCAxMi42LjI1OEMxMS40MjIuMjM4IDEwLjQxLjUyNCA5LjU5NCAxIDguNzkuNzIxIDcuMTIyLjI0IDUuMzY0LjMzNiA0LjE0LjQwMyAyLjgwNC43NzUgMS44MTQgMS44Mi44MjcgMi44NjUuMzA1IDQuNDgyLjQxNSA2LjY4MmMuMDMuNjA3LjIwMyAxLjU5Ny40OSAyLjg3OXMuNjkgMi43ODMgMS4xOTMgNC4xNTJjLjUwMyAxLjM3IDEuMDU0IDIuNiAxLjkxNSAzLjQzNi40My40MTkgMS4wMjIuNzcxIDEuNzIuNzQyLjQ5LS4wMi45MzMtLjIzNSAxLjMxNS0uNTUyLjE4Ni4yNDUuMzg1LjM1Mi41NjYuNDUxLjIyOC4xMjUuNDUuMjEuNjguMjY2LjQxMy4xMDMgMS4xMi4yNDEgMS45NDguMS4yODItLjA0Ny41NzktLjEzOS44NzUtLjI3LjAxMS4zMy4wMjQuNjUzLjAzNy45OC4wNDEgMS4wMzYuMDY3IDEuOTkzLjM3OCAyLjgzMi4wNS4xMzcuMTg3Ljg0My43MjcgMS40NjYuNTQuNjI0IDEuNTk4IDEuMDEzIDIuODAzLjc1NS44NS0uMTgyIDEuOTMxLS41MSAyLjY0OS0xLjUzMi43MS0xLjAxIDEuMDMtMi40NTkgMS4wOTMtNC44MDkuMDE2LS4xMjcuMDM1LS4yMzUuMDU1LS4zMzZsLjE2OS4wMTVoLjAyYy45MDcuMDQxIDEuODkxLS4wODggMi43MTMtLjQ3LjcyOC0uMzM3IDEuMjc5LS42NzggMS42OC0xLjI4My4xLS4xNS4yMS0uMzMxLjI0LS42NDNzLS4xNDktLjgtLjQ0Ni0xLjAyNWMtLjU5NS0uNDUyLS45NjktLjI4LTEuMzctLjE5N2E2LjI3IDYuMjcgMCAwIDEtMS4yMDIuMTQ2YzEuMTU2LTEuOTQ3IDEuOTg1LTQuMDE1IDIuNDU4LTUuODQ1LjI4LTEuMDguNDM3LTIuMDc2LjQ1LTIuOTQ3LjAxMy0uODcxLS4wNTgtMS42NDItLjU4LTIuMzA5QzIxLjM2LjYgMTkuMDY3LjAyNCAxNy4yOTMuMDA0Yy0uMDU1LS4wMDEtLjExLS4wMDItLjE2NS0uMDAxem0tLjA0Ny42NGMxLjY3OC0uMDE2IDMuODIyLjQ1NSA1LjM2MSAyLjQyMi4zNDYuNDQyLjQ0OSAxLjA4OC40MzcgMS44ODQtLjAxMy43OTUtLjE2IDEuNzQ3LS40MjkgMi43OS0uNTIyIDIuMDItMS41MDggNC4zNzUtMi44OTcgNi40ODhhLjc1Ni43NTYgMCAwIDAgLjE1OC4wODZjLjI5LjEyLjk1MS4yMjMgMi4yNy0uMDQ4LjMzMi0uMDcuNTc1LS4xMTcuODI3LjA3NWEuNTIuNTIgMCAwIDEgLjE4My40MjUuNzA0LjcwNCAwIDAgMS0uMTMuMzM2Yy0uMjU1LjM4My0uNzU4Ljc0Ni0xLjQwMyAxLjA0NS0uNTcxLjI2Ni0xLjM5LjQwNS0yLjExNi40MTMtLjM2NC4wMDQtLjctLjAyNC0uOTg1LS4xMTNsLS4wMTgtLjAwN2MtLjExIDEuMDYtLjM2MyAzLjE1My0uNTI4IDQuMTA4LS4xMzIuNzctLjM2MyAxLjM4Mi0uODA0IDEuODQtLjQ0LjQ1OC0xLjA2My43MzQtMS45MDEuOTE0LTEuMDM4LjIyMy0xLjc5NS0uMDE3LTIuMjgzLS40MjgtLjQ4Ny0uNDEtLjcxLS45NTQtLjg0NC0xLjI4Ny0uMDkyLS4yMy0uMTQtLjUyOC0uMTg2LS45MjYtLjA0Ni0uMzk4LS4wOC0uODg1LS4xMDMtMS40MzRhNTEuNDI2IDUxLjQyNiAwIDAgMS0uMDMtMi41MjMgMy4wNjEgMy4wNjEgMCAwIDEtMS41NTIuNzZjLS42ODkuMTE3LTEuMzA0LjAwMi0xLjY3MS0uMDlhMi4yNzYgMi4yNzYgMCAwIDEtLjUyLS4yMDFjLS4xNy0uMDkxLS4zMzItLjE5NC0uNDQtLjM5N2EuNTYuNTYgMCAwIDEtLjA1Ny0uMzgxLjYxLjYxIDAgMCAxIC4yMTgtLjMzMWMuMTk4LS4xNjEuNDYtLjI1MS44NTUtLjMzMy43MTktLjE0OC45Ny0uMjQ5IDEuMTIzLS4zNy4xMy0uMTA0LjI3Ny0uMzE0LjUzNy0uNjIyYTEuMTYgMS4xNiAwIDAgMS0uMDAzLS4wNDEgMi45NiAyLjk2IDAgMCAxLTEuMzMtLjM1OGMtLjE1LjE1OC0uOTE2Ljk2OC0xLjg1IDIuMDkyLS4zOTMuNDctLjgyNy43NC0xLjI4NS43NTktLjQ1OC4wMi0uODcyLS4yMTEtMS4yMjQtLjU1Mi0uNzAzLS42ODMtMS4yNjQtMS44NTgtMS43NTMtMy4xODYtLjQ4OC0xLjMyOC0uODg1LTIuODA3LTEuMTY3LTQuMDY3LS4yODMtMS4yNi0uNDUtMi4yNzYtLjQ3NC0yLjc2Ni0uMTA1LTIuMDgyLjM4Mi0zLjQ4NSAxLjIxNy00LjM3LjgzNi0uODg1IDEuOTgyLTEuMjIgMy4wOTktMS4yODQgMi4wMDUtLjExNSAzLjkwOS41ODQgNC4yOTQuNzM0Ljc0Mi0uNTA0IDEuNjk4LS44MTggMi44OTItLjc5OGE3LjM5IDcuMzkgMCAwIDEgMS42ODEuMjE4bC4wMi0uMDA5YTYuODU0IDYuODU0IDAgMCAxIC43MzktLjIxNEE5LjYyNiA5LjYyNiAwIDAgMSAxNy4wOC42NDJ6bS4xNTIuNjdoLS4xNDZhOC43NCA4Ljc0IDAgMCAwLTEuNzA0LjE5MmMxLjI0Ni41NTIgMi4xODcgMS40MDIgMi44NSAyLjI1YTguNDQgOC40NCAwIDAgMSAxLjEzMiAxLjkyYy4xMS4yNjQuMTg0LjQ4Ny4yMjYuNjYuMDIxLjA4Ny4wMzUuMTYuMDQuMjM2LjAwMi4wMzguMDA0LjA3Ny0uMDEyLjE0NCAwIC4wMDMtLjAwNS4wMS0uMDA2LjAxMy4wMy44NzYtLjE4NyAxLjQ3LS4yMTMgMi4zMDYtLjAyLjYwNi4xMzUgMS4zMTguMTczIDIuMDk1LjAzNi43My0uMDUyIDEuNTMyLS41MjYgMi4zMTkuMDQuMDQ4LjA3Ni4wOTYuMTE0LjE0NCAxLjI1NC0xLjk3NSAyLjE1OC00LjE2IDIuNjQtNi4wMjMuMjU4LTEuMDAzLjM5NS0xLjkxMi40MDctMi42MzIuMDEtLjcyLS4xMjQtMS4yNDItLjI5NS0xLjQ2LTEuMzQyLTEuNzE2LTMuMTU4LTIuMTUzLTQuNjgtMi4xNjV6bS00Ljc5LjI1NmMtMS4xODIuMDAzLTIuMDMuMzYtMi42NzMuODk1LS42NjMuNTUzLTEuMTA4IDEuMzEtMS40IDIuMDg1LS4zNDcuOTItLjQ2NiAxLjgxLS41MTMgMi40MTRsLjAxMy0uMDA4Yy4zNTctLjIuODI2LS40IDEuMzI4LS41MTYuNTAyLS4xMTUgMS4wNDMtLjE1MSAxLjUzMy4wMzlzLjg5NS42MzcgMS4wNDIgMS4zMTVjLjcwNCAzLjI1Ny0uMjE5IDQuNDY4LS41NTkgNS4zODJhOS42MSA5LjYxIDAgMCAwLS4zMzEgMS4wMTNjLjA0My0uMDEuMDg2LS4wMjIuMTI5LS4wMjYuMjQtLjAyLjQyOC4wNi41NC4xMDguMzQyLjE0Mi41NzcuNDQuNzA0Ljc4LjAzMy4wODkuMDU3LjE4NS4wNzEuMjg0YS4zMzYuMzM2IDAgMCAxIC4wMi4xMjcgNTUuMTQgNTUuMTQgMCAwIDAgLjAxMyAzLjczOGMuMDIzLjUzOC4wNTcgMS4wMTIuMSAxLjM4Ni4wNDMuMzczLjEwNC42NTcuMTQzLjc1My4xMjguMzIuMzE1LjczOS42NTMgMS4wMjQuMzM4LjI4NC44MjMuNDc0IDEuNzA5LjI4NC43NjgtLjE2NSAxLjI0Mi0uMzk0IDEuNTU5LS43MjMuMzE2LS4zMjkuNTA1LS43ODcuNjI2LTEuNDg4LjE4MS0xLjA1LjU0NS00LjA5NS41ODktNC42NjgtLjAyLS40MzIuMDQ0LS43NjQuMTgyLTEuMDE3LjE0Mi0uMjYuMzYyLS40MTkuNTUyLS41MDUuMDk1LS4wNDMuMTg0LS4wNzIuMjU3LS4wOTNhNS45NTYgNS45NTYgMCAwIDAtLjI0My0uMzI1IDQuNDU2IDQuNDU2IDAgMCAxLS42NjYtMS4wOTkgOC4yOTYgOC4yOTYgMCAwIDAtLjI1Ny0uNDgzYy0uMTMzLS4yNC0uMzAxLS41NC0uNDc3LS44NzctLjM1Mi0uNjc1LS43MzUtMS40OTMtLjkzNC0yLjI5LS4xOTgtLjc5Ni0uMjI3LTEuNjIuMjgxLTIuMjAxLjQ1LS41MTYgMS4yNC0uNzMgMi40MjYtLjYxLS4wMzUtLjEwNS0uMDU2LS4xOTItLjExNS0uMzMyYTcuODE3IDcuODE3IDAgMCAwLTEuMDQxLTEuNzY0Yy0xLjAwNS0xLjI4NS0yLjYzMi0yLjU1OS01LjE0Ni0yLjZoLS4xMTV6bS02LjY0Mi4wNTJjLS4xMjcgMC0uMjU0LjAwNC0uMzguMDExLTEuMDEuMDU4LTEuOTY1LjM1MS0yLjY0OCAxLjA3NS0uNjg0LjcyNC0xLjEzNCAxLjkxMS0xLjAzNiAzLjg3Ni4wMTkuMzcyLjE4MSAxLjQxNC40NTkgMi42NTIuMjc3IDEuMjM4LjY3IDIuNjk1IDEuMTQyIDMuOTgyLjQ3MyAxLjI4NyAxLjA0NiAyLjQwNyAxLjU5IDIuOTM3LjI3NC4yNjUuNTEyLjM3Mi43MjguMzYzLjIxNy0uMDEuNDc4LS4xMzUuNzk3LS41MThhNDMuMjQ0IDQzLjI0NCAwIDAgMSAxLjgxLTIuMDQ4IDMuNDk3IDMuNDk3IDAgMCAxLTEuMTY3LTMuMTVjLjEwMy0uNzM5LjExNy0xLjQzLjEwNS0xLjk3Ni0uMDEyLS41MzItLjA1LS44ODYtLjA1LTEuMTA3YS4zMzYuMzM2IDAgMCAxIDAtLjAxOXYtLjAwNWwtLjAwMS0uMDA2di0uMDAxYTkuODkzIDkuODkzIDAgMCAxIC41OTItMy4zNzZjLjI4LS43NDQuNjk3LTEuNSAxLjMyMi0yLjExMi0uNjE0LS4yMDItMS43MDQtLjUxLTIuODg0LS41NjhhNy42MDMgNy42MDMgMCAwIDAtLjM4LS4wMXpNMTguMTk5IDYuOWMtLjY3OS4wMDktMS4wNi4xODQtMS4yNi40MTMtLjI4My4zMjUtLjMxLjg5NS0uMTM0IDEuNTk3LjE3NS43MDMuNTM3IDEuNDg5Ljg3NyAyLjE0Mi4xNy4zMjcuMzM1LjYyMS40NjguODYuMTM0LjI0LjIzMi40MS4yOTIuNTU1LjA1NS4xMzQuMTE2LjI1Mi4xNzguMzYyLjI2My0uNTU1LjMxLTEuMS4yODMtMS42NjgtLjAzNS0uNzAzLS4xOTgtMS40MjItLjE3NC0yLjE1LjAyNy0uODUxLjE5NS0xLjQwNS4yMS0yLjA2M2E1Ljc5MyA1Ljc5MyAwIDAgMC0uNzQtLjA0OHptLTguMjM0LjExNWEyLjgyIDIuODIgMCAwIDAtLjYxNi4wNzQgNC42NjUgNC42NjUgMCAwIDAtMS4xNTMuNDQ5IDIuNDE3IDIuNDE3IDAgMCAwLS4zNDkuMjI4bC0uMDIyLjAyYy4wMDYuMTQ2LjAzNS41LjA0NyAxLjAyMS4wMTIuNTctLjAwMiAxLjI5Ny0uMTEyIDIuMDg0LS4yMzkgMS43MSAxLjAwMiAzLjEyNiAyLjQ2IDMuMTI4LjA4NS0uMzUxLjIyNS0uNzA3LjM2NS0xLjA4Mi40MDYtMS4wOTQgMS4yMDUtMS44OTIuNTMyLTUuMDA2LS4xMS0uNTEtLjMyOC0uNzE2LS42MjgtLjgzMmExLjQ3NCAxLjQ3NCAwIDAgMC0uNTI0LS4wODR6bTcuOTE3LjIwNGguMDVjLjA2Ni4wMDIuMTI3LjAwOS4xOC4wMjIuMDU0LjAxMi4xLjAzLjEzOC4wNTVhLjE2NC4xNjQgMCAwIDEgLjA3NS4xMWwtLjAwMS4wMDhoLjAwMS0uMDAxYS4yNC4yNCAwIDAgMS0uMDM1LjEzNS42NjguNjY4IDAgMCAxLS4xMS4xNS42NzcuNjc3IDAgMCAxLS4zODYuMjEyLjU5LjU5IDAgMCAxLS40MS0uMTAzLjYwOC42MDggMCAwIDEtLjEzLS4xMTguMjYuMjYgMCAwIDEtLjA2My0uMTI3LjE3LjE3IDAgMCAxIC4wNDItLjEyOC4zODQuMzg0IDAgMCAxIC4xMTctLjA5Yy4wOTYtLjA1NC4yMjYtLjA5NC4zNzMtLjExNi4wNTUtLjAwOC4xMDktLjAxMi4xNi0uMDEzem0tNy44Mi4xNjhjLjA1MyAwIC4xMDkuMDA1LjE2Ni4wMTMuMTUzLjAyMS4yODkuMDYyLjM5My4xMjJhLjQ0Ni40NDYgMCAwIDEgLjEzMy4xMDYuMjIzLjIyMyAwIDAgMSAuMDU0LjE3LjMwMi4zMDIgMCAwIDEtLjA3NS4xNTQuNjQ5LjY0OSAwIDAgMS0uMTQzLjEzLjY0LjY0IDAgMCAxLS40NDguMTEzLjcyOC43MjggMCAwIDEtLjQyLS4yMjguNzEuNzEgMCAwIDEtLjExOC0uMTY0LjI4LjI4IDAgMCAxLS4wNDEtLjE3N2MuMDE1LS4xMDguMTA0LS4xNjQuMTkxLS4xOTVhLjg2Ni44NjYgMCAwIDEgLjMwNy0uMDR6bTkuMDYgNy4zNDNsLS4wMDMuMDAxYy0uMTQ3LjA1My0uMjY4LjA3NS0uMzcuMTJhLjQ1Mi40NTIgMCAwIDAtLjIzOS4yMTRjLS4wNjMuMTE1LS4xMTcuMzE5LS4xMDEuNjY2YS41MS41MSAwIDAgMCAuMTQ4LjA3Yy4xNzEuMDUyLjQ1OC4wODYuNzc4LjA4MS42MzgtLjAwNyAxLjQyMy0uMTU2IDEuODQtLjM1YTMuOTUgMy45NSAwIDAgMCAuOTQzLS42MTVoLS4wMDFjLTEuMzkzLjI4OC0yLjE4LjIxMS0yLjY2My4wMTJhMS4zMTUgMS4zMTUgMCAwIDEtLjMzMi0uMnptLTguMDMxLjA5NGgtLjAyMWMtLjA1My4wMDUtLjEzLjAyMy0uMjc5LjE4OC0uMzQ4LjM5LS40Ny42MzUtLjc1Ny44NjQtLjI4Ny4yMjgtLjY2LjM1LTEuNDA1LjUwMy0uMjM2LjA0OC0uMzcxLjEwMS0uNDYxLjE0NC4wMjkuMDI0LjAyNi4wMy4wNy4wNTMuMTA5LjA2LjI0OS4xMTMuMzYyLjE0Mi4zMi4wOC44NDYuMTczIDEuMzk1LjA4LjU0OS0uMDk0IDEuMTItLjM1NyAxLjYwNy0xLjA0LjA4NC0uMTE4LjA5My0uMjkyLjAyNC0uNDc5LS4wNy0uMTg3LS4yMjMtLjM0OC0uMzMxLS4zOTNhLjY1My42NTMgMCAwIDAtLjIwNC0uMDZ6Ii8+PC9zdmc+" + camel.apache.org/provider: "Apache Software Foundation" + camel.apache.org/kamelet.group: "SQL" + camel.apache.org/kamelet.namespace: "Database" + labels: + camel.apache.org/kamelet.type: "source" +spec: + definition: + title: "PostgreSQL Source" + description: |- + Query data from a PostgreSQL Database. + required: + - serverName + - username + - password + - query + - databaseName + type: object + properties: + serverName: + title: Server Name + description: The server name for the data source. + type: string + example: localhost + serverPort: + title: Server Port + description: The server port for the data source. + type: string + default: 5432 + username: + title: Username + description: The username to access a secured PostgreSQL Database. + type: string + x-descriptors: + - urn:camel:group:credentials + password: + title: Password + description: The password to access a secured PostgreSQL Database. + type: string + format: password + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:password + - urn:camel:group:credentials + query: + title: Query + description: The query to execute against the PostgreSQL Database. + type: string + example: 'INSERT INTO accounts (username,city) VALUES (:#username,:#city)' + databaseName: + title: Database Name + description: The name of the PostgreSQL Database. + type: string + consumedQuery: + title: Consumed Query + description: A query to run on a tuple consumed. + type: string + example: 'DELETE FROM accounts where user_id = :#user_id' + delay: + title: Delay + description: The number of milliseconds before the next poll + type: integer + default: 500 + types: + out: + mediaType: application/json + dependencies: + - "camel:jackson" + - "camel:kamelet" + - "camel:sql" + - "mvn:org.postgresql:postgresql:42.6.0" + - "mvn:org.apache.commons:commons-dbcp2:2.10.0" + template: + beans: + - name: dsBean + type: "#class:org.apache.commons.dbcp2.BasicDataSource" + properties: + username: '{{username}}' + password: '{{password}}' + url: 'jdbc:postgresql://{{serverName}}:{{serverPort}}/{{databaseName}}' + driverClassName: 'org.postgresql.Driver' + from: + uri: "sql:{{query}}" + parameters: + dataSource: "#bean:{{dsBean}}" + onConsume: "{{?consumedQuery}}" + delay: "{{delay}}" + steps: + - marshal: + json: + library: Jackson + - to: "kamelet:sink" \ No newline at end of file diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx index b71267ba954..dcea7fc114d 100644 --- a/karavan-designer/src/App.tsx +++ b/karavan-designer/src/App.tsx @@ -69,7 +69,8 @@ class App extends React.Component { fetch("components/components.json"), fetch("snippets/org.apache.camel.AggregationStrategy"), fetch("snippets/org.apache.camel.Processor"), - fetch("example/demo.camel.yaml") + // fetch("example/demo.camel.yaml") + fetch("example/postgresql-source.kamelet.yaml") // fetch("components/supported-components.json"), ]).then(responses => Promise.all(responses.map(response => response.text())) @@ -88,7 +89,8 @@ class App extends React.Component { TemplateApi.saveTemplate("org.apache.camel.Processor", data[3]); if (data[4]) { - this.setState({yaml: data[4], name: "demo.camel.yaml"}) + // this.setState({yaml: data[4], name: "demo.camel.yaml"}) + this.setState({yaml: data[4], name: "postgresql-source.kamelet.yaml"}) } if (data[5]) { diff --git a/karavan-designer/src/designer/KaravanDesigner.tsx b/karavan-designer/src/designer/KaravanDesigner.tsx index cb58a377fbb..316a71be417 100644 --- a/karavan-designer/src/designer/KaravanDesigner.tsx +++ b/karavan-designer/src/designer/KaravanDesigner.tsx @@ -42,6 +42,7 @@ import {RestDesigner} from "./rest/RestDesigner"; import {BeansDesigner} from "./beans/BeansDesigner"; import {CodeEditor} from "./editor/CodeEditor"; import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon'; +import {KameletDesigner} from "./kamelet/KameletDesigner"; interface Props { onSave: (filename: string, yaml: string, propertyOnly: boolean) => void @@ -71,7 +72,9 @@ export function KaravanDesigner(props: Props) { InfrastructureAPI.setOnSave(props.onSave); setSelectedStep(undefined); - setIntegration(makeIntegration(props.yaml, props.filename), false); + const i = makeIntegration(props.yaml, props.filename); + setIntegration(i, false); + setTab(i.kind === 'Kamelet' ? 'kamelet' : 'routes') reset(); setDark(props.dark); setHideLogDSL(props.hideLogDSL === true); @@ -84,8 +87,10 @@ export function KaravanDesigner(props: Props) { function makeIntegration(yaml: string, filename: string): Integration { try { - if (yaml && CamelDefinitionYaml.yamlIsIntegration(yaml)) { - return CamelDefinitionYaml.yamlToIntegration(props.filename, props.yaml) + const type = CamelDefinitionYaml.yamlIsIntegration(yaml); + if (yaml && type !== 'none') { + const i = CamelDefinitionYaml.yamlToIntegration(props.filename, props.yaml) + return i; } else { return Integration.createNew(filename, 'plain'); } @@ -126,6 +131,8 @@ export function KaravanDesigner(props: Props) { ) } + const isKamelet = integration.type === 'kamelet'; + return ( @@ -137,8 +144,9 @@ export function KaravanDesigner(props: Props) { setSelectedStep(undefined); }} style={{width: "100%"}}> + {isKamelet && } - + {!isKamelet && } {props.showCodeTab && } @@ -156,6 +164,7 @@ export function KaravanDesigner(props: Props) { {/* />*/} {/*}*/} + {tab === 'kamelet' && } {tab === 'routes' && } {tab === 'rest' && } {tab === 'beans' && } diff --git a/karavan-designer/src/designer/beans/BeanProperties.tsx b/karavan-designer/src/designer/beans/BeanProperties.tsx index 15b8f086a44..c27ea28c351 100644 --- a/karavan-designer/src/designer/beans/BeanProperties.tsx +++ b/karavan-designer/src/designer/beans/BeanProperties.tsx @@ -194,7 +194,8 @@ export function BeanProperties (props: Props) { const icon = InfrastructureAPI.infrastructure === 'kubernetes' ? : return (
- { propertyChanged(i, beanFieldName, value, showPassword) }}/> @@ -211,8 +212,8 @@ export function BeanProperties (props: Props) { type={isSecret && !showPassword ? "password" : "text"} className="text-field" isRequired - id="value" - name="value" + id={"value-" + i} + name={"value-" + i} value={value} onChange={(_, value) => { propertyChanged(i, key, value, showPassword) diff --git a/karavan-designer/src/designer/kamelet/KameletAnnotationsPanel.tsx b/karavan-designer/src/designer/kamelet/KameletAnnotationsPanel.tsx new file mode 100644 index 00000000000..3c997173fc9 --- /dev/null +++ b/karavan-designer/src/designer/kamelet/KameletAnnotationsPanel.tsx @@ -0,0 +1,133 @@ +/* + * 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 { + capitalize, + Card, + CardBody, + CardTitle, + Form, + FormGroup, Grid, GridItem, + InputGroup, + InputGroupItem, + InputGroupText, + TextInput, ToggleGroup, ToggleGroupItem, +} from '@patternfly/react-core'; +import '../karavan.css'; +import './kamelet.css'; +import {useIntegrationStore} from "../KaravanStore"; +import {shallow} from "zustand/shallow"; + +const PREFIX = 'camel.apache.org/'; + +export function KameletAnnotationsPanel() { + + const [integration, setIntegration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow) + + function setValue(key: string, value: string) { + if (key && value && value.length > 0) { + (integration.metadata.annotations as any)[PREFIX + key] = value; + setIntegration(integration, true); + } + } + + function getValue(key: string): string { + const annotations = integration.metadata.annotations; + if (annotations) { + return (annotations as any)[PREFIX + key]; + } else { + return ''; + } + } + + function getElement(key: string, label: string, span: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12) { + return ( + + + + + setValue(key, value)} + value={getValue(key)}/> + + + + + ) + } + + function getElementToggleGroup(key: string, label: string, values: string[], span: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12) { + return ( + + + {/* eslint-disable-next-line react/jsx-no-undef */} + + {values.map(value => + setValue(key, value) } + /> + )} + + + + ) + } + + function getElementIcon(key: string, label: string, span: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12) { + return ( + + + + + + + + + + setValue(key, value)} + value={getValue(key)}/> + + + + + ) + } + + return ( + + Annotations + +
+ + {getElementToggleGroup('kamelet.support.level', 'Support Level', ['Preview', 'Stable'], 2)} + {getElementIcon('kamelet.icon', 'Icon', 10)} + {getElement('catalog.version', 'Version', 3)} + {getElement('provider', 'Provider', 3)} + {getElement('kamelet.group', 'Group', 3)} + {getElement('kamelet.namespace', 'Namespace', 3)} + +
+
+
+ ) +} diff --git a/karavan-designer/src/designer/kamelet/KameletDefinitionPropertyCard.tsx b/karavan-designer/src/designer/kamelet/KameletDefinitionPropertyCard.tsx new file mode 100644 index 00000000000..a478c101264 --- /dev/null +++ b/karavan-designer/src/designer/kamelet/KameletDefinitionPropertyCard.tsx @@ -0,0 +1,221 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, {useState} from 'react'; +import { + Button, + Card, + CardBody, + CardTitle, Flex, FlexItem, + FormGroup, FormSelect, FormSelectOption, + Grid, + GridItem, Label, Modal, Switch, + TextInput, +} from '@patternfly/react-core'; +import '../karavan.css'; +import './kamelet.css'; +import {useIntegrationStore} from "../KaravanStore"; +import {shallow} from "zustand/shallow"; +import {DefinitionProperty} from "karavan-core/lib/model/IntegrationDefinition"; + +interface Props { + index: number + propKey: string + property: DefinitionProperty +} + +export function KameletDefinitionPropertyCard(props: Props) { + + const [integration, setIntegration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow) + const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false); + + const key = props.propKey; + const required = integration.spec.definition?.required || []; + + function setPropertyValue(field: string, value: string) { + if (integration.spec.definition?.properties) { + (integration.spec.definition?.properties as any)[key][field] = value; + setIntegration(integration, true); + } + } + + function getPropertyValue(field: string) { + const properties: any = integration.spec.definition?.properties; + if (properties) { + return properties[key][field]; + } + return undefined; + } + + + function getPropertyField(field: string, label: string, isRequired: boolean, span: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12) { + return ( + + + setPropertyValue(field, value)} + value={getPropertyValue(field)}/> + + + ) + } + + function getPropertyTypeField(field: string, label: string, isRequired: boolean, span: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12) { + return ( + + + setPropertyValue(field, value)} + aria-label="FormSelect Input" + ouiaId="BasicFormSelect" + > + {['string', 'number', 'boolean'].map((option, index) => ( + + ))} + + + + ) + } + + function renameProperty(newKey: string) { + const oldKey = key; + newKey = newKey.replace(/[\W_]+/g,''); + if (oldKey !== newKey) { + if (integration.spec.definition?.properties) { + const o = (integration.spec.definition?.properties as any) + const newObject: any = {}; + Object.keys(o).forEach(k => { + if (k !== oldKey) { + newObject[k] = o[k]; + } else { + newObject[newKey] = o[k]; + } + }) + integration.spec.definition.properties = newObject; + setIntegration(integration, true); + } + } + } + + function deleteProperty() { + if (integration.spec.definition?.properties) { + delete integration.spec.definition.properties[key]; + setIntegration(integration, true); + } + } + + function getDeleteConfirmation() { + return ( setShowDeleteConfirmation(false)} + actions={[ + , + + ]} + onEscapePress={e => setShowDeleteConfirmation(false)}> +
+ Delete {key} property? +
+
) + } + + function setRequired(checked: boolean) { + console.log(required, key) + const newRequired = [...required]; + if (checked && !newRequired.includes(key)) { + newRequired.push(key); + } else if (!checked && newRequired.includes(key)) { + const index = newRequired.findIndex(r => r === key); + newRequired.splice(index, 1); + } + // console.log(newRequired) + if (integration.spec.definition?.required) { + integration.spec.definition.required.length = 0; + integration.spec.definition.required.push(...newRequired) + } + setIntegration(integration, true); + } + + function getTitle() { + return ( + + + + + + setRequired(checked)} + isReversed + /> + + + ) + } + + + return ( + + + {getTitle()} + + + + {getPropertyField("title", "Title", true, 3)} + {getPropertyField("description", "Description", true, 6)} + {getPropertyTypeField("type", "Type", true, 3)} + {getPropertyField("format", "Format", false, 3)} + {getPropertyField("example", "Example", false, 6)} + {getPropertyField("default", "Default", false, 3)} + {/*{getPropertyField("x-descriptors", "Descriptors", false, 12)}*/} + + + {getDeleteConfirmation()} + + ) +} diff --git a/karavan-designer/src/designer/kamelet/KameletDefinitionsPanel.tsx b/karavan-designer/src/designer/kamelet/KameletDefinitionsPanel.tsx new file mode 100644 index 00000000000..4c638ee3f26 --- /dev/null +++ b/karavan-designer/src/designer/kamelet/KameletDefinitionsPanel.tsx @@ -0,0 +1,108 @@ +/* + * 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, + Card, + CardBody, + CardTitle, Flex, FlexItem, + Form, + FormGroup, + Grid, + GridItem, + TextInput, +} from '@patternfly/react-core'; +import '../karavan.css'; +import './kamelet.css'; +import {useIntegrationStore} from "../KaravanStore"; +import {shallow} from "zustand/shallow"; +import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon"; +import {KameletDefinitionPropertyCard} from "./KameletDefinitionPropertyCard"; + +export function KameletDefinitionsPanel() { + + const [integration, setIntegration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow) + + function setValue(key: string, value: string) { + if (key && value && value.length > 0) { + (integration.spec.definition as any)[key] = value; + setIntegration(integration, true); + } + } + + function getValue(key: string): string { + const annotations = integration.spec.definition; + if (annotations) { + return (annotations as any)[key]; + } else { + return ''; + } + } + + function getElement(key: string, label: string, span: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12) { + return ( + + + setValue(key, value)} + value={getValue(key)}/> + + + ) + } + + const properties = integration.spec.definition?.properties ? Object.keys(integration.spec.definition?.properties) : []; + return ( + <> + + Definitions + +
+ + {getElement('title', 'Title', 4)} + {getElement('description', 'Description', 6)} + {getElement('type', 'Type', 2)} + +
+
+
+
+ + + + Properties + + + + + + +
+ {properties.map((key: string, index: number) => { + const property = (integration.spec.definition?.properties as any)[key]; + return + })} + +
+
+ + + ) +} diff --git a/karavan-designer/src/designer/kamelet/KameletDesigner.tsx b/karavan-designer/src/designer/kamelet/KameletDesigner.tsx new file mode 100644 index 00000000000..3c0b5fa89f7 --- /dev/null +++ b/karavan-designer/src/designer/kamelet/KameletDesigner.tsx @@ -0,0 +1,132 @@ +/* + * 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, Card, CardBody, CardFooter, CardTitle, Divider, + Drawer, + DrawerContent, + DrawerContentBody, + DrawerPanelContent, Flex, FlexItem, Gallery, GalleryItem, + Modal, + PageSection, +} from '@patternfly/react-core'; +import '../karavan.css'; +import './kamelet.css'; +import {RegistryBeanDefinition} from "karavan-core/lib/model/CamelDefinition"; +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 {useDesignerStore, useIntegrationStore} from "../KaravanStore"; +import {shallow} from "zustand/shallow"; +import {BeanProperties} from "../beans/BeanProperties"; +import {BeanCard} from "../beans/BeanCard"; +import {KameletAnnotationsPanel} from "./KameletAnnotationsPanel"; +import {KameletDefinitionsPanel} from "./KameletDefinitionsPanel"; +import {KameletProperties} from "./KameletProperties"; + +export function KameletDesigner() { + + const [integration, setIntegration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow) + const [dark, selectedStep, showDeleteConfirmation, setShowDeleteConfirmation, setSelectedStep] = useDesignerStore((s) => + [s.dark, s.selectedStep, s.showDeleteConfirmation, s.setShowDeleteConfirmation, s.setSelectedStep], shallow) + + + function onShowDeleteConfirmation(bean: RegistryBeanDefinition) { + setSelectedStep(bean); + setShowDeleteConfirmation(true); + } + + function deleteBean() { + const i = CamelDefinitionApiExt.deleteBeanFromIntegration(integration, selectedStep); + setIntegration(i, false); + setShowDeleteConfirmation(false); + setSelectedStep(undefined); + } + + function changeBean(bean: RegistryBeanDefinition) { + const clone = CamelUtil.cloneIntegration(integration); + const i = CamelDefinitionApiExt.addBeanToIntegration(clone, bean); + setIntegration(i, false); + setSelectedStep(bean); + } + + function getDeleteConfirmation() { + return ( setShowDeleteConfirmation(false)} + actions={[ + , + + ]} + onEscapePress={e => setShowDeleteConfirmation(false)}> +
+ Delete bean from integration? +
+
) + } + + function selectBean(bean?: RegistryBeanDefinition) { + setSelectedStep(bean); + } + + function unselectBean(evt: React.MouseEvent) { + if ((evt.target as any).dataset.click === 'BEANS') { + evt.stopPropagation() + setSelectedStep(undefined); + } + }; + + function createBean() { + changeBean(new RegistryBeanDefinition()); + } + + function getPropertiesPanel() { + return ( + + + + ) + } + + return ( + + + + + + +
+ + + + + + {getDeleteConfirmation()} + + ) +} diff --git a/karavan-designer/src/designer/kamelet/KameletProperties.tsx b/karavan-designer/src/designer/kamelet/KameletProperties.tsx new file mode 100644 index 00000000000..93c696fa831 --- /dev/null +++ b/karavan-designer/src/designer/kamelet/KameletProperties.tsx @@ -0,0 +1,249 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, {useEffect, useState} from 'react'; +import { + Form, + FormGroup, + TextInput, Button, Title, Tooltip, Popover, InputGroup, InputGroupItem, +} from '@patternfly/react-core'; +import '../karavan.css'; +import "@patternfly/patternfly/patternfly.css"; +import { + RegistryBeanDefinition, +} from "karavan-core/lib/model/CamelDefinition"; +import {Integration} from "karavan-core/lib/model/IntegrationDefinition"; +import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; +import {SensitiveKeys} from "karavan-core/lib/model/CamelMetadata"; +import {v4 as uuidv4} from "uuid"; +import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon"; +import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon"; +import CloneIcon from '@patternfly/react-icons/dist/esm/icons/clone-icon' +import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; +import {InfrastructureSelector} from "../route/property/InfrastructureSelector"; +import KubernetesIcon from "@patternfly/react-icons/dist/js/icons/openshift-icon"; +import {InfrastructureAPI} from "../utils/InfrastructureAPI"; +import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon"; +import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon"; +import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon"; +import {useDesignerStore} from "../KaravanStore"; +import {shallow} from "zustand/shallow"; +import {IntegrationHeader} from "../utils/IntegrationHeader"; + + +interface Props { + integration: Integration + dark: boolean + onChange: (bean: RegistryBeanDefinition) => void + onClone: (bean: RegistryBeanDefinition) => void +} + +export function KameletProperties (props: Props) { + + const [selectedStep] = useDesignerStore((s) => [s.selectedStep], shallow); + const [infrastructureSelector, setInfrastructureSelector] = useState(false); + const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState(undefined); + const [infrastructureSelectorUuid, setInfrastructureSelectorUuid] = useState(undefined); + const [properties, setProperties] = useState>(new Map()); + + useEffect(()=> { + setProperties(preparePropertiesMap((selectedStep as RegistryBeanDefinition)?.properties)) + }, [selectedStep?.uuid]) + + function preparePropertiesMap (properties: any): Map { + const result = new Map(); + if (properties) { + Object.keys(properties).forEach((k, i, a) => result.set(uuidv4(), [k, properties[k], false])); + } + return result; + } + + function onBeanPropertyUpdate () { + if (selectedStep) { + const bean = CamelUtil.cloneBean(selectedStep); + const beanProperties: any = {}; + properties.forEach((p: any) => beanProperties[p[0]] = p[1]); + bean.properties = beanProperties; + props.onChange(bean); + } + } + + function beanFieldChanged (fieldId: string, value: string) { + if (selectedStep) { + const bean = CamelUtil.cloneBean(selectedStep); + (bean as any)[fieldId] = value; + props.onChange(bean); + } + } + + function propertyChanged (uuid: string, key: string, value: string, showPassword: boolean) { + setProperties(prevState => { + prevState.set(uuid, [key, value, showPassword]); + return prevState; + }); + onBeanPropertyUpdate(); + } + + function propertyDeleted (uuid: string) { + setProperties(prevState => { + prevState.delete(uuid); + return prevState; + }) + onBeanPropertyUpdate(); + } + + function selectInfrastructure (value: string) { + const propertyId = infrastructureSelectorProperty; + const uuid = infrastructureSelectorUuid; + if (propertyId && uuid){ + if (value.startsWith("config") || value.startsWith("secret")) value = "{{" + value + "}}"; + propertyChanged(uuid, propertyId, value, false); + setInfrastructureSelector(false); + setInfrastructureSelectorProperty(undefined); + } + } + + function openInfrastructureSelector (uuid: string, propertyName: string) { + setInfrastructureSelector(true); + setInfrastructureSelectorProperty(propertyName); + setInfrastructureSelectorUuid(uuid); + } + + function closeInfrastructureSelector () { + setInfrastructureSelector(false); + } + + function getInfrastructureSelectorModal() { + return ( + closeInfrastructureSelector()} + onSelect={selectInfrastructure}/>) + } + + function cloneBean () { + if (selectedStep) { + const bean = CamelUtil.cloneBean(selectedStep); + bean.uuid = uuidv4(); + props.onClone(bean); + } + } + + function getLabelIcon (displayName: string, description: string) { + return ( + + Required +
+ }> + + + ) + } + function getBeanForm() { + const bean = (selectedStep as RegistryBeanDefinition); + return ( + <> +
+
+ Bean + +
+
+ + beanFieldChanged("name", value)}/> + + + beanFieldChanged("type", value)}/> + + + {Array.from(properties.entries()).map((v, index, array) => { + const i = v[0]; + const key = v[1][0]; + const value = v[1][1]; + const showPassword = v[1][2]; + const isSecret = key !== undefined && SensitiveKeys.includes(key.toLowerCase()); + const inInfrastructure = InfrastructureAPI.infrastructure !== 'local'; + const icon = InfrastructureAPI.infrastructure === 'kubernetes' ? : + return ( +
+ { + propertyChanged(i, beanFieldName, value, showPassword) + }}/> + + {inInfrastructure && + + + } + + { + propertyChanged(i, key, value, showPassword) + }}/> + + {isSecret && + + } + + +
+ ) + })} + +
+ + ) + } + + const bean = (selectedStep as RegistryBeanDefinition); + return ( +
+
event.preventDefault()}> + {bean === undefined && } + {bean !== undefined && getBeanForm()} + + {getInfrastructureSelectorModal()} +
+ ) +} diff --git a/karavan-designer/src/designer/kamelet/kamelet.css b/karavan-designer/src/designer/kamelet/kamelet.css new file mode 100644 index 00000000000..bbdf28a9859 --- /dev/null +++ b/karavan-designer/src/designer/kamelet/kamelet.css @@ -0,0 +1,47 @@ +/* + * 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. + */ + +.karavan .kamelet-designer { + display: block; + height: 100vh; + width: 100%; + overflow-y: auto; + padding-bottom: 106px; +} + +.karavan .kamelet-designer .main { + background-color: var(--pf-v5-global--BackgroundColor--light-300); +} +.karavan .kamelet-designer .icon { + height: 20px; + width: 20px; + border: none; + -webkit-user-select: none; + -o-user-select: none; + user-select: none; +} + +.karavan .kamelet-designer .properties { + padding: 10px 10px 10px 10px; + background: transparent; + width: 100%; + height: 100%; + overflow: auto; + display: flex; + flex-direction: column; + justify-content: space-between; +} \ No newline at end of file diff --git a/karavan-designer/src/designer/route/DslProperties.tsx b/karavan-designer/src/designer/route/DslProperties.tsx index 697291218a3..ef933585ea1 100644 --- a/karavan-designer/src/designer/route/DslProperties.tsx +++ b/karavan-designer/src/designer/route/DslProperties.tsx @@ -46,8 +46,7 @@ export function DslProperties(props: Props) { const {cloneElement, onDataFormatChange, onPropertyChange, onParametersChange, onExpressionChange} = usePropertiesHook(props.isRouteDesigner); - const [selectedStep, dark, setSelectedStep, setSelectedUuids] = useDesignerStore((s) => - [s.selectedStep, s.dark, s.setSelectedStep, s.setSelectedUuids], shallow) + const [selectedStep, dark] = useDesignerStore((s) => [s.selectedStep, s.dark], shallow) const [showAdvanced, setShowAdvanced] = useState(false); const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false); diff --git a/karavan-designer/src/designer/utils/IntegrationHeader.tsx b/karavan-designer/src/designer/utils/IntegrationHeader.tsx index c117d434872..0725d1e12a8 100644 --- a/karavan-designer/src/designer/utils/IntegrationHeader.tsx +++ b/karavan-designer/src/designer/utils/IntegrationHeader.tsx @@ -23,19 +23,36 @@ export function IntegrationHeader () { const [integration] = useIntegrationStore((state) => [state.integration], shallow) + const isKamelet = integration.type === 'kamelet'; + + function getKameletType(): string { + // const labels = integration.metadata.labels; + // if (labels && labels.l) + // "camel.apache.org/kamelet.type" + return ''; + } + return (
- Integration + {/*Integration*/} {/**/} {/* */} {/**/} + + + + {isKamelet && + + }
) } diff --git a/karavan-designer/src/designer/utils/KaravanIcons.tsx b/karavan-designer/src/designer/utils/KaravanIcons.tsx index 677945f514d..906ba86e047 100644 --- a/karavan-designer/src/designer/utils/KaravanIcons.tsx +++ b/karavan-designer/src/designer/utils/KaravanIcons.tsx @@ -263,21 +263,38 @@ export function CamelIcon(props?: (JSX.IntrinsicAttributes & React.SVGProps - - + if (icon === 'kamelet') return ( + + {"application"} + + + ) + if (icon === 'code') return ( + + + + ) if (icon === 'routes') return (