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

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


The following commit(s) were added to refs/heads/main by this push:
     new 1c4c46ab Exchange tracing #757
1c4c46ab is described below

commit 1c4c46abdabcb877068387a31970ae3e6626cc5c
Author: Marat Gubaidullin <[email protected]>
AuthorDate: Sat May 13 21:16:27 2023 -0400

    Exchange tracing #757
---
 .../apache/camel/karavan/api/LogWatchResource.java |  1 +
 .../apache/camel/karavan/api/RunnerResource.java   | 15 +++-
 .../apache/camel/karavan/model/RunnerStatus.java   |  3 +-
 .../camel/karavan/service/InfinispanService.java   |  1 -
 .../camel/karavan/service/KubernetesService.java   |  5 +-
 .../camel/karavan/service/ProjectService.java      |  1 -
 .../camel/karavan/service/RunnerService.java       | 40 +++++++++-
 karavan-app/src/main/webui/src/api/KaravanApi.tsx  | 13 ++-
 karavan-app/src/main/webui/src/index.css           |  7 ++
 .../main/webui/src/projects/CreateFileModal.tsx    |  2 +-
 .../main/webui/src/projects/ProjectDevelopment.tsx | 69 +++++++++++-----
 .../src/main/webui/src/projects/ProjectEventBus.ts | 17 ++++
 .../src/main/webui/src/projects/ProjectLog.tsx     |  6 +-
 .../main/webui/src/projects/RunnerInfoContext.tsx  | 22 +++---
 .../webui/src/projects/RunnerInfoDataModal.tsx     | 82 +++++++++++++++++++
 .../main/webui/src/projects/RunnerInfoMemory.tsx   |  2 -
 .../src/main/webui/src/projects/RunnerInfoPod.tsx  |  2 +-
 .../main/webui/src/projects/RunnerInfoTrace.tsx    | 92 ++++++++++++++++++++++
 .../src/main/webui/src/projects/RunnerToolbar.tsx  | 84 ++++++++++++++------
 19 files changed, 393 insertions(+), 71 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 e4e5efff..b0cade62 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
@@ -76,6 +76,7 @@ public class LogWatchResource {
                     LOGGER.error(e.getMessage());
                 }
                 logWatch.close();
+                sink.close();
                 LOGGER.info("LogWatch for " + name + " closed");
             }
         });
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/api/RunnerResource.java 
b/karavan-app/src/main/java/org/apache/camel/karavan/api/RunnerResource.java
index f07df5e2..eb169a48 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/api/RunnerResource.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/RunnerResource.java
@@ -62,18 +62,25 @@ public class RunnerResource {
         String status = infinispanService.getRunnerStatus(runnerName, 
RunnerStatus.NAME.context);
         if (status != null) {
             JsonObject js = new JsonObject(status);
-            System.out.println(status);
         }
         Project p = infinispanService.getProject(project.getProjectId());
         return kubernetesService.tryCreateRunner(p, runnerName);
     }
 
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/reload/{projectId}")
+    public Response reload(@PathParam("projectId") String projectId) {
+        runnerServices.reload(projectId);
+        return Response.ok().build();
+    }
+
     @DELETE
     @Produces(MediaType.APPLICATION_JSON)
     @Consumes(MediaType.APPLICATION_JSON)
-    @Path("/{name}")
-    public Response deletePod(@PathParam("name") String name) {
-        kubernetesService.deleteRunner(name);
+    @Path("/{name}/{deletePVC}")
+    public Response deleteRunner(@PathParam("name") String name, 
@PathParam("deletePVC") boolean deletePVC) {
+        kubernetesService.deleteRunner(name, deletePVC);
         infinispanService.deleteRunnerStatuses(name);
         return Response.accepted().build();
     }
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/model/RunnerStatus.java 
b/karavan-app/src/main/java/org/apache/camel/karavan/model/RunnerStatus.java
index de474bd9..f1928a20 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/model/RunnerStatus.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/model/RunnerStatus.java
@@ -9,6 +9,7 @@ public class RunnerStatus {
         properties,
         route,
         trace,
-        jvm
+        jvm,
+        source
     }
 }
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java
 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java
index b1fffc1f..b09a0831 100644
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java
+++ 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/InfinispanService.java
@@ -127,7 +127,6 @@ public class InfinispanService implements HealthCheck  {
             commits = 
cacheManager.administration().getOrCreateCache("commits", new 
StringConfiguration(String.format(CACHE_CONFIG, "commits")));
             runnerStatuses = 
cacheManager.administration().getOrCreateCache("runner_statuses", new 
StringConfiguration(String.format(CACHE_CONFIG, "runner_statuses")));
         }
