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 9df8d7da7e673f5a4abd6302f2cefd644eb1cf62
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Thu Feb 1 20:00:51 2024 -0500

    Bean config wizard
---
 .../camel/karavan/api/ProjectFileResource.java     |  13 ++-
 .../org/apache/camel/karavan/code/CodeService.java |  12 ++-
 .../camel/karavan/service/ProjectService.java      |   9 ++
 .../snippets/database-bean-template.camel.yaml     |  22 ++++
 .../snippets/messaging-bean-template.camel.yaml    |   9 ++
 .../src/main/webui/src/api/KaravanApi.tsx          |  11 ++
 .../src/main/webui/src/api/ProjectStore.ts         |  13 +++
 .../src/main/webui/src/designer/DesignerStore.ts   |   2 +-
 .../src/main/webui/src/project/ProjectPanel.tsx    |  30 +++---
 .../main/webui/src/project/beans/BeanWizard.tsx    | 118 +++++++++++++++++++++
 10 files changed, 223 insertions(+), 16 deletions(-)

diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java
index 77776f6c..41b78850 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java
@@ -21,6 +21,7 @@ import jakarta.ws.rs.*;
 import jakarta.ws.rs.core.MediaType;
 import org.apache.camel.karavan.code.CodeService;
 import org.apache.camel.karavan.infinispan.InfinispanService;
+import org.apache.camel.karavan.infinispan.model.Project;
 import org.apache.camel.karavan.infinispan.model.ProjectFile;
 import org.apache.camel.karavan.validation.project.ProjectFileCreateValidator;
 
