You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ma...@apache.org on 2023/07/01 17:22:33 UTC

[camel-karavan] branch main updated (3c5bd25e -> 020e4d20)

This is an automated email from the ASF dual-hosted git repository.

marat pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git


    from 3c5bd25e Redesign of Project pages #809
     new 72b64388 Redesign of folder stucture of code #809
     new 6c64bd46 Refactor for #809
     new a34cfdee Refactor for #809
     new bfe16f68 Fix issues with #762
     new 4ff9f403 Merging for #809
     new 020e4d20 Fix #810

The 6 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 karavan-app/pom.xml                                |   2 +-
 .../pipelines/karavan-task-dev-quarkus.yaml        |   2 +-
 .../pipelines/karavan-task-dev-spring-boot.yaml    |   2 +-
 .../spring-boot-kubernetes-application.properties  |   4 +-
 .../spring-boot-openshift-application.properties   |   4 +-
 karavan-app/src/main/webui/src/Main.tsx            |  18 +-
 karavan-app/src/main/webui/src/MainToolbar.tsx     |  51 ---
 karavan-app/src/main/webui/src/api/KaravanApi.tsx  |   5 +-
 .../webui/src/{projects => api}/ProjectEventBus.ts |  11 -
 .../webui/src/{projects => api}/ProjectModels.ts   |   8 +
 .../ProjectLogic.ts => api/ProjectService.ts}      |  91 ++++-
 .../webui/src/{projects => api}/ProjectStore.ts    |  46 ++-
 .../src/main/webui/src/common/MainToolbar.tsx      |  27 ++
 .../main/webui/src/config/ConfigurationPage.tsx    |  14 +-
 .../src/main/webui/src/dashboard/DashboardPage.tsx |   4 +-
 karavan-app/src/main/webui/src/index.css           |   5 +-
 .../modal => project}/CreateFileModal.tsx          |  12 +-
 .../modal => project}/DeleteFileModal.tsx          |   8 +-
 .../webui/src/{projects => project}/ProjectLog.tsx |   4 +-
 .../src/main/webui/src/project/ProjectPage.tsx     |  95 +++++
 .../src/main/webui/src/project/ProjectPanel.tsx    |  59 ++++
 .../src/main/webui/src/project/ProjectTitle.tsx    |  61 ++++
 .../toolbar => project}/ProjectToolbar.tsx         | 178 ++++++----
 .../src/main/webui/src/project/RunnerToolbar.tsx   | 109 ++++++
 .../tabs => project/dashboard}/DashboardTab.tsx    |  26 +-
 .../dashboard}/RunnerInfoContext.tsx               |   1 -
 .../dashboard}/RunnerInfoMemory.tsx                |   4 +-
 .../tabs => project/dashboard}/RunnerInfoPod.tsx   |   4 +-
 .../src/main/webui/src/project/file/FileEditor.tsx | 143 ++++++++
 .../{projects => project/file}/PropertiesTable.tsx | 103 +++---
 .../files/FilesTab.tsx}                            |  45 ++-
 .../main/webui/src/project/files/FilesToolbar.tsx  |  40 +++
 .../{projects => project/files}/UploadModal.tsx    |  20 +-
 .../src/project/pipeline/ProjectPipelineTab.tsx    |  23 ++
 .../pipeline}/ProjectStatus.tsx                    |   8 +-
 .../tabs => project/trace}/RunnerInfoTrace.tsx     |  20 +-
 .../trace}/RunnerInfoTraceModal.tsx                |  16 +
 .../tabs => project/trace}/RunnerInfoTraceNode.tsx |  16 +
 .../{projects/tabs => project/trace}/TraceTab.tsx  |  36 +-
 .../projects/{modal => }/CreateProjectModal.tsx    |  25 +-
 .../projects/{modal => }/DeleteProjectModal.tsx    |   8 +-
 .../src/main/webui/src/projects/ProjectPage.tsx    | 389 ---------------------
 .../src/main/webui/src/projects/ProjectsPage.tsx   |  22 +-
 .../main/webui/src/projects/ProjectsTableRow.tsx   |  44 +--
 .../main/webui/src/projects/PropertiesEditor.tsx   |  42 ---
 .../src/main/webui/src/projects/RunnerToolbar.tsx  | 154 --------
 .../webui/src/projects/tabs/ProjectPipelineTab.tsx |  37 --
 karavan-builder/Dockerfile                         |   2 +-
 karavan-cli/CLI.md                                 |   2 +-
 karavan-cli/pom-quarkus.xml                        | 129 +++++++
 karavan-cli/pom.xml                                |   2 +-
 .../cli/{KaravanCli.java => InstallCommand.java}   |  18 +-
 .../apache/camel/karavan/cli/KaravanCommand.java   |  19 +
 .../apache/camel/karavan/cli/UpgradeCommand.java   |  20 ++
 .../src/main/resources/application.properties      |   6 +
 karavan-cloud/AWS/karavan-quarkus-task-aws.yaml    |   2 +-
 .../AWS/karavan-spring-boot-task-aws.yaml          |   2 +-
 karavan-core/src/core/api/CamelDefinitionApi.ts    |   4 +-
 .../src/core/api/CamelDefinitionYamlStep.ts        |   4 +-
 karavan-core/src/core/model/CamelDefinition.ts     |   9 +-
 karavan-core/src/core/model/CamelMetadata.ts       |  50 +--
 karavan-demo/jms-to-kafka/docs/README.md           |   2 +-
 karavan-generator/pom.xml                          |   2 +-
 .../resources/quarkus-builder-script-kubernetes.sh |   2 +-
 .../resources/quarkus-builder-script-openshift.sh  |   2 +-
 .../spring-boot-builder-script-kubernetes.sh       |   2 +-
 .../spring-boot-builder-script-openshift.sh        |   2 +-
 karavan-runner/Dockerfile                          |   2 +-
 karavan-vscode/CHANGELOG.md                        |   4 +-
 karavan-vscode/README.md                           |   4 +-
 karavan-vscode/package.json                        |   6 +-
 71 files changed, 1293 insertions(+), 1050 deletions(-)
 delete mode 100644 karavan-app/src/main/webui/src/MainToolbar.tsx
 rename karavan-app/src/main/webui/src/{projects => api}/ProjectEventBus.ts (82%)
 rename karavan-app/src/main/webui/src/{projects => api}/ProjectModels.ts (95%)
 rename karavan-app/src/main/webui/src/{projects/ProjectLogic.ts => api/ProjectService.ts} (51%)
 rename karavan-app/src/main/webui/src/{projects => api}/ProjectStore.ts (66%)
 create mode 100644 karavan-app/src/main/webui/src/common/MainToolbar.tsx
 rename karavan-app/src/main/webui/src/{projects/modal => project}/CreateFileModal.tsx (92%)
 rename karavan-app/src/main/webui/src/{projects/modal => project}/DeleteFileModal.tsx (84%)
 rename karavan-app/src/main/webui/src/{projects => project}/ProjectLog.tsx (96%)
 create mode 100644 karavan-app/src/main/webui/src/project/ProjectPage.tsx
 create mode 100644 karavan-app/src/main/webui/src/project/ProjectPanel.tsx
 create mode 100644 karavan-app/src/main/webui/src/project/ProjectTitle.tsx
 rename karavan-app/src/main/webui/src/{projects/toolbar => project}/ProjectToolbar.tsx (66%)
 create mode 100644 karavan-app/src/main/webui/src/project/RunnerToolbar.tsx
 rename karavan-app/src/main/webui/src/{projects/tabs => project/dashboard}/DashboardTab.tsx (84%)
 rename karavan-app/src/main/webui/src/{projects/tabs => project/dashboard}/RunnerInfoContext.tsx (99%)
 rename karavan-app/src/main/webui/src/{projects/tabs => project/dashboard}/RunnerInfoMemory.tsx (98%)
 rename karavan-app/src/main/webui/src/{projects/tabs => project/dashboard}/RunnerInfoPod.tsx (97%)
 create mode 100644 karavan-app/src/main/webui/src/project/file/FileEditor.tsx
 rename karavan-app/src/main/webui/src/{projects => project/file}/PropertiesTable.tsx (53%)
 rename karavan-app/src/main/webui/src/{projects/tabs/ProjectFilesTab.tsx => project/files/FilesTab.tsx} (64%)
 create mode 100644 karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
 rename karavan-app/src/main/webui/src/{projects => project/files}/UploadModal.tsx (88%)
 create mode 100644 karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
 rename karavan-app/src/main/webui/src/{projects => project/pipeline}/ProjectStatus.tsx (98%)
 rename karavan-app/src/main/webui/src/{projects/tabs => project/trace}/RunnerInfoTrace.tsx (84%)
 rename karavan-app/src/main/webui/src/{projects/tabs => project/trace}/RunnerInfoTraceModal.tsx (75%)
 rename karavan-app/src/main/webui/src/{projects/tabs => project/trace}/RunnerInfoTraceNode.tsx (78%)
 rename karavan-app/src/main/webui/src/{projects/tabs => project/trace}/TraceTab.tsx (54%)
 rename karavan-app/src/main/webui/src/projects/{modal => }/CreateProjectModal.tsx (85%)
 rename karavan-app/src/main/webui/src/projects/{modal => }/DeleteProjectModal.tsx (91%)
 delete mode 100644 karavan-app/src/main/webui/src/projects/ProjectPage.tsx
 delete mode 100644 karavan-app/src/main/webui/src/projects/PropertiesEditor.tsx
 delete mode 100644 karavan-app/src/main/webui/src/projects/RunnerToolbar.tsx
 delete mode 100644 karavan-app/src/main/webui/src/projects/tabs/ProjectPipelineTab.tsx
 create mode 100644 karavan-cli/pom-quarkus.xml
 rename karavan-cli/src/main/java/org/apache/camel/karavan/cli/{KaravanCli.java => InstallCommand.java} (89%)
 create mode 100644 karavan-cli/src/main/java/org/apache/camel/karavan/cli/KaravanCommand.java
 create mode 100644 karavan-cli/src/main/java/org/apache/camel/karavan/cli/UpgradeCommand.java
 create mode 100644 karavan-cli/src/main/resources/application.properties


[camel-karavan] 04/06: Fix issues with #762

Posted by ma...@apache.org.
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 bfe16f681b81ba680f1ab74f1b6bd980e7db8cfc
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Sat Jul 1 13:12:57 2023 -0400

    Fix issues with #762
---
 karavan-cli/CLI.md                                 |   2 +-
 karavan-cli/pom-quarkus.xml                        | 129 +++++++++++++++++++++
 karavan-cli/pom.xml                                |   2 +-
 .../cli/{KaravanCli.java => InstallCommand.java}   |  18 +--
 .../apache/camel/karavan/cli/KaravanCommand.java   |  19 +++
 .../apache/camel/karavan/cli/UpgradeCommand.java   |  20 ++++
 .../src/main/resources/application.properties      |   6 +
 7 files changed, 179 insertions(+), 17 deletions(-)

diff --git a/karavan-cli/CLI.md b/karavan-cli/CLI.md
index 7f572360..d5a637ba 100644
--- a/karavan-cli/CLI.md
+++ b/karavan-cli/CLI.md
@@ -22,7 +22,7 @@
     ```
 5. Install Karavan
     ```
-    java -jar target/karavan-cli-VERSION.jar --git-repository=$GIT_REPOSITORY --git-password=$GIT_TOKEN --git-username=$GIT_USERNAME  --node-port=30777
+    java -jar target/karavan-cli-VERSION.jar install --git-repository=$GIT_REPOSITORY --git-password=$GIT_TOKEN --git-username=$GIT_USERNAME  --node-port=30777
     ```
 5. Get karavan service URL
     ```
