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/27 22:22:48 UTC

[camel-karavan] 03/03: devservices logs #817

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 17084c371fd0d0523463f6b69e88b0a218134942
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Thu Jul 27 18:22:36 2023 -0400

    devservices logs #817
---
 .../karavan-app/src/main/webui/src/Main.tsx        | 41 +++++++++++-------
 .../src/main/webui/src/api/ProjectService.ts       | 50 +++++++++++-----------
 .../src/main/webui/src/api/ProjectStore.ts         |  8 ----
 .../main/webui/src/containers/ContainersPage.tsx   | 29 ++-----------
 .../karavan-app/src/main/webui/src/index.css       | 16 +++----
 .../src/main/webui/src/project/DevModeToolbar.tsx  | 19 ++++----
 .../src/main/webui/src/project/ProjectPage.tsx     | 19 ++++----
 .../webui/src/project/dashboard/DashboardTab.tsx   | 19 ++++----
 .../main/webui/src/project/log/ProjectLogPanel.tsx | 14 +++---
 .../webui/src/project/pipeline/ProjectStatus.tsx   |  5 +--
 .../src/main/webui/src/services/ServicesPage.tsx   | 23 +++-------
 .../main/webui/src/services/ServicesTableRow.tsx   | 21 +++++++--
 12 files changed, 125 insertions(+), 139 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 8adfe79d..98ca08f1 100644
--- a/karavan-web/karavan-app/src/main/webui/src/Main.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/Main.tsx
@@ -28,12 +28,13 @@ import {ContainersPage} from "./containers/ContainersPage";
 import {ProjectEventBus} from "./api/ProjectEventBus";
 import {AppConfig, ContainerStatus, Project, ToastMessage} from "./api/ProjectModels";
 import {ProjectPage} from "./project/ProjectPage";
-import {useAppConfigStore, useDevModeStore, useFileStore, useProjectStore} from "./api/ProjectStore";
+import {useAppConfigStore, useDevModeStore, useFileStore, useProjectStore, useStatusesStore} from "./api/ProjectStore";
 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";
+import {ProjectService} from "./api/ProjectService";
 
 class MenuItem {
     pageId: string = '';
@@ -50,12 +51,16 @@ class MenuItem {
 export const Main = () => {
 
     const [config, setConfig] = useAppConfigStore((state) => [state.config, state.setConfig], shallow)
+    const [setContainers] = useStatusesStore((state) => [state.setContainers], shallow);
     const [pageId, setPageId] = useState<string>('projects');
     const [request, setRequest] = useState<string>(uuidv4());
     const [showUser, setShowUser] = useState<boolean>(false);
 
     useEffect(() => {
         console.log("Main Start");
+        const interval = setInterval(() => {
+            getStatuses();
+        }, 1000);
         const sub = ProjectEventBus.onSelectProject()?.subscribe((project: Project | undefined) => {
             if (project) setPageId("project");
         });
@@ -64,17 +69,15 @@ export const Main = () => {
             if (authType === 'oidc') {
                 SsoApi.auth(() => {
                     KaravanApi.getMe((user: any) => {
-                        console.log("me", user);
                         getData();
                     });
                 });
             }
-            if (KaravanApi.isAuthorized || KaravanApi.authType === 'public') {
-                getData();
-            }
+            getData();
         });
         return () => {
             console.log("Main End");
+            clearInterval(interval);
             sub?.unsubscribe();
         };
     }, []);
@@ -88,17 +91,26 @@ export const Main = () => {
             }
         });
     }