@@ -46,13 +47,21 @@ public class ProjectFileResource {
     @GET
     @Produces(MediaType.APPLICATION_JSON)
     @Path("/{projectId}")
-    public List<ProjectFile> get(@HeaderParam("username") String username,
-                                 @PathParam("projectId") String projectId) 
throws Exception {
+    public List<ProjectFile> get(@PathParam("projectId") String projectId) 
throws Exception {
         return infinispanService.getProjectFiles(projectId).stream()
                 .sorted(Comparator.comparing(ProjectFile::getName))
                 .collect(Collectors.toList());
     }
 
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/templates/beans")
+    public List<ProjectFile> getBeanTemplates() throws Exception {
+        return  codeService.getBeanTemplateNames().stream()
+                .map(s -> 
infinispanService.getProjectFile(Project.Type.templates.name(), s))
+                .toList();
+    }
+
     @POST
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java
index e519b5a4..12644148 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java
@@ -58,6 +58,7 @@ public class CodeService {
     private static final Logger LOGGER = 
Logger.getLogger(CodeService.class.getName());
     public static final String APPLICATION_PROPERTIES_FILENAME = 
"application.properties";
     public static final String BUILD_SCRIPT_FILENAME = "build.sh";
+    public static final String BEAN_TEMPLATE_SUFFIX_FILENAME = 
"-bean-template.camel.yaml";
     public static final String DEV_SERVICES_FILENAME = "devservices.yaml";
     public static final String PROJECT_COMPOSE_FILENAME = 
"docker-compose.yaml";
     public static final String MARKDOWN_EXTENSION = ".md";
@@ -86,6 +87,7 @@ public class CodeService {
     @Inject
     Vertx vertx;
 
+    List<String> beansTemplates = List.of("database", "messaging");
     List<String> targets = List.of("openshift", "kubernetes", "docker");
     List<String> interfaces = 
List.of("org.apache.camel.AggregationStrategy.java", 
"org.apache.camel.Processor.java");
 
@@ -185,6 +187,10 @@ public class CodeService {
         return null;
     }
 
+    public List<String> getBeanTemplateNames(){
+        return beansTemplates.stream().map(name -> name + 
BEAN_TEMPLATE_SUFFIX_FILENAME).toList();
+    }
+
     public Map<String, String> getTemplates() {
         Map<String, String> result = new HashMap<>();
 
@@ -192,10 +198,14 @@ public class CodeService {
         files.addAll(targets.stream().map(target -> target + "-" + 
APPLICATION_PROPERTIES_FILENAME).toList());
         files.addAll(targets.stream().map(target ->  target + "-" + 
BUILD_SCRIPT_FILENAME).toList());
 
+        files.addAll(getBeanTemplateNames());
+
         files.forEach(file -> {
             String templatePath = SNIPPETS_PATH + file;
             String templateText = getResourceFile(templatePath);
-            result.put(file, templateText);
+            if (templateText != null) {
+                result.put(file, templateText);
+            }
         });
 
         result.put(PROJECT_COMPOSE_FILENAME, getResourceFile(SNIPPETS_PATH + 
PROJECT_COMPOSE_FILENAME));
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
index 826990ec..aaebf8ae 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
@@ -386,6 +386,15 @@ public class ProjectService implements HealthCheck {
                     infinispanService.saveProjectFile(file);
                 });
                 commitAndPushProject(Project.Type.templates.name(), "Add 
default templates");
+            } else {
+                LOGGER.info("Add new templates if any");
+                codeService.getTemplates().forEach((name, value) -> {
+                    ProjectFile f = 
infinispanService.getProjectFile(Project.Type.templates.name(), name);
+                    if (f == null) {
+                        ProjectFile file = new ProjectFile(name, value, 
Project.Type.templates.name(), Instant.now().toEpochMilli());
+                        infinispanService.saveProjectFile(file);
+                    }
+                });
             }
         } catch (Exception e) {
             LOGGER.error("Error during templates project creation", e);
diff --git 
a/karavan-web/karavan-app/src/main/resources/snippets/database-bean-template.camel.yaml
 
b/karavan-web/karavan-app/src/main/resources/snippets/database-bean-template.camel.yaml
new file mode 100644
index 00000000..b2e8bc61
--- /dev/null
+++ 
b/karavan-web/karavan-app/src/main/resources/snippets/database-bean-template.camel.yaml
@@ -0,0 +1,22 @@
+- beans:
+    - name: PostgresDatabase
+      properties:
+        username: 'username'
+        password: 'password'
+        url: 'jdbc:postgresql://serverName:serverPort/databaseName'
+        driverClassName: org.postgresql.Driver
+      type: '#class:org.apache.commons.dbcp2.BasicDataSource'
+    - name: MySqlDatabase
+      properties:
+        username: 'username'
+        password: 'password'
+        url: 'jdbc:mysql://serverName:serverPort/databaseName'
+        driverClassName: com.mysql.cj.jdbc.Driver
+      type: '#class:org.apache.commons.dbcp2.BasicDataSource'
+    - name: OracleDatabase
+      properties:
+        username: 'username'
+        password: 'password'
+        url: 'jdbc:oracle:thin:@serverName:serverPort/databaseName'
+        driverClassName: oracle.jdbc.driver.OracleDriver
+      type: '#class:org.apache.commons.dbcp2.BasicDataSource'
\ No newline at end of file
diff --git 
a/karavan-web/karavan-app/src/main/resources/snippets/messaging-bean-template.camel.yaml
 
b/karavan-web/karavan-app/src/main/resources/snippets/messaging-bean-template.camel.yaml
new file mode 100644
index 00000000..ee05cbff
--- /dev/null
+++ 
b/karavan-web/karavan-app/src/main/resources/snippets/messaging-bean-template.camel.yaml
@@ -0,0 +1,9 @@
+- beans:
+    - name: JMSArtemis
+      properties:
+        brokerURL: 'tcp://serverHost:serverPort'
+      type: 
'#class:org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory'
+    - name: AMQP
+      properties:
+        brokerURL: 'amqp://serverHost:serverPort'
+      type: "#class:org.apache.qpid.jms.JmsConnectionFactory"
\ No newline at end of file
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx 
b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
index 4cef25c0..0e963238 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
@@ -318,6 +318,17 @@ export class KaravanApi {
         });
     }
 
+    static async getBeanTemplatesFiles( after: (files: ProjectFile []) => 
void) {
+        instance.get('/api/file/templates/beans')
+            .then(res => {
+                if (res.status === 200) {
+                    after(res.data);
+                }
+            }).catch(err => {
+            console.log(err);
+        });
+    }
+
     static async getDevModePodStatus(projectId: string, after: (res: 
AxiosResponse<ContainerStatus>) => void) {
         instance.get('/api/devmode/container/' + projectId)
             .then(res => {
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts 
b/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts
index d0078549..ccbb0399 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts
@@ -239,6 +239,19 @@ export const useFileStore = 
createWithEqualityFn<FileState>((set) => ({
     },
 }), shallow)
 
+
+
+interface WizardState {
+    showWizard: boolean;
+    setShowWizard: (showWizard: boolean) => void;
+}
+export const useWizardStore = createWithEqualityFn<WizardState>((set) => ({
+    showWizard: false,
+    setShowWizard: (showWizard: boolean)  => {
+        set({showWizard: showWizard})
+    },
+}), shallow)
+
 interface DevModeState {
     podName?: string,
     status: "none" | "wip",
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts 
b/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts
index 8a836e5f..ef95b8d4 100644
--- a/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/designer/DesignerStore.ts
@@ -19,7 +19,7 @@ import {CamelElement, Integration} from 
"karavan-core/lib/model/IntegrationDefin
 import {DslPosition, EventBus} from "./utils/EventBus";
 import {createWithEqualityFn} from "zustand/traditional";
 import {shallow} from "zustand/shallow";
-import {RegistryBeanDefinition} from 
"karavan-core/src/core/model/CamelDefinition";
+import {RegistryBeanDefinition} from "karavan-core/lib/model/CamelDefinition";
 
 interface IntegrationState {
     integration: Integration;
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/project/ProjectPanel.tsx 
b/karavan-web/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
index e9c842af..52d14df8 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
@@ -15,14 +15,14 @@
  * limitations under the License.
  */
 
-import React, {useEffect} from 'react';
+import React, {useEffect, useState} from 'react';
 import {
     Flex,
-    FlexItem, PageSection
+    FlexItem, Modal, ModalVariant, PageSection
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {FilesTab} from "./files/FilesTab";
-import {useAppConfigStore, useFilesStore, useFileStore, useProjectStore} from 
"../api/ProjectStore";
+import {useAppConfigStore, useFilesStore, useFileStore, useProjectStore, 
useWizardStore} from "../api/ProjectStore";
 import {DashboardTab} from "./dashboard/DashboardTab";
 import {TraceTab} from "./trace/TraceTab";
 import {ProjectBuildTab} from "./builder/ProjectBuildTab";
@@ -36,6 +36,7 @@ import {Buffer} from "buffer";
 import {CreateFileModal} from "./files/CreateFileModal";
 import {ProjectType} from "../api/ProjectModels";
 import {ReadmeTab} from "./readme/ReadmeTab";
+import {BeanWizard} from "./beans/BeanWizard";
 
 export function ProjectPanel() {
 
@@ -43,6 +44,7 @@ export function ProjectPanel() {
     const [project, tab, setTab] = useProjectStore((s) => [s.project, 
s.tabIndex, s.setTabIndex], shallow);
     const [setFile] = useFileStore((s) => [s.setFile], shallow);
     const [files] = useFilesStore((s) => [s.files], shallow);
+    const [setShowWizard] = useWizardStore((s) => [s.setShowWizard], shallow)
 
     useEffect(() => {
         onRefresh();
@@ -70,21 +72,25 @@ export function ProjectPanel() {
     const isTopology = tab === 'topology';
 
     const iFiles = files.map(f => new IntegrationFile(f.name, f.code));
-    const codes = iFiles.map(f=>f.code).join("");
+    const codes = iFiles.map(f => f.code).join("");
     const key = Buffer.from(codes).toString('base64')
 
     return isTopology
         ? (
             <>
-            <TopologyTab key={key}
-                         hideToolbar={false}
-                         files={files.map(f => new IntegrationFile(f.name, 
f.code))}
-                         onClickAddRoute={() => setFile('create', undefined, 
'routes')}
-                         onClickAddREST={() => setFile('create', undefined, 
'rest')}
-                         onClickAddBean={() => setFile('create', undefined, 
'beans')}
-                         onSetFile={(fileName) => selectFile(fileName)}
-            />
+                <TopologyTab key={key}
+                             hideToolbar={false}
+                             files={files.map(f => new IntegrationFile(f.name, 
f.code))}
+                             onClickAddRoute={() => setFile('create', 
undefined, 'routes')}
+                             onClickAddREST={() => setFile('create', 
undefined, 'rest')}
+                             onClickAddBean={() => {
+                                 // setFile('create', undefined, 'beans');
+                                 setShowWizard(true)
+                             }}
+                             onSetFile={(fileName) => selectFile(fileName)}
+                />
                 <CreateFileModal types={['INTEGRATION']} 
isKameletsProject={false}/>
+                <BeanWizard/>
             </>
         )
         : (<PageSection padding={{default: 'noPadding'}} 
className="scrollable-out">
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx 
b/karavan-web/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx
new file mode 100644
index 00000000..1adc5d0f
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx
@@ -0,0 +1,118 @@
+/*
+ * 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, useMemo, useState} from 'react';
+import {
+    Badge,
+    capitalize,
+    Flex,
+    Form, FormGroup,
+    Modal,
+    ModalVariant,
+    Radio, Text, TextInput,
+    Wizard,
+    WizardHeader,
+    WizardStep
+} from '@patternfly/react-core';
+import {KaravanApi} from "../../api/KaravanApi";
+import {RegistryBeanDefinition} from "karavan-core/lib/model/CamelDefinition";
+import {CodeUtils} from "../../util/CodeUtils";
+import {ProjectFile, ProjectType} from "../../api/ProjectModels";
+import {useWizardStore} from "../../api/ProjectStore";
+import {shallow} from "zustand/shallow";
+import {ProjectService} from "../../api/ProjectService";
+
+const BEAN_TEMPLATE_SUFFIX_FILENAME = "-bean-template.camel.yaml";
+
+export function BeanWizard() {
+
+    const [showWizard, setShowWizard] = useWizardStore((s) => [s.showWizard, 
s.setShowWizard], shallow)
+    const [files, setFiles] = useState<ProjectFile[]>([]);
+    const [templateNames, setTemplateNames] = useState<string[]>([]);
+    const [templateName, setTemplateName] = useState<string>('');
+    const [beanName, setBeanName] = useState<string>('');
+
+    useEffect(() => {
+        if (showWizard) {
+            KaravanApi.getBeanTemplatesFiles(files => {
+                const templateNames = files.map(file => 
file.name.replace(BEAN_TEMPLATE_SUFFIX_FILENAME, ''));
+                setFiles(prevState => {
+                    return [...files];
+                });
+                setTemplateNames(prevState => {
+                    return [...templateNames];
+                });
+                setTemplateName('');
+                setBeanName('');
+            });
+        }
+    }, [showWizard]);
+
+
+    function getRegistryBeanDefinitions():RegistryBeanDefinition[] {
+        const fs = files
+            .filter(f => f.name === 
templateName.concat(BEAN_TEMPLATE_SUFFIX_FILENAME));
+        return CodeUtils.getBeans(fs);
+    }
+
+    const getBeans = useMemo(() => getRegistryBeanDefinitions(), 
[templateName]);
+
+    return (
+        <Modal title={"Bean"} onClose={_ => setShowWizard(false)}
+               variant={ModalVariant.medium} isOpen={showWizard} 
onEscapePress={() => setShowWizard(false)}>
+            <Wizard height={600} title="Bean configuration" onClose={() => 
setShowWizard(false)}>
+                <WizardStep name={"Type"} id="type"
+                            footer={{ isNextDisabled: 
!templateNames.includes(templateName) }}
+                >
+                    <Flex direction={{default:"column"}} 
gap={{default:'gapLg'}}>
+                        {templateNames.map(n => <Radio id={n} 
label={capitalize(n)} name={n} isChecked={n === templateName}
+                                                       onChange={_ => 
setTemplateName(n)} />)}
+                    </Flex>
+                </WizardStep>
+                <WizardStep name={"Template"} id="template"
+                            isDisabled={templateName.length == 0}
+                            footer={{ isNextDisabled: !getBeans.map(b=> 
b.name).includes(beanName) }}
+                >
+                    <Flex direction={{default:"column"}} 
gap={{default:'gapLg'}}>
+                    {getBeans.map(b => <Radio id={b.name} label={b.name} 
name={b.name} isChecked={b.name === beanName}
+                                               onChange={_ => 
setBeanName(b.name)} />)}
+                    </Flex>
+                </WizardStep>
+                <WizardStep name="Properties" id="properties"
+                            isDisabled={templateName.length == 0 || 
beanName.length == 0}
+                            footer={{ nextButtonText: 'Add bean' }}
+                >
+                    <Form>
+                        {getBeans.filter(b=> b.name === beanName).map(b => (
+                           <>
+                               
{Object.getOwnPropertyNames(b.properties).map(prop => (
+                                   <FormGroup label={prop} fieldId={prop}>
+                                       <TextInput
+                                           value={b.properties[prop]}
+                                           id={prop}
+                                           aria-describedby={prop}
+                                           onChange={(_, value) => {}}
+                                       />
+                                   </FormGroup>
+                               ))}
+                           </>
+                        ))}
+                    </Form>
+                </WizardStep>
+            </Wizard>
+        </Modal>
+    )
+}
\ No newline at end of file

Reply via email to