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 f0a01a48 Fixes for 4.7
f0a01a48 is described below
commit f0a01a48feb27a12a8c03e9cf6ea083839703f82
Author: Marat Gubaidullin <[email protected]>
AuthorDate: Mon Jul 15 11:04:31 2024 -0400
Fixes for 4.7
---
.../org/apache/camel/karavan/KaravanCache.java | 4 +-
.../org/apache/camel/karavan/KaravanConstants.java | 8 ++
.../org/apache/camel/karavan/KaravanEvents.java | 8 +-
.../apache/camel/karavan/KaravanStartupLoader.java | 4 +-
.../camel/karavan/api/ConfigurationResource.java | 2 +-
.../camel/karavan/api/ContainerResource.java | 5 +-
.../apache/camel/karavan/api/DevModeResource.java | 6 +-
.../apache/camel/karavan/api/ImagesResource.java | 35 +++++--
.../karavan/docker/DockerComposeConverter.java | 2 +-
.../camel/karavan/docker/DockerEventHandler.java | 7 ++
.../apache/camel/karavan/docker/DockerService.java | 27 +++++-
.../camel/karavan/listener/CommitListener.java | 10 +-
.../camel/karavan/listener/ConfigListener.java | 6 +-
.../{ConfigListener.java => DockerListener.java} | 43 ++++-----
.../karavan/listener/NotificationListener.java | 16 +++-
.../apache/camel/karavan/model/Configuration.java | 14 ++-
.../apache/camel/karavan/model/ContainerImage.java | 68 ++++++++++++++
.../apache/camel/karavan/service/CodeService.java | 31 +++++-
.../camel/karavan/service/ConfigService.java | 30 +++---
.../apache/camel/karavan/service/GitService.java | 2 -
.../camel/karavan/service/ProjectService.java | 30 ++----
karavan-app/src/main/webui/src/api/KaravanApi.tsx | 35 +++++--
.../src/main/webui/src/api/NotificationService.ts | 3 +
.../src/main/webui/src/api/ProjectModels.ts | 8 ++
.../src/main/webui/src/api/ProjectService.ts | 4 +-
karavan-app/src/main/webui/src/api/ProjectStore.ts | 8 +-
.../src/main/webui/src/log/ProjectLogPanel.tsx | 19 ++--
karavan-app/src/main/webui/src/main/Main.tsx | 13 +--
.../src/main/webui/src/project/DevModeToolbar.tsx | 1 -
.../main/webui/src/project/beans/BeanWizard.tsx | 2 +-
.../main/webui/src/project/builder/ImagesPanel.tsx | 104 +++++++++++++++------
.../src/project/container/ProjectContainerTab.tsx | 47 +++++-----
.../src/main/webui/src/project/files/FilesTab.tsx | 20 ++--
.../main/webui/src/projects/CreateProjectModal.tsx | 7 +-
.../main/webui/src/projects/DeleteProjectModal.tsx | 3 +-
karavan-app/src/main/webui/src/util/StringUtils.ts | 4 +
36 files changed, 431 insertions(+), 205 deletions(-)
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/KaravanCache.java
b/karavan-app/src/main/java/org/apache/camel/karavan/KaravanCache.java
index 6f05f72d..b1d29405 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/KaravanCache.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/KaravanCache.java
@@ -97,8 +97,8 @@ public class KaravanCache {
}
public Map<String, ProjectFile> getProjectFilesMap(String projectId) {
- return files.entrySet().stream().filter(es ->
!Objects.isNull(es.getValue()) && Objects.equals(es.getValue().getProjectId(),
projectId))
- .collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue));
+ return getCopyProjectFiles().stream().filter(pf -> !Objects.isNull(pf)
&& Objects.equals(pf.getProjectId(), projectId))
+ .collect(Collectors.toMap(ProjectFile::getName,
ProjectFile::copy));
}
public ProjectFile getProjectFile(String projectId, String filename) {
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/KaravanConstants.java
b/karavan-app/src/main/java/org/apache/camel/karavan/KaravanConstants.java
index 63c285df..68e6f031 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/KaravanConstants.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/KaravanConstants.java
@@ -41,6 +41,14 @@ public class KaravanConstants {
public static final String LABEL_KUBERNETES_RUNTIME =
"app.kubernetes.io/runtime";
public static final String ANNOTATION_COMMIT =
"jkube.eclipse.org/git-commit";
+ public static final String PROPERTY_NAME_PROJECT_ID =
"camel.karavan.projectId";
+ public static final String PROPERTY_NAME_PROJECT_NAME =
"camel.karavan.projectName";
+ public static final String PROPERTY_NAME_GAV = "camel.jbang.gav";
+
+ public static final String PROPERTY_FORMATTER_PROJECT_ID =
PROPERTY_NAME_PROJECT_ID + "=%s";
+ public static final String PROPERTY_FORMATTER_PROJECT_NAME =
PROPERTY_NAME_PROJECT_NAME + "=%s";
+ public static final String PROPERTY_FORMATTER_GAV = PROPERTY_NAME_GAV +
"=org.camel.karavan.demo:%s:1";
+
public enum CamelRuntime {
CAMEL_MAIN("camel-main"),
QUARKUS("quarkus"),
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/KaravanEvents.java
b/karavan-app/src/main/java/org/apache/camel/karavan/KaravanEvents.java
index 58e9e366..b4f806bd 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/KaravanEvents.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/KaravanEvents.java
@@ -19,17 +19,19 @@ package org.apache.camel.karavan;
public class KaravanEvents {
public static final String CMD_PUSH_PROJECT = "CMD_PUSH_PROJECT";
- public static final String PROJECTS_STARTED = "PROJECTS_STARTED";
+ public static final String NOTIFICATION_PROJECTS_STARTED =
"NOTIFICATION_PROJECTS_STARTED";
public static final String COMMIT_HAPPENED = "COMMIT_HAPPENED";
+ public static final String NOTIFICATION_IMAGES_LOADED =
"NOTIFICATION_IMAGES_LOADED";
public static final String CMD_SHARE_CONFIGURATION =
"CMD_SHARE_CONFIGURATION";
- public static final String SHARE_HAPPENED = "SHARE_HAPPENED";
+ public static final String NOTIFICATION_CONFIG_SHARED =
"NOTIFICATION_CONFIG_SHARED";
- public static final String ERROR_HAPPENED = "ERROR_HAPPENED";
+ public static final String NOTIFICATION_ERROR = "NOTIFICATION_ERROR";
public static final String CMD_COLLECT_CAMEL_STATUS =
"CMD_COLLECT_CAMEL_STATUS";
public static final String CMD_COLLECT_CONTAINER_STATISTIC =
"CMD_COLLECT_CONTAINER_STATISTIC";
public static final String CMD_CLEAN_STATUSES = "CMD_CLEAN_STATUSES";
+ public static final String CMD_PULL_IMAGES = "CMD_PULL_IMAGES";
public static final String CMD_RELOAD_PROJECT_CODE =
"CMD_RELOAD_PROJECT_CODE";
public static final String CMD_DELETE_CONTAINER = "CMD_DELETE_CONTAINER";
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/KaravanStartupLoader.java
b/karavan-app/src/main/java/org/apache/camel/karavan/KaravanStartupLoader.java
index b23f8f07..f6df27ea 100644
---
a/karavan-app/src/main/java/org/apache/camel/karavan/KaravanStartupLoader.java
+++
b/karavan-app/src/main/java/org/apache/camel/karavan/KaravanStartupLoader.java
@@ -43,7 +43,7 @@ import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.apache.camel.karavan.KaravanConstants.DEV_ENVIRONMENT;
-import static org.apache.camel.karavan.KaravanEvents.PROJECTS_STARTED;
+import static
org.apache.camel.karavan.KaravanEvents.NOTIFICATION_PROJECTS_STARTED;
@Default
@Readiness
@@ -91,7 +91,7 @@ public class KaravanStartupLoader implements HealthCheck {
} else {
LOGGER.info("Projects loading...");
tryStart();
- eventBus.publish(PROJECTS_STARTED, null);
+ eventBus.publish(NOTIFICATION_PROJECTS_STARTED, null);
LOGGER.info("Projects loaded");
}
}
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 35780c1d..220fd9b3 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
@@ -44,7 +44,7 @@ public class ConfigurationResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getConfiguration() throws Exception {
- return Response.ok(configService.getConfiguration()).build();
+ return Response.ok(configService.getConfiguration(null)).build();
}
@GET
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/api/ContainerResource.java
b/karavan-app/src/main/java/org/apache/camel/karavan/api/ContainerResource.java
index a5e82000..d20c0e6e 100644
---
a/karavan-app/src/main/java/org/apache/camel/karavan/api/ContainerResource.java
+++
b/karavan-app/src/main/java/org/apache/camel/karavan/api/ContainerResource.java
@@ -104,7 +104,10 @@ public class ContainerResource {
}
return Response.ok().build();
} catch (Exception e) {
- return Response.serverError().entity(e.getMessage()).build();
+ var error = e.getCause() != null ? e.getCause().getMessage() :
e.getMessage();
+ var result = "Error while executing command " + command + " on " +
projectId + ": "+ error;
+ LOGGER.error(result);
+ return Response.serverError().entity(result).build();
}
}
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java
b/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java
index 69eb7bfe..4655b211 100644
---
a/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java
+++
b/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java
@@ -28,7 +28,6 @@ import org.apache.camel.karavan.service.ProjectService;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;
-import static org.apache.camel.karavan.KaravanConstants.DEV_ENVIRONMENT;
import static org.apache.camel.karavan.KaravanEvents.CMD_DELETE_CONTAINER;
import static org.apache.camel.karavan.KaravanEvents.CMD_RELOAD_PROJECT_CODE;
@@ -37,6 +36,9 @@ public class DevModeResource {
private static final Logger LOGGER =
Logger.getLogger(DevModeResource.class.getName());
+ @ConfigProperty(name = "karavan.environment")
+ String environment;
+
@Inject
KaravanCache karavanCache;
@@ -92,7 +94,7 @@ public class DevModeResource {
@Produces(MediaType.APPLICATION_JSON)
@Path("/container/{projectId}")
public Response getPodStatus(@PathParam("projectId") String projectId)
throws RuntimeException {
- PodContainerStatus cs =
karavanCache.getDevModePodContainerStatus(projectId, DEV_ENVIRONMENT);
+ PodContainerStatus cs =
karavanCache.getDevModePodContainerStatus(projectId, environment);
if (cs != null) {
return Response.ok(cs).build();
} else {
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/api/ImagesResource.java
b/karavan-app/src/main/java/org/apache/camel/karavan/api/ImagesResource.java
index d9a680a3..fdcfb7da 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/api/ImagesResource.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/ImagesResource.java
@@ -17,11 +17,13 @@
package org.apache.camel.karavan.api;
import io.vertx.core.json.JsonObject;
+import io.vertx.mutiny.core.eventbus.EventBus;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.apache.camel.karavan.docker.DockerService;
+import org.apache.camel.karavan.model.ContainerImage;
import org.apache.camel.karavan.model.RegistryConfig;
import org.apache.camel.karavan.service.ConfigService;
import org.apache.camel.karavan.service.ProjectService;
@@ -29,8 +31,11 @@ import org.apache.camel.karavan.service.RegistryService;
import org.jose4j.base64url.Base64;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
+import static org.apache.camel.karavan.KaravanEvents.CMD_PULL_IMAGES;
+
@Path("/ui/image")
public class ImagesResource {
@@ -43,25 +48,30 @@ public class ImagesResource {
@Inject
ProjectService projectService;
+ @Inject
+ EventBus eventBus;
+
@GET
@Produces(MediaType.APPLICATION_JSON)
- @Path("/{projectId}")
- public List<String> getImagesForProject(@PathParam("projectId") String
projectId) {
+ @Path("/project/{projectId}")
+ public List<ContainerImage> getImagesForProject(@PathParam("projectId")
String projectId) {
if (ConfigService.inKubernetes()) {
return List.of();
} else {
RegistryConfig registryConfig =
registryService.getRegistryConfig();
String pattern = registryConfig.getGroup() + "/" + projectId;
return dockerService.getImages()
- .stream().filter(s ->
s.contains(pattern)).sorted(Comparator.reverseOrder()).toList();
+ .stream().filter(s -> s.getTag().contains(pattern))
+
.sorted(Comparator.comparing(ContainerImage::getCreated).reversed().thenComparing(ContainerImage::getTag))
+ .toList();
}
}
@POST
+ @Path("/project/{projectId}")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
- @Path("/{projectId}")
- public Response build(JsonObject data, @PathParam("projectId") String
projectId) throws Exception {
+ public Response setProjectImage(JsonObject data, @PathParam("projectId")
String projectId) throws Exception {
try {
projectService.setProjectImage(projectId, data);
return Response.ok().entity(data.getString("imageName")).build();
@@ -72,7 +82,7 @@ public class ImagesResource {
@DELETE
@Produces(MediaType.APPLICATION_JSON)
- @Path("/{imageName}")
+ @Path("/project/{imageName}")
public Response deleteImage(@PathParam("imageName") String imageName) {
imageName= new String(Base64.decode(imageName));
if (ConfigService.inKubernetes()) {
@@ -82,4 +92,17 @@ public class ImagesResource {
return Response.ok().build();
}
}
+
+ @POST
+ @Path("/pull/")
+ @Produces(MediaType.APPLICATION_JSON)
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response share(HashMap<String, String> params) {
+ try {
+ eventBus.publish(CMD_PULL_IMAGES, JsonObject.mapFrom(params));
+ return Response.ok().build();
+ } catch (Exception e) {
+ return
Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
+ }
+ }
}
\ No newline at end of file
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerComposeConverter.java
b/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerComposeConverter.java
index b82114db..27323646 100644
---
a/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerComposeConverter.java
+++
b/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerComposeConverter.java
@@ -45,7 +45,7 @@ public class DockerComposeConverter {
DockerComposeService service = convertToDockerComposeService(name,
serviceJson);
composeServices.put(name, service);
});
- json.put("configuration", composeServices);
+ json.put("services", composeServices);
return json.mapTo(DockerCompose.class);
}
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventHandler.java
b/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventHandler.java
index 7c38bde5..cdda60df 100644
---
a/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventHandler.java
+++
b/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventHandler.java
@@ -21,6 +21,8 @@ import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.model.Container;
import com.github.dockerjava.api.model.Event;
import com.github.dockerjava.api.model.EventType;
+import io.vertx.core.json.JsonObject;
+import io.vertx.mutiny.core.eventbus.EventBus;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.apache.camel.karavan.model.PodContainerStatus;
@@ -32,6 +34,7 @@ import java.io.IOException;
import java.util.Objects;
import static org.apache.camel.karavan.KaravanConstants.*;
+import static org.apache.camel.karavan.KaravanEvents.CMD_PULL_IMAGES;
@ApplicationScoped
public class DockerEventHandler implements ResultCallback<Event> {
@@ -49,6 +52,9 @@ public class DockerEventHandler implements
ResultCallback<Event> {
LOGGER.info("DockerEventListener started");
}
+ @Inject
+ EventBus eventBus;
+
@Override
public void onNext(Event event) {
try {
@@ -74,6 +80,7 @@ public class DockerEventHandler implements
ResultCallback<Event> {
private void syncImage(String projectId, String tag) throws
InterruptedException {
String image = registryService.getRegistryWithGroupForSync() + "/" +
projectId + ":" + tag;
+ eventBus.publish(CMD_PULL_IMAGES, JsonObject.of("projectId",
projectId));
dockerService.pullImage(image, true);
}
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
b/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
index ded4efe5..43921da5 100644
---
a/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
+++
b/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java
@@ -19,6 +19,8 @@ package org.apache.camel.karavan.docker;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.CreateContainerCmd;
import com.github.dockerjava.api.command.CreateContainerResponse;
+import com.github.dockerjava.api.command.ListImagesCmd;
+import com.github.dockerjava.api.command.PullImageCmd;
import com.github.dockerjava.api.model.*;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientConfig;
@@ -32,6 +34,7 @@ import io.vertx.core.buffer.Buffer;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
+import org.apache.camel.karavan.model.ContainerImage;
import org.apache.camel.karavan.model.DockerComposeService;
import org.apache.camel.karavan.model.PodContainerStatus;
import org.apache.camel.karavan.service.CodeService;
@@ -302,13 +305,24 @@ public class DockerService {
.flatMap(Collection::stream)
.toList();
- if (pullAlways || !images.stream().anyMatch(i ->
tags.contains(image))) {
+ if (pullAlways || images.stream().noneMatch(i ->
tags.contains(image))) {
var callback = new DockerPullCallback(LOGGER::info);
getDockerClient().pullImageCmd(image).exec(callback);
callback.awaitCompletion();
}
}
+ public void pullImagesForProject(String projectId) throws
InterruptedException {
+ if (!Objects.equals(registry, "registry:5000") && username.isPresent()
&& password.isPresent()) {
+ var repository = registry + "/" + group + "/" + projectId;
+ try (PullImageCmd cmd =
getDockerClient().pullImageCmd(repository)) {
+ var callback = new DockerPullCallback(LOGGER::info);
+ cmd.exec(callback);
+ callback.awaitCompletion();
+ }
+ }
+ }
+
private DockerClientConfig getDockerClientConfig() {
LOGGER.info("Docker Client Configuring....");
LOGGER.info("Docker Client Registry " + registry);
@@ -350,10 +364,13 @@ public class DockerService {
.max().orElse(port);
}
- public List<String> getImages() {
- return
getDockerClient().listImagesCmd().withShowAll(true).exec().stream()
- .filter(image -> image != null && image.getRepoTags() != null
&& image.getRepoTags().length > 0)
- .map(image -> image.getRepoTags()[0]).toList();
+ public List<ContainerImage> getImages() {
+ try (ListImagesCmd cmd =
getDockerClient().listImagesCmd().withShowAll(true)) {
+ return cmd.exec().stream()
+ .filter(image -> image != null && image.getRepoTags() !=
null && image.getRepoTags().length > 0)
+ .map(image -> new ContainerImage(image.getId(),
image.getRepoTags()[0], image.getCreated(), image.getSize()))
+ .toList();
+ }
}
public void deleteImage(String imageName) {
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/listener/CommitListener.java
b/karavan-app/src/main/java/org/apache/camel/karavan/listener/CommitListener.java
index a0700899..7a689bf1 100644
---
a/karavan-app/src/main/java/org/apache/camel/karavan/listener/CommitListener.java
+++
b/karavan-app/src/main/java/org/apache/camel/karavan/listener/CommitListener.java
@@ -43,10 +43,10 @@ public class CommitListener {
@ConsumeEvent(value = CMD_PUSH_PROJECT, blocking = true, ordered = true)
public void onCommitAndPush(JsonObject event) throws Exception {
LOGGER.info("Commit event: " + event.encodePrettily());
- String projectId = event.getString("projectId");
- String message = event.getString("message");
- String userId = event.getString("userId");
- String eventId = event.getString("eventId");
+ String projectId = event.getString("projectId");
+ String message = event.getString("message");
+ String userId = event.getString("userId");
+ String eventId = event.getString("eventId");
try {
Project p = projectService.commitAndPushProject(projectId,
message);
if (userId != null) {
@@ -56,7 +56,7 @@ public class CommitListener {
var error = e.getCause() != null ? e.getCause() : e;
LOGGER.error("Failed to commit event", error);
if (userId != null) {
- eventBus.publish(ERROR_HAPPENED, JsonObject.of(
+ eventBus.publish(NOTIFICATION_ERROR, JsonObject.of(
"userId", userId,
"eventId", eventId,
"className", Project.class.getSimpleName(),
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/listener/ConfigListener.java
b/karavan-app/src/main/java/org/apache/camel/karavan/listener/ConfigListener.java
index 703b530b..e6efc3f7 100644
---
a/karavan-app/src/main/java/org/apache/camel/karavan/listener/ConfigListener.java
+++
b/karavan-app/src/main/java/org/apache/camel/karavan/listener/ConfigListener.java
@@ -39,7 +39,7 @@ public class ConfigListener {
@Inject
EventBus eventBus;
- @ConsumeEvent(value = PROJECTS_STARTED, blocking = true)
+ @ConsumeEvent(value = NOTIFICATION_PROJECTS_STARTED, blocking = true)
public void shareOnStartup(String data) throws Exception {
configService.shareOnStartup();
}
@@ -51,12 +51,12 @@ public class ConfigListener {
LOGGER.info("Config share event: for " + (filename != null ? filename
: "all"));
try {
configService.share(filename);
- eventBus.publish(SHARE_HAPPENED, JsonObject.of("userId", userId,
"className", "filename", "filename", filename));
+ eventBus.publish(NOTIFICATION_CONFIG_SHARED,
JsonObject.of("userId", userId, "className", "filename", "filename", filename));
} catch (Exception e) {
var error = e.getCause() != null ? e.getCause() : e;
LOGGER.error("Failed to share configuration", error);
if (userId != null) {
- eventBus.publish(ERROR_HAPPENED, JsonObject.of(
+ eventBus.publish(NOTIFICATION_ERROR, JsonObject.of(
"userId", userId,
"className", filename,
"error", "Failed to share configuration: " +
e.getMessage())
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/listener/ConfigListener.java
b/karavan-app/src/main/java/org/apache/camel/karavan/listener/DockerListener.java
similarity index 51%
copy from
karavan-app/src/main/java/org/apache/camel/karavan/listener/ConfigListener.java
copy to
karavan-app/src/main/java/org/apache/camel/karavan/listener/DockerListener.java
index 703b530b..aa340368 100644
---
a/karavan-app/src/main/java/org/apache/camel/karavan/listener/ConfigListener.java
+++
b/karavan-app/src/main/java/org/apache/camel/karavan/listener/DockerListener.java
@@ -14,54 +14,43 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.apache.camel.karavan.listener;
import io.quarkus.vertx.ConsumeEvent;
import io.vertx.core.json.JsonObject;
import io.vertx.mutiny.core.eventbus.EventBus;
import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.enterprise.inject.Default;
import jakarta.inject.Inject;
-import org.apache.camel.karavan.service.ConfigService;
+import org.apache.camel.karavan.docker.DockerService;
import org.jboss.logging.Logger;
import static org.apache.camel.karavan.KaravanEvents.*;
-@Default
@ApplicationScoped
-public class ConfigListener {
+public class DockerListener {
- private static final Logger LOGGER =
Logger.getLogger(ConfigListener.class.getName());
+ private static final Logger LOGGER =
Logger.getLogger(DockerListener.class.getName());
@Inject
- ConfigService configService;
+ DockerService dockerService;
@Inject
EventBus eventBus;
- @ConsumeEvent(value = PROJECTS_STARTED, blocking = true)
- public void shareOnStartup(String data) throws Exception {
- configService.shareOnStartup();
- }
-
- @ConsumeEvent(value = CMD_SHARE_CONFIGURATION, blocking = true, ordered =
true)
- public void shareConfig(JsonObject event) throws Exception {
- String filename = event.getString("filename");
+ @ConsumeEvent(value = CMD_PULL_IMAGES, blocking = true)
+ void loadImagesForProject(JsonObject event) {
+ LOGGER.info("Pull image event: " + event.encodePrettily());
+ String projectId = event.getString("projectId");
String userId = event.getString("userId");
- LOGGER.info("Config share event: for " + (filename != null ? filename
: "all"));
try {
- configService.share(filename);
- eventBus.publish(SHARE_HAPPENED, JsonObject.of("userId", userId,
"className", "filename", "filename", filename));
+ dockerService.pullImagesForProject(projectId);
+ eventBus.publish(NOTIFICATION_IMAGES_LOADED, event);
} catch (Exception e) {
- var error = e.getCause() != null ? e.getCause() : e;
- LOGGER.error("Failed to share configuration", error);
- if (userId != null) {
- eventBus.publish(ERROR_HAPPENED, JsonObject.of(
- "userId", userId,
- "className", filename,
- "error", "Failed to share configuration: " +
e.getMessage())
- );
- }
+ var error = "Failed to load images " + (e.getCause() != null ?
e.getCause().getMessage() : e.getMessage());
+ LOGGER.error(error);
+ eventBus.publish(NOTIFICATION_ERROR, JsonObject.of("userId",
userId, "className", "image", "error", error)
+ );
}
}
-}
+}
\ No newline at end of file
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/listener/NotificationListener.java
b/karavan-app/src/main/java/org/apache/camel/karavan/listener/NotificationListener.java
index a0160d83..0ea7ffe8 100644
---
a/karavan-app/src/main/java/org/apache/camel/karavan/listener/NotificationListener.java
+++
b/karavan-app/src/main/java/org/apache/camel/karavan/listener/NotificationListener.java
@@ -39,16 +39,16 @@ public class NotificationListener {
public static final String EVENT_ERROR = "error";
public static final String EVENT_COMMIT = "commit";
public static final String EVENT_CONFIG_SHARED = "configShared";
+ public static final String EVENT_IMAGES_LOADED = "imagesLoaded";
@Inject
EventBus eventBus;
- @ConsumeEvent(value = ERROR_HAPPENED, blocking = true, ordered = true)
+ @ConsumeEvent(value = NOTIFICATION_ERROR, blocking = true, ordered = true)
public void onErrorHappened(JsonObject event) throws Exception {
String eventId = event.getString("eventId");
String userId = event.getString("userId");
String className = event.getString("className");
- String error = event.getString("error");
if (userId != null) {
send(userId, eventId, EVENT_ERROR, className, event);
} else {
@@ -56,7 +56,7 @@ public class NotificationListener {
}
}
- @ConsumeEvent(value = SHARE_HAPPENED, blocking = true, ordered = true)
+ @ConsumeEvent(value = NOTIFICATION_CONFIG_SHARED, blocking = true, ordered
= true)
public void onShareHappened(JsonObject event) throws Exception {
String userId = event.getString("userId");
String className = event.getString("className");
@@ -67,6 +67,16 @@ public class NotificationListener {
}
}
+ @ConsumeEvent(value = NOTIFICATION_IMAGES_LOADED, blocking = true, ordered
= true)
+ public void onImageLoaded(JsonObject event) throws Exception {
+ String userId = event.getString("userId");
+ if (userId != null) {
+ send(userId, null, EVENT_IMAGES_LOADED, "image", event);
+ } else {
+ sendSystem(null, EVENT_IMAGES_LOADED, "image", event);
+ }
+ }
+
@ConsumeEvent(value = COMMIT_HAPPENED, blocking = true, ordered = true)
public void onCommitHappened(JsonObject event) throws Exception {
JsonObject pj = event.getJsonObject("project");
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/model/Configuration.java
b/karavan-app/src/main/java/org/apache/camel/karavan/model/Configuration.java
index 2c9fdac7..f554a219 100644
---
a/karavan-app/src/main/java/org/apache/camel/karavan/model/Configuration.java
+++
b/karavan-app/src/main/java/org/apache/camel/karavan/model/Configuration.java
@@ -17,6 +17,7 @@
package org.apache.camel.karavan.model;
import java.util.List;
+import java.util.Map;
public class Configuration {
private String title;
@@ -26,17 +27,20 @@ public class Configuration {
private List<String> environments;
private List<String> configFilenames;
private List<Object> status;
+ private Map<String, String> advanced;
public Configuration() {
}
- public Configuration(String title, String version, String infrastructure,
String environment, List<String> environments, List<String> configFilenames) {
+ public Configuration(String title, String version, String infrastructure,
String environment, List<String> environments, List<String> configFilenames,
+ Map<String, String> advanced) {
this.title = title;
this.version = version;
this.infrastructure = infrastructure;
this.environment = environment;
this.environments = environments;
this.configFilenames = configFilenames;
+ this.advanced = advanced;
}
public String getTitle() {
@@ -94,4 +98,12 @@ public class Configuration {
public void setConfigFilenames(List<String> configFilenames) {
this.configFilenames = configFilenames;
}
+
+ public Map<String, String> getAdvanced() {
+ return advanced;
+ }
+
+ public void setAdvanced(Map<String, String> advanced) {
+ this.advanced = advanced;
+ }
}
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/model/ContainerImage.java
b/karavan-app/src/main/java/org/apache/camel/karavan/model/ContainerImage.java
new file mode 100644
index 00000000..25966f40
--- /dev/null
+++
b/karavan-app/src/main/java/org/apache/camel/karavan/model/ContainerImage.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.karavan.model;
+
+public class ContainerImage {
+
+ private String id;
+ private String tag;
+ private Long created;
+ private Long size;
+
+ public ContainerImage() {
+ }
+
+ public ContainerImage(String id, String tag, Long created, Long size) {
+ this.id = id;
+ this.tag = tag;
+ this.created = created;
+ this.size = size;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getTag() {
+ return tag;
+ }
+
+ public void setTag(String tag) {
+ this.tag = tag;
+ }
+
+ public Long getCreated() {
+ return created;
+ }
+
+ public void setCreated(Long created) {
+ this.created = created;
+ }
+
+ public Long getSize() {
+ return size;
+ }
+
+ public void setSize(Long size) {
+ this.size = size;
+ }
+}
\ No newline at end of file
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
b/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
index e820091d..0e16df47 100644
---
a/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
+++
b/karavan-app/src/main/java/org/apache/camel/karavan/service/CodeService.java
@@ -44,8 +44,7 @@ import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;
-import static org.apache.camel.karavan.KaravanConstants.DEVMODE_IMAGE;
-import static org.apache.camel.karavan.KaravanConstants.DEV_ENVIRONMENT;
+import static org.apache.camel.karavan.KaravanConstants.*;
@ApplicationScoped
public class CodeService {
@@ -72,6 +71,9 @@ public class CodeService {
@ConfigProperty(name = "karavan.environment")
String environment;
+ @ConfigProperty(name = "karavan.gav")
+ Optional<String> gav;
+
@Inject
KaravanCache karavanCache;
@@ -280,9 +282,9 @@ public class CodeService {
.replace(prefix, "");
}
- public static String getValueForProperty(String line, String property) {
- String prefix = property + "=";
- return line.replace(prefix, "");
+ public static String getPropertyName(String line) {
+ var parts = line.indexOf("=");
+ return line.substring(0, parts).trim();
}
public String getProjectName(String file) {
@@ -290,6 +292,21 @@ public class CodeService {
return name != null && !name.isBlank() ? name : getProperty(file,
PROPERTY_PROJECT_NAME_OLD);
}
+ public static String replaceProperty(String file, String property, String
value) {
+ return file.lines().map(line -> {
+ if (line.startsWith(property)) {
+ return property + "=" + value;
+ } else {
+ return line;
+ }
+ }).collect(Collectors.joining(System.lineSeparator()));
+ }
+
+ public static String removePropertiesStartWith(String file, String
startWith) {
+ return file.lines().filter(line -> !line.startsWith(startWith))
+ .collect(Collectors.joining(System.lineSeparator()));
+ }
+
public ProjectFile createInitialProjectCompose(Project project, int
nextAvailablePort) {
String template = getTemplateText(PROJECT_COMPOSE_FILENAME);
String code = substituteVariables(template, Map.of(
@@ -421,4 +438,8 @@ public class CodeService {
public String getFileString(String fullName) {
return vertx.fileSystem().readFileBlocking(fullName).toString();
}
+
+ public String getGavFormatter() {
+ return PROPERTY_NAME_GAV + "=" + gav.orElse("org.camel.karavan.demo")
+ ":%s:1";
+ }
}
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/service/ConfigService.java
b/karavan-app/src/main/java/org/apache/camel/karavan/service/ConfigService.java
index 58cd1bc3..a4cb1e4d 100644
---
a/karavan-app/src/main/java/org/apache/camel/karavan/service/ConfigService.java
+++
b/karavan-app/src/main/java/org/apache/camel/karavan/service/ConfigService.java
@@ -17,6 +17,7 @@
package org.apache.camel.karavan.service;
import io.quarkus.runtime.StartupEvent;
+import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
@@ -72,19 +73,23 @@ public class ConfigService {
private static Boolean inKubernetes;
private static Boolean inDocker;
- void onStart(@Observes StartupEvent ev) {
- var configFilenames = codeService.getConfigurationList();
- configuration = new Configuration(
- title,
- version,
- inKubernetes() ? "kubernetes" : "docker",
- environment,
- getEnvs(),
- configFilenames
- );
+ void onStart(@Observes @Priority(10) StartupEvent ev) {
+ getConfiguration(null);
}
- public Configuration getConfiguration() {
+ public Configuration getConfiguration(Map<String, String> advanced) {
+ if (configuration == null) {
+ var configFilenames = codeService.getConfigurationList();
+ configuration = new Configuration(
+ title,
+ version,
+ inKubernetes() ? "kubernetes" : "docker",
+ environment,
+ getEnvs(),
+ configFilenames,
+ advanced
+ );
+ }
return configuration;
}
@@ -162,8 +167,5 @@ public class ConfigService {
return ConfigProvider.getConfig().getOptionalValue("karavan.appName",
String.class).orElse("karavan");
}
- public static String getParamWithAppPrefix(String param) {
- return getAppName() + "-" + param;
- }
}
\ No newline at end of file
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 8ef63cc7..2b47c1e5 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
@@ -326,8 +326,6 @@ public class GitService {
LOGGER.infof("Temp folder %s is created for deletion of project %s",
folder, projectId);
try {
Git git = getGit(true, folder);
-// git = clone(folder, gitConfig.getUri(), gitConfig.getBranch());
-// checkout(git, false, null, null, gitConfig.getBranch());
addDeletedFolderToIndex(git, folder, projectId, files);
commitAddedAndPush(git, gitConfig.getBranch(), commitMessage);
LOGGER.info("Delete Temp folder " + folder);
diff --git
a/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
b/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
index 8ff10c83..18a6b2f2 100644
---
a/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
+++
b/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
@@ -36,7 +36,7 @@ import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;
-import static org.apache.camel.karavan.KaravanConstants.DEV_ENVIRONMENT;
+import static org.apache.camel.karavan.KaravanConstants.*;
import static org.apache.camel.karavan.KaravanEvents.CMD_PUSH_PROJECT;
import static org.apache.camel.karavan.KaravanEvents.POD_CONTAINER_UPDATED;
import static org.apache.camel.karavan.service.CodeService.*;
@@ -183,15 +183,15 @@ public class ProjectService {
private void modifyPropertyFileOnProjectCopy(ProjectFile propertyFile,
Project sourceProject, Project project) {
String fileContent = propertyFile.getCode();
- String sourceProjectIdProperty =
String.format(Property.PROJECT_ID.getKeyValueFormatter(),
sourceProject.getProjectId());
- String sourceProjectNameProperty =
String.format(Property.PROJECT_NAME.getKeyValueFormatter(),
sourceProject.getName());
- String sourceGavProperty =
String.format(Property.GAV.getKeyValueFormatter(),
sourceProject.getProjectId());
+ String sourceProjectIdProperty =
String.format(PROPERTY_FORMATTER_PROJECT_ID, sourceProject.getProjectId());
+ String sourceProjectNameProperty =
String.format(PROPERTY_FORMATTER_PROJECT_NAME, sourceProject.getName());
+ String sourceGavProperty =
String.format(codeService.getGavFormatter(), sourceProject.getProjectId());
String[] searchValues = {sourceProjectIdProperty,
sourceProjectNameProperty, sourceGavProperty};
- String updatedProjectIdProperty =
String.format(Property.PROJECT_ID.getKeyValueFormatter(),
project.getProjectId());
- String updatedProjectNameProperty =
String.format(Property.PROJECT_NAME.getKeyValueFormatter(), project.getName());
- String updatedGavProperty =
String.format(Property.GAV.getKeyValueFormatter(), project.getProjectId());
+ String updatedProjectIdProperty =
String.format(PROPERTY_FORMATTER_PROJECT_ID, project.getProjectId());
+ String updatedProjectNameProperty =
String.format(PROPERTY_FORMATTER_PROJECT_NAME, project.getName());
+ String updatedGavProperty =
String.format(codeService.getGavFormatter(), project.getProjectId());
String[] replacementValues = {updatedProjectIdProperty,
updatedProjectNameProperty, updatedGavProperty};
@@ -305,20 +305,4 @@ public class ProjectService {
return INTERNAL_PORT;
}
}
-
- public enum Property {
- PROJECT_ID("camel.karavan.projectId=%s"),
- PROJECT_NAME("camel.karavan.projectName=%s"),
- GAV("camel.jbang.gav=org.camel.karavan.demo:%s:1");
-
- private final String keyValueFormatter;
-
- Property(String keyValueFormatter) {
- this.keyValueFormatter = keyValueFormatter;
- }
-
- public String getKeyValueFormatter() {
- return keyValueFormatter;
- }
- }
}
diff --git a/karavan-app/src/main/webui/src/api/KaravanApi.tsx
b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
index 99763ee5..cc2b1d6b 100644
--- a/karavan-app/src/main/webui/src/api/KaravanApi.tsx
+++ b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
@@ -484,14 +484,7 @@ export class KaravanApi {
});
}
- static async setProjectImage(projectId: string, imageName: string, commit:
boolean, message: string, after: (res: AxiosResponse<any>) => void) {
- instance.post('/ui/image/' + projectId, {imageName: imageName, commit:
commit, message: message})
- .then(res => {
- after(res);
- }).catch(err => {
- after(err);
- });
- }
+
static async stopBuild(environment: string, buildName: string, after:
(res: AxiosResponse<any>) => void) {
instance.delete('/ui/project/build/' + environment + "/" + buildName)
@@ -621,7 +614,7 @@ export class KaravanApi {
}
static async getImages(projectId: string, after: (string: []) => void) {
- instance.get('/ui/image/' + projectId)
+ instance.get('/ui/image/project/' + projectId)
.then(res => {
if (res.status === 200) {
after(res.data);
@@ -631,8 +624,17 @@ export class KaravanApi {
});
}
+ static async setProjectImage(projectId: string, imageName: string, commit:
boolean, message: string, after: (res: AxiosResponse<any>) => void) {
+ instance.post('/ui/image/project/' + projectId, {imageName: imageName,
commit: commit, message: message})
+ .then(res => {
+ after(res);
+ }).catch(err => {
+ after(err);
+ });
+ }
+
static async deleteImage(imageName: string, after: () => void) {
- instance.delete('/ui/image/' +
Buffer.from(imageName).toString('base64'))
+ instance.delete('/ui/image/project/' +
Buffer.from(imageName).toString('base64'))
.then(res => {
if (res.status === 200) {
after();
@@ -642,6 +644,19 @@ export class KaravanApi {
});
}
+ static async pullProjectImages(projectId: string, after: (res:
AxiosResponse<any>) => void) {
+ const params = {
+ 'projectId': projectId,
+ 'userId': KaravanApi.getUserId()
+ };
+ instance.post('/ui/image/pull/', params)
+ .then(res => {
+ after(res);
+ }).catch(err => {
+ after(err);
+ });
+ }
+
static async getSecrets(after: (any: []) => void) {
instance.get('/ui/infrastructure/secrets')
.then(res => {
diff --git a/karavan-app/src/main/webui/src/api/NotificationService.ts
b/karavan-app/src/main/webui/src/api/NotificationService.ts
index cd610df1..75fe5553 100644
--- a/karavan-app/src/main/webui/src/api/NotificationService.ts
+++ b/karavan-app/src/main/webui/src/api/NotificationService.ts
@@ -53,6 +53,9 @@ const sub = NotificationEventBus.onEvent()?.subscribe((event:
KaravanEvent) => {
ProjectService.refreshProjectData(projectId);
});
}
+ } else if (event.event === 'imagesLoaded') {
+ const projectId = event.data?.projectId;
+ EventBus.sendAlert('Success', 'Image loaded for ' + projectId);
} else if (event.event === 'error') {
const error = event.data?.error;
EventBus.sendAlert('Error', error, "danger");
diff --git a/karavan-app/src/main/webui/src/api/ProjectModels.ts
b/karavan-app/src/main/webui/src/api/ProjectModels.ts
index ccea5426..7431521e 100644
--- a/karavan-app/src/main/webui/src/api/ProjectModels.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectModels.ts
@@ -23,6 +23,7 @@ export class AppConfig {
environments: string[] = [];
status: any[] = [];
configFilenames: any[] = [];
+ advanced: any = {}
}
export enum ProjectType {
@@ -151,6 +152,13 @@ export class ProjectFileType {
}
}
+export class ContainerImage {
+ id: string = '';
+ tag: string = '';
+ size: number = 0;
+ created: number = 0;
+}
+
export const ProjectFileTypes: ProjectFileType[] = [
new ProjectFileType("INTEGRATION", "Integration", "camel.yaml"),
new ProjectFileType("KAMELET", "Kamelet", "kamelet.yaml"),
diff --git a/karavan-app/src/main/webui/src/api/ProjectService.ts
b/karavan-app/src/main/webui/src/api/ProjectService.ts
index bce8a5fa..f936e578 100644
--- a/karavan-app/src/main/webui/src/api/ProjectService.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectService.ts
@@ -16,7 +16,7 @@
*/
import {KaravanApi} from './KaravanApi';
-import {DeploymentStatus, ContainerStatus, Project, ProjectFile,
ServiceStatus, CamelStatus} from './ProjectModels';
+import {DeploymentStatus, ContainerStatus, Project, ProjectFile,
ServiceStatus, CamelStatus, ContainerImage} from './ProjectModels';
import {TemplateApi} from 'karavan-core/lib/api/TemplateApi';
import {InfrastructureAPI} from '../designer/utils/InfrastructureAPI';
import {unstable_batchedUpdates} from 'react-dom'
@@ -234,7 +234,7 @@ export class ProjectService {
}
public static refreshImages(projectId: string) {
- KaravanApi.getImages(projectId, (res: any) => {
+ KaravanApi.getImages(projectId, (res: ContainerImage[]) => {
useProjectStore.setState({images: res});
});
}
diff --git a/karavan-app/src/main/webui/src/api/ProjectStore.ts
b/karavan-app/src/main/webui/src/api/ProjectStore.ts
index 07875a26..87b6bd68 100644
--- a/karavan-app/src/main/webui/src/api/ProjectStore.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectStore.ts
@@ -22,7 +22,7 @@ import {
Project,
ProjectFile,
ServiceStatus,
- CamelStatus,
+ CamelStatus, ContainerImage,
} from "./ProjectModels";
import {ProjectEventBus} from "./ProjectEventBus";
import {unstable_batchedUpdates} from "react-dom";
@@ -121,8 +121,8 @@ interface ProjectState {
isPulling: boolean,
isPushing: boolean,
isRunning: boolean,
- images: string [],
- setImages: (images: string []) => void;
+ images: ContainerImage [],
+ setImages: (images: ContainerImage []) => void;
project: Project;
setProject: (project: Project, operation: "create" | "select" | "delete"|
"none" | "copy") => void;
operation: "create" | "select" | "delete" | "none" | "copy";
@@ -167,7 +167,7 @@ export const useProjectStore =
createWithEqualityFn<ProjectState>((set) => ({
return {tabIndex: tabIndex};
});
},
- setImages: (images: string[]) => {
+ setImages: (images: ContainerImage[]) => {
set((state: ProjectState) => {
state.images.length = 0;
state.images.push(...images);
diff --git a/karavan-app/src/main/webui/src/log/ProjectLogPanel.tsx
b/karavan-app/src/main/webui/src/log/ProjectLogPanel.tsx
index afaf4933..42ecea1c 100644
--- a/karavan-app/src/main/webui/src/log/ProjectLogPanel.tsx
+++ b/karavan-app/src/main/webui/src/log/ProjectLogPanel.tsx
@@ -17,13 +17,12 @@
import React, {useEffect, useState} from 'react';
import {Button, Checkbox, Label, PageSection, Tooltip, TooltipPosition} from
'@patternfly/react-core';
-import '../designer/karavan.css';
+import './ProjectLog.css';
import CloseIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/expand-icon';
import CollapseIcon from
'@patternfly/react-icons/dist/esm/icons/compress-icon';
import CleanIcon from '@patternfly/react-icons/dist/esm/icons/trash-alt-icon';
-import {useLogStore, useStatusesStore} from "../api/ProjectStore";
-import {KaravanApi} from "../api/KaravanApi";
+import {useLogStore} from "../api/ProjectStore";
import {shallow} from "zustand/shallow";
import {ProjectEventBus} from "../api/ProjectEventBus";
import {ProjectLog} from "./ProjectLog";
@@ -35,23 +34,23 @@ export function ProjectLogPanel () {
const [showLog, type, setShowLog, podName] = useLogStore(
(state) => [state.showLog, state.type, state.setShowLog,
state.podName], shallow)
- const [containers] = useStatusesStore((state) => [state.containers],
shallow);
const [height, setHeight] = useState(INITIAL_LOG_HEIGHT);
const [isTextWrapped, setIsTextWrapped] = useState(true);
const [autoScroll, setAutoScroll] = useState(true);
- const [fetch, setFetch] = useState<Promise<void> | undefined>(undefined);
+ const [controller, setController] = React.useState(new AbortController());
const [currentPodName, setCurrentPodName] = useState<string |
undefined>(undefined);
useEffect(() => {
- const controller = new AbortController();
+ controller.abort()
+ const c = new AbortController();
+ setController(c);
if (showLog && type !== 'none' && podName !== undefined) {
- const f = LogWatchApi.fetchData(type, podName,
controller).then(value => {
- console.log("Fetch Started for: " + podName)
+ const f = LogWatchApi.fetchData(type, podName, c).then(value => {
+ console.log("Fetch Started for: " + podName);
});
- setFetch(f);
}
return () => {
- controller.abort();
+ c.abort();
};
}, [showLog, type, podName]);
diff --git a/karavan-app/src/main/webui/src/main/Main.tsx
b/karavan-app/src/main/webui/src/main/Main.tsx
index 4e0d75de..d29961b2 100644
--- a/karavan-app/src/main/webui/src/main/Main.tsx
+++ b/karavan-app/src/main/webui/src/main/Main.tsx
@@ -89,15 +89,12 @@ export function Main() {
<Page className="karavan">
{!showMain() && <MainLoader/>}
{showMain() &&
- <Flex direction={{default: "row"}} style={{width: "100%",
height: "100%"}}
- alignItems={{default: "alignItemsStretch"}}
spaceItems={{default: 'spaceItemsNone'}}>
- <FlexItem>
- {<PageNavigation/>}
- </FlexItem>
- <FlexItem flex={{default: "flex_2"}} style={{height:
"100%"}}>
+ <div style={{display: 'flex', flexDirection: 'row',
alignItems: 'stretch', gap: '0', width: "100%", height: "100%"}}>
+ {<PageNavigation/>}
+ <div style={{height: "100%", flexGrow: '2'}}>
{<MainRoutes/>}
- </FlexItem>
- </Flex>
+ </div>
+ </div>
}
<Notification/>
</Page>
diff --git a/karavan-app/src/main/webui/src/project/DevModeToolbar.tsx
b/karavan-app/src/main/webui/src/project/DevModeToolbar.tsx
index 8df9a64e..84f3eff4 100644
--- a/karavan-app/src/main/webui/src/project/DevModeToolbar.tsx
+++ b/karavan-app/src/main/webui/src/project/DevModeToolbar.tsx
@@ -83,7 +83,6 @@ export function DevModeToolbar(props: Props) {
function refreshContainer(){
ProjectService.refreshContainerStatus(project.projectId,
config.environment);
ProjectService.refreshCamelStatus(project.projectId,
config.environment);
- ProjectService.refreshImages(project.projectId);
if (refreshTrace) {
ProjectService.refreshCamelTraces(project.projectId,
config.environment);
}
diff --git a/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx
b/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx
index 1ead482f..a41a6bd9 100644
--- a/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx
+++ b/karavan-app/src/main/webui/src/project/beans/BeanWizard.tsx
@@ -115,7 +115,7 @@ export function BeanWizard() {
useEffect(() => {
getBeans.filter(b => b.name === templateBeanName).forEach(b => {
- setBean(new BeanFactoryDefinition({...b}))
+ setBean(new BeanFactoryDefinition({...b}))
});
}, [templateBeanName]);
diff --git a/karavan-app/src/main/webui/src/project/builder/ImagesPanel.tsx
b/karavan-app/src/main/webui/src/project/builder/ImagesPanel.tsx
index 886b2dd3..b4dea27d 100644
--- a/karavan-app/src/main/webui/src/project/builder/ImagesPanel.tsx
+++ b/karavan-app/src/main/webui/src/project/builder/ImagesPanel.tsx
@@ -22,8 +22,6 @@ import {
Flex,
FlexItem,
Modal,
- Panel,
- PanelHeader,
TextContent,
Text,
TextVariants,
@@ -36,7 +34,7 @@ import {
Switch,
TextInput,
Card,
- CardBody, CardHeader
+ CardBody, CardHeader, HelperTextItem, HelperText
} from '@patternfly/react-core';
import '../../designer/karavan.css';
import {useFilesStore, useProjectStore} from "../../api/ProjectStore";
@@ -50,6 +48,8 @@ import {ProjectService} from "../../api/ProjectService";
import {ServicesYaml} from "../../api/ServiceModels";
import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
import {EventBus} from "../../designer/utils/EventBus";
+import {getMegabytes} from "../../util/StringUtils";
+import PullIcon from
"@patternfly/react-icons/dist/esm/icons/cloud-download-alt-icon";
export function ImagesPanel() {
@@ -57,6 +57,7 @@ export function ImagesPanel() {
const [files] = useFilesStore((s) => [s.files], shallow);
const [showSetConfirmation, setShowSetConfirmation] =
useState<boolean>(false);
const [showDeleteConfirmation, setShowDeleteConfirmation] =
useState<boolean>(false);
+ const [showPullConfirmation, setShowPullConfirmation] =
useState<boolean>(false);
const [imageName, setImageName] = useState<string>();
const [commitChanges, setCommitChanges] = useState<boolean>(false);
const [commitMessage, setCommitMessage] = useState('');
@@ -130,12 +131,12 @@ export function ImagesPanel() {
function getDeleteConfirmation() {
return (<Modal
- className="modal-delete"
title="Confirmation"
+ variant='medium'
isOpen={showDeleteConfirmation}
onClose={() => setShowDeleteConfirmation(false)}
actions={[
- <Button key="confirm" variant="primary" onClick={e => {
+ <Button key="confirm" variant="danger" onClick={e => {
if (imageName) {
KaravanApi.deleteImage(imageName, () => {
EventBus.sendAlert("Image deleted", "Image " +
imageName + " deleted", 'info');
@@ -148,8 +149,45 @@ export function ImagesPanel() {
onClick={e =>
setShowDeleteConfirmation(false)}>Cancel</Button>
]}
onEscapePress={e => setShowDeleteConfirmation(false)}>
- <div>{"Delete image:"}</div>
- <div>{imageName}</div>
+ <TextContent>
+ <Text component='p'>
+ {"Delete image: "}<b>{imageName}</b>
+ </Text>
+ <HelperText>
+ <HelperTextItem variant="warning" hasIcon>
+ Container Image will be deleted from Docker Engine
only!
+ </HelperTextItem>
+ </HelperText>
+ </TextContent>
+ </Modal>)
+ }
+
+ function getPullConfirmation() {
+ return (<Modal
+ title="Confirmation"
+ variant='medium'
+ isOpen={showPullConfirmation}
+ onClose={() => setShowPullConfirmation(false)}
+ actions={[
+ <Button key="confirm" variant="primary" onClick={e => {
+ KaravanApi.pullProjectImages(project.projectId, () => {
+ setShowPullConfirmation(false);
+ });
+ }}>Pull
+ </Button>,
+ <Button key="cancel" variant="link" onClick={_ =>
setShowPullConfirmation(false)}>Cancel</Button>
+ ]}
+ onEscapePress={e => setShowPullConfirmation(false)}>
+ <TextContent>
+ <Text component='p'>
+ {"Pull all images from Registry for project:
"}<b>{project.projectId}</b>
+ </Text>
+ <HelperText>
+ <HelperTextItem variant="warning" hasIcon>
+ Pull is a background process that might take some time!
+ </HelperTextItem>
+ </HelperText>
+ </TextContent>
</Modal>)
}
@@ -158,41 +196,53 @@ export function ImagesPanel() {
<PageSection className="project-tab-panel project-images-panel"
padding={{default: "padding"}}>
<Card>
<CardHeader>
- <Flex direction={{default: "row"}}
justifyContent={{default: "justifyContentFlexStart"}}>
+ <Flex direction={{default: "row"}}
justifyContent={{default: "justifyContentSpaceBetween"}}>
<FlexItem>
<TextContent>
<Text component={TextVariants.h6}>Images</Text>
</TextContent>
</FlexItem>
+ <FlexItem>
+ <Tooltip content="Pull all images from registry"
position={"bottom-end"}>
+ <Button variant={"secondary"}
className="dev-action-button" icon={<PullIcon/>}
+ onClick={() =>
setShowPullConfirmation(true)}>
+ Pull
+ </Button>
+ </Tooltip>
+ </FlexItem>
</Flex>
</CardHeader>
<CardBody className='table-card-body'>
<Table aria-label="Images" variant={"compact"}
className={"table"}>
<Thead>
<Tr>
- <Th key='status' width={10}></Th>
+ <Th key='status'
modifier={"fitContent"}>Status</Th>
<Th key='image' width={20}>Image</Th>
<Th key='tag' width={10}>Tag</Th>
- <Th key='actions' width={10}></Th>
+ <Th key='size' width={10}>Size</Th>
+ <Th key='created' width={10}>Created</Th>
+ <Th key='actions' width={20}></Th>
</Tr>
</Thead>
<Tbody>
{images.map(image => {
- const index = image.lastIndexOf(":");
- const name = image.substring(0, index);
- const tag = image.substring(index + 1);
- return <Tr key={image}>
+ const fullName = image.tag;
+ const index = fullName.lastIndexOf(":");
+ const name = fullName.substring(0, index);
+ const tag = fullName.substring(index + 1);
+ const created = new Date(image.created * 1000);
+ const size =
getMegabytes(image.size)?.toFixed(0);
+ return <Tr key={image.id}>
<Td modifier={"fitContent"}>
- {image === projectImage ? <SetIcon/> :
<div/>}
- </Td>
- <Td>
- {name}
- </Td>
- <Td>
- {tag}
+ {fullName === projectImage ?
<SetIcon/> : <div/>}
</Td>
+ <Td>{name}</Td>
+ <Td>{tag}</Td>
+ <Td>{size} MB</Td>
+ <Td>{created.toISOString()}</Td>
<Td modifier={"fitContent"} isActionCell>
<Flex direction={{default: "row"}}
+ flexWrap={{default:'nowrap'}}
justifyContent={{default:
"justifyContentFlexEnd"}}
spaceItems={{default:
'spaceItemsNone'}}>
<FlexItem>
@@ -200,9 +250,9 @@ export function ImagesPanel() {
<Button variant={"plain"}
className='dev-action-button'
icon={<DeleteIcon/>}
- isDisabled={image
=== projectImage}
+
isDisabled={fullName === projectImage}
onClick={e => {
-
setImageName(image);
+
setImageName(fullName);
setShowDeleteConfirmation(true);
}}>
</Button>
@@ -210,12 +260,11 @@ export function ImagesPanel() {
</FlexItem>
<FlexItem>
<Tooltip content="Set project
image" position={"bottom"}>
- <Button style={{padding:
'0'}}
- variant={"plain"}
+ <Button variant={"plain"}
className='dev-action-button'
- isDisabled={image
=== projectImage}
+
isDisabled={fullName === projectImage}
onClick={e => {
-
setImageName(image);
+
setImageName(fullName);
setCommitMessage(commitMessage === '' ? new Date().toLocaleString() :
commitMessage);
setShowSetConfirmation(true);
}}>
@@ -244,6 +293,7 @@ export function ImagesPanel() {
</Table>
{showSetConfirmation && getSetConfirmation()}
{showDeleteConfirmation && getDeleteConfirmation()}
+ {showPullConfirmation && getPullConfirmation()}
</CardBody>
</Card>
</PageSection>
diff --git
a/karavan-app/src/main/webui/src/project/container/ProjectContainerTab.tsx
b/karavan-app/src/main/webui/src/project/container/ProjectContainerTab.tsx
index a4f525da..702c34f9 100644
--- a/karavan-app/src/main/webui/src/project/container/ProjectContainerTab.tsx
+++ b/karavan-app/src/main/webui/src/project/container/ProjectContainerTab.tsx
@@ -30,37 +30,36 @@ export function ProjectContainerTab() {
const {config} = useAppConfigStore();
+ const env = config.environment;
return (
<PageSection className="project-tab-panel project-build-panel"
padding={{default: "padding"}}>
<div>
- {config.environments.map(env =>
- <Card key={env} className="project-status">
- <CardBody>
- <DescriptionList isHorizontal
horizontalTermWidthModifier={{default: '20ch'}}>
+ <Card key={env} className="project-status">
+ <CardBody>
+ <DescriptionList isHorizontal
horizontalTermWidthModifier={{default: '20ch'}}>
+ <DescriptionListGroup>
+
<DescriptionListTerm>Environment</DescriptionListTerm>
+ <DescriptionListDescription>
+ <Badge className="badge">{env}</Badge>
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ {config.infrastructure === 'kubernetes' &&
<DescriptionListGroup>
-
<DescriptionListTerm>Environment</DescriptionListTerm>
+
<DescriptionListTerm>Deployment</DescriptionListTerm>
<DescriptionListDescription>
- <Badge className="badge">{env}</Badge>
+ <DeploymentPanel env={env}/>
</DescriptionListDescription>
</DescriptionListGroup>
- {config.infrastructure === 'kubernetes' &&
- <DescriptionListGroup>
-
<DescriptionListTerm>Deployment</DescriptionListTerm>
- <DescriptionListDescription>
- <DeploymentPanel env={env}/>
- </DescriptionListDescription>
- </DescriptionListGroup>
- }
- <DescriptionListGroup>
-
<DescriptionListTerm>Containers</DescriptionListTerm>
- <DescriptionListDescription>
- <ContainerPanel env={env}/>
- </DescriptionListDescription>
- </DescriptionListGroup>
- </DescriptionList>
- </CardBody>
- </Card>
- )}
+ }
+ <DescriptionListGroup>
+
<DescriptionListTerm>Containers</DescriptionListTerm>
+ <DescriptionListDescription>
+ <ContainerPanel env={env}/>
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ </DescriptionList>
+ </CardBody>
+ </Card>
</div>
</PageSection>
)
diff --git a/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
index 81e8da38..da6ab20b 100644
--- a/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
+++ b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-import React, {useEffect, useState} from 'react';
+import React, {useState} from 'react';
import {
Badge,
Bullseye,
@@ -101,14 +101,16 @@ export function FilesTab () {
const currentEnv = config.environment;
const envs = config.environments;
- const parts = filename.split('.');
- const prefix = parts[0] && envs.includes(parts[0]) ? parts[0] :
undefined;
- if (prefix && envs.includes(prefix) && prefix !== currentEnv) {
- return true;
- }
- if (!prefix) {
- const prefixedFilename = `${currentEnv}.${filename}`;
- return allFiles.map(f => f.name).includes(prefixedFilename);
+ if (filename.endsWith(".jkube.yaml") ||
filename.endsWith(".docker-compose.yaml")) {
+ const parts = filename.split('.');
+ const prefix = parts[0] && envs.includes(parts[0]) ? parts[0] :
undefined;
+ if (prefix && envs.includes(prefix) && prefix !== currentEnv) {
+ return true;
+ }
+ if (!prefix) {
+ const prefixedFilename = `${currentEnv}.${filename}`;
+ return allFiles.map(f => f.name).includes(prefixedFilename);
+ }
}
return false;
}
diff --git a/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx
b/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx
index 4ce38392..a00aa8b3 100644
--- a/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx
+++ b/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx
@@ -24,7 +24,7 @@ import {
ModalVariant,
} from '@patternfly/react-core';
import '../designer/karavan.css';
-import {useProjectStore} from "../api/ProjectStore";
+import {useProjectsStore, useProjectStore} from "../api/ProjectStore";
import {ProjectService} from "../api/ProjectService";
import {Project} from "../api/ProjectModels";
import {isValidProjectId} from "../util/StringUtils";
@@ -33,10 +33,12 @@ import {SubmitHandler, useForm} from "react-hook-form";
import {useFormUtil} from "../util/useFormUtil";
import {KaravanApi} from "../api/KaravanApi";
import {AxiosResponse} from "axios";
+import {shallow} from "zustand/shallow";
export function CreateProjectModal() {
- const {project, operation, setOperation} = useProjectStore();
+ const [project, operation, setOperation] = useProjectStore((s) =>
[s.project, s.operation, s.setOperation], shallow);
+ const [projects] = useProjectsStore((s) => [s.projects], shallow);
const [isReset, setReset] = React.useState(false);
const [backendError, setBackendError] = React.useState<string>();
const formContext = useForm<Project>({mode: "all"});
@@ -115,6 +117,7 @@ export function CreateProjectModal() {
regex: v => isValidProjectId(v) || 'Only lowercase
characters, numbers and dashes allowed',
length: v => v.length > 5 || 'Project ID should be longer
that 5 characters',
name: v => !['templates', 'kamelets',
'karavan'].includes(v) || "'templates', 'kamelets', 'karavan' can't be used as
project",
+ uniques: v => !projects.map(p=> p.name).includes(v) ||
"Project already exists!",
})}
{getTextField('name', 'Name', {
length: v => v.length > 5 || 'Project name should be
longer that 5 characters',
diff --git a/karavan-app/src/main/webui/src/projects/DeleteProjectModal.tsx
b/karavan-app/src/main/webui/src/projects/DeleteProjectModal.tsx
index 3e10d7d0..5403cead 100644
--- a/karavan-app/src/main/webui/src/projects/DeleteProjectModal.tsx
+++ b/karavan-app/src/main/webui/src/projects/DeleteProjectModal.tsx
@@ -24,10 +24,11 @@ import {
import '../designer/karavan.css';
import {useProjectStore} from "../api/ProjectStore";
import {ProjectService} from "../api/ProjectService";
+import {shallow} from "zustand/shallow";
export function DeleteProjectModal () {
- const {project, operation} = useProjectStore();
+ const [project, operation] = useProjectStore((s) => [s.project,
s.operation], shallow);
const [deleteContainers, setDeleteContainers] = useState(false);
function closeModal () {
diff --git a/karavan-app/src/main/webui/src/util/StringUtils.ts
b/karavan-app/src/main/webui/src/util/StringUtils.ts
index ec04e358..e6ad8644 100644
--- a/karavan-app/src/main/webui/src/util/StringUtils.ts
+++ b/karavan-app/src/main/webui/src/util/StringUtils.ts
@@ -57,3 +57,7 @@ export function isValidPassword(password: string): boolean {
hasSpecialCharacter(password) &&
hasMinimumLength(password);
}
+
+export function getMegabytes(bytes?: number): number {
+ return (bytes ? (bytes / 1024 / 1024) : 0);
+}