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/08/18 18:34:05 UTC

[camel-karavan] 02/02: Fix #865

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 f91a11255e8a7f95dc63c087c1ad634a9cc19e56
Author: Marat Gubaidullin <ma...@Marats-MacBook-Pro.local>
AuthorDate: Fri Aug 18 14:33:01 2023 -0400

    Fix #865
---
 .../camel/karavan/api/InfrastructureResource.java  |  10 +-
 .../apache/camel/karavan/api/StatusResource.java   |  17 +-
 .../src/main/resources/services/devservices.yaml   |  15 +-
 .../src/main/webui/src/api/KaravanApi.tsx          |   6 +-
 .../src/main/webui/src/api/ProjectStore.ts         |  67 ++++-
 .../src/main/webui/src/dashboard/DashboardPage.tsx |  66 +----
 .../karavan-app/src/main/webui/src/index.css       |   2 +-
 .../src/main/webui/src/main/DataPoller.tsx         |  83 ------
 .../karavan-app/src/main/webui/src/main/Main.tsx   |  30 +--
 .../src/main/webui/src/main/MainDataPoller.tsx     |  62 +++++
 .../src/main/webui/src/main/MainLogin.tsx          | 102 ++++----
 .../src/main/webui/src/main/PageNavigation.tsx     |  16 +-
 .../main/webui/src/project/ProjectDataPoller.tsx   |  61 +++++
 .../src/main/webui/src/project/ProjectPage.tsx     |   2 +
 .../webui/src/project/dashboard/DashboardTab.tsx   |  44 +---
 .../main/webui/src/project/files/FilesToolbar.tsx  |   1 -
 .../main/webui/src/project/log/ProjectLogPanel.tsx |   2 -
 .../src/project/pipeline/ProjectPipelineTab.tsx    |   4 +-
 .../webui/src/project/pipeline/ProjectStatus.tsx   | 285 +++++++++------------
 .../src/project/trace/RunnerInfoTraceModal.tsx     |   4 +-
 .../src/project/trace/RunnerInfoTraceNode.tsx      |   4 +-
 .../src/main/webui/src/project/trace/TraceTab.tsx  |  36 +--
 .../src/main/webui/src/projects/ProjectsPage.tsx   |  38 +--
 .../karavan/infinispan/InfinispanService.java      |   7 +
 24 files changed, 444 insertions(+), 520 deletions(-)

diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java
index 478205b5..931aa2c5 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java
@@ -156,9 +156,13 @@ public class InfrastructureResource {
     @Produces(MediaType.APPLICATION_JSON)
     @Path("/service")
     public List<ServiceStatus> getAllServiceStatuses() throws Exception {
-        return infinispanService.getServiceStatuses().stream()
-                .sorted(Comparator.comparing(ServiceStatus::getProjectId))
-                .collect(Collectors.toList());
+        if (infinispanService.isReady()) {
+            return infinispanService.getServiceStatuses().stream()
+                    .sorted(Comparator.comparing(ServiceStatus::getProjectId))
+                    .collect(Collectors.toList());
+        } else {
+            return List.of();
+        }
     }
 
     @GET
diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/StatusResource.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/StatusResource.java
index 3a584f36..b4dcd21d 100644
--- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/StatusResource.java
+++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/StatusResource.java
@@ -44,13 +44,12 @@ public class StatusResource {
 
     @GET
     @Produces(MediaType.APPLICATION_JSON)
-    @Path("/pipeline/{projectId}/{env}")
-    public Response getPipelineStatus(@PathParam("projectId") String projectId, @PathParam("env") String env) {
-        PipelineStatus status = infinispanService.getPipelineStatus(projectId, env);
-        if (status != null) {
-            return Response.ok(status).build();
+    @Path("/pipeline/{env}")
+    public List<PipelineStatus> getPipelineStatuses(@PathParam("env") String env) {
+        if (infinispanService.isReady()) {
+            return infinispanService.getPipelineStatuses(env);
         } else {
-            return Response.noContent().build();
+            return List.of();
         }
     }
 
@@ -81,6 +80,10 @@ public class StatusResource {
     @Produces(MediaType.APPLICATION_JSON)
     @Path("/camel/{env}")
     public List<CamelStatus> getCamelStatusByEnv(@PathParam("env") String env) {
-        return infinispanService.getCamelStatusesByEnv(env, CamelStatus.Name.context);
+        if (infinispanService.isReady()) {
+            return infinispanService.getCamelStatusesByEnv(env, CamelStatus.Name.context);
+        } else {
+            return List.of();
+        }
     }
 }
\ No newline at end of file
diff --git a/karavan-web/karavan-app/src/main/resources/services/devservices.yaml b/karavan-web/karavan-app/src/main/resources/services/devservices.yaml
index 1365fd9a..e921cd22 100644
--- a/karavan-web/karavan-app/src/main/resources/services/devservices.yaml
+++ b/karavan-web/karavan-app/src/main/resources/services/devservices.yaml
@@ -41,17 +41,20 @@ services:
       KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
       KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
 
-  mariadb:
-    image: mariadb
+  postgres:
+    image: postgres:15.4
     restart: always
     environment:
-      MARIADB_ROOT_PASSWORD: mariadb
+      - POSTGRES_USER=postgres
+      - POSTGRES_PASSWORD=postgres
+    ports:
+      - '5432:5432'
 
-  mariadb-adminer:
-    image: adminer
+  adminer:
+    image: adminer:4.8.1-standalone
     restart: always
     ports:
-      - 9080:8080
+      - 8080:8080
 
   greenmail:
     container_name: greenmail
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
index 43919f12..cd2213f2 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx
@@ -155,13 +155,13 @@ export class KaravanApi {
         });
     }
 
-    static async getProjectPipelineStatus(projectId: string, env: string, after: (status?: PipelineStatus) => void) {
-        instance.get('/api/status/pipeline/' + projectId + "/" + env)
+    static async getPipelineStatuses(env: string, after: (status: PipelineStatus[]) => void) {
+        instance.get('/api/status/pipeline/' + env)
             .then(res => {
                 if (res.status === 200) {
                     after(res.data);
                 } else if (res.status === 204) {
-                    after(undefined);
+                    after([]);
                 }
 
             }).catch(err => {
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts b/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts
index 651c1071..ed7f1acc 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectStore.ts
@@ -16,16 +16,32 @@
  */
 
 import {create} from 'zustand'
-import {AppConfig, DeploymentStatus, ContainerStatus, Project, ProjectFile, ServiceStatus, CamelStatus} from "./ProjectModels";
+import {
+    AppConfig,
+    DeploymentStatus,
+    ContainerStatus,
+    Project,
+    ProjectFile,
+    ServiceStatus,
+    CamelStatus,
+    PipelineStatus
+} from "./ProjectModels";
 import {ProjectEventBus} from "./ProjectEventBus";
 import {unstable_batchedUpdates} from "react-dom";
+import {useState} from "react";
 
 interface AppConfigState {
+    loading: boolean;
+    setLoading: (loading: boolean) => void;
     config: AppConfig;
     setConfig: (config: AppConfig) => void;
 }
 
 export const useAppConfigStore = create<AppConfigState>((set) => ({
+    loading: false,
+    setLoading: (loading: boolean)  => {
+        set({loading: loading})
+    },
     config: new AppConfig(),
     setConfig: (config: AppConfig)  => {
         set({config: config})
@@ -56,12 +72,22 @@ export const useProjectsStore = create<ProjectsState>((set) => ({
 }))
 
 interface ProjectState {
-    project: Project;
     isPushing: boolean,
     isRunning: boolean,
-    operation: "create" | "select" | "delete" | "none" | "copy";
+    project: Project;
     setProject: (project: Project, operation:  "create" | "select" | "delete"| "none" | "copy") => void;
+    operation: "create" | "select" | "delete" | "none" | "copy";
     setOperation: (o: "create" | "select" | "delete"| "none" | "copy") => void;
+    memory: any,
+    setMemory: (memory: any) => void;
+    jvm: any,
+    setJvm: (jvm: any) => void;
+    context: any,
+    setContext: (context: any) => void;
+    trace: any,
+    setTrace: (trace: any) => void;
+    refreshTrace: boolean
+    setRefreshTrace: (refreshTrace: boolean) => void;
 }
 
 export const useProjectStore = create<ProjectState>((set) => ({
@@ -72,7 +98,12 @@ export const useProjectStore = create<ProjectState>((set) => ({
     setProject: (project: Project, operation:  "create" | "select" | "delete"| "none" | "copy") => {
         set((state: ProjectState) => ({
             project: project,
-            operation: operation
+            operation: operation,
+            refreshTrace: false,
+            jvm: {},
+            context: {},
+            trace: {},
+            memory: {},
         }));
     },
     setOperation: (o: "create" | "select" | "delete"| "none" | "copy") => {
@@ -80,6 +111,26 @@ export const useProjectStore = create<ProjectState>((set) => ({
             operation: o
         }));
     },
+    memory: {},
+    setMemory: (memory: boolean)  => {
+        set({memory: memory})
+    },
+    jvm: {},
+    setJvm: (jvm: boolean)  => {
+        set({jvm: jvm})
+    },
+    context: {},
+    setContext: (context: boolean)  => {
+        set({context: context})
+    },
+    trace: {},
+    setTrace: (trace: boolean)  => {
+        set({trace: trace})
+    },
+    refreshTrace: false,
+    setRefreshTrace: (refreshTrace: boolean)  => {
+        set({refreshTrace: refreshTrace})
+    },
 }))
 
 interface FilesState {
@@ -170,6 +221,8 @@ interface StatusesState {
     setServices: (s: ServiceStatus[]) => void;
     setContainers: (c: ContainerStatus[]) => void;
     setCamels: (c: CamelStatus[]) => void;
+    pipelineStatuses: PipelineStatus[],
+    setPipelineStatuses: (pipelineStatus: PipelineStatus[]) => void;
 }
 
 export const useStatusesStore = create<StatusesState>((set) => ({
@@ -196,7 +249,11 @@ export const useStatusesStore = create<StatusesState>((set) => ({
         set((state: StatusesState) => ({
             camels: c,
         }));
-    }
+    },
+    pipelineStatuses: [],
+    setPipelineStatuses: (pipelineStatuses: PipelineStatus[])  => {
+        set({pipelineStatuses: pipelineStatuses})
+    },
 }))
 
 interface LogState {
diff --git a/karavan-web/karavan-app/src/main/webui/src/dashboard/DashboardPage.tsx b/karavan-web/karavan-app/src/main/webui/src/dashboard/DashboardPage.tsx
index 9b54deba..c032a31b 100644
--- a/karavan-web/karavan-app/src/main/webui/src/dashboard/DashboardPage.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/dashboard/DashboardPage.tsx
@@ -1,4 +1,4 @@
-import React, {useEffect, useState} from 'react';
+import React, {useState} from 'react';
 import {
     Badge, Bullseye,
     Button, EmptyState, EmptyStateIcon, EmptyStateVariant,
@@ -26,11 +26,9 @@ import {
 	Table
 } from '@patternfly/react-table/deprecated';
 import {camelIcon, CamelUi} from "../designer/utils/CamelUi";
-import {KaravanApi} from "../api/KaravanApi";
 import Icon from "../Logo";
 import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
 import DownIcon from "@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
-import RefreshIcon from "@patternfly/react-icons/dist/esm/icons/sync-alt-icon";
 import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon";
 import {MainToolbar} from "../designer/MainToolbar";
 import {useAppConfigStore, useProjectsStore, useStatusesStore} from "../api/ProjectStore";
@@ -44,51 +42,8 @@ export const DashboardPage = () => {
         = useStatusesStore((state) => [state.deployments, state.services, state.containers, state.camels,
         state.setDeployments, state.setServices, state.setContainers, state.setCamels], shallow);
     const [filter, setFilter] = useState<string>('');
-    const [loading, setLoading] = useState<boolean>(true);
     const [selectedEnv, setSelectedEnv] = useState<string[]>([config.environment]);
 
-    useEffect(() => {
-        const interval = setInterval(() => {
-            onGetProjects()
-        }, 1300);
-        return () => {
-            clearInterval(interval)
-        };
-    }, []);
-
-    function onGetProjects() {
-        KaravanApi.getConfiguration((config: any) => {
-            KaravanApi.getProjects((projects: Project[]) => {
-                setProjects(projects);
-            });
-            KaravanApi.getAllDeploymentStatuses((statuses: DeploymentStatus[]) => {
-                setDeployments(statuses);
-            });
-            KaravanApi.getAllServiceStatuses((statuses: ServiceStatus[]) => {
-                setServices(statuses);
-            });
-            KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => {
-                setContainers(statuses);
-            });
-            selectedEnv.forEach(env => {
-                KaravanApi.getAllCamelStatuses(env, (statuses: CamelStatus[]) => {
-                    setCamels(statuses);
-                    setLoading(false);
-                    // setState((state) => {
-                    //     statuses.forEach(newStatus => {
-                    //         const index = state.camelStatuses.findIndex(s => s.projectId === newStatus.projectId && s.env === newStatus.env);
-                    //         if (index !== -1) {
-                    //             state.camelStatuses.splice(index, 1);
-                    //         }
-                    //         state.camelStatuses.push(newStatus);
-                    //     })
-                    //     return state;
-                    // })
-                });
-            });
-        });
-    }
-
     function selectEnvironment(name: string, selected: boolean) {
         if (selected && !selectedEnv.includes(name)) {
             setSelectedEnv((state: string[]) => {
@@ -105,9 +60,9 @@ export const DashboardPage = () => {
     function tools() {
         return (<Toolbar id="toolbar-group-types">
             <ToolbarContent>
-                <ToolbarItem>
-                    <Button variant="link" icon={<RefreshIcon/>} onClick={e => onGetProjects()}/>
-                </ToolbarItem>
+                {/*<ToolbarItem>*/}
+                {/*    <Button variant="link" icon={<RefreshIcon/>} onClick={e => onGetProjects()}/>*/}
+                {/*</ToolbarItem>*/}
                 <ToolbarItem>
                     <ToggleGroup aria-label="Default with single selectable">
                         {config.environments.map(env => (
@@ -170,9 +125,9 @@ export const DashboardPage = () => {
     }
 
     function getCamelStatusByEnvironments(name: string): [string, CamelStatus | undefined] [] {
-        return getSelectedEnvironments().map(e => {
+        return selectedEnv.map(e => {
             const env: string = e as string;
-            const status = camels.find(d => d.projectId === name && d.env === env);
+            const status = camels?.find(d => d.projectId === name && d.env === env);
             return [env, status];
         });
     }
@@ -222,12 +177,9 @@ export const DashboardPage = () => {
             <Tr>
                 <Td colSpan={8}>
                     <Bullseye>
-                        {loading && <Spinner className="progress-stepper" diameter="80px" aria-label="Loading..."/>}
-                        {!loading &&
-                            <EmptyState variant={EmptyStateVariant.sm}>
-                                <EmptyStateHeader titleText="No results found" icon={<EmptyStateIcon icon={SearchIcon}/>} headingLevel="h2" />
-                            </EmptyState>
-                        }
+                        <EmptyState variant={EmptyStateVariant.sm}>
+                            <EmptyStateHeader titleText="No results found" icon={<EmptyStateIcon icon={SearchIcon}/>} headingLevel="h2" />
+                        </EmptyState>
                     </Bullseye>
                 </Td>
             </Tr>
diff --git a/karavan-web/karavan-app/src/main/webui/src/index.css b/karavan-web/karavan-app/src/main/webui/src/index.css
index c013e3fd..d7ee69a5 100644
--- a/karavan-web/karavan-app/src/main/webui/src/index.css
+++ b/karavan-web/karavan-app/src/main/webui/src/index.css
@@ -28,7 +28,7 @@
 
 .karavan .nav-buttons .logo {
   margin-top: 16px;
-  margin-bottom: 10px;
+  margin-bottom: 16px;
   width: 32px;
   height: 32px;
 }
diff --git a/karavan-web/karavan-app/src/main/webui/src/main/DataPoller.tsx b/karavan-web/karavan-app/src/main/webui/src/main/DataPoller.tsx
deleted file mode 100644
index 3b917172..00000000
--- a/karavan-web/karavan-app/src/main/webui/src/main/DataPoller.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import React, {useEffect} from 'react';
-
-import {KaravanApi} from "../api/KaravanApi";
-import {KameletApi} from "karavan-core/lib/api/KameletApi";
-import '../designer/karavan.css';
-import {ComponentApi} from "karavan-core/lib/api/ComponentApi";
-import {AppConfig, ContainerStatus} from "../api/ProjectModels";
-import {useAppConfigStore, useStatusesStore} from "../api/ProjectStore";
-import {InfrastructureAPI} from "../designer/utils/InfrastructureAPI";
-import {shallow} from "zustand/shallow";
-
-export const DataPoller = () => {
-
-    const [setConfig] = useAppConfigStore((state) => [state.setConfig], shallow)
-    const [setContainers] = useStatusesStore((state) => [state.setContainers], shallow);
-
-    useEffect(() => {
-        console.log("DataPoller Start");
-        const interval = setInterval(() => {
-            getStatuses();
-        }, 1000);
-        return () => {
-            console.log("DataPoller End");
-            clearInterval(interval);
-        };
-    }, []);
-
-    function getStatuses() {
-        if (KaravanApi.isAuthorized || KaravanApi.authType === 'public') {
-            KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => {
-                setContainers(statuses);
-            });
-        }
-    }
-
-    function getData() {
-        if (KaravanApi.isAuthorized || KaravanApi.authType === 'public') {
-            KaravanApi.getConfiguration((config: AppConfig) => {
-                setConfig(config);
-                InfrastructureAPI.infrastructure = config.infrastructure;
-            });
-            updateKamelets();
-            updateComponents();
-            // updateSupportedComponents(); // not implemented yet
-        }
-    }
-
-    async function updateKamelets(): Promise<void> {
-        await new Promise(resolve => {
-            KaravanApi.getKamelets(yamls => {
-                const kamelets: string[] = [];
-                yamls.split("\n---\n").map(c => c.trim()).forEach(z => kamelets.push(z));
-                KameletApi.saveKamelets(kamelets, true);
-            })
-            KaravanApi.getCustomKameletNames(names => {
-                KameletApi.saveCustomKameletNames(names);
-            })
-        });
-    }
-
-    async function updateComponents(): Promise<void> {
-        await new Promise(resolve => {
-            KaravanApi.getComponents(code => {
-                const components: [] = JSON.parse(code);
-                const jsons: string[] = [];
-                components.forEach(c => jsons.push(JSON.stringify(c)));
-                ComponentApi.saveComponents(jsons, true);
-            })
-        });
-    }
-
-    async function updateSupportedComponents(): Promise<void> {
-        await new Promise(resolve => {
-            KaravanApi.getSupportedComponents(jsons => {
-                ComponentApi.saveSupportedComponents(jsons);
-            })
-        });
-    }
-
-    return (
-        <React.Fragment></React.Fragment>
-    )
-}
\ No newline at end of file
diff --git a/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx b/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx
index 3cf72154..7a3fa986 100644
--- a/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/main/Main.tsx
@@ -1,11 +1,9 @@
-import {Routes, Route, useNavigate, Navigate} from 'react-router-dom';
-import {useLocation} from 'react-router-dom';
-import React, {useEffect, useRef, useState} from "react";
+import {Routes, Route, Navigate} from 'react-router-dom';
+import React, {useEffect, useRef} from "react";
 import {KaravanApi} from "../api/KaravanApi";
 import {Bullseye, Flex, FlexItem, Page, Spinner} from "@patternfly/react-core";
 import Icon from "../Logo";
 import {MainLogin} from "./MainLogin";
-import {Notification} from "./Notification";
 import {DashboardPage} from "../dashboard/DashboardPage";
 import {ProjectsPage} from "../projects/ProjectsPage";
 import {ProjectPage} from "../project/ProjectPage";
@@ -13,12 +11,14 @@ import {ServicesPage} from "../services/ServicesPage";
 import {ContainersPage} from "../containers/ContainersPage";
 import {KnowledgebasePage} from "../knowledgebase/KnowledgebasePage";
 import {ProjectEventBus} from "../api/ProjectEventBus";
-import {Project, ToastMessage} from "../api/ProjectModels";
+import {ToastMessage} from "../api/ProjectModels";
 import {SsoApi} from "../api/SsoApi";
-import {useAppConfigStore, useStatusesStore} from "../api/ProjectStore";
+import {useAppConfigStore} from "../api/ProjectStore";
 import {shallow} from "zustand/shallow";
 import {PageNavigation} from "./PageNavigation";
+import {Notification} from "./Notification";
 import {useMainHook} from "./useMainHook";
+import {MainDataPoller} from "./MainDataPoller";
 
 export const Main = () => {
 
@@ -36,9 +36,6 @@ export const Main = () => {
 
     function effect() {
         console.log("Main Start");
-        const interval = setInterval(() => {
-            getStatuses();
-        }, 1000);
         KaravanApi.getAuthType((authType: string) => {
             console.log("authType", authType);
             if (authType === 'oidc') {
@@ -52,19 +49,9 @@ export const Main = () => {
         });
         return () => {
             console.log("Main End");
-            clearInterval(interval);
         };
     }
 
-    function onLogin(username: string, password: string) {
-        KaravanApi.auth(username, password, (res: any) => {
-            if (res?.status === 200) {
-                getData();
-            } else {
-                toast("Error", "Incorrect username and/or password!", "danger");
-            }
-        });
-    }
 
     function toast(title: string, text: string, variant: 'success' | 'danger' | 'warning' | 'info' | 'custom') {
         ProjectEventBus.sendAlert(new ToastMessage(title, text, variant))
@@ -97,8 +84,9 @@ export const Main = () => {
                 </Flex>
             }
             {!KaravanApi.isAuthorized && KaravanApi.authType === 'basic' &&
-                <MainLogin config={config} onLogin={onLogin}/>}
-            {/*<Notification/>*/}
+                <MainLogin/>}
+            <Notification/>
+            <MainDataPoller/>
         </Page>
     );
 };
diff --git a/karavan-web/karavan-app/src/main/webui/src/main/MainDataPoller.tsx b/karavan-web/karavan-app/src/main/webui/src/main/MainDataPoller.tsx
new file mode 100644
index 00000000..02f9301c
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/webui/src/main/MainDataPoller.tsx
@@ -0,0 +1,62 @@
+import React, {useEffect, useState} from 'react';
+
+import {KaravanApi} from "../api/KaravanApi";
+import '../designer/karavan.css';
+import {
+    AppConfig,
+    CamelStatus,
+    ContainerStatus,
+    DeploymentStatus,
+    PipelineStatus,
+    Project,
+    ServiceStatus
+} from "../api/ProjectModels";
+import {useAppConfigStore, useProjectsStore, useStatusesStore} from "../api/ProjectStore";
+import {shallow} from "zustand/shallow";
+
+export const MainDataPoller = () => {
+
+    const [config, setLoading] = useAppConfigStore((state) => [state.config, state.setLoading], shallow)
+    const [projects, setProjects] = useProjectsStore((state) => [state.projects, state.setProjects], shallow)
+    const [deployments, services, containers, camels, setDeployments, setServices, setContainers, setCamels, setPipelineStatuses]
+        = useStatusesStore((s) => [s.deployments, s.services, s.containers, s.camels,
+        s.setDeployments, s.setServices, s.setContainers, s.setCamels, s.setPipelineStatuses], shallow);
+
+    useEffect(() => {
+        console.log("MainDataPoller Start");
+        const interval = setInterval(() => {
+            getData();
+        }, 1300);
+        return () => {
+            console.log("MainDataPoller Stop");
+            clearInterval(interval);
+        };
+    }, []);
+
+    function getData() {
+        setLoading(true);
+        KaravanApi.getConfiguration((config: AppConfig) => {
+            KaravanApi.getProjects((projects: Project[]) => {
+                setProjects(projects);
+            });
+            KaravanApi.getAllDeploymentStatuses((statuses: DeploymentStatus[]) => {
+                setDeployments(statuses);
+            });
+            KaravanApi.getAllServiceStatuses((statuses: ServiceStatus[]) => {
+                setServices(statuses);
+            });
+            KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => {
+                setContainers(statuses);
+            });
+            KaravanApi.getAllCamelStatuses(config.environment, (statuses: CamelStatus[]) => {
+                setCamels(statuses);
+            });
+            KaravanApi.getPipelineStatuses(config.environment, (status: PipelineStatus[]) => {
+                setPipelineStatuses(status);
+            });
+            setLoading(false);
+        });
+    }
+
+    return (<></>)
+}
\ No newline at end of file
diff --git a/karavan-web/karavan-app/src/main/webui/src/main/MainLogin.tsx b/karavan-web/karavan-app/src/main/webui/src/main/MainLogin.tsx
index 40426253..a493a963 100644
--- a/karavan-web/karavan-app/src/main/webui/src/main/MainLogin.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/main/MainLogin.tsx
@@ -1,61 +1,63 @@
-import React from 'react';
+import React, {useState} from 'react';
 import {
     Bullseye, Card, CardBody, CardTitle, LoginForm, Text
 } from '@patternfly/react-core';
+import {KaravanApi} from "../api/KaravanApi";
+import {useAppConfigStore} from "../api/ProjectStore";
+import {shallow} from "zustand/shallow";
+import {ProjectEventBus} from "../api/ProjectEventBus";
+import {ToastMessage} from "../api/ProjectModels";
 
-interface Props {
-    config: any,
-    onLogin: (username: string, password: string) => void
-}
+export const MainLogin = () => {
 
-interface State {
-    username: string,
-    password: string,
-    isValidUsername: boolean,
-    isValidPassword: boolean,
-    isRememberMeChecked: boolean,
-}
+    const [config] = useAppConfigStore((state) => [state.config], shallow)
+    const [username, setUsername] = useState<string>();
+    const [password, setPassword] = useState<string>();
+    const [isValidUsername, setIsValidUsername] = useState<boolean>(true);
+    const [isValidPassword, setIsValidPassword] = useState<boolean>(true);
+    const [isRememberMeChecked, setIsRememberMeChecked] = useState<boolean>(false);
 
-export class MainLogin extends React.Component<Props, State> {
-    public state: State = {
-        username: "",
-        password: "",
-        isValidUsername: true,
-        isValidPassword: true,
-        isRememberMeChecked: false,
-    }
-
-    onLoginButtonClick = (event: any) => {
+    function onLoginButtonClick(event: any) {
         event.preventDefault();
-        this.props.onLogin?.call(this, this.state.username, this.state.password);
+        if (username && password) {
+            onLogin(username, password);
+        }
     }
 
-    render() {
-        return (
-            <Bullseye>
-                <Card isFlat isCompact>
-                    <CardTitle>
-                        <img alt="karavan-logo" src="karavan-logo-light.png" className="login-logo"/>
-                        <Text component="h3" style={{width:"fit-content", marginLeft:"auto"}}>{this.props.config.version}</Text>
-                    </CardTitle>
-                    <CardBody>
-                        <LoginForm
-                            showHelperText={true}
-                            usernameLabel="Username"
-                            usernameValue={this.state.username}
-                            onChangeUsername={(_event, value) => this.setState({username: value})}
-                            isValidUsername={this.state.isValidUsername}
-                            passwordLabel="Password"
-                            passwordValue={this.state.password}
-                            isShowPasswordEnabled
-                            onChangePassword={(_event, value) => this.setState({password: value})}
-                            isValidPassword={this.state.isValidPassword}
-                            onLoginButtonClick={this.onLoginButtonClick}
-                            loginButtonLabel="Log in"
-                        />
-                    </CardBody>
-                </Card>
-            </Bullseye>
-        );
+    function onLogin(username: string, password: string) {
+        KaravanApi.auth(username, password, (res: any) => {
+            if (res?.status === 200) {
+            } else {
+                ProjectEventBus.sendAlert(new ToastMessage("Error", "Incorrect username and/or password!", "danger"))
+            }
+        });
     }
+
+    return (
+        <Bullseye>
+            <Card isFlat isCompact>
+                <CardTitle>
+                    <img alt="karavan-logo" src="karavan-logo-light.png" className="login-logo"/>
+                    <Text component="h3"
+                          style={{width: "fit-content", marginLeft: "auto"}}>{config.version}</Text>
+                </CardTitle>
+                <CardBody>
+                    <LoginForm
+                        showHelperText={true}
+                        usernameLabel="Username"
+                        usernameValue={username}
+                        onChangeUsername={(_event, value) => setUsername(value)}
+                        isValidUsername={isValidUsername}
+                        passwordLabel="Password"
+                        passwordValue={password}
+                        isShowPasswordEnabled
+                        onChangePassword={(_event, value) => setPassword(value)}
+                        isValidPassword={isValidPassword}
+                        onLoginButtonClick={onLoginButtonClick}
+                        loginButtonLabel="Log in"
+                    />
+                </CardBody>
+            </Card>
+        </Bullseye>
+    );
 }
\ No newline at end of file
diff --git a/karavan-web/karavan-app/src/main/webui/src/main/PageNavigation.tsx b/karavan-web/karavan-app/src/main/webui/src/main/PageNavigation.tsx
index 648add20..2c35c44b 100644
--- a/karavan-web/karavan-app/src/main/webui/src/main/PageNavigation.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/main/PageNavigation.tsx
@@ -4,7 +4,7 @@ import {
     Flex,
     FlexItem,
     Tooltip,
-    Divider, Popover, Badge
+    Divider, Popover, Badge, Spinner, Bullseye
 } from '@patternfly/react-core';
 import {KaravanApi} from "../api/KaravanApi";
 import '../designer/karavan.css';
@@ -33,7 +33,7 @@ class MenuItem {
 
 export const PageNavigation = () => {
 
-    const [config] = useAppConfigStore((state) => [state.config], shallow)
+    const [config, loading] = useAppConfigStore((state) => [state.config, state.loading], shallow)
     const [setFile] = useFileStore((state) => [state.setFile], shallow)
     const [setStatus, setPodName] = useDevModeStore((state) => [state.setStatus, state.setPodName], shallow)
     const [showUser, setShowUser] = useState<boolean>(false);
@@ -58,10 +58,14 @@ export const PageNavigation = () => {
     return (<Flex className="nav-buttons" direction={{default: "column"}} style={{height: "100%"}}
                   spaceItems={{default: "spaceItemsNone"}}>
         <FlexItem alignSelf={{default: "alignSelfCenter"}}>
-            <Tooltip className="logo-tooltip" content={"Apache Camel Karavan " + config.version}
-                     position={"right"}>
-                {Icon()}
-            </Tooltip>
+            <Bullseye>
+                {loading && <Spinner style={{position: "absolute"}} diameter="40px" aria-label="Loading..."/>}
+                <Tooltip className="logo-tooltip" content={"Apache Camel Karavan " + config.version}
+                         position={"right"}>
+                    {Icon()}
+                </Tooltip>
+            </Bullseye>
+
         </FlexItem>
         {getMenu().map(page =>
             <FlexItem key={page.pageId} className={pageId === page.pageId ? "nav-button-selected" : ""}>
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/ProjectDataPoller.tsx b/karavan-web/karavan-app/src/main/webui/src/project/ProjectDataPoller.tsx
new file mode 100644
index 00000000..2b86d831
--- /dev/null
+++ b/karavan-web/karavan-app/src/main/webui/src/project/ProjectDataPoller.tsx
@@ -0,0 +1,61 @@
+import React, {useEffect} from 'react';
+
+import {KaravanApi} from "../api/KaravanApi";
+import '../designer/karavan.css';
+import {useAppConfigStore, useProjectStore} from "../api/ProjectStore";
+import {shallow} from "zustand/shallow";
+import {CamelStatus, DeploymentStatus, PipelineStatus} from "../api/ProjectModels";
+
+export const ProjectDataPoller = () => {
+
+    const [config] = useAppConfigStore((state) => [state.config], shallow)
+    const [project, setMemory, setJvm, setContext, refreshTrace, setTrace] = useProjectStore((s) =>
+        [s.project, s.setMemory, s.setJvm, s.setContext, s.refreshTrace, s.setTrace], shallow);
+
+    useEffect(() => {
+        console.log("ProjectDataPoller Start");
+        const interval = setInterval(() => {
+            onRefreshStatus();
+        }, 1000);
+        return () => {
+            console.log("ProjectDataPoller Stop");
+            clearInterval(interval)
+        };
+    }, [project, refreshTrace]);
+
+    function onRefreshStatus() {
+        const projectId = project.projectId;
+        KaravanApi.getDevModeStatus(projectId, "memory", res => {
+            if (res.status === 200) {
+                setMemory(JSON.parse(res.data.status));
+            } else {
+                setMemory({});
+            }
+        })
+        KaravanApi.getDevModeStatus(projectId, "jvm", res => {
+            if (res.status === 200) {
+                setJvm(JSON.parse(res.data.status));
+            } else {
+                setJvm({});
+            }
+        })
+        KaravanApi.getDevModeStatus(projectId, "context", res => {
+            if (res.status === 200) {
+                setContext(JSON.parse(res.data.status));
+            } else {
+                setContext({});
+            }
+        })
+        if (refreshTrace) {
+            KaravanApi.getDevModeStatus(projectId, "trace", res => {
+                if (res.status === 200) {
+                    setTrace(JSON.parse(res.data.status));
+                } else {
+                    setTrace({});
+                }
+            })
+        }
+    }
+
+    return (<></>)
+}
\ No newline at end of file
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx b/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx
index cb6bb96a..a771570c 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx
@@ -14,6 +14,7 @@ import {FileEditor} from "./file/FileEditor";
 import {shallow} from "zustand/shallow";
 import {useParams} from "react-router-dom";
 import {KaravanApi} from "../api/KaravanApi";
+import {ProjectDataPoller} from "./ProjectDataPoller";
 
 export const ProjectPage = () => {
 
@@ -53,6 +54,7 @@ export const ProjectPage = () => {
             {showFilePanel && <FileEditor/>}
             {!showFilePanel && <ProjectPanel/>}
             <ProjectLogPanel/>
+            <ProjectDataPoller/>
         </PageSection>
     )
 }
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx b/karavan-web/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx
index beceb5c8..4a21f9ad 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {useEffect, useState} from 'react';
+import React from 'react';
 import {
     Card,
     CardBody, Flex, FlexItem, Divider, PageSection
@@ -23,55 +23,19 @@ import '../../designer/karavan.css';
 import {InfoContainer} from "./InfoContainer";
 import {InfoContext} from "./InfoContext";
 import {InfoMemory} from "./InfoMemory";
-import {KaravanApi} from "../../api/KaravanApi";
 import {useProjectStore, useStatusesStore} from "../../api/ProjectStore";
 import {shallow} from "zustand/shallow";
 import {ContainerStatus} from "../../api/ProjectModels";
 
 export const DashboardTab = () => {
 
-    const [project] = useProjectStore((state) => [state.project], shallow);
+    const [project, memory, jvm, context] = useProjectStore((state) =>
+        [state.project, state.memory, state.jvm, state.context], shallow);
     const [containers] = useStatusesStore((state) => [state.containers], shallow);
-    const [memory, setMemory] = useState({});
-    const [jvm, setJvm] = useState({});
-    const [context, setContext] = useState({});
-
-    useEffect(() => {
-        const interval = setInterval(() => {
-            onRefreshStatus();
-        }, 1000);
-        return () => {
-            clearInterval(interval)
-        };
-    }, []);
-
-    function onRefreshStatus() {
-        const projectId = project.projectId;
-        KaravanApi.getDevModeStatus(projectId, "memory", res => {
-            if (res.status === 200) {
-                setMemory(JSON.parse(res.data.status));
-            } else {
-                setMemory({});
-            }
-        })
-        KaravanApi.getDevModeStatus(projectId, "jvm", res => {
-            if (res.status === 200) {
-                setJvm(JSON.parse(res.data.status));
-            } else {
-                setJvm({});
-            }
-        })
-        KaravanApi.getDevModeStatus(projectId, "context", res => {
-            if (res.status === 200) {
-                setContext(JSON.parse(res.data.status));
-            } else {
-                setContext({});
-            }
-        })
-    }
 
     const containerStatus = containers.filter(c => c.containerName === project.projectId).at(0);
     const showConsole = containerStatus?.state === 'running'
+
     return (
         <PageSection className="project-tab-panel" padding={{default: "padding"}}>
             <Card className="project-development">
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx b/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
index da56443a..a5ee8418 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
@@ -39,7 +39,6 @@ export const FileToolbar = () => {
 
 
     useEffect(() => {
-        console.log("ProjectToolbar useEffect", isPushing, project.lastCommitTimestamp);
     }, [project, file]);
 
     function push () {
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/log/ProjectLogPanel.tsx b/karavan-web/karavan-app/src/main/webui/src/project/log/ProjectLogPanel.tsx
index ba5e4d7e..c7eec061 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/log/ProjectLogPanel.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/log/ProjectLogPanel.tsx
@@ -25,8 +25,6 @@ export const ProjectLogPanel = () => {
     const [currentPodName, setCurrentPodName] = useState<string | undefined>(undefined);
 
     useEffect(() => {
-        const containerStatus = containers.filter(c => c.containerName === podName).at(0);
-        console.log("ProjectLogPanel", showLog, type, podName, containerStatus);
         const controller = new AbortController();
         if (showLog && type !== 'none' && podName !== undefined) {
             const f = KaravanApi.fetchData(type, podName, controller).then(value => {
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx b/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
index e2209f89..83b8b990 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
@@ -14,8 +14,8 @@ export const ProjectPipelineTab = () => {
         <PageSection className="project-tab-panel" padding={{default: "padding"}}>
             <div className="project-operations">
                 {/*{["dev", "test", "prod"].map(env =>*/}
-                {["dev"].map(env =>
-                    <ProjectStatus key={env} project={project} config={config} env={env}/>
+                {config.environments.map(env =>
+                    <ProjectStatus env={env}/>
                 )}
             </div>
         </PageSection>
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx b/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
index ae022589..698621b8 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, {useState} from 'react';
 import {
     Button,
     DescriptionList,
@@ -14,116 +14,76 @@ import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
 import DownIcon from "@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
 import ClockIcon from "@patternfly/react-icons/dist/esm/icons/clock-icon";
 import DeleteIcon from "@patternfly/react-icons/dist/esm/icons/times-circle-icon";
-import {CamelStatus, DeploymentStatus, PipelineStatus, ContainerStatus, Project} from "../../api/ProjectModels";
-import {useLogStore} from "../../api/ProjectStore";
+import {CamelStatus, DeploymentStatus, ContainerStatus} from "../../api/ProjectModels";
+import {useLogStore, useProjectStore, useStatusesStore} from "../../api/ProjectStore";
+import {shallow} from "zustand/shallow";
 
 interface Props {
-    project: Project,
-    config: any,
     env: string,
 }
 
-interface State {
-    pipelineStatus?: PipelineStatus,
-    deploymentStatus?: DeploymentStatus,
-    podStatuses: ContainerStatus[],
-    camelStatus?: CamelStatus,
-    isPushing: boolean,
-    isBuilding: boolean,
-    isRolling: boolean,
-    showDeleteConfirmation: boolean,
-    deleteEntity?: 'pod' | 'deployment' | 'pipelinerun',
-    deleteEntityName?: string,
-    deleteEntityEnv?: string,
-}
-
-export class ProjectStatus extends React.Component<Props, State> {
-
-    public state: State = {
-        podStatuses: [],
-        isPushing: false,
-        isBuilding: false,
-        isRolling: false,
-        showDeleteConfirmation: false,
-    };
-    interval: any;
-
-    componentDidMount() {
-        this.interval = setInterval(() => this.onRefreshStatus(), 700);
-    }
-
-    componentWillUnmount() {
-        clearInterval(this.interval);
-    }
+export const ProjectStatus = (props: Props) => {
 
-    onRefreshStatus = () => {
-        const projectId = this.props.project.projectId;
-        const env = this.props.env;
-        if (this.props.project) {
-            KaravanApi.getProjectPipelineStatus(projectId, env, (status?: PipelineStatus) => {
-                this.setState({pipelineStatus: status});
-            });
-            KaravanApi.getProjectDeploymentStatus(projectId, env, (status?: DeploymentStatus) => {
-                this.setState({deploymentStatus: status});
-            });
-            KaravanApi.getProjectPodStatuses(projectId, env, (statuses: ContainerStatus[]) => {
-                this.setState({podStatuses: statuses});
-            });
-            KaravanApi.getProjectCamelStatus(projectId, env, (status: CamelStatus) => {
-                this.setState({camelStatus: status});
-            });
-        }
-    }
+    const [project] = useProjectStore((s) => [s.project], shallow);
+    const [containers, deployments, camels, pipelineStatuses] =
+        useStatusesStore((s) => [s.containers, s.deployments, s.camels, s.pipelineStatuses], shallow);
+    const [isPushing, setIsPushing] = useState<boolean>(false);
+    const [isBuilding, setIsBuilding] = useState<boolean>(false);
+    const [isRolling, setIsRolling] = useState<boolean>(false);
+    const [showDeleteConfirmation, setShowDeleteConfirmation] = useState<boolean>(false);
+    const [deleteEntityType, setDeleteEntityType] = useState<'pod' | 'deployment' | 'pipelinerun'>('pod');
+    const [deleteEntityName, setDeleteEntityName] = useState<string>();
+    const [deleteEntityEnv, setDeleteEntityEnv] = useState<string>();
 
-    deleteEntity = (type: 'pod' | 'deployment' | 'pipelinerun', name: string, environment: string) => {
+    function deleteEntity (type: 'pod' | 'deployment' | 'pipelinerun', name: string, environment: string)  {
         switch (type) {
             case "deployment":
                 KaravanApi.deleteDeployment(environment, name, (res: any) => {
                     // if (Array.isArray(res) && Array.from(res).length > 0)
-                    // this.onRefresh();
+                    // onRefresh();
                 });
                 break;
             case "pod":
                 KaravanApi.deleteContainer(environment, 'project', name, (res: any) => {
                     // if (Array.isArray(res) && Array.from(res).length > 0)
-                    // this.onRefresh();
+                    // onRefresh();
                 });
                 break;
             case "pipelinerun":
                 KaravanApi.stopPipelineRun(environment, name, (res: any) => {
                     // if (Array.isArray(res) && Array.from(res).length > 0)
-                    // this.onRefresh();
+                    // onRefresh();
                 });
                 break;
         }
     }
 
-    build = () => {
-        this.setState({isBuilding: true});
-        KaravanApi.pipelineRun(this.props.project, this.props.env, res => {
+    function build () {
+        setIsBuilding(true);
+        KaravanApi.pipelineRun(project, env, res => {
             if (res.status === 200 || res.status === 201) {
-                this.setState({isBuilding: false});
+                setIsBuilding(false);
             } else {
                 // Todo notification
             }
         });
     }
 
-    rollout = () => {
-        this.setState({isRolling: true});
-        KaravanApi.rolloutDeployment(this.props.project.projectId, this.props.env, res => {
+    function rollout () {
+        setIsRolling(true);
+        KaravanApi.rolloutDeployment(project.projectId, env, res => {
             console.log(res)
             if (res.status === 200 || res.status === 201) {
-                this.setState({isRolling: false});
+                setIsRolling(false);
             } else {
                 // Todo notification
             }
         });
     }
 
-    buildButton = (env: string) => {
-        const {isBuilding, isPushing, pipelineStatus} = this.state;
-        const isRunning = pipelineStatus?.result === 'Running';
+    function buildButton (env: string) {
+        const status = pipelineStatuses.filter(p => p.projectId === project.projectId).at(0);
+        const isRunning = status?.result === 'Running';
         return (<Tooltip content="Start build pipeline" position={"left"}>
             <Button isLoading={isBuilding ? true : undefined}
                     isDisabled={isBuilding || isRunning || isPushing}
@@ -131,41 +91,41 @@ export class ProjectStatus extends React.Component<Props, State> {
                     variant="secondary"
                     className="project-button"
                     icon={!isBuilding ? <BuildIcon/> : <div></div>}
-                    onClick={e => this.build()}>
+                    onClick={e => build()}>
                 {isBuilding ? "..." : "Build"}
             </Button>
         </Tooltip>)
     }
 
-    rolloutButton = () => {
-        const isRolling = this.state.isRolling;
+    function rolloutButton () {
         return (<Tooltip content="Rollout deployment" position={"left"}>
             <Button isLoading={isRolling ? true : undefined} size="sm" variant="secondary"
                     className="project-button"
                     icon={!isRolling ? <RolloutIcon/> : <div></div>}
-                    onClick={e => this.rollout()}>
+                    onClick={e => rollout()}>
                 {isRolling ? "..." : "Rollout"}
             </Button>
         </Tooltip>)
     }
 
-    deleteDeploymentButton = (env: string) => {
+    function deleteDeploymentButton (env: string) {
         return (<Tooltip content="Delete deployment" position={"left"}>
             <Button size="sm" variant="secondary"
                     className="project-button"
                     icon={<DeleteIcon/>}
-                    onClick={e => this.setState({
-                        showDeleteConfirmation: true,
-                        deleteEntity: "deployment",
-                        deleteEntityEnv: env,
-                        deleteEntityName: this.props.project?.projectId
-                    })}>
+                    onClick={e => {
+                        setShowDeleteConfirmation(true);
+                        setDeleteEntityType("deployment");
+                        setDeleteEntityEnv(env);
+                        setDeleteEntityName(project?.projectId);
+                    }}>
                 {"Delete"}
             </Button>
         </Tooltip>)
     }
 
-    getReplicasPanel(env: string, deploymentStatus?: DeploymentStatus) {
+    function getReplicasPanel(env: string) {
+        const deploymentStatus = deployments.find(d => d.name === project?.projectId);
         const ok = (deploymentStatus && deploymentStatus?.readyReplicas > 0
             && (deploymentStatus.unavailableReplicas === 0 || deploymentStatus.unavailableReplicas === undefined || deploymentStatus.unavailableReplicas === null)
             && deploymentStatus?.replicas === deploymentStatus?.readyReplicas)
@@ -185,19 +145,20 @@ export class ProjectStatus extends React.Component<Props, State> {
                     </LabelGroup>}
                     {deploymentStatus === undefined && <Label icon={<DownIcon/>} color={"grey"}>No deployments</Label>}
                 </FlexItem>
-                <FlexItem>{env === "dev" && this.deleteDeploymentButton(env)}</FlexItem>
+                <FlexItem>{env === "dev" && deleteDeploymentButton(env)}</FlexItem>
             </Flex>
         )
     }
 
-    getPodsPanel(env: string, podStatuses: ContainerStatus[]) {
+    function getPodsPanel(env: string) {
+        const podStatuses = containers.filter(d => d.projectId === project?.projectId);
         return (
             <Flex justifyContent={{default: "justifyContentSpaceBetween"}}
                   alignItems={{default: "alignItemsFlexStart"}}>
                 <FlexItem>
                     {podStatuses.length === 0 && <Label icon={<DownIcon/>} color={"grey"}>No pods</Label>}
                     <LabelGroup numLabels={2} isVertical>
-                        {podStatuses.map(pod => {
+                        {podStatuses.map((pod: ContainerStatus) => {
                                 const ready = pod.state === 'running';
                                 return (
                                     <Tooltip key={pod.containerName} content={pod.state}>
@@ -213,12 +174,12 @@ export class ProjectStatus extends React.Component<Props, State> {
                                                 {pod.containerName}
                                             </Button>
                                             <Tooltip content={"Delete Pod"}>
-                                                <Button icon={<DeleteIcon/>} variant="link" onClick={e => this.setState({
-                                                    showDeleteConfirmation: true,
-                                                    deleteEntity: "pod",
-                                                    deleteEntityEnv: env,
-                                                    deleteEntityName: pod.containerName
-                                                })}></Button>
+                                                <Button icon={<DeleteIcon/>} variant="link" onClick={e => {
+                                                    setShowDeleteConfirmation(true);
+                                                    setDeleteEntityType("pod");
+                                                    setDeleteEntityEnv(env);
+                                                    setDeleteEntityName(pod.containerName);
+                                                }}></Button>
                                             </Tooltip>
                                         </Label>
                                     </Tooltip>
@@ -227,23 +188,23 @@ export class ProjectStatus extends React.Component<Props, State> {
                         )}
                     </LabelGroup>
                 </FlexItem>
-                <FlexItem>{env === "dev" && this.rolloutButton()}</FlexItem>
+                <FlexItem>{env === "dev" && rolloutButton()}</FlexItem>
             </Flex>
         )
     }
 
-    getStatusColor(status?: string) {
+    function getStatusColor(status?: string) {
         if (status === 'UP') return 'green';
         if (status === 'DOWN') return 'red';
         if (status === 'UNDEFINED') return 'grey';
     }
 
-    getStatusIcon(status?: string) {
+    function getStatusIcon(status?: string) {
         return (status === 'UP' ? <UpIcon/> : <DownIcon/>)
     }
 
-    getHealthPanel(env: string) {
-        const status = this.state.camelStatus;
+    function getHealthPanel(env: string) {
+        const status = camels;
         // const routesStatus = status?.routesStatus;
         // const consumersStatus = status?.consumerStatus;
         // const contextStatus = status?.contextStatus;
@@ -251,19 +212,19 @@ export class ProjectStatus extends React.Component<Props, State> {
         return (
             <LabelGroup numLabels={4}>
                 {/*{contextVersion &&*/}
-                {/*    <Label icon={this.getStatusIcon(contextStatus)}*/}
-                {/*           color={this.getStatusColor(contextStatus)}>{contextVersion}</Label>}*/}
-                {/*<Label icon={this.getStatusIcon(contextStatus)}*/}
-                {/*       color={this.getStatusColor(contextStatus)}>Context</Label>*/}
-                {/*<Label icon={this.getStatusIcon(consumersStatus)}*/}
-                {/*       color={this.getStatusColor(consumersStatus)}>Consumers</Label>*/}
-                {/*<Label icon={this.getStatusIcon(routesStatus)} color={this.getStatusColor(routesStatus)}>Routes</Label>*/}
+                {/*    <Label icon={getStatusIcon(contextStatus)}*/}
+                {/*           color={getStatusColor(contextStatus)}>{contextVersion}</Label>}*/}
+                {/*<Label icon={getStatusIcon(contextStatus)}*/}
+                {/*       color={getStatusColor(contextStatus)}>Context</Label>*/}
+                {/*<Label icon={getStatusIcon(consumersStatus)}*/}
+                {/*       color={getStatusColor(consumersStatus)}>Consumers</Label>*/}
+                {/*<Label icon={getStatusIcon(routesStatus)} color={getStatusColor(routesStatus)}>Routes</Label>*/}
             </LabelGroup>
         )
     }
 
-    getPipelineState(env: string) {
-        const status = this.state.pipelineStatus;
+    function getPipelineState(env: string) {
+        const status = pipelineStatuses.filter(p => p.projectId === project.projectId).at(0);
         const pipeline = status?.pipelineName;
         const pipelineResult = status?.result;
         let lastPipelineRunTime = 0;
@@ -292,14 +253,12 @@ export class ProjectStatus extends React.Component<Props, State> {
                                     </Button>
                                     : "No pipeline"}
                                 {isRunning && <Tooltip content={"Stop PipelineRun"}>
-                                    <Button icon={<DeleteIcon/>} variant="link" onClick={e =>
-                                        this.setState({
-                                            showDeleteConfirmation: true,
-                                            deleteEntity: "pipelinerun",
-                                            deleteEntityEnv: env,
-                                            deleteEntityName: pipeline
-                                        })
-                                    }></Button>
+                                    <Button icon={<DeleteIcon/>} variant="link" onClick={e => {
+                                        setShowDeleteConfirmation(true);
+                                        setDeleteEntityType("pipelinerun");
+                                        setDeleteEntityEnv(env);
+                                        setDeleteEntityName(pipeline);
+                                        }}></Button>
                                 </Tooltip>}
                             </Label>
                             {pipeline !== undefined && showTime === true && lastPipelineRunTime !== undefined &&
@@ -307,75 +266,71 @@ export class ProjectStatus extends React.Component<Props, State> {
                         </LabelGroup>
                     </Tooltip>
                 </FlexItem>
-                <FlexItem>{env === "dev" && this.buildButton(env)}</FlexItem>
+                <FlexItem>{env === "dev" && buildButton(env)}</FlexItem>
             </Flex>
         )
     }
 
-    getDeleteConfirmation() {
-        const {deleteEntity, deleteEntityEnv, deleteEntityName} = this.state;
+    function getDeleteConfirmation() {
         return (<Modal
             className="modal-delete"
             title="Confirmation"
-            isOpen={this.state.showDeleteConfirmation}
-            onClose={() => this.setState({showDeleteConfirmation: false})}
+            isOpen={showDeleteConfirmation}
+            onClose={() => setShowDeleteConfirmation(false)}
             actions={[
                 <Button key="confirm" variant="primary" onClick={e => {
                     if (deleteEntityEnv && deleteEntityName && deleteEntity) {
-                        this.deleteEntity(deleteEntity, deleteEntityName, deleteEntityEnv);
-                        this.setState({showDeleteConfirmation: false});
+                        deleteEntity(deleteEntityType, deleteEntityName, deleteEntityEnv);
+                        setShowDeleteConfirmation(false);
                     }
                 }}>Delete
                 </Button>,
                 <Button key="cancel" variant="link"
-                        onClick={e => this.setState({showDeleteConfirmation: false})}>Cancel</Button>
+                        onClick={e => setShowDeleteConfirmation(false)}>Cancel</Button>
             ]}
-            onEscapePress={e => this.setState({showDeleteConfirmation: false})}>
+            onEscapePress={e => setShowDeleteConfirmation(false)}>
             <div>{"Delete " + deleteEntity + " " + deleteEntityName + "?"}</div>
         </Modal>)
     }
 
-    render() {
-        const {deploymentStatus, podStatuses} = this.state;
-        const {env} = this.props;
-        return (
-            <Card className="project-status">
-                <CardBody>
-                    <DescriptionList isHorizontal>
-                        <DescriptionListGroup>
-                            <DescriptionListTerm>Environment</DescriptionListTerm>
-                            <DescriptionListDescription>
-                                <Badge className="badge">{env}</Badge>
-                            </DescriptionListDescription>
-                        </DescriptionListGroup>
-                        <DescriptionListGroup>
-                            <DescriptionListTerm>Pipeline</DescriptionListTerm>
-                            <DescriptionListDescription>
-                                {this.getPipelineState(env)}
-                            </DescriptionListDescription>
-                        </DescriptionListGroup>
-                        <DescriptionListGroup>
-                            <DescriptionListTerm>Deployment</DescriptionListTerm>
-                            <DescriptionListDescription>
-                                {this.getReplicasPanel(env, deploymentStatus)}
-                            </DescriptionListDescription>
-                        </DescriptionListGroup>
-                        <DescriptionListGroup>
-                            <DescriptionListTerm>Pods</DescriptionListTerm>
-                            <DescriptionListDescription>
-                                {this.getPodsPanel(env, podStatuses)}
-                            </DescriptionListDescription>
-                        </DescriptionListGroup>
-                        <DescriptionListGroup>
-                            <DescriptionListTerm>Camel health</DescriptionListTerm>
-                            <DescriptionListDescription>
-                                {this.getHealthPanel(env)}
-                            </DescriptionListDescription>
-                        </DescriptionListGroup>
-                    </DescriptionList>
-                </CardBody>
-                {this.state.showDeleteConfirmation && this.getDeleteConfirmation()}
-            </Card>
-        )
-    }
+    const env = props.env;
+    return (
+        <Card className="project-status">
+            <CardBody>
+                <DescriptionList isHorizontal>
+                    <DescriptionListGroup>
+                        <DescriptionListTerm>Environment</DescriptionListTerm>
+                        <DescriptionListDescription>
+                            <Badge className="badge">{env}</Badge>
+                        </DescriptionListDescription>
+                    </DescriptionListGroup>
+                    <DescriptionListGroup>
+                        <DescriptionListTerm>Pipeline</DescriptionListTerm>
+                        <DescriptionListDescription>
+                            {getPipelineState(env)}
+                        </DescriptionListDescription>
+                    </DescriptionListGroup>
+                    <DescriptionListGroup>
+                        <DescriptionListTerm>Deployment</DescriptionListTerm>
+                        <DescriptionListDescription>
+                            {getReplicasPanel(env)}
+                        </DescriptionListDescription>
+                    </DescriptionListGroup>
+                    <DescriptionListGroup>
+                        <DescriptionListTerm>Pods</DescriptionListTerm>
+                        <DescriptionListDescription>
+                            {getPodsPanel(env)}
+                        </DescriptionListDescription>
+                    </DescriptionListGroup>
+                    <DescriptionListGroup>
+                        <DescriptionListTerm>Camel health</DescriptionListTerm>
+                        <DescriptionListDescription>
+                            {getHealthPanel(env)}
+                        </DescriptionListDescription>
+                    </DescriptionListGroup>
+                </DescriptionList>
+            </CardBody>
+            {showDeleteConfirmation && getDeleteConfirmation()}
+        </Card>
+    )
 }
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceModal.tsx b/karavan-web/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceModal.tsx
index 6940f8a8..d734018d 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceModal.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceModal.tsx
@@ -58,8 +58,8 @@ export const RunnerInfoTraceModal = (props: Props) => {
                             <DescriptionListTerm>Nodes</DescriptionListTerm>
                         </DescriptionListGroup>
                     </DescriptionList>
-                    {props.nodes.map((node: any) => (
-                        <FlexItem>
+                    {props.nodes.map((node: any, index: number) => (
+                        <FlexItem key={node.uid + "-" + index}>
                             <Button variant={node.uid === activeNode.uid ? "secondary" : "link"}
                                     icon={node.nodeId === undefined ? <ArrowRightIcon/> : undefined}
                                     onClick={event => {setActiveNode(node)}}>
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceNode.tsx b/karavan-web/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceNode.tsx
index 90d71de6..5a470290 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceNode.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceNode.tsx
@@ -42,8 +42,8 @@ export const RunnerInfoTraceNode = (props: Props) => {
                                 <DescriptionListTerm>Headers</DescriptionListTerm>
                             </DescriptionListGroup>
                             <DataList aria-label="Compact data list example" isCompact>
-                                {headers.map((header: any) => (
-                                    <DataListItem key={header[0]} aria-labelledby="compact-item1">
+                                {headers.map((header: any, index: number) => (
+                                    <DataListItem key={header[0] + "-" + index} aria-labelledby="compact-item1">
                                         <DataListItemRow>
                                             <DataListItemCells
                                                 dataListCells={[
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx b/karavan-web/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx
index c62aa090..d4420f2f 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {useEffect, useState} from 'react';
+import React, {useState} from 'react';
 import {
     Bullseye,
     Button,
@@ -39,40 +39,16 @@ import {
 	Table
 } from '@patternfly/react-table/deprecated';
 import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon";
-import {KaravanApi} from "../../api/KaravanApi";
 import {useProjectStore} from "../../api/ProjectStore";
+import {shallow} from "zustand/shallow";
 
 
 export const TraceTab = () => {
 
-    const {project} = useProjectStore();
-    const [trace, setTrace] = useState<any>({});
+    const [refreshTrace, setRefreshTrace, trace] = useProjectStore((state) =>
+        [state.refreshTrace, state.setRefreshTrace, state.trace], shallow);
     const [nodes, setNodes] = useState([{}]);
-    const [isOpen, setIsOpen] = useState(false);
-    const [refreshTrace, setRefreshTrace] = useState(false);
-
-    useEffect(() => {
-        const interval = setInterval(() => {
-            onRefreshStatus();
-        }, 1000);
-        return () => {
-            clearInterval(interval)
-        };
-    }, [refreshTrace]);
-
-
-    function onRefreshStatus() {
-        const projectId = project.projectId;
-        if (refreshTrace) {
-            KaravanApi.getDevModeStatus(projectId, "trace", res => {
-                if (res.status === 200) {
-                    setTrace(JSON.parse(res.data.status));
-                } else {
-                    setTrace({});
-                }
-            })
-        }
-    }
+    const [isOpen, setIsOpen] = useState<boolean>(false);
 
     function closeModal() {
         setIsOpen(false);
@@ -134,7 +110,7 @@ export const TraceTab = () => {
                             <Td>
                                 <Button style={{padding: '0'}} variant={"link"}
                                         onClick={e => {
-                                            setTrace(trace);
+                                            // setTrace(trace);
                                             setNodes(getNodes(exchangeId));
                                             setIsOpen(true);
                                         }}>
diff --git a/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx b/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
index 64c5fd40..ef49c8aa 100644
--- a/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
@@ -49,36 +49,10 @@ export const ProjectsPage = () => {
     const [projects] = useProjectsStore((state) => [state.projects], shallow)
     const [operation] = useProjectStore((state) => [state.operation], shallow)
     const [filter, setFilter] = useState<string>('');
-    const [loading, setLoading] = useState<boolean>(false);
-
-    useEffect(() => {
-        const interval = setInterval(() => {
-            if (projects.length === 0) setLoading(true);
-            if (!["create", "delete", "select", "copy"].includes(operation)) {
-                refresh();
-            }
-        }, 1300);
-        return () => {
-            clearInterval(interval)
-        };
-    }, [operation]);
-
-    function refresh() {
-        ProjectService.refreshProjects();
-        ProjectService.refreshAllDeploymentStatuses();
-        ProjectService.refreshAllContainerStatuses();
-        setLoading(false);
-    }
 
     function getTools() {
         return <Toolbar id="toolbar-group-types">
             <ToolbarContent>
-                <ToolbarItem>
-                    <Button variant="link" icon={<RefreshIcon/>} onClick={e => {
-                        setLoading(true);
-                        refresh();
-                    }}/>
-                </ToolbarItem>
                 <ToolbarItem>
                     <TextInput className="text-field" type="search" id="search" name="search"
                                autoComplete="off" placeholder="Search by name"
@@ -106,14 +80,10 @@ export const ProjectsPage = () => {
             <Tr>
                 <Td colSpan={8}>
                     <Bullseye>
-                        {loading &&
-                            <Spinner className="progress-stepper" diameter="80px" aria-label="Loading..."/>}
-                        {!loading &&
-                            <EmptyState variant={EmptyStateVariant.sm}>
-                                <EmptyStateHeader titleText="No results found"
-                                                  icon={<EmptyStateIcon icon={SearchIcon}/>} headingLevel="h2"/>
-                            </EmptyState>
-                        }
+                        <EmptyState variant={EmptyStateVariant.sm}>
+                            <EmptyStateHeader titleText="No results found"
+                                              icon={<EmptyStateIcon icon={SearchIcon}/>} headingLevel="h2"/>
+                        </EmptyState>
                     </Bullseye>
                 </Td>
             </Tr>
diff --git a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
index 934f3042..e03e9c12 100644
--- a/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
+++ b/karavan-web/karavan-infinispan/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java
@@ -210,6 +210,13 @@ public class InfinispanService {
         return pipelineStatuses.get(GroupedKey.create(projectId, environment, projectId));
     }
 
+    public List<PipelineStatus> getPipelineStatuses(String environment) {
+        QueryFactory queryFactory = Search.getQueryFactory((RemoteCache<?, ?>) pipelineStatuses);
+        return queryFactory.<PipelineStatus>create("FROM karavan.PipelineStatus WHERE env = :env")
+                .setParameter("env", environment)
+                .execute().list();
+    }
+
     public void savePipelineStatus(PipelineStatus status) {
         pipelineStatuses.put(GroupedKey.create(status.getProjectId(), status.getEnv(), status.getProjectId()), status);
     }