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 2022/06/21 15:43:44 UTC

[camel-karavan] branch main updated: Simplified Project Management (#380)

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 6238883  Simplified Project Management (#380)
6238883 is described below

commit 62388835216f34216ccaa98ca5a1db2ec2138aba
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Tue Jun 21 11:43:41 2022 -0400

    Simplified Project Management (#380)
---
 .../camel/karavan/api/ConfigurationResource.java   |  7 +-
 .../org/apache/camel/karavan/api/GitResource.java  |  4 +-
 .../camel/karavan/api/ProjectFileResource.java     |  6 +-
 .../apache/camel/karavan/api/ProjectResource.java  |  4 +-
 .../camel/karavan/model/KaravanConfiguration.java  |  3 +-
 .../org/apache/camel/karavan/model/Project.java    | 77 +++++++-------------
 .../apache/camel/karavan/model/ProjectFile.java    | 14 ++--
 .../camel/karavan/service/GeneratorService.java    | 22 +++---
 .../apache/camel/karavan/service/GitService.java   |  6 +-
 .../camel/karavan/service/InfinispanService.java   | 71 +++++++++++--------
 .../src/main/resources/application.properties      |  5 +-
 karavan-app/src/main/webapp/src/Main.tsx           |  2 +-
 karavan-app/src/main/webapp/src/api/KaravanApi.tsx |  4 +-
 .../main/webapp/src/builder/PropertiesTable.tsx    |  2 +-
 .../src/main/webapp/src/models/ProjectModels.ts    | 31 +++-----
 .../main/webapp/src/projects/CreateFileModal.tsx   |  4 +-
 .../src/main/webapp/src/projects/ProjectPage.tsx   | 29 +++-----
 .../src/main/webapp/src/projects/ProjectsPage.tsx  | 82 ++++++++++------------
 karavan-designer/src/builder/PropertiesTable.tsx   |  2 +-
 19 files changed, 161 insertions(+), 214 deletions(-)

diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/api/ConfigurationResource.java b/karavan-app/src/main/java/org/apache/camel/karavan/api/ConfigurationResource.java
index d215181..5e01b29 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/api/ConfigurationResource.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/ConfigurationResource.java
@@ -25,7 +25,6 @@ import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
-import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
@@ -35,8 +34,6 @@ public class ConfigurationResource {
     @ConfigProperty(name = "karavan.version")
     String version;
 
-    @ConfigProperty(name = "karavan.mode")
-    String mode;
 
     @Inject
     KaravanConfiguration configuration;
@@ -47,10 +44,8 @@ public class ConfigurationResource {
         return RestResponse.ResponseBuilder.ok(
                 Map.of(
                         "version", version,
-                        "mode", mode,
                         "environments", configuration.environments().stream().map(e -> e.name()).collect(Collectors.toList()),
-                        "defaultRuntime", configuration.defaultRuntime(),
-                        "groupId", configuration.groupId()
+                        "runtime", configuration.runtime()
                 )
         ).build();
     }
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 05966b4..5fa989f 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.getKey());
-        List<ProjectFile> files = infinispanService.getProjectFiles(project.getKey());
+        Project p = infinispanService.getProject(project.getProjectId());
+        List<ProjectFile> files = infinispanService.getProjectFiles(project.getProjectId());
         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/ProjectFileResource.java b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java
index e45a8de..cf40128 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectFileResource.java
@@ -41,10 +41,10 @@ public class ProjectFileResource {
 
     @GET
     @Produces(MediaType.APPLICATION_JSON)
-    @Path("/{project}")
+    @Path("/{projectId}")
     public List<ProjectFile> get(@HeaderParam("username") String username,
-                                      @PathParam("project") String project) throws Exception {
-        return infinispanService.getProjectFiles(project);
+                                      @PathParam("projectId") String projectId) throws Exception {
+        return infinispanService.getProjectFiles(projectId);
     }
 
     @POST
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 edc4743..bdd149d 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
@@ -48,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::getKey))
+                .sorted(Comparator.comparing(Project::getProjectId))
                 .collect(Collectors.toList());
     }
 
@@ -87,7 +87,7 @@ public class ProjectResource {
 
 //        Copy files
         Map<GroupedKey, ProjectFile> map = infinispanService.getProjectFiles(sourceProject).stream()
-                .collect(Collectors.toMap(f -> new GroupedKey(project.getKey(), f.getName()), f -> f));
+                .collect(Collectors.toMap(f -> new GroupedKey(project.getProjectId(), f.getName()), f -> f));
         infinispanService.saveProjectFiles(map);
         return project;
     }
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/model/KaravanConfiguration.java b/karavan-app/src/main/java/org/apache/camel/karavan/model/KaravanConfiguration.java
index fed618c..8a2fc6b 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/model/KaravanConfiguration.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/model/KaravanConfiguration.java
@@ -3,13 +3,12 @@ package org.apache.camel.karavan.model;
 import io.smallrye.config.ConfigMapping;
 
 import java.util.List;
-import java.util.Set;
 
 @ConfigMapping(prefix = "karavan.config")
 public interface KaravanConfiguration {
 
     String groupId();
-    String defaultRuntime();
+    String runtime();
     List<Environment> environments();
 
     interface Environment {
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 6a13009..6f0b4cb 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,6 +1,5 @@
 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;
@@ -9,89 +8,67 @@ public class Project {
     public static final String CACHE = "projects";
 
     @ProtoField(number = 1)
-    String groupId;
+    String projectId;
     @ProtoField(number = 2)
-    String artifactId;
+    String name;
     @ProtoField(number = 3)
-    String version;
+    String description;
     @ProtoField(number = 4)
-    String folder;
-    @ProtoField(number = 5)
     Project.CamelRuntime runtime;
-    @ProtoField(number = 6)
+    @ProtoField(number = 5)
     String lastCommit;
 
     public enum CamelRuntime {
         @ProtoEnumValue(number = 0, name = "Quarkus")
         QUARKUS,
         @ProtoEnumValue(number = 1, name = "Spring")
-        SPRING
+        SPRING,
+        @ProtoEnumValue(number = 2, name = "Main")
+        MAIN
     }
 
     @ProtoFactory
-    public Project(String groupId, String artifactId, String version, String folder, CamelRuntime runtime, String lastCommit) {
-        this.groupId = groupId;
-        this.artifactId = artifactId;
-        this.version = version;
-        this.folder = folder;
+    public Project(String projectId, String name, String description, CamelRuntime runtime, String lastCommit) {
+        this.projectId = projectId;
+        this.name = name;
+        this.description = description;
         this.runtime = runtime;
         this.lastCommit = lastCommit;
     }
 
-    public Project(String groupId, String artifactId, String version, String folder, CamelRuntime runtime) {
-        this.groupId = groupId;
-        this.artifactId = artifactId;
-        this.version = version;
-        this.folder = folder != null && folder.trim().length() > 0 ? folder : toFolder(artifactId, version);
+    public Project(String projectId, String name, String description, CamelRuntime runtime) {
+        this.projectId = projectId;
+        this.name = name;
+        this.description = description;
         this.runtime = runtime;
     }
 
-    public static String toFolder(String artifactId, String version){
-        String folder = (artifactId+version).replaceAll("[^A-Za-z0-9 ]", "_");
-        return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, folder);
-    }
-
-    public static String toKey(String groupId, String artifactId, String version){
-        return groupId + ":" + artifactId + ":" + version;
-    }
-
-    public String getKey(){
-        return toKey(groupId, artifactId, version);
-    }
 
     public Project() {
     }
 
-    public String getGroupId() {
-        return groupId;
-    }
-
-    public void setGroupId(String groupId) {
-        this.groupId = groupId;
-    }
-
-    public String getArtifactId() {
-        return artifactId;
+    public String getProjectId() {
+        return projectId;
     }
 
-    public void setArtifactId(String artifactId) {
-        this.artifactId = artifactId;
+    public void setProjectId(String projectId) {
+        this.projectId = projectId;
     }
 
-    public String getVersion() {
-        return version;
+    public String getName() {
+        return name;
     }
 
-    public void setVersion(String version) {
-        this.version = version;
+    public void setName(String name) {
+        this.name = name;
     }
 
-    public String getFolder() {
-        return folder;
+    public String getDescription() {
+        return description;
     }
 
-    public void setFolder(String folder) {
-        this.folder = folder;
+    public void setDescription(String description) {
+        this.description = description;
     }
 
     public CamelRuntime getRuntime() {
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectFile.java b/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectFile.java
index ddd703b..30bb0fe 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectFile.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/model/ProjectFile.java
@@ -12,13 +12,13 @@ public class ProjectFile {
     String code;
     @ProtoField(number = 3)
     @ProtoDoc("@Field(index=Index.YES, analyze = Analyze.YES, store = Store.NO)")
-    String project;
+    String projectId;
 
     @ProtoFactory
-    public ProjectFile(String name, String code, String project) {
+    public ProjectFile(String name, String code, String projectId) {
         this.name = name;
         this.code = code;
-        this.project = project;
+        this.projectId = projectId;
     }
 
     public ProjectFile() {
@@ -40,11 +40,11 @@ public class ProjectFile {
         this.code = code;
     }
 
-    public String getProject() {
-        return project;
+    public String getProjectId() {
+        return projectId;
     }
 
-    public void setProject(String project) {
-        this.project = project;
+    public void setProjectId(String projectId) {
+        this.projectId = projectId;
     }
 }
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/GeneratorService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/GeneratorService.java
index 3467948..02bf3c8 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/GeneratorService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/GeneratorService.java
@@ -20,7 +20,6 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import io.apicurio.datamodels.Library;
 import io.apicurio.datamodels.openapi.models.OasDocument;
-import io.vertx.core.Vertx;
 import org.apache.camel.CamelContext;
 import org.apache.camel.generator.openapi.RestDslGenerator;
 import org.apache.camel.impl.lw.LightweightCamelContext;
@@ -29,17 +28,12 @@ import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.jboss.logging.Logger;
 
 import javax.enterprise.context.ApplicationScoped;
-import javax.inject.Inject;
 
 @ApplicationScoped
 public class GeneratorService {
 
-
-    @ConfigProperty(name = "karavan.folder.integrations")
-    String integrations;
-
-    @Inject
-    Vertx vertx;
+    @ConfigProperty(name = "karavan.config.group-id")
+    String groupId;
 
     private static final Logger LOGGER = Logger.getLogger(GeneratorService.class.getName());
 
@@ -54,12 +48,14 @@ public class GeneratorService {
 
     public String getDefaultApplicationProperties(Project project){
         StringBuilder s = new StringBuilder();
-        s.append("camel.jbang.gav=").append(project.getKey()).append(System.lineSeparator());
+        s.append("camel.jbang.projectId=").append(project.getProjectId()).append(System.lineSeparator());
+        s.append("camel.jbang.projectName=").append(project.getName()).append(System.lineSeparator());
+        s.append("camel.jbang.projectDescription=").append(project.getDescription()).append(System.lineSeparator());
+        s.append("camel.jbang.gav=").append(groupId).append(":").append(project.getProjectId()).append(":").append("1.0.0").append(System.lineSeparator());
         s.append("camel.jbang.runtime=").append(project.getRuntime().name().toLowerCase()).append(System.lineSeparator());
-        s.append("quarkus.container-image.group=").append(project.getGroupId()).append(System.lineSeparator());
-        s.append("quarkus.container-image.name=").append(project.getArtifactId()).append(System.lineSeparator());
-        s.append("quarkus.container-image.tag=").append(project.getVersion()).append(System.lineSeparator());
-        s.append("quarkus.openshift.route.expose=true").append(System.lineSeparator());
+        s.append("quarkus.container-image.group=").append(groupId).append(System.lineSeparator());
+        s.append("quarkus.container-image.name=").append(project.getProjectId()).append(System.lineSeparator());
+        s.append("quarkus.openshift.route.expose=false").append(System.lineSeparator());
         s.append("quarkus.kubernetes-client.trust-certs=true").append(System.lineSeparator());
         return s.toString();
     }
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 4dbcd60..4b3cd88 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.getKey());
+        LOGGER.info("Save project " + project.getProjectId());
         String uuid = UUID.randomUUID().toString();
         String folder = vertx.fileSystem().createTempDirectoryBlocking(uuid);
         LOGGER.infof("Temp folder created: {}", folder);
@@ -97,10 +97,10 @@ public class GitService {
     }
 
     private void writeProjectToFolder(String folder, Project project, List<ProjectFile> files) throws IOException {
-        Files.createDirectories(Paths.get(folder, project.getFolder()));
+        Files.createDirectories(Paths.get(folder, project.getProjectId()));
         files.forEach(file -> {
             try {
-                Files.writeString(Paths.get(folder, project.getFolder(), file.getName()), file.getCode());
+                Files.writeString(Paths.get(folder, project.getProjectId(), file.getName()), file.getCode());
             } catch (IOException e) {
                 throw new RuntimeException(e);
             }
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 a6f5e1c..0ae70c0 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
@@ -17,10 +17,14 @@
 package org.apache.camel.karavan.service;
 
 import io.quarkus.runtime.StartupEvent;
+import io.quarkus.runtime.configuration.ProfileManager;
 import org.apache.camel.karavan.KaravanLifecycleBean;
 import org.apache.camel.karavan.model.GroupedKey;
 import org.apache.camel.karavan.model.Project;
 import org.apache.camel.karavan.model.ProjectFile;
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.infinispan.Cache;
 import org.infinispan.client.hotrod.RemoteCache;
 import org.infinispan.client.hotrod.RemoteCacheManager;
@@ -56,6 +60,9 @@ public class InfinispanService {
     @Inject
     GeneratorService generatorService;
 
+    @ConfigProperty(name = "karavan.config.runtime")
+    String runtime;
+
     private static final String CACHE_CONFIG = "<distributed-cache name=\"%s\">"
             + " <encoding media-type=\"application/x-protostream\"/>"
             + " <groups enabled=\"true\"/>"
@@ -77,26 +84,8 @@ public class InfinispanService {
             projects = cacheManager.administration().getOrCreateCache(Project.CACHE, new XMLStringConfiguration(String.format(CACHE_CONFIG, Project.CACHE)));
             files = cacheManager.administration().getOrCreateCache(ProjectFile.CACHE, new XMLStringConfiguration(String.format(CACHE_CONFIG, ProjectFile.CACHE)));
         }
-
-        for (int i = 0; i < 10; i++){
-            String groupId = "org.apache.camel.karavan";
-            String artifactId = "parcel-demo" + i;
-            Project p = new Project(groupId, artifactId, "1.0.0",  null, Project.CamelRuntime.values()[new Random().nextInt(2)]);
-            this.saveProject(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" +
-                    "@BindToRegistry(\"myBean\")\n" +
-                    "public class CustomProcessor implements Processor {\n" +
-                    "\n" +
-                    "  public void process(Exchange exchange) throws Exception {\n" +
-                    "      exchange.getIn().setBody(\"Hello world\");\n" +
-                    "  }\n" +
-                    "}", p.getKey()));
+        if (ProfileManager.getLaunchMode().isDevOrTest()){
+            generateDevProjects();
         }
     }
 
@@ -105,35 +94,33 @@ public class InfinispanService {
     }
 
     public void saveProject(Project project) {
-        GroupedKey key = GroupedKey.create(project.getKey(), project.getKey());
+        GroupedKey key = GroupedKey.create(project.getProjectId(), project.getProjectId());
         boolean isNew = !projects.containsKey(key);
-        if (project.getFolder() == null || project.getFolder().trim().length() == 0) {
-            project.setFolder(Project.toFolder(project.getArtifactId(), project.getVersion()));
-        }
+        project.setRuntime(Project.CamelRuntime.valueOf(runtime));
         projects.put(key, project);
         if (isNew){
             String filename = "application.properties";
             String code = generatorService.getDefaultApplicationProperties(project);
-            files.put(new GroupedKey(project.getKey(), filename), new ProjectFile(filename, code, project.getKey()));
+            files.put(new GroupedKey(project.getProjectId(), filename), new ProjectFile(filename, code, project.getProjectId()));
         }
     }
 
-    public List<ProjectFile> getProjectFiles(String projectName) {
+    public List<ProjectFile> getProjectFiles(String projectId) {
         if (cacheManager == null) {
             QueryFactory queryFactory = org.infinispan.query.Search.getQueryFactory((Cache<?, ?>) files);
-            return queryFactory.<ProjectFile>create("FROM org.apache.camel.karavan.model.ProjectFile WHERE project = :project")
-                    .setParameter("project", projectName)
+            return queryFactory.<ProjectFile>create("FROM org.apache.camel.karavan.model.ProjectFile WHERE projectId = :projectId")
+                    .setParameter("projectId", projectId)
                     .execute().list();
         } else {
             QueryFactory queryFactory = Search.getQueryFactory((RemoteCache<?, ?>) files);
-            return queryFactory.<ProjectFile>create("FROM karavan.ProjectFile WHERE project = :project")
-                    .setParameter("project", projectName)
+            return queryFactory.<ProjectFile>create("FROM karavan.ProjectFile WHERE projectId = :projectId")
+                    .setParameter("projectId", projectId)
                     .execute().list();
         }
     }
 
     public void saveProjectFile(ProjectFile file) {
-        files.put(GroupedKey.create(file.getProject(), file.getName()), file);
+        files.put(GroupedKey.create(file.getProjectId(), file.getName()), file);
     }
 
     public void saveProjectFiles(Map<GroupedKey, ProjectFile> f) {
@@ -151,4 +138,26 @@ public class InfinispanService {
     public Project getProject(String project) {
         return projects.get(GroupedKey.create(project, project));
     }
+
+    private void generateDevProjects(){
+        for (int i = 0; i < 10; i++){
+            String projectId = "parcel-demo" + i;
+            Project p = new Project(projectId, "Demo project " + i, "Demo project placeholder for UI testing purposes", Project.CamelRuntime.valueOf(runtime));
+            this.saveProject(p);
+
+            files.put(GroupedKey.create(p.getProjectId(),"new-parcels.yaml"), new ProjectFile("new-parcels.yaml", "flows:", p.getProjectId()));
+            files.put(GroupedKey.create(p.getProjectId(),"parcel-confirmation.yaml"), new ProjectFile("parcel-confirmation.yaml", "rest:", p.getProjectId()));
+            files.put(GroupedKey.create(p.getProjectId(),"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" +
+                    "@BindToRegistry(\"myBean\")\n" +
+                    "public class CustomProcessor implements Processor {\n" +
+                    "\n" +
+                    "  public void process(Exchange exchange) throws Exception {\n" +
+                    "      exchange.getIn().setBody(\"Hello world\");\n" +
+                    "  }\n" +
+                    "}", p.getProjectId()));
+        }
+    }
 }
diff --git a/karavan-app/src/main/resources/application.properties b/karavan-app/src/main/resources/application.properties
index 9d953ca..13820c9 100644
--- a/karavan-app/src/main/resources/application.properties
+++ b/karavan-app/src/main/resources/application.properties
@@ -5,7 +5,6 @@ karavan.folder.kamelets-buildin=kamelets-buildin
 %dev.karavan.folder.kamelets-buildin=target/classes/kamelets
 karavan.folder.kamelets-custom=kamelets
 
-karavan.mode=local
 karavan.folder.integrations=integrations
 %dev.karavan.folder.integrations=target/classes/integrations
 
@@ -16,8 +15,8 @@ karavan.git.password=
 karavan.git.main=main
 
 # Projects configuration
-karavan.config.group-id=com.acme
-karavan.config.default-runtime=QUARKUS
+karavan.config.group-id=org.camel.karavan.demo
+karavan.config.runtime=QUARKUS
 karavan.config.environments[0].name=dev
 karavan.config.environments[0].cluster=kubernetes.default.svc
 karavan.config.environments[1].name=test
diff --git a/karavan-app/src/main/webapp/src/Main.tsx b/karavan-app/src/main/webapp/src/Main.tsx
index edc2b16..f34cad6 100644
--- a/karavan-app/src/main/webapp/src/Main.tsx
+++ b/karavan-app/src/main/webapp/src/Main.tsx
@@ -247,7 +247,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?.getKey() + "?"}</div>
+                    <div>{"Are you sure you want to delete the project " + this.state.projectToDelete?.projectId + "?"}</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 cb59783..b8616ee 100644
--- a/karavan-app/src/main/webapp/src/api/KaravanApi.tsx
+++ b/karavan-app/src/main/webapp/src/api/KaravanApi.tsx
@@ -60,7 +60,7 @@ export const KaravanApi = {
     },
 
     deleteProject: async (project: Project, after: (res: AxiosResponse<any>) => void) => {
-        axios.delete('/project/' + encodeURI(project.getKey()),
+        axios.delete('/project/' + encodeURI(project.projectId),
             {headers:{'username': 'cameleer'}})
             .then(res => {
                 after(res);
@@ -92,7 +92,7 @@ export const KaravanApi = {
     },
 
     deleteProjectFile: async (file: ProjectFile, after: (res: AxiosResponse<any>) => void) => {
-        axios.delete('/file/' + file.project + '/' + file.name,
+        axios.delete('/file/' + file.projectId + '/' + file.name,
             {headers:{'username': 'cameleer'}})
             .then(res => {
                 after(res);
diff --git a/karavan-app/src/main/webapp/src/builder/PropertiesTable.tsx b/karavan-app/src/main/webapp/src/builder/PropertiesTable.tsx
index 27d5a88..f59cec6 100644
--- a/karavan-app/src/main/webapp/src/builder/PropertiesTable.tsx
+++ b/karavan-app/src/main/webapp/src/builder/PropertiesTable.tsx
@@ -100,7 +100,7 @@ export class PropertiesTable extends React.Component<Props, State> {
                         </Thead>
                         <Tbody>
                             {properties.map((property, idx: number) => {
-                                const readOnly = ["camel.jbang.gav", "camel.jbang.runtime"].includes(property.key);
+                                const readOnly = property.key.startsWith("camel.jbang");
                                 return (
                                     <Tr key={property.id}>
                                         <Td noPadding width={20} dataLabel="key">{this.getTextInputField(property, "key", readOnly)}</Td>
diff --git a/karavan-app/src/main/webapp/src/models/ProjectModels.ts b/karavan-app/src/main/webapp/src/models/ProjectModels.ts
index edc033d..89daf48 100644
--- a/karavan-app/src/main/webapp/src/models/ProjectModels.ts
+++ b/karavan-app/src/main/webapp/src/models/ProjectModels.ts
@@ -1,42 +1,33 @@
 export class Project {
-    groupId: string = '';
-    artifactId: string = '';
-    version: string = '';
-    folder: string = '';
-    runtime: string = '';
+    projectId: string = '';
+    name: string = '';
+    description: string = '';
     lastCommit: string = '';
 
-
-    public constructor(groupId: string, artifactId: string, version: string, folder: string, runtime: string, lastCommit: string);
+    public constructor(projectId: string, name: string, description: 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.runtime = args[4];
-            this.lastCommit = args[5];
+            this.projectId = args[0];
+            this.name = args[1];
+            this.description = args[2];
+            this.lastCommit = args[3];
             return;
         }
     }
-
-    getKey():string{
-        return this.groupId + ":" + this.artifactId + ":" + this.version;
-    }
 }
 
 export class ProjectFile {
     name: string = '';
-    project: string = '';
+    projectId: string = '';
     code: string = '';
 
-    constructor(name: string, project: string, code: string) {
+    constructor(name: string, projectId: string, code: string) {
         this.name = name;
-        this.project = project;
+        this.projectId = projectId;
         this.code = code;
     }
 }
diff --git a/karavan-app/src/main/webapp/src/projects/CreateFileModal.tsx b/karavan-app/src/main/webapp/src/projects/CreateFileModal.tsx
index bf5edde..efe47c5 100644
--- a/karavan-app/src/main/webapp/src/projects/CreateFileModal.tsx
+++ b/karavan-app/src/main/webapp/src/projects/CreateFileModal.tsx
@@ -5,7 +5,7 @@ import {
     FormGroup,
     ModalVariant,
     Form,
-    ToggleGroupItem, ToggleGroup, TextInputGroupMain, ChipGroup, Chip, TextInputGroupUtilities, TextInputGroup, Text
+    ToggleGroupItem, ToggleGroup, TextInputGroupMain, TextInputGroupUtilities, TextInputGroup, Text
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {KaravanApi} from "../api/KaravanApi";
@@ -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.getKey(), '');
+            const file = new ProjectFile(filename + '.' + extension, this.props.project.projectId, '');
             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 f874a18..acdf5ae 100644
--- a/karavan-app/src/main/webapp/src/projects/ProjectPage.tsx
+++ b/karavan-app/src/main/webapp/src/projects/ProjectPage.tsx
@@ -156,7 +156,7 @@ export class ProjectPage extends React.Component<Props, State> {
                 <div>
                     <Breadcrumb>
                         <BreadcrumbItem to="#"
-                                        onClick={event => this.setState({file: undefined})}>{"Project: " + this.props.project?.getKey()}</BreadcrumbItem>
+                                        onClick={event => this.setState({file: undefined})}>{"Project: " + this.props.project?.projectId}</BreadcrumbItem>
                         <BreadcrumbItem to="#" isActive>{this.getType(file?.name)}</BreadcrumbItem>
                     </Breadcrumb>
                     <TextContent className="title">
@@ -172,12 +172,12 @@ export class ProjectPage extends React.Component<Props, State> {
 
     onRefresh = () => {
         if (this.props.project) {
-            KaravanApi.getProject(this.props.project.getKey(), (project: Project) => {
+            KaravanApi.getProject(this.props.project.projectId, (project: Project) => {
                 this.setState({
                     project: project
                 })
             });
-            KaravanApi.getFiles(this.props.project.getKey(), (files: []) => {
+            KaravanApi.getFiles(this.props.project.projectId, (files: []) => {
                 this.setState({
                     files: files
                 })
@@ -251,27 +251,18 @@ export class ProjectPage extends React.Component<Props, State> {
                         <FlexItem flex={{default: "flex_1"}}>
                             <DescriptionList isHorizontal>
                                 <DescriptionListGroup>
-                                    <DescriptionListTerm>Group</DescriptionListTerm>
-                                    <DescriptionListDescription>{project?.groupId}</DescriptionListDescription>
+                                    <DescriptionListTerm>Project ID</DescriptionListTerm>
+                                    <DescriptionListDescription>{project?.projectId}</DescriptionListDescription>
                                 </DescriptionListGroup>
                                 <DescriptionListGroup>
-                                    <DescriptionListTerm>Artifact</DescriptionListTerm>
-                                    <DescriptionListDescription>{project?.artifactId}</DescriptionListDescription>
+                                    <DescriptionListTerm>Name</DescriptionListTerm>
+                                    <DescriptionListDescription>{project?.name}</DescriptionListDescription>
                                 </DescriptionListGroup>
                                 <DescriptionListGroup>
-                                    <DescriptionListTerm>Version</DescriptionListTerm>
-                                    <DescriptionListDescription>{project?.version}</DescriptionListDescription>
-                                </DescriptionListGroup>
-                                <DescriptionListGroup>
-                                    <DescriptionListTerm>Folder</DescriptionListTerm>
-                                    <DescriptionListDescription>{project?.folder}</DescriptionListDescription>
-                                </DescriptionListGroup>
-                                <DescriptionListGroup>
-                                    <DescriptionListTerm>Runtime</DescriptionListTerm>
-                                    <DescriptionListDescription>
-                                        <Badge>{project?.runtime}</Badge>
-                                    </DescriptionListDescription>
+                                    <DescriptionListTerm>Description</DescriptionListTerm>
+                                    <DescriptionListDescription>{project?.description}</DescriptionListDescription>
                                 </DescriptionListGroup>
+
                             </DescriptionList>
                         </FlexItem>
                         <FlexItem flex={{default: "flex_1"}}>
diff --git a/karavan-app/src/main/webapp/src/projects/ProjectsPage.tsx b/karavan-app/src/main/webapp/src/projects/ProjectsPage.tsx
index c1bc9c3..7f19ac6 100644
--- a/karavan-app/src/main/webapp/src/projects/ProjectsPage.tsx
+++ b/karavan-app/src/main/webapp/src/projects/ProjectsPage.tsx
@@ -34,9 +34,9 @@ import PlusIcon from '@patternfly/react-icons/dist/esm/icons/plus-icon';
 import {Project} from "../models/ProjectModels";
 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";
+import {CamelUi} from "../designer/utils/CamelUi";
 
 interface Props {
     projects: Project[],
@@ -53,11 +53,9 @@ interface State {
     isCopy: boolean,
     projectToCopy?: Project,
     filter: string,
-    groupId: string,
-    artifactId: string,
-    version: string,
-    folder: string,
-    runtime: string,
+    name: string,
+    description: string,
+    projectId: string,
 }
 
 export class ProjectsPage extends React.Component<Props, State> {
@@ -67,11 +65,9 @@ export class ProjectsPage extends React.Component<Props, State> {
         isCreateModalOpen: false,
         isCopy: false,
         filter: '',
-        groupId: this.props.config.groupId,
-        artifactId: '',
-        version: '',
-        folder: '',
-        runtime: this.props.config.defaultRuntime,
+        name: '',
+        description: '',
+        projectId: '',
     };
 
     tools = () => (<Toolbar id="toolbar-group-types">
@@ -97,28 +93,28 @@ export class ProjectsPage extends React.Component<Props, State> {
     </TextContent>);
 
     closeModal = () => {
-        this.setState({isCreateModalOpen: false, isCopy: false, groupId: this.props.config.groupId, artifactId:'', version: '', folder: '', runtime: this.props.config.defaultRuntime});
+        this.setState({isCreateModalOpen: false, isCopy: false, name: this.props.config.groupId, description:'', projectId: ''});
         this.props.onRefresh.call(this);
     }
 
     saveAndCloseCreateModal = () => {
-        const {groupId, artifactId, version, runtime} = this.state;
-        const p = new Project(groupId, artifactId, version, '', runtime ? runtime : this.props.config.defaultRuntime, '');
+        const {name, description, projectId} = this.state;
+        const p = new Project(projectId, name, description, '');
         this.props.onCreate.call(this, p);
-        this.setState({isCreateModalOpen: false, isCopy: false, groupId: this.props.config.groupId, artifactId: '', version: '', folder: '', runtime: this.props.config.defaultRuntime});
+        this.setState({isCreateModalOpen: false, isCopy: false, name: this.props.config.groupId, description: '',  projectId: ''});
     }
 
     onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
-        if (event.key === 'Enter' && this.state.groupId !== undefined && this.state.artifactId !== undefined && this.state.version !== undefined) {
+        if (event.key === 'Enter' && this.state.name !== undefined && this.state.description !== undefined && this.state.projectId !== undefined) {
             this.saveAndCloseCreateModal();
         }
     }
 
     createModalForm() {
-        const {runtime, isCopy, projectToCopy } = this.state;
+        const {isCopy, projectToCopy, projectId, name} = this.state;
         return (
             <Modal
-                title={!isCopy ? "Create new project" : "Copy project from " + projectToCopy?.artifactId}
+                title={!isCopy ? "Create new project" : "Copy project from " + projectToCopy?.projectId}
                 variant={ModalVariant.small}
                 isOpen={this.state.isCreateModalOpen}
                 onClose={this.closeModal}
@@ -129,27 +125,21 @@ export class ProjectsPage extends React.Component<Props, State> {
                 ]}
             >
                 <Form isHorizontal={true} autoComplete="off">
-                    <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 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})}/>
                     </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 label="Description" fieldId="description" isRequired>
+                        <TextInput className="text-field" type="text" id="description" name="description"
+                                   value={this.state.description}
+                                   onChange={e => this.setState({description: e})}/>
                     </FormGroup>
-                    <FormGroup label="Version" fieldId="version" isRequired>
-                        <TextInput className="text-field" type="text" id="version" name="version"
-                                   value={this.state.version}
-                                   onChange={e => this.setState({version: e})}/>
-                    </FormGroup>
-                    <FormGroup label="Runtime" fieldId="runtime" isRequired>
-                        <ToggleGroup aria-label="Runtime">
-                            {["QUARKUS", "SPRING"].map(value =>
-                                <ToggleGroupItem key={value} text={CamelUtil.capitalizeName(value.toLowerCase())} buttonId={value} isSelected={runtime === value} onChange={selected => this.setState({runtime: value})} />
-                            )}
-                        </ToggleGroup>
+                    <FormGroup label="Project ID" fieldId="projectId" isRequired helperText="Unique project name">
+                        <TextInput className="text-field" type="text" id="projectId" name="projectId"
+                                   value={this.state.projectId}
+                                   onFocus={e => this.setState({projectId : projectId === '' ? CamelUi.nameFromTitle(name) : projectId})}
+                                   onChange={e => this.setState({projectId: CamelUi.nameFromTitle(e)})}/>
                     </FormGroup>
                 </Form>
             </Modal>
@@ -157,7 +147,7 @@ export class ProjectsPage extends React.Component<Props, State> {
     }
 
     render() {
-        const projects = this.state.projects.filter(p => p.groupId.includes(this.state.filter) || p.artifactId.includes(this.state.filter));
+        const projects = this.state.projects.filter(p => p.name.includes(this.state.filter) || p.description.includes(this.state.filter));
         const environments: string[] = this.props.config.environments && Array.isArray(this.props.config.environments)
             ? Array.from(this.props.config.environments)
             : [];
@@ -171,9 +161,9 @@ export class ProjectsPage extends React.Component<Props, State> {
                         <Thead>
                             <Tr>
                                 <Th key='type'>Runtime</Th>
-                                <Th key='group'>GroupId</Th>
-                                <Th key='artifact'>ArtifactId</Th>
-                                <Th key='version'>Version</Th>
+                                <Th key='projectId'>Project ID</Th>
+                                <Th key='name'>Name</Th>
+                                <Th key='description'>Description</Th>
                                 <Th key='commit'>Commit</Th>
                                 <Th key='deployment'>Deployment</Th>
                                 <Th key='action'></Th>
@@ -181,17 +171,17 @@ export class ProjectsPage extends React.Component<Props, State> {
                         </Thead>
                         <Tbody>
                             {projects.map(project => (
-                                <Tr key={project.artifactId}>
+                                <Tr key={project.projectId}>
                                     <Td modifier={"fitContent"}>
-                                        <Badge className="runtime-badge">{project.runtime}</Badge>
+                                        <Badge className="runtime-badge">{this.props.config.runtime}</Badge>
                                     </Td>
-                                    <Td>{project.groupId}</Td>
                                     <Td>
                                         <Button style={{padding: '6px'}} variant={"link"} onClick={e=>this.props.onSelect?.call(this, project)}>
-                                            {project.artifactId}
+                                            {project.projectId}
                                         </Button>
                                     </Td>
-                                    <Td>{project.version}</Td>
+                                    <Td>{project.name}</Td>
+                                    <Td>{project.description}</Td>
                                     <Td isActionCell>
                                         <Tooltip content={project.lastCommit} position={"bottom"}>
                                             <Badge>{project.lastCommit?.substr(0, 7)}</Badge>
diff --git a/karavan-designer/src/builder/PropertiesTable.tsx b/karavan-designer/src/builder/PropertiesTable.tsx
index 27d5a88..f59cec6 100644
--- a/karavan-designer/src/builder/PropertiesTable.tsx
+++ b/karavan-designer/src/builder/PropertiesTable.tsx
@@ -100,7 +100,7 @@ export class PropertiesTable extends React.Component<Props, State> {
                         </Thead>
                         <Tbody>
                             {properties.map((property, idx: number) => {
-                                const readOnly = ["camel.jbang.gav", "camel.jbang.runtime"].includes(property.key);
+                                const readOnly = property.key.startsWith("camel.jbang");
                                 return (
                                     <Tr key={property.id}>
                                         <Td noPadding width={20} dataLabel="key">{this.getTextInputField(property, "key", readOnly)}</Td>