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/07/23 18:52:14 UTC

[camel-karavan] branch main updated (f90b524b -> 067904ff)

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

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


    from f90b524b Log in UI with Docker #817
     new f73201e1 Main.tsx as functional #817
     new 4dd412f9 Dashboard supports containers in Docker #817
     new 067904ff Dashboard supports containers in Docker #817

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


Summary of changes:
 .../camel/karavan/api/InfrastructureResource.java  |  13 +
 .../src/main/resources/application.properties      |   2 +-
 .../karavan-app/src/main/webui/src/Main.tsx        | 169 ++++---
 .../src/main/webui/src/api/KaravanApi.tsx          |  11 +
 .../src/main/webui/src/api/ProjectService.ts       |  17 +-
 .../src/main/webui/src/api/ProjectStore.ts         |  56 ++-
 .../src/main/webui/src/dashboard/DashboardPage.tsx | 484 +++++++++++----------
 .../src/main/webui/src/projects/ProjectsPage.tsx   |  18 +-
 .../main/webui/src/projects/ProjectsTableRow.tsx   |  15 +-
 .../karavan/infinispan/InfinispanService.java      |   4 +
 10 files changed, 442 insertions(+), 347 deletions(-)


[camel-karavan] 01/03: Main.tsx as functional #817

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

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

commit f73201e13fa023395e6db83e06baf610432cf54a
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Sun Jul 23 13:32:51 2023 -0400

    Main.tsx as functional #817
---
 .../karavan-app/src/main/webui/src/Main.tsx        | 162 +++++++++------------
 1 file changed, 71 insertions(+), 91 deletions(-)

