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

[camel-karavan] branch main updated (9408aa4e -> edf46ac5)

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

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


    from 9408aa4e Project logs redesign
     new 1689fb5e Project logs redesign
     new ca01cbbf Project logs redesign
     new 9dd12904 SSE backend api for containers #563
     new edf46ac5 Fix #563

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


Summary of changes:
 .../camel/karavan/api/KubernetesResource.java      |   8 +-
 .../apache/camel/karavan/api/LogWatchResource.java |  86 +++++
 .../apache/camel/karavan/model/PipelineRunLog.java |  27 --
 .../camel/karavan/service/KubernetesService.java   |  63 +---
 .../camel/karavan/service/StatusService.java       |   4 +-
 karavan-app/src/main/webui/package-lock.json       | 371 ++++++++++++++-------
 karavan-app/src/main/webui/package.json            |  20 +-
 karavan-app/src/main/webui/src/api/KaravanApi.tsx  |   2 +-
 karavan-app/src/main/webui/src/index.css           |  23 +-
 .../src/main/webui/src/projects/ProjectEventBus.ts |  16 +-
 .../src/main/webui/src/projects/ProjectLog.tsx     | 160 ++++-----
 .../main/webui/src/projects/ProjectOperations.tsx  |   3 +-
 .../src/main/webui/src/projects/ProjectPage.tsx    |  23 +-
 .../src/main/webui/src/projects/ProjectStatus.tsx  |   5 +-
 14 files changed, 484 insertions(+), 327 deletions(-)
 create mode 100644 karavan-app/src/main/java/org/apache/camel/karavan/api/LogWatchResource.java
 delete mode 100644 karavan-app/src/main/java/org/apache/camel/karavan/model/PipelineRunLog.java


[camel-karavan] 02/04: Project logs redesign

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit ca01cbbf7eb055101489d8e905a539357b32be2e
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Tue May 2 16:50:31 2023 -0400

    Project logs redesign
---
 .../camel/karavan/service/StatusService.java       |   4 +-
 karavan-app/src/main/webui/src/index.css           |  10 +-
 .../src/main/webui/src/projects/ProjectLog.tsx     | 111 +++++----------------
 .../main/webui/src/projects/ProjectOperations.tsx  |   3 +-
 .../src/main/webui/src/projects/ProjectPage.tsx    |  17 ----
 .../src/main/webui/src/projects/ProjectStatus.tsx  |   5 +-
 6 files changed, 38 insertions(+), 112 deletions(-)

diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/StatusService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/StatusService.java
index c59d1440..a993437d 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/StatusService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/StatusService.java
@@ -82,7 +82,7 @@ public class StatusService {
         return webClient;
     }
 
-    @ConsumeEvent(value = CMD_COLLECT_PROJECT_STATUS, blocking = true, ordered = true)
+//    @ConsumeEvent(value = CMD_COLLECT_PROJECT_STATUS, blocking = true, ordered = true)
     public void collectProjectStatus(JsonObject data) {
         String projectId = data.getString("projectId");
         String env = data.getString("env");
@@ -98,7 +98,7 @@ public class StatusService {
         }
     }
 