-        System.out.println("READY");
         ready.set(true);
     }
 
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 77ddaed5..0224e4e4 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
@@ -397,11 +397,14 @@ public class KubernetesService implements HealthCheck{
         return runnerName;
     }
 
-    public void deleteRunner(String name) {
+    public void deleteRunner(String name, boolean deletePVC) {
         try {
             LOGGER.info("Delete runner: " + name + " in the namespace: " + 
getNamespace());
             
kubernetesClient().pods().inNamespace(getNamespace()).withName(name).delete();
             
kubernetesClient().services().inNamespace(getNamespace()).withName(name).delete();
+            if (deletePVC) {
+                
kubernetesClient().persistentVolumeClaims().inNamespace(getNamespace()).withName(name).delete();
+            }
         } catch (Exception ex) {
             LOGGER.error(ex.getMessage());
         }
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
index 85e511ca..f3f7bd7e 100644
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
+++ 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
@@ -77,7 +77,6 @@ public class ProjectService implements HealthCheck{
             LOGGER.info("Pull commits...");
             Tuple2<String, Integer> lastCommit = 
infinispanService.getLastCommit();
             
gitService.getCommitsAfterCommit(lastCommit.getItem2()).forEach(commitInfo -> {
-                System.out.println(commitInfo);
                 if (!infinispanService.hasCommit(commitInfo.getCommitId())) {
                     commitInfo.getRepos().forEach(repo -> {
                         Project project = importProjectFromRepo(repo);
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/RunnerService.java 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/RunnerService.java
index 709da51c..2bb70cf7 100644
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/RunnerService.java
+++ 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/RunnerService.java
@@ -25,6 +25,7 @@ import io.vertx.mutiny.core.eventbus.EventBus;
 import io.vertx.mutiny.ext.web.client.HttpResponse;
 import io.vertx.mutiny.ext.web.client.WebClient;
 import org.apache.camel.karavan.model.PodStatus;
+import org.apache.camel.karavan.model.ProjectFile;
 import org.apache.camel.karavan.model.RunnerStatus;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
@@ -32,7 +33,9 @@ import org.jboss.logging.Logger;
 
 import javax.enterprise.context.ApplicationScoped;
 import javax.inject.Inject;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.ExecutionException;
 
@@ -67,6 +70,39 @@ public class RunnerService {
         return webClient;
     }
 
+    public void reload(String projectId) {
+        try {
+            String runnerName = projectId + "-" + RUNNER_SUFFIX;
+            infinispanService.getProjectFiles(projectId).forEach(projectFile 
-> putRequest(runnerName, projectFile.getName(), projectFile.getCode(), 1000));
+            reloadRequest(runnerName);
+        } catch (Exception ex) {
+            LOGGER.error(ex.getMessage());
+        }
+    }
+
+    @CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 0.5, delay = 
1000)
+    public boolean putRequest(String runnerName, String fileName, String body, 
int timeout) {
+        try {
+            String url = "http://"; + runnerName + "." + 
kubernetesService.getNamespace() + ".svc.cluster.local/q/upload/" + fileName;
+            HttpResponse<Buffer> result = getWebClient().putAbs(url)
+                    
.timeout(timeout).sendBuffer(Buffer.buffer(body)).subscribeAsCompletionStage().toCompletableFuture().get();
+            return result.statusCode() == 200;
+        } catch (Exception e) {
+            LOGGER.info(e.getMessage());
+        }
+        return false;
+    }
+
+    public String reloadRequest(String runnerName) {
+        String url = "http://"; + runnerName + "." + 
kubernetesService.getNamespace() + 
".svc.cluster.local/q/dev/reload?reload=true";
+        try {
+            return result(url, 1000);
+        } catch (InterruptedException | ExecutionException e) {
+            LOGGER.error(e.getMessage());
+        }
+        return null;
+    }
+
     @Scheduled(every = "{karavan.runner-status-interval}", concurrentExecution 
= Scheduled.ConcurrentExecution.SKIP)
     void collectRunnerStatus() {
         if (infinispanService.call().getStatus().name().equals("UP")) {
@@ -86,7 +122,9 @@ public class RunnerService {
                     .filter(name -> !name.equals(RunnerStatus.NAME.context))
                     .forEach(statusName -> {
                         String status = getRunnerStatus(podName, statusName);
-                        infinispanService.saveRunnerStatus(podName, 
statusName, status);
+                        if (status != null) {
+                            infinispanService.saveRunnerStatus(podName, 
statusName, status);
+                        }
                     });
             reloadCode(podName, oldContext, newContext);
         }
diff --git a/karavan-app/src/main/webui/src/api/KaravanApi.tsx 
b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
index 45b86a2a..7142c571 100644
--- a/karavan-app/src/main/webui/src/api/KaravanApi.tsx
+++ b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
@@ -309,6 +309,15 @@ export class KaravanApi {
         });
     }
 
+    static async getRunnerReload(projectId: string, after: (res: 
AxiosResponse<any>) => void) {
+        instance.get('/api/runner/reload/' + projectId)
+            .then(res => {
+                after(res);
+            }).catch(err => {
+            after(err);
+        });
+    }
+
     static async getRunnerConsoleStatus(projectId: string, statusName: string, 
after: (res: AxiosResponse<string>) => void) {
         instance.get('/api/runner/console/' + statusName + "/" + projectId)
             .then(res => {
@@ -327,8 +336,8 @@ export class KaravanApi {
         });
     }
 
-    static async deleteRunner(name: string, after: (res: AxiosResponse<any>) 
=> void) {
-        instance.delete('/api/runner/' +  name)
+    static async deleteRunner(name: string, deletePVC: boolean, after: (res: 
AxiosResponse<any>) => void) {
+        instance.delete('/api/runner/' +  name + "/" + deletePVC)
             .then(res => {
                 after(res);
             }).catch(err => {
diff --git a/karavan-app/src/main/webui/src/index.css 
b/karavan-app/src/main/webui/src/index.css
index 7fa80c96..e3525ba9 100644
--- a/karavan-app/src/main/webui/src/index.css
+++ b/karavan-app/src/main/webui/src/index.css
@@ -161,6 +161,13 @@
   height: 30px;
 }
 
+.karavan .project-page .project-development .pf-c-panel__header {
+  padding: 0;
+}
+.karavan .project-page .project-development .pf-c-panel__main {
+  max-height: 212px;
+}
+
 .karavan .project-page .project-status {
   margin-bottom: 16px;
 }
diff --git a/karavan-app/src/main/webui/src/projects/CreateFileModal.tsx 
b/karavan-app/src/main/webui/src/projects/CreateFileModal.tsx
index 90183a0f..0bbcd836 100644
--- a/karavan-app/src/main/webui/src/projects/CreateFileModal.tsx
+++ b/karavan-app/src/main/webui/src/projects/CreateFileModal.tsx
@@ -5,7 +5,7 @@ import {
     FormGroup,
     ModalVariant,
     Form,
-    ToggleGroupItem, ToggleGroup, TextInputGroupMain, TextInputGroupUtilities, 
TextInputGroup, Text, FormHelperText, HelperText, HelperTextItem, TextInput
+    ToggleGroupItem, ToggleGroup, FormHelperText, HelperText, HelperTextItem, 
TextInput
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {KaravanApi} from "../api/KaravanApi";
diff --git a/karavan-app/src/main/webui/src/projects/ProjectDevelopment.tsx 
b/karavan-app/src/main/webui/src/projects/ProjectDevelopment.tsx
index 64f16999..1e19cc6d 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectDevelopment.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectDevelopment.tsx
@@ -1,7 +1,7 @@
 import React, {useEffect, useRef, useState} from 'react';
 import {
     Card,
-    CardBody, Flex, FlexItem, Divider
+    CardBody, Flex, FlexItem, Divider, Tab, Tabs, CardFooter
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {PodStatus, Project} from "./ProjectModels";
@@ -11,6 +11,7 @@ import {RunnerInfoContext} from "./RunnerInfoContext";
 import {RunnerInfoMemory} from "./RunnerInfoMemory";
 import {KaravanApi} from "../api/KaravanApi";
 import {ProjectEventBus} from "./ProjectEventBus";
+import {RunnerInfoTrace} from "./RunnerInfoTrace";
 
 export function isRunning(status: PodStatus): boolean {
     return status.phase === 'Running' && !status.terminating;
@@ -29,14 +30,28 @@ export const ProjectDevelopment = (props: Props) => {
     const [memory, setMemory] = useState({});
     const [jvm, setJvm] = useState({});
     const [context, setContext] = useState({});
+    const [trace, setTrace] = useState({});
+    const [showTrace, setShowTrace] = useState(false);
+    const [refreshTrace, setRefreshTrace] = useState(true);
 
 
     useEffect(() => {
         previousValue.current = podStatus;
+        const sub1 = ProjectEventBus.onShowTrace()?.subscribe((result) => {
+            setShowTrace(result.show);
+        });
+        const sub2 = ProjectEventBus.onRefreshTrace()?.subscribe((result) => {
+            setRefreshTrace(result);
+        });
         const interval = setInterval(() => {
             onRefreshStatus();
         }, 1000);
-        return () => clearInterval(interval);
+        return () => {
+            sub1.unsubscribe();
+            sub2.unsubscribe();
+            clearInterval(interval)
+        };
+
     }, [podStatus]);
 
     function onRefreshStatus() {
@@ -74,35 +89,49 @@ export const ProjectDevelopment = (props: Props) => {
                 setContext({});
             }
         })
+        if (refreshTrace) {
+            KaravanApi.getRunnerConsoleStatus(projectId, "trace", res => {
+                if (res.status === 200) {
+                    setTrace(res.data);
+                } else {
+                    setTrace({});
+                }
+            })
+        }
     }
 
     function showConsole(): boolean {
-        return podStatus.phase !== '' ;
+        return podStatus.phase !== '';
     }
 
     const {project, config} = props;
     return (
-            <Card className="project-development">
-                <CardBody>
-                    <Flex direction={{default: "row"}}
-                          justifyContent={{default: 
"justifyContentSpaceBetween"}}>
-                        <FlexItem flex={{default: "flex_1"}}>
-                            <RunnerInfoPod podStatus={podStatus} 
config={config} showConsole={showConsole()} />
-                        </FlexItem>
+        <Card className="project-development">
+            <CardBody>
+                <Flex direction={{default: "row"}}
+                      justifyContent={{default: "justifyContentSpaceBetween"}}>
+                    {!showTrace && <FlexItem flex={{default: "flex_1"}}>
+                        <RunnerInfoPod podStatus={podStatus} config={config} 
showConsole={showConsole()}/>
+                    </FlexItem>}
+                    {showConsole() && !showTrace && <>
                         <Divider orientation={{default: "vertical"}}/>
                         <FlexItem flex={{default: "flex_1"}}>
-                            <RunnerInfoMemory jvm={jvm} memory={memory} 
config={config} showConsole={showConsole()} />
+                            <RunnerInfoMemory jvm={jvm} memory={memory} 
config={config} showConsole={showConsole()}/>
                         </FlexItem>
                         <Divider orientation={{default: "vertical"}}/>
                         <FlexItem flex={{default: "flex_1"}}>
-                            <RunnerInfoContext context={context} 
config={config} showConsole={showConsole()} />
-                        </FlexItem>
-                        <Divider orientation={{default: "vertical"}}/>
-                        <FlexItem>
-                            <RunnerToolbar project={project} config={config} 
showConsole={showConsole()} />
+                            <RunnerInfoContext context={context} 
config={config} showConsole={showConsole()}/>
                         </FlexItem>
-                    </Flex>
-                </CardBody>
-            </Card>
-        )
+                    </>}
+                    {showConsole() && showTrace && <FlexItem flex={{default: 
"flex_1"}} style={{margin:"0"}}>
+                        <RunnerInfoTrace trace={trace} 
refreshTrace={refreshTrace}/>
+                    </FlexItem>}
+                    <Divider orientation={{default: "vertical"}}/>
+                    <FlexItem>
+                        <RunnerToolbar project={project} config={config} 
showConsole={showConsole()}/>
+                    </FlexItem>
+                </Flex>
+            </CardBody>
+        </Card>
+    )
 }
diff --git a/karavan-app/src/main/webui/src/projects/ProjectEventBus.ts 
b/karavan-app/src/main/webui/src/projects/ProjectEventBus.ts
index 93535629..f2d8397c 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectEventBus.ts
+++ b/karavan-app/src/main/webui/src/projects/ProjectEventBus.ts
@@ -20,6 +20,8 @@ import {Project} from "./ProjectModels";
 const currentProject = new Subject<Project>();
 const currentFile = new Subject<string>();
 const showLog = new Subject<ShowLogCommand>();
+const showTrace = new Subject<ShowTraceCommand>();
+const refreshTrace = new Subject<boolean>();
 
 export class ShowLogCommand {
     type: 'container' | 'pipeline'
@@ -35,6 +37,15 @@ export class ShowLogCommand {
     }
 }
 
+export class ShowTraceCommand {
+    name: string
+    show: boolean
+
+    constructor(name: string, show: boolean) {
+        this.name = name;
+        this.show = show;
+    }
+}
 export const ProjectEventBus = {
 
     selectProject: (project: Project) => currentProject.next(project),
@@ -46,4 +57,10 @@ export const ProjectEventBus = {
     showLog: (type: 'container' | 'pipeline', name: string, environment: 
string, show: boolean = true) =>
         showLog.next(new ShowLogCommand(type, name, environment, show)),
     onShowLog: () => showLog.asObservable(),
+
+    showTrace: (name: string, show: boolean = true) => showTrace.next(new 
ShowTraceCommand(name, show)),
+    onShowTrace: () => showTrace.asObservable(),
+
+    refreshTrace: (refresh: boolean) => refreshTrace.next(refresh),
+    onRefreshTrace: () => refreshTrace.asObservable(),
 }
diff --git a/karavan-app/src/main/webui/src/projects/ProjectLog.tsx 
b/karavan-app/src/main/webui/src/projects/ProjectLog.tsx
index 82b5fb23..829c58c7 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectLog.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectLog.tsx
@@ -62,8 +62,10 @@ export class ProjectLog extends React.Component<Props, 
State> {
             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});
+            this.setState((state: Readonly<State>) => {
+                const data = state.data.concat('\n').concat(event.data)
+                return {data: data, currentLine: this.state.currentLine + 1}
+            });
         };
     }
 
diff --git a/karavan-app/src/main/webui/src/projects/RunnerInfoContext.tsx 
b/karavan-app/src/main/webui/src/projects/RunnerInfoContext.tsx
index 8f49c15b..79624695 100644
--- a/karavan-app/src/main/webui/src/projects/RunnerInfoContext.tsx
+++ b/karavan-app/src/main/webui/src/projects/RunnerInfoContext.tsx
@@ -56,14 +56,14 @@ export const RunnerInfoContext = (props: Props) => {
                         {props.context?.context?.state}
                     </Label>
                 </Tooltip>
-                <Tooltip content="Uptime" position={"bottom"}>
+                <Tooltip content="Phase" position={"bottom"}>
                     <Label icon={getIcon()} color={getColor()}>
-                        {props.context?.context?.uptime}
+                        {props.context?.context?.phase}
                     </Label>
                 </Tooltip>
-                <Tooltip content="Phase" position={"bottom"}>
+                <Tooltip content="Uptime" position={"bottom"}>
                     <Label icon={getIcon()} color={getColor()}>
-                        {props.context?.context?.phase}
+                        {props.context?.context?.uptime}
                     </Label>
                 </Tooltip>
             </LabelGroup>
@@ -138,13 +138,12 @@ export const RunnerInfoContext = (props: Props) => {
 
     return (
         <DescriptionList isHorizontal>
-            <DescriptionListGroup>
-                <DescriptionListTerm>Camel</DescriptionListTerm>
-                <DescriptionListDescription>
-                    {getContextInfo()}
-                </DescriptionListDescription>
-            </DescriptionListGroup>
-            {props.showConsole && <>
+                <DescriptionListGroup>
+                    <DescriptionListTerm>Camel</DescriptionListTerm>
+                    <DescriptionListDescription>
+                        {getContextInfo()}
+                    </DescriptionListDescription>
+                </DescriptionListGroup>
                 <DescriptionListGroup>
                     <DescriptionListTerm>Version</DescriptionListTerm>
                     <DescriptionListDescription>
@@ -169,7 +168,6 @@ export const RunnerInfoContext = (props: Props) => {
                         {getProcessingTime()}
                     </DescriptionListDescription>
                 </DescriptionListGroup>
-            </>}
         </DescriptionList>
     );
 }
diff --git a/karavan-app/src/main/webui/src/projects/RunnerInfoDataModal.tsx 
b/karavan-app/src/main/webui/src/projects/RunnerInfoDataModal.tsx
new file mode 100644
index 00000000..f8700833
--- /dev/null
+++ b/karavan-app/src/main/webui/src/projects/RunnerInfoDataModal.tsx
@@ -0,0 +1,82 @@
+import React, {useEffect, useRef, useState} from 'react';
+import {
+    Badge, Bullseye,
+    Button, CodeBlock, CodeBlockCode, DataList, DataListCell, DataListItem, 
DataListItemCells, DataListItemRow,
+    DescriptionList,
+    DescriptionListDescription,
+    DescriptionListGroup,
+    DescriptionListTerm, Divider, EmptyState, EmptyStateIcon, 
EmptyStateVariant, Form, FormGroup, FormHelperText, HelperText, HelperTextItem,
+    Label, LabelGroup, Modal, ModalVariant, Panel, PanelHeader, PanelMain, 
PanelMainBody, Switch, Text, TextInput, Title, ToggleGroup, ToggleGroupItem,
+    Tooltip, TooltipPosition
+} from '@patternfly/react-core';
+import '../designer/karavan.css';
+import {getProjectFileType, PodStatus, Project, ProjectFileTypes} from 
"./ProjectModels";
+import {KaravanApi} from "../api/KaravanApi";
+import {ProjectEventBus} from "./ProjectEventBus";
+import DownIcon from 
"@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
+import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
+import {TableComposable, Tbody, Td, Th, Thead, Tr} from 
"@patternfly/react-table";
+import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
+import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon";
+
+interface Props {
+    trace: any
+    isOpen: boolean
+    onClose: () => void
+}
+
+export const RunnerInfoDataModal = (props: Props) => {
+
+    const type = props.trace?.message?.body?.type;
+    const body = props.trace?.message?.body?.value;
+    const headers: any[] = [{key: "header1", value: "value1"}, {key: 
"header2", value: "value2"}];
+    return (
+        <Modal
+            title={"Exchange: " + props.trace?.message?.exchangeId}
+            variant={ModalVariant.large}
+            isOpen={props.isOpen}
+            onClose={() => props.onClose?.call(this)}
+            actions={[
+                <Button key="cancel" variant="primary" onClick={event => 
props.onClose?.call(this)}>Close</Button>
+            ]}
+        >
+            <Panel isScrollable>
+                <PanelMain tabIndex={0}>
+                    <PanelHeader>
+                        <DescriptionList isHorizontal>
+                            <DescriptionListGroup>
+                                
<DescriptionListTerm>Headers</DescriptionListTerm>
+                                <DescriptionListDescription>
+                                    <DataList aria-label="Compact data list 
example" isCompact>
+                                        {headers.map((header: any) => (
+                                                <DataListItem key={header[0]} 
aria-labelledby="compact-item1">
+                                                    <DataListItemRow>
+                                                        <DataListItemCells
+                                                            dataListCells={[
+                                                                <DataListCell 
key="uid">{header.key}</DataListCell>,
+                                                                <DataListCell 
key="routeId">{header.value}</DataListCell>,
+                                                            ]}
+                                                        />
+                                                    </DataListItemRow>
+                                                </DataListItem>))}
+                                    </DataList>
+                                </DescriptionListDescription>
+                            </DescriptionListGroup>
+                            <DescriptionListGroup>
+                                <DescriptionListTerm>Body</DescriptionListTerm>
+                                <DescriptionListDescription>
+                                    {type && <Label>{type}</Label>}
+                                </DescriptionListDescription>
+                            </DescriptionListGroup>
+                        </DescriptionList>
+                    </PanelHeader>
+                    <PanelMainBody style={{padding: "0"}}>
+                        <CodeBlock title="Body">
+                            <CodeBlockCode 
id="code-content">{body}</CodeBlockCode>
+                        </CodeBlock>
+                    </PanelMainBody>
+                </PanelMain>
+            </Panel>
+        </Modal>
+    );
+}
diff --git a/karavan-app/src/main/webui/src/projects/RunnerInfoMemory.tsx 
b/karavan-app/src/main/webui/src/projects/RunnerInfoMemory.tsx
index 2511b79d..e90af48a 100644
--- a/karavan-app/src/main/webui/src/projects/RunnerInfoMemory.tsx
+++ b/karavan-app/src/main/webui/src/projects/RunnerInfoMemory.tsx
@@ -128,7 +128,6 @@ export const RunnerInfoMemory = (props: Props) => {
                     {getJvmInfo()}
                 </DescriptionListDescription>
             </DescriptionListGroup>
-            {props.showConsole && <>
                 <DescriptionListGroup>
                     <DescriptionListTerm>PID</DescriptionListTerm>
                     <DescriptionListDescription>
@@ -153,7 +152,6 @@ export const RunnerInfoMemory = (props: Props) => {
                         {getNonHeapInfo()}
                     </DescriptionListDescription>
                 </DescriptionListGroup>
-            </>}
         </DescriptionList>
     );
 }
diff --git a/karavan-app/src/main/webui/src/projects/RunnerInfoPod.tsx 
b/karavan-app/src/main/webui/src/projects/RunnerInfoPod.tsx
index be606cd3..3e05256a 100644
--- a/karavan-app/src/main/webui/src/projects/RunnerInfoPod.tsx
+++ b/karavan-app/src/main/webui/src/projects/RunnerInfoPod.tsx
@@ -28,7 +28,7 @@ export const RunnerInfoPod = (props: Props) => {
         const podStatus = props.podStatus;
         return (
             <Label icon={getIcon()} color={getColor()}>
-                <Tooltip content={`Phase: ${JSON.stringify(podStatus)}`}>
+                <Tooltip content={"Show log"}>
                     <Button variant="link"
                             onClick={e => ProjectEventBus.showLog('container', 
podStatus.name, env)}>
                         {podStatus.name}
diff --git a/karavan-app/src/main/webui/src/projects/RunnerInfoTrace.tsx 
b/karavan-app/src/main/webui/src/projects/RunnerInfoTrace.tsx
new file mode 100644
index 00000000..044c0345
--- /dev/null
+++ b/karavan-app/src/main/webui/src/projects/RunnerInfoTrace.tsx
@@ -0,0 +1,92 @@
+import React, {useEffect, useRef, useState} from 'react';
+import {
+    Badge, Bullseye,
+    Button, DataList, DataListCell, DataListItem, DataListItemCells, 
DataListItemRow,
+    DescriptionList,
+    DescriptionListDescription,
+    DescriptionListGroup,
+    DescriptionListTerm, Divider, EmptyState, EmptyStateIcon, 
EmptyStateVariant,
+    Label, LabelGroup, Panel, PanelHeader, PanelMain, PanelMainBody, Switch, 
Title,
+    Tooltip, TooltipPosition
+} from '@patternfly/react-core';
+import '../designer/karavan.css';
+import {getProjectFileType, PodStatus, Project} from "./ProjectModels";
+import {KaravanApi} from "../api/KaravanApi";
+import {ProjectEventBus} from "./ProjectEventBus";
+import DownIcon from 
"@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
+import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
+import {TableComposable, Tbody, Td, Th, Thead, Tr} from 
"@patternfly/react-table";
+import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
+import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon";
+import {RunnerInfoDataModal} from "./RunnerInfoDataModal";
+
+
+interface Props {
+    trace: any
+    refreshTrace: boolean
+}
+
+export const RunnerInfoTrace = (props: Props) => {
+
+    const [trace, setTrace] = useState({});
+    const [isOpen, setIsOpen] = useState(false);
+
+    function closeModal() {
+        setIsOpen(false);
+    }
+
+    const traces: any[] = props.trace?.trace?.traces || [];
+    return (
+        <Panel isScrollable>
+            <RunnerInfoDataModal isOpen={isOpen} trace={trace} 
onClose={closeModal}/>
+            <PanelHeader>
+                <div style={{display:"flex", flexDirection:"row", 
justifyContent:"space-between"}}>
+                    <DescriptionList>
+                        <DescriptionListGroup>
+                            <DescriptionListTerm>Trace routed 
messages</DescriptionListTerm>
+                        </DescriptionListGroup>
+                    </DescriptionList>
+                    <div style={{marginRight: "16px"}}>
+                        <Tooltip content="Auto refresh" 
position={TooltipPosition.left}>
+                            <Switch aria-label="refresh"
+                                id="refresh"
+                                isChecked={props.refreshTrace}
+                                onChange={checked => 
ProjectEventBus.refreshTrace(checked)}
+                            />
+                        </Tooltip>
+                    </div>
+                </div>
+            </PanelHeader>
+            <PanelMain tabIndex={0}>
+                <PanelMainBody style={{padding: "0"}}>
+                    <DataList aria-label="Compact data list example" isCompact>
+                        {traces.filter(t => t.nodeId === undefined)
+                            .sort((a, b) => b.uid > a.uid ? 1 : -1)
+                            .map(trace => (
+                                <DataListItem key={trace.uid} 
aria-labelledby="compact-item1">
+                                    <DataListItemRow>
+                                        <DataListItemCells
+                                            dataListCells={[
+                                                <DataListCell 
key="uid">{trace.uid}</DataListCell>,
+                                                <DataListCell 
key="routeId">{trace.routeId}</DataListCell>,
+                                                <DataListCell key="exchangeId">
+                                                    <Button style={{padding: 
'0'}} variant={"link"}
+                                                            onClick={e => {
+                                                                
setTrace(trace);
+                                                                
setIsOpen(true);
+                                                            }}>
+                                                        
{trace.message.exchangeId}
+                                                    </Button>
+
+                                                </DataListCell>,
+                                                <DataListCell 
key="timestamp">{new Date(trace.timestamp).toISOString()}</DataListCell>
+                                            ]}
+                                        />
+                                    </DataListItemRow>
+                                </DataListItem>))}
+                    </DataList>
+                </PanelMainBody>
+            </PanelMain>
+        </Panel>
+    );
+}
diff --git a/karavan-app/src/main/webui/src/projects/RunnerToolbar.tsx 
b/karavan-app/src/main/webui/src/projects/RunnerToolbar.tsx
index cd972fe3..f552aa33 100644
--- a/karavan-app/src/main/webui/src/projects/RunnerToolbar.tsx
+++ b/karavan-app/src/main/webui/src/projects/RunnerToolbar.tsx
@@ -1,13 +1,14 @@
 import React, {useState} from 'react';
 import {
-    Button,
+    Button, Label, Switch, Tab, Tabs,
     Tooltip,
     TooltipPosition
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {Project} from "./ProjectModels";
 import RocketIcon from "@patternfly/react-icons/dist/esm/icons/rocket-icon";
-import PlayIcon from "@patternfly/react-icons/dist/esm/icons/play-icon";
+import ReloadIcon from "@patternfly/react-icons/dist/esm/icons/bolt-icon";
+import TraceIcon from "@patternfly/react-icons/dist/esm/icons/list-icon";
 import DeleteIcon from 
"@patternfly/react-icons/dist/esm/icons/times-circle-icon";
 import {KaravanApi} from "../api/KaravanApi";
 import {ProjectEventBus} from "./ProjectEventBus";
@@ -25,6 +26,8 @@ export const RunnerToolbar = (props: Props) => {
     const [isJbangRunning, setJbangIsRunning] = useState(false);
     const [isRunning, setIsRunning] = useState(false);
     const [isDeletingPod, setIsDeletingPod] = useState(false);
+    const [isReloadingPod, setIsReloadingPod] = useState(false);
+    const [isShowingTrace, setIsShowingTrace] = useState(false);
 
     function jbangRun() {
         setJbangIsRunning(true);
@@ -40,9 +43,21 @@ export const RunnerToolbar = (props: Props) => {
         });
     }
 
+    function reloadRunner() {
+        setIsReloadingPod(true);
+        KaravanApi.getRunnerReload(props.project.projectId, res => {
+            if (res.status === 200 || res.status === 201) {
+                setIsReloadingPod(false);
+            } else {
+                // Todo notification
+                setIsReloadingPod(false);
+            }
+        });
+    }
+
     function deleteRunner() {
         setIsDeletingPod(true);
-        KaravanApi.deleteRunner(podName, res => {
+        KaravanApi.deleteRunner(podName, false, res => {
             if (res.status === 202) {
                 setIsDeletingPod(false);
             } else {
@@ -52,36 +67,61 @@ export const RunnerToolbar = (props: Props) => {
         });
     }
 
+    function showTrace() {
+        ProjectEventBus.showTrace(props.project.projectId, !isShowingTrace);
+        setIsShowingTrace((prevState) => !prevState);
+    }
+
     return (
-        <React.Fragment>
             <div className="runner-toolbar">
-                <div className="row">
-                    <Tooltip content="Run in development mode" 
position={TooltipPosition.left}>
-                        <Button isLoading={isJbangRunning ? true : undefined}
-                                isSmall
-                                variant={"primary"}
-                                className="project-button"
-                                icon={!isJbangRunning ? <RocketIcon/> : 
<div></div>}
-                                onClick={() => jbangRun()}>
-                            {isJbangRunning ? "..." : "Run"}
-                        </Button>
-                    </Tooltip>
-                </div>
+                {!props.showConsole &&
+                    <div className="row">
+                        <Tooltip content="Run in development mode" 
position={TooltipPosition.left}>
+                            <Button isLoading={isJbangRunning ? true : 
undefined}
+                                    isSmall
+                                    variant={"primary"}
+                                    className="project-button"
+                                    icon={!isJbangRunning ? <RocketIcon/> : 
<div></div>}
+                                    onClick={() => jbangRun()}>
+                                {isJbangRunning ? "..." : "Run"}
+                            </Button>
+                        </Tooltip>
+                    </div>}
                 {props.showConsole && <>
                     <div className="row">
-                        <Tooltip content="Delete pod" 
position={TooltipPosition.left}>
-                            <Button isLoading={isDeletingPod ? true : 
undefined}
+                        <Tooltip content="Reload" 
position={TooltipPosition.left}>
+                            <Button isLoading={isReloadingPod ? true : 
undefined}
                                     isSmall
+                                    variant={"primary"}
+                                    className="project-button"
+                                    icon={!isReloadingPod ? <ReloadIcon/> : 
<div></div>}
+                                    onClick={() => reloadRunner()}>
+                                {isReloadingPod ? "..." : "Reload"}
+                            </Button>
+                        </Tooltip>
+                    </div>
+                    <div className="row">
+                        <Tooltip content="Show trace" 
position={TooltipPosition.left}>
+                            <Button isSmall
                                     variant={"secondary"}
                                     className="project-button"
-                                    icon={!isRunning ? <DeleteIcon/> : 
<div></div>}
-                                    onClick={() => deleteRunner()}>
-                                {isDeletingPod ? "..." : "Delete"}
+                                    icon={ <TraceIcon/>}
+                                    onClick={() => showTrace()}>
+                                {isShowingTrace ? "Runtime" : "Trace"}
                             </Button>
                         </Tooltip>
                     </div>
+                    <Tooltip content="Delete runner" 
position={TooltipPosition.left}>
+                        <Button isLoading={isDeletingPod ? true : undefined}
+                                isSmall
+                                variant={"secondary"}
+                                className="project-button"
+                                icon={!isRunning ? <DeleteIcon/> : <div></div>}
+                                onClick={() => deleteRunner()}>
+                            {isDeletingPod ? "..." : "Delete"}
+                        </Button>
+                    </Tooltip>
                 </>}
             </div>
-        </React.Fragment>
     );
 }


Reply via email to