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

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

commit 024e8723a8b241e9cb1eb09ff17a581913a05b01
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Sat Feb 10 18:13:42 2024 -0500

    Fix #564
---
 .../apache/camel/karavan/api/ImagesResource.java   |  9 +--
 .../camel/karavan/api/NotificationResource.java    | 50 ++++++++------
 .../camel/karavan/api/ProjectGitResource.java      | 13 +++-
 .../camel/karavan/service/KaravanService.java      |  3 -
 .../camel/karavan/service/NotificationService.java | 50 ++++++++++++++
 .../camel/karavan/service/ProjectService.java      | 32 +++++++--
 .../org/apache/camel/karavan/shared/Constants.java |  7 ++
 .../exception/ExceptionToResponseMapper.java       | 66 -------------------
 .../src/main/webui/src/api/KaravanApi.tsx          | 19 +++++-
 .../src/main/webui/src/api/NotificationApi.tsx     | 77 +++++++++++++++-------
 .../src/main/webui/src/api/NotificationService.ts  | 57 ++++++++++++++++
 .../src/main/webui/src/api/ProjectEventBus.ts      | 34 +---------
 .../src/main/webui/src/api/ProjectService.ts       |  9 ++-
 .../karavan-app/src/main/webui/src/main/Main.tsx   | 22 +++++--
 .../main/webui/src/project/files/FilesToolbar.tsx  | 16 ++++-
 .../main/webui/src/project/log/ProjectLogPanel.tsx |  2 -
 16 files changed, 286 insertions(+), 180 deletions(-)

diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ImagesResource.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ImagesResource.java
index 6bbe60f5..0170918b 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ImagesResource.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ImagesResource.java
@@ -65,11 +65,8 @@ public class ImagesResource {
     @Path("/{projectId}")
     public Response build(JsonObject data, @PathParam("projectId") String 
projectId) throws Exception {
         try {
-            String imageName = data.getString("imageName");
-            boolean commit = data.getBoolean("commit");
-            String message = data.getString("message");
-            projectService.setProjectImage(projectId, imageName, commit, 
message);
-            return Response.ok().entity(imageName).build();
+            projectService.setProjectImage(projectId, data);
+            return Response.ok().entity(data.getString("imageName")).build();
         } catch (Exception e) {
             return Response.serverError().entity(e.getMessage()).build();
         }
@@ -78,7 +75,7 @@ public class ImagesResource {
     @DELETE
     @Produces(MediaType.APPLICATION_JSON)
     @Path("/{imageName}")
-    public Response deleteImage(@HeaderParam("username") String username, 
@PathParam("imageName") String imageName) {
+    public Response deleteImage(@PathParam("imageName") String imageName) {
         imageName= new String(Base64.decode(imageName));
         if (ConfigService.inKubernetes()) {
             return Response.ok().build();
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/NotificationResource.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/NotificationResource.java
index 4ad4de25..d7f2deb7 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/NotificationResource.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/NotificationResource.java
@@ -16,48 +16,56 @@
  */
 package org.apache.camel.karavan.api;
 
-import io.quarkus.runtime.StartupEvent;
 import io.smallrye.mutiny.Multi;
 import io.vertx.core.json.JsonObject;
-import io.vertx.mutiny.core.Vertx;
 import io.vertx.mutiny.core.eventbus.EventBus;
-import jakarta.enterprise.event.Observes;
 import jakarta.inject.Inject;
 import jakarta.ws.rs.GET;
 import jakarta.ws.rs.Path;
 import jakarta.ws.rs.PathParam;
 import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Context;
 import jakarta.ws.rs.core.MediaType;
-import org.jboss.logging.Logger;
+import jakarta.ws.rs.sse.OutboundSseEvent;
+import jakarta.ws.rs.sse.Sse;
 import org.jboss.resteasy.reactive.RestStreamElementType;
 
-import java.util.Date;
+import static org.apache.camel.karavan.shared.Constants.*;
 
 @Path("/api/notification")
 public class NotificationResource {
 
-    private static final Logger LOGGER = 
Logger.getLogger(NotificationResource.class.getName());
-
-    void onStart(@Observes StartupEvent ev) throws Exception {
-        System.out.println("STARTING!!!!!");
-        vertx.setPeriodic(1000,
-                aLong -> {
-                    vertx.eventBus().publish("test0", new 
JsonObject().put("user", "test0").put("date", new Date().toString()));
-                    vertx.eventBus().publish("test1", new 
JsonObject().put("user", "test1").put("date", new Date().toString()));
-                });
-    }
-
-    @Inject
-    Vertx vertx;
     @Inject
     EventBus bus;
 
+    @GET
+    @Path("/system")
+    @Produces(MediaType.SERVER_SENT_EVENTS)
+    @RestStreamElementType(MediaType.TEXT_PLAIN)
+    public Multi<OutboundSseEvent> karavanStream(
+            @Context Sse sse
+    ) {
+        return bus.<JsonObject>consumer(NOTIFICATION_ADDRESS_SYSTEM).toMulti()
+                .map(m -> sse.newEventBuilder()
+                        .id(m.headers().get(NOTIFICATION_HEADER_EVENT_ID))
+                        .name(m.headers().get(NOTIFICATION_HEADER_EVENT_NAME) 
+ ":" + m.headers().get(NOTIFICATION_HEADER_CLASS_NAME))
+                        .data(m.body())
+                        .build());
+    }
 
     @GET
-    @Path("{name}")
+    @Path("/user/{id}")
     @Produces(MediaType.SERVER_SENT_EVENTS)
     @RestStreamElementType(MediaType.TEXT_PLAIN)
-    public Multi<String> greetingStream(@PathParam("name") String name) {
-        return bus.<String>consumer(name).bodyStream().toMulti();
+    public Multi<OutboundSseEvent> userStream(
+            @PathParam("id") String id,
+            @Context Sse sse
+    ) {
+        return bus.<JsonObject>consumer(id).toMulti()
+                .map(m -> sse.newEventBuilder()
+                        .id(m.headers().get(NOTIFICATION_HEADER_EVENT_ID))
+                        .name(m.headers().get(NOTIFICATION_HEADER_EVENT_NAME) 
+ ":" + m.headers().get(NOTIFICATION_HEADER_CLASS_NAME))
+                        .data(m.body())
+                        .build());
     }
 }
\ No newline at end of file
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectGitResource.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectGitResource.java
index aea987ea..b617f7b0 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectGitResource.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectGitResource.java
@@ -16,6 +16,8 @@
  */
 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;
@@ -25,6 +27,9 @@ import org.apache.camel.karavan.service.ProjectService;
 import org.jboss.logging.Logger;
 
 import java.util.HashMap;
+import java.util.Map;
+
+import static org.apache.camel.karavan.service.ProjectService.PUSH_PROJECT;
 
 @Path("/api/git")
 public class ProjectGitResource {
@@ -34,11 +39,15 @@ public class ProjectGitResource {
     @Inject
     ProjectService projectService;
 
+    @Inject
+    EventBus eventBus;
+
     @POST
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
-    public Project push(HashMap<String, String> params) throws Exception {
-        return projectService.commitAndPushProject(params.get("projectId"), 
params.get("message"));
+    public HashMap<String, String> push(HashMap<String, String> params) throws 
Exception {
+        eventBus.publish(PUSH_PROJECT, JsonObject.mapFrom(params));
+        return params;
     }
 
     @PUT
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java
index 9d22d1c1..fe2df760 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java
@@ -64,9 +64,6 @@ public class KaravanService implements HealthCheck {
     @Inject
     DockerForRegistry dockerForRegistry;
 
-    @Inject
-    KaravanCacheService karavanCacheService;
-
     @Inject
     EventBus eventBus;
 
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/NotificationService.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/NotificationService.java
new file mode 100644
index 00000000..b73e9d78
--- /dev/null
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/NotificationService.java
@@ -0,0 +1,50 @@
+/*
+ * 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.service;
+
+import io.vertx.core.eventbus.DeliveryOptions;
+import io.vertx.core.json.JsonObject;
+import io.vertx.mutiny.core.eventbus.EventBus;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import java.util.UUID;
+
+import static org.apache.camel.karavan.shared.Constants.*;
+
+@ApplicationScoped
+public class NotificationService {
+
+    @Inject
+    EventBus eventBus;
+
+    public void send(String userId, String eventId, String evenName, String 
className, JsonObject data) {
+        eventBus.publish(userId, data, new DeliveryOptions()
+                        .addHeader(NOTIFICATION_HEADER_EVENT_ID, eventId != 
null ? eventId : UUID.randomUUID().toString())
+                        .addHeader(NOTIFICATION_HEADER_EVENT_NAME, evenName)
+                        .addHeader(NOTIFICATION_HEADER_CLASS_NAME, className)
+        );
+    }
+
+    public void sendSystem(String eventId, String evenName, String className, 
JsonObject data) {
+        eventBus.publish(NOTIFICATION_ADDRESS_SYSTEM, data, new 
DeliveryOptions()
+                        .addHeader(NOTIFICATION_HEADER_EVENT_ID, eventId != 
null ? eventId : UUID.randomUUID().toString())
+                        .addHeader(NOTIFICATION_HEADER_EVENT_NAME, evenName)
+                        .addHeader(NOTIFICATION_HEADER_CLASS_NAME, className)
+        );
+    }
+}
\ No newline at end of file
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
index e3f2aa1a..a17044fd 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.karavan.service;
 
+import io.quarkus.vertx.ConsumeEvent;
 import io.smallrye.mutiny.tuples.Tuple2;
 import io.vertx.core.json.JsonObject;
 import io.vertx.mutiny.core.eventbus.EventBus;
@@ -53,6 +54,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 
 import static org.apache.camel.karavan.code.CodeService.*;
+import static 
org.apache.camel.karavan.shared.Constants.NOTIFICATION_EVENT_COMMIT;
 
 @Default
 @Readiness
@@ -61,6 +63,8 @@ public class ProjectService implements HealthCheck {
 
     private static final Logger LOGGER = 
Logger.getLogger(ProjectService.class.getName());
 
+    public static final String PUSH_PROJECT = "PUSH_PROJECT";
+
     @ConfigProperty(name = "karavan.environment")
     String environment;
 
@@ -71,6 +75,9 @@ public class ProjectService implements HealthCheck {
     @Inject
     KaravanCacheService karavanCacheService;
 
+    @Inject
+    NotificationService notificationService;
+
     @Inject
     KubernetesService kubernetesService;
 
@@ -348,7 +355,13 @@ public class ProjectService implements HealthCheck {
 
     }
 
-    public Project commitAndPushProject(String projectId, String message) 
throws Exception {
+    @ConsumeEvent(value = PUSH_PROJECT, blocking = true, ordered = true)
+    void commitAndPushProject(JsonObject event) throws Exception {
+        LOGGER.info("Commit: " + event.encodePrettily());
+        String projectId = event.getString("projectId");
+        String message = event.getString("message");
+        String userId = event.getString("userId");
+        String eventId = event.getString("eventId");
         Project p = karavanCacheService.getProject(projectId);
         List<ProjectFile> files = 
karavanCacheService.getProjectFiles(projectId);
         RevCommit commit = gitService.commitAndPushProject(p, files, message);
@@ -357,7 +370,9 @@ public class ProjectService implements HealthCheck {
         p.setLastCommit(commitId);
         p.setLastCommitTimestamp(lastUpdate);
         karavanCacheService.saveProject(p);
-        return p;
+        if (userId != null) {
+            notificationService.sendSystem(eventId, NOTIFICATION_EVENT_COMMIT, 
Project.class.getSimpleName(), JsonObject.mapFrom(p));
+        }
     }
 
     void addKameletsProject() {
@@ -367,7 +382,7 @@ public class ProjectService implements HealthCheck {
             if (kamelets == null) {
                 kamelets = new Project(Project.Type.kamelets.name(), "Custom 
Kamelets", "Custom Kamelets", "", Instant.now().toEpochMilli(), 
Project.Type.kamelets);
                 karavanCacheService.saveProject(kamelets);
-                commitAndPushProject(Project.Type.kamelets.name(), "Add custom 
kamelets");
+                commitAndPushProject(JsonObject.of("projectId", 
Project.Type.kamelets.name(), "message", "Add custom kamelets"));
             }
         } catch (Exception e) {
             LOGGER.error("Error during custom kamelets project creation", e);
@@ -386,7 +401,7 @@ public class ProjectService implements HealthCheck {
                     ProjectFile file = new ProjectFile(name, value, 
Project.Type.templates.name(), Instant.now().toEpochMilli());
                     karavanCacheService.saveProjectFile(file);
                 });
-                commitAndPushProject(Project.Type.templates.name(), "Add 
default templates");
+                commitAndPushProject(JsonObject.of("projectId", 
Project.Type.templates.name(), "message", "Add custom templates"));
             } else {
                 LOGGER.info("Add new templates if any");
                 codeService.getTemplates().forEach((name, value) -> {
@@ -414,7 +429,7 @@ public class ProjectService implements HealthCheck {
                     ProjectFile file = new ProjectFile(name, value, 
Project.Type.services.name(), Instant.now().toEpochMilli());
                     karavanCacheService.saveProjectFile(file);
                 });
-                commitAndPushProject(Project.Type.services.name(), "Add 
services");
+                commitAndPushProject(JsonObject.of("projectId", 
Project.Type.services.name(), "message", "Add services"));
             }
         } catch (Exception e) {
             LOGGER.error("Error during services project creation", e);
@@ -427,10 +442,13 @@ public class ProjectService implements HealthCheck {
         return file.orElse(new ProjectFile()).getCode();
     }
 
-    public void setProjectImage(String projectId, String imageName, boolean 
commit, String message) throws Exception {
+    public void setProjectImage(String projectId, JsonObject data) throws 
Exception {
+        String imageName = data.getString("imageName");
+        boolean commit = data.getBoolean("commit");
+        data.put("projectId", projectId);
         codeService.updateDockerComposeImage(projectId, imageName);
         if (commit) {
-            commitAndPushProject(projectId, message);
+            eventBus.publish(PUSH_PROJECT, data);
         }
     }
 
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java
index 28f01ce8..ff6152d9 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java
@@ -42,6 +42,13 @@ public class Constants {
     public static final String KNOWN_HOSTS_SECRET_KEY = "known-hosts";
     public static final String BUILD_SCRIPT_FILENAME_SUFFIX = "-build.sh";
 
+    public static final String NOTIFICATION_ADDRESS_SYSTEM = "karavanSystem";
+    public static final String NOTIFICATION_HEADER_EVENT_ID = "id";
+    public static final String NOTIFICATION_HEADER_EVENT_NAME = "eventName";
+    public static final String NOTIFICATION_HEADER_CLASS_NAME = "className";
+
+    public static final String NOTIFICATION_EVENT_COMMIT = "commit";
+
     public enum CamelRuntime {
         CAMEL_MAIN("camel-main"),
         QUARKUS("quarkus"),
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/exception/ExceptionToResponseMapper.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/exception/ExceptionToResponseMapper.java
deleted file mode 100644
index b26b49fc..00000000
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/exception/ExceptionToResponseMapper.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package org.apache.camel.karavan.shared.exception;
-
-import java.util.Collection;
-import java.util.List;
-
-import org.apache.camel.karavan.shared.error.Error;
-import org.apache.camel.karavan.shared.error.ErrorResponse;
-import org.jboss.logging.Logger;
-import org.jboss.resteasy.reactive.RestResponse;
-import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
-
-import jakarta.ws.rs.core.MediaType;
-import jakarta.ws.rs.core.Response;
-
-public class ExceptionToResponseMapper {
-    private static final Logger LOGGER = 
Logger.getLogger(ExceptionToResponseMapper.class.getName());
-
-    @ServerExceptionMapper
-    public RestResponse<Object> validationException(ValidationException 
exception) {
-        List<Error> errors = (exception).getErrors()
-                .stream()
-                .map(fieldError -> new Error(fieldError.getField(), 
fieldError.getMessage()))
-                .toList();
-
-        return logAndBuildResponse(
-                exception,
-                Response.Status.BAD_REQUEST.getStatusCode(),
-                Response.Status.BAD_REQUEST.getReasonPhrase(),
-                errors
-        );
-    }
-
-    private RestResponse<Object> logAndBuildResponse(
-            Throwable exception,
-            int status,
-            String reasonPhrase,
-            Collection<Error> errors
-    ) {
-        LOGGER.error("Error occurred", exception);
-
-        String cause = (exception.getCause() != null) ? 
exception.getCause().getMessage() : null;
-        String message = (cause != null) ? exception.getMessage() + ", caused 
by: " + cause : exception.getMessage();
-
-        if (message == null) {
-            message = exception.getClass().toString();
-        }
-
-        // Hide errors array if there are no errors and leave just error 
message
-        if (errors != null && errors.isEmpty()) {
-            errors = null;
-        }
-
-        ErrorResponse responseBody = new ErrorResponse(
-                status,
-                reasonPhrase,
-                message,
-                errors
-        );
-
-        return RestResponse.ResponseBuilder
-                .create(status)
-                .entity(responseBody)
-                .type(MediaType.APPLICATION_JSON)
-                .build();
-    }
-}
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx 
b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
index 2efbeeb6..78239239 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
@@ -26,9 +26,9 @@ import {
 } from "./ProjectModels";
 import {Buffer} from 'buffer';
 import {SsoApi} from "./SsoApi";
-import {EventStreamContentType, fetchEventSource} from 
"@microsoft/fetch-event-source";
-import {ProjectEventBus} from "./ProjectEventBus";
+import {v4 as uuidv4} from "uuid";
 
+const USER_ID_KEY = 'KARAVAN_USER_ID';
 axios.defaults.headers.common['Accept'] = 'application/json';
 axios.defaults.headers.common['Content-Type'] = 'application/json';
 const instance = axios.create();
@@ -43,6 +43,21 @@ export class KaravanApi {
         return instance;
     }
 
+    static getUserId(): string {
+        if (KaravanApi.me?.userName !== undefined) {
+            return KaravanApi.me?.userName;
+        } else {
+            const userId = localStorage.getItem(USER_ID_KEY);
+            if (userId !== null && userId !== undefined) {
+                return userId;
+            } else {
+                const newId = uuidv4().toString();
+                localStorage.setItem(USER_ID_KEY, newId);
+                return newId;
+            }
+        }
+    }
+
     static setAuthType(authType: string) {
         KaravanApi.authType = authType;
         switch (authType){
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/NotificationApi.tsx 
b/karavan-web/karavan-app/src/main/webui/src/api/NotificationApi.tsx
index 20ea0c1f..cabf1c9e 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/NotificationApi.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/api/NotificationApi.tsx
@@ -18,40 +18,69 @@
 import {SsoApi} from "./SsoApi";
 import {EventStreamContentType, fetchEventSource} from 
"@microsoft/fetch-event-source";
 import {KaravanApi} from "./KaravanApi";
+import {EventBus} from "../designer/utils/EventBus";
+import {EventSourceMessage} from "@microsoft/fetch-event-source/lib/cjs/parse";
+import {KaravanEvent, NotificationEventBus} from "./NotificationService";
 
 export class NotificationApi {
 
+     static getKaravanEvent (ev: EventSourceMessage, type: 'system' | 'user') {
+        const eventParts = ev.event?.split(':');
+        const event = eventParts?.length > 1 ? eventParts[0] : undefined;
+        const className = eventParts?.length > 1 ? eventParts[1] : undefined;
+        return new KaravanEvent({id: ev.id, event: event, type: type, 
className: className, data: JSON.parse(ev.data)});
+    }
+
+    static onSystemMessage (ev: EventSourceMessage) {
+        const ke = NotificationApi.getKaravanEvent(ev, 'system');
+        NotificationEventBus.sendEvent(ke);
+    }
+
+    static onUserMessage (ev: EventSourceMessage) {
+        const ke = NotificationApi.getKaravanEvent(ev, 'user');
+        NotificationEventBus.sendEvent(ke);
+    }
+
     static async notification(controller: AbortController) {
         const fetchData = async () => {
             const headers: any = { Accept: "text/event-stream" };
             if (KaravanApi.authType === 'oidc') {
                 headers.Authorization = "Bearer " + SsoApi.keycloak?.token
             }
-            await fetchEventSource("/api/notification", {
-                method: "GET",
-                headers: headers,
-                signal: controller.signal,
-                async onopen(response) {
-                    if (response.ok && response.headers.get('content-type') 
=== EventStreamContentType) {
-                        return; // everything's good
-                    } else if (response.status >= 400 && response.status < 500 
&& response.status !== 429) {
-                        // client-side errors are usually non-retriable:
-                        console.log("Server side error ", response);
-                    } else {
-                        console.log("Error ", response);
-                    }
-                },
-                onmessage(event) {
-                    console.log(event)
-                },
-                onclose() {
-                    console.log("Connection closed by the server");
-                },
-                onerror(err) {
-                    console.log("There was an error from server", err);
-                },
-            });
+            NotificationApi.fetch('/api/notification/system', controller, 
headers,
+                    ev => NotificationApi.onSystemMessage(ev));
+            NotificationApi.fetch('/api/notification/user/' + 
KaravanApi.getUserId(), controller, headers,
+                ev => NotificationApi.onUserMessage(ev));
         };
         return fetchData();
+    };
+
+    static async fetch(input: string, controller: AbortController, headers: 
any, onmessage: (ev: EventSourceMessage) => void) {
+        fetchEventSource(input, {
+            method: "GET",
+            headers: headers,
+            signal: controller.signal,
+            async onopen(response) {
+                if (response.ok && response.headers.get('content-type') === 
EventStreamContentType) {
+                    return; // everything's good
+                } else if (response.status >= 400 && response.status < 500 && 
response.status !== 429) {
+                    // client-side errors are usually non-retriable:
+                    console.log("Server side error ", response);
+                    EventBus.sendAlert("Error fetching", `${input} : 
${response.statusText}`, "danger");
+                } else {
+                    console.log("Error ", response);
+                    EventBus.sendAlert("Error fetching", `${input} : 
${response.statusText}`, "danger");
+                }
+            },
+            onmessage(event) {
+                onmessage(event);
+            },
+            onclose() {
+                console.log("Connection closed by the server");
+            },
+            onerror(err) {
+                console.log("There was an error from server", err);
+            },
+        });
     }
 }
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/api/NotificationService.ts 
b/karavan-web/karavan-app/src/main/webui/src/api/NotificationService.ts
new file mode 100644
index 00000000..2008c652
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/webui/src/api/NotificationService.ts
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {Subject} from "rxjs";
+import {ProjectEventBus} from "./ProjectEventBus";
+import {unstable_batchedUpdates} from "react-dom";
+import {useLogStore, useProjectStore} from "./ProjectStore";
+import {ProjectService} from "./ProjectService";
+
+export class KaravanEvent {
+    id: string = '';
+    type: 'system' | 'user' = 'system';
+    event: string = '';
+    className: string = '';
+    data: any = {};
+
+    public constructor(init?: Partial<KaravanEvent>) {
+        Object.assign(this, init);
+    }
+}
+
+const karavanEvents = new Subject<KaravanEvent>();
+
+export const NotificationEventBus = {
+    sendEvent: (event: KaravanEvent) =>  karavanEvents.next(event),
+    onEvent: () => karavanEvents.asObservable(),
+}
+
+console.log("Start Notification subscriber");
+const sub = NotificationEventBus.onEvent()?.subscribe((event: KaravanEvent) => 
{
+    // console.log('KaravanEvent', event);
+    if (event.event === 'commit' && event.className === 'Project') {
+        const projectId = event.data?.projectId;
+        if (useProjectStore.getState().project?.projectId === projectId) {
+            unstable_batchedUpdates(() => {
+                useProjectStore.setState({isPushing: false});
+                ProjectService.refreshProject(projectId);
+                ProjectService.refreshProjectData(projectId);
+            });
+        }
+    }
+});
+
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectEventBus.ts 
b/karavan-web/karavan-app/src/main/webui/src/api/ProjectEventBus.ts
index dcbc6c6e..84e458e7 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectEventBus.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectEventBus.ts
@@ -14,44 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {BehaviorSubject, Subject} from 'rxjs';
-import {Project} from "./ProjectModels";
+import {Subject} from 'rxjs';
 
-const selectedProject = new BehaviorSubject<Project | undefined>(undefined);
-const currentFile = new BehaviorSubject<string | undefined>(undefined);
-const mode = new BehaviorSubject<"design" | "code">("design");
 const log = new Subject<["add" | "set", string]>();
 
-export class ShowLogCommand {
-    name: string
-    environment: string
-    show: boolean
-
-    constructor(name: string, environment: string, show: boolean) {
-        this.name = name;
-        this.environment = environment;
-        this.show = show;
-    }
-}
-
-export class ShowTraceCommand {
-    name: string
-    show: boolean
-
-    constructor(name: string, show: boolean) {
-        this.name = name;
-        this.show = show;
-    }
-}
 export const ProjectEventBus = {
 
-    selectProject: (project: Project) => selectedProject.next(project),
-    onSelectProject: () => selectedProject.asObservable(),
-
-    setMode: (m: 'design' | 'code') =>  mode.next(m),
-    onSetMode: () => mode.asObservable(),
-
     sendLog: (type: "add" | "set", m: string) =>  log.next([type, m]),
     onLog: () => log.asObservable(),
-
 }
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts 
b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
index 9f9891f4..9a1b5f21 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
@@ -96,19 +96,18 @@ export class ProjectService {
     }
 
     public static pushProject(project: Project, commitMessage: string) {
-        useProjectStore.setState({isPushing: true})
         const params = {
             'projectId': project.projectId,
-            'message': commitMessage
+            'message': commitMessage,
+            'userId': KaravanApi.getUserId()
         };
         KaravanApi.push(params, res => {
             if (res.status === 200 || res.status === 201) {
-                ProjectService.refreshProject(project.projectId);
-                ProjectService.refreshProjectData(project.projectId);
+                // ProjectService.refreshProject(project.projectId);
+                // ProjectService.refreshProjectData(project.projectId);
             } else {
                 EventBus.sendAlert("Error pushing", (res as 
any)?.response?.data, 'danger')
             }
-            useProjectStore.setState({isPushing: false})
         });
     }
 
diff --git a/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx 
b/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx
index 00fc6ff5..d08f5dc8 100644
--- a/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx
@@ -30,16 +30,28 @@ import {useMainHook} from "./useMainHook";
 import {Notification} from "../designer/utils/Notification";
 import {MainLoader} from "./MainLoader";
 import {MainRoutes} from "./MainRoutes";
+import {NotificationApi} from "../api/NotificationApi";
 
 export function Main() {
 
     const [readiness, setReadiness] = useAppConfigStore((s) => [s.readiness, 
s.setReadiness], shallow)
-    const {getData, getStatuses} = useMainHook();
+    const {getData} = useMainHook();
 
-    const initialized = useRef(false)
+    const initialized = useRef(false);
+
+    useEffect(() => {
+        if (showMain()) {
+            console.log("Start Notification fetcher");
+            const controller = new AbortController();
+            NotificationApi.notification(controller);
+            return () => {
+                console.log("Stop Notification fetcher");
+                controller.abort();
+            };
+        }
+    }, [readiness]);
 
     useEffect(() => {
-        console.log("Main");
         if (!initialized.current) {
             initialized.current = true
             effect()
@@ -55,7 +67,6 @@ export function Main() {
     }, [])
 
     function effect() {
-        console.log("Main effect start");
         KaravanApi.getAuthType((authType: string) => {
             console.log("authType", authType);
             if (authType === 'oidc') {
@@ -67,9 +78,6 @@ export function Main() {
             }
             getData();
         });
-        return () => {
-            console.log("Main effect end");
-        };
     }
 
     function showSpinner() {
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx 
b/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
index 5f4216e3..166bdaaf 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
@@ -41,7 +41,8 @@ import UpdateIcon from 
"@patternfly/react-icons/dist/esm/icons/cog-icon";
 import RefreshIcon from "@patternfly/react-icons/dist/esm/icons/sync-alt-icon";
 import {ProjectType} from "../../api/ProjectModels";
 import {KaravanApi} from "../../api/KaravanApi";
-import {EventBus} from "../../designer/utils/EventBus";
+import {DslPosition, EventBus} from "../../designer/utils/EventBus";
+import {KaravanEvent, NotificationEventBus} from 
"../../api/NotificationService";
 
 export function FileToolbar () {
 
@@ -49,17 +50,28 @@ export function FileToolbar () {
     const [commitMessageIsOpen, setCommitMessageIsOpen] = useState(false);
     const [pullIsOpen, setPullIsOpen] = useState(false);
     const [commitMessage, setCommitMessage] = useState('');
-    const [project, isPushing, isPulling] = useProjectStore((s) => [s.project, 
s.isPushing, s.isPulling], shallow )
+    const [project, isPushing, isPulling] =
+        useProjectStore((s) => [s.project, s.isPushing, s.isPulling], shallow )
     const {files} = useFilesStore();
     const [file, editAdvancedProperties, setEditAdvancedProperties, 
setAddProperty, setFile] = useFileStore((s) =>
         [s.file, s.editAdvancedProperties, s.setEditAdvancedProperties, 
s.setAddProperty, s.setFile], shallow )
 
+    // useEffect(() => {
+    //     const sub1 = NotificationEventBus.onEvent()?.subscribe((evt: 
KaravanEvent) => {
+    //         console.log(evt);
+    //         setIsPushing(false);
+    //     });
+    //     return () => {
+    //         sub1?.unsubscribe();
+    //     };
+    // });
 
     useEffect(() => {
     }, [project, file]);
 
     function push () {
         setCommitMessageIsOpen(false);
+        useProjectStore.setState({isPushing: true});
         ProjectService.pushProject(project, commitMessage);
     }
 
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/project/log/ProjectLogPanel.tsx 
b/karavan-web/karavan-app/src/main/webui/src/project/log/ProjectLogPanel.tsx
index 99d62b43..6e7de890 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/log/ProjectLogPanel.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/log/ProjectLogPanel.tsx
@@ -48,11 +48,9 @@ export function ProjectLogPanel () {
             const f = LogWatchApi.fetchData(type, podName, 
controller).then(value => {
                 console.log("Fetch Started for: " + podName)
             });
-            console.log("new fetch")
             setFetch(f);
         }
         return () => {
-            console.log("end");
             controller.abort();
         };
     }, [showLog, type, podName]);


Reply via email to