diff --git a/karavan-web/karavan-app/src/main/webui/src/Main.tsx b/karavan-web/karavan-app/src/main/webui/src/Main.tsx
index a9f46911..11bf54f0 100644
--- a/karavan-web/karavan-app/src/main/webui/src/Main.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/Main.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, {useEffect, useState} from 'react';
 import {
     Page,
     Button,
@@ -18,13 +18,12 @@ import {ProjectsPage} from "./projects/ProjectsPage";
 import UserIcon from "@patternfly/react-icons/dist/js/icons/user-icon";
 import ProjectsIcon from "@patternfly/react-icons/dist/js/icons/repository-icon";
 import KnowledgebaseIcon from "@patternfly/react-icons/dist/js/icons/book-open-icon";
-import ServicesIcon from "@patternfly/react-icons/dist/js/icons/registry-icon";
+import ContainersIcon from "@patternfly/react-icons/dist/js/icons/cubes-icon";
 import DashboardIcon from "@patternfly/react-icons/dist/js/icons/tachometer-alt-icon";
-import EipIcon from "@patternfly/react-icons/dist/js/icons/topology-icon";
+import ServicesIcon from "@patternfly/react-icons/dist/js/icons/services-icon";
 import ComponentsIcon from "@patternfly/react-icons/dist/js/icons/module-icon";
 import {MainLogin} from "./MainLogin";
 import {DashboardPage} from "./dashboard/DashboardPage";
-import {Subscription} from "rxjs";
 import {ProjectEventBus} from "./api/ProjectEventBus";
 import {AppConfig, ContainerStatus, Project, ToastMessage} from "./api/ProjectModels";
 import {ProjectPage} from "./project/ProjectPage";
@@ -33,6 +32,7 @@ import {Notification} from "./Notification";
 import {InfrastructureAPI} from "./designer/utils/InfrastructureAPI";
 import {KnowledgebasePage} from "./knowledgebase/KnowledgebasePage";
 import {ServicesPage} from "./services/ServicesPage";
+import {shallow} from "zustand/shallow";
 
 class MenuItem {
     pageId: string = '';
@@ -46,38 +46,21 @@ class MenuItem {
     }
 }
 
-interface Props {
-}
-
-interface State {
-    config: any,
-    pageId: string,
-    isModalOpen: boolean,
-    openapi: string,
-    request: string,
-    filename: string,
-    key: string,
-    showUser?: boolean,
-}
-
-export class Main extends React.Component<Props, State> {
-
-    public state: State = {
-        config: {},
-        pageId: "projects",
-        isModalOpen: false,
-        request: uuidv4(),
-        openapi: '',
-        filename: '',
-        key: '',
-    };
-
-    designer = React.createRef();
-    sub?: Subscription;
-
-    componentDidMount() {
-        this.sub = ProjectEventBus.onSelectProject()?.subscribe((project: Project | undefined) => {
-            if (project) this.setState({pageId: "project"});
+export const Main = () => {
+
+    const [config, setConfig] = useAppConfigStore((state) => [state.config, state.setConfig], shallow)
+    const [pageId, setPageId] = useState<string>('projects');
+    const [openapi, setOpenapi] = useState<string>('');
+    const [filename, setFileName] = useState<string>('');
+    const [key, setKey] = useState<string>('');
+    const [request, setRequest] = useState<string>(uuidv4());
+    const [isModalOpen, setIsModelOpen] = useState<boolean>(false);
+    const [showUser, setShowUser] = useState<boolean>(false);
+
+    useEffect(() => {
+        console.log("Main Start");
+        const sub = ProjectEventBus.onSelectProject()?.subscribe((project: Project | undefined) => {
+            if (project) setPageId("project");
         });
         KaravanApi.getAuthType((authType: string) => {
             console.log("authType", authType);
@@ -85,44 +68,45 @@ export class Main extends React.Component<Props, State> {
                 SsoApi.auth(() => {
                     KaravanApi.getMe((user: any) => {
                         console.log("me", user);
-                        this.getData();
+                        getData();
                     });
                 });
             } else {
-                this.setState({key: Math.random().toString()})
+                setKey(Math.random().toString())
             }
             if (KaravanApi.isAuthorized || KaravanApi.authType === 'public') {
-                this.getData();
+                getData();
             }
         });
-    }
-
-    componentWillUnmount() {
-        this.sub?.unsubscribe();
-    }
+        return () => {
+            console.log("Main End");
+            sub?.unsubscribe();
+        };
+    }, []);
 
-    onLogin = (username: string, password: string) => {
+    function onLogin(username: string, password: string) {
         KaravanApi.auth(username, password, (res: any) => {
             if (res?.status === 200) {
-                this.getData();
+                getData();
             } else {
-                this.toast("Error", "Incorrect username and/or password!", "danger");
+                toast("Error", "Incorrect username and/or password!", "danger");
             }
         });
     }
 
-    getData() {
+    function getData() {
         KaravanApi.getConfiguration((config: AppConfig) => {
-            this.setState({config: config, request: uuidv4()});
+            setRequest(uuidv4());
+            setConfig(config);
             useAppConfigStore.setState({config: config});
             InfrastructureAPI.infrastructure = config.infrastructure;
         });
-        this.updateKamelets();
-        this.updateComponents();
-        // this.updateSupportedComponents(); // not implemented yet
+        updateKamelets();
+        updateComponents();
+        // updateSupportedComponents(); // not implemented yet
     }
 
-    updateKamelets: () => Promise<void> = async () => {
+    async function updateKamelets(): Promise<void> {
         await new Promise(resolve => {
             KaravanApi.getKamelets(yamls => {
                 const kamelets: string[] = [];
@@ -135,7 +119,7 @@ export class Main extends React.Component<Props, State> {
         });
     }
 
-    updateComponents: () => Promise<void> = async () => {
+    async function updateComponents(): Promise<void> {
         await new Promise(resolve => {
             KaravanApi.getComponents(code => {
                 const components: [] = JSON.parse(code);
@@ -146,7 +130,7 @@ export class Main extends React.Component<Props, State> {
         });
     }
 
-    updateSupportedComponents: () => Promise<void> = async () => {
+    async function updateSupportedComponents(): Promise<void> {
         await new Promise(resolve => {
             KaravanApi.getSupportedComponents(jsons => {
                 ComponentApi.saveSupportedComponents(jsons);
@@ -154,33 +138,33 @@ export class Main extends React.Component<Props, State> {
         });
     }
 
-    pageNav = () => {
+    function pageNav() {
         const pages: MenuItem[] = [
             new MenuItem("dashboard", "Dashboard", <DashboardIcon/>),
             new MenuItem("projects", "Projects", <ProjectsIcon/>),
             new MenuItem("services", "Services", <ServicesIcon/>),
+            new MenuItem("containers", "Containers", <ContainersIcon/>),
             new MenuItem("knowledgebase", "Knowledgebase", <KnowledgebaseIcon/>),
-            // new MenuItem("eip", "Enterprise Integration Patterns", <EipIcon/>),
             // new MenuItem("components", "Components", <ComponentsIcon/>)
         ]
         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 " + this.state.config.version}
+                <Tooltip className="logo-tooltip" content={"Apache Camel Karavan " + config.version}
                          position={"right"}>
                     {Icon()}
                 </Tooltip>
             </FlexItem>
             {pages.map(page =>
-                <FlexItem key={page.pageId} className={this.state.pageId === page.pageId ? "nav-button-selected" : ""}>
+                <FlexItem key={page.pageId} className={pageId === page.pageId ? "nav-button-selected" : ""}>
                     <Tooltip content={page.tooltip} position={"right"}>
                         <Button id={page.pageId} icon={page.icon} variant={"plain"}
-                                className={this.state.pageId === page.pageId ? "nav-button-selected" : ""}
+                                className={pageId === page.pageId ? "nav-button-selected" : ""}
                                 onClick={event => {
-                                    useFileStore.setState({operation:'none', file: undefined})
+                                    useFileStore.setState({operation: 'none', file: undefined})
                                     useDevModeStore.setState({podName: undefined, status: "none"})
-                                    useProjectStore.setState({containerStatus: new ContainerStatus({}), })
-                                    this.setState({pageId: page.pageId});
+                                    useProjectStore.setState({containerStatus: new ContainerStatus({}),})
+                                    setPageId(page.pageId);
                                 }}
                         />
                     </Tooltip>
@@ -195,9 +179,9 @@ export class Main extends React.Component<Props, State> {
                         aria-label="Current user"
                         position={"right-end"}
                         hideOnOutsideClick={false}
-                        isVisible={this.state.showUser === true}
-                        shouldClose={tip => this.setState({showUser: false})}
-                        shouldOpen={tip => this.setState({showUser: true})}
+                        isVisible={showUser}
+                        shouldClose={tip => setShowUser(false)}
+                        shouldOpen={tip => setShowUser(true)}
                         headerContent={<div>{KaravanApi.me.userName}</div>}
                         bodyContent={
                             <Flex direction={{default: "row"}}>
@@ -214,45 +198,41 @@ export class Main extends React.Component<Props, State> {
         </Flex>)
     }
 
-    toast = (title: string, text: string, variant: 'success' | 'danger' | 'warning' | 'info' | 'default') => {
+    function toast(title: string, text: string, variant: 'success' | 'danger' | 'warning' | 'info' | 'default') {
         ProjectEventBus.sendAlert(new ToastMessage(title, text, variant))
     }
 
-    getMain() {
+    function getMain() {
         return (
             <>
                 <Flex direction={{default: "row"}} style={{width: "100%", height: "100%"}}
                       alignItems={{default: "alignItemsStretch"}} spaceItems={{default: 'spaceItemsNone'}}>
                     <FlexItem>
-                        {this.pageNav()}
+                        {pageNav()}
                     </FlexItem>
                     <FlexItem flex={{default: "flex_2"}} style={{height: "100%"}}>
-                        {this.state.pageId === 'dashboard' && <DashboardPage key={this.state.request}
-                                                                             toast={this.toast}
-                                                                             config={this.state.config}/>}
-                        {this.state.pageId === 'projects' && <ProjectsPage key={this.state.request}/>}
-                        {this.state.pageId === 'project' && <ProjectPage key="projects"/>}
-                        {this.state.pageId === 'services' && <ServicesPage key="services"/>}
-                        {this.state.pageId === 'knowledgebase' && <KnowledgebasePage dark={false}/>}
+                        {pageId === 'dashboard' && <DashboardPage key={request} toast={toast} config={config}/>}
+                        {pageId === 'projects' && <ProjectsPage key={request}/>}
+                        {pageId === 'project' && <ProjectPage key="projects"/>}
+                        {pageId === 'services' && <ServicesPage key="services"/>}
+                        {pageId === 'knowledgebase' && <KnowledgebasePage dark={false}/>}
                     </FlexItem>
                 </Flex>
             </>
         )
     }
 
-    render() {
-        return (
-            <Page className="karavan">
-                {KaravanApi.authType === undefined &&
-                    <Bullseye className="loading-page">
-                        <Spinner className="spinner" isSVG diameter="140px" aria-label="Loading..."/>
-                        <div className="logo-placeholder">{Icon()}</div>
-                    </Bullseye>}
-                {(KaravanApi.isAuthorized || KaravanApi.authType === 'public') && this.getMain()}
-                {!KaravanApi.isAuthorized && KaravanApi.authType === 'basic' &&
-                    <MainLogin config={this.state.config} onLogin={this.onLogin}/>}
-                <Notification/>
-            </Page>
-        )
-    }
+    return (
+        <Page className="karavan">
+            {KaravanApi.authType === undefined &&
+                <Bullseye className="loading-page">
+                    <Spinner className="spinner" isSVG diameter="140px" aria-label="Loading..."/>
+                    <div className="logo-placeholder">{Icon()}</div>
+                </Bullseye>}
+            {(KaravanApi.isAuthorized || KaravanApi.authType === 'public') && getMain()}
+            {!KaravanApi.isAuthorized && KaravanApi.authType === 'basic' &&
+                <MainLogin config={config} onLogin={onLogin}/>}
+            <Notification/>
+        </Page>
+    )
 }
\ No newline at end of file


[camel-karavan] 02/03: Dashboard supports containers in Docker #817

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

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

commit 4dd412f91ac73346b079121b7029c548c781a896
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Sun Jul 23 14:51:25 2023 -0400

    Dashboard supports containers in Docker #817
---
 .../camel/karavan/api/InfrastructureResource.java  |  13 +
 .../src/main/resources/application.properties      |   2 +-
 .../karavan-app/src/main/webui/src/Main.tsx        |  27 +-
 .../src/main/webui/src/api/KaravanApi.tsx          |  11 +
 .../src/main/webui/src/api/ProjectService.ts       |  17 +-
 .../src/main/webui/src/api/ProjectStore.ts         |  56 ++-
 .../src/main/webui/src/dashboard/DashboardPage.tsx | 484 +++++++++++----------
 .../src/main/webui/src/projects/ProjectsPage.tsx   |  20 +-
 .../main/webui/src/projects/ProjectsTableRow.tsx   |  15 +-
 .../karavan/infinispan/InfinispanService.java      |   4 +
 10 files changed, 382 insertions(+), 267 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 2a7ec6c2..443c9e69 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
@@ -141,6 +141,19 @@ public class InfrastructureResource {
                 .collect(Collectors.toList());
     }
 
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/container")
+    public List<ContainerStatus> getAllContainerStatuses() throws Exception {
+        if (infinispanService.isReady()) {
+            return infinispanService.getContainerStatuses().stream()
+                    .sorted(Comparator.comparing(ContainerStatus::getProjectId))
+                    .collect(Collectors.toList());
+        } else {
+            return List.of();
+        }
+    }
+
     @GET
     @Produces(MediaType.APPLICATION_JSON)
     @Path("/pod/{env}")
diff --git a/karavan-web/karavan-app/src/main/resources/application.properties b/karavan-web/karavan-app/src/main/resources/application.properties
index 2cee473c..37a78899 100644
--- a/karavan-web/karavan-app/src/main/resources/application.properties
+++ b/karavan-web/karavan-app/src/main/resources/application.properties
@@ -1,6 +1,6 @@
 karavan.version=3.21.1-SNAPSHOT
 karavan.environment=dev
-karavan.environments=dev,test,prod
+karavan.environments=dev
 karavan.default-runtime=quarkus
 karavan.runtimes=quarkus,spring-boot
 karavan.camel.status.interval=off
diff --git a/karavan-web/karavan-app/src/main/webui/src/Main.tsx b/karavan-web/karavan-app/src/main/webui/src/Main.tsx
index 11bf54f0..9e36ffa3 100644
--- a/karavan-web/karavan-app/src/main/webui/src/Main.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/Main.tsx
@@ -50,11 +50,7 @@ export const Main = () => {
 
     const [config, setConfig] = useAppConfigStore((state) => [state.config, state.setConfig], shallow)
     const [pageId, setPageId] = useState<string>('projects');
-    const [openapi, setOpenapi] = useState<string>('');
-    const [filename, setFileName] = useState<string>('');
-    const [key, setKey] = useState<string>('');
     const [request, setRequest] = useState<string>(uuidv4());
-    const [isModalOpen, setIsModelOpen] = useState<boolean>(false);
     const [showUser, setShowUser] = useState<boolean>(false);
 
     useEffect(() => {
@@ -71,8 +67,6 @@ export const Main = () => {
                         getData();
                     });
                 });
-            } else {
-                setKey(Math.random().toString())
             }
             if (KaravanApi.isAuthorized || KaravanApi.authType === 'public') {
                 getData();
@@ -138,15 +132,22 @@ export const Main = () => {
         });
     }
 
-    function pageNav() {
+    function getMenu() : MenuItem[]  {
         const pages: MenuItem[] = [
             new MenuItem("dashboard", "Dashboard", <DashboardIcon/>),
             new MenuItem("projects", "Projects", <ProjectsIcon/>),
-            new MenuItem("services", "Services", <ServicesIcon/>),
-            new MenuItem("containers", "Containers", <ContainersIcon/>),
-            new MenuItem("knowledgebase", "Knowledgebase", <KnowledgebaseIcon/>),
-            // new MenuItem("components", "Components", <ComponentsIcon/>)
         ]
+        if (config.infrastructure === 'docker') {
+            pages.push(
+                new MenuItem("services", "Services", <ServicesIcon/>),
+                new MenuItem("containers", "Containers", <ContainersIcon/>)
+            )
+        }
+        pages.push(new MenuItem("knowledgebase", "Knowledgebase", <KnowledgebaseIcon/>));
+        return pages;
+    }
+
+    function pageNav() {
         return (<Flex className="nav-buttons" direction={{default: "column"}} style={{height: "100%"}}
                       spaceItems={{default: "spaceItemsNone"}}>
             <FlexItem alignSelf={{default: "alignSelfCenter"}}>
@@ -155,7 +156,7 @@ export const Main = () => {
                     {Icon()}
                 </Tooltip>
             </FlexItem>
-            {pages.map(page =>
+            {getMenu().map(page =>
                 <FlexItem key={page.pageId} className={pageId === page.pageId ? "nav-button-selected" : ""}>
                     <Tooltip content={page.tooltip} position={"right"}>
                         <Button id={page.pageId} icon={page.icon} variant={"plain"}
@@ -211,7 +212,7 @@ export const Main = () => {
                         {pageNav()}
                     </FlexItem>
                     <FlexItem flex={{default: "flex_2"}} style={{height: "100%"}}>
-                        {pageId === 'dashboard' && <DashboardPage key={request} toast={toast} config={config}/>}
+                        {pageId === 'dashboard' && <DashboardPage key='dashboard'/>}
                         {pageId === 'projects' && <ProjectsPage key={request}/>}
                         {pageId === 'project' && <ProjectPage key="projects"/>}
                         {pageId === 'services' && <ServicesPage key="services"/>}
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 6515e83a..7a964268 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
@@ -401,6 +401,17 @@ export class KaravanApi {
         });
     }
 
+    static async getAllContainerStatuses(after: (statuses: ContainerStatus[]) => void) {
+        instance.get('/api/infrastructure/container')
+            .then(res => {
+                if (res.status === 200) {
+                    after(res.data);
+                }
+            }).catch(err => {
+            console.log(err);
+        });
+    }
+
     static async getAllDeploymentStatuses(after: (statuses: DeploymentStatus[]) => void) {
         instance.get('/api/infrastructure/deployment')
             .then(res => {
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
index 4e6621cc..4fbd9da7 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts
@@ -4,9 +4,8 @@ import {TemplateApi} from "karavan-core/lib/api/TemplateApi";
 import {InfrastructureAPI} from "../designer/utils/InfrastructureAPI";
 import {unstable_batchedUpdates} from 'react-dom'
 import {
-    useAppConfigStore,
-    useDeploymentStatusesStore,
     useFilesStore,
+    useStatusesStore,
     useFileStore, useLogStore,
     useProjectsStore,
     useProjectStore, useDevModeStore
@@ -123,9 +122,21 @@ export class ProjectService {
         });
     }
 
+    public static refreshAllContainerStatuses() {
+        KaravanApi.getAllContainerStatuses( (statuses: ContainerStatus[]) => {
+            useStatusesStore.setState({containers: statuses});
+        });
+    }
+
+    public static refreshAllDeploymentStatuses() {
+        KaravanApi.getAllDeploymentStatuses( (statuses: DeploymentStatus[]) => {
+            useStatusesStore.setState({deployments: statuses});
+        });
+    }
+
     public static refreshDeploymentStatuses(environment: string) {
         KaravanApi.getDeploymentStatuses(environment, (statuses: DeploymentStatus[]) => {
-            useDeploymentStatusesStore.setState({statuses: statuses});
+            useStatusesStore.setState({deployments: statuses});
         });
     }
 
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 b4930ead..418d1848 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,10 +16,9 @@
  */
 
 import {create} from 'zustand'
-import {AppConfig, DeploymentStatus, ContainerStatus, Project, ProjectFile, ToastMessage} from "./ProjectModels";
+import {AppConfig, DeploymentStatus, ContainerStatus, Project, ProjectFile, ServiceStatus, CamelStatus} from "./ProjectModels";
 import {ProjectEventBus} from "./ProjectEventBus";
 import {unstable_batchedUpdates} from "react-dom";
-import {bottom} from "@patternfly/react-core/helpers/Popper/thirdparty/popper-core";
 
 interface AppConfigState {
     config: AppConfig;
@@ -136,21 +135,6 @@ export const useFileStore = create<FileState>((set) => ({
     },
 }))
 
-interface DeploymentStatusesState {
-    statuses: DeploymentStatus[];
-    setDeploymentStatuses: (statuses: DeploymentStatus[]) => void;
-}
-
-export const useDeploymentStatusesStore = create<DeploymentStatusesState>((set) => ({
-    statuses: [],
-    setDeploymentStatuses: (statuses: DeploymentStatus[]) => {
-        set((state: DeploymentStatusesState) => ({
-            statuses: statuses
-        }));
-    },
-}))
-
-
 interface DevModeState {
     podName?: string,
     status: "none" | "starting" | "deleting"| "reloading" | "running",
@@ -167,6 +151,44 @@ export const useDevModeStore = create<DevModeState>((set) => ({
     },
 }))
 
+interface StatusesState {
+    deployments: DeploymentStatus[];
+    services: ServiceStatus[];
+    containers: ContainerStatus[];
+    camels: CamelStatus[];
+    setDeployments: (d: DeploymentStatus[]) => void;
+    setServices: (s: ServiceStatus[]) => void;
+    setContainers: (c: ContainerStatus[]) => void;
+    setCamels: (c: CamelStatus[]) => void;
+}
+
+export const useStatusesStore = create<StatusesState>((set) => ({
+    deployments: [],
+    services: [],
+    containers: [],
+    camels: [],
+    setDeployments: (d: DeploymentStatus[]) => {
+        set((state: StatusesState) => ({
+            deployments: d,
+        }));
+    },
+    setServices: (s: ServiceStatus[]) => {
+        set((state: StatusesState) => ({
+            services: s,
+        }));
+    },
+    setContainers: (c: ContainerStatus[]) => {
+        set((state: StatusesState) => ({
+            containers: c,
+        }));
+    },
+    setCamels: (c: CamelStatus[]) => {
+        set((state: StatusesState) => ({
+            camels: c,
+        }));
+    }
+}))
+
 interface LogState {
     podName?: string,
     isRunning: boolean,
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 285508d3..4f2cc9ca 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 from 'react';
+import React, {useEffect, useState} from 'react';
 import {
     Badge, Bullseye,
     Button, EmptyState, EmptyStateIcon, EmptyStateVariant,
@@ -13,7 +13,7 @@ import {
     ToolbarItem, Tooltip
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
-import {CamelStatus, DeploymentStatus, Project, ServiceStatus} from "../api/ProjectModels";
+import {CamelStatus, ContainerStatus, DeploymentStatus, Project, ServiceStatus} from "../api/ProjectModels";
 import {TableComposable, TableVariant, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
 import {camelIcon, CamelUi} from "../designer/utils/CamelUi";
 import {KaravanApi} from "../api/KaravanApi";
@@ -23,177 +23,158 @@ 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";
+import {shallow} from "zustand/shallow";
 
-interface Props {
-    config: any,
-    toast: (title: string, text: string, variant: 'success' | 'danger' | 'warning' | 'info' | 'default') => void
-}
+export const DashboardPage = () => {
 
-interface State {
-    projects: Project[],
-    deploymentStatuses: DeploymentStatus[],
-    serviceStatuses: ServiceStatus[],
-    camelStatuses: CamelStatus[],
-    isCreateModalOpen: boolean,
-    isDeleteModalOpen: boolean,
-    isCopy: boolean,
-    loading: boolean,
-    projectToCopy?: Project,
-    projectToDelete?: Project,
-    filter: string,
-    name: string,
-    description: string,
-    projectId: string,
-    selectedEnv: string[]
-}
+    const [config] = useAppConfigStore((state) => [state.config], shallow)
+    const [projects, setProjects] = useProjectsStore((state) => [state.projects, state.setProjects], shallow)
+    const [deployments, services, containers, camels, setDeployments, setServices, setContainers, setCamels]
+        = 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]);
 
-export class DashboardPage extends React.Component<Props, State> {
+    useEffect(() => {
+        const interval = setInterval(() => {
+            onGetProjects()
+        }, 1300);
+        return () => {
+            clearInterval(interval)
+        };
+    }, []);
 
-    public state: State = {
-        projects: [],
-        deploymentStatuses: [],
-        serviceStatuses: [],
-        camelStatuses: [],
-        isCreateModalOpen: false,
-        isDeleteModalOpen: false,
-        isCopy: false,
-        loading: true,
-        filter: '',
-        name: '',
-        description: '',
-        projectId: '',
-        selectedEnv: this.getEnvironments()
-    };
-    interval: any;
-
-    componentDidMount() {
-        this.interval = setInterval(() => this.onGetProjects(), 1300);
-    }
-
-    componentWillUnmount() {
-        clearInterval(this.interval);
-    }
-
-    onGetProjects = () => {
+    function onGetProjects() {
         KaravanApi.getConfiguration((config: any) => {
             KaravanApi.getProjects((projects: Project[]) => {
-                this.setState({projects: projects, loading: false})
+                setProjects(projects);
             });
             KaravanApi.getAllDeploymentStatuses((statuses: DeploymentStatus[]) => {
-                this.setState({deploymentStatuses: statuses});
+                setDeployments(statuses);
             });
             KaravanApi.getAllServiceStatuses((statuses: ServiceStatus[]) => {
-                this.setState({serviceStatuses: statuses});
+                setServices(statuses);
             });
-            this.getSelectedEnvironments().forEach(env => {
-                KaravanApi.getAllCamelStatuses(env,(statuses: CamelStatus[]) => {
-                    this.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;
-                    })
+            KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => {
+                setContainers(statuses);
+            });
+            selectedEnv.forEach(env => {
+                KaravanApi.getAllCamelStatuses(env, (statuses: CamelStatus[]) => {
+                    setCamels(statuses);
+                    // 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;
+                    // })
                 });
-            })
+            });
+            setLoading(false);
         });
     }
 
-    selectEnvironment(name: string, selected: boolean) {
-        if (selected && !this.state.selectedEnv.includes(name)) {
-            this.setState((state) => {
-                state.selectedEnv.push(name);
+    function selectEnvironment(name: string, selected: boolean) {
+        if (selected && !selectedEnv.includes(name)) {
+            setSelectedEnv((state: string[]) => {
+                state.push(name);
                 return state;
             })
-        } else if (!selected && this.state.selectedEnv.includes(name)) {
-            this.setState((prevState) => ({
-                selectedEnv: prevState.selectedEnv.filter(e => e !== name)
-            }));
+        } else if (!selected && selectedEnv.includes(name)) {
+            setSelectedEnv((state: string[]) => {
+                return state.filter(e => e !== name)
+            })
         }
     }
 
-    tools = () => (<Toolbar id="toolbar-group-types">
-        <ToolbarContent>
-            <ToolbarItem>
-                <Button variant="link" icon={<RefreshIcon/>} onClick={e => this.onGetProjects()}/>
-            </ToolbarItem>
-            <ToolbarItem>
-                <ToggleGroup aria-label="Default with single selectable">
-                    {this.getEnvironments().map(env => (
-                        <ToggleGroupItem key={env} text={env} buttonId={env} isSelected={this.state.selectedEnv.includes(env)} onChange={selected => this.selectEnvironment(env, selected)}/>
-                    ))}
-                </ToggleGroup>
-            </ToolbarItem>
-            <ToolbarItem>
-                <TextInput className="text-field" type="search" id="search" name="search"
-                           autoComplete="off" placeholder="Search deployment by name"
-                           value={this.state.filter}
-                           onChange={e => this.setState({filter: e})}/>
-            </ToolbarItem>
-        </ToolbarContent>
-    </Toolbar>);
-
-    title = () => (<TextContent>
-        <Text component="h2">Dashboard</Text>
-    </TextContent>);
+    function tools() {
+        return (<Toolbar id="toolbar-group-types">
+            <ToolbarContent>
+                <ToolbarItem>
+                    <Button variant="link" icon={<RefreshIcon/>} onClick={e => onGetProjects()}/>
+                </ToolbarItem>
+                <ToolbarItem>
+                    <ToggleGroup aria-label="Default with single selectable">
+                        {config.environments.map(env => (
+                            <ToggleGroupItem key={env} text={env} buttonId={env} isSelected={selectedEnv.includes(env)} onChange={selected => selectEnvironment(env, selected)}/>
+                        ))}
+                    </ToggleGroup>
+                </ToolbarItem>
+                <ToolbarItem>
+                    <TextInput className="text-field" type="search" id="search" name="search"
+                               autoComplete="off" placeholder="Search deployment by name"
+                               value={filter}
+                               onChange={e => setFilter(e)}/>
+                </ToolbarItem>
+            </ToolbarContent>
+        </Toolbar>);
+    }
 
-    getEnvironments(): string [] {
-        return this.props.config.environments && Array.isArray(this.props.config.environments) ? Array.from(this.props.config.environments) : [];
+    function title() {
+        return (<TextContent>
+            <Text component="h2">Dashboard</Text>
+        </TextContent>);
     }
 
-    getSelectedEnvironments(): string [] {
-        return this.getEnvironments().filter(e => this.state.selectedEnv.includes(e));
+    function getSelectedEnvironments(): string [] {
+        return config.environments.filter(e => selectedEnv.includes(e));
     }
 
-    getDeploymentEnvironments(name: string): [string, boolean] [] {
-        const deps = this.state.deploymentStatuses;
-        return this.getSelectedEnvironments().map(e => {
+    function getDeploymentEnvironments(name: string): [string, boolean] [] {
+        return selectedEnv.map(e => {
             const env: string = e as string;
-            const dep = deps.find(d => d.name === name && d.env === env);
+            const dep = deployments.find(d => d.name === name && d.env === env);
             const deployed: boolean = dep !== undefined && dep.replicas > 0 && dep.replicas === dep.readyReplicas;
             return [env, deployed];
         });
     }
 
-    getDeploymentByEnvironments(name: string): [string, DeploymentStatus | undefined] [] {
-        const deps = this.state.deploymentStatuses;
-        return this.getSelectedEnvironments().map(e => {
+    function getDeploymentByEnvironments(name: string): [string, DeploymentStatus | undefined] [] {
+        return selectedEnv.map(e => {
             const env: string = e as string;
-            const dep = deps.find(d => d.name === name && d.env === env);
+            const dep = deployments.find(d => d.name === name && d.env === env);
             return [env, dep];
         });
     }
 
-    getServiceByEnvironments(name: string): [string, ServiceStatus | undefined] [] {
-        const services = this.state.serviceStatuses;
-        return this.getSelectedEnvironments().map(e => {
+    function getServiceByEnvironments(name: string): [string, ServiceStatus | undefined] [] {
+        return selectedEnv.map(e => {
             const env: string = e as string;
             const service = services.find(d => d.name === name && d.env === env);
             return [env, service];
         });
     }
 
-    getCamelStatusByEnvironments(name: string): [string, CamelStatus | undefined] [] {
-        const camelStatuses = this.state.camelStatuses;
-        return this.getSelectedEnvironments().map(e => {
+    function getContainerByEnvironments(name: string): [string, ContainerStatus | undefined] [] {
+        return selectedEnv.map(e => {
+            const env: string = e as string;
+            const container = containers.find(d => d.containerName === name && d.env === env);
+            return [env, container];
+        });
+    }
+
+    function getCamelStatusByEnvironments(name: string): [string, CamelStatus | undefined] [] {
+        return getSelectedEnvironments().map(e => {
             const env: string = e as string;
-            const status = camelStatuses.find(d => d.projectId === name && d.env === env);
+            const status = camels.find(d => d.projectId === name && d.env === env);
             return [env, status];
         });
     }
 
-    getProject(name: string): Project | undefined {
-        return this.state.projects.filter(p => p.projectId === name)?.at(0);
+    function getProject(name: string): Project | undefined {
+        return projects.filter(p => p.projectId === name)?.at(0);
     }
 
-    isKaravan(name: string): boolean {
-        return this.state.projects.findIndex(p => p.projectId === name) > -1;
+    function isKaravan(name: string): boolean {
+        return projects.findIndex(p => p.projectId === name) > -1;
     }
 
-    getReplicasPanel(deploymentStatus?: DeploymentStatus) {
+    function getReplicasPanel(deploymentStatus?: DeploymentStatus) {
         if (deploymentStatus) {
             const readyReplicas = deploymentStatus.readyReplicas ? deploymentStatus.readyReplicas : 0;
             const ok = (deploymentStatus && readyReplicas > 0
@@ -225,8 +206,7 @@ export class DashboardPage extends React.Component<Props, State> {
         }
     }
 
-    getEmptyState() {
-        const {loading} = this.state;
+    function getEmptyState() {
         return (
             <Tr>
                 <Td colSpan={8}>
@@ -246,103 +226,167 @@ export class DashboardPage extends React.Component<Props, State> {
         )
     }
 
-    render() {
-        const deployments = Array.from(new Set(this.state.deploymentStatuses.filter(d => d.name.toLowerCase().includes(this.state.filter)).map(d => d.name)));
+    function getKubernetesTable() {
+        const deps = Array.from(new Set(deployments.filter(d => d.name.toLowerCase().includes(filter)).map(d => d.name)));
         return (
-            <PageSection className="kamelet-section dashboard-page" padding={{default: 'noPadding'}}>
-                <PageSection className="tools-section" padding={{default: 'noPadding'}}>
-                    <MainToolbar title={this.title()} tools={this.tools()}/>
-                </PageSection>
-                <PageSection isFilled className="kamelets-page">
-                    <TableComposable aria-label="Projects" variant={TableVariant.compact}>
-                        <Thead>
-                            <Tr>
-                                <Th key='type'>Type</Th>
-                                <Th key='name'>Deployment</Th>
-                                <Th key='description'>Project/Description</Th>
-                                <Th key='environment'>Environment</Th>
-                                <Th key='namespace'>Namespace</Th>
-                                <Th key='replicas'>Replicas</Th>
-                                <Th key='services'>Services</Th>
-                                <Th key='camel'>Camel Health</Th>
-                                {/*<Th key='action'></Th>*/}
-                            </Tr>
-                        </Thead>
-                        <Tbody>
-                            {deployments.map(deployment => (
-                                <Tr key={deployment}>
-                                    <Td style={{verticalAlign: "middle"}}>
-                                        {this.isKaravan(deployment) ? Icon("icon") : CamelUi.getIconFromSource(camelIcon)}
-                                    </Td>
-                                    <Td style={{verticalAlign: "middle"}}>
-                                        <Button style={{padding: '6px'}} variant={"link"}>{deployment}</Button>
-                                    </Td>
-                                    <Td style={{verticalAlign: "middle"}}>
-                                        <HelperText>
-                                            <HelperTextItem>{this.getProject(deployment)?.name || ""}</HelperTextItem>
-                                            <HelperTextItem>{this.getProject(deployment)?.description || "Camel project"}</HelperTextItem>
-                                        </HelperText>
-                                    </Td>
-                                    <Td>
-                                        <Flex direction={{default: "column"}}>
-                                            {this.getDeploymentEnvironments(deployment).map(value => (
-                                                <FlexItem className="badge-flex-item" key={value[0]}><Badge className="badge"
-                                                                                                            isRead={!value[1]}>{value[0]}</Badge></FlexItem>
-                                            ))}
-                                        </Flex>
-                                    </Td>
-                                    <Td>
-                                        <Flex direction={{default: "column"}}>
-                                            {this.getDeploymentByEnvironments(deployment).map(value => (
-                                                <FlexItem className="badge-flex-item" key={value[0]}>
-                                                    <Label variant={"outline"}>
-                                                        {value[1]?.namespace || "???"}
-                                                    </Label>
-                                                </FlexItem>
-                                            ))}
-                                        </Flex>
-                                    </Td>
-                                    <Td>
-                                        <Flex direction={{default: "column"}}>
-                                            {this.getDeploymentByEnvironments(deployment).map(value => (
-                                                <FlexItem className="badge-flex-item" key={value[0]}>{this.getReplicasPanel(value[1])}</FlexItem>
-                                            ))}
-                                        </Flex>
-                                    </Td>
-                                    <Td>
-                                        <Flex direction={{default: "column"}}>
-                                            {this.getServiceByEnvironments(deployment).map(value => (
-                                                <FlexItem className="badge-flex-item" key={value[0]}>
-                                                    <Label variant={"outline"}>
-                                                        {value[1] ? (value[1]?.port + " -> " + value[1]?.targetPort) : "???"}
-                                                    </Label>
-                                                </FlexItem>
-                                            ))}
-                                        </Flex>
-                                    </Td>
-                                    <Td modifier={"fitContent"}>
-                                        <Flex direction={{default: "column"}}>
-                                            {this.getCamelStatusByEnvironments(deployment).map(value => {
-                                                // const color = value[1] ? (value[1].consumerStatus === "UP" ? "green" : "red") : "grey";
-                                                // let icon = undefined;
-                                                // if (value[1]?.consumerStatus === "UP") icon = <UpIcon/>
-                                                // if (value[1]?.consumerStatus === "DOWN") icon = <DownIcon/>
-                                                // const text = value[1] && value[1]?.contextVersion ? value[1]?.contextVersion : "???";
-                                                return <FlexItem key={value[0]}>
-                                                    {/*<LabelGroup numLabels={4} className="camel-label-group">*/}
-                                                    {/*    <Label color={color} className="table-label" icon={icon}>{text}</Label>*/}
-                                                    {/*</LabelGroup>*/}
-                                                </FlexItem>
-                                            })}
-                                        </Flex>
-                                    </Td>
-                                </Tr>
-                            ))}
-                            {deployments.length === 0 && this.getEmptyState()}
-                        </Tbody>
-                    </TableComposable>
-                </PageSection>
-            </PageSection>
+            <TableComposable aria-label="Projects" variant={TableVariant.compact}>
+                <Thead>
+                    <Tr>
+                        <Th key='type'>Type</Th>
+                        <Th key='name'>Deployment</Th>
+                        <Th key='description'>Project/Description</Th>
+                        <Th key='environment'>Environment</Th>
+                        <Th key='namespace'>Namespace</Th>
+                        <Th key='replicas'>Replicas</Th>
+                        <Th key='services'>Services</Th>
+                        <Th key='camel'>Camel Health</Th>
+                        {/*<Th key='action'></Th>*/}
+                    </Tr>
+                </Thead>
+                <Tbody>
+                    {deps.map(deployment => (
+                        <Tr key={deployment}>
+                            <Td style={{verticalAlign: "middle"}}>
+                                {isKaravan(deployment) ? Icon("icon") : CamelUi.getIconFromSource(camelIcon)}
+                            </Td>
+                            <Td style={{verticalAlign: "middle"}}>
+                                <Button style={{padding: '6px'}} variant={"link"}>{deployment}</Button>
+                            </Td>
+                            <Td style={{verticalAlign: "middle"}}>
+                                <HelperText>
+                                    <HelperTextItem>{getProject(deployment)?.name || ""}</HelperTextItem>
+                                    <HelperTextItem>{getProject(deployment)?.description || "Camel project"}</HelperTextItem>
+                                </HelperText>
+                            </Td>
+                            <Td>
+                                <Flex direction={{default: "column"}}>
+                                    {getDeploymentEnvironments(deployment).map(value => (
+                                        <FlexItem className="badge-flex-item" key={value[0]}><Badge className="badge"
+                                                                                                    isRead={!value[1]}>{value[0]}</Badge></FlexItem>
+                                    ))}
+                                </Flex>
+                            </Td>
+                            <Td>
+                                <Flex direction={{default: "column"}}>
+                                    {getServiceByEnvironments(deployment).map(value => (
+                                        <FlexItem className="badge-flex-item" key={value[0]}>
+                                            <Label variant={"outline"}>
+                                                {value[1] ? (value[1]?.port + " -> " + value[1]?.targetPort) : "???"}
+                                            </Label>
+                                        </FlexItem>
+                                    ))}
+                                </Flex>
+                            </Td>
+                            <Td modifier={"fitContent"}>
+                                <Flex direction={{default: "column"}}>
+                                    {getCamelStatusByEnvironments(deployment).map(value => {
+                                        // const color = value[1] ? (value[1].consumerStatus === "UP" ? "green" : "red") : "grey";
+                                        // let icon = undefined;
+                                        // if (value[1]?.consumerStatus === "UP") icon = <UpIcon/>
+                                        // if (value[1]?.consumerStatus === "DOWN") icon = <DownIcon/>
+                                        // const text = value[1] && value[1]?.contextVersion ? value[1]?.contextVersion : "???";
+                                        return <FlexItem key={value[0]}>
+                                            {/*<LabelGroup numLabels={4} className="camel-label-group">*/}
+                                            {/*    <Label color={color} className="table-label" icon={icon}>{text}</Label>*/}
+                                            {/*</LabelGroup>*/}
+                                        </FlexItem>
+                                    })}
+                                </Flex>
+                            </Td>
+                        </Tr>
+                    ))}
+                    {deps.length === 0 && getEmptyState()}
+                </Tbody>
+            </TableComposable>
+        )
+    }
+
+    function getDockerTable() {
+        const conts = containers
+            .filter(c => ['devmode', 'project'].includes(c.type))
+            .filter(d => d.containerName.toLowerCase().includes(filter));
+        return (
+            <TableComposable aria-label="Projects" variant={TableVariant.compact}>
+                <Thead>
+                    <Tr>
+                        <Th key='type'>Type</Th>
+                        <Th key='container'>Container</Th>
+                        <Th key='description'>Project Description</Th>
+                        <Th key='ports'>Ports</Th>
+                        <Th key='environment'>Environment</Th>
+                        <Th key='camel'>Camel Health</Th>
+                        {/*<Th key='action'></Th>*/}
+                    </Tr>
+                </Thead>
+                <Tbody>
+                    {conts.map(container => (
+                        <Tr key={container.containerName}>
+                            <Td style={{verticalAlign: "middle"}} modifier={"fitContent"}>
+                                <Label variant={"outline"}>{container.type}</Label>
+                            </Td>
+                            <Td style={{verticalAlign: "middle"}}>
+                                <Label color={container.lifeCycle === 'ready' ? "green" : 'grey'}>
+                                    {container.containerName}
+                                </Label>
+                            </Td>
+                            <Td style={{verticalAlign: "middle"}}>
+                                <HelperText>
+                                    <HelperTextItem>{getProject(container.containerName)?.description || "Camel project"}</HelperTextItem>
+                                </HelperText>
+                            </Td>
+                            <Td>
+                                <Flex direction={{default: "column"}}>
+                                    {container.ports.map(port => (
+                                        <FlexItem className="badge-flex-item" key={port}><Badge className="badge"
+                                                                                                    isRead={true}>{port}</Badge></FlexItem>
+                                    ))}
+                                </Flex>
+                            </Td>
+                            <Td>
+                                <Flex direction={{default: "column"}}>
+                                    {getContainerByEnvironments(container.containerName).map(value => (
+                                        <FlexItem className="badge-flex-item" key={value[0]}>
+                                            <Badge className={"badge"}>
+                                                {value[1] ? value[1]?.env : ""}
+                                            </Badge>
+                                        </FlexItem>
+                                    ))}
+                                </Flex>
+                            </Td>
+                            <Td modifier={"fitContent"}>
+                                <Flex direction={{default: "column"}}>
+                                    {getCamelStatusByEnvironments(container.containerName).map(value => {
+                                        // const color = value[1] ? (value[1].consumerStatus === "UP" ? "green" : "red") : "grey";
+                                        // let icon = undefined;
+                                        // if (value[1]?.consumerStatus === "UP") icon = <UpIcon/>
+                                        // if (value[1]?.consumerStatus === "DOWN") icon = <DownIcon/>
+                                        // const text = value[1] && value[1]?.contextVersion ? value[1]?.contextVersion : "???";
+                                        return <FlexItem key={value[0]}>
+                                            {/*<LabelGroup numLabels={4} className="camel-label-group">*/}
+                                            {/*    <Label color={color} className="table-label" icon={icon}>{text}</Label>*/}
+                                            {/*</LabelGroup>*/}
+                                        </FlexItem>
+                                    })}
+                                </Flex>
+                            </Td>
+                        </Tr>
+                    ))}
+                    {conts.length === 0 && getEmptyState()}
+                </Tbody>
+            </TableComposable>
         )
     }
+
+    const isKubernetes = config.infrastructure === 'kubernetes';
+    return (
+        <PageSection className="kamelet-section dashboard-page" padding={{default: 'noPadding'}}>
+            <PageSection className="tools-section" padding={{default: 'noPadding'}}>
+                <MainToolbar title={title()} tools={tools()}/>
+            </PageSection>
+            <PageSection isFilled className="kamelets-page">
+                {isKubernetes ? getKubernetesTable() : getDockerTable()}
+            </PageSection>
+        </PageSection>
+    )
+
 }
\ No newline at end of file
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 4f74ab8b..735f99d0 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
@@ -22,23 +22,28 @@ import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
 import {ProjectsTableRow} from "./ProjectsTableRow";
 import {DeleteProjectModal} from "./DeleteProjectModal";
 import {CreateProjectModal} from "./CreateProjectModal";
-import {useProjectsStore, useProjectStore} from "../api/ProjectStore";
+import {useAppConfigStore, useProjectsStore, useProjectStore} from "../api/ProjectStore";
 import {ProjectService} from "../api/ProjectService";
 import {MainToolbar} from "../designer/MainToolbar";
 import {Project, ProjectType} from "../api/ProjectModels";
+import {shallow} from "zustand/esm/shallow";
 
 
 export const ProjectsPage = () => {
 
-    const {projects, setProjects} = useProjectsStore();
-    const {operation} = useProjectStore();
+    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)) ProjectService.refreshProjects();
+            if (!["create", "delete", "select", "copy"].includes(operation)) {
+                ProjectService.refreshProjects();
+                ProjectService.refreshAllDeploymentStatuses();
+                ProjectService.refreshAllContainerStatuses();
+            }
         }, 1300);
         return () => {
             clearInterval(interval)
@@ -49,8 +54,11 @@ export const ProjectsPage = () => {
         return <Toolbar id="toolbar-group-types">
             <ToolbarContent>
                 <ToolbarItem>
-                    <Button variant="link" icon={<RefreshIcon/>} onClick={e =>
-                        ProjectService.refreshProjects()}/>
+                    <Button variant="link" icon={<RefreshIcon/>} onClick={e => {
+                        ProjectService.refreshProjects();
+                        ProjectService.refreshAllDeploymentStatuses();
+                        ProjectService.refreshAllContainerStatuses();
+                    }}/>
                 </ToolbarItem>
                 <ToolbarItem>
                     <TextInput className="text-field" type="search" id="search" name="search"
diff --git a/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx b/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
index a416035f..43b38dbf 100644
--- a/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
@@ -12,9 +12,8 @@ import CopyIcon from "@patternfly/react-icons/dist/esm/icons/copy-icon";
 import {DeploymentStatus, Project} from '../api/ProjectModels';
 import {
     useAppConfigStore,
-    useDeploymentStatusesStore,
     useLogStore,
-    useProjectStore,
+    useProjectStore, useStatusesStore,
 } from "../api/ProjectStore";
 import {ProjectEventBus} from "../api/ProjectEventBus";
 import {shallow} from "zustand/shallow";
@@ -25,7 +24,7 @@ interface Props {
 
 export const ProjectsTableRow = (props: Props) => {
 
-    const {statuses} = useDeploymentStatusesStore();
+    const [deployments, containers] = useStatusesStore((state) => [state.deployments, state.containers], shallow)
     const {config} = useAppConfigStore();
     const [setProject] = useProjectStore((state) => [state.setProject, state.setOperation], shallow);
     const [setShowLog] = useLogStore((state) => [state.setShowLog], shallow);
@@ -34,11 +33,13 @@ export const ProjectsTableRow = (props: Props) => {
         return config.environments && Array.isArray(config.environments) ? Array.from(config.environments) : [];
     }
 
-    function getDeploymentByEnvironments(name: string): [string, DeploymentStatus | undefined] [] {
+    function getStatusByEnvironments(name: string): [string, any] [] {
         return getEnvironments().map(e => {
             const env: string = e as string;
-            const dep = statuses.find(d => d.name === name && d.env === env);
-            return [env, dep];
+            const status = config.infrastructure === 'kubernetes'
+                ? deployments.find(d => d.name === name && d.env === env)
+                : containers.find(d => d.containerName === name && d.env === env);
+            return [env, status];
         });
     }
 
@@ -71,7 +72,7 @@ export const ProjectsTableRow = (props: Props) => {
             <Td noPadding style={{width: "180px"}}>
                 {!isBuildIn &&
                     <Flex direction={{default: "row"}}>
-                        {getDeploymentByEnvironments(project.projectId).map(value => (
+                        {getStatusByEnvironments(project.projectId).map(value => (
                             <FlexItem className="badge-flex-item" key={value[0]}>
                                 <Badge className="badge" isRead={!value[1]}>{value[0]}</Badge>
                             </FlexItem>
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 58732c4b..efd1ee2c 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
@@ -250,6 +250,10 @@ public class InfinispanService {
         return new ArrayList<>(serviceStatuses.values());
     }
 
+    public List<ContainerStatus> getContainerStatuses() {
+        return new ArrayList<>(containerStatuses.values());
+    }
+
     public List<ContainerStatus> getContainerStatuses(String projectId, String env) {
         QueryFactory queryFactory = Search.getQueryFactory(containerStatuses);
         return queryFactory.<ContainerStatus>create("FROM karavan.ContainerStatus WHERE projectId = :projectId AND env = :env")


[camel-karavan] 03/03: Dashboard supports containers in Docker #817

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

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

commit 067904ff3f1dcd8754f38e11a96c2db3a1f59953
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Sun Jul 23 14:51:57 2023 -0400

    Dashboard supports containers in Docker #817
---
 karavan-web/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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 735f99d0..dd03c5dc 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
@@ -22,11 +22,11 @@ import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
 import {ProjectsTableRow} from "./ProjectsTableRow";
 import {DeleteProjectModal} from "./DeleteProjectModal";
 import {CreateProjectModal} from "./CreateProjectModal";
-import {useAppConfigStore, useProjectsStore, useProjectStore} from "../api/ProjectStore";
+import {useProjectsStore, useProjectStore} from "../api/ProjectStore";
 import {ProjectService} from "../api/ProjectService";
 import {MainToolbar} from "../designer/MainToolbar";
 import {Project, ProjectType} from "../api/ProjectModels";
-import {shallow} from "zustand/esm/shallow";
+import {shallow} from "zustand/shallow";
 
 
 export const ProjectsPage = () => {