+    function getStatuses() {
+        if (KaravanApi.isAuthorized || KaravanApi.authType === 'public') {
+            KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => {
+                setContainers(statuses);
+            });
+        }
+    }
 
     function getData() {
-        KaravanApi.getConfiguration((config: AppConfig) => {
-            setRequest(uuidv4());
-            setConfig(config);
-            useAppConfigStore.setState({config: config});
-            InfrastructureAPI.infrastructure = config.infrastructure;
-        });
-        updateKamelets();
-        updateComponents();
-        // updateSupportedComponents(); // not implemented yet
+        if (KaravanApi.isAuthorized || KaravanApi.authType === 'public') {
+            KaravanApi.getConfiguration((config: AppConfig) => {
+                setRequest(uuidv4());
+                setConfig(config);
+                useAppConfigStore.setState({config: config});
+                InfrastructureAPI.infrastructure = config.infrastructure;
+            });
+            updateKamelets();
+            updateComponents();
+            // updateSupportedComponents(); // not implemented yet
+        }
     }
 
     async function updateKamelets(): Promise<void> {
@@ -165,7 +177,6 @@ export const Main = () => {
                                 onClick={event => {
                                     useFileStore.setState({operation: 'none', file: undefined})
                                     useDevModeStore.setState({podName: undefined, status: "none"})
-                                    useProjectStore.setState({containerStatus: new ContainerStatus({}),})
                                     setPageId(page.pageId);
                                 }}
                         />
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 cdfada6f..14f42cbb 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
@@ -45,7 +45,7 @@ export class ProjectService {
         KaravanApi.manageContainer('dev', 'devmove', project.projectId, 'stop', res => {
             useDevModeStore.setState({status: 'none'})
             if (res.status === 200) {
-                useLogStore.setState({showLog: false, type: 'container', isRunning: false})
+                useLogStore.setState({showLog: false, type: 'container'})
             } else {
                 ProjectEventBus.sendAlert(new ToastMessage('Error stopping DevMode container', res.statusText, 'warning'))
             }
@@ -57,7 +57,7 @@ export class ProjectService {
         KaravanApi.manageContainer('dev', 'devmove', project.projectId, 'pause', res => {
             useDevModeStore.setState({status: 'none'})
             if (res.status === 200) {
-                useLogStore.setState({showLog: false, type: 'container', isRunning: false})
+                useLogStore.setState({showLog: false, type: 'container'})
             } else {
                 ProjectEventBus.sendAlert(new ToastMessage('Error stopping DevMode container', res.statusText, 'warning'))
             }
@@ -70,35 +70,35 @@ export class ProjectService {
         KaravanApi.deleteDevModeContainer(project.projectId, false, res => {
             useDevModeStore.setState({status: 'none'})
             if (res.status === 202) {
-                useLogStore.setState({showLog: false, type: 'container', isRunning: false})
+                useLogStore.setState({showLog: false, type: 'container'})
             } else {
                 ProjectEventBus.sendAlert(new ToastMessage('Error delete runner', res.statusText, 'warning'))
             }
         });
     }
 
-    public static getDevModeStatus(project: Project) {
-        const projectId = project.projectId;
-        KaravanApi.getDevModePodStatus(projectId, res => {
-            if (res.status === 200) {
-                unstable_batchedUpdates(() => {
-                    const containerStatus = res.data;
-                    if (useDevModeStore.getState().podName !== containerStatus.containerName){
-                        useDevModeStore.setState({podName: containerStatus.containerName})
-                    }
-                    if (useDevModeStore.getState().status !== 'wip'){
-                        useLogStore.setState({isRunning: true})
-                    }
-                    useProjectStore.setState({containerStatus: containerStatus});
-                })
-            } else {
-                unstable_batchedUpdates(() => {
-                    useDevModeStore.setState({status: 'none', podName: undefined})
-                    useProjectStore.setState({containerStatus: new ContainerStatus({})});
-                })
-            }
-        });
-    }
+    // public static getDevModeStatus(project: Project) {
+    //     const projectId = project.projectId;
+    //     KaravanApi.getDevModePodStatus(projectId, res => {
+    //         if (res.status === 200) {
+    //             unstable_batchedUpdates(() => {
+    //                 const containerStatus = res.data;
+    //                 if (useDevModeStore.getState().podName !== containerStatus.containerName){
+    //                     useDevModeStore.setState({podName: containerStatus.containerName})
+    //                 }
+    //                 if (useDevModeStore.getState().status !== 'wip'){
+    //                     useLogStore.setState({isRunning: true})
+    //                 }
+    //                 useStatusesStore.setState({containerStatus: containerStatus});
+    //             })
+    //         } else {
+    //             unstable_batchedUpdates(() => {
+    //                 useDevModeStore.setState({status: 'none', podName: undefined})
+    //                 useStatusesStore.setState({containerStatus: new ContainerStatus({})});
+    //             })
+    //         }
+    //     });
+    // }
 
     public static pushProject(project: Project, commitMessage: string) {
         useProjectStore.setState({isPushing: true})
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 80c99593..4c554926 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
@@ -59,7 +59,6 @@ interface ProjectState {
     project: Project;
     isPushing: boolean,
     isRunning: boolean,
-    containerStatus: ContainerStatus,
     operation: "create" | "select" | "delete" | "none" | "copy";
     setProject: (project: Project, operation:  "create" | "select" | "delete"| "none" | "copy") => void;
     setOperation: (o: "create" | "select" | "delete"| "none" | "copy") => void;
@@ -70,7 +69,6 @@ export const useProjectStore = create<ProjectState>((set) => ({
     operation: "none",
     isPushing: false,
     isRunning: false,
-    containerStatus: new ContainerStatus(),
     setProject: (project: Project, operation:  "create" | "select" | "delete"| "none" | "copy") => {
         set((state: ProjectState) => ({
             project: project,
@@ -191,7 +189,6 @@ export const useStatusesStore = create<StatusesState>((set) => ({
 
 interface LogState {
     podName?: string,
-    isRunning: boolean,
     data: string;
     setData: (data: string) => void;
     addData: (data: string) => void;
@@ -202,13 +199,11 @@ interface LogState {
     setShowLog: (showLog: boolean) => void;
     type: 'container' | 'pipeline' | 'none',
     setType: (type: 'container' | 'pipeline' | 'none') => void,
-    setIsRunning: (isRunning: boolean) => void;
 }
 
 export const useLogStore = create<LogState>((set) => ({
     podName: undefined,
     data: '',
-    isRunning: false,
     setData: (data: string)  => {
         set({data: data})
     },
@@ -232,9 +227,6 @@ export const useLogStore = create<LogState>((set) => ({
     setShowLog: (showLog: boolean) => {
         set(() => ({showLog: showLog}));
     },
-    setIsRunning: (isRunning: boolean) => {
-        set(() => ({isRunning: isRunning}));
-    },
     type: "none",
     setType: (type: 'container' | 'pipeline' | 'none') =>  {
         set((state: LogState) => ({type: type}));
diff --git a/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx b/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx
index 08539413..a2ae7686 100644
--- a/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/containers/ContainersPage.tsx
@@ -1,7 +1,7 @@
-import React, {useEffect, useState} from 'react';
+import React, {useState} from 'react';
 import {
     Bullseye,
-    Button, EmptyState, EmptyStateIcon, EmptyStateVariant,
+    EmptyState, EmptyStateIcon, EmptyStateVariant,
     PageSection, Spinner,
     Text,
     TextContent,
@@ -13,8 +13,6 @@ import {
 import '../designer/karavan.css';
 import {ContainerStatus} from "../api/ProjectModels";
 import {TableComposable, TableVariant, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
-import {KaravanApi} from "../api/KaravanApi";
-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, useStatusesStore} from "../api/ProjectStore";
@@ -24,27 +22,11 @@ import {ContainerTableRow} from "./ContainerTableRow";
 export const ContainersPage = () => {
 
     const [config] = useAppConfigStore((state) => [state.config], shallow)
-    const [containers, setContainers] = useStatusesStore((state) => [state.containers, state.setContainers], shallow);
+    const [containers] = useStatusesStore((state) => [state.containers, state.setContainers], shallow);
     const [filter, setFilter] = useState<string>('');
-    const [loading, setLoading] = useState<boolean>(true);
+    const [loading] = useState<boolean>(true);
     const [selectedEnv, setSelectedEnv] = useState<string[]>([config.environment]);
 
-    useEffect(() => {
-        const interval = setInterval(() => {
-            updateContainerStatuses()
-        }, 700);
-        return () => {
-            clearInterval(interval)
-        };
-    }, []);
-
-    function updateContainerStatuses() {
-        KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => {
-            setContainers(statuses);
-            setLoading(false);
-        });
-    }
-
     function selectEnvironment(name: string, selected: boolean) {
         if (selected && !selectedEnv.includes(name)) {
             setSelectedEnv((state: string[]) => {
@@ -61,9 +43,6 @@ export const ContainersPage = () => {
     function tools() {
         return (<Toolbar id="toolbar-group-types">
             <ToolbarContent>
-                <ToolbarItem>
-                    <Button variant="link" icon={<RefreshIcon/>} onClick={e => updateContainerStatuses()}/>
-                </ToolbarItem>
                 <ToolbarItem>
                     <ToggleGroup aria-label="Default with single selectable">
                         {config.environments.map(env => (
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 54dbc583..0367351c 100644
--- a/karavan-web/karavan-app/src/main/webui/src/index.css
+++ b/karavan-web/karavan-app/src/main/webui/src/index.css
@@ -180,7 +180,7 @@
   margin-bottom: 100px;
 }
 
-.karavan .project-page .project-log {
+.karavan .project-log {
   position: absolute;
   bottom: 0;
   right: 0;
@@ -191,7 +191,7 @@
   align-items: stretch;
 }
 
-.karavan .project-page .project-log .buttons {
+.karavan .project-log .buttons {
   display: flex;
   flex-direction: row;
   justify-content: flex-end;
@@ -199,26 +199,26 @@
   padding-right: 6px;
 }
 
-.karavan .project-page .project-log .buttons button,
-.karavan .project-page .project-log .buttons .pf-c-check {
+.karavan .project-log .buttons button,
+.karavan .project-log .buttons .pf-c-check {
   padding: 8px;
 }
 
-.karavan .project-page .project-log .buttons .pf-c-check .pf-c-check__label{
+.karavan .project-log .buttons .pf-c-check .pf-c-check__label{
   font-size: 12px;
   line-height: 20px;
   padding: 0;
 }
 
-.karavan .project-page .project-log .pf-c-log-viewer__scroll-container {
+.karavan .project-log .pf-c-log-viewer__scroll-container {
   /*height: 100% !important;*/
 }
 
-.karavan .project-page .project-log .pf-c-log-viewer__text {
+.karavan .project-log .pf-c-log-viewer__text {
   font-size: 12px;
 }
 
-.karavan .project-page .project-log .log-name {
+.karavan .project-log .log-name {
   --pf-c-label__content--before--BorderWidth: 0;
   --pf-c-label--BackgroundColor: transparent;
   margin-right: auto;
diff --git a/karavan-web/karavan-app/src/main/webui/src/project/DevModeToolbar.tsx b/karavan-web/karavan-app/src/main/webui/src/project/DevModeToolbar.tsx
index 9777019b..9401aeb0 100644
--- a/karavan-web/karavan-app/src/main/webui/src/project/DevModeToolbar.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/project/DevModeToolbar.tsx
@@ -4,8 +4,7 @@ import '../designer/karavan.css';
 import RocketIcon from "@patternfly/react-icons/dist/esm/icons/rocket-icon";
 import ReloadIcon from "@patternfly/react-icons/dist/esm/icons/bolt-icon";
 import DeleteIcon from "@patternfly/react-icons/dist/esm/icons/trash-icon";
-import PauseIcon from "@patternfly/react-icons/dist/esm/icons/pause-icon";
-import {useDevModeStore, useLogStore, useProjectStore} from "../api/ProjectStore";
+import {useDevModeStore, useLogStore, useProjectStore, useStatusesStore} from "../api/ProjectStore";
 import {ProjectService} from "../api/ProjectService";
 import {shallow} from "zustand/shallow";
 import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
@@ -20,23 +19,25 @@ interface Props {
 export const DevModeToolbar = (props: Props) => {
 
     const [status] = useDevModeStore((state) => [state.status], shallow)
-    const [project, containerStatus ] = useProjectStore((state) => [state.project, state.containerStatus], shallow)
+    const [project ] = useProjectStore((state) => [state.project], shallow)
+    const [containers] = useStatusesStore((state) => [state.containers], shallow);
     const [verbose, setVerbose] = useState(false);
 
-    const commands = containerStatus.commands;
-    const isRunning = containerStatus.state === 'running';
-    const inTransit = containerStatus.inTransit;
+    const containerStatus = containers.filter(c => c.containerName === project.projectId).at(0);
+    const commands = containerStatus?.commands || ['run'];
+    const isRunning = containerStatus?.state === 'running';
+    const inTransit = containerStatus?.inTransit;
     const isLoading= status === 'wip';
-    const color = containerStatus.state === 'running' ? "green" : "grey";
+    const color = containerStatus?.state === 'running' ? "green" : "grey";
     const icon = isRunning ? <UpIcon/> : <DownIcon/>;
     return (<Flex className="toolbar" direction={{default: "row"}} alignItems={{default: "alignItemsCenter"}}>
         <FlexItem>
             {(inTransit || isLoading) && <Spinner isSVG size="lg" aria-label="spinner"/>}
         </FlexItem>
-        {containerStatus.containerId && <FlexItem>
+        {containerStatus?.containerId && <FlexItem>
             <Label icon={icon} color={color}>
                 <Tooltip content={"Show log"} position={TooltipPosition.bottom}>
-                    <Button variant="link"
+                    <Button variant="link" isDisabled={!isRunning}
                             onClick={e =>
                                 useLogStore.setState({showLog: true, type: 'container', podName: containerStatus.containerName})}>
                         {containerStatus.containerName}
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 cab863b6..976123ef 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
@@ -13,7 +13,6 @@ import {MainToolbar} from "../designer/MainToolbar";
 import {ProjectTitle} from "./ProjectTitle";
 import {ProjectPanel} from "./ProjectPanel";
 import {FileEditor} from "./file/FileEditor";
-import {ProjectService} from "../api/ProjectService";
 import {shallow} from "zustand/shallow";
 
 export const ProjectPage = () => {
@@ -23,15 +22,15 @@ export const ProjectPage = () => {
     const [key, setKey] = useState<string>('');
     const [project] = useProjectStore((state) => [state.project], shallow )
 
-    useEffect(() => {
-        // TODO: make status request only when started or just opened
-        const interval = setInterval(() => {
-            ProjectService.getDevModeStatus(project);
-        }, 1000);
-        return () => {
-            clearInterval(interval)
-        };
-    }, []);
+    // useEffect(() => {
+    //     // TODO: make status request only when started or just opened
+    //     const interval = setInterval(() => {
+    //         ProjectService.getDevModeStatus(project);
+    //     }, 1000);
+    //     return () => {
+    //         clearInterval(interval)
+    //     };
+    // }, []);
 
     function post (file: ProjectFile)  {
         KaravanApi.postProjectFile(file, res => {
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 9aa0a0f8..beceb5c8 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
@@ -24,11 +24,14 @@ import {InfoContainer} from "./InfoContainer";
 import {InfoContext} from "./InfoContext";
 import {InfoMemory} from "./InfoMemory";
 import {KaravanApi} from "../../api/KaravanApi";
-import {useProjectStore} from "../../api/ProjectStore";
+import {useProjectStore, useStatusesStore} from "../../api/ProjectStore";
+import {shallow} from "zustand/shallow";
+import {ContainerStatus} from "../../api/ProjectModels";
 
 export const DashboardTab = () => {
 
-    const {project, containerStatus} = useProjectStore();
+    const [project] = useProjectStore((state) => [state.project], shallow);
+    const [containers] = useStatusesStore((state) => [state.containers], shallow);
     const [memory, setMemory] = useState({});
     const [jvm, setJvm] = useState({});
     const [context, setContext] = useState({});
@@ -67,10 +70,8 @@ export const DashboardTab = () => {
         })
     }
 
-    function showConsole(): boolean {
-        return containerStatus.lifeCycle === 'ready';
-    }
-
+    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">
@@ -78,15 +79,15 @@ export const DashboardTab = () => {
                     <Flex direction={{default: "row"}}
                           justifyContent={{default: "justifyContentSpaceBetween"}}>
                         <FlexItem flex={{default: "flex_1"}}>
-                            <InfoContainer containerStatus={containerStatus}/>
+                            <InfoContainer containerStatus={containerStatus || new ContainerStatus()}/>
                         </FlexItem>
                         <Divider orientation={{default: "vertical"}}/>
                         <FlexItem flex={{default: "flex_1"}}>
-                            <InfoMemory jvm={jvm} memory={memory} showConsole={showConsole()}/>
+                            <InfoMemory jvm={jvm} memory={memory} showConsole={showConsole}/>
                         </FlexItem>
                         <Divider orientation={{default: "vertical"}}/>
                         <FlexItem flex={{default: "flex_1"}}>
-                            <InfoContext context={context} showConsole={showConsole()}/>
+                            <InfoContext context={context} showConsole={showConsole}/>
                         </FlexItem>
                     </Flex>
                 </CardBody>
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 619b1ff7..d02ee9ac 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
@@ -5,7 +5,7 @@ import CloseIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
 import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/expand-icon';
 import CollapseIcon from '@patternfly/react-icons/dist/esm/icons/compress-icon';
 import CleanIcon from '@patternfly/react-icons/dist/esm/icons/trash-alt-icon';
-import {useLogStore} from "../../api/ProjectStore";
+import {useLogStore, useProjectStore, useStatusesStore} from "../../api/ProjectStore";
 import {KaravanApi} from "../../api/KaravanApi";
 import {shallow} from "zustand/shallow";
 import {ProjectEventBus} from "../../api/ProjectEventBus";
@@ -14,9 +14,10 @@ import {ProjectLog} from "./ProjectLog";
 const INITIAL_LOG_HEIGHT = "50%";
 
 export const ProjectLogPanel = () => {
-    const [showLog, type, setShowLog, podName, isRunning] = useLogStore(
-        (state) => [state.showLog, state.type, state.setShowLog, state.podName, state.isRunning], shallow)
+    const [showLog, type, setShowLog, podName] = useLogStore(
+        (state) => [state.showLog, state.type, state.setShowLog, state.podName], shallow)
 
+    const [containers] = useStatusesStore((state) => [state.containers], shallow);
     const [height, setHeight] = useState(INITIAL_LOG_HEIGHT);
     const [isTextWrapped, setIsTextWrapped] = useState(true);
     const [autoScroll, setAutoScroll] = useState(true);
@@ -24,9 +25,10 @@ export const ProjectLogPanel = () => {
     const [currentPodName, setCurrentPodName] = useState<string | undefined>(undefined);
 
     useEffect(() => {
-        console.log("ProjectLogPanel", showLog, type, podName, isRunning);
+        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 && isRunning) {
+        if (showLog && type !== 'none' && podName !== undefined) {
             const f = KaravanApi.fetchData(type, podName, controller).then(value => {
                 console.log("Fetch Started for: " + podName)
             });
@@ -37,7 +39,7 @@ export const ProjectLogPanel = () => {
             console.log("end");
             controller.abort();
         };
-    }, [showLog, type, podName, isRunning]);
+    }, [showLog, type, podName]);
 
     useEffect(() => {
         if (currentPodName !== podName) {
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 14dc1eaa..ba26da23 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
@@ -207,8 +207,7 @@ export class ProjectStatus extends React.Component<Props, State> {
                                                         useLogStore.setState({
                                                             showLog: true,
                                                             type: 'container',
-                                                            podName: pod.containerName,
-                                                            isRunning: true
+                                                            podName: pod.containerName
                                                         });
                                                     }}>
                                                 {pod.containerName}
@@ -287,7 +286,7 @@ export class ProjectStatus extends React.Component<Props, State> {
                             <Label icon={isRunning ? <Spinner isSVG diameter="16px" className="spinner"/> : icon} color={color}>
                                 {pipeline
                                     ? <Button variant="link" onClick={e =>
-                                        useLogStore.setState({showLog: true, type: 'pipeline', podName: pipeline, isRunning: true})
+                                        useLogStore.setState({showLog: true, type: 'pipeline', podName: pipeline})
                                     }>
                                         {pipeline}
                                     </Button>
diff --git a/karavan-web/karavan-app/src/main/webui/src/services/ServicesPage.tsx b/karavan-web/karavan-app/src/main/webui/src/services/ServicesPage.tsx
index 87606ac9..b7dd8fde 100644
--- a/karavan-web/karavan-app/src/main/webui/src/services/ServicesPage.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/services/ServicesPage.tsx
@@ -23,36 +23,24 @@ import {DeleteServiceModal} from "./DeleteServiceModal";
 import {CreateServiceModal} from "./CreateServiceModal";
 import {useProjectStore, useStatusesStore} from "../api/ProjectStore";
 import {MainToolbar} from "../designer/MainToolbar";
-import {ContainerStatus, Project, ProjectType} from "../api/ProjectModels";
+import {Project, ProjectType} from "../api/ProjectModels";
 import {KaravanApi} from "../api/KaravanApi";
 import {DevService, Services, ServicesYaml} from "../api/ServiceModels";
 import {shallow} from "zustand/shallow";
+import {ProjectLogPanel} from "../project/log/ProjectLogPanel";
 
 
 export const ServicesPage = () => {
 
     const [services, setServices] = useState<Services>();
-    const [containers, setContainers] = useStatusesStore((state) => [state.containers, state.setContainers], shallow);
-    const [operation, setOperation] = useState<'create' | 'delete' | 'none'>('none');
-    const [loading, setLoading] = useState<boolean>(false);
+    const [containers] = useStatusesStore((state) => [state.containers, state.setContainers], shallow);
+    const [operation] = useState<'create' | 'delete' | 'none'>('none');
+    const [loading] = useState<boolean>(false);
 
     useEffect(() => {
         getServices();
-        const interval = setInterval(() => {
-            updateContainerStatuses()
-        }, 700);
-        return () => {
-            clearInterval(interval)
-        };
     }, []);
 
-    function updateContainerStatuses() {
-        KaravanApi.getAllContainerStatuses((statuses: ContainerStatus[]) => {
-            setContainers(statuses);
-            setLoading(false);
-        });
-    }
-
     function getServices() {
         KaravanApi.getFiles(ProjectType.services, files => {
             const file = files.at(0);
@@ -142,6 +130,7 @@ export const ServicesPage = () => {
             </PageSection>
             {["create"].includes(operation) && <CreateServiceModal/>}
             {["delete"].includes(operation) && <DeleteServiceModal/>}
+            <ProjectLogPanel/>
         </PageSection>
     )
 }
\ No newline at end of file
diff --git a/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx b/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx
index fab5456d..c5bc953a 100644
--- a/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx
@@ -2,7 +2,7 @@ import React, {useState} from 'react';
 import {
     Button,
     Tooltip,
-    Flex, FlexItem, Label, ToolbarContent, Toolbar, ToolbarItem, Spinner
+    Flex, FlexItem, Label, ToolbarContent, Toolbar, ToolbarItem, Spinner, TooltipPosition
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {ActionsColumn, ExpandableRowContent, Tbody, Td, Tr} from "@patternfly/react-table";
@@ -12,9 +12,11 @@ import {DevService} from "../api/ServiceModels";
 import {ContainerStatus} from "../api/ProjectModels";
 import PauseIcon from "@patternfly/react-icons/dist/esm/icons/pause-icon";
 import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
-import {useAppConfigStore} from "../api/ProjectStore";
+import {useAppConfigStore, useLogStore} from "../api/ProjectStore";
 import {shallow} from "zustand/shallow";
 import {KaravanApi} from "../api/KaravanApi";
+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";
 
 interface Props {
     index: number
@@ -80,7 +82,8 @@ export const ServicesTableRow = (props: Props) => {
     const container = props.container;
     const isRunning = container?.state === 'running';
     const inTransit = container?.inTransit;
-    const color = container?.state === 'running' ? "green" : "grey";
+    const color = isRunning ? "green" : "grey";
+    const icon = isRunning ? <UpIcon/> : <DownIcon/>;
     return (
         <Tbody isExpanded={isExpanded}>
             <Tr key={service.container_name}>
@@ -96,7 +99,17 @@ export const ServicesTableRow = (props: Props) => {
                     modifier={"fitContent"}>
                 </Td>
                 <Td>
-                    <Label color={color}>{service.container_name}</Label>
+                    {container && <Label icon={icon} color={color}>
+                        <Tooltip content={"Show log"} position={TooltipPosition.bottom}>
+                            <Button variant="link" isDisabled={!isRunning}
+                                    onClick={e => {
+                                        useLogStore.setState({showLog: true, type: 'container', podName: container.containerName});
+                                    }}>
+                                {service.container_name}
+                            </Button>
+                        </Tooltip>
+                    </Label>}
+                    {!container && <Label color={color}>{service.container_name}</Label>}
                 </Td>
                 <Td>{service.container_name}</Td>
                 <Td>{service.image}</Td>