-    @ConsumeEvent(value = CMD_COLLECT_ALL_STATUSES, blocking = true, ordered = true)
+//    @ConsumeEvent(value = CMD_COLLECT_ALL_STATUSES, blocking = true, ordered = true)
     public void collectAllStatuses(String data) {
         String all = "ALL_PROJECTS";
         if ((System.currentTimeMillis() - lastCollect.getOrDefault(all, 0L)) > threshold) {
diff --git a/karavan-app/src/main/webui/src/index.css b/karavan-app/src/main/webui/src/index.css
index f947f243..8adb5fa9 100644
--- a/karavan-app/src/main/webui/src/index.css
+++ b/karavan-app/src/main/webui/src/index.css
@@ -182,9 +182,15 @@
   height: 100% !important;
 }
 
-
 .karavan .project-page .project-log .pf-c-log-viewer__text {
-  font-size: 11px;
+  font-size: 12px;
+}
+
+.karavan .project-page .project-log .log-name {
+  --pf-c-label__content--before--BorderWidth: 0;
+  --pf-c-label--BackgroundColor: transparent;
+  margin-right: auto;
+  font-size: 12px;
 }
 
 .karavan .project-page .project-button {
diff --git a/karavan-app/src/main/webui/src/projects/ProjectLog.tsx b/karavan-app/src/main/webui/src/projects/ProjectLog.tsx
index 1646663d..3c706444 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectLog.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectLog.tsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import {Button, Checkbox, PageSection, Tooltip, TooltipPosition} from '@patternfly/react-core';
+import {Button, Checkbox, Label, PageSection, Text, Tooltip, TooltipPosition} from '@patternfly/react-core';
 import '../designer/karavan.css';
 import CloseIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
 import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/expand-icon';
@@ -9,16 +9,20 @@ import {LogViewer} from '@patternfly/react-log-viewer';
 import {Subscription} from "rxjs";
 import {ProjectEventBus, ShowLogCommand} from "./ProjectEventBus";
 import {findDOMNode} from "react-dom";
+import {ProjectFile} from "./ProjectModels";
+import {KaravanApi} from "../api/KaravanApi";
 
 interface Props {
 
 }
 
 interface State {
+    log?: ShowLogCommand,
     showLog: boolean,
     height?: number | string,
     logViewerRef: any,
     isTextWrapped: boolean
+    data: string[]
 }
 
 export class ProjectLog extends React.Component<Props, State> {
@@ -27,14 +31,17 @@ export class ProjectLog extends React.Component<Props, State> {
         showLog: false,
         height: "30%",
         logViewerRef: React.createRef(),
-        isTextWrapped: false
+        isTextWrapped: false,
+        data: []
     }
 
     sub?: Subscription;
 
     componentDidMount() {
         this.sub = ProjectEventBus.onShowLog()?.subscribe((log: ShowLogCommand) => {
-            this.setState({showLog: true});
+            this.setState({showLog: true, log: log});
+            console.log(log)
+            this.showLogs(log.type, log.name, log.environment);
         });
     }
 
@@ -42,91 +49,24 @@ export class ProjectLog extends React.Component<Props, State> {
         this.sub?.unsubscribe();
     }
 
-    componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => {
-        if (this.state.height === "100%" && prevState.height !== "100%") {
-            const element = findDOMNode(this.state.logViewerRef.current)
-            console.log("change", element)
-            console.log("change", this.state.logViewerRef.current)
+    showLogs = (type: 'container' | 'pipeline', name: string, environment: string) => {
+        if (type === 'pipeline') {
+            KaravanApi.getPipelineLog(environment, name, (res: any) => {
+                if (Array.isArray(res) && Array.from(res).length > 0)
+                    this.setState({data: res.at(0).log});
+            });
+        } else if (type === 'container') {
+            KaravanApi.getContainerLog(environment, name, (res: any) => {
+                this.setState({data: res});
+            });
         }
-    }
 
-    code = "apiVersion: helm.openshift.io/v1beta1/\n" +
-        "kind: HelmChartRepository\n" +
-        "metadata:\n" +
-        "name: azure-sample-repo0oooo00ooo\n" +
-        "spec:\n" +
-        "connectionConfig:\n" +
-        "url: https://raw.githubusercontent.com/Azure-Samples/helm-charts/master/docsapiVersion: helm.openshift.io/v1beta1/\n" +
-        "kind: HelmChartRepository\n" +
-        "metadata:\n" +
-        "name: azure-sample-repo0oooo00ooo\n" +
-        "spec:\n" +
-        "connectionConfig:\n" +
-        "url: https://raw.githubusercontent.com/Azure-Samples/helm-charts/master/docsapiVersion: helm.openshift.io/v1beta1/\n" +
-        "kind: HelmChartRepository\n" +
-        "metadata:\n" +
-        "name: azure-sample-repo0oooo00ooo\n" +
-        "spec:\n" +
-        "connectionConfig:\n" +
-        "url: https://raw.githubusercontent.com/Azure-Samples/helm-charts/master/docsapiVersion: helm.openshift.io/v1beta1/\n" +
-        "kind: HelmChartRepository\n" +
-        "metadata:\n" +
-        "name: azure-sample-repo0oooo00ooo\n" +
-        "spec:\n" +
-        "connectionConfig:\n" +
-        "url: https://raw.githubusercontent.com/Azure-Samples/helm-charts/master/docsapiVersion: helm.openshift.io/v1beta1/\n" +
-        "kind: HelmChartRepository\n" +
-        "metadata:\n" +
-        "name: azure-sample-repo0oooo00ooo\n" +
-        "spec:\n" +
-        "connectionConfig:\n" +
-        "url: https://raw.githubusercontent.com/Azure-Samples/helm-charts/master/docsapiVersion: helm.openshift.io/v1beta1/\n" +
-        "kind: HelmChartRepository\n" +
-        "metadata:\n" +
-        "name: azure-sample-repo0oooo00ooo\n" +
-        "spec:\n" +
-        "connectionConfig:\n" +
-        "url: https://raw.githubusercontent.com/Azure-Samples/helm-charts/master/docsapiVersion: helm.openshift.io/v1beta1/\n" +
-        "kind: HelmChartRepository\n" +
-        "metadata:\n" +
-        "name: azure-sample-repo0oooo00ooo\n" +
-        "spec:\n" +
-        "connectionConfig:\n" +
-        "url: https://raw.githubusercontent.com/Azure-Samples/helm-charts/master/docsapiVersion: helm.openshift.io/v1beta1/\n" +
-        "kind: HelmChartRepository\n" +
-        "metadata:\n" +
-        "name: azure-sample-repo0oooo00ooo\n" +
-        "spec:\n" +
-        "connectionConfig:\n" +
-        "url: https://raw.githubusercontent.com/Azure-Samples/helm-charts/master/docsapiVersion: helm.openshift.io/v1beta1/\n" +
-        "kind: HelmChartRepository\n" +
-        "metadata:\n" +
-        "name: azure-sample-repo0oooo00ooo\n" +
-        "spec:\n" +
-        "connectionConfig:\n" +
-        "url: https://raw.githubusercontent.com/Azure-Samples/helm-charts/master/docsapiVersion: helm.openshift.io/v1beta1/\n" +
-        "kind: HelmChartRepository\n" +
-        "metadata:\n" +
-        "name: azure-sample-repo0oooo00ooo\n" +
-        "spec:\n" +
-        "connectionConfig:\n" +
-        "url: https://raw.githubusercontent.com/Azure-Samples/helm-charts/master/docsapiVersion: helm.openshift.io/v1beta1/\n" +
-        "kind: HelmChartRepository\n" +
-        "metadata:\n" +
-        "name: azure-sample-repo0oooo00ooo\n" +
-        "spec:\n" +
-        "connectionConfig:\n" +
-        "url: https://raw.githubusercontent.com/Azure-Samples/helm-charts/master/docsapiVersion: helm.openshift.io/v1beta1/\n" +
-        "kind: HelmChartRepository\n" +
-        "metadata:\n" +
-        "name: azure-sample-repo0oooo00ooo\n" +
-        "spec:\n" +
-        "connectionConfig:\n" +
-        "url: https://raw.githubusercontent.com/Azure-Samples/helm-charts/master/docs"
+    }
 
     getButtons() {
-        const {height, isTextWrapped, logViewerRef} = this.state;
+        const {height, isTextWrapped, logViewerRef, log} = this.state;
         return (<div className="buttons">
+            <Label className="log-name">{log?.type + ": " + log?.name}</Label>
             <Checkbox label="Wrap text" aria-label="wrap text checkbox" isChecked={isTextWrapped} id="wrap-text-checkbox"
                       onChange={checked => this.setState({isTextWrapped: checked})} />
             <Tooltip content={"Scroll to bottom"} position={TooltipPosition.bottom}>
@@ -143,8 +83,7 @@ export class ProjectLog extends React.Component<Props, State> {
     }
 
     render() {
-        const {showLog, height, logViewerRef, isTextWrapped} = this.state;
-        console.log(this.state)
+        const {showLog, height, logViewerRef, isTextWrapped, data} = this.state;
         return (showLog ?
             <PageSection className="project-log" padding={{default: "noPadding"}} style={{height: height}}>
                 <LogViewer
@@ -154,7 +93,7 @@ export class ProjectLog extends React.Component<Props, State> {
                     loadingContent={"Loading..."}
                     header={this.getButtons()}
                     height={"100vh"}
-                    data={this.code.concat(this.code).concat(this.code).concat(this.code)}
+                    data={data}
                     theme={'dark'}/>
             </PageSection>
             : <></>);
diff --git a/karavan-app/src/main/webui/src/projects/ProjectOperations.tsx b/karavan-app/src/main/webui/src/projects/ProjectOperations.tsx
index bdba3c49..38c68857 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectOperations.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectOperations.tsx
@@ -24,7 +24,8 @@ export class ProjectOperations extends React.Component<Props, State> {
         const {project, config,} = this.props;
         return (
             <div className="project-operations">
-                {["dev", "test", "prod"].map(env =>
+                {/*{["dev", "test", "prod"].map(env =>*/}
+                {["dev"].map(env =>
                     <ProjectStatus key={env} project={project} config={config} env={env}/>
                 )}
             </div>
diff --git a/karavan-app/src/main/webui/src/projects/ProjectPage.tsx b/karavan-app/src/main/webui/src/projects/ProjectPage.tsx
index bc376c72..4ae933dd 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectPage.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectPage.tsx
@@ -300,23 +300,6 @@ export class ProjectPage extends React.Component<Props, State> {
         )
     }
 
-    showLogs = (type: 'container' | 'pipeline', name: string, environment: string) => {
-        const filename = name + ".log";
-        const code = '';
-        this.setState({file: new ProjectFile(filename, this.props.project.projectId, code, Date.now())});
-        if (type === 'pipeline') {
-            KaravanApi.getPipelineLog(environment, name, (res: any) => {
-                if (Array.isArray(res) && Array.from(res).length > 0)
-                    this.setState({file: new ProjectFile(filename, this.props.project.projectId, res.at(0).log, Date.now())});
-            });
-        } else if (type === 'container') {
-            KaravanApi.getContainerLog(environment, name, (res: any) => {
-                this.setState({file: new ProjectFile(filename, this.props.project.projectId, res, Date.now())});
-            });
-        }
-
-    }
-
     deleteEntity = (type: 'pod' | 'deployment' | 'pipelinerun', name: string, environment: string) => {
         switch (type) {
             case "deployment":
diff --git a/karavan-app/src/main/webui/src/projects/ProjectStatus.tsx b/karavan-app/src/main/webui/src/projects/ProjectStatus.tsx
index a5850a9e..a7c35ff2 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectStatus.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectStatus.tsx
@@ -21,7 +21,6 @@ interface Props {
     project: Project,
     config: any,
     env: string,
-    // showLog: (type: 'container' | 'pipeline', name: string, environment: string) => void
 }
 
 interface State {
@@ -218,8 +217,7 @@ export class ProjectStatus extends React.Component<Props, State> {
                                     <Tooltip key={pod.name} content={running ? "Running" : pod.phase}>
                                         <Label icon={running ? <UpIcon/> : <DownIcon/>} color={running ? "green" : "red"}>
                                             <Button variant="link"
-                                                    // onClick={e => this.props.showLog?.call(this, 'container', pod.name, env)}>
-                                                    onClick={e => {}}>
+                                                    onClick={e => ProjectEventBus.showLog('container', pod.name, env)}>
                                                 {pod.name}
                                             </Button>
                                             <Tooltip content={"Delete Pod"}>
@@ -254,7 +252,6 @@ export class ProjectStatus extends React.Component<Props, State> {
 
     showPipelineLog(pipeline: string, env: string) {
         if (pipeline) {
-            // this.props.showLog?.call(this, 'pipeline', pipeline, env);
             ProjectEventBus.showLog('pipeline', pipeline, env);
         }
     }


[camel-karavan] 04/04: Fix #563

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit edf46ac538c0beaaaa564971e76734ceec9dad39
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Tue May 2 22:32:54 2023 -0400

    Fix #563
---
 .../apache/camel/karavan/api/LogWatchResource.java | 23 +++++++------
 .../camel/karavan/service/KubernetesService.java   | 40 +++++-----------------
 karavan-app/src/main/webui/src/api/KaravanApi.tsx  |  2 +-
 .../src/main/webui/src/projects/ProjectLog.tsx     | 36 +++++++++++--------
 4 files changed, 44 insertions(+), 57 deletions(-)

diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/api/LogWatchResource.java b/karavan-app/src/main/java/org/apache/camel/karavan/api/LogWatchResource.java
index 5fa14161..ae7ef8b6 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/api/LogWatchResource.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/LogWatchResource.java
@@ -59,26 +59,27 @@ public class LogWatchResource {
     @Produces(MediaType.SERVER_SENT_EVENTS)
     @Path("/{type}/{env}/{name}")
     public void eventSourcing(@PathParam("env") String env,
-                                       @PathParam("type") String type,
-                                       @PathParam("name") String name,
-                                       @Context SseEventSink eventSink,
-                                       @Context Sse sse
-                                       ) {
+                              @PathParam("type") String type,
+                              @PathParam("name") String name,
+                              @Context SseEventSink eventSink,
+                              @Context Sse sse
+    ) {
         managedExecutor.execute(() -> {
+            LOGGER.info("LogWatch for " + name + " starting...");
             try (SseEventSink sink = eventSink) {
-                LogWatch logWatch = kubernetesService.getLogWatch(name);
+                LogWatch logWatch = type.equals("container")
+                        ? kubernetesService.getContainerLogWatch(name)
+                        : kubernetesService.getPipelineRunLogWatch(name);
                 BufferedReader reader = new BufferedReader(new InputStreamReader(logWatch.getOutput()));
                 try {
                     for (String line; (line = reader.readLine()) != null && !sink.isClosed(); ) {
-                        sink.send(sse.newEvent(line + System.lineSeparator()));
+                        sink.send(sse.newEvent(line));
                     }
                 } catch (IOException e) {
                     LOGGER.error(e.getMessage());
                 }
-                if (sink.isClosed()) {
-                    logWatch.close();
-                    LOGGER.info("LogWatch for " + name + " closed");
-                }
+                logWatch.close();
+                LOGGER.info("LogWatch for " + name + " closed");
             }
         });
     }
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java
index 13b4484c..7bbac8ea 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java
@@ -185,37 +185,15 @@ public class KubernetesService implements HealthCheck{
         return logText;
     }
 
-    // TODO: implement log watch
-    public LogWatch getLogWatch(String podName) {
-        return kubernetesClient().pods().inNamespace(getNamespace()).withName(podName).watchLog();
-    }
-//    public void startContainerLogWatch(String session, String podName) {
-//        Tuple2<CompletableFuture<Void>, LogWatch> old = logWatches.get(session);
-//        if (old != null) {
-//            LOGGER.info("Closing old");
-//            old.getItem1().cancel(true);
-//            old.getItem2().close();
-//            logWatches.remove(session);
-//            LOGGER.info("Closed old");
-//        }
-//
-//        LOGGER.info("Starting startContainerLogWatch");
-//        CompletableFuture<Void> future = managedExecutor.runAsync(() -> {
-//            LogWatch logWatch =
-//            BufferedReader reader = new BufferedReader(new InputStreamReader(logWatch.getOutput()));
-//            try {
-//                for (String line; (line = reader.readLine()) != null; ) {
-//                    eventBus.publish(session, System.lineSeparator());
-//                    eventBus.publish(session, line);
-//                    System.out.println(line);
-//                }
-//            } catch (IOException e) {
-//                LOGGER.error(e.getMessage());
-//            }
-//        });
-//        logWatches.put(session, Tuple2.of(future, logWatch));
-//        LOGGER.info("Done startContainerLogWatch");
-//    }
+    public LogWatch getContainerLogWatch(String podName) {
+        return kubernetesClient().pods().inNamespace(getNamespace()).withName(podName).tailingLines(100).watchLog();
+    }
+
+    public LogWatch getPipelineRunLogWatch(String pipelineRuneName) {
+        List<TaskRun> tasks = getTaskRuns(pipelineRuneName, getNamespace());
+        TaskRun taskRun = tasks.get(0);
+        return kubernetesClient().pods().inNamespace(getNamespace()).withName(taskRun.getStatus().getPodName()).tailingLines(100).watchLog();
+    }
 
     public String getPipelineRunLog(String pipelineRuneName, String namespace) {
         StringBuilder result = new StringBuilder();
diff --git a/karavan-app/src/main/webui/src/api/KaravanApi.tsx b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
index b456a556..5210e0de 100644
--- a/karavan-app/src/main/webui/src/api/KaravanApi.tsx
+++ b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
@@ -1,4 +1,4 @@
-import axios, {AxiosResponse, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosRequestHeaders } from "axios";
+import axios, {AxiosResponse } from "axios";
 import {
     CamelStatus,
     DeploymentStatus,
diff --git a/karavan-app/src/main/webui/src/projects/ProjectLog.tsx b/karavan-app/src/main/webui/src/projects/ProjectLog.tsx
index 2b81dda5..f9c3e64c 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectLog.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectLog.tsx
@@ -22,7 +22,8 @@ interface State {
     height?: number | string,
     logViewerRef: any,
     isTextWrapped: boolean
-    data: string[]
+    data: string,
+    currentLine: number
 }
 
 export class ProjectLog extends React.Component<Props, State> {
@@ -32,12 +33,14 @@ export class ProjectLog extends React.Component<Props, State> {
         height: "30%",
         logViewerRef: React.createRef(),
         isTextWrapped: true,
-        data: []
+        data: '',
+        currentLine: 0
     }
-
+    eventSource?: EventSource;
     sub?: Subscription;
 
     componentDidMount() {
+        this.eventSource?.close();
         this.sub = ProjectEventBus.onShowLog()?.subscribe((log: ShowLogCommand) => {
             this.setState({showLog: true, log: log});
             this.showLogs(log.type, log.name, log.environment);
@@ -45,20 +48,20 @@ export class ProjectLog extends React.Component<Props, State> {
     }
 
     componentWillUnmount() {
+        this.eventSource?.close();
         this.sub?.unsubscribe();
     }
 
     showLogs = (type: 'container' | 'pipeline', name: string, environment: string) => {
-        if (type === 'pipeline') {
-            KaravanApi.getPipelineLog(environment, name, (res: any) => {
-                this.setState({data: res});
-            });
-        } else if (type === 'container') {
-            KaravanApi.getContainerLog(environment, name, (res: any) => {
-                this.setState({data: res});
-            });
+        this.eventSource?.close();
+        this.eventSource = new EventSource("/api/logwatch/"+type+"/"+environment+"/"+name);
+        this.eventSource.onerror = (event) => {
+            this.eventSource?.close();
         }
-
+        this.eventSource.onmessage = (event) => {
+            const data = this.state.data.concat('\n').concat(event.data)
+            this.setState({data: data, currentLine: this.state.currentLine + 1});
+        };
     }
 
     getButtons() {
@@ -76,12 +79,15 @@ export class ProjectLog extends React.Component<Props, State> {
                     this.setState({height: h, showLog: true});
                 }} icon={height === "100%" ? <CollapseIcon/> : <ExpandIcon/>}/>
             </Tooltip>
-            <Button variant="plain" onClick={() => this.setState({height: "30%", showLog: false})} icon={<CloseIcon/>}/>
+            <Button variant="plain" onClick={() => {
+                this.eventSource?.close();
+                this.setState({height: "30%", showLog: false, data: '', currentLine: 0});
+            }} icon={<CloseIcon/>}/>
         </div>);
     }
 
     render() {
-        const {showLog, height, logViewerRef, isTextWrapped, data} = this.state;
+        const {showLog, height, logViewerRef, isTextWrapped, data, currentLine} = this.state;
         return (showLog ?
             <PageSection className="project-log" padding={{default: "noPadding"}} style={{height: height}}>
                 <LogViewer
@@ -92,6 +98,8 @@ export class ProjectLog extends React.Component<Props, State> {
                     header={this.getButtons()}
                     height={"100vh"}
                     data={data}
+                    scrollToRow={currentLine}
+                    overScanCount={10}
                     theme={'dark'}/>
             </PageSection>
             : <></>);


[camel-karavan] 01/04: Project logs redesign

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 1689fb5e222e4b5c7935bedc1356f74463346e1f
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Tue May 2 16:21:27 2023 -0400

    Project logs redesign
---
 karavan-app/src/main/webui/package-lock.json       | 371 ++++++++++++++-------
 karavan-app/src/main/webui/package.json            |  20 +-
 karavan-app/src/main/webui/src/index.css           |  15 +-
 .../src/main/webui/src/projects/ProjectEventBus.ts |  16 +-
 .../src/main/webui/src/projects/ProjectLog.tsx     |  67 +++-
 .../src/main/webui/src/projects/ProjectPage.tsx    |   6 +-
 6 files changed, 347 insertions(+), 148 deletions(-)

diff --git a/karavan-app/src/main/webui/package-lock.json b/karavan-app/src/main/webui/package-lock.json
index e4742afc..cefbe60a 100644
--- a/karavan-app/src/main/webui/package-lock.json
+++ b/karavan-app/src/main/webui/package-lock.json
@@ -8,16 +8,16 @@
       "name": "karavan",
       "version": "3.20.2-SNAPSHOT",
       "dependencies": {
-        "@monaco-editor/react": "4.4.6",
+        "@monaco-editor/react": "4.5.0",
         "@patternfly/patternfly": "4.224.2",
-        "@patternfly/react-charts": "6.94.18",
-        "@patternfly/react-core": "4.276.6",
-        "@patternfly/react-log-viewer": "^4.87.100",
-        "@patternfly/react-table": "4.112.39",
+        "@patternfly/react-charts": "6.94.19",
+        "@patternfly/react-core": "4.276.8",
+        "@patternfly/react-log-viewer": "5.0.0-alpha.1",
+        "@patternfly/react-table": "4.113.0",
         "@types/js-yaml": "4.0.5",
-        "@types/node": "18.11.18",
+        "@types/node": "18.16.3",
         "@types/uuid": "9.0.1",
-        "axios": "1.3.4",
+        "axios": "1.4.0",
         "buffer": "^6.0.3",
         "dagre": "0.8.5",
         "file-saver": "^2.0.5",
@@ -26,8 +26,8 @@
         "keycloak-js": "^19.0.1",
         "react": "17.0.2",
         "react-dom": "17.0.2",
-        "react-scripts": "5.0.0",
-        "rxjs": "7.8.0",
+        "react-scripts": "5.0.1",
+        "rxjs": "7.8.1",
         "uuid": "9.0.0"
       },
       "devDependencies": {
@@ -39,7 +39,7 @@
         "@typescript-eslint/eslint-plugin": "^5.51.0",
         "@typescript-eslint/parser": "^5.51.0",
         "eslint": "^8.33.0",
-        "monaco-editor": "0.36.1",
+        "monaco-editor": "0.38.0",
         "typescript": "^4.9.5"
       }
     },
@@ -2751,9 +2751,9 @@
       "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A=="
     },
     "node_modules/@monaco-editor/loader": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.3.2.tgz",
-      "integrity": "sha512-BTDbpHl3e47r3AAtpfVFTlAi7WXv4UQ/xZmz8atKl4q7epQV5e7+JbigFDViWF71VBi4IIBdcWP57Hj+OWuc9g==",
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.3.3.tgz",
+      "integrity": "sha512-6KKF4CTzcJiS8BJwtxtfyYt9shBiEv32ateQ9T4UVogwn4HM/uPo9iJd2Dmbkpz8CM6Y0PDUpjnZzCwC+eYo2Q==",
       "dependencies": {
         "state-local": "^1.0.6"
       },
@@ -2762,12 +2762,11 @@
       }
     },
     "node_modules/@monaco-editor/react": {
-      "version": "4.4.6",
-      "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.4.6.tgz",
-      "integrity": "sha512-Gr3uz3LYf33wlFE3eRnta4RxP5FSNxiIV9ENn2D2/rN8KgGAD8ecvcITRtsbbyuOuNkwbuHYxfeaz2Vr+CtyFA==",
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.5.0.tgz",
+      "integrity": "sha512-VJMkp5Fe1+w8pLEq8tZPHZKu8zDXQIA1FtiDTSNccg1D3wg1YIZaH2es2Qpvop1k62g3c/YySRb3bnGXu2XwYQ==",
       "dependencies": {
-        "@monaco-editor/loader": "^1.3.2",
-        "prop-types": "^15.7.2"
+        "@monaco-editor/loader": "^1.3.3"
       },
       "peerDependencies": {
         "monaco-editor": ">= 0.25.0 < 1",
@@ -2813,9 +2812,9 @@
       "integrity": "sha512-HGNV26uyHSIECuhjPg/WGn0mXbAotcs6ODfhAOkfYjIgGylddgiwElxUe1rpEHV5mQJJ2rMn4OdeJIIpzRX61g=="
     },
     "node_modules/@patternfly/react-charts": {
-      "version": "6.94.18",
-      "resolved": "https://registry.npmjs.org/@patternfly/react-charts/-/react-charts-6.94.18.tgz",
-      "integrity": "sha512-56WxnZYC3blRX41mW67JaPxJ3YhXViLvwGpEsZrYCccla/rTV8JgKK0gjHnqtzPQiVBfpn+3ewOyNCOR5uRoSw==",
+      "version": "6.94.19",
+      "resolved": "https://registry.npmjs.org/@patternfly/react-charts/-/react-charts-6.94.19.tgz",
+      "integrity": "sha512-+yYwXAy/GBH2bTHFzMpdVKc8LUJCCNEqqS7bqovNkNLd0m3FP3q9fKJ22QxNnP9NeFHK6UFa4KfouQAb2fInfQ==",
       "dependencies": {
         "@patternfly/react-styles": "^4.92.6",
         "@patternfly/react-tokens": "^4.94.6",
@@ -2845,9 +2844,9 @@
       }
     },
     "node_modules/@patternfly/react-core": {
-      "version": "4.276.6",
-      "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-4.276.6.tgz",
-      "integrity": "sha512-G0K+378jf9jw9g+hCAoKnsAe/UGTRspqPeuAYypF2FgNr+dC7dUpc7/VkNhZBVqSJzUWVEK8NyXcqkfi0IemIg==",
+      "version": "4.276.8",
+      "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-4.276.8.tgz",
+      "integrity": "sha512-dn322rEzBeiVztZEuCZMUUittNb8l1hk30h9ZN31FLZLLVtXGlThFNV9ieqOJYA9zrYxYZrHMkTnOxSWVacMZg==",
       "dependencies": {
         "@patternfly/react-icons": "^4.93.6",
         "@patternfly/react-styles": "^4.92.6",
@@ -2872,33 +2871,121 @@
       }
     },
     "node_modules/@patternfly/react-log-viewer": {
-      "version": "4.87.100",
-      "resolved": "https://registry.npmjs.org/@patternfly/react-log-viewer/-/react-log-viewer-4.87.100.tgz",
-      "integrity": "sha512-FG+oZDnymvtWNTSSNSQ573NCikpCKr1Iq0Fz5FOSeqW/rLxvuJPqBnlK+A4nUORvxvYc3DhBri2ycCaZMoZfPg==",
+      "version": "5.0.0-alpha.1",
+      "resolved": "https://registry.npmjs.org/@patternfly/react-log-viewer/-/react-log-viewer-5.0.0-alpha.1.tgz",
+      "integrity": "sha512-QHJCqD4muCrYuJsIf9Zs3xAlsUJk5lyyjU9aNE1e+RIFIAlJeagL9MF/bfs50/EwPnZYZtXBsFiyox+kbccW7Q==",
       "dependencies": {
-        "@patternfly/react-core": "^4.273.1",
-        "@patternfly/react-icons": "^4.93.4",
-        "@patternfly/react-styles": "^4.92.4",
+        "@patternfly/react-core": "^5.0.0-alpha.50",
+        "@patternfly/react-icons": "^5.0.0-alpha.7",
+        "@patternfly/react-styles": "^5.0.0-alpha.5",
         "memoize-one": "^5.1.0",
-        "resize-observer-polyfill": "^1.5.1",
-        "tslib": "^2.0.0"
+        "monaco-editor": "^0.33.0"
       },
       "peerDependencies": {
         "react": "^16.8 || ^17 || ^18",
         "react-dom": "^16.8 || ^17 || ^18"
       }
     },
+    "node_modules/@patternfly/react-log-viewer/node_modules/@patternfly/react-core": {
+      "version": "5.0.0-alpha.85",
+      "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-5.0.0-alpha.85.tgz",
+      "integrity": "sha512-wK/NvwLxFYIMyMSNDwFiUARoIPgrE8t0Rs2Fyx9zDnyMMUijSMxZ3s0qb/YcxOyaOdUamMSWxVQtaPbYc7+V9A==",
+      "dependencies": {
+        "@patternfly/react-icons": "^5.0.0-alpha.12",
+        "@patternfly/react-styles": "^5.0.0-alpha.8",
+        "@patternfly/react-tokens": "^5.0.0-alpha.7",
+        "focus-trap": "7.4.0",
+        "react-dropzone": "^14.2.3",
+        "tslib": "^2.5.0"
+      },
+      "peerDependencies": {
+        "react": "^17 || ^18",
+        "react-dom": "^17 || ^18"
+      }
+    },
+    "node_modules/@patternfly/react-log-viewer/node_modules/@patternfly/react-icons": {
+      "version": "5.0.0-alpha.12",
+      "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-5.0.0-alpha.12.tgz",
+      "integrity": "sha512-VwCGlB+JtpsjHJtqCcfy+CpaE+rAY2si7cd4ufJouybkoBPve/6wuTPA3K3eGtgn5cAIr18IF1Pkfkp2lR18sg==",
+      "peerDependencies": {
+        "react": "^17 || ^18",
+        "react-dom": "^17 || ^18"
+      }
+    },
+    "node_modules/@patternfly/react-log-viewer/node_modules/@patternfly/react-styles": {
+      "version": "5.0.0-alpha.8",
+      "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-5.0.0-alpha.8.tgz",
+      "integrity": "sha512-V/jhoKp6pz3c+2N1Yi/Ez/EJIHKEHe4SSCzF5ocGpOfaYlQpDGdttKIF0GSWksXlShYZJ5heKfARuESK9XjqRA=="
+    },
+    "node_modules/@patternfly/react-log-viewer/node_modules/@patternfly/react-tokens": {
+      "version": "5.0.0-alpha.7",
+      "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-5.0.0-alpha.7.tgz",
+      "integrity": "sha512-w22cIlzzD7U40Ut8VDqjU1RyOmdwn9Z10qiUSZCzEJY/D5Vwo9bDXbHX23bBUr2rCJUkAwWNaAhWquHFBiSK4w=="
+    },
+    "node_modules/@patternfly/react-log-viewer/node_modules/attr-accept": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
+      "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/@patternfly/react-log-viewer/node_modules/file-selector": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
+      "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
+      "dependencies": {
+        "tslib": "^2.4.0"
+      },
+      "engines": {
+        "node": ">= 12"
+      }
+    },
+    "node_modules/@patternfly/react-log-viewer/node_modules/focus-trap": {
+      "version": "7.4.0",
+      "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.4.0.tgz",
+      "integrity": "sha512-yI7FwUqU4TVb+7t6PaQ3spT/42r/KLEi8mtdGoQo2li/kFzmu9URmalTvw7xCCJtSOyhBxscvEAmvjeN9iHARg==",
+      "dependencies": {
+        "tabbable": "^6.1.1"
+      }
+    },
+    "node_modules/@patternfly/react-log-viewer/node_modules/monaco-editor": {
+      "version": "0.33.0",
+      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.33.0.tgz",
+      "integrity": "sha512-VcRWPSLIUEgQJQIE0pVT8FcGBIgFoxz7jtqctE+IiCxWugD0DwgyQBcZBhdSrdMC84eumoqMZsGl2GTreOzwqw=="
+    },
+    "node_modules/@patternfly/react-log-viewer/node_modules/react-dropzone": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz",
+      "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==",
+      "dependencies": {
+        "attr-accept": "^2.2.2",
+        "file-selector": "^0.6.0",
+        "prop-types": "^15.8.1"
+      },
+      "engines": {
+        "node": ">= 10.13"
+      },
+      "peerDependencies": {
+        "react": ">= 16.8 || 18.0.0"
+      }
+    },
+    "node_modules/@patternfly/react-log-viewer/node_modules/tabbable": {
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.1.1.tgz",
+      "integrity": "sha512-4kl5w+nCB44EVRdO0g/UGoOp3vlwgycUVtkk/7DPyeLZUCuNFFKCFG6/t/DgHLrUPHjrZg6s5tNm+56Q2B0xyg=="
+    },
     "node_modules/@patternfly/react-styles": {
       "version": "4.92.6",
       "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-4.92.6.tgz",
       "integrity": "sha512-b8uQdEReMyeoMzjpMri845QxqtupY/tIFFYfVeKoB2neno8gkcW1RvDdDe62LF88q45OktCwAe/8A99ker10Iw=="
     },
     "node_modules/@patternfly/react-table": {
-      "version": "4.112.39",
-      "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-4.112.39.tgz",
-      "integrity": "sha512-U+hOMgYlbghGH4M5MX+qt0GkVi/ocrGnxDnm11YiS3CtEGsd6Rr0NeqMmk0uoR46Od4Pr5tKuXxZhPP32sCL/w==",
+      "version": "4.113.0",
+      "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-4.113.0.tgz",
+      "integrity": "sha512-qxa3NWCdYasqQQL1rqEd8DyNa8oWr6HNveNW5YJRakE7imWZhUPG2Nd6Op60+KYX8kbCSl7gwSmgAZAYMBMZkQ==",
       "dependencies": {
-        "@patternfly/react-core": "^4.276.6",
+        "@patternfly/react-core": "^4.276.8",
         "@patternfly/react-icons": "^4.93.6",
         "@patternfly/react-styles": "^4.92.6",
         "@patternfly/react-tokens": "^4.94.6",
@@ -3555,9 +3642,9 @@
       "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
     },
     "node_modules/@types/node": {
-      "version": "18.11.18",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
-      "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA=="
+      "version": "18.16.3",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz",
+      "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q=="
     },
     "node_modules/@types/parse-json": {
       "version": "4.0.0",
@@ -4748,9 +4835,9 @@
       }
     },
     "node_modules/axios": {
-      "version": "1.3.4",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
-      "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
+      "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
       "dependencies": {
         "follow-redirects": "^1.15.0",
         "form-data": "^4.0.0",
@@ -11673,9 +11760,9 @@
       }
     },
     "node_modules/monaco-editor": {
-      "version": "0.36.1",
-      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.36.1.tgz",
-      "integrity": "sha512-/CaclMHKQ3A6rnzBzOADfwdSJ25BFoFT0Emxsc4zYVyav5SkK9iA6lEtIeuN/oRYbwPgviJT+t3l+sjFa28jYg=="
+      "version": "0.38.0",
+      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.38.0.tgz",
+      "integrity": "sha512-11Fkh6yzEmwx7O0YoLxeae0qEGFwmyPRlVxpg7oF9czOOCB/iCjdJrG5I67da5WiXK3YJCxoz9TJFE8Tfq/v9A=="
     },
     "node_modules/ms": {
       "version": "2.1.2",
@@ -13823,9 +13910,9 @@
       }
     },
     "node_modules/react-scripts": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz",
-      "integrity": "sha512-3i0L2CyIlROz7mxETEdfif6Sfhh9Lfpzi10CtcGs1emDQStmZfWjJbAIMtRD0opVUjQuFWqHZyRZ9PPzKCFxWg==",
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
+      "integrity": "sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==",
       "dependencies": {
         "@babel/core": "^7.16.0",
         "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",
@@ -13843,7 +13930,7 @@
         "dotenv": "^10.0.0",
         "dotenv-expand": "^5.1.0",
         "eslint": "^8.3.0",
-        "eslint-config-react-app": "^7.0.0",
+        "eslint-config-react-app": "^7.0.1",
         "eslint-webpack-plugin": "^3.1.1",
         "file-loader": "^6.2.0",
         "fs-extra": "^10.0.0",
@@ -13860,7 +13947,7 @@
         "postcss-preset-env": "^7.0.1",
         "prompts": "^2.4.2",
         "react-app-polyfill": "^3.0.0",
-        "react-dev-utils": "^12.0.0",
+        "react-dev-utils": "^12.0.1",
         "react-refresh": "^0.11.0",
         "resolve": "^1.20.0",
         "resolve-url-loader": "^4.0.0",
@@ -14070,11 +14157,6 @@
       "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
       "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
     },
-    "node_modules/resize-observer-polyfill": {
-      "version": "1.5.1",
-      "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
-      "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
-    },
     "node_modules/resolve": {
       "version": "1.22.0",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
@@ -14296,9 +14378,9 @@
       }
     },
     "node_modules/rxjs": {
-      "version": "7.8.0",
-      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz",
-      "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==",
+      "version": "7.8.1",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+      "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
       "dependencies": {
         "tslib": "^2.1.0"
       }
@@ -15483,9 +15565,9 @@
       }
     },
     "node_modules/tslib": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
-      "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
+      "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
     },
     "node_modules/tsutils": {
       "version": "3.21.0",
@@ -18809,20 +18891,19 @@
       "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A=="
     },
     "@monaco-editor/loader": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.3.2.tgz",
-      "integrity": "sha512-BTDbpHl3e47r3AAtpfVFTlAi7WXv4UQ/xZmz8atKl4q7epQV5e7+JbigFDViWF71VBi4IIBdcWP57Hj+OWuc9g==",
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.3.3.tgz",
+      "integrity": "sha512-6KKF4CTzcJiS8BJwtxtfyYt9shBiEv32ateQ9T4UVogwn4HM/uPo9iJd2Dmbkpz8CM6Y0PDUpjnZzCwC+eYo2Q==",
       "requires": {
         "state-local": "^1.0.6"
       }
     },
     "@monaco-editor/react": {
-      "version": "4.4.6",
-      "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.4.6.tgz",
-      "integrity": "sha512-Gr3uz3LYf33wlFE3eRnta4RxP5FSNxiIV9ENn2D2/rN8KgGAD8ecvcITRtsbbyuOuNkwbuHYxfeaz2Vr+CtyFA==",
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.5.0.tgz",
+      "integrity": "sha512-VJMkp5Fe1+w8pLEq8tZPHZKu8zDXQIA1FtiDTSNccg1D3wg1YIZaH2es2Qpvop1k62g3c/YySRb3bnGXu2XwYQ==",
       "requires": {
-        "@monaco-editor/loader": "^1.3.2",
-        "prop-types": "^15.7.2"
+        "@monaco-editor/loader": "^1.3.3"
       }
     },
     "@nodelib/fs.scandir": {
@@ -18854,9 +18935,9 @@
       "integrity": "sha512-HGNV26uyHSIECuhjPg/WGn0mXbAotcs6ODfhAOkfYjIgGylddgiwElxUe1rpEHV5mQJJ2rMn4OdeJIIpzRX61g=="
     },
     "@patternfly/react-charts": {
-      "version": "6.94.18",
-      "resolved": "https://registry.npmjs.org/@patternfly/react-charts/-/react-charts-6.94.18.tgz",
-      "integrity": "sha512-56WxnZYC3blRX41mW67JaPxJ3YhXViLvwGpEsZrYCccla/rTV8JgKK0gjHnqtzPQiVBfpn+3ewOyNCOR5uRoSw==",
+      "version": "6.94.19",
+      "resolved": "https://registry.npmjs.org/@patternfly/react-charts/-/react-charts-6.94.19.tgz",
+      "integrity": "sha512-+yYwXAy/GBH2bTHFzMpdVKc8LUJCCNEqqS7bqovNkNLd0m3FP3q9fKJ22QxNnP9NeFHK6UFa4KfouQAb2fInfQ==",
       "requires": {
         "@patternfly/react-styles": "^4.92.6",
         "@patternfly/react-tokens": "^4.94.6",
@@ -18882,9 +18963,9 @@
       }
     },
     "@patternfly/react-core": {
-      "version": "4.276.6",
-      "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-4.276.6.tgz",
-      "integrity": "sha512-G0K+378jf9jw9g+hCAoKnsAe/UGTRspqPeuAYypF2FgNr+dC7dUpc7/VkNhZBVqSJzUWVEK8NyXcqkfi0IemIg==",
+      "version": "4.276.8",
+      "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-4.276.8.tgz",
+      "integrity": "sha512-dn322rEzBeiVztZEuCZMUUittNb8l1hk30h9ZN31FLZLLVtXGlThFNV9ieqOJYA9zrYxYZrHMkTnOxSWVacMZg==",
       "requires": {
         "@patternfly/react-icons": "^4.93.6",
         "@patternfly/react-styles": "^4.92.6",
@@ -18902,16 +18983,87 @@
       "requires": {}
     },
     "@patternfly/react-log-viewer": {
-      "version": "4.87.100",
-      "resolved": "https://registry.npmjs.org/@patternfly/react-log-viewer/-/react-log-viewer-4.87.100.tgz",
-      "integrity": "sha512-FG+oZDnymvtWNTSSNSQ573NCikpCKr1Iq0Fz5FOSeqW/rLxvuJPqBnlK+A4nUORvxvYc3DhBri2ycCaZMoZfPg==",
+      "version": "5.0.0-alpha.1",
+      "resolved": "https://registry.npmjs.org/@patternfly/react-log-viewer/-/react-log-viewer-5.0.0-alpha.1.tgz",
+      "integrity": "sha512-QHJCqD4muCrYuJsIf9Zs3xAlsUJk5lyyjU9aNE1e+RIFIAlJeagL9MF/bfs50/EwPnZYZtXBsFiyox+kbccW7Q==",
       "requires": {
-        "@patternfly/react-core": "^4.273.1",
-        "@patternfly/react-icons": "^4.93.4",
-        "@patternfly/react-styles": "^4.92.4",
+        "@patternfly/react-core": "^5.0.0-alpha.50",
+        "@patternfly/react-icons": "^5.0.0-alpha.7",
+        "@patternfly/react-styles": "^5.0.0-alpha.5",
         "memoize-one": "^5.1.0",
-        "resize-observer-polyfill": "^1.5.1",
-        "tslib": "^2.0.0"
+        "monaco-editor": "^0.33.0"
+      },
+      "dependencies": {
+        "@patternfly/react-core": {
+          "version": "5.0.0-alpha.85",
+          "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-5.0.0-alpha.85.tgz",
+          "integrity": "sha512-wK/NvwLxFYIMyMSNDwFiUARoIPgrE8t0Rs2Fyx9zDnyMMUijSMxZ3s0qb/YcxOyaOdUamMSWxVQtaPbYc7+V9A==",
+          "requires": {
+            "@patternfly/react-icons": "^5.0.0-alpha.12",
+            "@patternfly/react-styles": "^5.0.0-alpha.8",
+            "@patternfly/react-tokens": "^5.0.0-alpha.7",
+            "focus-trap": "7.4.0",
+            "react-dropzone": "^14.2.3",
+            "tslib": "^2.5.0"
+          }
+        },
+        "@patternfly/react-icons": {
+          "version": "5.0.0-alpha.12",
+          "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-5.0.0-alpha.12.tgz",
+          "integrity": "sha512-VwCGlB+JtpsjHJtqCcfy+CpaE+rAY2si7cd4ufJouybkoBPve/6wuTPA3K3eGtgn5cAIr18IF1Pkfkp2lR18sg==",
+          "requires": {}
+        },
+        "@patternfly/react-styles": {
+          "version": "5.0.0-alpha.8",
+          "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-5.0.0-alpha.8.tgz",
+          "integrity": "sha512-V/jhoKp6pz3c+2N1Yi/Ez/EJIHKEHe4SSCzF5ocGpOfaYlQpDGdttKIF0GSWksXlShYZJ5heKfARuESK9XjqRA=="
+        },
+        "@patternfly/react-tokens": {
+          "version": "5.0.0-alpha.7",
+          "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-5.0.0-alpha.7.tgz",
+          "integrity": "sha512-w22cIlzzD7U40Ut8VDqjU1RyOmdwn9Z10qiUSZCzEJY/D5Vwo9bDXbHX23bBUr2rCJUkAwWNaAhWquHFBiSK4w=="
+        },
+        "attr-accept": {
+          "version": "2.2.2",
+          "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
+          "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg=="
+        },
+        "file-selector": {
+          "version": "0.6.0",
+          "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
+          "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
+          "requires": {
+            "tslib": "^2.4.0"
+          }
+        },
+        "focus-trap": {
+          "version": "7.4.0",
+          "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.4.0.tgz",
+          "integrity": "sha512-yI7FwUqU4TVb+7t6PaQ3spT/42r/KLEi8mtdGoQo2li/kFzmu9URmalTvw7xCCJtSOyhBxscvEAmvjeN9iHARg==",
+          "requires": {
+            "tabbable": "^6.1.1"
+          }
+        },
+        "monaco-editor": {
+          "version": "0.33.0",
+          "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.33.0.tgz",
+          "integrity": "sha512-VcRWPSLIUEgQJQIE0pVT8FcGBIgFoxz7jtqctE+IiCxWugD0DwgyQBcZBhdSrdMC84eumoqMZsGl2GTreOzwqw=="
+        },
+        "react-dropzone": {
+          "version": "14.2.3",
+          "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz",
+          "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==",
+          "requires": {
+            "attr-accept": "^2.2.2",
+            "file-selector": "^0.6.0",
+            "prop-types": "^15.8.1"
+          }
+        },
+        "tabbable": {
+          "version": "6.1.1",
+          "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.1.1.tgz",
+          "integrity": "sha512-4kl5w+nCB44EVRdO0g/UGoOp3vlwgycUVtkk/7DPyeLZUCuNFFKCFG6/t/DgHLrUPHjrZg6s5tNm+56Q2B0xyg=="
+        }
       }
     },
     "@patternfly/react-styles": {
@@ -18920,11 +19072,11 @@
       "integrity": "sha512-b8uQdEReMyeoMzjpMri845QxqtupY/tIFFYfVeKoB2neno8gkcW1RvDdDe62LF88q45OktCwAe/8A99ker10Iw=="
     },
     "@patternfly/react-table": {
-      "version": "4.112.39",
-      "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-4.112.39.tgz",
-      "integrity": "sha512-U+hOMgYlbghGH4M5MX+qt0GkVi/ocrGnxDnm11YiS3CtEGsd6Rr0NeqMmk0uoR46Od4Pr5tKuXxZhPP32sCL/w==",
+      "version": "4.113.0",
+      "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-4.113.0.tgz",
+      "integrity": "sha512-qxa3NWCdYasqQQL1rqEd8DyNa8oWr6HNveNW5YJRakE7imWZhUPG2Nd6Op60+KYX8kbCSl7gwSmgAZAYMBMZkQ==",
       "requires": {
-        "@patternfly/react-core": "^4.276.6",
+        "@patternfly/react-core": "^4.276.8",
         "@patternfly/react-icons": "^4.93.6",
         "@patternfly/react-styles": "^4.92.6",
         "@patternfly/react-tokens": "^4.94.6",
@@ -19412,9 +19564,9 @@
       "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
     },
     "@types/node": {
-      "version": "18.11.18",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
-      "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA=="
+      "version": "18.16.3",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz",
+      "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q=="
     },
     "@types/parse-json": {
       "version": "4.0.0",
@@ -20261,9 +20413,9 @@
       "integrity": "sha512-btWy2rze3NnxSSxb7LtNhPYYFrRoFBfjiGzmSc/5Hu47wApO2KNXjP/w7Nv2Uz/Fyr/pfEiwOkcXhDxu0jz5FA=="
     },
     "axios": {
-      "version": "1.3.4",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
-      "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
+      "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
       "requires": {
         "follow-redirects": "^1.15.0",
         "form-data": "^4.0.0",
@@ -25293,9 +25445,9 @@
       }
     },
     "monaco-editor": {
-      "version": "0.36.1",
-      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.36.1.tgz",
-      "integrity": "sha512-/CaclMHKQ3A6rnzBzOADfwdSJ25BFoFT0Emxsc4zYVyav5SkK9iA6lEtIeuN/oRYbwPgviJT+t3l+sjFa28jYg=="
+      "version": "0.38.0",
+      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.38.0.tgz",
+      "integrity": "sha512-11Fkh6yzEmwx7O0YoLxeae0qEGFwmyPRlVxpg7oF9czOOCB/iCjdJrG5I67da5WiXK3YJCxoz9TJFE8Tfq/v9A=="
     },
     "ms": {
       "version": "2.1.2",
@@ -26720,9 +26872,9 @@
       "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
     },
     "react-scripts": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz",
-      "integrity": "sha512-3i0L2CyIlROz7mxETEdfif6Sfhh9Lfpzi10CtcGs1emDQStmZfWjJbAIMtRD0opVUjQuFWqHZyRZ9PPzKCFxWg==",
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
+      "integrity": "sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==",
       "requires": {
         "@babel/core": "^7.16.0",
         "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",
@@ -26740,7 +26892,7 @@
         "dotenv": "^10.0.0",
         "dotenv-expand": "^5.1.0",
         "eslint": "^8.3.0",
-        "eslint-config-react-app": "^7.0.0",
+        "eslint-config-react-app": "^7.0.1",
         "eslint-webpack-plugin": "^3.1.1",
         "file-loader": "^6.2.0",
         "fs-extra": "^10.0.0",
@@ -26758,7 +26910,7 @@
         "postcss-preset-env": "^7.0.1",
         "prompts": "^2.4.2",
         "react-app-polyfill": "^3.0.0",
-        "react-dev-utils": "^12.0.0",
+        "react-dev-utils": "^12.0.1",
         "react-refresh": "^0.11.0",
         "resolve": "^1.20.0",
         "resolve-url-loader": "^4.0.0",
@@ -26910,11 +27062,6 @@
       "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
       "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
     },
-    "resize-observer-polyfill": {
-      "version": "1.5.1",
-      "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
-      "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
-    },
     "resolve": {
       "version": "1.22.0",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
@@ -27055,9 +27202,9 @@
       }
     },
     "rxjs": {
-      "version": "7.8.0",
-      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz",
-      "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==",
+      "version": "7.8.1",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+      "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
       "requires": {
         "tslib": "^2.1.0"
       }
@@ -27960,9 +28107,9 @@
       }
     },
     "tslib": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
-      "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
+      "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
     },
     "tsutils": {
       "version": "3.21.0",
diff --git a/karavan-app/src/main/webui/package.json b/karavan-app/src/main/webui/package.json
index 5e9c238c..638c27ba 100644
--- a/karavan-app/src/main/webui/package.json
+++ b/karavan-app/src/main/webui/package.json
@@ -26,16 +26,16 @@
     ]
   },
   "dependencies": {
-    "@monaco-editor/react": "4.4.6",
+    "@monaco-editor/react": "4.5.0",
     "@patternfly/patternfly": "4.224.2",
-    "@patternfly/react-charts": "6.94.18",
-    "@patternfly/react-core": "4.276.6",
-    "@patternfly/react-log-viewer": "^4.87.100",
-    "@patternfly/react-table": "4.112.39",
+    "@patternfly/react-charts": "6.94.19",
+    "@patternfly/react-core": "4.276.8",
+    "@patternfly/react-log-viewer": "5.0.0-alpha.1",
+    "@patternfly/react-table": "4.113.0",
     "@types/js-yaml": "4.0.5",
-    "@types/node": "18.11.18",
+    "@types/node": "18.16.3",
     "@types/uuid": "9.0.1",
-    "axios": "1.3.4",
+    "axios": "1.4.0",
     "buffer": "^6.0.3",
     "dagre": "0.8.5",
     "file-saver": "^2.0.5",
@@ -44,8 +44,8 @@
     "keycloak-js": "^19.0.1",
     "react": "17.0.2",
     "react-dom": "17.0.2",
-    "react-scripts": "5.0.0",
-    "rxjs": "7.8.0",
+    "react-scripts": "5.0.1",
+    "rxjs": "7.8.1",
     "uuid": "9.0.0"
   },
   "devDependencies": {
@@ -57,7 +57,7 @@
     "@typescript-eslint/eslint-plugin": "^5.51.0",
     "@typescript-eslint/parser": "^5.51.0",
     "eslint": "^8.33.0",
-    "monaco-editor": "0.36.1",
+    "monaco-editor": "0.38.0",
     "typescript": "^4.9.5"
   }
 }
diff --git a/karavan-app/src/main/webui/src/index.css b/karavan-app/src/main/webui/src/index.css
index 55de584e..f947f243 100644
--- a/karavan-app/src/main/webui/src/index.css
+++ b/karavan-app/src/main/webui/src/index.css
@@ -156,6 +156,7 @@
   bottom: 0;
   right: 0;
   width: 100%;
+  z-index: 200;
 }
 
 .karavan .project-page .project-log .buttons {
@@ -166,10 +167,22 @@
   padding-right: 6px;
 }
 
-.karavan .project-page .project-log .buttons button {
+.karavan .project-page .project-log .buttons button,
+.karavan .project-page .project-log .buttons .pf-c-check {
   padding: 8px;
 }
 
+.karavan .project-page .project-log .buttons .pf-c-check .pf-c-check__label{
+  font-size: 12px;
+  line-height: 20px;
+  padding: 0;
+}
+
+.karavan .project-page .project-log .pf-c-log-viewer__scroll-container {
+  height: 100% !important;
+}
+
+
 .karavan .project-page .project-log .pf-c-log-viewer__text {
   font-size: 11px;
 }
diff --git a/karavan-app/src/main/webui/src/projects/ProjectEventBus.ts b/karavan-app/src/main/webui/src/projects/ProjectEventBus.ts
index e36a0d5e..8f6399c3 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectEventBus.ts
+++ b/karavan-app/src/main/webui/src/projects/ProjectEventBus.ts
@@ -19,7 +19,19 @@ import {Project} from "./ProjectModels";
 
 const currentProject = new Subject<Project>();
 const currentFile = new Subject<string>();
-const showLog = new Subject<string>();
+const showLog = new Subject<ShowLogCommand>();
+
+export class ShowLogCommand {
+    type: 'container' | 'pipeline'
+    name: string
+    environment: string
+
+    constructor(type: "container" | "pipeline", name: string, environment: string) {
+        this.type = type;
+        this.name = name;
+        this.environment = environment;
+    }
+}
 
 
 export const ProjectEventBus = {
@@ -30,6 +42,6 @@ export const ProjectEventBus = {
     selectProjectFile: (fileName: string) => currentFile.next(fileName),
     onSelectProjectFile: () => currentFile.asObservable(),
 
-    showLog: (type: 'container' | 'pipeline', name: string, environment: string) => showLog.next(name),
+    showLog: (type: 'container' | 'pipeline', name: string, environment: string) => showLog.next(new ShowLogCommand(type, name, environment)),
     onShowLog: () => showLog.asObservable(),
 }
diff --git a/karavan-app/src/main/webui/src/projects/ProjectLog.tsx b/karavan-app/src/main/webui/src/projects/ProjectLog.tsx
index c96f676e..1646663d 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectLog.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectLog.tsx
@@ -1,16 +1,14 @@
 import React from 'react';
-import {
-    CodeBlockAction, CodeBlockCode, CodeBlock, Button, Skeleton, Banner, Divider
-} from '@patternfly/react-core';
+import {Button, Checkbox, PageSection, Tooltip, TooltipPosition} from '@patternfly/react-core';
 import '../designer/karavan.css';
 import CloseIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
 import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/expand-icon';
+import ScrollIcon from '@patternfly/react-icons/dist/esm/icons/scroll-icon';
+import CollapseIcon from '@patternfly/react-icons/dist/esm/icons/compress-icon';
 import {LogViewer} from '@patternfly/react-log-viewer';
 import {Subscription} from "rxjs";
-import {ProjectEventBus} from "./ProjectEventBus";
-import {Project} from "./ProjectModels";
-import {KaravanApi} from "../api/KaravanApi";
-import {SsoApi} from "../api/SsoApi";
+import {ProjectEventBus, ShowLogCommand} from "./ProjectEventBus";
+import {findDOMNode} from "react-dom";
 
 interface Props {
 
@@ -18,18 +16,24 @@ interface Props {
 
 interface State {
     showLog: boolean,
+    height?: number | string,
+    logViewerRef: any,
+    isTextWrapped: boolean
 }
 
 export class ProjectLog extends React.Component<Props, State> {
 
     public state: State = {
-        showLog: false
+        showLog: false,
+        height: "30%",
+        logViewerRef: React.createRef(),
+        isTextWrapped: false
     }
 
     sub?: Subscription;
 
     componentDidMount() {
-        this.sub = ProjectEventBus.onShowLog()?.subscribe((logName: String) => {
+        this.sub = ProjectEventBus.onShowLog()?.subscribe((log: ShowLogCommand) => {
             this.setState({showLog: true});
         });
     }
@@ -38,6 +42,14 @@ export class ProjectLog extends React.Component<Props, State> {
         this.sub?.unsubscribe();
     }
 
+    componentDidUpdate = (prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) => {
+        if (this.state.height === "100%" && prevState.height !== "100%") {
+            const element = findDOMNode(this.state.logViewerRef.current)
+            console.log("change", element)
+            console.log("change", this.state.logViewerRef.current)
+        }
+    }
+
     code = "apiVersion: helm.openshift.io/v1beta1/\n" +
         "kind: HelmChartRepository\n" +
         "metadata:\n" +
@@ -113,19 +125,38 @@ export class ProjectLog extends React.Component<Props, State> {
         "url: https://raw.githubusercontent.com/Azure-Samples/helm-charts/master/docs"
 
     getButtons() {
+        const {height, isTextWrapped, logViewerRef} = this.state;
         return (<div className="buttons">
-            <Button variant="plain" onClick={event => {
-            }} icon={<ExpandIcon/>}/>
-            <Button variant="plain" onClick={() =>this.setState({showLog: false})} icon={<CloseIcon/>}/>
+            <Checkbox label="Wrap text" aria-label="wrap text checkbox" isChecked={isTextWrapped} id="wrap-text-checkbox"
+                      onChange={checked => this.setState({isTextWrapped: checked})} />
+            <Tooltip content={"Scroll to bottom"} position={TooltipPosition.bottom}>
+                <Button variant="plain" onClick={() => logViewerRef.current.scrollToBottom()} icon={<ScrollIcon/>}/>
+            </Tooltip>
+            <Tooltip content={height === "100%" ? "Collapse": "Expand"} position={TooltipPosition.bottom}>
+                <Button variant="plain" onClick={() => {
+                    const h = height === "100%" ? "30%" : "100%";
+                    this.setState({height: h, showLog: true});
+                }} icon={height === "100%" ? <CollapseIcon/> : <ExpandIcon/>}/>
+            </Tooltip>
+            <Button variant="plain" onClick={() => this.setState({height: "30%", showLog: false})} icon={<CloseIcon/>}/>
         </div>);
     }
 
     render() {
-        const {showLog} = this.state;
-        return (showLog ? <LogViewer hasLineNumbers={false}
-                           header={this.getButtons()}
-                           height={300}
-                           data={this.code}
-                           theme={'dark'}/> : <></>);
+        const {showLog, height, logViewerRef, isTextWrapped} = this.state;
+        console.log(this.state)
+        return (showLog ?
+            <PageSection className="project-log" padding={{default: "noPadding"}} style={{height: height}}>
+                <LogViewer
+                    isTextWrapped={isTextWrapped}
+                    innerRef={logViewerRef}
+                    hasLineNumbers={false}
+                    loadingContent={"Loading..."}
+                    header={this.getButtons()}
+                    height={"100vh"}
+                    data={this.code.concat(this.code).concat(this.code).concat(this.code)}
+                    theme={'dark'}/>
+            </PageSection>
+            : <></>);
     }
 }
diff --git a/karavan-app/src/main/webui/src/projects/ProjectPage.tsx b/karavan-app/src/main/webui/src/projects/ProjectPage.tsx
index 199233c0..bc376c72 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectPage.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectPage.tsx
@@ -435,11 +435,7 @@ export class ProjectPage extends React.Component<Props, State> {
         const {tab, files} = this.state;
         const {project} = this.props;
         const isBuildIn = this.isBuildIn();
-        return (
-            <PageSection className="project-log" padding={{default: "noPadding"}}>
-                <ProjectLog/>
-            </PageSection>
-        )
+        return (<ProjectLog/>)
     }
 
     getFilePanel() {


[camel-karavan] 03/04: SSE backend api for containers #563

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 9dd129045b449e58c0fab1aa0308e128dc3a009d
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Tue May 2 21:07:05 2023 -0400

    SSE backend api for containers #563
---
 .../camel/karavan/api/KubernetesResource.java      |  8 +-
 .../apache/camel/karavan/api/LogWatchResource.java | 85 +++++++++++++++++++++
 .../apache/camel/karavan/model/PipelineRunLog.java | 27 -------
 .../camel/karavan/service/KubernetesService.java   | 89 ++++++++++------------
 .../src/main/webui/src/projects/ProjectLog.tsx     |  6 +-
 5 files changed, 130 insertions(+), 85 deletions(-)

diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/api/KubernetesResource.java b/karavan-app/src/main/java/org/apache/camel/karavan/api/KubernetesResource.java
index 60733747..fee4841c 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/api/KubernetesResource.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/KubernetesResource.java
@@ -29,13 +29,7 @@ import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.jboss.logging.Logger;
 
 import javax.inject.Inject;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
+import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import java.util.Comparator;
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/api/LogWatchResource.java b/karavan-app/src/main/java/org/apache/camel/karavan/api/LogWatchResource.java
new file mode 100644
index 00000000..5fa14161
--- /dev/null
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/LogWatchResource.java
@@ -0,0 +1,85 @@
+/*
+ * 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.api;
+
+import io.fabric8.kubernetes.client.dsl.LogWatch;
+import io.smallrye.mutiny.tuples.Tuple2;
+import org.apache.camel.karavan.service.KubernetesService;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.eclipse.microprofile.context.ManagedExecutor;
+import org.jboss.logging.Logger;
+
+import javax.inject.Inject;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.sse.Sse;
+import javax.ws.rs.sse.SseEventSink;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Date;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@Path("/api/logwatch")
+public class LogWatchResource {
+
+    private static final Logger LOGGER = Logger.getLogger(LogWatchResource.class.getName());
+    private static final ConcurrentHashMap<String, LogWatch> logWatches = new ConcurrentHashMap<>();
+
+    @Inject
+    KubernetesService kubernetesService;
+
+    @ConfigProperty(name = "karavan.environment")
+    String environment;
+
+
+    @Inject
+    ManagedExecutor managedExecutor;
+
+    @GET
+    @Produces(MediaType.SERVER_SENT_EVENTS)
+    @Path("/{type}/{env}/{name}")
+    public void eventSourcing(@PathParam("env") String env,
+                                       @PathParam("type") String type,
+                                       @PathParam("name") String name,
+                                       @Context SseEventSink eventSink,
+                                       @Context Sse sse
+                                       ) {
+        managedExecutor.execute(() -> {
+            try (SseEventSink sink = eventSink) {
+                LogWatch logWatch = kubernetesService.getLogWatch(name);
+                BufferedReader reader = new BufferedReader(new InputStreamReader(logWatch.getOutput()));
+                try {
+                    for (String line; (line = reader.readLine()) != null && !sink.isClosed(); ) {
+                        sink.send(sse.newEvent(line + System.lineSeparator()));
+                    }
+                } catch (IOException e) {
+                    LOGGER.error(e.getMessage());
+                }
+                if (sink.isClosed()) {
+                    logWatch.close();
+                    LOGGER.info("LogWatch for " + name + " closed");
+                }
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/model/PipelineRunLog.java b/karavan-app/src/main/java/org/apache/camel/karavan/model/PipelineRunLog.java
deleted file mode 100644
index 307a12e5..00000000
--- a/karavan-app/src/main/java/org/apache/camel/karavan/model/PipelineRunLog.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.apache.camel.karavan.model;
-
-public class PipelineRunLog {
-    private String task;
-    private String log;
-
-    public PipelineRunLog(String task, String log) {
-        this.task = task;
-        this.log = log;
-    }
-
-    public String getTask() {
-        return task;
-    }
-
-    public void setTask(String task) {
-        this.task = task;
-    }
-
-    public String getLog() {
-        return log;
-    }
-
-    public void setLog(String log) {
-        this.log = log;
-    }
-}
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java b/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java
index 165e800b..13b4484c 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java
@@ -17,11 +17,7 @@
 package org.apache.camel.karavan.service;
 
 import io.fabric8.knative.internal.pkg.apis.Condition;
-import io.fabric8.kubernetes.api.model.ObjectMeta;
-import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
-import io.fabric8.kubernetes.api.model.Pod;
-import io.fabric8.kubernetes.api.model.Secret;
-import io.fabric8.kubernetes.api.model.Service;
+import io.fabric8.kubernetes.api.model.*;
 import io.fabric8.kubernetes.api.model.apps.Deployment;
 import io.fabric8.kubernetes.client.DefaultKubernetesClient;
 import io.fabric8.kubernetes.client.KubernetesClient;
@@ -30,23 +26,14 @@ import io.fabric8.kubernetes.client.informers.SharedIndexInformer;
 import io.fabric8.openshift.api.model.ImageStream;
 import io.fabric8.openshift.client.OpenShiftClient;
 import io.fabric8.tekton.client.DefaultTektonClient;
-import io.fabric8.tekton.pipeline.v1beta1.ParamBuilder;
-import io.fabric8.tekton.pipeline.v1beta1.PipelineRef;
-import io.fabric8.tekton.pipeline.v1beta1.PipelineRefBuilder;
-import io.fabric8.tekton.pipeline.v1beta1.PipelineRun;
-import io.fabric8.tekton.pipeline.v1beta1.PipelineRunBuilder;
-import io.fabric8.tekton.pipeline.v1beta1.PipelineRunSpec;
-import io.fabric8.tekton.pipeline.v1beta1.PipelineRunSpecBuilder;
-import io.fabric8.tekton.pipeline.v1beta1.TaskRun;
-import io.fabric8.tekton.pipeline.v1beta1.WorkspaceBindingBuilder;
+import io.fabric8.tekton.pipeline.v1beta1.*;
 import io.quarkus.vertx.ConsumeEvent;
 import io.vertx.mutiny.core.eventbus.EventBus;
-import org.apache.camel.karavan.informer.ServiceEventHandler;
-import org.apache.camel.karavan.model.PipelineRunLog;
-import org.apache.camel.karavan.model.Project;
 import org.apache.camel.karavan.informer.DeploymentEventHandler;
 import org.apache.camel.karavan.informer.PipelineRunEventHandler;
 import org.apache.camel.karavan.informer.PodEventHandler;
+import org.apache.camel.karavan.informer.ServiceEventHandler;
+import org.apache.camel.karavan.model.Project;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.eclipse.microprofile.health.HealthCheck;
 import org.eclipse.microprofile.health.HealthCheckResponse;
@@ -57,13 +44,7 @@ import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.inject.Default;
 import javax.enterprise.inject.Produces;
 import javax.inject.Inject;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
 import java.util.stream.Collectors;
 
 
@@ -103,8 +84,7 @@ public class KubernetesService implements HealthCheck{
     @ConfigProperty(name = "karavan.environment")
     public String environment;
 
-
-    List<SharedIndexInformer> informers = new ArrayList<>(3);
+    List<SharedIndexInformer> informers = new ArrayList<>(4);
 
     @ConsumeEvent(value = START_INFORMERS, blocking = true)
     void startInformers(String data) {
@@ -205,34 +185,49 @@ public class KubernetesService implements HealthCheck{
         return logText;
     }
 
-
     // TODO: implement log watch
-    public void startContainerLogWatch(String podName, String namespace) {
-        LogWatch logWatch = kubernetesClient().pods().inNamespace(namespace).withName(podName).watchLog();
-        InputStream is = logWatch.getOutput();
-        Integer i;
-        try {
-            while ((i = is.available()) != null) {
-                eventBus.publish(podName + "-" + namespace, new String(is.readNBytes(i)));
-            }
-        } catch (IOException e) {
-            LOGGER.error(e);
-        }
-    }
-
-    public List<PipelineRunLog> getPipelineRunLog(String pipelineRuneName, String namespace) {
-        List<PipelineRunLog> result = new ArrayList<>(1);
+    public LogWatch getLogWatch(String podName) {
+        return kubernetesClient().pods().inNamespace(getNamespace()).withName(podName).watchLog();
+    }
+//    public void startContainerLogWatch(String session, String podName) {
+//        Tuple2<CompletableFuture<Void>, LogWatch> old = logWatches.get(session);
+//        if (old != null) {
+//            LOGGER.info("Closing old");
+//            old.getItem1().cancel(true);
+//            old.getItem2().close();
+//            logWatches.remove(session);
+//            LOGGER.info("Closed old");
+//        }
+//
+//        LOGGER.info("Starting startContainerLogWatch");
+//        CompletableFuture<Void> future = managedExecutor.runAsync(() -> {
+//            LogWatch logWatch =
+//            BufferedReader reader = new BufferedReader(new InputStreamReader(logWatch.getOutput()));
+//            try {
+//                for (String line; (line = reader.readLine()) != null; ) {
+//                    eventBus.publish(session, System.lineSeparator());
+//                    eventBus.publish(session, line);
+//                    System.out.println(line);
+//                }
+//            } catch (IOException e) {
+//                LOGGER.error(e.getMessage());
+//            }
+//        });
+//        logWatches.put(session, Tuple2.of(future, logWatch));
+//        LOGGER.info("Done startContainerLogWatch");
+//    }
+
+    public String getPipelineRunLog(String pipelineRuneName, String namespace) {
+        StringBuilder result = new StringBuilder();
         getTaskRuns(pipelineRuneName, namespace).forEach(taskRun -> {
             String podName = taskRun.getStatus().getPodName();
-            StringBuilder log = new StringBuilder();
             taskRun.getStatus().getSteps().forEach(stepState -> {
                 String logText = kubernetesClient().pods().inNamespace(namespace).withName(podName).inContainer(stepState.getContainer()).getLog(true);
-                log.append(stepState.getContainer()).append(System.lineSeparator());
-                log.append(logText).append(System.lineSeparator());
+                result.append(stepState.getContainer()).append(System.lineSeparator());
+                result.append(logText).append(System.lineSeparator());
             });
-            result.add(new PipelineRunLog(taskRun.getMetadata().getName(), log.toString()));
         });
-        return result;
+        return result.toString();
     }
 
     public PipelineRun getLastPipelineRun(String projectId, String pipelineName, String namespace) {
diff --git a/karavan-app/src/main/webui/src/projects/ProjectLog.tsx b/karavan-app/src/main/webui/src/projects/ProjectLog.tsx
index 3c706444..2b81dda5 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectLog.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectLog.tsx
@@ -31,7 +31,7 @@ export class ProjectLog extends React.Component<Props, State> {
         showLog: false,
         height: "30%",
         logViewerRef: React.createRef(),
-        isTextWrapped: false,
+        isTextWrapped: true,
         data: []
     }
 
@@ -40,7 +40,6 @@ export class ProjectLog extends React.Component<Props, State> {
     componentDidMount() {
         this.sub = ProjectEventBus.onShowLog()?.subscribe((log: ShowLogCommand) => {
             this.setState({showLog: true, log: log});
-            console.log(log)
             this.showLogs(log.type, log.name, log.environment);
         });
     }
@@ -52,8 +51,7 @@ export class ProjectLog extends React.Component<Props, State> {
     showLogs = (type: 'container' | 'pipeline', name: string, environment: string) => {
         if (type === 'pipeline') {
             KaravanApi.getPipelineLog(environment, name, (res: any) => {
-                if (Array.isArray(res) && Array.from(res).length > 0)
-                    this.setState({data: res.at(0).log});
+                this.setState({data: res});
             });
         } else if (type === 'container') {
             KaravanApi.getContainerLog(environment, name, (res: any) => {