diff --git a/karavan-cli/pom-quarkus.xml b/karavan-cli/pom-quarkus.xml
new file mode 100644
index 00000000..9fd786a3
--- /dev/null
+++ b/karavan-cli/pom-quarkus.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.camel.karavan</groupId>
+  <artifactId>karavan-cli</artifactId>
+  <version>3.20.2-SNAPSHOT</version>
+  <properties>
+    <compiler-plugin.version>3.10.1</compiler-plugin.version>
+    <maven.compiler.release>17</maven.compiler.release>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
+    <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
+    <quarkus.platform.version>2.16.7.Final</quarkus.platform.version>
+    <skipITs>true</skipITs>
+    <surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
+  </properties>
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>${quarkus.platform.group-id}</groupId>
+        <artifactId>${quarkus.platform.artifact-id}</artifactId>
+        <version>${quarkus.platform.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+  <dependencies>
+    <dependency>
+      <groupId>io.quarkiverse.tektonclient</groupId>
+      <artifactId>quarkus-tekton-client</artifactId>
+      <version>0.7.0</version>
+    </dependency>
+    <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-openshift-client</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-picocli</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-kubernetes-client</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-arc</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-junit5</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>${quarkus.platform.group-id}</groupId>
+        <artifactId>quarkus-maven-plugin</artifactId>
+        <version>${quarkus.platform.version}</version>
+        <extensions>true</extensions>
+        <executions>
+          <execution>
+            <goals>
+              <goal>build</goal>
+              <goal>generate-code</goal>
+              <goal>generate-code-tests</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>${compiler-plugin.version}</version>
+        <configuration>
+          <compilerArgs>
+            <arg>-parameters</arg>
+          </compilerArgs>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>${surefire-plugin.version}</version>
+        <configuration>
+          <systemPropertyVariables>
+            <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
+            <maven.home>${maven.home}</maven.home>
+          </systemPropertyVariables>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-failsafe-plugin</artifactId>
+        <version>${surefire-plugin.version}</version>
+        <executions>
+          <execution>
+            <goals>
+              <goal>integration-test</goal>
+              <goal>verify</goal>
+            </goals>
+            <configuration>
+              <systemPropertyVariables>
+                <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
+                <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
+                <maven.home>${maven.home}</maven.home>
+              </systemPropertyVariables>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  <profiles>
+    <profile>
+      <id>native</id>
+      <activation>
+        <property>
+          <name>native</name>
+        </property>
+      </activation>
+      <properties>
+        <skipITs>false</skipITs>
+        <quarkus.package.type>native</quarkus.package.type>
+      </properties>
+    </profile>
+  </profiles>
+</project>
diff --git a/karavan-cli/pom.xml b/karavan-cli/pom.xml
index 267b0b4d..4f2dae60 100644
--- a/karavan-cli/pom.xml
+++ b/karavan-cli/pom.xml
@@ -76,7 +76,7 @@
                 <configuration>
                     <transformers>
                         <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
-                            <mainClass>org.apache.camel.karavan.cli.KaravanCli</mainClass>
+                            <mainClass>org.apache.camel.karavan.cli.KaravanCommand</mainClass>
                             <manifestEntries>
                                 <Multi-Release>true</Multi-Release>
                             </manifestEntries>
diff --git a/karavan-cli/src/main/java/org/apache/camel/karavan/cli/KaravanCli.java b/karavan-cli/src/main/java/org/apache/camel/karavan/cli/InstallCommand.java
similarity index 89%
rename from karavan-cli/src/main/java/org/apache/camel/karavan/cli/KaravanCli.java
rename to karavan-cli/src/main/java/org/apache/camel/karavan/cli/InstallCommand.java
index a852c8e9..f6248ea3 100644
--- a/karavan-cli/src/main/java/org/apache/camel/karavan/cli/KaravanCli.java
+++ b/karavan-cli/src/main/java/org/apache/camel/karavan/cli/InstallCommand.java
@@ -7,11 +7,10 @@ import java.nio.file.Path;
 import java.util.HashMap;
 import java.util.concurrent.Callable;
 
-@CommandLine.Command(name = "karavan",
+@CommandLine.Command(name = "install",
         mixinStandardHelpOptions = true,
-        version = "Karavan 3.20.2-SNAPSHOT",
-        description = "Apache Camel Karavan CLI")
-public class KaravanCli implements Callable<Integer> {
+        description = "Install Karavan")
+public class InstallCommand implements Callable<Integer> {
 
     @CommandLine.Option(names = {"-v", "--version"}, required = true, description = "Karavan version", defaultValue = "3.20.2-SNAPSHOT")
     private String version;
@@ -107,15 +106,4 @@ public class KaravanCli implements Callable<Integer> {
         }
         return 0;
     }
-
-    public static void main(String... args) {
-        CommandLine commandLine = new CommandLine(new KaravanCli());
-        commandLine.parseArgs(args);
-        if (commandLine.isUsageHelpRequested()) {
-            commandLine.usage(System.out);
-            System.exit(0);
-        }
-        int exitCode = commandLine.execute(args);
-        System.exit(exitCode);
-    }
 }
diff --git a/karavan-cli/src/main/java/org/apache/camel/karavan/cli/KaravanCommand.java b/karavan-cli/src/main/java/org/apache/camel/karavan/cli/KaravanCommand.java
new file mode 100644
index 00000000..5963ae6f
--- /dev/null
+++ b/karavan-cli/src/main/java/org/apache/camel/karavan/cli/KaravanCommand.java
@@ -0,0 +1,19 @@
+package org.apache.camel.karavan.cli;
+
+import picocli.CommandLine;
+
+@CommandLine.Command(mixinStandardHelpOptions = true, subcommands = {InstallCommand.class, UpgradeCommand.class})
+public class KaravanCommand {
+
+
+    public static void main(String... args) {
+        CommandLine commandLine = new CommandLine(new KaravanCommand());
+        commandLine.parseArgs(args);
+        if (commandLine.isUsageHelpRequested()) {
+            commandLine.usage(System.out);
+            System.exit(0);
+        }
+        int exitCode = commandLine.execute(args);
+        System.exit(exitCode);
+    }
+}
diff --git a/karavan-cli/src/main/java/org/apache/camel/karavan/cli/UpgradeCommand.java b/karavan-cli/src/main/java/org/apache/camel/karavan/cli/UpgradeCommand.java
new file mode 100644
index 00000000..8a05a100
--- /dev/null
+++ b/karavan-cli/src/main/java/org/apache/camel/karavan/cli/UpgradeCommand.java
@@ -0,0 +1,20 @@
+package org.apache.camel.karavan.cli;
+
+import picocli.CommandLine;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.concurrent.Callable;
+
+@CommandLine.Command(name = "upgrade",
+        mixinStandardHelpOptions = true,
+        description = "Upgrade Karavan")
+public class UpgradeCommand implements Callable<Integer> {
+
+    @Override
+    public Integer call() throws Exception {
+        System.out.println("Not implemented yet");
+        return 0;
+    }
+}
diff --git a/karavan-cli/src/main/resources/application.properties b/karavan-cli/src/main/resources/application.properties
new file mode 100644
index 00000000..726e936b
--- /dev/null
+++ b/karavan-cli/src/main/resources/application.properties
@@ -0,0 +1,6 @@
+quarkus.banner.enabled=false
+
+quarkus.package.type=uber-jar
+quarkus.package.add-runner-suffix=false
+
+quarkus.native.resources.includes=**.**
\ No newline at end of file


[camel-karavan] 02/06: Refactor for #809

Posted by ma...@apache.org.
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 6c64bd46db012daddae7c910d2a985728256bdbb
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Mon Jun 26 10:08:49 2023 -0400

    Refactor for #809
---
 karavan-app/src/main/webui/src/Main.tsx            |   7 +-
 karavan-app/src/main/webui/src/api/KaravanApi.tsx  |   3 +-
 .../src/main/webui/src/api/ProjectModels.ts        |   8 +
 karavan-app/src/main/webui/src/api/ProjectStore.ts |  24 ++-
 karavan-app/src/main/webui/src/index.css           |   5 +-
 .../src/main/webui/src/project/ProjectPage.tsx     | 214 ++++-----------------
 .../src/main/webui/src/project/ProjectPanel.tsx    |  49 +++++
 .../src/main/webui/src/project/ProjectTitle.tsx    |  61 ++++++
 .../src/main/webui/src/project/ProjectToolbar.tsx  |  42 ++--
 .../src/main/webui/src/project/RunnerToolbar.tsx   |   5 +-
 .../webui/src/project/dashboard/DashboardTab.tsx   |  20 +-
 .../src/project/dashboard/RunnerInfoContext.tsx    |   3 +-
 .../src/project/dashboard/RunnerInfoMemory.tsx     |   1 -
 .../src/main/webui/src/project/files/FilesTab.tsx  |  30 +--
 .../main/webui/src/project/files/FilesToolbar.tsx  |  24 +++
 .../src/project/pipeline/ProjectPipelineTab.tsx    |  42 ++--
 .../src/main/webui/src/project/trace/TraceTab.tsx  |  12 +-
 .../main/webui/src/projects/CreateProjectModal.tsx |  10 +-
 .../src/main/webui/src/projects/ProjectsPage.tsx   |   7 +-
 .../main/webui/src/projects/ProjectsTableRow.tsx   |   8 +-
 20 files changed, 281 insertions(+), 294 deletions(-)

diff --git a/karavan-app/src/main/webui/src/Main.tsx b/karavan-app/src/main/webui/src/Main.tsx
index 6b64d6e1..39f3192f 100644
--- a/karavan-app/src/main/webui/src/Main.tsx
+++ b/karavan-app/src/main/webui/src/Main.tsx
@@ -32,6 +32,7 @@ import {Subscription} from "rxjs";
 import {ProjectEventBus} from "./api/ProjectEventBus";
 import {Project} from "./api/ProjectModels";
 import {ProjectPage} from "./project/ProjectPage";
+import {useAppConfigStore} from "./api/ProjectStore";
 
 class ToastMessage {
     id: string = ''
@@ -129,6 +130,7 @@ export class Main extends React.Component<Props, State> {
     getData() {
         KaravanApi.getConfiguration((config: any) => {
             this.setState({config: config, request: uuidv4()});
+            useAppConfigStore.setState({config: config})
         });
         this.updateKamelets();
         this.updateComponents();
@@ -242,10 +244,9 @@ export class Main extends React.Component<Props, State> {
                     <FlexItem flex={{default: "flex_2"}} style={{height: "100%"}}>
                         {this.state.pageId === 'projects' &&
                             <ProjectsPage key={this.state.request}
-                                          toast={this.toast}
-                                          config={this.state.config}/>}
+                                          toast={this.toast}/>}
                         {this.state.pageId === 'project' &&
-                            <ProjectPage key="projects" config={this.state.config}/>}
+                            <ProjectPage key="projects"/>}
                         {this.state.pageId === 'dashboard' && <DashboardPage key={this.state.request}
                                                                              toast={this.toast}
                                                                              config={this.state.config}/>}
diff --git a/karavan-app/src/main/webui/src/api/KaravanApi.tsx b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
index 06811cb6..617b2e96 100644
--- a/karavan-app/src/main/webui/src/api/KaravanApi.tsx
+++ b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
@@ -1,5 +1,6 @@
 import axios, {AxiosResponse } from "axios";
 import {
+    AppConfig,
     CamelStatus,
     DeploymentStatus,
     PipelineStatus,
@@ -130,7 +131,7 @@ export class KaravanApi {
         });
     }
 
-    static async getConfiguration(after: (config: {}) => void) {
+    static async getConfiguration(after: (config: AppConfig) => void) {
         instance.get('/api/configuration')
             .then(res => {
                 if (res.status === 200) {
diff --git a/karavan-app/src/main/webui/src/api/ProjectModels.ts b/karavan-app/src/main/webui/src/api/ProjectModels.ts
index b65ea9a8..63757dcc 100644
--- a/karavan-app/src/main/webui/src/api/ProjectModels.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectModels.ts
@@ -1,3 +1,11 @@
+export class AppConfig {
+    version: string = '';
+    environment: string = '';
+    environments: string[] = [];
+    runtime: string = '';
+    runtimes: string[] = [];
+}
+
 export class Project {
     projectId: string = '';
     name: string = '';
diff --git a/karavan-app/src/main/webui/src/api/ProjectStore.ts b/karavan-app/src/main/webui/src/api/ProjectStore.ts
index ca385656..a524f6bc 100644
--- a/karavan-app/src/main/webui/src/api/ProjectStore.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectStore.ts
@@ -16,12 +16,20 @@
  */
 
 import {create} from 'zustand'
-import {DeploymentStatus, Project, ProjectFile} from "./ProjectModels";
+import {AppConfig, DeploymentStatus, Project, ProjectFile} from "./ProjectModels";
+
+interface AppConfigState {
+    config: AppConfig;
+    setConfig: (config: AppConfig) => void;
+}
+
+export const useAppConfigStore = create<AppConfigState>((set) => ({
+    config: new AppConfig(),
+    setConfig: (config: AppConfig)  => {
+        set({config: config})
+    },
+}))
 
-const projects: Project[] = [];
-var project: Project = new Project();
-const files: ProjectFile[] = [];
-var file: ProjectFile | undefined = undefined;
 
 interface ProjectsState {
     projects: Project[];
@@ -70,14 +78,14 @@ export const useFilesStore = create<FilesState>((set) => ({
 
 interface FileState {
     file?: ProjectFile;
-    operation: "create" | "select" | "delete" | "none" | "copy";
-    setFile: (file: ProjectFile, operation:  "create" | "select" | "delete"| "none" | "copy") => void;
+    operation: "create" | "select" | "delete" | "none" | "copy" | "upload";
+    setFile: (file: ProjectFile, operation:  "create" | "select" | "delete"| "none" | "copy" | "upload") => void;
 }
 
 export const useFileStore = create<FileState>((set) => ({
     file: undefined,
     operation: "none",
-    setFile: (file: ProjectFile, operation:  "create" | "select" | "delete"| "none" | "copy") => {
+    setFile: (file: ProjectFile, operation:  "create" | "select" | "delete"| "none" | "copy" | "upload") => {
         set((state: FileState) => ({
             file: file,
             operation: operation,
diff --git a/karavan-app/src/main/webui/src/index.css b/karavan-app/src/main/webui/src/index.css
index e3525ba9..1baa8f91 100644
--- a/karavan-app/src/main/webui/src/index.css
+++ b/karavan-app/src/main/webui/src/index.css
@@ -172,9 +172,8 @@
   margin-bottom: 16px;
 }
 
-.karavan .project-page .project-bottom {
-  overflow: auto;
-  max-height: 100vh;
+.karavan .project-page .project-tab-panel .pf-c-panel__header {
+  padding-bottom: 0;
 }
 
 .karavan .project-page .project-operations {
diff --git a/karavan-app/src/main/webui/src/project/ProjectPage.tsx b/karavan-app/src/main/webui/src/project/ProjectPage.tsx
index 34c6c429..edf0ef6c 100644
--- a/karavan-app/src/main/webui/src/project/ProjectPage.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectPage.tsx
@@ -1,19 +1,11 @@
 import React, {useEffect, useState} from 'react';
 import {
-    Badge,
-    Breadcrumb,
-    BreadcrumbItem,
     PageSection,
-    Text,
-    TextContent,
-    Flex,
-    FlexItem,
     CodeBlockCode,
-    CodeBlock, Skeleton, Tabs, Tab
+    CodeBlock, Skeleton
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {KaravanApi} from "../api/KaravanApi";
-import {KaravanDesigner} from "../designer/KaravanDesigner";
 import FileSaver from "file-saver";
 import Editor from "@monaco-editor/react";
 import {PropertiesEditor} from "./PropertiesEditor";
@@ -21,84 +13,56 @@ import {ProjectModel, ProjectProperty} from "karavan-core/lib/model/ProjectModel
 import {ProjectModelApi} from "karavan-core/lib/api/ProjectModelApi";
 import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
 import {ProjectToolbar} from "./ProjectToolbar";
-import {FilesTab} from "./files/FilesTab";
 import {EventBus} from "../designer/utils/EventBus";
 import {ProjectLog} from "./ProjectLog";
-import {getProjectFileType, ProjectFile, ProjectFileTypes} from "../api/ProjectModels";
-import {useProjectStore} from "../api/ProjectStore";
+import {AppConfig, ProjectFile, ProjectFileTypes} from "../api/ProjectModels";
+import {useAppConfigStore, useFileStore, useProjectStore} from "../api/ProjectStore";
 import {ProjectService} from "../api/ProjectService";
-import {DashboardTab} from "./dashboard/DashboardTab";
-import {TraceTab} from "./trace/TraceTab";
-import {ProjectPipelineTab} from "./pipeline/ProjectPipelineTab";
 import {MainToolbar} from "../common/MainToolbar";
 import {CreateFileModal} from "./CreateFileModal";
 import {DeleteFileModal} from "./DeleteFileModal";
+import {ProjectTitle} from "./ProjectTitle";
+import {ProjectPanel} from "./ProjectPanel";
 
-interface Props {
-    config: any,
-}
-
-export const ProjectPage = (props: Props) => {
+export const ProjectPage = () => {
 
     const [isUploadModalOpen, setIsUploadModalOpen] = useState<boolean>(false);
-    const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<boolean>(false);
     const [editAdvancedProperties, setEditAdvancedProperties] = useState<boolean>(false);
-    const [files, setFiles] = useState<ProjectFile[]>([]);
-    const [file, setFile] = useState<ProjectFile | undefined>(undefined);
-    const [fileToDelete, setFileToDelete] = useState<ProjectFile | undefined>(undefined);
+    const {file, operation} = useFileStore();
     const [mode, setMode] = useState<"design" | "code">("design");
     const [key, setKey] = useState<string>('');
     const [tab, setTab] = useState<string | number>('files');
-    const [environments, setEnvironments] = useState<string[]>((
-        props.config.environments && Array.isArray(props.config.environments)) ? Array.from(props.config.environments) : []
-    );
-    const [environment, setEnvironment] = useState<string>(props.config.environment);
-    const {project, setProject} = useProjectStore();
+    const {project} = useProjectStore();
+    const {config} = useAppConfigStore();
 
     useEffect(() => {
-        // console.log("UseEffect ProjectPage")
-
         onRefresh();
-        // const sub1 = ProjectEventBus.onCurrentRunner()?.subscribe((result) => {
-            // setCurrentRunner(result || '');
-        // });
-        // return () => {
-        //     sub1.unsubscribe();
-        // };
     });
 
-    function needCommit(): boolean {
-        return project ? files.filter(f => f.lastUpdate > project.lastCommitTimestamp).length > 0 : false;
-    }
-
     function onRefresh () {
-        ProjectService.refreshProjectData(environment);
+        ProjectService.refreshProjectData(config.environment);
     }
 
     function post (file: ProjectFile)  {
         KaravanApi.postProjectFile(file, res => {
             if (res.status === 200) {
                 const newFile = res.data;
-                setFiles((files => {
-                    const index = files.findIndex(f => f.name === newFile.name);
-                    if (index !== -1) files.splice(index, 1, newFile)
-                    else files.push(newFile);
-                    return files
-                }))
+                // setFiles((files => {
+                //     const index = files.findIndex(f => f.name === newFile.name);
+                //     if (index !== -1) files.splice(index, 1, newFile)
+                //     else files.push(newFile);
+                //     return files
+                // }))
             } else {
                 // console.log(res) //TODO show notification
             }
         })
     }
 
-    function copyToClipboard (data: string) {
-        navigator.clipboard.writeText(data);
-    }
-
     function save (name: string, code: string) {
         if (file) {
             file.code = code;
-            setFile(file);
+            // setFile(file);
             post(file);
         }
     }
@@ -132,93 +96,33 @@ export const ProjectPage = (props: Props) => {
                                mode={mode}
                                isTemplates={false}
                                isKamelets={false}
-                               config={props.config}
                                addProperty={() => addProperty()}
-                               download={() => download()}
                                downloadImage={() => downloadImage()}
                                editAdvancedProperties={editAdvancedProperties}
                                setEditAdvancedProperties={checked => setEditAdvancedProperties(checked)}
                                setMode={mode => setMode(mode)}
                                setUploadModalOpen={() => setIsUploadModalOpen(isUploadModalOpen)}
-                               needCommit={needCommit()}
+                               needCommit={false}
                                onRefresh={onRefresh}
         />
     }
 
-
-    function title ()  {
-        const isFile = file !== undefined;
-        const isLog = file !== undefined && file.name.endsWith("log");
-        const filename = file ? file.name.substring(0, file.name.lastIndexOf('.')) : "";
-        return (<div className="dsl-title project-title">
-            {isFile && <Flex direction={{default: "column"}} >
-                <FlexItem>
-                    <Breadcrumb>
-                        <BreadcrumbItem to="#" onClick={event => {
-                            setFile(undefined)
-                            onRefresh();
-                        }}>
-                            <div className={"project-breadcrumb"}>{project?.name + " (" + project?.projectId + ")"}</div>
-                        </BreadcrumbItem>
-                    </Breadcrumb>
-                </FlexItem>
-                <FlexItem>
-                    <Flex direction={{default: "row"}}>
-                        <FlexItem>
-                            <Badge>{getProjectFileType(file)}</Badge>
-                        </FlexItem>
-                        <FlexItem>
-                            <TextContent className="description">
-                                <Text>{isLog ? filename : file.name}</Text>
-                            </TextContent>
-                        </FlexItem>
-                    </Flex>
-                </FlexItem>
-            </Flex>}
-            {!isFile && <Flex direction={{default: "column"}} >
-                <FlexItem>
-                    <TextContent className="title">
-                        <Text component="h2">{project?.name + " (" + project?.projectId + ")"}</Text>
-                    </TextContent>
-                </FlexItem>
-                <FlexItem>
-                    <TextContent className="description">
-                        <Text>{project?.description}</Text>
-                    </TextContent>
-                </FlexItem>
-            </Flex>}
-        </div>)
-    };
-
-    function closeModal (isPushing: boolean = false) {
-        setIsUploadModalOpen(false);
-    }
-
-    function select (file: ProjectFile) {
-        setFile(file);
-    }
-
-    function openDeleteConfirmation (file: ProjectFile) {
-        setIsDeleteModalOpen(true)
-        setFileToDelete(file);
-    }
-
-    function getDesigner () {
-        return (
-            file !== undefined &&
-            <KaravanDesigner
-                dark={false}
-                key={"key"}
-                filename={file.name}
-                yaml={file.code}
-                onSave={(name, yaml) => save(name, yaml)}
-                onSaveCustomCode={(name, code) => post(new ProjectFile(name + ".java", project.projectId, code, Date.now()))}
-                onGetCustomCode={(name, javaType) => {
-                    return new Promise<string | undefined>(resolve => resolve(files.filter(f => f.name === name + ".java")?.at(0)?.code))
-                }}
-            />
-        )
-    }
+    // function getDesigner () {
+    //     return (
+    //         file !== undefined &&
+    //         <KaravanDesigner
+    //             dark={false}
+    //             key={"key"}
+    //             filename={file.name}
+    //             yaml={file.code}
+    //             onSave={(name, yaml) => save(name, yaml)}
+    //             onSaveCustomCode={(name, code) => post(new ProjectFile(name + ".java", project.projectId, code, Date.now()))}
+    //             onGetCustomCode={(name, javaType) => {
+    //                 return new Promise<string | undefined>(resolve => resolve(files.filter(f => f.name === name + ".java")?.at(0)?.code))
+    //             }}
+    //         />
+    //     )
+    // }
 
     function getEditor () {
         const language = file?.name.split('.').pop();
@@ -298,28 +202,6 @@ export const ProjectPage = (props: Props) => {
         )
     }
 
-    function getProjectPanel() {
-        return (
-            <Flex direction={{default: "column"}} spaceItems={{default: "spaceItemsNone"}}>
-                {getProjectPanelTabs()}
-                {getProjectPanelDetails()}
-            </Flex>
-        )
-    }
-
-    function getProjectPanelTabs() {
-        return (
-            <FlexItem className="project-tabs">
-                <Tabs activeKey={tab} onSelect={(event, tabIndex) => setTab(tabIndex)}>
-                    <Tab eventKey="files" title="Files"/>
-                    <Tab eventKey="dashboard" title="Dashboard"/>
-                    <Tab eventKey="trace" title="Trace"/>
-                    <Tab eventKey="pipeline" title="Pipeline"/>
-                </Tabs>
-            </FlexItem>
-        )
-    }
-
     function isBuildIn(): boolean {
         return ['kamelets', 'templates'].includes(project.projectId);
     }
@@ -332,25 +214,6 @@ export const ProjectPage = (props: Props) => {
         return project.projectId === 'templates';
     }
 
-    function getProjectPanelDetails() {
-        const buildIn = isBuildIn();
-        return (
-            <FlexItem>
-                {buildIn && tab === 'files' && <FilesTab/>}
-                {!buildIn &&
-                    <>
-                        {tab === 'files' && <FilesTab/>}
-                        {tab === 'dashboard' && project && <DashboardTab config={props.config}/>}
-                        {tab === 'trace' && project && <TraceTab config={props.config}/>}
-                        {tab === 'pipeline' && <ProjectPipelineTab project={project}
-                                                                   needCommit={needCommit()}
-                                                                   config={props.config}/>}
-                    </>
-                }
-            </FlexItem>
-        )
-    }
-
     function getFilePanel() {
         const isYaml = file !== undefined && file.name.endsWith("yaml");
         const isIntegration = isYaml && file?.code && CamelDefinitionYaml.yamlIsIntegration(file.code);
@@ -361,23 +224,24 @@ export const ProjectPage = (props: Props) => {
         const showEditor = isCode || (isYaml && !isIntegration) || (isYaml && mode === 'code');
         return (
             <>
-                {showDesigner && getDesigner()}
+                {/*{showDesigner && getDesigner()}*/}
                 {showEditor && getEditor()}
                 {isLog && getLogView()}
                 {isProperties && getPropertiesEditor()}
             </>
         )
     }
+    console.log(operation, file)
     const types = isBuildIn()
         ? (isKameletsProject() ? ['KAMELET'] : ['CODE', 'PROPERTIES'])
         : ProjectFileTypes.filter(p => !['PROPERTIES', 'LOG', 'KAMELET'].includes(p.name)).map(p => p.name);
     return (
         <PageSection key={key} className="kamelet-section project-page" padding={{default: 'noPadding'}}>
             <PageSection className="tools-section" padding={{default: 'noPadding'}}>
-                <MainToolbar title={title()} tools={tools()}/>
+                <MainToolbar title={<ProjectTitle/>} tools={tools()}/>
             </PageSection>
-            {file === undefined && getProjectPanel()}
-            {/*{file !== undefined && getFilePanel()}*/}
+            {file === undefined && operation !== 'select' && <ProjectPanel/>}
+            {file !== undefined && operation === 'select' && getFilePanel()}
             <ProjectLog/>
             <CreateFileModal types={types}/>
             <DeleteFileModal />
diff --git a/karavan-app/src/main/webui/src/project/ProjectPanel.tsx b/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
new file mode 100644
index 00000000..8fa32f2d
--- /dev/null
+++ b/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
@@ -0,0 +1,49 @@
+import React, {useState} from 'react';
+import {
+    Flex,
+    FlexItem, Tabs, Tab
+} from '@patternfly/react-core';
+import '../designer/karavan.css';
+import {FilesTab} from "./files/FilesTab";
+import {useProjectStore} from "../api/ProjectStore";
+import {DashboardTab} from "./dashboard/DashboardTab";
+import {TraceTab} from "./trace/TraceTab";
+import {ProjectPipelineTab} from "./pipeline/ProjectPipelineTab";
+
+export const ProjectPanel = () => {
+
+    const [tab, setTab] = useState<string | number>('files');
+    const {project} = useProjectStore();
+
+    function isBuildIn(): boolean {
+        return ['kamelets', 'templates'].includes(project.projectId);
+    }
+
+    const buildIn = isBuildIn();
+    return (
+        <Flex direction={{default: "column"}} spaceItems={{default: "spaceItemsNone"}}>
+            <FlexItem className="project-tabs">
+                {buildIn && <Tabs activeKey={tab} onSelect={(event, tabIndex) => setTab(tabIndex)}>
+                    <Tab eventKey="files" title="Files"/>
+                </Tabs>}
+                {!buildIn && <Tabs activeKey={tab} onSelect={(event, tabIndex) => setTab(tabIndex)}>
+                    <Tab eventKey="files" title="Files"/>
+                    <Tab eventKey="dashboard" title="Dashboard"/>
+                    <Tab eventKey="trace" title="Trace"/>
+                    <Tab eventKey="pipeline" title="Pipeline"/>
+                </Tabs>}
+            </FlexItem>
+            <FlexItem>
+                {buildIn && tab === 'files' && <FilesTab/>}
+                {!buildIn &&
+                    <>
+                        {tab === 'files' && <FilesTab/>}
+                        {tab === 'dashboard' && project && <DashboardTab/>}
+                        {tab === 'trace' && project && <TraceTab/>}
+                        {tab === 'pipeline' && <ProjectPipelineTab/>}
+                    </>
+                }
+            </FlexItem>
+        </Flex>
+    )
+}
diff --git a/karavan-app/src/main/webui/src/project/ProjectTitle.tsx b/karavan-app/src/main/webui/src/project/ProjectTitle.tsx
new file mode 100644
index 00000000..f3c0c816
--- /dev/null
+++ b/karavan-app/src/main/webui/src/project/ProjectTitle.tsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import {
+    Badge,
+    Breadcrumb,
+    BreadcrumbItem,
+    Text,
+    TextContent,
+    Flex,
+    FlexItem,
+} from '@patternfly/react-core';
+import '../designer/karavan.css';
+import {getProjectFileType} from "../api/ProjectModels";
+import {useAppConfigStore, useFileStore, useProjectStore} from "../api/ProjectStore";
+
+export const ProjectTitle = () => {
+
+    const {project} = useProjectStore();
+    const {file, operation, setFile} = useFileStore();
+    const {config} = useAppConfigStore();
+
+    const isFile = file !== undefined;
+    const isLog = file !== undefined && file.name.endsWith("log");
+    const filename = file ? file.name.substring(0, file.name.lastIndexOf('.')) : "";
+    return (<div className="dsl-title project-title">
+        {isFile && <Flex direction={{default: "column"}} >
+            <FlexItem>
+                <Breadcrumb>
+                    <BreadcrumbItem to="#" onClick={event => {
+                        useFileStore.setState({file: undefined, operation: 'none'});
+                    }}>
+                        <div className={"project-breadcrumb"}>{project?.name + " (" + project?.projectId + ")"}</div>
+                    </BreadcrumbItem>
+                </Breadcrumb>
+            </FlexItem>
+            <FlexItem>
+                <Flex direction={{default: "row"}}>
+                    <FlexItem>
+                        <Badge>{getProjectFileType(file)}</Badge>
+                    </FlexItem>
+                    <FlexItem>
+                        <TextContent className="description">
+                            <Text>{isLog ? filename : file.name}</Text>
+                        </TextContent>
+                    </FlexItem>
+                </Flex>
+            </FlexItem>
+        </Flex>}
+        {!isFile && <Flex direction={{default: "column"}} >
+            <FlexItem>
+                <TextContent className="title">
+                    <Text component="h2">{project?.name + " (" + project?.projectId + ")"}</Text>
+                </TextContent>
+            </FlexItem>
+            <FlexItem>
+                <TextContent className="description">
+                    <Text>{project?.description}</Text>
+                </TextContent>
+            </FlexItem>
+        </Flex>}
+    </div>)
+}
diff --git a/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx b/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
index 1957f9d6..0e7a75c4 100644
--- a/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
@@ -31,19 +31,17 @@ import ReloadIcon from "@patternfly/react-icons/dist/esm/icons/bolt-icon";
 import {RunnerToolbar} from "./RunnerToolbar";
 import {Project, ProjectFile} from "../api/ProjectModels";
 import {ProjectEventBus} from "../api/ProjectEventBus";
-import {useFileStore} from "../api/ProjectStore";
+import {useAppConfigStore, useFilesStore, useFileStore, useProjectStore} from "../api/ProjectStore";
 
 interface Props {
     project: Project,
     needCommit: boolean,
     isTemplates: boolean,
     isKamelets: boolean,
-    config: any,
     file?: ProjectFile,
     mode: "design" | "code",
     editAdvancedProperties: boolean,
     addProperty: () => void,
-    download: () => void,
     downloadImage: () => void,
     setUploadModalOpen: () => void,
     setEditAdvancedProperties: (checked: boolean) => void,
@@ -62,6 +60,9 @@ export const ProjectToolbar = (props: Props) => {
     const [isRunning, setIsRunning] = useState(false);
     const [isDeletingPod, setIsDeletingPod] = useState(false);
     const [isReloadingPod, setIsReloadingPod] = useState(false);
+    const {project} = useProjectStore();
+    const {files} = useFilesStore();
+    const {config} = useAppConfigStore();
 
     useEffect(() => {
         const sub1 = ProjectEventBus.onCurrentRunner()?.subscribe((result) => {
@@ -73,6 +74,10 @@ export const ProjectToolbar = (props: Props) => {
         };
     });
 
+    function needCommit(): boolean {
+        return project ? files.filter(f => f.lastUpdate > project.lastCommitTimestamp).length > 0 : false;
+    }
+
     function jbangRun() {
         setJbangIsRunning(true);
         KaravanApi.runProject(props.project, res => {
@@ -80,7 +85,7 @@ export const ProjectToolbar = (props: Props) => {
                 ProjectEventBus.setCurrentRunner(props.project.name);
                 setJbangIsRunning(false);
                 setPodName(res.data);
-                ProjectEventBus.showLog('container', res.data, props.config.environment)
+                ProjectEventBus.showLog('container', res.data, config.environment)
             } else {
                 // Todo notification
                 setJbangIsRunning(false);
@@ -164,7 +169,7 @@ export const ProjectToolbar = (props: Props) => {
     }
 
     function getTemplatesToolbar() {
-        const {file,needCommit, editAdvancedProperties, download, setUploadModalOpen} = props;
+        const {file,needCommit, editAdvancedProperties, setUploadModalOpen} = props;
         const isFile = file !== undefined;
         const isProperties = file !== undefined && file.name.endsWith("properties");
         return <Toolbar id="toolbar-group-types">
@@ -194,11 +199,7 @@ export const ProjectToolbar = (props: Props) => {
                                 onChange={checked => props.setEditAdvancedProperties(checked)}
                             />
                         </FlexItem>}
-                        {isFile && <FlexItem>
-                            <Tooltip content="Download source" position={"bottom-end"}>
-                                <Button isSmall variant="control" icon={<DownloadIcon/>} onClick={e => download()}/>
-                            </Tooltip>
-                        </FlexItem>}
+
                         {!isFile && <FlexItem>
                             <Button isSmall variant={"secondary"} icon={<PlusIcon/>}
                                     onClick={e => ProjectEventBus.showCreateProjectModal(true)}>Create</Button>
@@ -214,8 +215,8 @@ export const ProjectToolbar = (props: Props) => {
     }
 
     function getProjectToolbar() {
-        const {file,needCommit, mode, editAdvancedProperties, project, config,
-            addProperty, setEditAdvancedProperties, download, downloadImage, setUploadModalOpen} = props;
+        const {file,needCommit, mode, editAdvancedProperties, project,
+            addProperty, setEditAdvancedProperties, downloadImage, setUploadModalOpen} = props;
         const isFile = file !== undefined;
         const isYaml = file !== undefined && file.name.endsWith("yaml");
         const isIntegration = isYaml && file?.code && CamelDefinitionYaml.yamlIsIntegration(file.code);
@@ -262,27 +263,14 @@ export const ProjectToolbar = (props: Props) => {
                         <Button isSmall variant="primary" icon={<PlusIcon/>} onClick={e => addProperty()}>Add property</Button>
                     </FlexItem>}
 
-                    {isFile && <FlexItem>
-                        <Tooltip content="Download source" position={"bottom-end"}>
-                            <Button isSmall variant="control" icon={<DownloadIcon/>} onClick={e => download()}/>
-                        </Tooltip>
-                    </FlexItem>}
+
                     {isIntegration && <FlexItem>
                         <Tooltip content="Download image" position={"bottom-end"}>
                             <Button isSmall variant="control" icon={<DownloadImageIcon/>} onClick={e => downloadImage()}/>
                         </Tooltip>
                     </FlexItem>}
-                    {!isFile && <FlexItem>
-                        <Button isSmall variant={"secondary"} icon={<PlusIcon/>}
-                                onClick={e => useFileStore.setState({operation:"create"})}>Create</Button>
-                    </FlexItem>}
-                    {!isFile && <FlexItem>
-                        <Button isSmall variant="secondary" icon={<UploadIcon/>}
-                                onClick={e => setUploadModalOpen()}>Upload</Button>
-                    </FlexItem>}
-
                     {isYaml && currentRunner === project.name && <FlexItem>
-                        <RunnerToolbar project={project} config={config} showConsole={false} reloadOnly={true} />
+                        <RunnerToolbar project={project} showConsole={false} reloadOnly={true} />
                     </FlexItem>}
                 </Flex>
             </ToolbarContent>
diff --git a/karavan-app/src/main/webui/src/project/RunnerToolbar.tsx b/karavan-app/src/main/webui/src/project/RunnerToolbar.tsx
index 4b63bf2b..e309a531 100644
--- a/karavan-app/src/main/webui/src/project/RunnerToolbar.tsx
+++ b/karavan-app/src/main/webui/src/project/RunnerToolbar.tsx
@@ -11,11 +11,11 @@ import DeleteIcon from "@patternfly/react-icons/dist/esm/icons/times-circle-icon
 import {KaravanApi} from "../api/KaravanApi";
 import {Project} from "../api/ProjectModels";
 import {ProjectEventBus} from "../api/ProjectEventBus";
+import {useAppConfigStore} from "../api/ProjectStore";
 
 
 interface Props {
     project: Project,
-    config: any,
     showConsole: boolean,
     reloadOnly: boolean
 }
@@ -27,6 +27,7 @@ export const RunnerToolbar = (props: Props) => {
     const [isRunning, setIsRunning] = useState(false);
     const [isDeletingPod, setIsDeletingPod] = useState(false);
     const [isReloadingPod, setIsReloadingPod] = useState(false);
+    const {config} = useAppConfigStore();
 
     useEffect(() => {
         const sub1 = ProjectEventBus.onCurrentRunner()?.subscribe((result) => {
@@ -44,7 +45,7 @@ export const RunnerToolbar = (props: Props) => {
                 ProjectEventBus.setCurrentRunner(props.project.name);
                 setJbangIsRunning(false);
                 setPodName(res.data);
-                ProjectEventBus.showLog('container', res.data, props.config.environment)
+                ProjectEventBus.showLog('container', res.data, config.environment)
             } else {
                 // Todo notification
                 setJbangIsRunning(false);
diff --git a/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx b/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx
index 7eddf0c6..6f919cad 100644
--- a/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx
+++ b/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx
@@ -9,18 +9,14 @@ import {RunnerInfoContext} from "./RunnerInfoContext";
 import {RunnerInfoMemory} from "./RunnerInfoMemory";
 import {KaravanApi} from "../../api/KaravanApi";
 import {PodStatus} from "../../api/ProjectModels";
-import {useProjectStore} from "../../api/ProjectStore";
+import {useAppConfigStore, useProjectStore} from "../../api/ProjectStore";
 import {ProjectEventBus} from "../../api/ProjectEventBus";
 
 export function isRunning(status: PodStatus): boolean {
     return status.phase === 'Running' && !status.terminating;
 }
 
-interface Props {
-    config: any,
-}
-
-export const DashboardTab = (props: Props) => {
+export const DashboardTab = () => {
 
     const {project, setProject} = useProjectStore();
     const [podStatus, setPodStatus] = useState(new PodStatus());
@@ -28,6 +24,7 @@ export const DashboardTab = (props: Props) => {
     const [memory, setMemory] = useState({});
     const [jvm, setJvm] = useState({});
     const [context, setContext] = useState({});
+    const {config} = useAppConfigStore();
 
     useEffect(() => {
         previousValue.current = podStatus;
@@ -47,10 +44,10 @@ export const DashboardTab = (props: Props) => {
             if (res.status === 200) {
                 setPodStatus(res.data);
                 if (isRunning(res.data) && !isRunning(previousValue.current)) {
-                    ProjectEventBus.showLog('container', res.data.name, props.config.environment);
+                    ProjectEventBus.showLog('container', res.data.name, config.environment);
                 }
             } else {
-                ProjectEventBus.showLog('container', name, props.config.environment, false);
+                ProjectEventBus.showLog('container', name, config.environment, false);
                 setPodStatus(new PodStatus({name: name}));
             }
         });
@@ -81,9 +78,8 @@ export const DashboardTab = (props: Props) => {
         return podStatus.phase !== '';
     }
 
-    const {config} = props;
     return (
-        <PageSection className="project-bottom" padding={{default: "padding"}}>
+        <PageSection className="project-tab-panel" padding={{default: "padding"}}>
             <Card className="project-development">
                 <CardBody>
                     <Flex direction={{default: "row"}}
@@ -93,11 +89,11 @@ export const DashboardTab = (props: Props) => {
                         </FlexItem>
                         <Divider orientation={{default: "vertical"}}/>
                         <FlexItem flex={{default: "flex_1"}}>
-                            <RunnerInfoMemory jvm={jvm} memory={memory} config={config} showConsole={showConsole()}/>
+                            <RunnerInfoMemory jvm={jvm} memory={memory} showConsole={showConsole()}/>
                         </FlexItem>
                         <Divider orientation={{default: "vertical"}}/>
                         <FlexItem flex={{default: "flex_1"}}>
-                            <RunnerInfoContext context={context} config={config} showConsole={showConsole()}/>
+                            <RunnerInfoContext context={context} showConsole={showConsole()}/>
                         </FlexItem>
                     </Flex>
                 </CardBody>
diff --git a/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoContext.tsx b/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoContext.tsx
index 62cc67ca..7683be64 100644
--- a/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoContext.tsx
+++ b/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoContext.tsx
@@ -14,12 +14,13 @@ import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
 
 interface Props {
     context: any,
-    config: any,
     showConsole: boolean
 }
 
 export const RunnerInfoContext = (props: Props) => {
 
+
+
     function getContextInfo() {
         return (
             <LabelGroup numLabels={3}>
diff --git a/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoMemory.tsx b/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoMemory.tsx
index 15c37bd0..67e576ad 100644
--- a/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoMemory.tsx
+++ b/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoMemory.tsx
@@ -15,7 +15,6 @@ import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
 interface Props {
     jvm: any,
     memory: any,
-    config: any,
     showConsole: boolean
 }
 
diff --git a/karavan-app/src/main/webui/src/project/files/FilesTab.tsx b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
index feb57616..3ae79da5 100644
--- a/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
+++ b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
@@ -6,14 +6,17 @@ import {
     EmptyState,
     EmptyStateVariant,
     EmptyStateIcon,
-    Title, PageSection, PanelHeader, Flex, FlexItem, Panel,
+    Title, PageSection, PanelHeader, Panel, Tooltip,
 } from '@patternfly/react-core';
 import '../../designer/karavan.css';
 import {TableComposable, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
 import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
 import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
 import {useFilesStore, useFileStore} from "../../api/ProjectStore";
-import {getProjectFileType} from "../../api/ProjectModels";
+import {getProjectFileType, ProjectFile} from "../../api/ProjectModels";
+import {FileToolbar} from "./FilesToolbar";
+import DownloadIcon from "@patternfly/react-icons/dist/esm/icons/download-icon";
+import FileSaver from "file-saver";
 
 
 export const FilesTab = () => {
@@ -28,18 +31,20 @@ export const FilesTab = () => {
             return "N/A"
         }
     }
+
+    function download (file: ProjectFile) {
+        if (file) {
+            const type = file.name.endsWith("yaml") ? "application/yaml;charset=utf-8" : undefined;
+            const f = new File([file.code], file.name, {type: type});
+            FileSaver.saveAs(f);
+        }
+    }
+
     return (
-        <PageSection className="project-bottom" padding={{default: "padding"}}>
+        <PageSection className="project-tab-panel" padding={{default: "padding"}}>
             <Panel>
                 <PanelHeader>
-                    <Flex direction={{default: "row"}} justifyContent={{default:"justifyContentFlexEnd"}}>
-                        <FlexItem>
-
-                        </FlexItem>
-                        <FlexItem>
-
-                        </FlexItem>
-                    </Flex>
+                    <FileToolbar/>
                 </PanelHeader>
             </Panel>
             <TableComposable aria-label="Files" variant={"compact"} className={"table"}>
@@ -79,6 +84,9 @@ export const FilesTab = () => {
                                         <DeleteIcon/>
                                     </Button>
                                 }
+                                <Tooltip content="Download source" position={"bottom-end"}>
+                                    <Button isSmall variant="plain" icon={<DownloadIcon/>} onClick={e => download(file)}/>
+                                </Tooltip>
                             </Td>
                         </Tr>
                     })}
diff --git a/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx b/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
new file mode 100644
index 00000000..2ef4c092
--- /dev/null
+++ b/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import {
+    Button,
+    Flex,
+    FlexItem,
+} from '@patternfly/react-core';
+import '../../designer/karavan.css';
+import UploadIcon from "@patternfly/react-icons/dist/esm/icons/upload-icon";
+import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
+import {useFileStore} from "../../api/ProjectStore";
+
+export const FileToolbar = () => {
+
+    return <Flex className="toolbar" direction={{default: "row"}} justifyContent={{default: "justifyContentFlexEnd"}}>
+        <FlexItem>
+            <Button isSmall variant={"secondary"} icon={<PlusIcon/>}
+                    onClick={e => useFileStore.setState({operation:"create"})}>Create</Button>
+        </FlexItem>
+        <FlexItem>
+            <Button isSmall variant="secondary" icon={<UploadIcon/>}
+                    onClick={e => useFileStore.setState({operation:"upload"})}>Upload</Button>
+        </FlexItem>
+    </Flex>
+}
diff --git a/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx b/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
index 54cd6f99..6cd7e4fd 100644
--- a/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
+++ b/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
@@ -2,36 +2,22 @@ import React from 'react';
 import '../../designer/karavan.css';
 import {ProjectStatus} from "../ProjectStatus";
 import {PageSection} from "@patternfly/react-core";
-import {Project} from "../../api/ProjectModels";
+import {useAppConfigStore, useProjectStore} from "../../api/ProjectStore";
 
 
-interface Props {
-    project: Project,
-    config: any,
-    needCommit: boolean,
-}
-
-interface State {
-    environment: string,
-}
-
-export class ProjectPipelineTab extends React.Component<Props, State> {
+export const ProjectPipelineTab = () => {
 
-    public state: State = {
-        environment: this.props.config.environment
-    };
+    const {config} = useAppConfigStore();
+    const {project} = useProjectStore();
 
-    render() {
-        const {project, config,} = this.props;
-        return (
-            <PageSection className="project-bottom" padding={{default: "padding"}}>
-                <div className="project-operations">
-                    {/*{["dev", "test", "prod"].map(env =>*/}
-                    {["dev"].map(env =>
-                        <ProjectStatus key={env} project={project} config={config} env={env}/>
-                    )}
-                </div>
-            </PageSection>
-        )
-    }
+    return (
+        <PageSection className="project-tab-panel" padding={{default: "padding"}}>
+            <div className="project-operations">
+                {/*{["dev", "test", "prod"].map(env =>*/}
+                {["dev"].map(env =>
+                    <ProjectStatus key={env} project={project} config={config} env={env}/>
+                )}
+            </div>
+        </PageSection>
+    )
 }
diff --git a/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx b/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx
index 144c2b00..31f1c0c9 100644
--- a/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx
+++ b/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx
@@ -6,21 +6,18 @@ import {PodStatus} from "../../api/ProjectModels";
 import {KaravanApi} from "../../api/KaravanApi";
 import {ProjectEventBus} from "../../api/ProjectEventBus";
 import {RunnerInfoTrace} from "./RunnerInfoTrace";
-import {useProjectStore} from "../../api/ProjectStore";
+import {useAppConfigStore, useProjectStore} from "../../api/ProjectStore";
 
 export function isRunning(status: PodStatus): boolean {
     return status.phase === 'Running' && !status.terminating;
 }
 
-interface Props {
-    config: any,
-}
-
-export const TraceTab = (props: Props) => {
+export const TraceTab = () => {
 
     const {project, setProject} = useProjectStore();
     const [trace, setTrace] = useState({});
     const [refreshTrace, setRefreshTrace] = useState(true);
+    const {config} = useAppConfigStore();
 
     useEffect(() => {
         const sub2 = ProjectEventBus.onRefreshTrace()?.subscribe((result: boolean) => {
@@ -50,9 +47,8 @@ export const TraceTab = (props: Props) => {
         }
     }
 
-    const {config} = props;
     return (
-        <PageSection className="project-bottom" padding={{default: "padding"}}>
+        <PageSection className="project-tab-panel" padding={{default: "padding"}}>
             <RunnerInfoTrace trace={trace} refreshTrace={refreshTrace}/>
         </PageSection>
     )
diff --git a/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx b/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx
index 19fa2cb5..7f635a47 100644
--- a/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx
+++ b/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx
@@ -6,23 +6,21 @@ import {
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
-import {useProjectStore} from "../api/ProjectStore";
+import {useAppConfigStore, useProjectStore} from "../api/ProjectStore";
 import {ProjectService} from "../api/ProjectService";
 import {Project} from "../api/ProjectModels";
 import {QuarkusIcon, SpringIcon} from "../designer/utils/KaravanIcons";
 import {CamelUi} from "../designer/utils/CamelUi";
 
-interface Props {
-    config: any,
-}
 
-export const CreateProjectModal = (props: Props) => {
+export const CreateProjectModal = () => {
 
     const {project, operation} = useProjectStore();
     const [name, setName] = useState('');
     const [description, setDescription] = useState('');
     const [runtime, setRuntime] = useState('');
     const [projectId, setProjectId] = useState('');
+    const {config} = useAppConfigStore();
 
     function cleanValues()  {
         setName("");
@@ -48,7 +46,7 @@ export const CreateProjectModal = (props: Props) => {
         }
     }
 
-    const runtimes = props.config.runtimes;
+    const runtimes = config.runtimes;
     const isReady = projectId && name && description && !['templates', 'kamelets'].includes(projectId);
     return (
         <Modal
diff --git a/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx b/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
index dec4ea29..5edf4006 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
@@ -22,14 +22,13 @@ import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
 import {ProjectsTableRow} from "./ProjectsTableRow";
 import {DeleteProjectModal} from "./DeleteProjectModal";
 import {CreateProjectModal} from "./CreateProjectModal";
-import {useProjectsStore, useProjectStore} from "../api/ProjectStore";
+import {useAppConfigStore, useProjectsStore, useProjectStore} from "../api/ProjectStore";
 import {ProjectService} from "../api/ProjectService";
 import {MainToolbar} from "../common/MainToolbar";
 import {Project} from "../api/ProjectModels";
 
 
 interface Props {
-    config: any,
     toast: (title: string, text: string, variant: 'success' | 'danger' | 'warning' | 'info' | 'default') => void
 }
 
@@ -39,6 +38,7 @@ export const ProjectsPage = (props: Props) => {
     const {operation} = useProjectStore();
     const [filter, setFilter] = useState<string>('');
     const [loading, setLoading] = useState<boolean>(false);
+    const {config} = useAppConfigStore();
 
     useEffect(() => {
         const interval = setInterval(() => {
@@ -119,7 +119,6 @@ export const ProjectsPage = (props: Props) => {
                     {projs.map(project => (
                         <ProjectsTableRow
                             key={project.projectId}
-                            config={props.config}
                             project={project}/>
                     ))}
                     {projs.length === 0 && getEmptyState()}
@@ -136,7 +135,7 @@ export const ProjectsPage = (props: Props) => {
             <PageSection isFilled className="kamelets-page">
                 {getProjectsTable()}
             </PageSection>
-            {["create", "copy"].includes(operation) && <CreateProjectModal config={props.config}/>}
+            {["create", "copy"].includes(operation) && <CreateProjectModal/>}
             {["delete"].includes(operation) && <DeleteProjectModal/>}
         </PageSection>
     )
diff --git a/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx b/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
index 9ce6b07e..701d1424 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
@@ -10,20 +10,20 @@ import { Td, Tr} from "@patternfly/react-table";
 import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
 import CopyIcon from "@patternfly/react-icons/dist/esm/icons/copy-icon";
 import {DeploymentStatus, Project} from '../api/ProjectModels';
-import {useDeploymentStatusesStore, useProjectStore} from "../api/ProjectStore";
+import {useAppConfigStore, useDeploymentStatusesStore, useProjectStore} from "../api/ProjectStore";
 import {ProjectEventBus} from "../api/ProjectEventBus";
 
 interface Props {
-    config: any,
     project: Project
 }
 
 export const ProjectsTableRow = (props: Props) => {
 
-    const {statuses} = useDeploymentStatusesStore()
+    const {statuses} = useDeploymentStatusesStore();
+    const {config} = useAppConfigStore();
 
     function getEnvironments(): string [] {
-        return props.config.environments && Array.isArray(props.config.environments) ? Array.from(props.config.environments) : [];
+        return config.environments && Array.isArray(config.environments) ? Array.from(config.environments) : [];
     }
 
     function getDeploymentByEnvironments(name: string): [string, DeploymentStatus | undefined] [] {


[camel-karavan] 01/06: Redesign of folder stucture of code #809

Posted by ma...@apache.org.
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 72b6438861e42b70426f06c0f51d48e8567dcaec
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Sun Jun 25 11:21:11 2023 -0400

    Redesign of folder stucture of code #809
---
 karavan-app/src/main/webui/src/Main.tsx            |  6 +--
 karavan-app/src/main/webui/src/MainToolbar.tsx     | 51 -------------------
 karavan-app/src/main/webui/src/api/KaravanApi.tsx  |  2 +-
 .../webui/src/{projects => api}/ProjectEventBus.ts |  0
 .../webui/src/{projects => api}/ProjectModels.ts   |  0
 .../ProjectLogic.ts => api/ProjectService.ts}      | 12 ++---
 .../webui/src/{projects => api}/ProjectStore.ts    |  0
 .../src/main/webui/src/common/MainToolbar.tsx      | 27 ++++++++++
 .../main/webui/src/config/ConfigurationPage.tsx    | 14 +----
 .../src/main/webui/src/dashboard/DashboardPage.tsx |  4 +-
 .../modal => project}/CreateFileModal.tsx          | 12 ++---
 .../modal => project}/DeleteFileModal.tsx          |  8 +--
 .../webui/src/{projects => project}/ProjectLog.tsx |  4 +-
 .../src/{projects => project}/ProjectPage.tsx      | 33 ++++++------
 .../src/{projects => project}/ProjectStatus.tsx    |  4 +-
 .../toolbar => project}/ProjectToolbar.tsx         | 59 +++++++++++++++++++---
 .../src/{projects => project}/PropertiesEditor.tsx |  2 +-
 .../src/{projects => project}/PropertiesTable.tsx  |  0
 .../src/{projects => project}/RunnerToolbar.tsx    | 24 ++-------
 .../tabs => project/dashboard}/DashboardTab.tsx    |  6 +--
 .../dashboard}/RunnerInfoContext.tsx               |  0
 .../dashboard}/RunnerInfoMemory.tsx                |  3 +-
 .../tabs => project/dashboard}/RunnerInfoPod.tsx   |  4 +-
 .../files/FilesTab.tsx}                            | 20 ++++++--
 .../{projects => project/files}/UploadModal.tsx    |  4 +-
 .../pipeline}/ProjectPipelineTab.tsx               |  2 +-
 .../tabs => project/trace}/RunnerInfoTrace.tsx     |  4 +-
 .../trace}/RunnerInfoTraceModal.tsx                |  0
 .../tabs => project/trace}/RunnerInfoTraceNode.tsx |  0
 .../{projects/tabs => project/trace}/TraceTab.tsx  | 10 ++--
 .../projects/{modal => }/CreateProjectModal.tsx    | 17 +++----
 .../projects/{modal => }/DeleteProjectModal.tsx    |  8 +--
 .../src/main/webui/src/projects/ProjectsPage.tsx   | 17 +++----
 .../main/webui/src/projects/ProjectsTableRow.tsx   | 38 ++------------
 34 files changed, 182 insertions(+), 213 deletions(-)

diff --git a/karavan-app/src/main/webui/src/Main.tsx b/karavan-app/src/main/webui/src/Main.tsx
index 759e77d1..6b64d6e1 100644
--- a/karavan-app/src/main/webui/src/Main.tsx
+++ b/karavan-app/src/main/webui/src/Main.tsx
@@ -20,8 +20,6 @@ import Icon from "./Logo";
 import {ComponentsPage} from "./components/ComponentsPage";
 import {EipPage} from "./eip/EipPage";
 import {ProjectsPage} from "./projects/ProjectsPage";
-import {Project} from "./projects/ProjectModels";
-import {ProjectPage} from "./projects/ProjectPage";
 import UserIcon from "@patternfly/react-icons/dist/js/icons/user-icon";
 import ProjectsIcon from "@patternfly/react-icons/dist/js/icons/repository-icon";
 import KameletsIcon from "@patternfly/react-icons/dist/js/icons/registry-icon";
@@ -31,7 +29,9 @@ import ComponentsIcon from "@patternfly/react-icons/dist/js/icons/module-icon";
 import {MainLogin} from "./MainLogin";
 import {DashboardPage} from "./dashboard/DashboardPage";
 import {Subscription} from "rxjs";
-import {ProjectEventBus} from "./projects/ProjectEventBus";
+import {ProjectEventBus} from "./api/ProjectEventBus";
+import {Project} from "./api/ProjectModels";
+import {ProjectPage} from "./project/ProjectPage";
 
 class ToastMessage {
     id: string = ''
diff --git a/karavan-app/src/main/webui/src/MainToolbar.tsx b/karavan-app/src/main/webui/src/MainToolbar.tsx
deleted file mode 100644
index c312ae8c..00000000
--- a/karavan-app/src/main/webui/src/MainToolbar.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import React, {useEffect, useState} from 'react';
-import {
-    PageSectionVariants, Flex, PageSection, FlexItem
-} from '@patternfly/react-core';
-import './designer/karavan.css';
-import {ProjectEventBus} from "./projects/ProjectEventBus";
-import {Project, ProjectFile} from "./projects/ProjectModels";
-
-interface Props {
-    title: React.ReactNode;
-    tools: React.ReactNode;
-    file?: ProjectFile;
-}
-
-export const MainToolbar = (props: Props) => {
-    const {title, tools, file} = props;
-
-    const [project, setProject] = useState<Project | undefined>(undefined);
-    const [mode, setMode] = useState<"design" | "code">("design");
-
-    useEffect(() => {
-        const sub1 = ProjectEventBus.onSelectProject()?.subscribe((result) => setProject(result));
-        const sub2 = ProjectEventBus.onSetMode()?.subscribe((result) => setMode(result));
-        return () => {
-            sub1.unsubscribe();
-            sub2.unsubscribe();
-        };
-    });
-
-    function isKameletsProject(): boolean {
-        return project?.projectId === 'kamelets';
-    }
-
-    function isTemplatesProject(): boolean {
-        return project?.projectId === 'templates';
-    }
-
-    return (
-        <PageSection className="tools-section" variant={PageSectionVariants.light}>
-            <Flex className="tools" justifyContent={{default: 'justifyContentSpaceBetween'}}
-                  alignItems={{default: 'alignItemsCenter'}}>
-                <FlexItem>
-                    {title}
-                </FlexItem>
-                <FlexItem>
-                    {tools}
-                </FlexItem>
-            </Flex>
-        </PageSection>
-    );
-}
diff --git a/karavan-app/src/main/webui/src/api/KaravanApi.tsx b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
index 7142c571..06811cb6 100644
--- a/karavan-app/src/main/webui/src/api/KaravanApi.tsx
+++ b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
@@ -6,7 +6,7 @@ import {
     PodStatus,
     Project,
     ProjectFile, ServiceStatus
-} from "../projects/ProjectModels";
+} from "./ProjectModels";
 import {Buffer} from 'buffer';
 import {SsoApi} from "./SsoApi";
 
diff --git a/karavan-app/src/main/webui/src/projects/ProjectEventBus.ts b/karavan-app/src/main/webui/src/api/ProjectEventBus.ts
similarity index 100%
rename from karavan-app/src/main/webui/src/projects/ProjectEventBus.ts
rename to karavan-app/src/main/webui/src/api/ProjectEventBus.ts
diff --git a/karavan-app/src/main/webui/src/projects/ProjectModels.ts b/karavan-app/src/main/webui/src/api/ProjectModels.ts
similarity index 100%
rename from karavan-app/src/main/webui/src/projects/ProjectModels.ts
rename to karavan-app/src/main/webui/src/api/ProjectModels.ts
diff --git a/karavan-app/src/main/webui/src/projects/ProjectLogic.ts b/karavan-app/src/main/webui/src/api/ProjectService.ts
similarity index 92%
rename from karavan-app/src/main/webui/src/projects/ProjectLogic.ts
rename to karavan-app/src/main/webui/src/api/ProjectService.ts
index 0b924478..e346c5bd 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectLogic.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectService.ts
@@ -1,10 +1,10 @@
-import {KaravanApi} from "../api/KaravanApi";
+import {KaravanApi} from "./KaravanApi";
 import {DeploymentStatus, Project, ProjectFile} from "./ProjectModels";
 import {TemplateApi} from "karavan-core/lib/api/TemplateApi";
 import {KubernetesAPI} from "../designer/utils/KubernetesAPI";
 import {useDeploymentStatusesStore, useFilesStore, useProjectsStore, useProjectStore} from "./ProjectStore";
 
-export class ProjectLogic {
+export class ProjectService {
 
     public static refreshProjects() {
         KaravanApi.getProjects((projects: Project[]) => {
@@ -22,7 +22,7 @@ export class ProjectLogic {
         KaravanApi.deleteProject(project, res => {
             if (res.status === 204) {
                 // this.props.toast?.call(this, "Success", "Project deleted", "success");
-                ProjectLogic.refreshProjectData();
+                ProjectService.refreshProjectData();
             } else {
                 // this.props.toast?.call(this, "Error", res.statusText, "danger");
             }
@@ -33,7 +33,7 @@ export class ProjectLogic {
         KaravanApi.postProject(project, res => {
             console.log(res.status)
             if (res.status === 200 || res.status === 201) {
-                ProjectLogic.refreshProjectData();
+                ProjectService.refreshProjectData();
                 // this.props.toast?.call(this, "Success", "Project created", "success");
             } else {
                 // this.props.toast?.call(this, "Error", res.status + ", " + res.statusText, "danger");
@@ -45,7 +45,7 @@ export class ProjectLogic {
         KaravanApi.postProjectFile(file, res => {
             if (res.status === 200) {
                 // console.log(res) //TODO show notification
-                ProjectLogic.refreshProjectData();
+                ProjectService.refreshProjectData();
             } else {
                 // console.log(res) //TODO show notification
             }
@@ -55,7 +55,7 @@ export class ProjectLogic {
     public static deleteFile(file: ProjectFile) {
         KaravanApi.deleteProjectFile(file, res => {
             if (res.status === 204) {
-                ProjectLogic.refreshProjectData();
+                ProjectService.refreshProjectData();
             } else {
             }
         });
diff --git a/karavan-app/src/main/webui/src/projects/ProjectStore.ts b/karavan-app/src/main/webui/src/api/ProjectStore.ts
similarity index 100%
rename from karavan-app/src/main/webui/src/projects/ProjectStore.ts
rename to karavan-app/src/main/webui/src/api/ProjectStore.ts
diff --git a/karavan-app/src/main/webui/src/common/MainToolbar.tsx b/karavan-app/src/main/webui/src/common/MainToolbar.tsx
new file mode 100644
index 00000000..ed344840
--- /dev/null
+++ b/karavan-app/src/main/webui/src/common/MainToolbar.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import {
+    PageSectionVariants, Flex, PageSection, FlexItem
+} from '@patternfly/react-core';
+import '../designer/karavan.css';
+
+interface Props {
+    title: React.ReactNode;
+    tools: React.ReactNode;
+}
+
+export const MainToolbar = (props: Props) => {
+
+    return (
+        <PageSection className="tools-section" variant={PageSectionVariants.light}>
+            <Flex className="tools" justifyContent={{default: 'justifyContentSpaceBetween'}}
+                  alignItems={{default: 'alignItemsCenter'}}>
+                <FlexItem>
+                    {props.title}
+                </FlexItem>
+                <FlexItem>
+                    {props.tools}
+                </FlexItem>
+            </Flex>
+        </PageSection>
+    );
+}
diff --git a/karavan-app/src/main/webui/src/config/ConfigurationPage.tsx b/karavan-app/src/main/webui/src/config/ConfigurationPage.tsx
index c9efa9ff..0e796725 100644
--- a/karavan-app/src/main/webui/src/config/ConfigurationPage.tsx
+++ b/karavan-app/src/main/webui/src/config/ConfigurationPage.tsx
@@ -1,19 +1,9 @@
 import React from 'react';
-import {
-    Badge, Bullseye, Button, EmptyState, EmptyStateIcon, EmptyStateVariant, Flex, FlexItem,
-    Gallery, OverflowMenu, OverflowMenuContent, OverflowMenuGroup, OverflowMenuItem,
-    PageSection, Tab, Tabs, TabTitleText, Text, TextContent, TextInput, Title, Toolbar, ToolbarContent, ToolbarItem, Tooltip
+import {Button, PageSection, Tab, Tabs, TabTitleText, Text, TextContent, Toolbar, ToolbarContent, ToolbarItem
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
-import {MainToolbar} from "../MainToolbar";
-import {TableComposable, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
-import CopyIcon from "@patternfly/react-icons/dist/esm/icons/copy-icon";
-import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
-import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon";
+import {MainToolbar} from "../common/MainToolbar";
 import RefreshIcon from "@patternfly/react-icons/dist/esm/icons/sync-alt-icon";
-import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
-import {KaravanApi} from "../api/KaravanApi";
-import {DeploymentStatus, Project} from "../projects/ProjectModels";
 
 interface Props {
 }
diff --git a/karavan-app/src/main/webui/src/dashboard/DashboardPage.tsx b/karavan-app/src/main/webui/src/dashboard/DashboardPage.tsx
index 2538efe6..8bc9be07 100644
--- a/karavan-app/src/main/webui/src/dashboard/DashboardPage.tsx
+++ b/karavan-app/src/main/webui/src/dashboard/DashboardPage.tsx
@@ -13,8 +13,7 @@ import {
     ToolbarItem, Tooltip
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
-import {MainToolbar} from "../MainToolbar";
-import {CamelStatus, DeploymentStatus, Project, ServiceStatus} from "../projects/ProjectModels";
+import {CamelStatus, DeploymentStatus, Project, ServiceStatus} from "../api/ProjectModels";
 import {TableComposable, TableVariant, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
 import {camelIcon, CamelUi} from "../designer/utils/CamelUi";
 import {KaravanApi} from "../api/KaravanApi";
@@ -23,6 +22,7 @@ import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
 import DownIcon from "@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
 import RefreshIcon from "@patternfly/react-icons/dist/esm/icons/sync-alt-icon";
 import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon";
+import {MainToolbar} from "../common/MainToolbar";
 
 interface Props {
     config: any,
diff --git a/karavan-app/src/main/webui/src/projects/modal/CreateFileModal.tsx b/karavan-app/src/main/webui/src/project/CreateFileModal.tsx
similarity index 92%
rename from karavan-app/src/main/webui/src/projects/modal/CreateFileModal.tsx
rename to karavan-app/src/main/webui/src/project/CreateFileModal.tsx
index fa29401c..79887096 100644
--- a/karavan-app/src/main/webui/src/projects/modal/CreateFileModal.tsx
+++ b/karavan-app/src/main/webui/src/project/CreateFileModal.tsx
@@ -7,13 +7,13 @@ import {
     Form,
     ToggleGroupItem, ToggleGroup, FormHelperText, HelperText, HelperTextItem, TextInput
 } from '@patternfly/react-core';
-import '../../designer/karavan.css';
-import {ProjectFile, ProjectFileTypes} from "../ProjectModels";
-import {CamelUi} from "../../designer/utils/CamelUi";
+import '../designer/karavan.css';
 import {Integration} from "karavan-core/lib/model/IntegrationDefinition";
 import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
-import {useFileStore, useProjectStore} from "../ProjectStore";
-import {ProjectLogic} from "../ProjectLogic";
+import {useFileStore, useProjectStore} from "../api/ProjectStore";
+import {ProjectFile, ProjectFileTypes} from "../api/ProjectModels";
+import {CamelUi} from "../designer/utils/CamelUi";
+import {ProjectService} from "../api/ProjectService";
 
 interface Props {
     types: string[]
@@ -44,7 +44,7 @@ export const CreateFileModal = (props: Props) => {
             : '';
         if (filename && extension) {
             const file = new ProjectFile(filename + '.' + extension, project.projectId, code, Date.now());
-            ProjectLogic.createFile(file);
+            ProjectService.createFile(file);
             useFileStore.setState({operation: "none"});
             cleanValues();
         }
diff --git a/karavan-app/src/main/webui/src/projects/modal/DeleteFileModal.tsx b/karavan-app/src/main/webui/src/project/DeleteFileModal.tsx
similarity index 84%
rename from karavan-app/src/main/webui/src/projects/modal/DeleteFileModal.tsx
rename to karavan-app/src/main/webui/src/project/DeleteFileModal.tsx
index 85dc7acf..4ed433ea 100644
--- a/karavan-app/src/main/webui/src/projects/modal/DeleteFileModal.tsx
+++ b/karavan-app/src/main/webui/src/project/DeleteFileModal.tsx
@@ -4,9 +4,9 @@ import {
     Modal,
     ModalVariant,
 } from '@patternfly/react-core';
-import '../../designer/karavan.css';
-import {useFileStore, useProjectStore} from "../ProjectStore";
-import {ProjectLogic} from "../ProjectLogic";
+import '../designer/karavan.css';
+import {useFileStore} from "../api/ProjectStore";
+import {ProjectService} from "../api/ProjectService";
 
 export const DeleteFileModal = () => {
 
@@ -17,7 +17,7 @@ export const DeleteFileModal = () => {
     }
 
     function confirmAndCloseModal () {
-        if (file) ProjectLogic.deleteFile(file);
+        if (file) ProjectService.deleteFile(file);
         useFileStore.setState({operation: "none"});
     }
 
diff --git a/karavan-app/src/main/webui/src/projects/ProjectLog.tsx b/karavan-app/src/main/webui/src/project/ProjectLog.tsx
similarity index 96%
rename from karavan-app/src/main/webui/src/projects/ProjectLog.tsx
rename to karavan-app/src/main/webui/src/project/ProjectLog.tsx
index dea7aaad..ba674aaa 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectLog.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectLog.tsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import {Button, Checkbox, Label, PageSection, Text, Tooltip, TooltipPosition} from '@patternfly/react-core';
+import {Button, Checkbox, Label, PageSection, Tooltip, TooltipPosition} from '@patternfly/react-core';
 import '../designer/karavan.css';
 import CloseIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
 import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/expand-icon';
@@ -8,7 +8,7 @@ import CollapseIcon from '@patternfly/react-icons/dist/esm/icons/compress-icon';
 import CleanIcon from '@patternfly/react-icons/dist/esm/icons/trash-alt-icon';
 import {LogViewer} from '@patternfly/react-log-viewer';
 import {Subscription} from "rxjs";
-import {ProjectEventBus, ShowLogCommand} from "./ProjectEventBus";
+import {ProjectEventBus, ShowLogCommand} from "../api/ProjectEventBus";
 
 const INITIAL_LOG_HEIGHT = "50%";
 
diff --git a/karavan-app/src/main/webui/src/projects/ProjectPage.tsx b/karavan-app/src/main/webui/src/project/ProjectPage.tsx
similarity index 93%
rename from karavan-app/src/main/webui/src/projects/ProjectPage.tsx
rename to karavan-app/src/main/webui/src/project/ProjectPage.tsx
index 77a0ce85..34c6c429 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectPage.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectPage.tsx
@@ -3,39 +3,36 @@ import {
     Badge,
     Breadcrumb,
     BreadcrumbItem,
-    Button,
     PageSection,
     Text,
     TextContent,
-    ModalVariant,
-    Modal,
     Flex,
     FlexItem,
     CodeBlockCode,
     CodeBlock, Skeleton, Tabs, Tab
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
-import {MainToolbar} from "../MainToolbar";
 import {KaravanApi} from "../api/KaravanApi";
-import {getProjectFileType, Project, ProjectFile, ProjectFileTypes} from "./ProjectModels";
 import {KaravanDesigner} from "../designer/KaravanDesigner";
 import FileSaver from "file-saver";
 import Editor from "@monaco-editor/react";
-import {CreateFileModal} from "./modal/CreateFileModal";
 import {PropertiesEditor} from "./PropertiesEditor";
 import {ProjectModel, ProjectProperty} from "karavan-core/lib/model/ProjectModel";
 import {ProjectModelApi} from "karavan-core/lib/api/ProjectModelApi";
-import {ProjectPipelineTab} from "./tabs/ProjectPipelineTab";
 import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
-import {ProjectToolbar} from "./toolbar/ProjectToolbar";
-import {ProjectFilesTab} from "./tabs/ProjectFilesTab";
+import {ProjectToolbar} from "./ProjectToolbar";
+import {FilesTab} from "./files/FilesTab";
 import {EventBus} from "../designer/utils/EventBus";
 import {ProjectLog} from "./ProjectLog";
-import {ProjectLogic} from "./ProjectLogic";
-import {useProjectStore} from "./ProjectStore";
-import {DeleteFileModal} from "./modal/DeleteFileModal";
-import {DashboardTab} from "./tabs/DashboardTab";
-import {TraceTab} from "./tabs/TraceTab";
+import {getProjectFileType, ProjectFile, ProjectFileTypes} from "../api/ProjectModels";
+import {useProjectStore} from "../api/ProjectStore";
+import {ProjectService} from "../api/ProjectService";
+import {DashboardTab} from "./dashboard/DashboardTab";
+import {TraceTab} from "./trace/TraceTab";
+import {ProjectPipelineTab} from "./pipeline/ProjectPipelineTab";
+import {MainToolbar} from "../common/MainToolbar";
+import {CreateFileModal} from "./CreateFileModal";
+import {DeleteFileModal} from "./DeleteFileModal";
 
 interface Props {
     config: any,
@@ -75,7 +72,7 @@ export const ProjectPage = (props: Props) => {
     }
 
     function onRefresh () {
-        ProjectLogic.refreshProjectData(environment);
+        ProjectService.refreshProjectData(environment);
     }
 
     function post (file: ProjectFile)  {
@@ -339,10 +336,10 @@ export const ProjectPage = (props: Props) => {
         const buildIn = isBuildIn();
         return (
             <FlexItem>
-                {buildIn && tab === 'files' && <ProjectFilesTab/>}
+                {buildIn && tab === 'files' && <FilesTab/>}
                 {!buildIn &&
                     <>
-                        {tab === 'files' && <ProjectFilesTab/>}
+                        {tab === 'files' && <FilesTab/>}
                         {tab === 'dashboard' && project && <DashboardTab config={props.config}/>}
                         {tab === 'trace' && project && <TraceTab config={props.config}/>}
                         {tab === 'pipeline' && <ProjectPipelineTab project={project}
@@ -377,7 +374,7 @@ export const ProjectPage = (props: Props) => {
     return (
         <PageSection key={key} className="kamelet-section project-page" padding={{default: 'noPadding'}}>
             <PageSection className="tools-section" padding={{default: 'noPadding'}}>
-                <MainToolbar title={title()} tools={tools()} file={file}/>
+                <MainToolbar title={title()} tools={tools()}/>
             </PageSection>
             {file === undefined && getProjectPanel()}
             {/*{file !== undefined && getFilePanel()}*/}
diff --git a/karavan-app/src/main/webui/src/projects/ProjectStatus.tsx b/karavan-app/src/main/webui/src/project/ProjectStatus.tsx
similarity index 99%
rename from karavan-app/src/main/webui/src/projects/ProjectStatus.tsx
rename to karavan-app/src/main/webui/src/project/ProjectStatus.tsx
index 9974e4db..3e57ebe9 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectStatus.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectStatus.tsx
@@ -8,14 +8,14 @@ import {
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {KaravanApi} from "../api/KaravanApi";
-import {DeploymentStatus, Project, PipelineStatus, CamelStatus, PodStatus} from "./ProjectModels";
 import BuildIcon from "@patternfly/react-icons/dist/esm/icons/build-icon";
 import RolloutIcon from "@patternfly/react-icons/dist/esm/icons/process-automation-icon";
 import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
 import DownIcon from "@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
 import ClockIcon from "@patternfly/react-icons/dist/esm/icons/clock-icon";
 import DeleteIcon from "@patternfly/react-icons/dist/esm/icons/times-circle-icon";
-import {ProjectEventBus} from "./ProjectEventBus";
+import {CamelStatus, DeploymentStatus, PipelineStatus, PodStatus, Project} from "../api/ProjectModels";
+import {ProjectEventBus} from "../api/ProjectEventBus";
 
 interface Props {
     project: Project,
diff --git a/karavan-app/src/main/webui/src/projects/toolbar/ProjectToolbar.tsx b/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
similarity index 85%
rename from karavan-app/src/main/webui/src/projects/toolbar/ProjectToolbar.tsx
rename to karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
index 39cf4c84..1957f9d6 100644
--- a/karavan-app/src/main/webui/src/projects/toolbar/ProjectToolbar.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
@@ -19,19 +19,19 @@ import {
     Tooltip,
     TooltipPosition
 } from '@patternfly/react-core';
-import '../../designer/karavan.css';
-import {Project, ProjectFile} from "../ProjectModels";
+import '../designer/karavan.css';
 import UploadIcon from "@patternfly/react-icons/dist/esm/icons/upload-icon";
 import DownloadIcon from "@patternfly/react-icons/dist/esm/icons/download-icon";
 import DownloadImageIcon from "@patternfly/react-icons/dist/esm/icons/image-icon";
 import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
 import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
 import PushIcon from "@patternfly/react-icons/dist/esm/icons/code-branch-icon";
-import {KaravanApi} from "../../api/KaravanApi";
+import {KaravanApi} from "../api/KaravanApi";
 import ReloadIcon from "@patternfly/react-icons/dist/esm/icons/bolt-icon";
-import {RunnerToolbar} from "../RunnerToolbar";
-import {ProjectEventBus} from "../ProjectEventBus";
-import {useFileStore, useProjectStore} from "../ProjectStore";
+import {RunnerToolbar} from "./RunnerToolbar";
+import {Project, ProjectFile} from "../api/ProjectModels";
+import {ProjectEventBus} from "../api/ProjectEventBus";
+import {useFileStore} from "../api/ProjectStore";
 
 interface Props {
     project: Project,
@@ -57,16 +57,63 @@ export const ProjectToolbar = (props: Props) => {
     const [commitMessageIsOpen, setCommitMessageIsOpen] = useState(false);
     const [commitMessage, setCommitMessage] = useState('');
     const [currentRunner, setCurrentRunner] = useState('');
+    const [podName, setPodName] = useState(props.project.projectId + '-runner');
+    const [isJbangRunning, setJbangIsRunning] = useState(false);
+    const [isRunning, setIsRunning] = useState(false);
+    const [isDeletingPod, setIsDeletingPod] = useState(false);
+    const [isReloadingPod, setIsReloadingPod] = useState(false);
 
     useEffect(() => {
         const sub1 = ProjectEventBus.onCurrentRunner()?.subscribe((result) => {
             setCurrentRunner(result || '');
+            setJbangIsRunning(result === props.project.name);
         });
         return () => {
             sub1.unsubscribe();
         };
     });
 
+    function jbangRun() {
+        setJbangIsRunning(true);
+        KaravanApi.runProject(props.project, res => {
+            if (res.status === 200 || res.status === 201) {
+                ProjectEventBus.setCurrentRunner(props.project.name);
+                setJbangIsRunning(false);
+                setPodName(res.data);
+                ProjectEventBus.showLog('container', res.data, props.config.environment)
+            } else {
+                // Todo notification
+                setJbangIsRunning(false);
+                ProjectEventBus.setCurrentRunner(undefined);
+            }
+        });
+    }
+
+    function reloadRunner() {
+        setIsReloadingPod(true);
+        KaravanApi.getRunnerReload(props.project.projectId, res => {
+            if (res.status === 200 || res.status === 201) {
+                setIsReloadingPod(false);
+            } else {
+                // Todo notification
+                setIsReloadingPod(false);
+            }
+        });
+    }
+
+    function deleteRunner() {
+        ProjectEventBus.setCurrentRunner(undefined);
+        setIsDeletingPod(true);
+        KaravanApi.deleteRunner(podName, false, res => {
+            if (res.status === 202) {
+                setIsDeletingPod(false);
+            } else {
+                // Todo notification
+                setIsDeletingPod(false);
+            }
+        });
+    }
+
     function push () {
         setIsPushing(true);
         setCommitMessageIsOpen(false);
diff --git a/karavan-app/src/main/webui/src/projects/PropertiesEditor.tsx b/karavan-app/src/main/webui/src/project/PropertiesEditor.tsx
similarity index 96%
rename from karavan-app/src/main/webui/src/projects/PropertiesEditor.tsx
rename to karavan-app/src/main/webui/src/project/PropertiesEditor.tsx
index 28cbcf40..bf2f4609 100644
--- a/karavan-app/src/main/webui/src/projects/PropertiesEditor.tsx
+++ b/karavan-app/src/main/webui/src/project/PropertiesEditor.tsx
@@ -3,10 +3,10 @@ import {
     PageSection,
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
-import {ProjectFile} from "./ProjectModels";
 import {PropertiesTable} from "./PropertiesTable";
 import {ProjectModel} from "karavan-core/lib/model/ProjectModel";
 import {ProjectModelApi} from "karavan-core/lib/api/ProjectModelApi";
+import {ProjectFile} from "../api/ProjectModels";
 
 interface Props {
     file: ProjectFile,
diff --git a/karavan-app/src/main/webui/src/projects/PropertiesTable.tsx b/karavan-app/src/main/webui/src/project/PropertiesTable.tsx
similarity index 100%
rename from karavan-app/src/main/webui/src/projects/PropertiesTable.tsx
rename to karavan-app/src/main/webui/src/project/PropertiesTable.tsx
diff --git a/karavan-app/src/main/webui/src/projects/RunnerToolbar.tsx b/karavan-app/src/main/webui/src/project/RunnerToolbar.tsx
similarity index 83%
rename from karavan-app/src/main/webui/src/projects/RunnerToolbar.tsx
rename to karavan-app/src/main/webui/src/project/RunnerToolbar.tsx
index 9b124372..4b63bf2b 100644
--- a/karavan-app/src/main/webui/src/projects/RunnerToolbar.tsx
+++ b/karavan-app/src/main/webui/src/project/RunnerToolbar.tsx
@@ -1,17 +1,16 @@
 import React, {useEffect, useState} from 'react';
 import {
-    Button, Label, Switch, Tab, Tabs,
+    Button,
     Tooltip,
     TooltipPosition
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
-import {Project} from "./ProjectModels";
 import RocketIcon from "@patternfly/react-icons/dist/esm/icons/rocket-icon";
 import ReloadIcon from "@patternfly/react-icons/dist/esm/icons/bolt-icon";
-import TraceIcon from "@patternfly/react-icons/dist/esm/icons/list-icon";
 import DeleteIcon from "@patternfly/react-icons/dist/esm/icons/times-circle-icon";
 import {KaravanApi} from "../api/KaravanApi";
-import {ProjectEventBus} from "./ProjectEventBus";
+import {Project} from "../api/ProjectModels";
+import {ProjectEventBus} from "../api/ProjectEventBus";
 
 
 interface Props {
@@ -28,7 +27,6 @@ export const RunnerToolbar = (props: Props) => {
     const [isRunning, setIsRunning] = useState(false);
     const [isDeletingPod, setIsDeletingPod] = useState(false);
     const [isReloadingPod, setIsReloadingPod] = useState(false);
-    const [isShowingTrace, setIsShowingTrace] = useState(false);
 
     useEffect(() => {
         const sub1 = ProjectEventBus.onCurrentRunner()?.subscribe((result) => {
@@ -80,11 +78,6 @@ export const RunnerToolbar = (props: Props) => {
         });
     }
 
-    function showTrace() {
-        ProjectEventBus.showTrace(props.project.projectId, !isShowingTrace);
-        setIsShowingTrace((prevState) => !prevState);
-    }
-
     return (
             <div className="runner-toolbar">
                 {!props.showConsole && !props.reloadOnly  &&
@@ -127,17 +120,6 @@ export const RunnerToolbar = (props: Props) => {
                             </Button>
                         </Tooltip>
                     </div>
-                    <div className="row">
-                        <Tooltip content={isShowingTrace ? "Show runtime" : "Show trace"} position={TooltipPosition.left}>
-                            <Button isSmall
-                                    variant={"secondary"}
-                                    className="project-button"
-                                    icon={ <TraceIcon/>}
-                                    onClick={() => showTrace()}>
-                                {isShowingTrace ? "Runtime" : "Trace"}
-                            </Button>
-                        </Tooltip>
-                    </div>
                     <Tooltip content="Stop runner" position={TooltipPosition.left}>
                         <Button isLoading={isDeletingPod ? true : undefined}
                                 isSmall
diff --git a/karavan-app/src/main/webui/src/projects/tabs/DashboardTab.tsx b/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx
similarity index 95%
rename from karavan-app/src/main/webui/src/projects/tabs/DashboardTab.tsx
rename to karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx
index a31954f2..7eddf0c6 100644
--- a/karavan-app/src/main/webui/src/projects/tabs/DashboardTab.tsx
+++ b/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx
@@ -4,13 +4,13 @@ import {
     CardBody, Flex, FlexItem, Divider, PageSection
 } from '@patternfly/react-core';
 import '../../designer/karavan.css';
-import {PodStatus} from "../ProjectModels";
 import {RunnerInfoPod} from "./RunnerInfoPod";
 import {RunnerInfoContext} from "./RunnerInfoContext";
 import {RunnerInfoMemory} from "./RunnerInfoMemory";
 import {KaravanApi} from "../../api/KaravanApi";
-import {ProjectEventBus} from "../ProjectEventBus";
-import {useProjectStore} from "../ProjectStore";
+import {PodStatus} from "../../api/ProjectModels";
+import {useProjectStore} from "../../api/ProjectStore";
+import {ProjectEventBus} from "../../api/ProjectEventBus";
 
 export function isRunning(status: PodStatus): boolean {
     return status.phase === 'Running' && !status.terminating;
diff --git a/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoContext.tsx b/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoContext.tsx
similarity index 100%
rename from karavan-app/src/main/webui/src/projects/tabs/RunnerInfoContext.tsx
rename to karavan-app/src/main/webui/src/project/dashboard/RunnerInfoContext.tsx
diff --git a/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoMemory.tsx b/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoMemory.tsx
similarity index 98%
rename from karavan-app/src/main/webui/src/projects/tabs/RunnerInfoMemory.tsx
rename to karavan-app/src/main/webui/src/project/dashboard/RunnerInfoMemory.tsx
index f2eb27ce..15c37bd0 100644
--- a/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoMemory.tsx
+++ b/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoMemory.tsx
@@ -1,6 +1,5 @@
-import React, {useEffect, useRef, useState} from 'react';
+import React from 'react';
 import {
-    Button,
     DescriptionList,
     DescriptionListDescription,
     DescriptionListGroup,
diff --git a/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoPod.tsx b/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoPod.tsx
similarity index 97%
rename from karavan-app/src/main/webui/src/projects/tabs/RunnerInfoPod.tsx
rename to karavan-app/src/main/webui/src/project/dashboard/RunnerInfoPod.tsx
index 0c31d40c..c55ae090 100644
--- a/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoPod.tsx
+++ b/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoPod.tsx
@@ -9,10 +9,10 @@ import {
     Tooltip
 } from '@patternfly/react-core';
 import '../../designer/karavan.css';
-import {PodStatus} from "../ProjectModels";
-import {ProjectEventBus} from "../ProjectEventBus";
 import DownIcon from "@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
 import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
+import {PodStatus} from "../../api/ProjectModels";
+import {ProjectEventBus} from "../../api/ProjectEventBus";
 
 
 export function isRunning(status: PodStatus): boolean {
diff --git a/karavan-app/src/main/webui/src/projects/tabs/ProjectFilesTab.tsx b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
similarity index 85%
rename from karavan-app/src/main/webui/src/projects/tabs/ProjectFilesTab.tsx
rename to karavan-app/src/main/webui/src/project/files/FilesTab.tsx
index aafc1bc5..feb57616 100644
--- a/karavan-app/src/main/webui/src/projects/tabs/ProjectFilesTab.tsx
+++ b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
@@ -6,17 +6,17 @@ import {
     EmptyState,
     EmptyStateVariant,
     EmptyStateIcon,
-    Title, PageSection,
+    Title, PageSection, PanelHeader, Flex, FlexItem, Panel,
 } from '@patternfly/react-core';
 import '../../designer/karavan.css';
-import {getProjectFileType} from "../ProjectModels";
 import {TableComposable, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
 import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
 import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
-import {useFilesStore, useFileStore, useProjectStore} from "../ProjectStore";
+import {useFilesStore, useFileStore} from "../../api/ProjectStore";
+import {getProjectFileType} from "../../api/ProjectModels";
 
 
-export const ProjectFilesTab = () => {
+export const FilesTab = () => {
 
     const {files} = useFilesStore();
 
@@ -30,6 +30,18 @@ export const ProjectFilesTab = () => {
     }
     return (
         <PageSection className="project-bottom" padding={{default: "padding"}}>
+            <Panel>
+                <PanelHeader>
+                    <Flex direction={{default: "row"}} justifyContent={{default:"justifyContentFlexEnd"}}>
+                        <FlexItem>
+
+                        </FlexItem>
+                        <FlexItem>
+
+                        </FlexItem>
+                    </Flex>
+                </PanelHeader>
+            </Panel>
             <TableComposable aria-label="Files" variant={"compact"} className={"table"}>
                 <Thead>
                     <Tr>
diff --git a/karavan-app/src/main/webui/src/projects/UploadModal.tsx b/karavan-app/src/main/webui/src/project/files/UploadModal.tsx
similarity index 98%
rename from karavan-app/src/main/webui/src/projects/UploadModal.tsx
rename to karavan-app/src/main/webui/src/project/files/UploadModal.tsx
index fae83f8b..a2af3243 100644
--- a/karavan-app/src/main/webui/src/projects/UploadModal.tsx
+++ b/karavan-app/src/main/webui/src/project/files/UploadModal.tsx
@@ -4,8 +4,8 @@ import {
     Button, Modal, FormGroup, ModalVariant, Switch, Form, FileUpload, Radio
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
-import {KaravanApi} from "../api/KaravanApi";
-import {ProjectFile} from "./ProjectModels";
+import {ProjectFile} from "../../api/ProjectModels";
+import {KaravanApi} from "../../api/KaravanApi";
 
 interface Props {
     projectId: string,
diff --git a/karavan-app/src/main/webui/src/projects/tabs/ProjectPipelineTab.tsx b/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
similarity index 95%
rename from karavan-app/src/main/webui/src/projects/tabs/ProjectPipelineTab.tsx
rename to karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
index 7eb5ca69..54cd6f99 100644
--- a/karavan-app/src/main/webui/src/projects/tabs/ProjectPipelineTab.tsx
+++ b/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
@@ -1,8 +1,8 @@
 import React from 'react';
 import '../../designer/karavan.css';
-import {Project} from "../ProjectModels";
 import {ProjectStatus} from "../ProjectStatus";
 import {PageSection} from "@patternfly/react-core";
+import {Project} from "../../api/ProjectModels";
 
 
 interface Props {
diff --git a/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoTrace.tsx b/karavan-app/src/main/webui/src/project/trace/RunnerInfoTrace.tsx
similarity index 98%
rename from karavan-app/src/main/webui/src/projects/tabs/RunnerInfoTrace.tsx
rename to karavan-app/src/main/webui/src/project/trace/RunnerInfoTrace.tsx
index a168b89e..8f0f655d 100644
--- a/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoTrace.tsx
+++ b/karavan-app/src/main/webui/src/project/trace/RunnerInfoTrace.tsx
@@ -9,11 +9,9 @@ import {
     PanelHeader,
     Text,
     Switch, TextContent, TextVariants, Title,
-    Tooltip,
-    TooltipPosition
 } from '@patternfly/react-core';
 import '../../designer/karavan.css';
-import {ProjectEventBus} from "../ProjectEventBus";
+import {ProjectEventBus} from "../../api/ProjectEventBus";
 import {RunnerInfoTraceModal} from "./RunnerInfoTraceModal";
 import {TableComposable, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
 import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon";
diff --git a/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoTraceModal.tsx b/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceModal.tsx
similarity index 100%
rename from karavan-app/src/main/webui/src/projects/tabs/RunnerInfoTraceModal.tsx
rename to karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceModal.tsx
diff --git a/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoTraceNode.tsx b/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceNode.tsx
similarity index 100%
rename from karavan-app/src/main/webui/src/projects/tabs/RunnerInfoTraceNode.tsx
rename to karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceNode.tsx
diff --git a/karavan-app/src/main/webui/src/projects/tabs/TraceTab.tsx b/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx
similarity index 89%
rename from karavan-app/src/main/webui/src/projects/tabs/TraceTab.tsx
rename to karavan-app/src/main/webui/src/project/trace/TraceTab.tsx
index 46c26403..144c2b00 100644
--- a/karavan-app/src/main/webui/src/projects/tabs/TraceTab.tsx
+++ b/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx
@@ -1,14 +1,12 @@
 import React, {useEffect, useState} from 'react';
-import {
-    Card,
-    CardBody, PageSection
+import { PageSection
 } from '@patternfly/react-core';
 import '../../designer/karavan.css';
-import {PodStatus} from "../ProjectModels";
+import {PodStatus} from "../../api/ProjectModels";
 import {KaravanApi} from "../../api/KaravanApi";
-import {ProjectEventBus} from "../ProjectEventBus";
+import {ProjectEventBus} from "../../api/ProjectEventBus";
 import {RunnerInfoTrace} from "./RunnerInfoTrace";
-import {useProjectStore} from "../ProjectStore";
+import {useProjectStore} from "../../api/ProjectStore";
 
 export function isRunning(status: PodStatus): boolean {
     return status.phase === 'Running' && !status.terminating;
diff --git a/karavan-app/src/main/webui/src/projects/modal/CreateProjectModal.tsx b/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx
similarity index 88%
rename from karavan-app/src/main/webui/src/projects/modal/CreateProjectModal.tsx
rename to karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx
index 6312c117..19fa2cb5 100644
--- a/karavan-app/src/main/webui/src/projects/modal/CreateProjectModal.tsx
+++ b/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx
@@ -1,17 +1,16 @@
-import React, {useEffect, useState} from 'react';
+import React, {useState} from 'react';
 import {
     Button, Form, FormGroup,
     Modal,
     ModalVariant, Radio, TextInput,
 } from '@patternfly/react-core';
-import '../../designer/karavan.css';
-import {useProjectStore} from "../ProjectStore";
-import {ProjectLogic} from "../ProjectLogic";
-import {QuarkusIcon, SpringIcon} from "../../designer/utils/KaravanIcons";
+import '../designer/karavan.css';
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
-import {CamelUi} from "../../designer/utils/CamelUi";
-import {Project} from "../ProjectModels";
-import {ProjectEventBus} from "../ProjectEventBus";
+import {useProjectStore} from "../api/ProjectStore";
+import {ProjectService} from "../api/ProjectService";
+import {Project} from "../api/ProjectModels";
+import {QuarkusIcon, SpringIcon} from "../designer/utils/KaravanIcons";
+import {CamelUi} from "../designer/utils/CamelUi";
 
 interface Props {
     config: any,
@@ -38,7 +37,7 @@ export const CreateProjectModal = (props: Props) => {
     }
 
     function confirmAndCloseModal () {
-        ProjectLogic.createProject(new Project({name: name, description: description, runtime: runtime, projectId: projectId}));
+        ProjectService.createProject(new Project({name: name, description: description, runtime: runtime, projectId: projectId}));
         useProjectStore.setState({operation: "none"});
         cleanValues();
     }
diff --git a/karavan-app/src/main/webui/src/projects/modal/DeleteProjectModal.tsx b/karavan-app/src/main/webui/src/projects/DeleteProjectModal.tsx
similarity index 91%
rename from karavan-app/src/main/webui/src/projects/modal/DeleteProjectModal.tsx
rename to karavan-app/src/main/webui/src/projects/DeleteProjectModal.tsx
index ad07326c..5ae29dd4 100644
--- a/karavan-app/src/main/webui/src/projects/modal/DeleteProjectModal.tsx
+++ b/karavan-app/src/main/webui/src/projects/DeleteProjectModal.tsx
@@ -4,9 +4,9 @@ import {
     Modal,
     ModalVariant,
 } from '@patternfly/react-core';
-import '../../designer/karavan.css';
-import {useProjectStore} from "../ProjectStore";
-import {ProjectLogic} from "../ProjectLogic";
+import '../designer/karavan.css';
+import {useProjectStore} from "../api/ProjectStore";
+import {ProjectService} from "../api/ProjectService";
 
 export const DeleteProjectModal = () => {
 
@@ -17,7 +17,7 @@ export const DeleteProjectModal = () => {
     }
 
     function confirmAndCloseModal () {
-        ProjectLogic.deleteProject(project);
+        ProjectService.deleteProject(project);
         useProjectStore.setState({operation: "none"});
     }
 
diff --git a/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx b/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
index ff1423cc..dec4ea29 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
@@ -15,18 +15,17 @@ import {
     Title, Spinner
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
-import {MainToolbar} from "../MainToolbar";
 import RefreshIcon from '@patternfly/react-icons/dist/esm/icons/sync-alt-icon';
 import PlusIcon from '@patternfly/react-icons/dist/esm/icons/plus-icon';
-import {Project} from "./ProjectModels";
 import {TableComposable, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
 import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
 import {ProjectsTableRow} from "./ProjectsTableRow";
-import {useProjectStore} from "./ProjectStore";
-import {ProjectLogic} from "./ProjectLogic";
-import {useProjectsStore} from "./ProjectStore";
-import {DeleteProjectModal} from "./modal/DeleteProjectModal";
-import {CreateProjectModal} from "./modal/CreateProjectModal";
+import {DeleteProjectModal} from "./DeleteProjectModal";
+import {CreateProjectModal} from "./CreateProjectModal";
+import {useProjectsStore, useProjectStore} from "../api/ProjectStore";
+import {ProjectService} from "../api/ProjectService";
+import {MainToolbar} from "../common/MainToolbar";
+import {Project} from "../api/ProjectModels";
 
 
 interface Props {
@@ -44,7 +43,7 @@ export const ProjectsPage = (props: Props) => {
     useEffect(() => {
         const interval = setInterval(() => {
             if (projects.length === 0) setLoading(true);
-            if (!["create", "delete", "select", "copy"].includes(operation)) ProjectLogic.refreshProjects();
+            if (!["create", "delete", "select", "copy"].includes(operation)) ProjectService.refreshProjects();
         }, 1300);
         return () => {
             clearInterval(interval)
@@ -56,7 +55,7 @@ export const ProjectsPage = (props: Props) => {
             <ToolbarContent>
                 <ToolbarItem>
                     <Button variant="link" icon={<RefreshIcon/>} onClick={e =>
-                        ProjectLogic.refreshProjects()}/>
+                        ProjectService.refreshProjects()}/>
                 </ToolbarItem>
                 <ToolbarItem>
                     <TextInput className="text-field" type="search" id="search" name="search"
diff --git a/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx b/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
index 76d264a3..9ce6b07e 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
@@ -1,45 +1,17 @@
 import React from 'react';
 import {
-    Toolbar,
-    ToolbarContent,
-    ToolbarItem,
-    TextInput,
-    PageSection,
-    TextContent,
-    Text,
     Button,
-    Modal,
-    FormGroup,
-    ModalVariant,
-    Form,
     Badge,
     Tooltip,
-    Bullseye,
-    EmptyState,
-    EmptyStateVariant,
-    EmptyStateIcon,
-    Title,
-    OverflowMenu,
-    OverflowMenuContent,
-    OverflowMenuGroup,
-    OverflowMenuItem,
-    Flex, FlexItem, Radio, Spinner
+    Flex, FlexItem
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
-import {MainToolbar} from "../MainToolbar";
-import RefreshIcon from '@patternfly/react-icons/dist/esm/icons/sync-alt-icon';
-import PlusIcon from '@patternfly/react-icons/dist/esm/icons/plus-icon';
-import {DeploymentStatus, Project} from "./ProjectModels";
-import {TableComposable, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
+import { Td, Tr} from "@patternfly/react-table";
 import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
-import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
 import CopyIcon from "@patternfly/react-icons/dist/esm/icons/copy-icon";
-import {CamelUi} from "../designer/utils/CamelUi";
-import {KaravanApi} from "../api/KaravanApi";
-import {QuarkusIcon, SpringIcon} from "../designer/utils/KaravanIcons";
-import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
-import {ProjectEventBus} from "./ProjectEventBus";
-import {useDeploymentStatusesStore, useProjectsStore, useProjectStore} from "./ProjectStore";
+import {DeploymentStatus, Project} from '../api/ProjectModels';
+import {useDeploymentStatusesStore, useProjectStore} from "../api/ProjectStore";
+import {ProjectEventBus} from "../api/ProjectEventBus";
 
 interface Props {
     config: any,


[camel-karavan] 06/06: Fix #810

Posted by ma...@apache.org.
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 020e4d20d64008302cba089677a98cd2f150cec4
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Sat Jul 1 13:20:44 2023 -0400

    Fix #810
---
 karavan-app/pom.xml                                |  2 +-
 .../pipelines/karavan-task-dev-quarkus.yaml        |  2 +-
 .../pipelines/karavan-task-dev-spring-boot.yaml    |  2 +-
 .../spring-boot-kubernetes-application.properties  |  4 +-
 .../spring-boot-openshift-application.properties   |  4 +-
 karavan-builder/Dockerfile                         |  2 +-
 karavan-cloud/AWS/karavan-quarkus-task-aws.yaml    |  2 +-
 .../AWS/karavan-spring-boot-task-aws.yaml          |  2 +-
 karavan-core/src/core/api/CamelDefinitionApi.ts    |  4 +-
 .../src/core/api/CamelDefinitionYamlStep.ts        |  4 +-
 karavan-core/src/core/model/CamelDefinition.ts     |  9 +++-
 karavan-core/src/core/model/CamelMetadata.ts       | 50 ++++++++++++----------
 karavan-demo/jms-to-kafka/docs/README.md           |  2 +-
 karavan-generator/pom.xml                          |  2 +-
 .../resources/quarkus-builder-script-kubernetes.sh |  2 +-
 .../resources/quarkus-builder-script-openshift.sh  |  2 +-
 .../spring-boot-builder-script-kubernetes.sh       |  2 +-
 .../spring-boot-builder-script-openshift.sh        |  2 +-
 karavan-runner/Dockerfile                          |  2 +-
 karavan-vscode/CHANGELOG.md                        |  4 +-
 karavan-vscode/README.md                           |  4 +-
 karavan-vscode/package.json                        |  6 +--
 22 files changed, 63 insertions(+), 52 deletions(-)

diff --git a/karavan-app/pom.xml b/karavan-app/pom.xml
index 2a5da14c..6f21699e 100644
--- a/karavan-app/pom.xml
+++ b/karavan-app/pom.xml
@@ -31,7 +31,7 @@
         <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
         <quarkus.platform.version>2.16.7.Final</quarkus.platform.version>
         <camel-quarkus.version>2.16.7</camel-quarkus.version>
-        <camel.version>3.20.6</camel.version>
+        <camel.version>3.21.0</camel.version>
         <surefire-plugin.version>3.0.0-M5</surefire-plugin.version>
         <infinispan.version>14.0.6.Final</infinispan.version>
         <tekton.version>6.3.1</tekton.version>
diff --git a/karavan-app/src/main/resources/pipelines/karavan-task-dev-quarkus.yaml b/karavan-app/src/main/resources/pipelines/karavan-task-dev-quarkus.yaml
index e7a89556..19940ac7 100644
--- a/karavan-app/src/main/resources/pipelines/karavan-task-dev-quarkus.yaml
+++ b/karavan-app/src/main/resources/pipelines/karavan-task-dev-quarkus.yaml
@@ -54,7 +54,7 @@ spec:
 
         cd ${CHECKOUT_DIR}/$(inputs.params.project)
 
-        entrypoint -Dcamel.jbang.version=3.20.6 camel@apache/camel export
+        entrypoint -Dcamel.jbang.version=3.21.0 camel@apache/camel export
         --local-kamelet-dir=${KAMELETS_DIR}
 
         export LAST_COMMIT=$(git rev-parse --short HEAD)
diff --git a/karavan-app/src/main/resources/pipelines/karavan-task-dev-spring-boot.yaml b/karavan-app/src/main/resources/pipelines/karavan-task-dev-spring-boot.yaml
index 1c96d652..cf57e23e 100644
--- a/karavan-app/src/main/resources/pipelines/karavan-task-dev-spring-boot.yaml
+++ b/karavan-app/src/main/resources/pipelines/karavan-task-dev-spring-boot.yaml
@@ -54,7 +54,7 @@ spec:
 
         cd ${CHECKOUT_DIR}/$(inputs.params.project)
 
-        entrypoint -Dcamel.jbang.version=3.20.6 camel@apache/camel export
+        entrypoint -Dcamel.jbang.version=3.21.0 camel@apache/camel export
         --local-kamelet-dir=${KAMELETS_DIR}
 
         export LAST_COMMIT=$(git rev-parse --short HEAD)
diff --git a/karavan-app/src/main/resources/snippets/spring-boot-kubernetes-application.properties b/karavan-app/src/main/resources/snippets/spring-boot-kubernetes-application.properties
index 1669574d..352f7d97 100644
--- a/karavan-app/src/main/resources/snippets/spring-boot-kubernetes-application.properties
+++ b/karavan-app/src/main/resources/snippets/spring-boot-kubernetes-application.properties
@@ -3,8 +3,8 @@ camel.karavan.project-name={projectName}
 camel.karavan.project-description={projectDescription}
 camel.jbang.gav=org.camel.karavan.demo:{projectId}:1
 camel.jbang.runtime=spring-boot
-camel.jbang.camelSpringBootVersion=3.20.6
-camel.jbang.springBootVersion=2.7.11
+camel.jbang.camelSpringBootVersion=3.21.0
+camel.jbang.springBootVersion=2.7.13
 camel.jbang.dependencies=camel:microprofile-health
 camel.health.enabled=true
 camel.health.exposure-level=full
diff --git a/karavan-app/src/main/resources/snippets/spring-boot-openshift-application.properties b/karavan-app/src/main/resources/snippets/spring-boot-openshift-application.properties
index 3788ab80..a8026fbe 100644
--- a/karavan-app/src/main/resources/snippets/spring-boot-openshift-application.properties
+++ b/karavan-app/src/main/resources/snippets/spring-boot-openshift-application.properties
@@ -3,8 +3,8 @@ camel.karavan.project-name={projectName}
 camel.karavan.project-description={projectDescription}
 camel.jbang.gav=org.camel.karavan.demo:{projectId}:1
 camel.jbang.runtime=spring-boot
-camel.jbang.camelSpringBootVersion=3.20.6
-camel.jbang.springBootVersion=2.7.11
+camel.jbang.camelSpringBootVersion=3.21.0
+camel.jbang.springBootVersion=2.7.13
 camel.jbang.dependencies=camel:microprofile-health
 camel.health.enabled=true
 camel.health.exposure-level=full
diff --git a/karavan-builder/Dockerfile b/karavan-builder/Dockerfile
index 2ba9bdcb..75c605d7 100644
--- a/karavan-builder/Dockerfile
+++ b/karavan-builder/Dockerfile
@@ -1,6 +1,6 @@
 FROM jbangdev/jbang-action:0.106.1
 
-ENV CAMEL_VERSION=3.20.6
+ENV CAMEL_VERSION=3.21.0
 ENV MAVEN_SETTINGS="/karavan-config-map/maven-settings.xml"
 
 # Add Camel-JBang
diff --git a/karavan-cloud/AWS/karavan-quarkus-task-aws.yaml b/karavan-cloud/AWS/karavan-quarkus-task-aws.yaml
index 2749dc1b..3b756680 100644
--- a/karavan-cloud/AWS/karavan-quarkus-task-aws.yaml
+++ b/karavan-cloud/AWS/karavan-quarkus-task-aws.yaml
@@ -79,7 +79,7 @@ spec:
         
         cd ${CHECKOUT_DIR}/$(inputs.params.project)
         
-        entrypoint -Dcamel.jbang.version=3.20.6 camel@apache/camel export --local-kamelet-dir=${KAMELETS_DIR}
+        entrypoint -Dcamel.jbang.version=3.21.0 camel@apache/camel export --local-kamelet-dir=${KAMELETS_DIR}
         
         export LAST_COMMIT=$(git rev-parse --short HEAD)
         export DATE=$(date '+%Y%m%d%H%M%S')
diff --git a/karavan-cloud/AWS/karavan-spring-boot-task-aws.yaml b/karavan-cloud/AWS/karavan-spring-boot-task-aws.yaml
index 63f64171..d9696c79 100644
--- a/karavan-cloud/AWS/karavan-spring-boot-task-aws.yaml
+++ b/karavan-cloud/AWS/karavan-spring-boot-task-aws.yaml
@@ -79,7 +79,7 @@ spec:
 
           cd ${CHECKOUT_DIR}/$(inputs.params.project)
 
-          entrypoint -Dcamel.jbang.version=3.20.6 camel@apache/camel export --local-kamelet-dir=${KAMELETS_DIR}
+          entrypoint -Dcamel.jbang.version=3.21.0 camel@apache/camel export --local-kamelet-dir=${KAMELETS_DIR}
 
           export LAST_COMMIT=$(git rev-parse --short HEAD)
           export DATE=$(date '+%Y%m%d%H%M%S')
diff --git a/karavan-core/src/core/api/CamelDefinitionApi.ts b/karavan-core/src/core/api/CamelDefinitionApi.ts
index 446aeb60..bf765304 100644
--- a/karavan-core/src/core/api/CamelDefinitionApi.ts
+++ b/karavan-core/src/core/api/CamelDefinitionApi.ts
@@ -1267,7 +1267,7 @@ export class CamelDefinitionApi {
     static createRouteTemplateDefinition = (element: any): RouteTemplateDefinition => { 
         const def = element ? new RouteTemplateDefinition({...element}) : new RouteTemplateDefinition();
         def.uuid = element?.uuid ? element.uuid : def.uuid; 
-        def.beans = element && element?.beans ? element?.beans.map((x:any) => CamelDefinitionApi.createNamedBeanDefinition(x)) :[];
+        def.beans = element && element?.beans ? element?.beans.map((x:any) => CamelDefinitionApi.createRouteTemplateBeanDefinition(x)) :[];
         if (element?.from !== undefined) { 
             def.from = CamelDefinitionApi.createFromDefinition(element.from); 
         }
@@ -1396,7 +1396,7 @@ export class CamelDefinitionApi {
     static createTemplatedRouteDefinition = (element: any): TemplatedRouteDefinition => { 
         const def = element ? new TemplatedRouteDefinition({...element}) : new TemplatedRouteDefinition();
         def.uuid = element?.uuid ? element.uuid : def.uuid; 
-        def.beans = element && element?.beans ? element?.beans.map((x:any) => CamelDefinitionApi.createNamedBeanDefinition(x)) :[];
+        def.beans = element && element?.beans ? element?.beans.map((x:any) => CamelDefinitionApi.createTemplatedRouteBeanDefinition(x)) :[];
         def.parameters = element && element?.parameters ? element?.parameters.map((x:any) => CamelDefinitionApi.createTemplatedRouteParameterDefinition(x)) :[];
         return def;
     }
diff --git a/karavan-core/src/core/api/CamelDefinitionYamlStep.ts b/karavan-core/src/core/api/CamelDefinitionYamlStep.ts
index c31272f9..fe953d0c 100644
--- a/karavan-core/src/core/api/CamelDefinitionYamlStep.ts
+++ b/karavan-core/src/core/api/CamelDefinitionYamlStep.ts
@@ -2063,7 +2063,7 @@ export class CamelDefinitionYamlStep {
                def.route = CamelDefinitionYamlStep.readRouteDefinition(element.route); 
             } 
         } 
-        def.beans = element && element?.beans ? element?.beans.map((x:any) => CamelDefinitionYamlStep.readNamedBeanDefinition(x)) :[]; 
+        def.beans = element && element?.beans ? element?.beans.map((x:any) => CamelDefinitionYamlStep.readRouteTemplateBeanDefinition(x)) :[]; 
         if (element?.from !== undefined) { 
             if (Array.isArray(element.from)) { 
                def.from = CamelDefinitionYamlStep.readFromDefinition(element.from[0]); 
@@ -2267,7 +2267,7 @@ export class CamelDefinitionYamlStep {
     static readTemplatedRouteDefinition = (element: any): TemplatedRouteDefinition => {
         
         let def = element ? new TemplatedRouteDefinition({...element}) : new TemplatedRouteDefinition();
-        def.beans = element && element?.beans ? element?.beans.map((x:any) => CamelDefinitionYamlStep.readNamedBeanDefinition(x)) :[]; 
+        def.beans = element && element?.beans ? element?.beans.map((x:any) => CamelDefinitionYamlStep.readTemplatedRouteBeanDefinition(x)) :[]; 
         def.parameters = element && element?.parameters ? element?.parameters.map((x:any) => CamelDefinitionYamlStep.readTemplatedRouteParameterDefinition(x)) :[]; 
 
         return def;
diff --git a/karavan-core/src/core/model/CamelDefinition.ts b/karavan-core/src/core/model/CamelDefinition.ts
index e27de264..13875680 100644
--- a/karavan-core/src/core/model/CamelDefinition.ts
+++ b/karavan-core/src/core/model/CamelDefinition.ts
@@ -692,6 +692,7 @@ export class MulticastDefinition extends CamelElement {
     aggregationStrategyMethodAllowNull?: boolean;
     parallelAggregate?: boolean;
     parallelProcessing?: boolean;
+    synchronous?: boolean;
     streaming?: boolean;
     stopOnException?: boolean;
     timeout?: string;
@@ -932,6 +933,7 @@ export class RecipientListDefinition extends CamelElement {
     aggregationStrategyMethodAllowNull?: boolean;
     parallelAggregate?: boolean;
     parallelProcessing?: boolean;
+    synchronous?: boolean;
     timeout?: string;
     executorService?: string;
     stopOnException?: boolean;
@@ -1197,7 +1199,7 @@ export class RouteTemplateDefinition extends CamelElement {
     stepName?: string = 'routeTemplate';
     route?: RouteDefinition;
     id: string = 'routeTemplate-' + uuidv4().substring(0,4);
-    beans?: NamedBeanDefinition[] = [];
+    beans?: RouteTemplateBeanDefinition[] = [];
     from?: FromDefinition;
     parameters?: RouteTemplateParameterDefinition[] = [];
     public constructor(init?: Partial<RouteTemplateDefinition>) {
@@ -1372,6 +1374,7 @@ export class SplitDefinition extends CamelElement {
     aggregationStrategyMethodAllowNull?: boolean;
     parallelAggregate?: boolean;
     parallelProcessing?: boolean;
+    synchronous?: boolean;
     streaming?: boolean;
     stopOnException?: boolean;
     timeout?: string;
@@ -1433,7 +1436,7 @@ export class TemplatedRouteDefinition extends CamelElement {
     routeTemplateRef: string = '';
     routeId?: string;
     prefixId?: string;
-    beans?: NamedBeanDefinition[] = [];
+    beans?: TemplatedRouteBeanDefinition[] = [];
     parameters?: TemplatedRouteParameterDefinition[] = [];
     public constructor(init?: Partial<TemplatedRouteDefinition>) {
         super('TemplatedRouteDefinition');
@@ -2428,6 +2431,7 @@ export class JaxbDataFormat extends CamelElement {
     noNamespaceSchemaLocation?: string;
     jaxbProviderProperties?: string;
     contentTypeHeader?: boolean;
+    accessExternalSchemaProtocols?: string;
     id?: string = 'jaxb-' + uuidv4().substring(0,4);
     public constructor(init?: Partial<JaxbDataFormat>) {
         super('JaxbDataFormat');
@@ -2471,6 +2475,7 @@ export class JsonDataFormat extends CamelElement {
     autoDiscoverSchemaResolver?: boolean;
     namingStrategy?: string;
     contentTypeHeader?: boolean;
+    dateFormatPattern?: string;
     id?: string = 'json-' + uuidv4().substring(0,4);
     public constructor(init?: Partial<JsonDataFormat>) {
         super('JsonDataFormat');
diff --git a/karavan-core/src/core/model/CamelMetadata.ts b/karavan-core/src/core/model/CamelMetadata.ts
index 3670c612..20f0c884 100644
--- a/karavan-core/src/core/model/CamelMetadata.ts
+++ b/karavan-core/src/core/model/CamelMetadata.ts
@@ -195,7 +195,7 @@ export const CamelDataFormatMetadata: ElementMeta[] = [
     ]),
     new ElementMeta('avro', 'AvroDataFormat', 'Avro', "Serialize and deserialize messages using Apache Avro binary data format.", 'dataformat,transformation', [
         new PropertyMeta('instanceClassName', 'Instance Class Name', "Class name to use for marshal and unmarshalling", 'string', '', '', false, false, false, false, '', ''),
-        new PropertyMeta('library', 'Library', "Which Avro library to use.", 'string', 'apache-avro, jackson', 'ApacheAvro', false, false, false, false, '', ''),
+        new PropertyMeta('library', 'Library', "Which Avro library to use.", 'string', 'ApacheAvro, Jackson', 'ApacheAvro', false, false, false, false, '', ''),
         new PropertyMeta('objectMapper', 'Object Mapper', "Lookup and use the existing ObjectMapper with the given id when using Jackson.", 'string', '', '', false, false, false, false, 'advanced', ''),
         new PropertyMeta('useDefaultObjectMapper', 'Use Default Object Mapper', "Whether to lookup and use default Jackson ObjectMapper from the registry.", 'boolean', '', 'true', false, false, false, false, '', ''),
         new PropertyMeta('unmarshalType', 'Unmarshal Type', "Class name of the java type to use when unmarshalling", 'string', '', '', false, false, false, false, '', ''),
@@ -248,7 +248,7 @@ export const CamelDataFormatMetadata: ElementMeta[] = [
         new PropertyMeta('objectMapper', 'Object Mapper', "Lookup and use the existing ObjectMapper with the given id when using Jackson.", 'string', '', '', false, false, false, false, 'advanced', ''),
         new PropertyMeta('useDefaultObjectMapper', 'Use Default Object Mapper', "Whether to lookup and use default Jackson ObjectMapper from the registry.", 'boolean', '', 'true', false, false, false, false, '', ''),
         new PropertyMeta('autoDiscoverObjectMapper', 'Auto Discover Object Mapper', "If set to true then Jackson will lookup for an objectMapper into the registry", 'boolean', '', 'false', false, false, false, false, '', ''),
-        new PropertyMeta('library', 'Library', "Which Protobuf library to use.", 'string', 'google-protobuf, jackson', 'GoogleProtobuf', false, false, false, false, '', ''),
+        new PropertyMeta('library', 'Library', "Which Protobuf library to use.", 'string', 'GoogleProtobuf, Jackson', 'GoogleProtobuf', false, false, false, false, '', ''),
         new PropertyMeta('unmarshalType', 'Unmarshal Type', "Class name of the java type to use when unmarshalling", 'string', '', '', false, false, false, false, '', ''),
         new PropertyMeta('jsonView', 'Json View', "When marshalling a POJO to JSON you might want to exclude certain fields from the JSON output. With Jackson you can use JSON views to accomplish this. This option is to refer to the class which has JsonView annotations", 'string', '', '', false, false, false, false, '', ''),
         new PropertyMeta('include', 'Include', "If you want to marshal a pojo to JSON, and the pojo has some fields with null values. And you want to skip these null values, you can set this option to NON_NULL", 'string', '', '', false, false, false, false, '', ''),
@@ -295,7 +295,7 @@ export const CamelDataFormatMetadata: ElementMeta[] = [
         new PropertyMeta('useDefaultObjectMapper', 'Use Default Object Mapper', "Whether to lookup and use default Jackson ObjectMapper from the registry.", 'boolean', '', 'true', false, false, false, false, '', ''),
         new PropertyMeta('autoDiscoverObjectMapper', 'Auto Discover Object Mapper', "If set to true then Jackson will look for an objectMapper to use from the registry", 'boolean', '', 'false', false, false, false, false, '', ''),
         new PropertyMeta('prettyPrint', 'Pretty Print', "To enable pretty printing output nicely formatted. Is by default false.", 'boolean', '', 'false', false, false, false, false, '', ''),
-        new PropertyMeta('library', 'Library', "Which json library to use.", 'string', 'fastjson, gson, jackson, johnzon, jsonb, x-stream', 'Jackson', false, false, false, false, '', ''),
+        new PropertyMeta('library', 'Library', "Which json library to use.", 'string', 'Fastjson, Gson, Jackson, Johnzon, Jsonb, XStream', 'Jackson', false, false, false, false, '', ''),
         new PropertyMeta('unmarshalType', 'Unmarshal Type', "Class name of the java type to use when unmarshalling", 'string', '', '', false, false, false, false, '', ''),
         new PropertyMeta('jsonView', 'Json View', "When marshalling a POJO to JSON you might want to exclude certain fields from the JSON output. With Jackson you can use JSON views to accomplish this. This option is to refer to the class which has JsonView annotations", 'string', '', '', false, false, false, false, 'advanced', ''),
         new PropertyMeta('include', 'Include', "If you want to marshal a pojo to JSON, and the pojo has some fields with null values. And you want to skip these null values, you can set this option to NON_NULL", 'string', '', '', false, false, false, false, 'advanced', ''),
@@ -314,6 +314,7 @@ export const CamelDataFormatMetadata: ElementMeta[] = [
         new PropertyMeta('autoDiscoverSchemaResolver', 'Auto Discover Schema Resolver', "When not disabled, the SchemaResolver will be looked up into the registry", 'boolean', '', 'true', false, false, false, false, 'advanced', ''),
         new PropertyMeta('namingStrategy', 'Naming Strategy', "If set then Jackson will use the the defined Property Naming Strategy.Possible values are: LOWER_CAMEL_CASE, LOWER_DOT_CASE, LOWER_CASE, KEBAB_CASE, SNAKE_CASE and UPPER_CAMEL_CASE", 'string', '', '', false, false, false, false, '', ''),
         new PropertyMeta('contentTypeHeader', 'Content Type Header', "Whether the data format should set the Content-Type header with the type from the data format. For example application/xml for data formats marshalling to XML, or application/json for data formats marshalling to JSON", 'boolean', '', 'true', false, false, false, false, '', ''),
+        new PropertyMeta('dateFormatPattern', 'Date Format Pattern', "To configure the date format while marshall or unmarshall Date fields in JSON using Gson", 'string', '', '', false, false, false, false, '', ''),
         new PropertyMeta('id', 'Id', "The id of this node", 'string', '', '', false, false, false, false, '', ''),
     ]),
     new ElementMeta('mimeMultipart', 'MimeMultipartDataFormat', 'MIME Multipart', "Marshal Camel messages with attachments into MIME-Multipart messages and back.", 'dataformat,transformation', [
@@ -379,7 +380,7 @@ export const CamelDataFormatMetadata: ElementMeta[] = [
         new PropertyMeta('id', 'Id', "The id of this node", 'string', '', '', false, false, false, false, '', ''),
     ]),
     new ElementMeta('yaml', 'YAMLDataFormat', 'YAML', "Marshal and unmarshal Java objects to and from YAML.", 'dataformat,transformation,yaml', [
-        new PropertyMeta('library', 'Library', "Which yaml library to use. By default it is SnakeYAML", 'string', 'snake-yaml', 'SnakeYAML', false, false, false, false, '', ''),
+        new PropertyMeta('library', 'Library', "Which yaml library to use. By default it is SnakeYAML", 'string', 'SnakeYAML', 'SnakeYAML', false, false, false, false, '', ''),
         new PropertyMeta('unmarshalType', 'Unmarshal Type', "Class name of the java type to use when unmarshalling", 'string', '', '', false, false, false, false, '', ''),
         new PropertyMeta('constructor', 'Constructor', "BaseConstructor to construct incoming documents.", 'string', '', '', false, false, false, false, 'advanced', ''),
         new PropertyMeta('representer', 'Representer', "Representer to emit outgoing objects.", 'string', '', '', false, false, false, false, 'advanced', ''),
@@ -460,6 +461,7 @@ export const CamelDataFormatMetadata: ElementMeta[] = [
         new PropertyMeta('noNamespaceSchemaLocation', 'No Namespace Schema Location', "To define the location of the namespaceless schema", 'string', '', '', false, false, false, false, 'advanced', ''),
         new PropertyMeta('jaxbProviderProperties', 'Jaxb Provider Properties', "Refers to a custom java.util.Map to lookup in the registry containing custom JAXB provider properties to be used with the JAXB marshaller.", 'string', '', '', false, false, false, false, 'advanced', ''),
         new PropertyMeta('contentTypeHeader', 'Content Type Header', "Whether the data format should set the Content-Type header with the type from the data format. For example application/xml for data formats marshalling to XML, or application/json for data formats marshalling to JSON", 'boolean', '', 'true', false, false, false, false, '', ''),
+        new PropertyMeta('accessExternalSchemaProtocols', 'Access External Schema Protocols', "Only in use if schema validation has been enabled. Restrict access to the protocols specified for external reference set by the schemaLocation attribute, Import and Include element. Examples of protocols are file, http, jar:file. false or none to deny all access to external references; a specific protocol, such as file, to give permission to only the protocol; the keyword all to grant permissio [...]
         new PropertyMeta('id', 'Id', "The id of this node", 'string', '', '', false, false, false, false, '', ''),
     ]),
     new ElementMeta('univocityTsv', 'UniVocityTsvDataFormat', 'uniVocity TSV', "Marshal and unmarshal Java objects from and to TSV (Tab-Separated Values) records using UniVocity Parsers.", 'dataformat,transformation,csv', [
@@ -688,7 +690,7 @@ export const CamelLanguageMetadata: ElementMeta[] = [
         new PropertyMeta('inheritNamespaceTagName', 'Inherit Namespace Tag Name', "To inherit namespaces from a root/parent tag name when using XML You can use simple language as the tag name to support dynamic names.", 'string', '', '', false, false, false, false, 'advanced', ''),
         new PropertyMeta('regex', 'Regex', "If the token is a regular expression pattern. The default value is false", 'boolean', '', 'false', false, false, false, false, 'advanced', ''),
         new PropertyMeta('xml', 'Xml', "Whether the input is XML messages. This option must be set to true if working with XML payloads.", 'boolean', '', 'false', false, false, false, false, '', ''),
-        new PropertyMeta('includeTokens', 'Include Tokens', "Whether to include the tokens in the parts when using pairs The default value is false", 'boolean', '', 'false', false, false, false, false, '', ''),
+        new PropertyMeta('includeTokens', 'Include Tokens', "Whether to include the tokens in the parts when using pairs. When including tokens then the endToken property must also be configured (to use pair mode). The default value is false", 'boolean', '', 'false', false, false, false, false, '', ''),
         new PropertyMeta('group', 'Group', "To group N parts together, for example to split big files into chunks of 1000 lines. You can use simple language as the group to support dynamic group sizes.", 'string', '', '', false, false, false, false, 'advanced', ''),
         new PropertyMeta('groupDelimiter', 'Group Delimiter', "Sets the delimiter to use when grouping. If this has not been set then token will be used as the delimiter.", 'string', '', '', false, false, false, false, 'advanced', ''),
         new PropertyMeta('skipFirst', 'Skip First', "To skip the very first element", 'boolean', '', 'false', false, false, false, false, 'advanced', ''),
@@ -922,7 +924,7 @@ export const CamelModelMetadata: ElementMeta[] = [
         new PropertyMeta('apiContextPath', 'Api Context Path', "Sets a leading API context-path the REST API services will be using. This can be used when using components such as camel-servlet where the deployed web application is deployed using a context-path.", 'string', '', '', false, false, false, false, 'consumer', ''),
         new PropertyMeta('apiContextRouteId', 'Api Context Route Id', "Sets the route id to use for the route that services the REST API. The route will by default use an auto assigned route id.", 'string', '', '', false, false, false, false, 'consumer,advanced', ''),
         new PropertyMeta('apiVendorExtension', 'Api Vendor Extension', "Whether vendor extension is enabled in the Rest APIs. If enabled then Camel will include additional information as vendor extension (eg keys starting with x-) such as route ids, class names etc. Not all 3rd party API gateways and tools supports vendor-extensions when importing your API docs.", 'boolean', '', 'false', false, false, false, false, 'consumer,advanced', ''),
-        new PropertyMeta('hostNameResolver', 'Host Name Resolver', "If no hostname has been explicit configured, then this resolver is used to compute the hostname the REST service will be using.", 'string', 'all-local-ip, local-host-name, local-ip', 'allLocalIp', false, false, false, false, 'consumer,advanced', ''),
+        new PropertyMeta('hostNameResolver', 'Host Name Resolver', "If no hostname has been explicit configured, then this resolver is used to compute the hostname the REST service will be using.", 'string', 'allLocalIp, localHostName, localIp', 'allLocalIp', false, false, false, false, 'consumer,advanced', ''),
         new PropertyMeta('bindingMode', 'Binding Mode', "Sets the binding mode to use. The default value is off", 'string', 'off, auto, json, xml, json_xml', 'off', false, false, false, false, '', ''),
         new PropertyMeta('skipBindingOnErrorCode', 'Skip Binding On Error Code', "Whether to skip binding on output if there is a custom HTTP error code header. This allows to build custom error messages that do not bind to json / xml etc, as success messages otherwise will do.", 'boolean', '', 'false', false, false, false, false, 'advanced', ''),
         new PropertyMeta('clientRequestValidation', 'Client Request Validation', "Whether to enable validation of the client request to check: 1) Content-Type header matches what the Rest DSL consumes; returns HTTP Status 415 if validation error. 2) Accept header matches what the Rest DSL produces; returns HTTP Status 406 if validation error. 3) Missing required data (query parameters, HTTP headers, body); returns HTTP Status 400 if validation error. 4) Parsing error of the message body  [...]
@@ -1133,7 +1135,7 @@ export const CamelModelMetadata: ElementMeta[] = [
         new PropertyMeta('routeTemplateRef', 'Route Template Ref', "Sets the id of the route template to use to build the route.", 'string', '', '', true, false, false, false, '', ''),
         new PropertyMeta('routeId', 'Route Id', "Sets the id of the route built from the route template.", 'string', '', '', false, false, false, false, '', ''),
         new PropertyMeta('prefixId', 'Prefix Id', "Sets a prefix to use for all node ids (not route id).", 'string', '', '', false, false, false, false, '', ''),
-        new PropertyMeta('beans', 'beans', "beans", 'NamedBeanDefinition', '', '', false, false, true, true, '', ''),
+        new PropertyMeta('beans', 'beans', "beans", 'TemplatedRouteBeanDefinition', '', '', false, false, true, true, '', ''),
         new PropertyMeta('parameters', 'parameters', "parameters", 'TemplatedRouteParameterDefinition', '', '', false, false, true, true, '', ''),
     ]),
     new ElementMeta('customLoadBalancer', 'CustomLoadBalancerDefinition', 'Custom Load Balancer', "To use a custom load balancer implementation.", 'eip,routing', [
@@ -1142,7 +1144,7 @@ export const CamelModelMetadata: ElementMeta[] = [
     ]),
     new ElementMeta('param', 'ParamDefinition', 'Param', "To specify the rest operation parameters.", 'rest', [
         new PropertyMeta('name', 'Name', "Sets the parameter name.", 'string', '', '', true, false, false, false, '', ''),
-        new PropertyMeta('type', 'Type', "Sets the parameter type.", 'string', 'body, form-data, header, path, query', 'path', false, false, false, false, '', ''),
+        new PropertyMeta('type', 'Type', "Sets the parameter type.", 'string', 'body, formData, header, path, query', 'path', false, false, false, false, '', ''),
         new PropertyMeta('defaultValue', 'Default Value', "Sets the parameter default value.", 'string', '', '', false, false, false, false, '', ''),
         new PropertyMeta('required', 'Required', "Sets the parameter required flag.", 'boolean', '', 'true', false, false, false, false, '', ''),
         new PropertyMeta('collectionFormat', 'Collection Format', "Sets the parameter collection format.", 'string', 'csv, multi, pipes, ssv, tsv', 'csv', false, false, false, false, '', ''),
@@ -1192,8 +1194,8 @@ export const CamelModelMetadata: ElementMeta[] = [
         new PropertyMeta('loggerRef', 'Logger Ref', "References to a logger to use as logger for the error handler", 'string', '', '', false, false, false, false, 'advanced', ''),
         new PropertyMeta('level', 'Level', "Logging level to use when using the logging error handler type.", 'string', 'TRACE, DEBUG, INFO, WARN, ERROR, OFF', 'ERROR', false, false, false, false, 'advanced', ''),
         new PropertyMeta('logName', 'Log Name', "Name of the logger to use for the logging error handler", 'string', '', '', false, false, false, false, 'advanced', ''),
-        new PropertyMeta('useOriginalMessage', 'Use Original Message', "Will use the original input org.apache.camel.Message (original body and headers) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the [...]
-        new PropertyMeta('useOriginalBody', 'Use Original Body', "Will use the original input org.apache.camel.Message body (original body only) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the origina [...]
+        new PropertyMeta('useOriginalMessage', 'Use Original Message', "Will use the original input org.apache.camel.Message (original body and headers) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the [...]
+        new PropertyMeta('useOriginalBody', 'Use Original Body', "Will use the original input org.apache.camel.Message body (original body only) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the origina [...]
         new PropertyMeta('onRedeliveryRef', 'On Redelivery Ref', "Sets a reference to a processor that should be processed before a redelivery attempt. Can be used to change the org.apache.camel.Exchange before its being redelivered.", 'string', '', '', false, false, false, false, 'advanced', 'org.apache.camel.Processor'),
         new PropertyMeta('onExceptionOccurredRef', 'On Exception Occurred Ref', "Sets a reference to a processor that should be processed just after an exception occurred. Can be used to perform custom logging about the occurred exception at the exact time it happened. Important: Any exception thrown from this processor will be ignored.", 'string', '', '', false, false, false, false, 'advanced', 'org.apache.camel.Processor'),
         new PropertyMeta('onPrepareFailureRef', 'On Prepare Failure Ref', "Sets a reference to a processor to prepare the org.apache.camel.Exchange before handled by the failure processor / dead letter channel. This allows for example to enrich the message before sending to a dead letter queue.", 'string', '', '', false, false, false, false, 'advanced', 'org.apache.camel.Processor'),
@@ -1679,7 +1681,8 @@ export const CamelModelMetadata: ElementMeta[] = [
         new PropertyMeta('aggregationStrategyMethodName', 'Aggregation Strategy Method Name', "This option can be used to explicit declare the method name to use, when using POJOs as the AggregationStrategy.", 'string', '', '', false, false, false, false, 'advanced', ''),
         new PropertyMeta('aggregationStrategyMethodAllowNull', 'Aggregation Strategy Method Allow Null', "If this option is false then the aggregate method is not used if there was no data to enrich. If this option is true then null values is used as the oldExchange (when no data to enrich), when using POJOs as the AggregationStrategy", 'boolean', '', 'false', false, false, false, false, 'advanced', ''),
         new PropertyMeta('parallelAggregate', 'Parallel Aggregate', "If enabled then the aggregate method on AggregationStrategy can be called concurrently. Notice that this would require the implementation of AggregationStrategy to be implemented as thread-safe. By default this is false meaning that Camel synchronizes the call to the aggregate method. Though in some use-cases this can be used to archive higher performance when the AggregationStrategy is implemented as thread-safe.", 'bo [...]
-        new PropertyMeta('parallelProcessing', 'Parallel Processing', "If enabled then processing each split messages occurs concurrently. Note the caller thread will still wait until all messages has been fully processed, before it continues. It's only processing the sub messages from the splitter which happens concurrently.", 'boolean', '', 'false', false, false, false, false, '', ''),
+        new PropertyMeta('parallelProcessing', 'Parallel Processing', "If enabled then processing each split messages occurs concurrently. Note the caller thread will still wait until all messages has been fully processed, before it continues. It's only processing the sub messages from the splitter which happens concurrently. When parallel processing is enabled, then the Camel routing engin will continue processing using last used thread from the parallel thread pool. However, if you wan [...]
+        new PropertyMeta('synchronous', 'Synchronous', "Sets whether synchronous processing should be strictly used. When enabled then the same thread is used to continue routing after the split is complete, even if parallel processing is enabled.", 'boolean', '', 'false', false, false, false, false, '', ''),
         new PropertyMeta('streaming', 'Streaming', "When in streaming mode, then the splitter splits the original message on-demand, and each split message is processed one by one. This reduces memory usage as the splitter do not split all the messages first, but then we do not know the total size, and therefore the org.apache.camel.Exchange#SPLIT_SIZE is empty. In non-streaming mode (default) the splitter will split each message first, to know the total size, and then process each messa [...]
         new PropertyMeta('stopOnException', 'Stop On Exception', "Will now stop further processing if an exception or failure occurred during processing of an org.apache.camel.Exchange and the caused exception will be thrown. Will also stop if processing the exchange failed (has a fault message) or an exception was thrown and handled by the error handler (such as using onException). In all situations the splitter will stop further processing. This is the same behavior as in pipeline, whi [...]
         new PropertyMeta('timeout', 'Timeout', "Sets a total timeout specified in millis, when using parallel processing. If the Splitter hasn't been able to split and process all the sub messages within the given timeframe, then the timeout triggers and the Splitter breaks out and continues. Notice if you provide a TimeoutAwareAggregationStrategy then the timeout method is invoked before breaking out. If the timeout is reached with running tasks still remaining, certain tasks for which  [...]
@@ -1711,8 +1714,8 @@ export const CamelModelMetadata: ElementMeta[] = [
         new PropertyMeta('continued', 'Continued', "Sets whether the exchange should handle and continue routing from the point of failure. If this option is enabled then its considered handled as well.", 'ExpressionSubElementDefinition', '', '', false, false, false, true, 'advanced', ''),
         new PropertyMeta('onRedeliveryRef', 'On Redelivery Ref', "Sets a reference to a processor that should be processed before a redelivery attempt. Can be used to change the org.apache.camel.Exchange before its being redelivered.", 'string', '', '', false, false, false, false, 'advanced', ''),
         new PropertyMeta('onExceptionOccurredRef', 'On Exception Occurred Ref', "Sets a reference to a processor that should be processed just after an exception occurred. Can be used to perform custom logging about the occurred exception at the exact time it happened. Important: Any exception thrown from this processor will be ignored.", 'string', '', '', false, false, false, false, 'advanced', ''),
-        new PropertyMeta('useOriginalMessage', 'Use Original Message', "Will use the original input org.apache.camel.Message (original body and headers) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the [...]
-        new PropertyMeta('useOriginalBody', 'Use Original Body', "Will use the original input org.apache.camel.Message body (original body only) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the origina [...]
+        new PropertyMeta('useOriginalMessage', 'Use Original Message', "Will use the original input org.apache.camel.Message (original body and headers) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the [...]
+        new PropertyMeta('useOriginalBody', 'Use Original Body', "Will use the original input org.apache.camel.Message body (original body only) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the origina [...]
         new PropertyMeta('disabled', 'Disabled', "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime.", 'boolean', '', 'false', false, false, false, false, 'advanced', ''),
         new PropertyMeta('id', 'Id', "Sets the id of this node", 'string', '', '', false, false, false, false, '', ''),
         new PropertyMeta('description', 'Description', "Sets the description of this node", 'string', '', '', false, false, false, false, '', ''),
@@ -1723,7 +1726,8 @@ export const CamelModelMetadata: ElementMeta[] = [
         new PropertyMeta('aggregationStrategyMethodName', 'Aggregation Strategy Method Name', "This option can be used to explicit declare the method name to use, when using POJOs as the AggregationStrategy.", 'string', '', '', false, false, false, false, 'advanced', ''),
         new PropertyMeta('aggregationStrategyMethodAllowNull', 'Aggregation Strategy Method Allow Null', "If this option is false then the aggregate method is not used if there was no data to enrich. If this option is true then null values is used as the oldExchange (when no data to enrich), when using POJOs as the AggregationStrategy", 'boolean', '', 'false', false, false, false, false, 'advanced', ''),
         new PropertyMeta('parallelAggregate', 'Parallel Aggregate', "If enabled then the aggregate method on AggregationStrategy can be called concurrently. Notice that this would require the implementation of AggregationStrategy to be implemented as thread-safe. By default this is false meaning that Camel synchronizes the call to the aggregate method. Though in some use-cases this can be used to archive higher performance when the AggregationStrategy is implemented as thread-safe.", 'bo [...]
-        new PropertyMeta('parallelProcessing', 'Parallel Processing', "If enabled then sending messages to the multicasts occurs concurrently. Note the caller thread will still wait until all messages has been fully processed, before it continues. Its only the sending and processing the replies from the multicasts which happens concurrently.", 'boolean', '', 'false', false, false, false, false, '', ''),
+        new PropertyMeta('parallelProcessing', 'Parallel Processing', "If enabled then sending messages to the multicasts occurs concurrently. Note the caller thread will still wait until all messages has been fully processed, before it continues. Its only the sending and processing the replies from the multicasts which happens concurrently. When parallel processing is enabled, then the Camel routing engin will continue processing using last used thread from the parallel thread pool. How [...]
+        new PropertyMeta('synchronous', 'Synchronous', "Sets whether synchronous processing should be strictly used. When enabled then the same thread is used to continue routing after the multicast is complete, even if parallel processing is enabled.", 'boolean', '', 'false', false, false, false, false, '', ''),
         new PropertyMeta('streaming', 'Streaming', "If enabled then Camel will process replies out-of-order, eg in the order they come back. If disabled, Camel will process replies in the same order as defined by the multicast.", 'boolean', '', 'false', false, false, false, false, '', ''),
         new PropertyMeta('stopOnException', 'Stop On Exception', "Will now stop further processing if an exception or failure occurred during processing of an org.apache.camel.Exchange and the caused exception will be thrown. Will also stop if processing the exchange failed (has a fault message) or an exception was thrown and handled by the error handler (such as using onException). In all situations the multicast will stop further processing. This is the same behavior as in pipeline, wh [...]
         new PropertyMeta('timeout', 'Timeout', "Sets a total timeout specified in millis, when using parallel processing. If the Multicast hasn't been able to send and process all replies within the given timeframe, then the timeout triggers and the Multicast breaks out and continues. Notice if you provide a TimeoutAwareAggregationStrategy then the timeout method is invoked before breaking out. If the timeout is reached with running tasks still remaining, certain tasks for which it is di [...]
@@ -1769,8 +1773,8 @@ export const CamelModelMetadata: ElementMeta[] = [
         new PropertyMeta('loggerRef', 'Logger Ref', "References to a logger to use as logger for the error handler", 'string', '', '', false, false, false, false, 'advanced', ''),
         new PropertyMeta('level', 'Level', "Logging level to use when using the logging error handler type.", 'string', 'TRACE, DEBUG, INFO, WARN, ERROR, OFF', 'ERROR', false, false, false, false, 'advanced', ''),
         new PropertyMeta('logName', 'Log Name', "Name of the logger to use for the logging error handler", 'string', '', '', false, false, false, false, 'advanced', ''),
-        new PropertyMeta('useOriginalMessage', 'Use Original Message', "Will use the original input org.apache.camel.Message (original body and headers) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the [...]
-        new PropertyMeta('useOriginalBody', 'Use Original Body', "Will use the original input org.apache.camel.Message body (original body only) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the origina [...]
+        new PropertyMeta('useOriginalMessage', 'Use Original Message', "Will use the original input org.apache.camel.Message (original body and headers) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the [...]
+        new PropertyMeta('useOriginalBody', 'Use Original Body', "Will use the original input org.apache.camel.Message body (original body only) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the origina [...]
         new PropertyMeta('onRedeliveryRef', 'On Redelivery Ref', "Sets a reference to a processor that should be processed before a redelivery attempt. Can be used to change the org.apache.camel.Exchange before its being redelivered.", 'string', '', '', false, false, false, false, 'advanced', 'org.apache.camel.Processor'),
         new PropertyMeta('onExceptionOccurredRef', 'On Exception Occurred Ref', "Sets a reference to a processor that should be processed just after an exception occurred. Can be used to perform custom logging about the occurred exception at the exact time it happened. Important: Any exception thrown from this processor will be ignored.", 'string', '', '', false, false, false, false, 'advanced', 'org.apache.camel.Processor'),
         new PropertyMeta('onPrepareFailureRef', 'On Prepare Failure Ref', "Sets a reference to a processor to prepare the org.apache.camel.Exchange before handled by the failure processor / dead letter channel. This allows for example to enrich the message before sending to a dead letter queue.", 'string', '', '', false, false, false, false, 'advanced', 'org.apache.camel.Processor'),
@@ -1900,8 +1904,8 @@ export const CamelModelMetadata: ElementMeta[] = [
         new PropertyMeta('loggerRef', 'Logger Ref', "References to a logger to use as logger for the error handler", 'string', '', '', false, false, false, false, 'advanced', ''),
         new PropertyMeta('level', 'Level', "Logging level to use when using the logging error handler type.", 'string', 'TRACE, DEBUG, INFO, WARN, ERROR, OFF', 'ERROR', false, false, false, false, 'advanced', ''),
         new PropertyMeta('logName', 'Log Name', "Name of the logger to use for the logging error handler", 'string', '', '', false, false, false, false, 'advanced', ''),
-        new PropertyMeta('useOriginalMessage', 'Use Original Message', "Will use the original input org.apache.camel.Message (original body and headers) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the [...]
-        new PropertyMeta('useOriginalBody', 'Use Original Body', "Will use the original input org.apache.camel.Message body (original body only) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the origina [...]
+        new PropertyMeta('useOriginalMessage', 'Use Original Message', "Will use the original input org.apache.camel.Message (original body and headers) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the [...]
+        new PropertyMeta('useOriginalBody', 'Use Original Body', "Will use the original input org.apache.camel.Message body (original body only) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the origina [...]
         new PropertyMeta('onRedeliveryRef', 'On Redelivery Ref', "Sets a reference to a processor that should be processed before a redelivery attempt. Can be used to change the org.apache.camel.Exchange before its being redelivered.", 'string', '', '', false, false, false, false, 'advanced', 'org.apache.camel.Processor'),
         new PropertyMeta('onExceptionOccurredRef', 'On Exception Occurred Ref', "Sets a reference to a processor that should be processed just after an exception occurred. Can be used to perform custom logging about the occurred exception at the exact time it happened. Important: Any exception thrown from this processor will be ignored.", 'string', '', '', false, false, false, false, 'advanced', 'org.apache.camel.Processor'),
         new PropertyMeta('onPrepareFailureRef', 'On Prepare Failure Ref', "Sets a reference to a processor to prepare the org.apache.camel.Exchange before handled by the failure processor / dead letter channel. This allows for example to enrich the message before sending to a dead letter queue.", 'string', '', '', false, false, false, false, 'advanced', 'org.apache.camel.Processor'),
@@ -2037,7 +2041,8 @@ export const CamelModelMetadata: ElementMeta[] = [
         new PropertyMeta('aggregationStrategyMethodName', 'Aggregation Strategy Method Name', "This option can be used to explicit declare the method name to use, when using POJOs as the AggregationStrategy.", 'string', '', '', false, false, false, false, 'advanced', ''),
         new PropertyMeta('aggregationStrategyMethodAllowNull', 'Aggregation Strategy Method Allow Null', "If this option is false then the aggregate method is not used if there was no data to enrich. If this option is true then null values is used as the oldExchange (when no data to enrich), when using POJOs as the AggregationStrategy", 'boolean', '', 'false', false, false, false, false, 'advanced', ''),
         new PropertyMeta('parallelAggregate', 'Parallel Aggregate', "If enabled then the aggregate method on AggregationStrategy can be called concurrently. Notice that this would require the implementation of AggregationStrategy to be implemented as thread-safe. By default this is false meaning that Camel synchronizes the call to the aggregate method. Though in some use-cases this can be used to archive higher performance when the AggregationStrategy is implemented as thread-safe.", 'bo [...]
-        new PropertyMeta('parallelProcessing', 'Parallel Processing', "If enabled then sending messages to the recipients occurs concurrently. Note the caller thread will still wait until all messages has been fully processed, before it continues. Its only the sending and processing the replies from the recipients which happens concurrently.", 'boolean', '', 'false', false, false, false, false, '', ''),
+        new PropertyMeta('parallelProcessing', 'Parallel Processing', "If enabled then sending messages to the recipients occurs concurrently. Note the caller thread will still wait until all messages has been fully processed, before it continues. Its only the sending and processing the replies from the recipients which happens concurrently. When parallel processing is enabled, then the Camel routing engin will continue processing using last used thread from the parallel thread pool. How [...]
+        new PropertyMeta('synchronous', 'Synchronous', "Sets whether synchronous processing should be strictly used. When enabled then the same thread is used to continue routing after the recipient list is complete, even if parallel processing is enabled.", 'boolean', '', 'false', false, false, false, false, '', ''),
         new PropertyMeta('timeout', 'Timeout', "Sets a total timeout specified in millis, when using parallel processing. If the Recipient List hasn't been able to send and process all replies within the given timeframe, then the timeout triggers and the Recipient List breaks out and continues. Notice if you provide a TimeoutAwareAggregationStrategy then the timeout method is invoked before breaking out. If the timeout is reached with running tasks still remaining, certain tasks for whic [...]
         new PropertyMeta('executorService', 'Executor Service', "To use a custom Thread Pool to be used for parallel processing. Notice if you set this option, then parallel processing is automatic implied, and you do not have to enable that option as well.", 'string', '', '', false, false, false, false, 'advanced', ''),
         new PropertyMeta('stopOnException', 'Stop On Exception', "Will now stop further processing if an exception or failure occurred during processing of an org.apache.camel.Exchange and the caused exception will be thrown. Will also stop if processing the exchange failed (has a fault message) or an exception was thrown and handled by the error handler (such as using onException). In all situations the recipient list will stop further processing. This is the same behavior as in pipelin [...]
@@ -2081,8 +2086,8 @@ export const CamelModelMetadata: ElementMeta[] = [
         new PropertyMeta('loggerRef', 'Logger Ref', "References to a logger to use as logger for the error handler", 'string', '', '', false, false, false, false, 'advanced', ''),
         new PropertyMeta('level', 'Level', "Logging level to use when using the logging error handler type.", 'string', 'TRACE, DEBUG, INFO, WARN, ERROR, OFF', 'ERROR', false, false, false, false, 'advanced', ''),
         new PropertyMeta('logName', 'Log Name', "Name of the logger to use for the logging error handler", 'string', '', '', false, false, false, false, 'advanced', ''),
-        new PropertyMeta('useOriginalMessage', 'Use Original Message', "Will use the original input org.apache.camel.Message (original body and headers) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the [...]
-        new PropertyMeta('useOriginalBody', 'Use Original Body', "Will use the original input org.apache.camel.Message body (original body only) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the origina [...]
+        new PropertyMeta('useOriginalMessage', 'Use Original Message', "Will use the original input org.apache.camel.Message (original body and headers) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the [...]
+        new PropertyMeta('useOriginalBody', 'Use Original Body', "Will use the original input org.apache.camel.Message body (original body only) when an org.apache.camel.Exchange is moved to the dead letter queue. Notice: this only applies when all redeliveries attempt have failed and the org.apache.camel.Exchange is doomed for failure. Instead of using the current inprogress org.apache.camel.Exchange IN message we use the original IN message instead. This allows you to store the origina [...]
         new PropertyMeta('onRedeliveryRef', 'On Redelivery Ref', "Sets a reference to a processor that should be processed before a redelivery attempt. Can be used to change the org.apache.camel.Exchange before its being redelivered.", 'string', '', '', false, false, false, false, 'advanced', 'org.apache.camel.Processor'),
         new PropertyMeta('onExceptionOccurredRef', 'On Exception Occurred Ref', "Sets a reference to a processor that should be processed just after an exception occurred. Can be used to perform custom logging about the occurred exception at the exact time it happened. Important: Any exception thrown from this processor will be ignored.", 'string', '', '', false, false, false, false, 'advanced', 'org.apache.camel.Processor'),
         new PropertyMeta('onPrepareFailureRef', 'On Prepare Failure Ref', "Sets a reference to a processor to prepare the org.apache.camel.Exchange before handled by the failure processor / dead letter channel. This allows for example to enrich the message before sending to a dead letter queue.", 'string', '', '', false, false, false, false, 'advanced', 'org.apache.camel.Processor'),
@@ -2124,7 +2129,7 @@ export const CamelModelMetadata: ElementMeta[] = [
     new ElementMeta('routeTemplate', 'RouteTemplateDefinition', 'Route Template', "Defines a route template (parameterized routes)", 'configuration', [
         new PropertyMeta('route', 'Route', "To define the route in the template", 'RouteDefinition', '', '', true, false, false, true, '', ''),
         new PropertyMeta('id', 'Id', "Sets the id of this node", 'string', '', '', false, false, false, false, '', ''),
-        new PropertyMeta('beans', 'beans', "beans", 'NamedBeanDefinition', '', '', false, false, true, true, '', ''),
+        new PropertyMeta('beans', 'beans', "beans", 'RouteTemplateBeanDefinition', '', '', false, false, true, true, '', ''),
         new PropertyMeta('from', 'from', "from", 'FromDefinition', '', '', false, false, false, true, '', ''),
         new PropertyMeta('parameters', 'parameters', "parameters", 'RouteTemplateParameterDefinition', '', '', false, false, true, true, '', ''),
     ]),
@@ -2185,6 +2190,7 @@ export const CamelModelMetadata: ElementMeta[] = [
 
 export const SensitiveKeys: string[] = [
     "oauthtokenurl",
+    "clientid",
     "sassignature",
     "connectionstring",
     "sslpassword",
diff --git a/karavan-demo/jms-to-kafka/docs/README.md b/karavan-demo/jms-to-kafka/docs/README.md
index 98b363e0..6fd39330 100644
--- a/karavan-demo/jms-to-kafka/docs/README.md
+++ b/karavan-demo/jms-to-kafka/docs/README.md
@@ -26,7 +26,7 @@ docker-compose up
 
 ### Start integration 
 ```
-jbang -Dcamel.jbang.version=3.20.6 camel@apache/camel run *
+jbang -Dcamel.jbang.version=3.21.0 camel@apache/camel run *
 ```
 
 ### Publish payment to JMS
diff --git a/karavan-generator/pom.xml b/karavan-generator/pom.xml
index 4078ae21..3a39828c 100644
--- a/karavan-generator/pom.xml
+++ b/karavan-generator/pom.xml
@@ -28,7 +28,7 @@
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <surefire-plugin.version>3.0.0-M5</surefire-plugin.version>
-        <version.camel-core>3.20.6</version.camel-core>
+        <version.camel-core>3.21.0</version.camel-core>
         <version.camel-kamelet>3.20.5</version.camel-kamelet>
         <version.vertx>4.4.3</version.vertx>
     </properties>
diff --git a/karavan-operator/src/main/resources/quarkus-builder-script-kubernetes.sh b/karavan-operator/src/main/resources/quarkus-builder-script-kubernetes.sh
index 13d8a207..8df65507 100644
--- a/karavan-operator/src/main/resources/quarkus-builder-script-kubernetes.sh
+++ b/karavan-operator/src/main/resources/quarkus-builder-script-kubernetes.sh
@@ -14,7 +14,7 @@ fi
 
 cd ${CHECKOUT_DIR}/$(inputs.params.project)
 
-entrypoint -Dcamel.jbang.version=3.20.6 camel@apache/camel export --local-kamelet-dir=${KAMELETS_DIR}
+entrypoint -Dcamel.jbang.version=3.21.0 camel@apache/camel export --local-kamelet-dir=${KAMELETS_DIR}
 
 export LAST_COMMIT=$(git rev-parse --short HEAD)
 export DATE=$(date '+%Y%m%d%H%M%S')
diff --git a/karavan-operator/src/main/resources/quarkus-builder-script-openshift.sh b/karavan-operator/src/main/resources/quarkus-builder-script-openshift.sh
index b15d3fc8..d3c3b559 100644
--- a/karavan-operator/src/main/resources/quarkus-builder-script-openshift.sh
+++ b/karavan-operator/src/main/resources/quarkus-builder-script-openshift.sh
@@ -14,7 +14,7 @@ fi
 
 cd ${CHECKOUT_DIR}/$(inputs.params.project)
 
-entrypoint -Dcamel.jbang.version=3.20.6 camel@apache/camel export --local-kamelet-dir=${KAMELETS_DIR}
+entrypoint -Dcamel.jbang.version=3.21.0 camel@apache/camel export --local-kamelet-dir=${KAMELETS_DIR}
 
 export LAST_COMMIT=$(git rev-parse --short HEAD)
 export DATE=$(date '+%Y%m%d%H%M%S')
diff --git a/karavan-operator/src/main/resources/spring-boot-builder-script-kubernetes.sh b/karavan-operator/src/main/resources/spring-boot-builder-script-kubernetes.sh
index 656c4381..6d52f005 100644
--- a/karavan-operator/src/main/resources/spring-boot-builder-script-kubernetes.sh
+++ b/karavan-operator/src/main/resources/spring-boot-builder-script-kubernetes.sh
@@ -14,7 +14,7 @@ fi
 
 cd ${CHECKOUT_DIR}/$(inputs.params.project)
 
-entrypoint -Dcamel.jbang.version=3.20.6 camel@apache/camel export --local-kamelet-dir=${KAMELETS_DIR}
+entrypoint -Dcamel.jbang.version=3.21.0 camel@apache/camel export --local-kamelet-dir=${KAMELETS_DIR}
 
 export LAST_COMMIT=$(git rev-parse --short HEAD)
 export DATE=$(date '+%Y%m%d%H%M%S')
diff --git a/karavan-operator/src/main/resources/spring-boot-builder-script-openshift.sh b/karavan-operator/src/main/resources/spring-boot-builder-script-openshift.sh
index 01fefb19..f08a43ee 100644
--- a/karavan-operator/src/main/resources/spring-boot-builder-script-openshift.sh
+++ b/karavan-operator/src/main/resources/spring-boot-builder-script-openshift.sh
@@ -14,7 +14,7 @@ fi
 
 cd ${CHECKOUT_DIR}/$(inputs.params.project)
 
-entrypoint -Dcamel.jbang.version=3.20.6 camel@apache/camel export --local-kamelet-dir=${KAMELETS_DIR}
+entrypoint -Dcamel.jbang.version=3.21.0 camel@apache/camel export --local-kamelet-dir=${KAMELETS_DIR}
 
 export LAST_COMMIT=$(git rev-parse --short HEAD)
 export DATE=$(date '+%Y%m%d%H%M%S')
diff --git a/karavan-runner/Dockerfile b/karavan-runner/Dockerfile
index 0cbaebea..b820e3d1 100644
--- a/karavan-runner/Dockerfile
+++ b/karavan-runner/Dockerfile
@@ -1,6 +1,6 @@
 FROM jbangdev/jbang-action:0.106.1
 
-ENV CAMEL_VERSION=3.20.6
+ENV CAMEL_VERSION=3.21.0
 ENV MAVEN_SETTINGS="/karavan-config-map/maven-settings.xml"
 ENV KAMELETS_DIR="/scripts/kamelets"
 ENV CODE_DIR="/scripts/code"
diff --git a/karavan-vscode/CHANGELOG.md b/karavan-vscode/CHANGELOG.md
index 3547e0e9..e636c8af 100644
--- a/karavan-vscode/CHANGELOG.md
+++ b/karavan-vscode/CHANGELOG.md
@@ -1,8 +1,8 @@
 # Changelog
 
 ## 3.20.2
-0. Camel 3.20.6
-1. Spring Boot 2.7.11
+0. Camel 3.21.0
+1. Spring Boot 2.7.13
 
 ## 3.20.1
 0. Camel 3.20.3
diff --git a/karavan-vscode/README.md b/karavan-vscode/README.md
index eb815488..2908e4aa 100644
--- a/karavan-vscode/README.md
+++ b/karavan-vscode/README.md
@@ -72,7 +72,7 @@ Build-in catalogues:
 
 * Run using CLI
     ```shell
-    jbang -Dcamel.jbang.version=3.20.6 camel@apache/camel run $INTEGRATION.yaml --max-messages=10 --logging-level=info
+    jbang -Dcamel.jbang.version=3.21.0 camel@apache/camel run $INTEGRATION.yaml --max-messages=10 --logging-level=info
     ```
 
 ## Export integration to Maven project
@@ -82,7 +82,7 @@ Build-in catalogues:
 
 * Export using CLI
     ```shell
-    jbang -Dcamel.jbang.version=3.20.6 camel@apache/camel export --directory=export
+    jbang -Dcamel.jbang.version=3.21.0 camel@apache/camel export --directory=export
     ```
 
 # Issues
diff --git a/karavan-vscode/package.json b/karavan-vscode/package.json
index 19ac5013..1001c714 100644
--- a/karavan-vscode/package.json
+++ b/karavan-vscode/package.json
@@ -64,7 +64,7 @@
       "properties": {
         "camel.version": {
           "type": "string",
-          "default": "3.20.6",
+          "default": "3.21.0",
           "description": "Camel version",
           "scope": "machine",
           "order": 10
@@ -246,8 +246,8 @@
             "camel.karavan.project-description=$NAME",
             "camel.karavan.target=$TARGET",
             "camel.jbang.gav=$GAV",
-            "camel.jbang.camelSpringBootVersion=3.20.6",
-            "camel.jbang.springBootVersion=2.7.11",
+            "camel.jbang.camelSpringBootVersion=3.21.0",
+            "camel.jbang.springBootVersion=2.7.13",
             "camel.jbang.runtime=$RUNTIME",
             "camel.jbang.exportDir=.export",
             "management.endpoints.web.exposure.include=health",


[camel-karavan] 03/06: Refactor for #809

Posted by ma...@apache.org.
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 a34cfdee06339365d92540d97214027835017650
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Tue Jun 27 10:14:26 2023 -0400

    Refactor for #809
---
 karavan-app/src/main/webui/src/Main.tsx            |   9 +-
 .../src/main/webui/src/api/ProjectService.ts       |  55 ++++++-
 karavan-app/src/main/webui/src/api/ProjectStore.ts |  18 +++
 .../src/main/webui/src/project/ProjectPage.tsx     | 165 +--------------------
 .../src/main/webui/src/project/ProjectPanel.tsx    |  14 +-
 .../src/main/webui/src/project/ProjectToolbar.tsx  |  80 +++++-----
 .../main/webui/src/project/PropertiesEditor.tsx    |  42 ------
 .../src/main/webui/src/project/file/FileEditor.tsx | 143 ++++++++++++++++++
 .../src/project/{ => file}/PropertiesTable.tsx     | 103 +++++++------
 .../src/main/webui/src/project/files/FilesTab.tsx  |  19 ++-
 .../main/webui/src/project/files/FilesToolbar.tsx  |  16 ++
 .../main/webui/src/project/files/UploadModal.tsx   |  16 ++
 .../src/project/pipeline/ProjectPipelineTab.tsx    |   2 +-
 .../src/project/{ => pipeline}/ProjectStatus.tsx   |   8 +-
 .../webui/src/project/trace/RunnerInfoTrace.tsx    |  16 ++
 .../src/project/trace/RunnerInfoTraceModal.tsx     |  16 ++
 .../src/project/trace/RunnerInfoTraceNode.tsx      |  16 ++
 .../src/main/webui/src/project/trace/TraceTab.tsx  |  16 ++
 18 files changed, 448 insertions(+), 306 deletions(-)

diff --git a/karavan-app/src/main/webui/src/Main.tsx b/karavan-app/src/main/webui/src/Main.tsx
index 39f3192f..c16599bd 100644
--- a/karavan-app/src/main/webui/src/Main.tsx
+++ b/karavan-app/src/main/webui/src/Main.tsx
@@ -30,9 +30,9 @@ import {MainLogin} from "./MainLogin";
 import {DashboardPage} from "./dashboard/DashboardPage";
 import {Subscription} from "rxjs";
 import {ProjectEventBus} from "./api/ProjectEventBus";
-import {Project} from "./api/ProjectModels";
+import {Project, ProjectFile} from "./api/ProjectModels";
 import {ProjectPage} from "./project/ProjectPage";
-import {useAppConfigStore} from "./api/ProjectStore";
+import {useAppConfigStore, useFileStore} from "./api/ProjectStore";
 
 class ToastMessage {
     id: string = ''
@@ -194,7 +194,10 @@ export class Main extends React.Component<Props, State> {
                     <Tooltip content={page.tooltip} position={"right"}>
                         <Button id={page.pageId} icon={page.icon} variant={"plain"}
                                 className={this.state.pageId === page.pageId ? "nav-button-selected" : ""}
-                                onClick={event => this.setState({pageId: page.pageId})}
+                                onClick={event => {
+                                    useFileStore.setState({operation:'none', file: undefined})
+                                    this.setState({pageId: page.pageId});
+                                }}
                         />
                     </Tooltip>
                 </FlexItem>
diff --git a/karavan-app/src/main/webui/src/api/ProjectService.ts b/karavan-app/src/main/webui/src/api/ProjectService.ts
index e346c5bd..feca6ec3 100644
--- a/karavan-app/src/main/webui/src/api/ProjectService.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectService.ts
@@ -2,10 +2,59 @@ import {KaravanApi} from "./KaravanApi";
 import {DeploymentStatus, Project, ProjectFile} from "./ProjectModels";
 import {TemplateApi} from "karavan-core/lib/api/TemplateApi";
 import {KubernetesAPI} from "../designer/utils/KubernetesAPI";
-import {useDeploymentStatusesStore, useFilesStore, useProjectsStore, useProjectStore} from "./ProjectStore";
+import { unstable_batchedUpdates } from 'react-dom'
+import {
+    useAppConfigStore,
+    useDeploymentStatusesStore,
+    useFilesStore,
+    useFileStore,
+    useProjectsStore,
+    useProjectStore
+} from "./ProjectStore";
 
 export class ProjectService {
 
+    public static pushProject (project: Project, commitMessage: string) {
+        useProjectStore.setState({isPushing: true})
+        const params = {
+            "projectId": project.projectId,
+            "message": commitMessage
+        };
+        KaravanApi.push(params, res => {
+            if (res.status === 200 || res.status === 201) {
+                useProjectStore.setState({isPushing: false})
+                ProjectService.refreshProject(project.projectId);
+                ProjectService.refreshProjectData();
+            } else {
+                // Todo notification
+            }
+        });
+    }
+
+    public static saveFile (file: ProjectFile) {
+        console.log(file)
+        KaravanApi.postProjectFile(file, res => {
+            if (res.status === 200) {
+                const newFile = res.data;
+                useFileStore.setState({file: newFile});
+                unstable_batchedUpdates(() => {
+                    useFilesStore.getState().upsertFile(newFile);
+                })
+            } else {
+                // console.log(res) //TODO show notification
+            }
+        })
+    }
+
+    public static refreshProject(projectId: string) {
+        KaravanApi.getProject(projectId , (project: Project)=> {
+            useProjectStore.setState({project: project});
+            unstable_batchedUpdates(() => {
+                useProjectsStore.getState().upsertProject(project);
+            })
+        });
+    }
+
     public static refreshProjects() {
         KaravanApi.getProjects((projects: Project[]) => {
             useProjectsStore.setState({projects: projects});
@@ -61,9 +110,9 @@ export class ProjectService {
         });
     }
 
-
-    public static refreshProjectData(environment?: string) {
+    public static refreshProjectData() {
         const project = useProjectStore.getState().project;
+        const environment = useAppConfigStore.getState().config.environment;
         KaravanApi.getProject(project.projectId, (project: Project) => {
             // ProjectEventBus.selectProject(project);
             KaravanApi.getTemplatesFiles((files: ProjectFile[]) => {
diff --git a/karavan-app/src/main/webui/src/api/ProjectStore.ts b/karavan-app/src/main/webui/src/api/ProjectStore.ts
index a524f6bc..4edce361 100644
--- a/karavan-app/src/main/webui/src/api/ProjectStore.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectStore.ts
@@ -34,6 +34,7 @@ export const useAppConfigStore = create<AppConfigState>((set) => ({
 interface ProjectsState {
     projects: Project[];
     setProjects: (projects: Project[]) => void;
+    upsertProject: (project: Project) => void;
 }
 
 export const useProjectsStore = create<ProjectsState>((set) => ({
@@ -43,10 +44,18 @@ export const useProjectsStore = create<ProjectsState>((set) => ({
             projects: ps,
         }), true);
     },
+    upsertProject: (project: Project) => {
+        set((state: ProjectsState) => ({
+            projects: state.projects.find(f => f.projectId === project.projectId) === undefined
+                ? [...state.projects, project]
+                : [...state.projects.filter(f => f.projectId !== project.projectId), project],
+        }), true);
+    }
 }))
 
 interface ProjectState {
     project: Project;
+    isPushing: boolean,
     operation: "create" | "select" | "delete" | "none" | "copy";
     setProject: (project: Project, operation:  "create" | "select" | "delete"| "none" | "copy") => void;
 }
@@ -54,6 +63,7 @@ interface ProjectState {
 export const useProjectStore = create<ProjectState>((set) => ({
     project: new Project(),
     operation: "none",
+    isPushing: false,
     setProject: (p: Project, o: "create" | "select" | "delete"| "none" | "copy") => {
         set((state: ProjectState) => ({
             project: p,
@@ -65,6 +75,7 @@ export const useProjectStore = create<ProjectState>((set) => ({
 interface FilesState {
     files: ProjectFile[];
     setFiles: (files: ProjectFile[]) => void;
+    upsertFile: (file: ProjectFile) => void;
 }
 
 export const useFilesStore = create<FilesState>((set) => ({
@@ -74,6 +85,13 @@ export const useFilesStore = create<FilesState>((set) => ({
             files: files,
         }), true);
     },
+    upsertFile: (file: ProjectFile) => {
+        set((state: FilesState) => ({
+            files: state.files.find(f => f.name === file.name) === undefined
+                ? [...state.files, file]
+                : [...state.files.filter(f => f.name !== file.name), file],
+        }), true);
+    }
 }))
 
 interface FileState {
diff --git a/karavan-app/src/main/webui/src/project/ProjectPage.tsx b/karavan-app/src/main/webui/src/project/ProjectPage.tsx
index edf0ef6c..d23d86c1 100644
--- a/karavan-app/src/main/webui/src/project/ProjectPage.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectPage.tsx
@@ -1,28 +1,20 @@
 import React, {useEffect, useState} from 'react';
 import {
     PageSection,
-    CodeBlockCode,
-    CodeBlock, Skeleton
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {KaravanApi} from "../api/KaravanApi";
 import FileSaver from "file-saver";
-import Editor from "@monaco-editor/react";
-import {PropertiesEditor} from "./PropertiesEditor";
-import {ProjectModel, ProjectProperty} from "karavan-core/lib/model/ProjectModel";
-import {ProjectModelApi} from "karavan-core/lib/api/ProjectModelApi";
-import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
 import {ProjectToolbar} from "./ProjectToolbar";
-import {EventBus} from "../designer/utils/EventBus";
 import {ProjectLog} from "./ProjectLog";
-import {AppConfig, ProjectFile, ProjectFileTypes} from "../api/ProjectModels";
-import {useAppConfigStore, useFileStore, useProjectStore} from "../api/ProjectStore";
-import {ProjectService} from "../api/ProjectService";
+import {ProjectFile, ProjectFileTypes} from "../api/ProjectModels";
+import {useFileStore, useProjectStore} from "../api/ProjectStore";
 import {MainToolbar} from "../common/MainToolbar";
 import {CreateFileModal} from "./CreateFileModal";
 import {DeleteFileModal} from "./DeleteFileModal";
 import {ProjectTitle} from "./ProjectTitle";
 import {ProjectPanel} from "./ProjectPanel";
+import {FileEditor} from "./file/FileEditor";
 
 export const ProjectPage = () => {
 
@@ -31,17 +23,7 @@ export const ProjectPage = () => {
     const {file, operation} = useFileStore();
     const [mode, setMode] = useState<"design" | "code">("design");
     const [key, setKey] = useState<string>('');
-    const [tab, setTab] = useState<string | number>('files');
     const {project} = useProjectStore();
-    const {config} = useAppConfigStore();
-
-    useEffect(() => {
-        onRefresh();
-    });
-
-    function onRefresh () {
-        ProjectService.refreshProjectData(config.environment);
-    }
 
     function post (file: ProjectFile)  {
         KaravanApi.postProjectFile(file, res => {
@@ -75,133 +57,18 @@ export const ProjectPage = () => {
         }
     }
 
-    function downloadImage () {
-        EventBus.sendCommand("downloadImage");
-    }
-
-    function addProperty() {
-        if (file) {
-            const project = file ? ProjectModelApi.propertiesToProject(file?.code) : ProjectModel.createNew();
-            const props = project.properties;
-            props.push(ProjectProperty.createNew("", ""))
-            save(file.name, ProjectModelApi.propertiesToString(props));
-            setKey(Math.random().toString());
-        }
-    }
 
     function tools () {
-        return <ProjectToolbar key={key}
-                               project={project}
+        return <ProjectToolbar
                                file={file}
                                mode={mode}
-                               isTemplates={false}
-                               isKamelets={false}
-                               addProperty={() => addProperty()}
-                               downloadImage={() => downloadImage()}
                                editAdvancedProperties={editAdvancedProperties}
                                setEditAdvancedProperties={checked => setEditAdvancedProperties(checked)}
                                setMode={mode => setMode(mode)}
                                setUploadModalOpen={() => setIsUploadModalOpen(isUploadModalOpen)}
-                               needCommit={false}
-                               onRefresh={onRefresh}
         />
     }
 
-    // function getDesigner () {
-    //     return (
-    //         file !== undefined &&
-    //         <KaravanDesigner
-    //             dark={false}
-    //             key={"key"}
-    //             filename={file.name}
-    //             yaml={file.code}
-    //             onSave={(name, yaml) => save(name, yaml)}
-    //             onSaveCustomCode={(name, code) => post(new ProjectFile(name + ".java", project.projectId, code, Date.now()))}
-    //             onGetCustomCode={(name, javaType) => {
-    //                 return new Promise<string | undefined>(resolve => resolve(files.filter(f => f.name === name + ".java")?.at(0)?.code))
-    //             }}
-    //         />
-    //     )
-    // }
-
-    function getEditor () {
-        const language = file?.name.split('.').pop();
-        return (
-            file !== undefined &&
-            <Editor
-                height="100vh"
-                defaultLanguage={language}
-                theme={'light'}
-                value={file.code}
-                className={'code-editor'}
-                onChange={(value, ev) => {
-                    if (value) {
-                        save(file?.name, value)
-                    }
-                }}
-            />
-        )
-    }
-
-    function deleteEntity  (type: 'pod' | 'deployment' | 'pipelinerun', name: string, environment: string)  {
-        switch (type) {
-            case "deployment":
-                KaravanApi.deleteDeployment(environment, name, (res: any) => {
-                    if (Array.isArray(res) && Array.from(res).length > 0)
-                        onRefresh();
-                });
-                break;
-            case "pod":
-                KaravanApi.deletePod(environment, name, (res: any) => {
-                    if (Array.isArray(res) && Array.from(res).length > 0)
-                        onRefresh();
-                });
-                break;
-            case "pipelinerun":
-                KaravanApi.stopPipelineRun(environment, name, (res: any) => {
-                    if (Array.isArray(res) && Array.from(res).length > 0)
-                        onRefresh();
-                });
-                break;
-        }
-    }
-
-    function getLogView ()  {
-        return (
-            <div>
-                {file !== undefined && file.code.length !== 0 &&
-                    <CodeBlock>
-                        <CodeBlockCode id="code-content" className="log-code">{file.code}</CodeBlockCode>
-                    </CodeBlock>}
-                {(file === undefined || file.code.length === 0) &&
-                    <div>
-                        <Skeleton width="25%" screenreaderText="Loading contents"/>
-                        <br/>
-                        <Skeleton width="33%"/>
-                        <br/>
-                        <Skeleton width="50%"/>
-                        <br/>
-                        <Skeleton width="66%"/>
-                        <br/>
-                        <Skeleton width="75%"/>
-                        <br/>
-                        <Skeleton/>
-                    </div>}
-            </div>
-        )
-    }
-
-    function getPropertiesEditor  ()  {
-        return (
-            file !== undefined &&
-            <PropertiesEditor key={key}
-                              editAdvanced={editAdvancedProperties}
-                              file={file}
-                              onSave={(name, code) => save(name, code)}
-            />
-        )
-    }
-
     function isBuildIn(): boolean {
         return ['kamelets', 'templates'].includes(project.projectId);
     }
@@ -210,28 +77,6 @@ export const ProjectPage = () => {
         return project.projectId === 'kamelets';
     }
 
-    function isTemplatesProject(): boolean {
-        return project.projectId === 'templates';
-    }
-
-    function getFilePanel() {
-        const isYaml = file !== undefined && file.name.endsWith("yaml");
-        const isIntegration = isYaml && file?.code && CamelDefinitionYaml.yamlIsIntegration(file.code);
-        const isProperties = file !== undefined && file.name.endsWith("properties");
-        const isLog = file !== undefined && file.name.endsWith("log");
-        const isCode = file !== undefined && (file.name.endsWith("java") || file.name.endsWith("groovy") || file.name.endsWith("json"));
-        const showDesigner = isYaml && isIntegration && mode === 'design';
-        const showEditor = isCode || (isYaml && !isIntegration) || (isYaml && mode === 'code');
-        return (
-            <>
-                {/*{showDesigner && getDesigner()}*/}
-                {showEditor && getEditor()}
-                {isLog && getLogView()}
-                {isProperties && getPropertiesEditor()}
-            </>
-        )
-    }
-    console.log(operation, file)
     const types = isBuildIn()
         ? (isKameletsProject() ? ['KAMELET'] : ['CODE', 'PROPERTIES'])
         : ProjectFileTypes.filter(p => !['PROPERTIES', 'LOG', 'KAMELET'].includes(p.name)).map(p => p.name);
@@ -241,7 +86,7 @@ export const ProjectPage = () => {
                 <MainToolbar title={<ProjectTitle/>} tools={tools()}/>
             </PageSection>
             {file === undefined && operation !== 'select' && <ProjectPanel/>}
-            {file !== undefined && operation === 'select' && getFilePanel()}
+            {file !== undefined && operation === 'select' && <FileEditor/>}
             <ProjectLog/>
             <CreateFileModal types={types}/>
             <DeleteFileModal />
diff --git a/karavan-app/src/main/webui/src/project/ProjectPanel.tsx b/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
index 8fa32f2d..fe21736d 100644
--- a/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
@@ -1,19 +1,29 @@
-import React, {useState} from 'react';
+import React, {useEffect, useState} from 'react';
 import {
     Flex,
     FlexItem, Tabs, Tab
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {FilesTab} from "./files/FilesTab";
-import {useProjectStore} from "../api/ProjectStore";
+import {useAppConfigStore, useProjectStore} from "../api/ProjectStore";
 import {DashboardTab} from "./dashboard/DashboardTab";
 import {TraceTab} from "./trace/TraceTab";
 import {ProjectPipelineTab} from "./pipeline/ProjectPipelineTab";
+import {ProjectService} from "../api/ProjectService";
 
 export const ProjectPanel = () => {
 
     const [tab, setTab] = useState<string | number>('files');
     const {project} = useProjectStore();
+    const {config} = useAppConfigStore();
+
+    useEffect(() => {
+        onRefresh();
+    });
+
+    function onRefresh () {
+        ProjectService.refreshProjectData();
+    }
 
     function isBuildIn(): boolean {
         return ['kamelets', 'templates'].includes(project.projectId);
diff --git a/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx b/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
index 0e7a75c4..8c27e40c 100644
--- a/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
@@ -32,59 +32,70 @@ import {RunnerToolbar} from "./RunnerToolbar";
 import {Project, ProjectFile} from "../api/ProjectModels";
 import {ProjectEventBus} from "../api/ProjectEventBus";
 import {useAppConfigStore, useFilesStore, useFileStore, useProjectStore} from "../api/ProjectStore";
+import {EventBus} from "../designer/utils/EventBus";
+import {ProjectService} from "../api/ProjectService";
 
 interface Props {
-    project: Project,
-    needCommit: boolean,
-    isTemplates: boolean,
-    isKamelets: boolean,
     file?: ProjectFile,
     mode: "design" | "code",
     editAdvancedProperties: boolean,
-    addProperty: () => void,
-    downloadImage: () => void,
     setUploadModalOpen: () => void,
     setEditAdvancedProperties: (checked: boolean) => void,
     setMode: (mode: "design" | "code") => void,
-    onRefresh: () => void,
 }
 
 export const ProjectToolbar = (props: Props) => {
 
-    const [isPushing, setIsPushing] = useState(false);
     const [commitMessageIsOpen, setCommitMessageIsOpen] = useState(false);
     const [commitMessage, setCommitMessage] = useState('');
     const [currentRunner, setCurrentRunner] = useState('');
-    const [podName, setPodName] = useState(props.project.projectId + '-runner');
     const [isJbangRunning, setJbangIsRunning] = useState(false);
     const [isRunning, setIsRunning] = useState(false);
     const [isDeletingPod, setIsDeletingPod] = useState(false);
     const [isReloadingPod, setIsReloadingPod] = useState(false);
-    const {project} = useProjectStore();
+    const {project, isPushing} = useProjectStore();
     const {files} = useFilesStore();
     const {config} = useAppConfigStore();
 
     useEffect(() => {
+        console.log("ProjectToolbar useEffect", isPushing, project.lastCommitTimestamp)
         const sub1 = ProjectEventBus.onCurrentRunner()?.subscribe((result) => {
             setCurrentRunner(result || '');
-            setJbangIsRunning(result === props.project.name);
+            setJbangIsRunning(result === project.name);
         });
         return () => {
             sub1.unsubscribe();
         };
     });
 
+    function podName() {
+        return project.projectId + '-runner';
+    }
+
     function needCommit(): boolean {
         return project ? files.filter(f => f.lastUpdate > project.lastCommitTimestamp).length > 0 : false;
     }
 
+    function downloadImage () {
+        EventBus.sendCommand("downloadImage");
+    }
+
+    function addProperty() {
+        // if (file) {
+        //     const project = file ? ProjectModelApi.propertiesToProject(file?.code) : ProjectModel.createNew();
+        //     const props = project.properties;
+        //     props.push(ProjectProperty.createNew("", ""))
+        //     save(file.name, ProjectModelApi.propertiesToString(props));
+        //     setKey(Math.random().toString());
+        // }
+    }
+
     function jbangRun() {
         setJbangIsRunning(true);
-        KaravanApi.runProject(props.project, res => {
+        KaravanApi.runProject(project, res => {
             if (res.status === 200 || res.status === 201) {
-                ProjectEventBus.setCurrentRunner(props.project.name);
+                ProjectEventBus.setCurrentRunner(project.name);
                 setJbangIsRunning(false);
-                setPodName(res.data);
                 ProjectEventBus.showLog('container', res.data, config.environment)
             } else {
                 // Todo notification
@@ -96,7 +107,7 @@ export const ProjectToolbar = (props: Props) => {
 
     function reloadRunner() {
         setIsReloadingPod(true);
-        KaravanApi.getRunnerReload(props.project.projectId, res => {
+        KaravanApi.getRunnerReload(project.projectId, res => {
             if (res.status === 200 || res.status === 201) {
                 setIsReloadingPod(false);
             } else {
@@ -109,7 +120,7 @@ export const ProjectToolbar = (props: Props) => {
     function deleteRunner() {
         ProjectEventBus.setCurrentRunner(undefined);
         setIsDeletingPod(true);
-        KaravanApi.deleteRunner(podName, false, res => {
+        KaravanApi.deleteRunner(podName(), false, res => {
             if (res.status === 202) {
                 setIsDeletingPod(false);
             } else {
@@ -120,20 +131,8 @@ export const ProjectToolbar = (props: Props) => {
     }
 
     function push () {
-        setIsPushing(true);
         setCommitMessageIsOpen(false);
-        const params = {
-            "projectId": props.project.projectId,
-            "message": commitMessage
-        };
-        KaravanApi.push(params, res => {
-            if (res.status === 200 || res.status === 201) {
-                setIsPushing(false);
-                props.onRefresh();
-            } else {
-                // Todo notification
-            }
-        });
+        ProjectService.pushProject(project, commitMessage);
     }
 
     function getDate(lastUpdate: number): string {
@@ -146,8 +145,7 @@ export const ProjectToolbar = (props: Props) => {
     }
 
     function getLastUpdatePanel() {
-        const {project, needCommit} = props;
-        const color = needCommit ? "grey" : "green";
+        const color = needCommit() ? "grey" : "green";
         const commit = project?.lastCommit;
         return (
             <Flex direction={{default: "row"}} justifyContent={{default: "justifyContentFlexStart"}}>
@@ -169,7 +167,7 @@ export const ProjectToolbar = (props: Props) => {
     }
 
     function getTemplatesToolbar() {
-        const {file,needCommit, editAdvancedProperties, setUploadModalOpen} = props;
+        const {file, editAdvancedProperties, setUploadModalOpen} = props;
         const isFile = file !== undefined;
         const isProperties = file !== undefined && file.name.endsWith("properties");
         return <Toolbar id="toolbar-group-types">
@@ -183,7 +181,7 @@ export const ProjectToolbar = (props: Props) => {
                             <Tooltip content="Commit and push to git" position={"bottom"}>
                                 <Button isLoading={isPushing ? true : undefined}
                                         isSmall
-                                        variant={needCommit ? "primary" : "secondary"}
+                                        variant={needCommit() ? "primary" : "secondary"}
                                         className="project-button"
                                         icon={!isPushing ? <PushIcon/> : <div></div>}
                                         onClick={() => setCommitMessageIsOpen(true)}>
@@ -215,8 +213,8 @@ export const ProjectToolbar = (props: Props) => {
     }
 
     function getProjectToolbar() {
-        const {file,needCommit, mode, editAdvancedProperties, project,
-            addProperty, setEditAdvancedProperties, downloadImage, setUploadModalOpen} = props;
+        const {file, mode, editAdvancedProperties,
+             setEditAdvancedProperties, setUploadModalOpen} = props;
         const isFile = file !== undefined;
         const isYaml = file !== undefined && file.name.endsWith("yaml");
         const isIntegration = isYaml && file?.code && CamelDefinitionYaml.yamlIsIntegration(file.code);
@@ -231,7 +229,7 @@ export const ProjectToolbar = (props: Props) => {
                         <Tooltip content="Commit and push to git" position={"bottom-end"}>
                             <Button isLoading={isPushing ? true : undefined}
                                     isSmall
-                                    variant={needCommit ? "primary" : "secondary"}
+                                    variant={needCommit() ? "primary" : "secondary"}
                                     className="project-button"
                                     icon={!isPushing ? <PushIcon/> : <div></div>}
                                     onClick={() => {
@@ -299,7 +297,15 @@ export const ProjectToolbar = (props: Props) => {
         )
     }
 
-    const {isTemplates} = props;
+    function isKameletsProject(): boolean {
+        return project.projectId === 'kamelets';
+    }
+
+    function isTemplatesProject(): boolean {
+        return project.projectId === 'templates';
+    }
+
+    const isTemplates = isTemplatesProject();
     return  (
          <>
             {isTemplates && getTemplatesToolbar()}
diff --git a/karavan-app/src/main/webui/src/project/PropertiesEditor.tsx b/karavan-app/src/main/webui/src/project/PropertiesEditor.tsx
deleted file mode 100644
index bf2f4609..00000000
--- a/karavan-app/src/main/webui/src/project/PropertiesEditor.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react';
-import {
-    PageSection,
-} from '@patternfly/react-core';
-import '../designer/karavan.css';
-import {PropertiesTable} from "./PropertiesTable";
-import {ProjectModel} from "karavan-core/lib/model/ProjectModel";
-import {ProjectModelApi} from "karavan-core/lib/api/ProjectModelApi";
-import {ProjectFile} from "../api/ProjectModels";
-
-interface Props {
-    file: ProjectFile,
-    editAdvanced: boolean,
-    onSave?: (filename: string, code: string) => void
-}
-
-interface State {
-    project: ProjectModel,
-    file: ProjectFile,
-}
-
-export class PropertiesEditor extends React.Component<Props, State> {
-
-    public state: State = {
-        project: this.props.file ? ProjectModelApi.propertiesToProject(this.props.file?.code) : ProjectModel.createNew(),
-        file: this.props.file,
-    }
-
-    render() {
-        const file = this.state.file;
-        const project = file ? ProjectModelApi.propertiesToProject(file?.code) : ProjectModel.createNew();
-        return (
-            <PageSection isFilled className="kamelets-page" padding={{default: file !== undefined ? 'noPadding' : 'padding'}}>
-                <PropertiesTable
-                    editAdvanced={this.props.editAdvanced}
-                    properties={project.properties}
-                    onChange={properties => this.props.onSave?.call(this, file.name, ProjectModelApi.propertiesToString(properties))}
-                />
-            </PageSection>
-        )
-    }
-}
diff --git a/karavan-app/src/main/webui/src/project/file/FileEditor.tsx b/karavan-app/src/main/webui/src/project/file/FileEditor.tsx
new file mode 100644
index 00000000..c58008e2
--- /dev/null
+++ b/karavan-app/src/main/webui/src/project/file/FileEditor.tsx
@@ -0,0 +1,143 @@
+/*
+ * 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 {
+    CodeBlockCode,
+    CodeBlock, Skeleton
+} from '@patternfly/react-core';
+import '../../designer/karavan.css';
+import Editor from "@monaco-editor/react";
+import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
+import {ProjectFile, ProjectFileTypes} from "../../api/ProjectModels";
+import {useAppConfigStore, useFilesStore, useFileStore, useProjectStore} from "../../api/ProjectStore";
+import {KaravanDesigner} from "../../designer/KaravanDesigner";
+import {ProjectService} from "../../api/ProjectService";
+import {PropertiesTable} from "./PropertiesTable";
+
+export const FileEditor = () => {
+
+    const [editAdvancedProperties] = useState<boolean>(false);
+    const {file, operation} = useFileStore();
+    const [mode, setMode] = useState<"design" | "code">("design");
+    const [key, setKey] = useState<string>('');
+    const {project} = useProjectStore();
+    const {config} = useAppConfigStore();
+
+    function save (name: string, code: string) {
+        if (file) {
+            file.code = code;
+            ProjectService.saveFile(file);
+        }
+    }
+
+    function onGetCustomCode (name: string, javaType: string): Promise<string | undefined> {
+        const files = useFilesStore.getState().files;
+        return new Promise<string | undefined>(resolve => resolve(files.filter(f => f.name === name + ".java")?.at(0)?.code));
+    }
+
+    function getDesigner () {
+        return (
+            file !== undefined &&
+            <KaravanDesigner
+                dark={false}
+                key={"key"}
+                filename={file.name}
+                yaml={file.code}
+                onSave={(name, yaml) => save(name, yaml)}
+                onSaveCustomCode={(name, code) =>
+                    ProjectService.saveFile(new ProjectFile(name + ".java", project.projectId, code, Date.now()))}
+                onGetCustomCode={onGetCustomCode}
+            />
+        )
+    }
+
+    function getEditor () {
+        const language = file?.name.split('.').pop();
+        return (
+            file !== undefined &&
+            <Editor
+                height="100vh"
+                defaultLanguage={language}
+                theme={'light'}
+                value={file.code}
+                className={'code-editor'}
+                onChange={(value, ev) => {
+                    if (value) {
+                        save(file?.name, value)
+                    }
+                }}
+            />
+        )
+    }
+
+    function getLogView ()  {
+        return (
+            <div>
+                {file !== undefined && file.code.length !== 0 &&
+                    <CodeBlock>
+                        <CodeBlockCode id="code-content" className="log-code">{file.code}</CodeBlockCode>
+                    </CodeBlock>}
+                {(file === undefined || file.code.length === 0) &&
+                    <div>
+                        <Skeleton width="25%" screenreaderText="Loading contents"/>
+                        <br/>
+                        <Skeleton width="33%"/>
+                        <br/>
+                        <Skeleton width="50%"/>
+                        <br/>
+                        <Skeleton width="66%"/>
+                        <br/>
+                        <Skeleton width="75%"/>
+                        <br/>
+                        <Skeleton/>
+                    </div>}
+            </div>
+        )
+    }
+
+
+    function isBuildIn(): boolean {
+        return ['kamelets', 'templates'].includes(project.projectId);
+    }
+
+    function isKameletsProject(): boolean {
+        return project.projectId === 'kamelets';
+    }
+
+    function isTemplatesProject(): boolean {
+        return project.projectId === 'templates';
+    }
+
+    const types = isBuildIn()
+        ? (isKameletsProject() ? ['KAMELET'] : ['CODE', 'PROPERTIES'])
+        : ProjectFileTypes.filter(p => !['PROPERTIES', 'LOG', 'KAMELET'].includes(p.name)).map(p => p.name);
+    const isYaml = file !== undefined && file.name.endsWith("yaml");
+    const isIntegration = isYaml && file?.code && CamelDefinitionYaml.yamlIsIntegration(file.code);
+    const isProperties = file !== undefined && file.name.endsWith("properties");
+    const isLog = file !== undefined && file.name.endsWith("log");
+    const isCode = file !== undefined && (file.name.endsWith("java") || file.name.endsWith("groovy") || file.name.endsWith("json"));
+    const showDesigner = isYaml && isIntegration && mode === 'design';
+    const showEditor = isCode || (isYaml && !isIntegration) || (isYaml && mode === 'code');
+    return (
+        <>
+            {showDesigner && getDesigner()}
+            {showEditor && getEditor()}
+            {isLog && getLogView()}
+            {isProperties && file !== undefined && <PropertiesTable/>}
+        </>
+    )
+}
diff --git a/karavan-app/src/main/webui/src/project/PropertiesTable.tsx b/karavan-app/src/main/webui/src/project/file/PropertiesTable.tsx
similarity index 53%
rename from karavan-app/src/main/webui/src/project/PropertiesTable.tsx
rename to karavan-app/src/main/webui/src/project/file/PropertiesTable.tsx
index 72287a35..5b58cfeb 100644
--- a/karavan-app/src/main/webui/src/project/PropertiesTable.tsx
+++ b/karavan-app/src/main/webui/src/project/file/PropertiesTable.tsx
@@ -14,85 +14,90 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
+import React, {useState} from 'react';
 import {
     Button,
     Modal,
     PageSection,
     TextInput
 } from '@patternfly/react-core';
-import '../designer/karavan.css';
+import '../../designer/karavan.css';
 import {TableComposable, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
 import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
-import {ProjectProperty} from "karavan-core/lib/model/ProjectModel";
+import {ProjectModel, ProjectProperty} from "karavan-core/lib/model/ProjectModel";
+import {useFileStore} from "../../api/ProjectStore";
+import {ProjectService} from "../../api/ProjectService";
+import {ProjectModelApi} from "karavan-core/lib/api/ProjectModelApi";
 
-interface Props {
-    properties: ProjectProperty[]
-    onChange?: (properties: ProjectProperty[]) => void
-    editAdvanced: boolean
-}
+export const PropertiesTable = () => {
 
-interface State {
-    properties: ProjectProperty[]
-    showDeleteConfirmation: boolean
-    deleteId?: string
-}
+    const {file, operation} = useFileStore();
+    const [showDeleteConfirmation, setShowDeleteConfirmation] = useState<boolean>(false);
+    const [editAdvanced, setEditAdvanced] = useState<boolean>(false);
+    const [deleteId, setDeleteId] = useState<string | undefined>(undefined);
+    const [properties, setProperties] = useState<ProjectProperty[]>([]);
 
-export class PropertiesTable extends React.Component<Props, State> {
-
-    public state: State = {
-        properties: this.props.properties,
-        showDeleteConfirmation: false,
+    function save (props: ProjectProperty[]) {
+        console.log("save")
+        if (file) {
+            file.code = ProjectModelApi.propertiesToString(props);
+            console.log("save", file)
+            ProjectService.saveFile(file);
+        }
     }
 
-    sendUpdate = (props: ProjectProperty[]) => {
-        this.props.onChange?.call(this, props);
+    function getProjectModel (): ProjectModel {
+        return file ? ProjectModelApi.propertiesToProject(file?.code) : ProjectModel.createNew()
     }
 
-    changeProperty(p: ProjectProperty, field: "key" | "value", val?: string) {
+    function changeProperty(p: ProjectProperty, field: "key" | "value", val?: string) {
         const key: string = field === 'key' && val !== undefined ? val : p.key;
         const value: any = field === 'value' ? val : p.value;
         const property: ProjectProperty = {id: p.id, key: key, value: value};
-        const props = this.state.properties.map(prop => prop.id === property.id ? property : prop);
-        this.setState({properties: props});
-        this.sendUpdate(props);
+        const properties = getProjectModel().properties;
+        const props = properties.map(prop => prop.id === property.id ? property : prop);
+        save(props);
     }
 
-    startDelete(id: string) {
-        this.setState({showDeleteConfirmation: true, deleteId: id});
+    function startDelete(id: string) {
+        console.log("startDelete", id)
+        setShowDeleteConfirmation(true);
+        setDeleteId(id);
     }
 
-    confirmDelete() {
-        const props = this.state.properties.filter(p => p.id !== this.state.deleteId);
-        this.setState({properties: props, showDeleteConfirmation: false, deleteId: undefined});
-        this.sendUpdate(props);
+    function confirmDelete() {
+        console.log("confirmDelete")
+        const properties = getProjectModel().properties;
+        const props = properties.filter(p => p.id !== deleteId);
+        save(props);
+        setShowDeleteConfirmation(false);
+        setDeleteId(undefined);
     }
 
-    getDeleteConfirmation() {
+    function getDeleteConfirmation() {
         return (<Modal
             className="modal-delete"
             title="Confirmation"
-            isOpen={this.state.showDeleteConfirmation}
-            onClose={() => this.setState({showDeleteConfirmation: false})}
+            isOpen={showDeleteConfirmation}
+            onClose={() => setShowDeleteConfirmation(false)}
             actions={[
-                <Button key="confirm" variant="primary" onClick={e => this.confirmDelete()}>Delete</Button>,
+                <Button key="confirm" variant="primary" onClick={e => confirmDelete()}>Delete</Button>,
                 <Button key="cancel" variant="link"
-                        onClick={e => this.setState({showDeleteConfirmation: false})}>Cancel</Button>
+                        onClick={e => setShowDeleteConfirmation(false)}>Cancel</Button>
             ]}
-            onEscapePress={e => this.setState({showDeleteConfirmation: false})}>
+            onEscapePress={e => setShowDeleteConfirmation(false)}>
             <div>Delete property?</div>
         </Modal>)
     }
 
-    getTextInputField(property: ProjectProperty, field: "key" | "value", readOnly: boolean) {
-        return (<TextInput isDisabled={readOnly} isRequired={true} className="text-field" type={"text"} id={"key"} name={"key"}
+    function getTextInputField(property: ProjectProperty, field: "key" | "value", readOnly: boolean) {
+        return (<TextInput isDisabled={readOnly} isRequired={true} className="text-field" type={"text"} id={field + "-" + property.key}
                            value={field === "key" ? property.key : property.value}
-                           onChange={val => this.changeProperty(property, field, val)}/>)
+                           onChange={val => changeProperty(property, field, val)}/>)
     }
 
-    render() {
-        const properties = this.state.properties;
-        return (
+    return (
+        <PageSection isFilled className="kamelets-page" padding={{default: file !== undefined ? 'noPadding' : 'padding'}}>
             <PageSection padding={{default: "noPadding"}}>
                 {properties.length > 0 &&
                     <TableComposable aria-label="Property table" variant='compact' borders={false}
@@ -106,21 +111,21 @@ export class PropertiesTable extends React.Component<Props, State> {
                         </Thead>
                         <Tbody>
                             {properties.map((property, idx: number) => {
-                                const readOnly = (property.key.startsWith("camel.jbang") || property.key.startsWith("camel.karavan")) && !this.props.editAdvanced;
+                                const readOnly = (property.key.startsWith("camel.jbang") || property.key.startsWith("camel.karavan")) && !editAdvanced;
                                 return (
                                     <Tr key={property.id}>
-                                        <Td noPadding width={10} dataLabel="key">{this.getTextInputField(property, "key", readOnly)}</Td>
-                                        <Td noPadding width={20} dataLabel="value">{this.getTextInputField(property, "value", readOnly)}</Td>
+                                        <Td noPadding width={10} dataLabel="key">{getTextInputField(property, "key", readOnly)}</Td>
+                                        <Td noPadding width={20} dataLabel="value">{getTextInputField(property, "value", readOnly)}</Td>
                                         <Td noPadding isActionCell dataLabel="delete" className="delete-cell">
                                             {!readOnly && <Button variant={"plain"} icon={<DeleteIcon/>} className={"delete-button"}
-                                                                  onClick={event => this.startDelete(property.id)}/>}
+                                                                  onClick={event => startDelete(property.id)}/>}
                                         </Td>
                                     </Tr>
                                 )})}
                         </Tbody>
                     </TableComposable>}
-                {this.state.showDeleteConfirmation && this.getDeleteConfirmation()}
+                {showDeleteConfirmation && getDeleteConfirmation()}
             </PageSection>
-        )
-    }
+        </PageSection>
+    )
 }
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/project/files/FilesTab.tsx b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
index 3ae79da5..28e25c10 100644
--- a/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
+++ b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
@@ -6,32 +6,36 @@ import {
     EmptyState,
     EmptyStateVariant,
     EmptyStateIcon,
-    Title, PageSection, PanelHeader, Panel, Tooltip,
+    Title, PageSection, PanelHeader, Panel, Tooltip, Label,
 } from '@patternfly/react-core';
 import '../../designer/karavan.css';
 import {TableComposable, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
 import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
 import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
-import {useFilesStore, useFileStore} from "../../api/ProjectStore";
+import {useFilesStore, useFileStore, useProjectStore} from "../../api/ProjectStore";
 import {getProjectFileType, ProjectFile} from "../../api/ProjectModels";
 import {FileToolbar} from "./FilesToolbar";
 import DownloadIcon from "@patternfly/react-icons/dist/esm/icons/download-icon";
 import FileSaver from "file-saver";
 
-
 export const FilesTab = () => {
 
     const {files} = useFilesStore();
+    const {project} = useProjectStore();
 
     function getDate(lastUpdate: number): string {
         if (lastUpdate) {
             const date = new Date(lastUpdate);
-            return date.toDateString() + ' ' + date.toLocaleTimeString();
+            return date.toISOString().slice(0, 19).replace('T',' ');
         } else {
             return "N/A"
         }
     }
 
+    function needCommit(lastUpdate: number): boolean {
+        return lastUpdate > project.lastCommitTimestamp;
+    }
+
     function download (file: ProjectFile) {
         if (file) {
             const type = file.name.endsWith("yaml") ? "application/yaml;charset=utf-8" : undefined;
@@ -72,7 +76,12 @@ export const FilesTab = () => {
                                 </Button>
                             </Td>
                             <Td>
-                                {getDate(file.lastUpdate)}
+                                {needCommit(file.lastUpdate) &&
+                                    <Tooltip content="Updated after last commit" position={"right"}>
+                                        <Label color="grey">{getDate(file.lastUpdate)}</Label>
+                                    </Tooltip>
+                                }
+                                {!needCommit(file.lastUpdate) && getDate(file.lastUpdate)}
                             </Td>
                             <Td modifier={"fitContent"}>
                                 {file.projectId !== 'templates' &&
diff --git a/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx b/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
index 2ef4c092..00835fe4 100644
--- a/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
+++ b/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
@@ -1,3 +1,19 @@
+/*
+ * 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,
diff --git a/karavan-app/src/main/webui/src/project/files/UploadModal.tsx b/karavan-app/src/main/webui/src/project/files/UploadModal.tsx
index a2af3243..d0d73e65 100644
--- a/karavan-app/src/main/webui/src/project/files/UploadModal.tsx
+++ b/karavan-app/src/main/webui/src/project/files/UploadModal.tsx
@@ -1,3 +1,19 @@
+/*
+ * 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 {
     TextInput,
diff --git a/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx b/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
index 6cd7e4fd..e2209f89 100644
--- a/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
+++ b/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import '../../designer/karavan.css';
-import {ProjectStatus} from "../ProjectStatus";
+import {ProjectStatus} from "./ProjectStatus";
 import {PageSection} from "@patternfly/react-core";
 import {useAppConfigStore, useProjectStore} from "../../api/ProjectStore";
 
diff --git a/karavan-app/src/main/webui/src/project/ProjectStatus.tsx b/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
similarity index 98%
rename from karavan-app/src/main/webui/src/project/ProjectStatus.tsx
rename to karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
index 3e57ebe9..27c898dc 100644
--- a/karavan-app/src/main/webui/src/project/ProjectStatus.tsx
+++ b/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
@@ -6,16 +6,16 @@ import {
     DescriptionListGroup,
     DescriptionListDescription, Spinner, Tooltip, Flex, FlexItem, LabelGroup, Label, Modal, Badge, CardBody, Card
 } from '@patternfly/react-core';
-import '../designer/karavan.css';
-import {KaravanApi} from "../api/KaravanApi";
+import '../../designer/karavan.css';
+import {KaravanApi} from "../../api/KaravanApi";
 import BuildIcon from "@patternfly/react-icons/dist/esm/icons/build-icon";
 import RolloutIcon from "@patternfly/react-icons/dist/esm/icons/process-automation-icon";
 import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
 import DownIcon from "@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
 import ClockIcon from "@patternfly/react-icons/dist/esm/icons/clock-icon";
 import DeleteIcon from "@patternfly/react-icons/dist/esm/icons/times-circle-icon";
-import {CamelStatus, DeploymentStatus, PipelineStatus, PodStatus, Project} from "../api/ProjectModels";
-import {ProjectEventBus} from "../api/ProjectEventBus";
+import {CamelStatus, DeploymentStatus, PipelineStatus, PodStatus, Project} from "../../api/ProjectModels";
+import {ProjectEventBus} from "../../api/ProjectEventBus";
 
 interface Props {
     project: Project,
diff --git a/karavan-app/src/main/webui/src/project/trace/RunnerInfoTrace.tsx b/karavan-app/src/main/webui/src/project/trace/RunnerInfoTrace.tsx
index 8f0f655d..cf2a37ce 100644
--- a/karavan-app/src/main/webui/src/project/trace/RunnerInfoTrace.tsx
+++ b/karavan-app/src/main/webui/src/project/trace/RunnerInfoTrace.tsx
@@ -1,3 +1,19 @@
+/*
+ * 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 {
     Bullseye,
diff --git a/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceModal.tsx b/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceModal.tsx
index 4f83cedc..6940f8a8 100644
--- a/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceModal.tsx
+++ b/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceModal.tsx
@@ -1,3 +1,19 @@
+/*
+ * 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 {
     Flex, FlexItem,
diff --git a/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceNode.tsx b/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceNode.tsx
index 7dbb4c45..975d7df5 100644
--- a/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceNode.tsx
+++ b/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceNode.tsx
@@ -1,3 +1,19 @@
+/*
+ * 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 {
     CodeBlock, CodeBlockCode, DataList, DataListCell, DataListItem, DataListItemCells, DataListItemRow, DataListWrapModifier,
diff --git a/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx b/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx
index 31f1c0c9..1c108f8b 100644
--- a/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx
+++ b/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx
@@ -1,3 +1,19 @@
+/*
+ * 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 { PageSection
 } from '@patternfly/react-core';


[camel-karavan] 05/06: Merging for #809

Posted by ma...@apache.org.
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 4ff9f403194d19d01420abfb78009c4415ff2426
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Sat Jul 1 13:14:38 2023 -0400

    Merging for #809
---
 .../src/main/webui/src/api/ProjectEventBus.ts      |  11 --
 .../src/main/webui/src/api/ProjectService.ts       |  24 +++-
 karavan-app/src/main/webui/src/api/ProjectStore.ts |   6 +-
 .../src/main/webui/src/project/ProjectToolbar.tsx  | 119 +++++++---------
 .../src/main/webui/src/project/RunnerToolbar.tsx   | 152 +++++++++------------
 .../webui/src/project/dashboard/DashboardTab.tsx   |   2 +-
 .../src/project/dashboard/RunnerInfoContext.tsx    |   2 -
 7 files changed, 144 insertions(+), 172 deletions(-)

diff --git a/karavan-app/src/main/webui/src/api/ProjectEventBus.ts b/karavan-app/src/main/webui/src/api/ProjectEventBus.ts
index 142785b8..81e3a944 100644
--- a/karavan-app/src/main/webui/src/api/ProjectEventBus.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectEventBus.ts
@@ -18,10 +18,8 @@ import {BehaviorSubject, Subject} from 'rxjs';
 import {Project} from "./ProjectModels";
 
 const selectedProject = new BehaviorSubject<Project | undefined>(undefined);
-const currentRunner = new BehaviorSubject<string | undefined>(undefined);
 const currentFile = new BehaviorSubject<string | undefined>(undefined);
 const showLog = new BehaviorSubject<ShowLogCommand | undefined>(undefined);
-const showTrace = new BehaviorSubject<ShowTraceCommand | undefined>(undefined);
 const refreshTrace = new BehaviorSubject<boolean>(false);
 const mode = new BehaviorSubject<"design" | "code">("design");
 const config = new BehaviorSubject<any>({});
@@ -55,19 +53,10 @@ export const ProjectEventBus = {
     selectProject: (project: Project) => selectedProject.next(project),
     onSelectProject: () => selectedProject.asObservable(),
 
-    setCurrentRunner: (name: string | undefined) => currentRunner.next(name),
-    onCurrentRunner: () => currentRunner.asObservable(),
-
-    selectProjectFile: (fileName: string) => currentFile.next(fileName),
-    onSelectProjectFile: () => currentFile.asObservable(),
-
     showLog: (type: 'container' | 'pipeline', name: string, environment: string, show: boolean = true) =>
         showLog.next(new ShowLogCommand(type, name, environment, show)),
     onShowLog: () => showLog.asObservable(),
 
-    showTrace: (name: string, show: boolean = true) => showTrace.next(new ShowTraceCommand(name, show)),
-    onShowTrace: () => showTrace.asObservable(),
-
     refreshTrace: (refresh: boolean) => refreshTrace.next(refresh),
     onRefreshTrace: () => refreshTrace.asObservable(),
 
diff --git a/karavan-app/src/main/webui/src/api/ProjectService.ts b/karavan-app/src/main/webui/src/api/ProjectService.ts
index feca6ec3..5f3aeccb 100644
--- a/karavan-app/src/main/webui/src/api/ProjectService.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectService.ts
@@ -1,5 +1,5 @@
 import {KaravanApi} from "./KaravanApi";
-import {DeploymentStatus, Project, ProjectFile} from "./ProjectModels";
+import {DeploymentStatus, PodStatus, Project, ProjectFile} from "./ProjectModels";
 import {TemplateApi} from "karavan-core/lib/api/TemplateApi";
 import {KubernetesAPI} from "../designer/utils/KubernetesAPI";
 import { unstable_batchedUpdates } from 'react-dom'
@@ -14,6 +14,28 @@ import {
 
 export class ProjectService {
 
+    public static runProject(project: Project) {
+        KaravanApi.runProject(project, res => {
+            if (res.status === 200 || res.status === 201) {
+            } else {
+                // Todo notification
+            }
+        });
+    }
+
+    public static getRunnerPodStatus(project: Project) {
+        const projectId = project.projectId;
+        const name = projectId + "-runner";
+        KaravanApi.getRunnerPodStatus(projectId, name, res => {
+            if (res.status === 200) {
+                useProjectStore.setState({podStatus: res.data});
+            } else {
+                useProjectStore.setState({podStatus: new PodStatus()});
+                // Todo notification
+            }
+        });
+    }
+
     public static pushProject (project: Project, commitMessage: string) {
         useProjectStore.setState({isPushing: true})
         const params = {
diff --git a/karavan-app/src/main/webui/src/api/ProjectStore.ts b/karavan-app/src/main/webui/src/api/ProjectStore.ts
index 4edce361..c323ddac 100644
--- a/karavan-app/src/main/webui/src/api/ProjectStore.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectStore.ts
@@ -16,7 +16,7 @@
  */
 
 import {create} from 'zustand'
-import {AppConfig, DeploymentStatus, Project, ProjectFile} from "./ProjectModels";
+import {AppConfig, DeploymentStatus, PodStatus, Project, ProjectFile} from "./ProjectModels";
 
 interface AppConfigState {
     config: AppConfig;
@@ -56,6 +56,8 @@ export const useProjectsStore = create<ProjectsState>((set) => ({
 interface ProjectState {
     project: Project;
     isPushing: boolean,
+    isRunning: boolean,
+    podStatus: PodStatus,
     operation: "create" | "select" | "delete" | "none" | "copy";
     setProject: (project: Project, operation:  "create" | "select" | "delete"| "none" | "copy") => void;
 }
@@ -64,6 +66,8 @@ export const useProjectStore = create<ProjectState>((set) => ({
     project: new Project(),
     operation: "none",
     isPushing: false,
+    isRunning: false,
+    podStatus: new PodStatus(),
     setProject: (p: Project, o: "create" | "select" | "delete"| "none" | "copy") => {
         set((state: ProjectState) => ({
             project: p,
diff --git a/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx b/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
index 8c27e40c..81368c14 100644
--- a/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
@@ -48,24 +48,26 @@ export const ProjectToolbar = (props: Props) => {
 
     const [commitMessageIsOpen, setCommitMessageIsOpen] = useState(false);
     const [commitMessage, setCommitMessage] = useState('');
-    const [currentRunner, setCurrentRunner] = useState('');
-    const [isJbangRunning, setJbangIsRunning] = useState(false);
-    const [isRunning, setIsRunning] = useState(false);
-    const [isDeletingPod, setIsDeletingPod] = useState(false);
-    const [isReloadingPod, setIsReloadingPod] = useState(false);
+    const [isFile, setIsFile] = useState(false);
+    const [isYaml, setIsYaml] = useState(false);
+    const [isIntegration, setIsIntegration] = useState(false);
+    const [isProperties, setIsProperties] = useState(false);
     const {project, isPushing} = useProjectStore();
     const {files} = useFilesStore();
     const {config} = useAppConfigStore();
 
     useEffect(() => {
-        console.log("ProjectToolbar useEffect", isPushing, project.lastCommitTimestamp)
-        const sub1 = ProjectEventBus.onCurrentRunner()?.subscribe((result) => {
-            setCurrentRunner(result || '');
-            setJbangIsRunning(result === project.name);
-        });
-        return () => {
-            sub1.unsubscribe();
-        };
+        console.log("ProjectToolbar useEffect", isPushing, project.lastCommitTimestamp);
+        const {file, mode, editAdvancedProperties,
+            setEditAdvancedProperties, setUploadModalOpen} = props;
+        const isFile = file !== undefined;
+        const isYaml = file !== undefined && file.name.endsWith("yaml");
+        const isIntegration = isYaml && file?.code !== undefined && CamelDefinitionYaml.yamlIsIntegration(file.code);
+        const isProperties = file !== undefined && file.name.endsWith("properties");
+        setIsFile(isFile);
+        setIsYaml(isYaml);
+        setIsIntegration(isIntegration);
+        setIsProperties(isProperties);
     });
 
     function podName() {
@@ -90,46 +92,6 @@ export const ProjectToolbar = (props: Props) => {
         // }
     }
 
-    function jbangRun() {
-        setJbangIsRunning(true);
-        KaravanApi.runProject(project, res => {
-            if (res.status === 200 || res.status === 201) {
-                ProjectEventBus.setCurrentRunner(project.name);
-                setJbangIsRunning(false);
-                ProjectEventBus.showLog('container', res.data, config.environment)
-            } else {
-                // Todo notification
-                setJbangIsRunning(false);
-                ProjectEventBus.setCurrentRunner(undefined);
-            }
-        });
-    }
-
-    function reloadRunner() {
-        setIsReloadingPod(true);
-        KaravanApi.getRunnerReload(project.projectId, res => {
-            if (res.status === 200 || res.status === 201) {
-                setIsReloadingPod(false);
-            } else {
-                // Todo notification
-                setIsReloadingPod(false);
-            }
-        });
-    }
-
-    function deleteRunner() {
-        ProjectEventBus.setCurrentRunner(undefined);
-        setIsDeletingPod(true);
-        KaravanApi.deleteRunner(podName(), false, res => {
-            if (res.status === 202) {
-                setIsDeletingPod(false);
-            } else {
-                // Todo notification
-                setIsDeletingPod(false);
-            }
-        });
-    }
-
     function push () {
         setCommitMessageIsOpen(false);
         ProjectService.pushProject(project, commitMessage);
@@ -168,8 +130,6 @@ export const ProjectToolbar = (props: Props) => {
 
     function getTemplatesToolbar() {
         const {file, editAdvancedProperties, setUploadModalOpen} = props;
-        const isFile = file !== undefined;
-        const isProperties = file !== undefined && file.name.endsWith("properties");
         return <Toolbar id="toolbar-group-types">
             <ToolbarContent>
                 <ToolbarItem>
@@ -212,13 +172,9 @@ export const ProjectToolbar = (props: Props) => {
         </Toolbar>
     }
 
-    function getProjectToolbar() {
+    function getFileToolbar() {
         const {file, mode, editAdvancedProperties,
-             setEditAdvancedProperties, setUploadModalOpen} = props;
-        const isFile = file !== undefined;
-        const isYaml = file !== undefined && file.name.endsWith("yaml");
-        const isIntegration = isYaml && file?.code && CamelDefinitionYaml.yamlIsIntegration(file.code);
-        const isProperties = file !== undefined && file.name.endsWith("properties");
+            setEditAdvancedProperties, setUploadModalOpen} = props;
         return <Toolbar id="toolbar-group-types">
             <ToolbarContent>
                 <Flex className="toolbar" direction={{default: "row"}} alignItems={{default: "alignItemsCenter"}}>
@@ -267,14 +223,40 @@ export const ProjectToolbar = (props: Props) => {
                             <Button isSmall variant="control" icon={<DownloadImageIcon/>} onClick={e => downloadImage()}/>
                         </Tooltip>
                     </FlexItem>}
-                    {isYaml && currentRunner === project.name && <FlexItem>
-                        <RunnerToolbar project={project} showConsole={false} reloadOnly={true} />
-                    </FlexItem>}
+                    {/*{isYaml && currentRunner === project.name && <FlexItem>*/}
+                    {/*    <RunnerToolbar project={project} showConsole={false} reloadOnly={true} />*/}
+                    {/*</FlexItem>}*/}
                 </Flex>
             </ToolbarContent>
         </Toolbar>
     }
 
+    function getProjectToolbar() {
+        return (<Toolbar id="toolbar-group-types">
+            <ToolbarContent>
+                <Flex className="toolbar" direction={{default: "row"}} alignItems={{default: "alignItemsCenter"}}>
+                    <FlexItem>{getLastUpdatePanel()}</FlexItem>
+                    <FlexItem>
+                        <Tooltip content="Commit and push to git" position={"bottom-end"}>
+                            <Button isLoading={isPushing ? true : undefined}
+                                    isSmall
+                                    variant={needCommit() ? "primary" : "secondary"}
+                                    className="project-button"
+                                    icon={!isPushing ? <PushIcon/> : <div></div>}
+                                    onClick={() => {
+                                        setCommitMessage(commitMessage === '' ? new Date().toLocaleString() : commitMessage);
+                                        setCommitMessageIsOpen(true);
+                                    }}>
+                                {isPushing ? "..." : "Push"}
+                            </Button>
+                        </Tooltip>
+                    </FlexItem>
+                    {isRunnable() && <RunnerToolbar/>}
+                </Flex>
+            </ToolbarContent>
+        </Toolbar>)
+    }
+
     function getCommitModal() {
         return (
             <Modal
@@ -305,11 +287,16 @@ export const ProjectToolbar = (props: Props) => {
         return project.projectId === 'templates';
     }
 
+    function isRunnable(): boolean {
+        return !isKameletsProject() && !isTemplatesProject();
+    }
+
     const isTemplates = isTemplatesProject();
     return  (
          <>
-            {isTemplates && getTemplatesToolbar()}
-            {!isTemplates && getProjectToolbar()}
+            {/*{isTemplates && getTemplatesToolbar()}*/}
+            {/*{!isTemplates && getProjectToolbar()}*/}
+             {!isFile && getProjectToolbar()}
              {getCommitModal()}
         </>
     )
diff --git a/karavan-app/src/main/webui/src/project/RunnerToolbar.tsx b/karavan-app/src/main/webui/src/project/RunnerToolbar.tsx
index e309a531..8fc52840 100644
--- a/karavan-app/src/main/webui/src/project/RunnerToolbar.tsx
+++ b/karavan-app/src/main/webui/src/project/RunnerToolbar.tsx
@@ -1,6 +1,6 @@
 import React, {useEffect, useState} from 'react';
 import {
-    Button,
+    Button, FlexItem,
     Tooltip,
     TooltipPosition
 } from '@patternfly/react-core';
@@ -9,54 +9,44 @@ import RocketIcon from "@patternfly/react-icons/dist/esm/icons/rocket-icon";
 import ReloadIcon from "@patternfly/react-icons/dist/esm/icons/bolt-icon";
 import DeleteIcon from "@patternfly/react-icons/dist/esm/icons/times-circle-icon";
 import {KaravanApi} from "../api/KaravanApi";
-import {Project} from "../api/ProjectModels";
-import {ProjectEventBus} from "../api/ProjectEventBus";
-import {useAppConfigStore} from "../api/ProjectStore";
+import {useAppConfigStore, useProjectStore} from "../api/ProjectStore";
+import {ProjectService} from "../api/ProjectService";
 
 
-interface Props {
-    project: Project,
-    showConsole: boolean,
-    reloadOnly: boolean
-}
-
-export const RunnerToolbar = (props: Props) => {
+export const RunnerToolbar = () => {
 
-    const [podName, setPodName] = useState(props.project.projectId + '-runner');
-    const [isJbangRunning, setJbangIsRunning] = useState(false);
-    const [isRunning, setIsRunning] = useState(false);
+    const [isStartingPod, setIsStartingPod] = useState(false);
     const [isDeletingPod, setIsDeletingPod] = useState(false);
     const [isReloadingPod, setIsReloadingPod] = useState(false);
     const {config} = useAppConfigStore();
+    const {project, podStatus} = useProjectStore();
+
+    function isRunning() {
+        return podStatus.started;
+    }
 
     useEffect(() => {
-        const sub1 = ProjectEventBus.onCurrentRunner()?.subscribe((result) => {
-            setJbangIsRunning(result === props.project.name);
-        });
+        console.log("Runner toolbar", podStatus);
+        const interval = setInterval(() => {
+            if (isRunning()) {
+                ProjectService.getRunnerPodStatus(project);
+                if (isStartingPod) setIsStartingPod(false);
+            }
+        }, 1000);
         return () => {
-            sub1.unsubscribe();
+            clearInterval(interval)
         };
-    });
+
+    }, []);
 
     function jbangRun() {
-        setJbangIsRunning(true);
-        KaravanApi.runProject(props.project, res => {
-            if (res.status === 200 || res.status === 201) {
-                ProjectEventBus.setCurrentRunner(props.project.name);
-                setJbangIsRunning(false);
-                setPodName(res.data);
-                ProjectEventBus.showLog('container', res.data, config.environment)
-            } else {
-                // Todo notification
-                setJbangIsRunning(false);
-                ProjectEventBus.setCurrentRunner(undefined);
-            }
-        });
+        setIsStartingPod(true);
+        ProjectService.runProject(project);
     }
 
     function reloadRunner() {
         setIsReloadingPod(true);
-        KaravanApi.getRunnerReload(props.project.projectId, res => {
+        KaravanApi.getRunnerReload(project.projectId, res => {
             if (res.status === 200 || res.status === 201) {
                 setIsReloadingPod(false);
             } else {
@@ -67,9 +57,8 @@ export const RunnerToolbar = (props: Props) => {
     }
 
     function deleteRunner() {
-        ProjectEventBus.setCurrentRunner(undefined);
         setIsDeletingPod(true);
-        KaravanApi.deleteRunner(podName, false, res => {
+        KaravanApi.deleteRunner(project.projectId + "-runner", false, res => {
             if (res.status === 202) {
                 setIsDeletingPod(false);
             } else {
@@ -79,59 +68,42 @@ export const RunnerToolbar = (props: Props) => {
         });
     }
 
-    return (
-            <div className="runner-toolbar">
-                {!props.showConsole && !props.reloadOnly  &&
-                    <div className="row">
-                        <Tooltip content="Run in development mode" position={TooltipPosition.left}>
-                            <Button isLoading={isJbangRunning ? true : undefined}
-                                    isSmall
-                                    variant={"primary"}
-                                    className="project-button"
-                                    icon={!isJbangRunning ? <RocketIcon/> : <div></div>}
-                                    onClick={() => jbangRun()}>
-                                {isJbangRunning ? "..." : "Run"}
-                            </Button>
-                        </Tooltip>
-                    </div>}
-                {props.reloadOnly &&
-                    <div className="row">
-                        <Tooltip content="Reload" position={TooltipPosition.left}>
-                            <Button isLoading={isReloadingPod ? true : undefined}
-                                    isSmall
-                                    variant={"primary"}
-                                    className="project-button"
-                                    icon={!isReloadingPod ? <ReloadIcon/> : <div></div>}
-                                    onClick={() => reloadRunner()}>
-                                {isReloadingPod ? "..." : "Reload"}
-                            </Button>
-                        </Tooltip>
-                    </div>
-                }
-                {props.showConsole && <>
-                    <div className="row">
-                        <Tooltip content="Reload" position={TooltipPosition.left}>
-                            <Button isLoading={isReloadingPod ? true : undefined}
-                                    isSmall
-                                    variant={"primary"}
-                                    className="project-button"
-                                    icon={!isReloadingPod ? <ReloadIcon/> : <div></div>}
-                                    onClick={() => reloadRunner()}>
-                                {isReloadingPod ? "..." : "Reload"}
-                            </Button>
-                        </Tooltip>
-                    </div>
-                    <Tooltip content="Stop runner" position={TooltipPosition.left}>
-                        <Button isLoading={isDeletingPod ? true : undefined}
-                                isSmall
-                                variant={"secondary"}
-                                className="project-button"
-                                icon={!isRunning ? <DeleteIcon/> : <div></div>}
-                                onClick={() => deleteRunner()}>
-                            {isDeletingPod ? "..." : "Stop"}
-                        </Button>
-                    </Tooltip>
-                </>}
-            </div>
-    );
+    return (<>
+        {!isRunning() && <FlexItem>
+            <Tooltip content="Run in development mode" position={TooltipPosition.bottomEnd}>
+                <Button isLoading={isStartingPod ? true : undefined}
+                        isSmall
+                        variant={"primary"}
+                        className="project-button"
+                        icon={!isStartingPod ? <RocketIcon/> : <div></div>}
+                        onClick={() => jbangRun()}>
+                    {isStartingPod ? "..." : "Run"}
+                </Button>
+            </Tooltip>
+        </FlexItem>}
+        {isRunning() && <FlexItem>
+            <Tooltip content="Reload" position={TooltipPosition.bottomEnd}>
+                <Button isLoading={isReloadingPod ? true : undefined}
+                        isSmall
+                        variant={"primary"}
+                        className="project-button"
+                        icon={!isReloadingPod ? <ReloadIcon/> : <div></div>}
+                        onClick={() => reloadRunner()}>
+                    {isReloadingPod ? "..." : "Reload"}
+                </Button>
+            </Tooltip>
+        </FlexItem>}
+        {isRunning() && <FlexItem>
+        <Tooltip content="Stop runner" position={TooltipPosition.bottomEnd}>
+            <Button isLoading={isDeletingPod ? true : undefined}
+                    isSmall
+                    variant={"secondary"}
+                    className="project-button"
+                    icon={!isRunning ? <DeleteIcon/> : <div></div>}
+                    onClick={() => deleteRunner()}>
+                {isDeletingPod ? "..." : "Stop"}
+            </Button>
+        </Tooltip>
+        </FlexItem>}
+    </>);
 }
diff --git a/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx b/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx
index 6f919cad..07bb5541 100644
--- a/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx
+++ b/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx
@@ -18,7 +18,7 @@ export function isRunning(status: PodStatus): boolean {
 
 export const DashboardTab = () => {
 
-    const {project, setProject} = useProjectStore();
+    const {project} = useProjectStore();
     const [podStatus, setPodStatus] = useState(new PodStatus());
     const previousValue = useRef(new PodStatus());
     const [memory, setMemory] = useState({});
diff --git a/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoContext.tsx b/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoContext.tsx
index 7683be64..c07cf810 100644
--- a/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoContext.tsx
+++ b/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoContext.tsx
@@ -19,8 +19,6 @@ interface Props {
 
 export const RunnerInfoContext = (props: Props) => {
 
-
-
     function getContextInfo() {
         return (
             <LabelGroup numLabels={3}>