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


The following commit(s) were added to refs/heads/main by this push:
     new 8ab2174  Saas fix1 (#361)
8ab2174 is described below

commit 8ab217452014d37c5da087101dc14b51c3b41678
Author: Marat Gubaidullin <marat.gubaidul...@gmail.com>
AuthorDate: Thu Jun 9 17:11:09 2022 -0400

    Saas fix1 (#361)
    
    * New menu
    
    * Dockerfile
    
    * Dockerfile
    
    * Project create/copy
    
    * UI improvements
    
    * UI improvements
    
    * UI improvements
---
 karavan-app/pom.xml                                |   4 +
 .../org/apache/camel/karavan/api/GitResource.java  |   4 +-
 .../apache/camel/karavan/api/ProjectResource.java  |  21 ++-
 .../org/apache/camel/karavan/model/Project.java    |  52 +++++--
 .../apache/camel/karavan/service/GitService.java   |   2 +-
 .../camel/karavan/service/InfinispanService.java   |  25 ++--
 karavan-app/src/main/webapp/src/Logo.tsx           | 135 +++++++++---------
 karavan-app/src/main/webapp/src/Main.tsx           | 152 ++++++++++++---------
 karavan-app/src/main/webapp/src/api/KaravanApi.tsx |  16 ++-
 .../main/webapp/src/components/ComponentCard.tsx   |   1 -
 karavan-app/src/main/webapp/src/index.css          |  40 ++++++
 .../src/main/webapp/src/models/ProjectModels.ts    |  29 +++-
 .../main/webapp/src/projects/CreateFileModal.tsx   |   2 +-
 .../src/main/webapp/src/projects/ProjectPage.tsx   |  99 ++++++++------
 .../src/main/webapp/src/projects/ProjectsPage.tsx  |  97 +++++++++----
 karavan-docker/Dockerfile                          |  25 +++-
 karavan-docker/demo/application.properties         |   2 -
 17 files changed, 455 insertions(+), 251 deletions(-)

diff --git a/karavan-app/pom.xml b/karavan-app/pom.xml
index 67e2ada..2830d0a 100644
--- a/karavan-app/pom.xml
+++ b/karavan-app/pom.xml
@@ -94,6 +94,10 @@
             <artifactId>camel-quarkus-kubernetes</artifactId>
             <version>2.9.0</version>
         </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
 <!--        <dependency>-->
 <!--            <groupId>io.quarkus</groupId>-->
 <!--            <artifactId>quarkus-openshift</artifactId>-->
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/api/GitResource.java 
b/karavan-app/src/main/java/org/apache/camel/karavan/api/GitResource.java
index 0368459..05966b4 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/api/GitResource.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/GitResource.java
@@ -64,8 +64,8 @@ public class GitResource {
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
     public Project push(@HeaderParam("username") String username, Project 
project) throws Exception {
-        Project p = infinispanService.getProject(project.getName());
-        List<ProjectFile> files = 
infinispanService.getProjectFiles(project.getName());
+        Project p = infinispanService.getProject(project.getKey());
+        List<ProjectFile> files = 
infinispanService.getProjectFiles(project.getKey());
         String commitId = gitService.save(p, files);
         p.setLastCommit(commitId);
         infinispanService.saveProject(p);
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java 
b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java
index e22ff6a..b0cc160 100644
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java
+++ 
b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.karavan.api;
 
+import org.apache.camel.karavan.model.GroupedKey;
 import org.apache.camel.karavan.model.Project;
 import org.apache.camel.karavan.model.ProjectFile;
 import org.apache.camel.karavan.service.InfinispanService;
@@ -34,6 +35,7 @@ import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 @Path("/project")
@@ -46,7 +48,7 @@ public class ProjectResource {
     @Produces(MediaType.APPLICATION_JSON)
     public List<Project> getAll(@HeaderParam("username") String username) 
throws Exception {
         return infinispanService.getProjects().stream()
-                .sorted(Comparator.comparing(Project::getName))
+                .sorted(Comparator.comparing(Project::getKey))
                 .collect(Collectors.toList());
     }
 
@@ -72,4 +74,21 @@ public class ProjectResource {
                           @PathParam("project") String project) throws 
Exception {
         infinispanService.deleteProject(URLDecoder.decode(project, 
StandardCharsets.UTF_8.toString()));
     }
+
+    @POST
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Path("/copy/{sourceProject}")
+    public Project copy(@HeaderParam("username") String username, 
@PathParam("sourceProject") String sourceProject, Project project) throws 
Exception {
+//        Save project
+        Project s = infinispanService.getProject(sourceProject);
+        project.setType(s.getType());
+        infinispanService.saveProject(project);
+
+//        Copy files
+        Map<GroupedKey, ProjectFile> map = 
infinispanService.getProjectFiles(sourceProject).stream()
+                .collect(Collectors.toMap(f -> new 
GroupedKey(project.getKey(), f.getName()), f -> f));
+        infinispanService.saveProjectFiles(map);
+        return project;
+    }
 }
\ No newline at end of file
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/model/Project.java 
b/karavan-app/src/main/java/org/apache/camel/karavan/model/Project.java
index c432cd4..b90dff2 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/model/Project.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/model/Project.java
@@ -1,5 +1,6 @@
 package org.apache.camel.karavan.model;
 
+import com.google.common.base.CaseFormat;
 import org.infinispan.protostream.annotations.ProtoEnumValue;
 import org.infinispan.protostream.annotations.ProtoFactory;
 import org.infinispan.protostream.annotations.ProtoField;
@@ -8,14 +9,16 @@ public class Project {
     public static final String CACHE = "projects";
 
     @ProtoField(number = 1)
-    String name;
+    String groupId;
     @ProtoField(number = 2)
-    String version;
+    String artifactId;
     @ProtoField(number = 3)
-    String folder;
+    String version;
     @ProtoField(number = 4)
-    ProjectType type;
+    String folder;
     @ProtoField(number = 5)
+    ProjectType type;
+    @ProtoField(number = 6)
     String lastCommit;
 
     public enum ProjectType {
@@ -28,30 +31,53 @@ public class Project {
     }
 
     @ProtoFactory
-    public Project(String name, String version, String folder, ProjectType 
type, String lastCommit) {
-        this.name = name;
+    public Project(String groupId, String artifactId, String version, String 
folder, ProjectType type, String lastCommit) {
+        this.groupId = groupId;
+        this.artifactId = artifactId;
         this.version = version;
         this.folder = folder;
         this.type = type;
         this.lastCommit = lastCommit;
     }
 
-    public Project(String name, String version, String folder, ProjectType 
type) {
-        this.name = name;
+    public Project(String groupId, String artifactId, String version, String 
folder, ProjectType type) {
+        this.groupId = groupId;
+        this.artifactId = artifactId;
         this.version = version;
-        this.folder = folder;
+        this.folder = folder != null && folder.trim().length() > 0 ? folder : 
toFolder(groupId, artifactId, version);
         this.type = type;
     }
 
+    private String toFolder(String groupId, String artifactId, String version){
+        String folder = toKey(groupId, artifactId, 
version).replaceAll("[^A-Za-z0-9 ]", "_");
+        return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, folder);
+    }
+
+    private String toKey(String groupId, String artifactId, String version){
+        return groupId + ":" + artifactId + ":" + version;
+    }
+
+    public String getKey(){
+        return toKey(groupId, artifactId, version);
+    }
+
     public Project() {
     }
 
-    public String getName() {
-        return name;
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public void setGroupId(String groupId) {
+        this.groupId = groupId;
+    }
+
+    public String getArtifactId() {
+        return artifactId;
     }
 
-    public void setName(String name) {
-        this.name = name;
+    public void setArtifactId(String artifactId) {
+        this.artifactId = artifactId;
     }
 
     public String getVersion() {
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java
index 611f186..4dbcd60 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java
@@ -78,7 +78,7 @@ public class GitService {
     }
 
     public String save(Project project, List<ProjectFile> files) throws 
GitAPIException, IOException, URISyntaxException {
-        LOGGER.info("Save project " + project.getName());
+        LOGGER.info("Save project " + project.getKey());
         String uuid = UUID.randomUUID().toString();
         String folder = vertx.fileSystem().createTempDirectoryBlocking(uuid);
         LOGGER.infof("Temp folder created: {}", folder);
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java
 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java
index accd14f..4b043e5 100644
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java
+++ 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java
@@ -39,6 +39,7 @@ import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.event.Observes;
 import javax.inject.Inject;
 import java.util.List;
+import java.util.Map;
 import java.util.Random;
 import java.util.stream.Collectors;
 
@@ -75,12 +76,14 @@ public class InfinispanService {
         }
 
         for (int i = 0; i < 10; i++){
-            String project = "parcel-demo" + i;
-            projects.put(GroupedKey.create(project, project), new 
Project(project, "1.0.0",  project, Project.ProjectType.values()[new 
Random().nextInt(3)]));
-
-            files.put(GroupedKey.create(project,"new-parcels.yaml"), new 
ProjectFile("new-parcels.yaml", "flows:", project));
-            files.put(GroupedKey.create(project,"parcel-confirmation.yaml"), 
new ProjectFile("parcel-confirmation.yaml", "rest:", project));
-            files.put(GroupedKey.create(project,"CustomProcessor.java"), new 
ProjectFile("CustomProcessor.java", "import org.apache.camel.BindToRegistry;\n" 
+
+            String groupId = "org.apache.camel.karavan";
+            String artifactId = "parcel-demo" + i;
+            Project p = new Project(groupId, artifactId, "1.0.0",  null, 
Project.ProjectType.values()[new Random().nextInt(3)]);
+            projects.put(GroupedKey.create(p.getKey(), p.getKey()), p);
+
+            files.put(GroupedKey.create(p.getKey(),"new-parcels.yaml"), new 
ProjectFile("new-parcels.yaml", "flows:", p.getKey()));
+            
files.put(GroupedKey.create(p.getKey(),"parcel-confirmation.yaml"), new 
ProjectFile("parcel-confirmation.yaml", "rest:", p.getKey()));
+            files.put(GroupedKey.create(p.getKey(),"CustomProcessor.java"), 
new ProjectFile("CustomProcessor.java", "import 
org.apache.camel.BindToRegistry;\n" +
                     "import org.apache.camel.Exchange;\n" +
                     "import org.apache.camel.Processor;\n" +
                     "\n" +
@@ -90,8 +93,8 @@ public class InfinispanService {
                     "  public void process(Exchange exchange) throws Exception 
{\n" +
                     "      exchange.getIn().setBody(\"Hello world\");\n" +
                     "  }\n" +
-                    "}", project));
-            files.put(GroupedKey.create(project,"application.properties"), new 
ProjectFile("application.properties", "parameter1:hello", project));
+                    "}", p.getKey()));
+            files.put(GroupedKey.create(p.getKey(),"application.properties"), 
new ProjectFile("application.properties", "parameter1:hello", p.getKey()));
         }
     }
 
@@ -100,7 +103,7 @@ public class InfinispanService {
     }
 
     public void saveProject(Project project) {
-        projects.put(GroupedKey.create(project.getName(), project.getName()), 
project);
+        projects.put(GroupedKey.create(project.getKey(), project.getKey()), 
project);
     }
 
     public List<ProjectFile> getProjectFiles(String projectName) {
@@ -121,6 +124,10 @@ public class InfinispanService {
         files.put(GroupedKey.create(file.getProject(), file.getName()), file);
     }
 
+    public void saveProjectFiles(Map<GroupedKey, ProjectFile> f) {
+        files.putAll(f);
+    }
+
     public void deleteProject(String project) {
         projects.remove(GroupedKey.create(project, project));
     }
diff --git a/karavan-app/src/main/webapp/src/Logo.tsx 
b/karavan-app/src/main/webapp/src/Logo.tsx
index 82c49d4..b59e932 100644
--- a/karavan-app/src/main/webapp/src/Logo.tsx
+++ b/karavan-app/src/main/webapp/src/Logo.tsx
@@ -2,20 +2,31 @@ import React from "react";
 
 function Icon() {
     return (
-        <div className="logo">
         <svg
             xmlns="http://www.w3.org/2000/svg";
             xmlnsXlink="http://www.w3.org/1999/xlink";
-            width="1132.022"
-            height="360.139"
+            id="svg50"
+            width="256"
+            height="256"
             preserveAspectRatio="xMidYMid"
             version="1.1"
-            viewBox="0 0 1132.022 360.139"
+            viewBox="0 0 256 256"
+            className="logo"
         >
-            <defs>
+            <defs id="defs31">
                 <linearGradient id="linearGradient1351">
-                    <stop offset="0" stopColor="#dcffff" 
stopOpacity="1"></stop>
-                    <stop offset="1" stopColor="#96d2e6" 
stopOpacity="1"></stop>
+                    <stop
+                        id="stop1347"
+                        offset="0"
+                        stopColor="#dcffff"
+                        stopOpacity="1"
+                    ></stop>
+                    <stop
+                        id="stop1349"
+                        offset="1"
+                        stopColor="#96d2e6"
+                        stopOpacity="1"
+                    ></stop>
                 </linearGradient>
                 <circle id="path-1" cx="128" cy="128" r="128"></circle>
                 <linearGradient
@@ -26,9 +37,24 @@ function Icon() {
                     y2="0.048"
                     gradientUnits="userSpaceOnUse"
                 >
-                    <stop offset="0%" stopColor="#4790bb" 
stopOpacity="1"></stop>
-                    <stop offset="10.996%" stopColor="#64b7db" 
stopOpacity="1"></stop>
-                    <stop offset="94.502%" stopColor="#326ea0" 
stopOpacity="1"></stop>
+                    <stop
+                        id="stop10"
+                        offset="0%"
+                        stopColor="#4790bb"
+                        stopOpacity="1"
+                    ></stop>
+                    <stop
+                        id="stop12"
+                        offset="10.996%"
+                        stopColor="#64b7db"
+                        stopOpacity="1"
+                    ></stop>
+                    <stop
+                        id="stop14"
+                        offset="94.502%"
+                        stopColor="#326ea0"
+                        stopOpacity="1"
+                    ></stop>
                 </linearGradient>
                 <linearGradient
                     id="linearGradient-4"
@@ -38,9 +64,9 @@ function Icon() {
                     y2="-5.028"
                     gradientUnits="userSpaceOnUse"
                 >
-                    <stop offset="0%" stopColor="#F69923"></stop>
-                    <stop offset="8.048%" stopColor="#F79A23"></stop>
-                    <stop offset="41.874%" stopColor="#E97826"></stop>
+                    <stop id="stop17" offset="0%" stopColor="#F69923"></stop>
+                    <stop id="stop19" offset="8.048%" 
stopColor="#F79A23"></stop>
+                    <stop id="stop21" offset="41.874%" 
stopColor="#E97826"></stop>
                 </linearGradient>
                 <linearGradient
                     id="linearGradient-5"
@@ -52,15 +78,30 @@ function Icon() {
                     gradientUnits="userSpaceOnUse"
                     xlinkHref="#linearGradient-4"
                 >
-                    <stop offset="0%" stopColor="#92d6d5" 
stopOpacity="1"></stop>
-                    <stop offset="41.191%" stopColor="#79b7cc" 
stopOpacity="1"></stop>
-                    <stop offset="73.271%" stopColor="#5891c5" 
stopOpacity="1"></stop>
+                    <stop
+                        id="stop24"
+                        offset="0%"
+                        stopColor="#92d6d5"
+                        stopOpacity="1"
+                    ></stop>
+                    <stop
+                        id="stop26"
+                        offset="41.191%"
+                        stopColor="#79b7cc"
+                        stopOpacity="1"
+                    ></stop>
+                    <stop
+                        id="stop28"
+                        offset="73.271%"
+                        stopColor="#5891c5"
+                        stopOpacity="1"
+                    ></stop>
                 </linearGradient>
                 <mask id="mask-2" fill="#fff">
-                    <use width="100%" height="100%" x="0" y="0" 
xlinkHref="#path-1"></use>
+                    <use id="use33" xlinkHref="#path-1"></use>
                 </mask>
                 <mask id="mask-2-7" fill="#fff">
-                    <use width="100%" height="100%" x="0" y="0" 
xlinkHref="#path-1"></use>
+                    <use id="use137" xlinkHref="#path-1"></use>
                 </mask>
                 <linearGradient
                     id="linearGradient1345"
@@ -73,16 +114,17 @@ function Icon() {
                 ></linearGradient>
             </defs>
             <circle
+                id="circle38"
                 cx="127.994"
                 cy="127.994"
                 r="123.111"
                 fill="url(#linearGradient-3)"
                 fillRule="nonzero"
                 mask="url(#mask-2)"
-                transform="translate(45.105 56.042)"
             ></circle>
-            <g transform="translate(45.105 56.042)">
+            <g id="g2266">
                 <path
+                    id="path42"
                     fill="url(#linearGradient-5)"
                     fillOpacity="1"
                     fillRule="nonzero"
@@ -92,76 +134,33 @@ function Icon() {
                 ></path>
             </g>
             <path
+                id="path44"
                 fill="#1e4b7b"
                 fillOpacity="1"
                 fillRule="nonzero"
                 d="M84.752 77.368C66.895 83.378 32.83 104.546.079 132.81c2.487 
67.334 57.028 121.313 124.548 123.07 33.233-64.016 
13.901-137.68-39.875-178.513z"
                 mask="url(#mask-2)"
                 opacity="0.75"
-                transform="translate(45.105 56.042)"
             ></path>
             <path
+                id="path150"
                 fill="url(#linearGradient1345)"
                 fillOpacity="1"
                 fillRule="nonzero"
                 d="M128.747 54.005c-10.985 5.495 0 27.466 0 27.466C95.774 
108.954 102.78 155.9 64.312 155.9c-20.97 0-42.242-24.077-64.233-38.828-.283 
3.479-.785 6.972-.785 10.524 0 48.095 26.263 89.924 65.42 111.897 10.952-1.38 
22.838-4.114 31.05-9.592 43.146-28.765 53.857-83.491 71.487-109.925 
10.979-16.492 62.434-15.061 65.906-22.01 
5.502-10.991-10.99-27.467-16.491-27.467h-43.958c-3.071 
0-7.897-5.456-10.974-5.456h-16.492s-7.307-11.085-13.794-11.526c-.93-.066-1.83.053-2.7.488z"
                 mask="url(#mask-2-7)"
-                transform="translate(44.336 55.909)"
+                transform="translate(-.769 -.133)"
             ></path>
             <path
+                id="path40"
                 fill="#2d4150"
                 fillOpacity="1"
                 fillRule="nonzero"
                 d="M128 256C57.308 256 0 198.692 0 128 0 57.308 57.308 0 128 
0c70.692 0 128 57.308 128 128 0 70.692-57.308 128-128 128zm0-9.768c65.298 0 
118.232-52.934 118.232-118.232S193.298 9.768 128 9.768 9.768 62.702 9.768 128 
62.702 246.232 128 246.232z"
                 mask="url(#mask-2)"
-                transform="matrix(1.02035 0 0 1.02035 41.98 53.958)"
+                transform="translate(-.59) scale(1.00078)"
             ></path>
-            <text
-                xmlSpace="preserve"
-                style={{
-                    lineHeight: "1.25",
-                    fontVariantLigatures: "normal",
-                    fontVariantCaps: "normal",
-                    fontVariantNumeric: "normal",
-                    fontVariantEastAsian: "normal",
-                }}
-                x="346.913"
-                y="259.391"
-                fill="#96d2e6"
-                fillOpacity="1"
-                stroke="none"
-                strokeWidth="5.349"
-                fontFamily="M PLUS 1 Code"
-                fontSize="213.949"
-                fontStretch="normal"
-                fontStyle="normal"
-                fontVariant="normal"
-                fontWeight="normal"
-            >
-                <tspan
-                    x="346.913"
-                    y="259.391"
-                    style={{
-                        fontVariantLigatures: "normal",
-                        fontVariantCaps: "normal",
-                        fontVariantNumeric: "normal",
-                        fontVariantEastAsian: "normal",
-                    }}
-                    fill="#96d2e6"
-                    fillOpacity="1"
-                    strokeWidth="5.349"
-                    fontFamily="M PLUS 1 Code"
-                    fontSize="213.949"
-                    fontStretch="normal"
-                    fontStyle="normal"
-                    fontVariant="normal"
-                    fontWeight="normal"
-                >
-                    Karavan
-                </tspan>
-            </text>
         </svg>
-        </div>
     );
 }
 
diff --git a/karavan-app/src/main/webapp/src/Main.tsx 
b/karavan-app/src/main/webapp/src/Main.tsx
index 82454cb..d994bdb 100644
--- a/karavan-app/src/main/webapp/src/Main.tsx
+++ b/karavan-app/src/main/webapp/src/Main.tsx
@@ -2,10 +2,6 @@ import React from 'react';
 import {
     Page,
     PageHeader,
-    PageSidebar,
-    NavItem,
-    NavList,
-    Nav,
     ModalVariant,
     Button,
     Modal,
@@ -16,7 +12,11 @@ import {
     Avatar,
     PageHeaderTools,
     PageHeaderToolsGroup,
-    PageHeaderToolsItem, Dropdown, DropdownToggle, NavExpandable, NavGroup
+    PageHeaderToolsItem,
+    Dropdown,
+    DropdownToggle,
+    Tooltip,
+    Divider
 } from '@patternfly/react-core';
 import {KaravanApi} from "./api/KaravanApi";
 import {KameletApi} from "karavan-core/lib/api/KameletApi";
@@ -33,6 +33,13 @@ import {OpenApiPage} from "./integrations/OpenApiPage";
 import {ProjectsPage} from "./projects/ProjectsPage";
 import {Project} from "./models/ProjectModels";
 import {ProjectPage} from "./projects/ProjectPage";
+import TachometerAltIcon from 
"@patternfly/react-icons/dist/js/icons/tachometer-alt-icon";
+import UsersIcon from "@patternfly/react-icons/dist/js/icons/users-icon";
+import ProjectsIcon from 
"@patternfly/react-icons/dist/js/icons/repository-icon";
+import KameletsIcon from "@patternfly/react-icons/dist/js/icons/registry-icon";
+import EipIcon from "@patternfly/react-icons/dist/js/icons/topology-icon";
+import ComponentsIcon from "@patternfly/react-icons/dist/js/icons/module-icon";
+import ConfigurationIcon from 
"@patternfly/react-icons/dist/js/icons/cogs-icon";
 
 class ToastMessage {
     id: string = ''
@@ -48,6 +55,18 @@ class ToastMessage {
     }
 }
 
+class MenuItem {
+    pageId: string = '';
+    tooltip: string = '';
+    icon: any;
+
+    constructor(pageId: string, tooltip: string, icon: any) {
+        this.pageId = pageId;
+        this.tooltip = tooltip;
+        this.icon = icon;
+    }
+}
+
 interface Props {
 }
 
@@ -55,7 +74,7 @@ interface State {
     version: string,
     mode: 'local' | 'gitops' | 'serverless',
     isNavOpen: boolean,
-    pageId: 'projects' | 'project' | 'configuration' | 'kamelets' | 'designer' 
| "components" | "eip" | "openapi" | "acl"
+    pageId: string,
     projects: Project[],
     project?: Project,
     isModalOpen: boolean,
@@ -118,13 +137,6 @@ export class Main extends React.Component<Props, State> {
         <div className="top-toolbar">
             <Flex direction={{default: "row"}} justifyContent={{default: 
"justifyContentSpaceBetween"}}
                   style={{width: "100%"}}>
-                <FlexItem style={{marginTop: "auto", marginBottom: "auto"}}>
-                    {/*<FlexItem>*/}
-                    {/*    <TextContent>*/}
-                    {/*        <Text component={TextVariants.h5}>{"v. " + 
version}</Text>*/}
-                    {/*    </TextContent>*/}
-                    {/*</FlexItem>*/}
-                </FlexItem>
                 <FlexItem style={{marginTop: "auto", marginBottom: "auto"}}>
                     <PageHeaderTools>
                         <PageHeaderToolsGroup>
@@ -157,40 +169,42 @@ export class Main extends React.Component<Props, State> {
                     logo={Icon()}
                     headerTools={this.toolBar(version)}
         />
-    );
-
-    pageNav = () => (<Nav onSelect={this.onNavSelect}>
-        <NavList>
-            <NavItem id="projects" to="#" itemId={'projects'}
-                     isActive={this.state.pageId === 'projects'}>
-                Projects
-            </NavItem>
-            <NavItem id="configuration" to="#" itemId={"configuration"}
-                     isActive={this.state.pageId === 'configuration'}>
-                Configuration
-            </NavItem>
-            <NavItem id="acl" to="#" itemId={"acl"}
-                     isActive={this.state.pageId === 'acl'}>
-                User Management
-            </NavItem>
-            <NavExpandable id="help" title={"Help"} isExpanded={false}>
-                <NavItem id="eip" to="#" itemId={"eip"}
-                         isActive={this.state.pageId === 'eip'}>
-                    Enterprise Integration Patterns
-                </NavItem>
-                <NavItem id="kamelets" to="#" itemId={"kamelets"}
-                         isActive={this.state.pageId === 'kamelets'}>
-                    Kamelets
-                </NavItem>
-                <NavItem id="components" to="#" itemId={"components"}
-                         isActive={this.state.pageId === 'components'}>
-                    Components
-                </NavItem>
-            </NavExpandable>
-        </NavList>
-    </Nav>);
+    )
 
-    sidebar = () => (<PageSidebar nav={this.pageNav()} 
isNavOpen={this.state.isNavOpen}/>);
+    pageNav = () => {
+        const pages: MenuItem[] = [
+            new MenuItem("dashboard", "Dashboard", <TachometerAltIcon/>),
+            new MenuItem("projects", "Projects", <ProjectsIcon/>),
+            new MenuItem("eip", "Enterprise Integration Patterns", <EipIcon/>),
+            new MenuItem("kamelets", "Kamelets", <KameletsIcon/>),
+            new MenuItem("components", "Components", <ComponentsIcon/>),
+            new MenuItem("acl", "Access Control", <UsersIcon/>),
+            new MenuItem("configuration", "Configuration", 
<ConfigurationIcon/>)
+        ]
+        return (<Flex className="nav-buttons" direction={{default: "column"}} 
style={{height:"100%"}} spaceItems={{default:"spaceItemsNone"}}>
+            <FlexItem alignSelf={{default:"alignSelfCenter"}}>
+                <Tooltip content={"Apache Camel Karavan"} position={"right"}>
+                    {Icon()}
+                </Tooltip>
+            </FlexItem>
+            {pages.map(page =>
+                <FlexItem className={this.state.pageId === page.pageId ? 
"nav-button-selected" : ""}>
+                    <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})}
+                        />
+                    </Tooltip>
+                </FlexItem>
+            )}
+            <FlexItem flex={{default:"flex_2"}} 
alignSelf={{default:"alignSelfCenter"}}>
+                <Divider/>
+            </FlexItem>
+            <FlexItem alignSelf={{default:"alignSelfCenter"}}>
+                <Avatar src={avatarImg} alt="avatar" border="dark"/>
+            </FlexItem>
+        </Flex>)
+    }
 
     onProjectDelete = (project: Project) => {
         this.setState({isModalOpen: true, projectToDelete: project})
@@ -235,7 +249,7 @@ export class Main extends React.Component<Props, State> {
     };
 
     onGetProjects() {
-        KaravanApi.getProjects((projects: []) => {
+        KaravanApi.getProjects((projects: Project[]) => {
             this.setState({
                 projects: projects, request: uuidv4()
             })
@@ -244,24 +258,30 @@ export class Main extends React.Component<Props, State> {
 
     render() {
         return (
-            <Page className="karavan" header={this.header(this.state.version)} 
sidebar={this.sidebar()}>
-                {this.state.pageId === 'projects' &&
-                    <ProjectsPage key={this.state.request}
-                                  projects={this.state.projects}
-                                  onDelete={this.onProjectDelete}
-                                  onSelect={this.onProjectSelect}
-                                  onRefresh={() => {
-                                      this.onGetProjects();
-                                  }}
-                                  onCreate={this.onProjectCreate}/>}
-                {this.state.pageId === 'project' && this.state.project && 
<ProjectPage project={this.state.project}/>}
-                {this.state.pageId === 'configuration' && <ConfigurationPage/>}
-                {this.state.pageId === 'kamelets' && <KameletsPage 
dark={false}/>}
-                {this.state.pageId === 'components' && <ComponentsPage 
dark={false}/>}
-                {this.state.pageId === 'eip' && <EipPage dark={false}/>}
-                {this.state.pageId === 'openapi' &&
-                    <OpenApiPage dark={false} openapi={this.state.openapi} 
filename={this.state.filename}/>}
-
+            <Page className="karavan">
+                <Flex style={{width: "100%", height:"100%"}} 
alignItems={{default:"alignItemsStretch"}} spaceItems={{ default: 
'spaceItemsNone' }}>
+                    <FlexItem>
+                        {this.pageNav()}
+                    </FlexItem>
+                    <FlexItem flex={{default:"flex_2"}} 
style={{height:"100%"}}>
+                        {this.state.pageId === 'projects' &&
+                            <ProjectsPage key={this.state.request}
+                                          projects={this.state.projects}
+                                          onDelete={this.onProjectDelete}
+                                          onSelect={this.onProjectSelect}
+                                          onRefresh={() => {
+                                              this.onGetProjects();
+                                          }}
+                                          onCreate={this.onProjectCreate}/>}
+                        {this.state.pageId === 'project' && this.state.project 
&& <ProjectPage project={this.state.project}/>}
+                        {this.state.pageId === 'configuration' && 
<ConfigurationPage/>}
+                        {this.state.pageId === 'kamelets' && <KameletsPage 
dark={false}/>}
+                        {this.state.pageId === 'components' && <ComponentsPage 
dark={false}/>}
+                        {this.state.pageId === 'eip' && <EipPage 
dark={false}/>}
+                        {this.state.pageId === 'openapi' &&
+                            <OpenApiPage dark={false} 
openapi={this.state.openapi} filename={this.state.filename}/>}
+                    </FlexItem>
+                </Flex>
                 <Modal
                     title="Confirmation"
                     variant={ModalVariant.small}
@@ -273,7 +293,7 @@ export class Main extends React.Component<Props, State> {
                                 onClick={e => this.setState({isModalOpen: 
false})}>Cancel</Button>
                     ]}
                     onEscapePress={e => this.setState({isModalOpen: false})}>
-                    <div>{"Are you sure you want to delete the project " + 
this.state.projectToDelete?.name + "?"}</div>
+                    <div>{"Are you sure you want to delete the project " + 
this.state.projectToDelete?.getKey() + "?"}</div>
                 </Modal>
                 {this.state.alerts.map((e: ToastMessage) => (
                     <Alert key={e.id} className="main-alert" 
variant={e.variant} title={e.title}
diff --git a/karavan-app/src/main/webapp/src/api/KaravanApi.tsx 
b/karavan-app/src/main/webapp/src/api/KaravanApi.tsx
index c5adbac..cb59783 100644
--- a/karavan-app/src/main/webapp/src/api/KaravanApi.tsx
+++ b/karavan-app/src/main/webapp/src/api/KaravanApi.tsx
@@ -27,12 +27,12 @@ export const KaravanApi = {
         });
     },
 
-    getProjects: async (after: (projects: []) => void) => {
+    getProjects: async (after: (projects: Project[]) => void) => {
         axios.get('/project',
             {headers: {'Accept': 'application/json', 'username': 'cameleer'}})
             .then(res => {
                 if (res.status === 200) {
-                    after(res.data);
+                    after(res.data.map((p: Partial<Project> | undefined) => 
new Project(p)));
                 }
             }).catch(err => {
             console.log(err);
@@ -49,8 +49,18 @@ export const KaravanApi = {
         });
     },
 
+    copyProject: async (sourceProject: string, project: Project, after: (res: 
AxiosResponse<any>) => void) => {
+        axios.post('/project/copy/' + sourceProject, project,
+            {headers: {'Accept': 'application/json', 'Content-Type': 
'application/json', 'username': 'cameleer'}})
+            .then(res => {
+                after(res);
+            }).catch(err => {
+            after(err);
+        });
+    },
+
     deleteProject: async (project: Project, after: (res: AxiosResponse<any>) 
=> void) => {
-        axios.delete('/project/' + encodeURI(project.name),
+        axios.delete('/project/' + encodeURI(project.getKey()),
             {headers:{'username': 'cameleer'}})
             .then(res => {
                 after(res);
diff --git a/karavan-app/src/main/webapp/src/components/ComponentCard.tsx 
b/karavan-app/src/main/webapp/src/components/ComponentCard.tsx
index 925c720..2b56994 100644
--- a/karavan-app/src/main/webapp/src/components/ComponentCard.tsx
+++ b/karavan-app/src/main/webapp/src/components/ComponentCard.tsx
@@ -3,7 +3,6 @@ import {
     CardHeader, Card, CardTitle, CardBody, CardActions, CardFooter,Badge
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
-import {KameletModel} from "karavan-core/lib/model/KameletModels";
 import {camelIcon, CamelUi} from "../designer/utils/CamelUi";
 import {Component} from "karavan-core/lib/model/ComponentModels";
 
diff --git a/karavan-app/src/main/webapp/src/index.css 
b/karavan-app/src/main/webapp/src/index.css
index 8810b6b..26e7528 100644
--- a/karavan-app/src/main/webapp/src/index.css
+++ b/karavan-app/src/main/webapp/src/index.css
@@ -19,6 +19,46 @@
   width: unset;
 }
 
+.karavan .nav-buttons {
+  background: var(--pf-c-page__header--BackgroundColor);
+}
+
+.karavan .nav-buttons .logo {
+  margin-top: 16px;
+  margin-bottom: 10px;
+  width: 32px;
+  height: 32px;
+}
+
+.karavan .nav-buttons .pf-c-button {
+  padding: 0;
+  width: 64px;
+  height: 64px;
+  color: var(--pf-global--Color--light-100);
+}
+
+.karavan .nav-buttons .pf-c-button svg {
+  width: 24px;
+}
+
+.karavan .nav-buttons .pf-c-avatar {
+  width: 32px;
+  height: 32px;
+  margin-bottom: 6px;
+}
+
+.karavan .nav-buttons .pf-c-button.pf-m-plain {
+  border-left-width: 3px;
+  border-left-style: solid;
+  border-left-color: transparent;
+  border-radius: 0;
+}
+
+.karavan .nav-button-selected .pf-c-button.pf-m-plain {
+  border-left-color: var(--pf-global--active-color--400);
+  background-color: var(--pf-global--BackgroundColor--dark-400);
+}
+
 .karavan .kamelet-section {
   display: flex;
   flex-direction: column;
diff --git a/karavan-app/src/main/webapp/src/models/ProjectModels.ts 
b/karavan-app/src/main/webapp/src/models/ProjectModels.ts
index 2ef6775..add44cc 100644
--- a/karavan-app/src/main/webapp/src/models/ProjectModels.ts
+++ b/karavan-app/src/main/webapp/src/models/ProjectModels.ts
@@ -1,16 +1,31 @@
 export class Project {
-    name: string = '';
+    groupId: string = '';
+    artifactId: string = '';
     version: string = '';
     folder: string = '';
     type: string = '';
     lastCommit: string = '';
 
-    constructor(name: string, version: string, folder: string, type: string, 
lastCommit: string) {
-        this.name = name;
-        this.version = version;
-        this.folder = folder;
-        this.type = type;
-        this.lastCommit = lastCommit;
+
+    public constructor(groupId: string, artifactId: string, version: string, 
folder: string, type: string, lastCommit: string);
+    public constructor(init?: Partial<Project>);
+    public constructor(...args: any[]) {
+        if (args.length === 1){
+            Object.assign(this, args[0]);
+            return;
+        } else {
+            this.groupId = args[0];
+            this.artifactId = args[1];
+            this.version = args[2];
+            this.folder = args[3];
+            this.type = args[4];
+            this.lastCommit = args[5];
+            return;
+        }
+    }
+
+    getKey():string{
+        return this.groupId + ":" + this.artifactId + ":" + this.version;
     }
 }
 
diff --git a/karavan-app/src/main/webapp/src/projects/CreateFileModal.tsx 
b/karavan-app/src/main/webapp/src/projects/CreateFileModal.tsx
index e965583..cdc3822 100644
--- a/karavan-app/src/main/webapp/src/projects/CreateFileModal.tsx
+++ b/karavan-app/src/main/webapp/src/projects/CreateFileModal.tsx
@@ -38,7 +38,7 @@ export class CreateFileModal extends React.Component<Props, 
State> {
         const {name, extension} = this.state;
         const filename = (extension !== 'java') ? CamelUi.nameFromTitle(name) 
: CamelUi.javaNameFromTitle(name)
         if (filename && extension){
-            const file = new ProjectFile(filename + '.' + extension, 
this.props.project.name, '');
+            const file = new ProjectFile(filename + '.' + extension, 
this.props.project.getKey(), '');
             KaravanApi.postProjectFile(file, res => {
                 if (res.status === 200) {
                     console.log(res) //TODO show notification
diff --git a/karavan-app/src/main/webapp/src/projects/ProjectPage.tsx 
b/karavan-app/src/main/webapp/src/projects/ProjectPage.tsx
index a8d353e..f02cff3 100644
--- a/karavan-app/src/main/webapp/src/projects/ProjectPage.tsx
+++ b/karavan-app/src/main/webapp/src/projects/ProjectPage.tsx
@@ -4,14 +4,9 @@ import {
     Breadcrumb,
     BreadcrumbItem,
     Button,
-    Form,
-    FormGroup,
     PageSection,
     Text,
     TextContent,
-    TextInput,
-    ToggleGroup,
-    ToggleGroupItem,
     Toolbar,
     ToolbarContent,
     ToolbarItem,
@@ -26,14 +21,13 @@ import {
     EmptyStateVariant,
     EmptyStateIcon,
     Title,
-    ModalVariant, Modal, Spinner, Tooltip
+    ModalVariant, Modal, Spinner, Tooltip, Flex, FlexItem,
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {MainToolbar} from "../MainToolbar";
 import {KaravanApi} from "../api/KaravanApi";
 import {Project, ProjectFile, ProjectFileTypes} from "../models/ProjectModels";
 import {CamelUi} from "../designer/utils/CamelUi";
-import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import UploadIcon from "@patternfly/react-icons/dist/esm/icons/upload-icon";
 import {TableComposable, Tbody, Td, Th, Thead, Tr} from 
"@patternfly/react-table";
 import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
@@ -160,7 +154,7 @@ export class ProjectPage extends React.Component<Props, 
State> {
                 <div>
                     <Breadcrumb>
                         <BreadcrumbItem to="#"
-                                        onClick={event => this.setState({file: 
undefined})}>{"Project: " + this.props.project?.name}</BreadcrumbItem>
+                                        onClick={event => this.setState({file: 
undefined})}>{"Project: " + this.props.project?.getKey()}</BreadcrumbItem>
                         <BreadcrumbItem to="#" 
isActive>{this.getType(file?.name)}</BreadcrumbItem>
                     </Breadcrumb>
                     <TextContent className="title">
@@ -176,12 +170,12 @@ export class ProjectPage extends React.Component<Props, 
State> {
 
     onRefresh = () => {
         if (this.props.project) {
-            KaravanApi.getProject(this.props.project.name, (project: Project) 
=> {
+            KaravanApi.getProject(this.props.project.getKey(), (project: 
Project) => {
                 this.setState({
                     project: project
                 })
             });
-            KaravanApi.getFiles(this.props.project.name, (files: []) => {
+            KaravanApi.getFiles(this.props.project.getKey(), (files: []) => {
                 this.setState({
                     files: files
                 })
@@ -246,41 +240,56 @@ export class ProjectPage extends React.Component<Props, 
State> {
         const project = this.state.project;
         return (
             <Card>
-                <CardBody>
-                    <DescriptionList isHorizontal>
-                        <DescriptionListGroup>
-                            <DescriptionListTerm>Name</DescriptionListTerm>
-                            
<DescriptionListDescription>{CamelUi.titleFromName(project?.name)}</DescriptionListDescription>
-                        </DescriptionListGroup>
-                        <DescriptionListGroup>
-                            <DescriptionListTerm>Version</DescriptionListTerm>
-                            
<DescriptionListDescription>{project?.version}</DescriptionListDescription>
-                        </DescriptionListGroup>
-                        <DescriptionListGroup>
-                            <DescriptionListTerm>Folder</DescriptionListTerm>
-                            
<DescriptionListDescription>{project?.folder}</DescriptionListDescription>
-                        </DescriptionListGroup>
-                        <DescriptionListGroup>
-                            <DescriptionListTerm>Type</DescriptionListTerm>
-                            <DescriptionListDescription>
-                                <ToggleGroup aria-label="Default with single 
selectable">
-                                    {["KARAVAN", "QUARKUS", 
"SPRING"].map(value =>
-                                        <ToggleGroupItem key={value}
-                                                         
text={CamelUtil.capitalizeName(value.toLowerCase())}
-                                                         buttonId={value} 
isSelected={project?.type === value}/>
-                                    )}
-                                </ToggleGroup>
-                            </DescriptionListDescription>
-                        </DescriptionListGroup>
-                        {project?.lastCommit && <DescriptionListGroup>
-                            <DescriptionListTerm>Latest 
Commit</DescriptionListTerm>
-                            <DescriptionListDescription>
-                                <Tooltip content={project?.lastCommit} 
position={"right"}>
-                                    <Badge>{project?.lastCommit.substr(0, 
7)}</Badge>
-                                </Tooltip>
-                            </DescriptionListDescription>
-                        </DescriptionListGroup>}
-                    </DescriptionList>
+                <CardBody isFilled>
+                    <Flex direction={{default: "row"}} alignContent={{default: 
"alignContentSpaceBetween"}}
+                          style={{width: "100%"}}>
+                        <FlexItem flex={{default: "flex_1"}}>
+                            <DescriptionList isHorizontal>
+                                <DescriptionListGroup>
+                                    
<DescriptionListTerm>Group</DescriptionListTerm>
+                                    
<DescriptionListDescription>{project?.groupId}</DescriptionListDescription>
+                                </DescriptionListGroup>
+                                <DescriptionListGroup>
+                                    
<DescriptionListTerm>Artifact</DescriptionListTerm>
+                                    
<DescriptionListDescription>{project?.artifactId}</DescriptionListDescription>
+                                </DescriptionListGroup>
+                                <DescriptionListGroup>
+                                    
<DescriptionListTerm>Version</DescriptionListTerm>
+                                    
<DescriptionListDescription>{project?.version}</DescriptionListDescription>
+                                </DescriptionListGroup>
+                                <DescriptionListGroup>
+                                    
<DescriptionListTerm>Type</DescriptionListTerm>
+                                    <DescriptionListDescription>
+                                        <Tooltip content={"Folder: " + 
project?.folder} position={"bottom"}>
+                                            
<Badge>{project?.type.toLowerCase()}</Badge>
+                                        </Tooltip>
+                                    </DescriptionListDescription>
+                                </DescriptionListGroup>
+                            </DescriptionList>
+                        </FlexItem>
+                        <FlexItem flex={{default: "flex_1"}}>
+                            <DescriptionList isHorizontal>
+                                <DescriptionListGroup>
+                                    <DescriptionListTerm>Latest 
Commit</DescriptionListTerm>
+                                    <DescriptionListDescription>
+                                        <Tooltip content={project?.lastCommit} 
position={"bottom"}>
+                                            
<Badge>{project?.lastCommit?.substr(0, 7)}</Badge>
+                                        </Tooltip>
+                                    </DescriptionListDescription>
+                                </DescriptionListGroup>
+                                <DescriptionListGroup>
+                                    
<DescriptionListTerm>Deployment</DescriptionListTerm>
+                                    <DescriptionListDescription>
+                                        <Flex direction={{default: "row"}}>
+                                            <FlexItem><Badge 
isRead>dev</Badge></FlexItem>
+                                            <FlexItem><Badge 
isRead>test</Badge></FlexItem>
+                                            <FlexItem><Badge 
isRead>prod</Badge></FlexItem>
+                                        </Flex>
+                                    </DescriptionListDescription>
+                                </DescriptionListGroup>
+                            </DescriptionList>
+                        </FlexItem>
+                    </Flex>
                 </CardBody>
             </Card>
         )
diff --git a/karavan-app/src/main/webapp/src/projects/ProjectsPage.tsx 
b/karavan-app/src/main/webapp/src/projects/ProjectsPage.tsx
index e8c933f..5033c0b 100644
--- a/karavan-app/src/main/webapp/src/projects/ProjectsPage.tsx
+++ b/karavan-app/src/main/webapp/src/projects/ProjectsPage.tsx
@@ -18,18 +18,25 @@ import {
     ToggleGroupItem,
     Bullseye,
     EmptyState,
-    EmptyStateVariant, EmptyStateIcon, Title
+    EmptyStateVariant,
+    EmptyStateIcon,
+    Title,
+    OverflowMenu,
+    OverflowMenuContent,
+    OverflowMenuGroup,
+    OverflowMenuItem,
+    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 {Project} from "../models/ProjectModels";
-import {CamelUi} from "../designer/utils/CamelUi";
 import {TableComposable, Tbody, Td, Th, Thead, Tr} from 
"@patternfly/react-table";
 import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
+import CopyIcon from "@patternfly/react-icons/dist/esm/icons/copy-icon";
 
 interface Props {
     projects: Project[],
@@ -42,8 +49,11 @@ interface Props {
 interface State {
     projects: Project[],
     isCreateModalOpen: boolean,
+    isCopy: boolean,
+    projectToCopy?: Project,
     filter: string,
-    name: string,
+    groupId: string,
+    artifactId: string,
     version: string,
     folder: string,
     type: string,
@@ -54,8 +64,10 @@ export class ProjectsPage extends React.Component<Props, 
State> {
     public state: State = {
         projects: this.props.projects,
         isCreateModalOpen: false,
+        isCopy: false,
         filter: '',
-        name: '',
+        groupId: '',
+        artifactId: '',
         version: '',
         folder: '',
         type: 'KARAVAN',
@@ -74,7 +86,7 @@ export class ProjectsPage extends React.Component<Props, 
State> {
                         onClick={e => 
this.props.onRefresh.call(this)}>Refresh</Button>
             </ToolbarItem>
             <ToolbarItem>
-                <Button icon={<PlusIcon/>} onClick={e => 
this.setState({isCreateModalOpen: true})}>Create</Button>
+                <Button icon={<PlusIcon/>} onClick={e => 
this.setState({isCreateModalOpen: true, isCopy: false})}>Create</Button>
             </ToolbarItem>
         </ToolbarContent>
     </Toolbar>);
@@ -84,27 +96,28 @@ export class ProjectsPage extends React.Component<Props, 
State> {
     </TextContent>);
 
     closeModal = () => {
-        this.setState({isCreateModalOpen: false, name: '', version: '', 
folder: '', type: 'KARAVAN'});
+        this.setState({isCreateModalOpen: false, isCopy: false, groupId: '', 
artifactId:'', version: '', folder: '', type: 'KARAVAN'});
         this.props.onRefresh.call(this);
     }
 
     saveAndCloseCreateModal = () => {
-        const p = new Project(this.state.name, this.state.version, 
this.state.folder, this.state.type? this.state.type : "KARAVAN", '');
+        const {groupId, artifactId, version, type} = this.state;
+        const p = new Project(groupId, artifactId, version, '', type? type : 
"KARAVAN", '');
         this.props.onCreate.call(this, p);
-        this.setState({isCreateModalOpen: false, name: '', version: '', 
folder: '', type: 'KARAVAN'});
+        this.setState({isCreateModalOpen: false, isCopy: false, groupId: '', 
artifactId: '', version: '', folder: '', type: 'KARAVAN'});
     }
 
     onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
-        if (event.key === 'Enter' && this.state.name !== undefined && 
this.state.version !== undefined) {
+        if (event.key === 'Enter' && this.state.groupId !== undefined && 
this.state.artifactId !== undefined && this.state.version !== undefined) {
             this.saveAndCloseCreateModal();
         }
     }
 
     createModalForm() {
-        const {type } = this.state;
+        const {type, isCopy, projectToCopy } = this.state;
         return (
             <Modal
-                title="Create new Project"
+                title={!isCopy ? "Create new project" : "Copy project from " + 
projectToCopy?.artifactId}
                 variant={ModalVariant.small}
                 isOpen={this.state.isCreateModalOpen}
                 onClose={this.closeModal}
@@ -115,10 +128,15 @@ export class ProjectsPage extends React.Component<Props, 
State> {
                 ]}
             >
                 <Form isHorizontal={true} autoComplete="off">
-                    <FormGroup label="Name" fieldId="name" isRequired>
-                        <TextInput className="text-field" type="text" 
id="name" name="name"
-                                   value={this.state.name}
-                                   onChange={e => this.setState({name: e, 
folder: CamelUi.nameFromTitle(e.trim())})}/>
+                    <FormGroup label="GroupId" fieldId="group" isRequired>
+                        <TextInput className="text-field" type="text" 
id="group" name="group"
+                                   value={this.state.groupId}
+                                   onChange={e => this.setState({groupId: 
e})}/>
+                    </FormGroup>
+                    <FormGroup label="ArtifactId" fieldId="artifact" 
isRequired>
+                        <TextInput className="text-field" type="text" 
id="artifact" name="artifact"
+                                   value={this.state.artifactId}
+                                   onChange={e => this.setState({artifactId: 
e})}/>
                     </FormGroup>
                     <FormGroup label="Version" fieldId="version" isRequired>
                         <TextInput className="text-field" type="text" 
id="version" name="version"
@@ -138,7 +156,7 @@ export class ProjectsPage extends React.Component<Props, 
State> {
     }
 
     render() {
-        const projects = this.state.projects.filter(p => 
p.name.includes(this.state.filter));
+        const projects = this.state.projects.filter(p => 
p.groupId.includes(this.state.filter) || 
p.artifactId.includes(this.state.filter));
         return (
             <PageSection className="kamelet-section projects-page" 
padding={{default: 'noPadding'}}>
                 <PageSection className="tools-section" padding={{default: 
'noPadding'}}>
@@ -149,33 +167,58 @@ export class ProjectsPage extends React.Component<Props, 
State> {
                         <Thead>
                             <Tr>
                                 <Th key='type'>Type</Th>
-                                <Th key='name'>Name</Th>
+                                <Th key='group'>GroupId</Th>
+                                <Th key='artifact'>ArtifactId</Th>
                                 <Th key='version'>Version</Th>
-                                <Th key='folder'>Folder</Th>
-                                <Th key='status'>Status</Th>
+                                <Th key='commit'>Commit</Th>
+                                <Th key='deployment'>Deployment</Th>
                                 <Th key='action'></Th>
                             </Tr>
                         </Thead>
                         <Tbody>
                             {projects.map(project => (
-                                <Tr key={project.name}>
+                                <Tr key={project.artifactId}>
                                     <Td modifier={"fitContent"}>
                                         <Tooltip content={project.type} 
position={"left"}>
                                             
<Badge>{project.type.charAt(0)}</Badge>
                                         </Tooltip>
                                     </Td>
+                                    <Td>{project.groupId}</Td>
                                     <Td>
                                         <Button style={{padding: '6px'}} 
variant={"link"} onClick={e=>this.props.onSelect?.call(this, project)}>
-                                            
{CamelUi.titleFromName(project.name)}
+                                            {project.artifactId}
                                         </Button>
                                     </Td>
                                     <Td>{project.version}</Td>
-                                    <Td>{project.folder}</Td>
-                                    <Td>Active</Td>
-                                    <Td modifier={"fitContent"}>
-                                        <Button style={{padding: '0'}} 
variant={"plain"} onClick={e=>this.props.onDelete?.call(this, project)}>
-                                            <DeleteIcon/>
-                                        </Button>
+                                    <Td isActionCell>
+                                        <Tooltip content={project.lastCommit} 
position={"bottom"}>
+                                            
<Badge>{project.lastCommit?.substr(0, 7)}</Badge>
+                                        </Tooltip>
+                                    </Td>
+                                    <Td noPadding style={{width:"180px"}}>
+                                        <Flex direction={{default: "row"}}>
+                                            <FlexItem><Badge 
isRead>dev</Badge></FlexItem>
+                                            <FlexItem><Badge 
isRead>test</Badge></FlexItem>
+                                            <FlexItem><Badge 
isRead>prod</Badge></FlexItem>
+                                        </Flex>
+                                    </Td>
+                                    <Td isActionCell>
+                                        <OverflowMenu breakpoint="md">
+                                            <OverflowMenuContent>
+                                                <OverflowMenuGroup 
groupType="button">
+                                                    <OverflowMenuItem>
+                                                        <Tooltip 
content={"Copy project"} position={"bottom"}>
+                                                            <Button 
variant={"plain"} icon={<CopyIcon/>} 
onClick={e=>this.setState({isCreateModalOpen: true, isCopy: true, 
projectToCopy: project})}></Button>
+                                                        </Tooltip>
+                                                    </OverflowMenuItem>
+                                                    <OverflowMenuItem>
+                                                        <Tooltip 
content={"Delete project"} position={"bottom"}>
+                                                            <Button 
variant={"plain"} icon={<DeleteIcon/>} 
onClick={e=>this.props.onDelete?.call(this, project)}></Button>
+                                                        </Tooltip>
+                                                    </OverflowMenuItem>
+                                                </OverflowMenuGroup>
+                                            </OverflowMenuContent>
+                                        </OverflowMenu>
                                     </Td>
                                 </Tr>
                             ))}
diff --git a/karavan-docker/Dockerfile b/karavan-docker/Dockerfile
index 295a5df..06b4d18 100644
--- a/karavan-docker/Dockerfile
+++ b/karavan-docker/Dockerfile
@@ -1,8 +1,23 @@
-FROM jbangdev/jbang-action
+FROM adoptopenjdk/maven-openjdk11
 
-WORKDIR /ws
+# Install JBang
+RUN curl -Ls 
"https://github.com/jbangdev/jbang/releases/download/v0.94.0/jbang-0.94.0.zip"; 
--output jbang-0.94.0.zip && \
+    apt-get update -y && apt-get install unzip git -y && apt-get clean && \
+    unzip jbang-0.94.0.zip && \
+    rm jbang-0.94.0.zip && \
+    chmod +x jbang-0.94.0/bin/jbang    
+
+# Environment variables
+ENV PATH="${PATH}:/jbang-0.94.0/bin"
+ENV JBANG_VERSION 0.94.0
+ENV JBANG_PATH=/jbang/bin
+ENV PATH="${PATH}:/jbang/bin"
+ENV JBANG_DIR="/jbang/.jbang"
 
-RUN jbang trust add -o --fresh --quiet 
https://github.com/apache/camel/blob/HEAD/dsl/camel-jbang/camel-jbang-main/dist/CamelJBang.java
-RUN jbang -Dcamel.jbang.version=3.18.0-SNAPSHOT camel@apache/camel --version 
+# Install Camel-JBang
+RUN jbang trust add -o --fresh --quiet 
https://github.com/apache/camel/blob/HEAD/dsl/camel-jbang/camel-jbang-main/dist/CamelJBang.java
 && \
+    jbang -Dcamel.jbang.version=3.18.0-SNAPSHOT camel@apache/camel --version 
 
-ENTRYPOINT ["entrypoint"]
\ No newline at end of file
+
+WORKDIR /ws
+ENTRYPOINT ["/jbang-0.94.0/bin/jbang"]
\ No newline at end of file
diff --git a/karavan-docker/demo/application.properties 
b/karavan-docker/demo/application.properties
deleted file mode 100644
index 97a2189..0000000
--- a/karavan-docker/demo/application.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-camel.jbang.project.name=postman
-camel.jbang.project.version=1.0.0

Reply via email to