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/01 17:22:35 UTC

[camel-karavan] 02/06: Refactor for #809

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 6c64bd46db012daddae7c910d2a985728256bdbb
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Mon Jun 26 10:08:49 2023 -0400

    Refactor for #809
---
 karavan-app/src/main/webui/src/Main.tsx            |   7 +-
 karavan-app/src/main/webui/src/api/KaravanApi.tsx  |   3 +-
 .../src/main/webui/src/api/ProjectModels.ts        |   8 +
 karavan-app/src/main/webui/src/api/ProjectStore.ts |  24 ++-
 karavan-app/src/main/webui/src/index.css           |   5 +-
 .../src/main/webui/src/project/ProjectPage.tsx     | 214 ++++-----------------
 .../src/main/webui/src/project/ProjectPanel.tsx    |  49 +++++
 .../src/main/webui/src/project/ProjectTitle.tsx    |  61 ++++++
 .../src/main/webui/src/project/ProjectToolbar.tsx  |  42 ++--
 .../src/main/webui/src/project/RunnerToolbar.tsx   |   5 +-
 .../webui/src/project/dashboard/DashboardTab.tsx   |  20 +-
 .../src/project/dashboard/RunnerInfoContext.tsx    |   3 +-
 .../src/project/dashboard/RunnerInfoMemory.tsx     |   1 -
 .../src/main/webui/src/project/files/FilesTab.tsx  |  30 +--
 .../main/webui/src/project/files/FilesToolbar.tsx  |  24 +++
 .../src/project/pipeline/ProjectPipelineTab.tsx    |  42 ++--
 .../src/main/webui/src/project/trace/TraceTab.tsx  |  12 +-
 .../main/webui/src/projects/CreateProjectModal.tsx |  10 +-
 .../src/main/webui/src/projects/ProjectsPage.tsx   |   7 +-
 .../main/webui/src/projects/ProjectsTableRow.tsx   |   8 +-
 20 files changed, 281 insertions(+), 294 deletions(-)

diff --git a/karavan-app/src/main/webui/src/Main.tsx b/karavan-app/src/main/webui/src/Main.tsx
index 6b64d6e1..39f3192f 100644
--- a/karavan-app/src/main/webui/src/Main.tsx
+++ b/karavan-app/src/main/webui/src/Main.tsx
@@ -32,6 +32,7 @@ import {Subscription} from "rxjs";
 import {ProjectEventBus} from "./api/ProjectEventBus";
 import {Project} from "./api/ProjectModels";
 import {ProjectPage} from "./project/ProjectPage";
+import {useAppConfigStore} from "./api/ProjectStore";
 
 class ToastMessage {
     id: string = ''
@@ -129,6 +130,7 @@ export class Main extends React.Component<Props, State> {
     getData() {
         KaravanApi.getConfiguration((config: any) => {
             this.setState({config: config, request: uuidv4()});
+            useAppConfigStore.setState({config: config})
         });
         this.updateKamelets();
         this.updateComponents();
@@ -242,10 +244,9 @@ export class Main extends React.Component<Props, State> {
                     <FlexItem flex={{default: "flex_2"}} style={{height: "100%"}}>
                         {this.state.pageId === 'projects' &&
                             <ProjectsPage key={this.state.request}
-                                          toast={this.toast}
-                                          config={this.state.config}/>}
+                                          toast={this.toast}/>}
                         {this.state.pageId === 'project' &&
-                            <ProjectPage key="projects" config={this.state.config}/>}
+                            <ProjectPage key="projects"/>}
                         {this.state.pageId === 'dashboard' && <DashboardPage key={this.state.request}
                                                                              toast={this.toast}
                                                                              config={this.state.config}/>}
diff --git a/karavan-app/src/main/webui/src/api/KaravanApi.tsx b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
index 06811cb6..617b2e96 100644
--- a/karavan-app/src/main/webui/src/api/KaravanApi.tsx
+++ b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
@@ -1,5 +1,6 @@
 import axios, {AxiosResponse } from "axios";
 import {
+    AppConfig,
     CamelStatus,
     DeploymentStatus,
     PipelineStatus,
@@ -130,7 +131,7 @@ export class KaravanApi {
         });
     }
 
-    static async getConfiguration(after: (config: {}) => void) {
+    static async getConfiguration(after: (config: AppConfig) => void) {
         instance.get('/api/configuration')
             .then(res => {
                 if (res.status === 200) {
diff --git a/karavan-app/src/main/webui/src/api/ProjectModels.ts b/karavan-app/src/main/webui/src/api/ProjectModels.ts
index b65ea9a8..63757dcc 100644
--- a/karavan-app/src/main/webui/src/api/ProjectModels.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectModels.ts
@@ -1,3 +1,11 @@
+export class AppConfig {
+    version: string = '';
+    environment: string = '';
+    environments: string[] = [];
+    runtime: string = '';
+    runtimes: string[] = [];
+}
+
 export class Project {
     projectId: string = '';
     name: string = '';
diff --git a/karavan-app/src/main/webui/src/api/ProjectStore.ts b/karavan-app/src/main/webui/src/api/ProjectStore.ts
index ca385656..a524f6bc 100644
--- a/karavan-app/src/main/webui/src/api/ProjectStore.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectStore.ts
@@ -16,12 +16,20 @@
  */
 
 import {create} from 'zustand'
-import {DeploymentStatus, Project, ProjectFile} from "./ProjectModels";
+import {AppConfig, DeploymentStatus, Project, ProjectFile} from "./ProjectModels";
+
+interface AppConfigState {
+    config: AppConfig;
+    setConfig: (config: AppConfig) => void;
+}
+
+export const useAppConfigStore = create<AppConfigState>((set) => ({
+    config: new AppConfig(),
+    setConfig: (config: AppConfig)  => {
+        set({config: config})
+    },
+}))
 
-const projects: Project[] = [];
-var project: Project = new Project();
-const files: ProjectFile[] = [];
-var file: ProjectFile | undefined = undefined;
 
 interface ProjectsState {
     projects: Project[];
@@ -70,14 +78,14 @@ export const useFilesStore = create<FilesState>((set) => ({
 
 interface FileState {
     file?: ProjectFile;
-    operation: "create" | "select" | "delete" | "none" | "copy";
-    setFile: (file: ProjectFile, operation:  "create" | "select" | "delete"| "none" | "copy") => void;
+    operation: "create" | "select" | "delete" | "none" | "copy" | "upload";
+    setFile: (file: ProjectFile, operation:  "create" | "select" | "delete"| "none" | "copy" | "upload") => void;
 }
 
 export const useFileStore = create<FileState>((set) => ({
     file: undefined,
     operation: "none",
-    setFile: (file: ProjectFile, operation:  "create" | "select" | "delete"| "none" | "copy") => {
+    setFile: (file: ProjectFile, operation:  "create" | "select" | "delete"| "none" | "copy" | "upload") => {
         set((state: FileState) => ({
             file: file,
             operation: operation,
diff --git a/karavan-app/src/main/webui/src/index.css b/karavan-app/src/main/webui/src/index.css
index e3525ba9..1baa8f91 100644
--- a/karavan-app/src/main/webui/src/index.css
+++ b/karavan-app/src/main/webui/src/index.css
@@ -172,9 +172,8 @@
   margin-bottom: 16px;
 }
 
-.karavan .project-page .project-bottom {
-  overflow: auto;
-  max-height: 100vh;
+.karavan .project-page .project-tab-panel .pf-c-panel__header {
+  padding-bottom: 0;
 }
 
 .karavan .project-page .project-operations {
diff --git a/karavan-app/src/main/webui/src/project/ProjectPage.tsx b/karavan-app/src/main/webui/src/project/ProjectPage.tsx
index 34c6c429..edf0ef6c 100644
--- a/karavan-app/src/main/webui/src/project/ProjectPage.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectPage.tsx
@@ -1,19 +1,11 @@
 import React, {useEffect, useState} from 'react';
 import {
-    Badge,
-    Breadcrumb,
-    BreadcrumbItem,
     PageSection,
-    Text,
-    TextContent,
-    Flex,
-    FlexItem,
     CodeBlockCode,
-    CodeBlock, Skeleton, Tabs, Tab
+    CodeBlock, Skeleton
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {KaravanApi} from "../api/KaravanApi";
-import {KaravanDesigner} from "../designer/KaravanDesigner";
 import FileSaver from "file-saver";
 import Editor from "@monaco-editor/react";
 import {PropertiesEditor} from "./PropertiesEditor";
@@ -21,84 +13,56 @@ import {ProjectModel, ProjectProperty} from "karavan-core/lib/model/ProjectModel
 import {ProjectModelApi} from "karavan-core/lib/api/ProjectModelApi";
 import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
 import {ProjectToolbar} from "./ProjectToolbar";
-import {FilesTab} from "./files/FilesTab";
 import {EventBus} from "../designer/utils/EventBus";
 import {ProjectLog} from "./ProjectLog";
-import {getProjectFileType, ProjectFile, ProjectFileTypes} from "../api/ProjectModels";
-import {useProjectStore} from "../api/ProjectStore";
+import {AppConfig, ProjectFile, ProjectFileTypes} from "../api/ProjectModels";
+import {useAppConfigStore, useFileStore, useProjectStore} from "../api/ProjectStore";
 import {ProjectService} from "../api/ProjectService";
-import {DashboardTab} from "./dashboard/DashboardTab";
-import {TraceTab} from "./trace/TraceTab";
-import {ProjectPipelineTab} from "./pipeline/ProjectPipelineTab";
 import {MainToolbar} from "../common/MainToolbar";
 import {CreateFileModal} from "./CreateFileModal";
 import {DeleteFileModal} from "./DeleteFileModal";
+import {ProjectTitle} from "./ProjectTitle";
+import {ProjectPanel} from "./ProjectPanel";
 
-interface Props {
-    config: any,
-}
-
-export const ProjectPage = (props: Props) => {
+export const ProjectPage = () => {
 
     const [isUploadModalOpen, setIsUploadModalOpen] = useState<boolean>(false);
-    const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<boolean>(false);
     const [editAdvancedProperties, setEditAdvancedProperties] = useState<boolean>(false);
-    const [files, setFiles] = useState<ProjectFile[]>([]);
-    const [file, setFile] = useState<ProjectFile | undefined>(undefined);
-    const [fileToDelete, setFileToDelete] = useState<ProjectFile | undefined>(undefined);
+    const {file, operation} = useFileStore();
     const [mode, setMode] = useState<"design" | "code">("design");
     const [key, setKey] = useState<string>('');
     const [tab, setTab] = useState<string | number>('files');
-    const [environments, setEnvironments] = useState<string[]>((
-        props.config.environments && Array.isArray(props.config.environments)) ? Array.from(props.config.environments) : []
-    );
-    const [environment, setEnvironment] = useState<string>(props.config.environment);
-    const {project, setProject} = useProjectStore();
+    const {project} = useProjectStore();
+    const {config} = useAppConfigStore();
 
     useEffect(() => {
-        // console.log("UseEffect ProjectPage")
-
         onRefresh();
-        // const sub1 = ProjectEventBus.onCurrentRunner()?.subscribe((result) => {
-            // setCurrentRunner(result || '');
-        // });
-        // return () => {
-        //     sub1.unsubscribe();
-        // };
     });
 
-    function needCommit(): boolean {
-        return project ? files.filter(f => f.lastUpdate > project.lastCommitTimestamp).length > 0 : false;
-    }
-
     function onRefresh () {
-        ProjectService.refreshProjectData(environment);
+        ProjectService.refreshProjectData(config.environment);
     }
 
     function post (file: ProjectFile)  {
         KaravanApi.postProjectFile(file, res => {
             if (res.status === 200) {
                 const newFile = res.data;
-                setFiles((files => {
-                    const index = files.findIndex(f => f.name === newFile.name);
-                    if (index !== -1) files.splice(index, 1, newFile)
-                    else files.push(newFile);
-                    return files
-                }))
+                // setFiles((files => {
+                //     const index = files.findIndex(f => f.name === newFile.name);
+                //     if (index !== -1) files.splice(index, 1, newFile)
+                //     else files.push(newFile);
+                //     return files
+                // }))
             } else {
                 // console.log(res) //TODO show notification
             }
         })
     }
 
-    function copyToClipboard (data: string) {
-        navigator.clipboard.writeText(data);
-    }
-
     function save (name: string, code: string) {
         if (file) {
             file.code = code;
-            setFile(file);
+            // setFile(file);
             post(file);
         }
     }
@@ -132,93 +96,33 @@ export const ProjectPage = (props: Props) => {
                                mode={mode}
                                isTemplates={false}
                                isKamelets={false}
-                               config={props.config}
                                addProperty={() => addProperty()}
-                               download={() => download()}
                                downloadImage={() => downloadImage()}
                                editAdvancedProperties={editAdvancedProperties}
                                setEditAdvancedProperties={checked => setEditAdvancedProperties(checked)}
                                setMode={mode => setMode(mode)}
                                setUploadModalOpen={() => setIsUploadModalOpen(isUploadModalOpen)}
-                               needCommit={needCommit()}
+                               needCommit={false}
                                onRefresh={onRefresh}
         />
     }
 
-
-    function title ()  {
-        const isFile = file !== undefined;
-        const isLog = file !== undefined && file.name.endsWith("log");
-        const filename = file ? file.name.substring(0, file.name.lastIndexOf('.')) : "";
-        return (<div className="dsl-title project-title">
-            {isFile && <Flex direction={{default: "column"}} >
-                <FlexItem>
-                    <Breadcrumb>
-                        <BreadcrumbItem to="#" onClick={event => {
-                            setFile(undefined)
-                            onRefresh();
-                        }}>
-                            <div className={"project-breadcrumb"}>{project?.name + " (" + project?.projectId + ")"}</div>
-                        </BreadcrumbItem>
-                    </Breadcrumb>
-                </FlexItem>
-                <FlexItem>
-                    <Flex direction={{default: "row"}}>
-                        <FlexItem>
-                            <Badge>{getProjectFileType(file)}</Badge>
-                        </FlexItem>
-                        <FlexItem>
-                            <TextContent className="description">
-                                <Text>{isLog ? filename : file.name}</Text>
-                            </TextContent>
-                        </FlexItem>
-                    </Flex>
-                </FlexItem>
-            </Flex>}
-            {!isFile && <Flex direction={{default: "column"}} >
-                <FlexItem>
-                    <TextContent className="title">
-                        <Text component="h2">{project?.name + " (" + project?.projectId + ")"}</Text>
-                    </TextContent>
-                </FlexItem>
-                <FlexItem>
-                    <TextContent className="description">
-                        <Text>{project?.description}</Text>
-                    </TextContent>
-                </FlexItem>
-            </Flex>}
-        </div>)
-    };
-
-    function closeModal (isPushing: boolean = false) {
-        setIsUploadModalOpen(false);
-    }
-
-    function select (file: ProjectFile) {
-        setFile(file);
-    }
-
-    function openDeleteConfirmation (file: ProjectFile) {
-        setIsDeleteModalOpen(true)
-        setFileToDelete(file);
-    }
-
-    function getDesigner () {
-        return (
-            file !== undefined &&
-            <KaravanDesigner
-                dark={false}
-                key={"key"}
-                filename={file.name}
-                yaml={file.code}
-                onSave={(name, yaml) => save(name, yaml)}
-                onSaveCustomCode={(name, code) => post(new ProjectFile(name + ".java", project.projectId, code, Date.now()))}
-                onGetCustomCode={(name, javaType) => {
-                    return new Promise<string | undefined>(resolve => resolve(files.filter(f => f.name === name + ".java")?.at(0)?.code))
-                }}
-            />
-        )
-    }
+    // function getDesigner () {
+    //     return (
+    //         file !== undefined &&
+    //         <KaravanDesigner
+    //             dark={false}
+    //             key={"key"}
+    //             filename={file.name}
+    //             yaml={file.code}
+    //             onSave={(name, yaml) => save(name, yaml)}
+    //             onSaveCustomCode={(name, code) => post(new ProjectFile(name + ".java", project.projectId, code, Date.now()))}
+    //             onGetCustomCode={(name, javaType) => {
+    //                 return new Promise<string | undefined>(resolve => resolve(files.filter(f => f.name === name + ".java")?.at(0)?.code))
+    //             }}
+    //         />
+    //     )
+    // }
 
     function getEditor () {
         const language = file?.name.split('.').pop();
@@ -298,28 +202,6 @@ export const ProjectPage = (props: Props) => {
         )
     }
 
-    function getProjectPanel() {
-        return (
-            <Flex direction={{default: "column"}} spaceItems={{default: "spaceItemsNone"}}>
-                {getProjectPanelTabs()}
-                {getProjectPanelDetails()}
-            </Flex>
-        )
-    }
-
-    function getProjectPanelTabs() {
-        return (
-            <FlexItem className="project-tabs">
-                <Tabs activeKey={tab} onSelect={(event, tabIndex) => setTab(tabIndex)}>
-                    <Tab eventKey="files" title="Files"/>
-                    <Tab eventKey="dashboard" title="Dashboard"/>
-                    <Tab eventKey="trace" title="Trace"/>
-                    <Tab eventKey="pipeline" title="Pipeline"/>
-                </Tabs>
-            </FlexItem>
-        )
-    }
-
     function isBuildIn(): boolean {
         return ['kamelets', 'templates'].includes(project.projectId);
     }
@@ -332,25 +214,6 @@ export const ProjectPage = (props: Props) => {
         return project.projectId === 'templates';
     }
 
-    function getProjectPanelDetails() {
-        const buildIn = isBuildIn();
-        return (
-            <FlexItem>
-                {buildIn && tab === 'files' && <FilesTab/>}
-                {!buildIn &&
-                    <>
-                        {tab === 'files' && <FilesTab/>}
-                        {tab === 'dashboard' && project && <DashboardTab config={props.config}/>}
-                        {tab === 'trace' && project && <TraceTab config={props.config}/>}
-                        {tab === 'pipeline' && <ProjectPipelineTab project={project}
-                                                                   needCommit={needCommit()}
-                                                                   config={props.config}/>}
-                    </>
-                }
-            </FlexItem>
-        )
-    }
-
     function getFilePanel() {
         const isYaml = file !== undefined && file.name.endsWith("yaml");
         const isIntegration = isYaml && file?.code && CamelDefinitionYaml.yamlIsIntegration(file.code);
@@ -361,23 +224,24 @@ export const ProjectPage = (props: Props) => {
         const showEditor = isCode || (isYaml && !isIntegration) || (isYaml && mode === 'code');
         return (
             <>
-                {showDesigner && getDesigner()}
+                {/*{showDesigner && getDesigner()}*/}
                 {showEditor && getEditor()}
                 {isLog && getLogView()}
                 {isProperties && getPropertiesEditor()}
             </>
         )
     }
+    console.log(operation, file)
     const types = isBuildIn()
         ? (isKameletsProject() ? ['KAMELET'] : ['CODE', 'PROPERTIES'])
         : ProjectFileTypes.filter(p => !['PROPERTIES', 'LOG', 'KAMELET'].includes(p.name)).map(p => p.name);
     return (
         <PageSection key={key} className="kamelet-section project-page" padding={{default: 'noPadding'}}>
             <PageSection className="tools-section" padding={{default: 'noPadding'}}>
-                <MainToolbar title={title()} tools={tools()}/>
+                <MainToolbar title={<ProjectTitle/>} tools={tools()}/>
             </PageSection>
-            {file === undefined && getProjectPanel()}
-            {/*{file !== undefined && getFilePanel()}*/}
+            {file === undefined && operation !== 'select' && <ProjectPanel/>}
+            {file !== undefined && operation === 'select' && getFilePanel()}
             <ProjectLog/>
             <CreateFileModal types={types}/>
             <DeleteFileModal />
diff --git a/karavan-app/src/main/webui/src/project/ProjectPanel.tsx b/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
new file mode 100644
index 00000000..8fa32f2d
--- /dev/null
+++ b/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
@@ -0,0 +1,49 @@
+import React, {useState} from 'react';
+import {
+    Flex,
+    FlexItem, Tabs, Tab
+} from '@patternfly/react-core';
+import '../designer/karavan.css';
+import {FilesTab} from "./files/FilesTab";
+import {useProjectStore} from "../api/ProjectStore";
+import {DashboardTab} from "./dashboard/DashboardTab";
+import {TraceTab} from "./trace/TraceTab";
+import {ProjectPipelineTab} from "./pipeline/ProjectPipelineTab";
+
+export const ProjectPanel = () => {
+
+    const [tab, setTab] = useState<string | number>('files');
+    const {project} = useProjectStore();
+
+    function isBuildIn(): boolean {
+        return ['kamelets', 'templates'].includes(project.projectId);
+    }
+
+    const buildIn = isBuildIn();
+    return (
+        <Flex direction={{default: "column"}} spaceItems={{default: "spaceItemsNone"}}>
+            <FlexItem className="project-tabs">
+                {buildIn && <Tabs activeKey={tab} onSelect={(event, tabIndex) => setTab(tabIndex)}>
+                    <Tab eventKey="files" title="Files"/>
+                </Tabs>}
+                {!buildIn && <Tabs activeKey={tab} onSelect={(event, tabIndex) => setTab(tabIndex)}>
+                    <Tab eventKey="files" title="Files"/>
+                    <Tab eventKey="dashboard" title="Dashboard"/>
+                    <Tab eventKey="trace" title="Trace"/>
+                    <Tab eventKey="pipeline" title="Pipeline"/>
+                </Tabs>}
+            </FlexItem>
+            <FlexItem>
+                {buildIn && tab === 'files' && <FilesTab/>}
+                {!buildIn &&
+                    <>
+                        {tab === 'files' && <FilesTab/>}
+                        {tab === 'dashboard' && project && <DashboardTab/>}
+                        {tab === 'trace' && project && <TraceTab/>}
+                        {tab === 'pipeline' && <ProjectPipelineTab/>}
+                    </>
+                }
+            </FlexItem>
+        </Flex>
+    )
+}
diff --git a/karavan-app/src/main/webui/src/project/ProjectTitle.tsx b/karavan-app/src/main/webui/src/project/ProjectTitle.tsx
new file mode 100644
index 00000000..f3c0c816
--- /dev/null
+++ b/karavan-app/src/main/webui/src/project/ProjectTitle.tsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import {
+    Badge,
+    Breadcrumb,
+    BreadcrumbItem,
+    Text,
+    TextContent,
+    Flex,
+    FlexItem,
+} from '@patternfly/react-core';
+import '../designer/karavan.css';
+import {getProjectFileType} from "../api/ProjectModels";
+import {useAppConfigStore, useFileStore, useProjectStore} from "../api/ProjectStore";
+
+export const ProjectTitle = () => {
+
+    const {project} = useProjectStore();
+    const {file, operation, setFile} = useFileStore();
+    const {config} = useAppConfigStore();
+
+    const isFile = file !== undefined;
+    const isLog = file !== undefined && file.name.endsWith("log");
+    const filename = file ? file.name.substring(0, file.name.lastIndexOf('.')) : "";
+    return (<div className="dsl-title project-title">
+        {isFile && <Flex direction={{default: "column"}} >
+            <FlexItem>
+                <Breadcrumb>
+                    <BreadcrumbItem to="#" onClick={event => {
+                        useFileStore.setState({file: undefined, operation: 'none'});
+                    }}>
+                        <div className={"project-breadcrumb"}>{project?.name + " (" + project?.projectId + ")"}</div>
+                    </BreadcrumbItem>
+                </Breadcrumb>
+            </FlexItem>
+            <FlexItem>
+                <Flex direction={{default: "row"}}>
+                    <FlexItem>
+                        <Badge>{getProjectFileType(file)}</Badge>
+                    </FlexItem>
+                    <FlexItem>
+                        <TextContent className="description">
+                            <Text>{isLog ? filename : file.name}</Text>
+                        </TextContent>
+                    </FlexItem>
+                </Flex>
+            </FlexItem>
+        </Flex>}
+        {!isFile && <Flex direction={{default: "column"}} >
+            <FlexItem>
+                <TextContent className="title">
+                    <Text component="h2">{project?.name + " (" + project?.projectId + ")"}</Text>
+                </TextContent>
+            </FlexItem>
+            <FlexItem>
+                <TextContent className="description">
+                    <Text>{project?.description}</Text>
+                </TextContent>
+            </FlexItem>
+        </Flex>}
+    </div>)
+}
diff --git a/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx b/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
index 1957f9d6..0e7a75c4 100644
--- a/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
@@ -31,19 +31,17 @@ import ReloadIcon from "@patternfly/react-icons/dist/esm/icons/bolt-icon";
 import {RunnerToolbar} from "./RunnerToolbar";
 import {Project, ProjectFile} from "../api/ProjectModels";
 import {ProjectEventBus} from "../api/ProjectEventBus";
-import {useFileStore} from "../api/ProjectStore";
+import {useAppConfigStore, useFilesStore, useFileStore, useProjectStore} from "../api/ProjectStore";
 
 interface Props {
     project: Project,
     needCommit: boolean,
     isTemplates: boolean,
     isKamelets: boolean,
-    config: any,
     file?: ProjectFile,
     mode: "design" | "code",
     editAdvancedProperties: boolean,
     addProperty: () => void,
-    download: () => void,
     downloadImage: () => void,
     setUploadModalOpen: () => void,
     setEditAdvancedProperties: (checked: boolean) => void,
@@ -62,6 +60,9 @@ export const ProjectToolbar = (props: Props) => {
     const [isRunning, setIsRunning] = useState(false);
     const [isDeletingPod, setIsDeletingPod] = useState(false);
     const [isReloadingPod, setIsReloadingPod] = useState(false);
+    const {project} = useProjectStore();
+    const {files} = useFilesStore();
+    const {config} = useAppConfigStore();
 
     useEffect(() => {
         const sub1 = ProjectEventBus.onCurrentRunner()?.subscribe((result) => {
@@ -73,6 +74,10 @@ export const ProjectToolbar = (props: Props) => {
         };
     });
 
+    function needCommit(): boolean {
+        return project ? files.filter(f => f.lastUpdate > project.lastCommitTimestamp).length > 0 : false;
+    }
+
     function jbangRun() {
         setJbangIsRunning(true);
         KaravanApi.runProject(props.project, res => {
@@ -80,7 +85,7 @@ export const ProjectToolbar = (props: Props) => {
                 ProjectEventBus.setCurrentRunner(props.project.name);
                 setJbangIsRunning(false);
                 setPodName(res.data);
-                ProjectEventBus.showLog('container', res.data, props.config.environment)
+                ProjectEventBus.showLog('container', res.data, config.environment)
             } else {
                 // Todo notification
                 setJbangIsRunning(false);
@@ -164,7 +169,7 @@ export const ProjectToolbar = (props: Props) => {
     }
 
     function getTemplatesToolbar() {
-        const {file,needCommit, editAdvancedProperties, download, setUploadModalOpen} = props;
+        const {file,needCommit, editAdvancedProperties, setUploadModalOpen} = props;
         const isFile = file !== undefined;
         const isProperties = file !== undefined && file.name.endsWith("properties");
         return <Toolbar id="toolbar-group-types">
@@ -194,11 +199,7 @@ export const ProjectToolbar = (props: Props) => {
                                 onChange={checked => props.setEditAdvancedProperties(checked)}
                             />
                         </FlexItem>}
-                        {isFile && <FlexItem>
-                            <Tooltip content="Download source" position={"bottom-end"}>
-                                <Button isSmall variant="control" icon={<DownloadIcon/>} onClick={e => download()}/>
-                            </Tooltip>
-                        </FlexItem>}
+
                         {!isFile && <FlexItem>
                             <Button isSmall variant={"secondary"} icon={<PlusIcon/>}
                                     onClick={e => ProjectEventBus.showCreateProjectModal(true)}>Create</Button>
@@ -214,8 +215,8 @@ export const ProjectToolbar = (props: Props) => {
     }
 
     function getProjectToolbar() {
-        const {file,needCommit, mode, editAdvancedProperties, project, config,
-            addProperty, setEditAdvancedProperties, download, downloadImage, setUploadModalOpen} = props;
+        const {file,needCommit, mode, editAdvancedProperties, project,
+            addProperty, setEditAdvancedProperties, downloadImage, setUploadModalOpen} = props;
         const isFile = file !== undefined;
         const isYaml = file !== undefined && file.name.endsWith("yaml");
         const isIntegration = isYaml && file?.code && CamelDefinitionYaml.yamlIsIntegration(file.code);
@@ -262,27 +263,14 @@ export const ProjectToolbar = (props: Props) => {
                         <Button isSmall variant="primary" icon={<PlusIcon/>} onClick={e => addProperty()}>Add property</Button>
                     </FlexItem>}
 
-                    {isFile && <FlexItem>
-                        <Tooltip content="Download source" position={"bottom-end"}>
-                            <Button isSmall variant="control" icon={<DownloadIcon/>} onClick={e => download()}/>
-                        </Tooltip>
-                    </FlexItem>}
+
                     {isIntegration && <FlexItem>
                         <Tooltip content="Download image" position={"bottom-end"}>
                             <Button isSmall variant="control" icon={<DownloadImageIcon/>} onClick={e => downloadImage()}/>
                         </Tooltip>
                     </FlexItem>}
-                    {!isFile && <FlexItem>
-                        <Button isSmall variant={"secondary"} icon={<PlusIcon/>}
-                                onClick={e => useFileStore.setState({operation:"create"})}>Create</Button>
-                    </FlexItem>}
-                    {!isFile && <FlexItem>
-                        <Button isSmall variant="secondary" icon={<UploadIcon/>}
-                                onClick={e => setUploadModalOpen()}>Upload</Button>
-                    </FlexItem>}
-
                     {isYaml && currentRunner === project.name && <FlexItem>
-                        <RunnerToolbar project={project} config={config} showConsole={false} reloadOnly={true} />
+                        <RunnerToolbar project={project} showConsole={false} reloadOnly={true} />
                     </FlexItem>}
                 </Flex>
             </ToolbarContent>
diff --git a/karavan-app/src/main/webui/src/project/RunnerToolbar.tsx b/karavan-app/src/main/webui/src/project/RunnerToolbar.tsx
index 4b63bf2b..e309a531 100644
--- a/karavan-app/src/main/webui/src/project/RunnerToolbar.tsx
+++ b/karavan-app/src/main/webui/src/project/RunnerToolbar.tsx
@@ -11,11 +11,11 @@ import DeleteIcon from "@patternfly/react-icons/dist/esm/icons/times-circle-icon
 import {KaravanApi} from "../api/KaravanApi";
 import {Project} from "../api/ProjectModels";
 import {ProjectEventBus} from "../api/ProjectEventBus";
+import {useAppConfigStore} from "../api/ProjectStore";
 
 
 interface Props {
     project: Project,
-    config: any,
     showConsole: boolean,
     reloadOnly: boolean
 }
@@ -27,6 +27,7 @@ export const RunnerToolbar = (props: Props) => {
     const [isRunning, setIsRunning] = useState(false);
     const [isDeletingPod, setIsDeletingPod] = useState(false);
     const [isReloadingPod, setIsReloadingPod] = useState(false);
+    const {config} = useAppConfigStore();
 
     useEffect(() => {
         const sub1 = ProjectEventBus.onCurrentRunner()?.subscribe((result) => {
@@ -44,7 +45,7 @@ export const RunnerToolbar = (props: Props) => {
                 ProjectEventBus.setCurrentRunner(props.project.name);
                 setJbangIsRunning(false);
                 setPodName(res.data);
-                ProjectEventBus.showLog('container', res.data, props.config.environment)
+                ProjectEventBus.showLog('container', res.data, config.environment)
             } else {
                 // Todo notification
                 setJbangIsRunning(false);
diff --git a/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx b/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx
index 7eddf0c6..6f919cad 100644
--- a/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx
+++ b/karavan-app/src/main/webui/src/project/dashboard/DashboardTab.tsx
@@ -9,18 +9,14 @@ import {RunnerInfoContext} from "./RunnerInfoContext";
 import {RunnerInfoMemory} from "./RunnerInfoMemory";
 import {KaravanApi} from "../../api/KaravanApi";
 import {PodStatus} from "../../api/ProjectModels";
-import {useProjectStore} from "../../api/ProjectStore";
+import {useAppConfigStore, useProjectStore} from "../../api/ProjectStore";
 import {ProjectEventBus} from "../../api/ProjectEventBus";
 
 export function isRunning(status: PodStatus): boolean {
     return status.phase === 'Running' && !status.terminating;
 }
 
-interface Props {
-    config: any,
-}
-
-export const DashboardTab = (props: Props) => {
+export const DashboardTab = () => {
 
     const {project, setProject} = useProjectStore();
     const [podStatus, setPodStatus] = useState(new PodStatus());
@@ -28,6 +24,7 @@ export const DashboardTab = (props: Props) => {
     const [memory, setMemory] = useState({});
     const [jvm, setJvm] = useState({});
     const [context, setContext] = useState({});
+    const {config} = useAppConfigStore();
 
     useEffect(() => {
         previousValue.current = podStatus;
@@ -47,10 +44,10 @@ export const DashboardTab = (props: Props) => {
             if (res.status === 200) {
                 setPodStatus(res.data);
                 if (isRunning(res.data) && !isRunning(previousValue.current)) {
-                    ProjectEventBus.showLog('container', res.data.name, props.config.environment);
+                    ProjectEventBus.showLog('container', res.data.name, config.environment);
                 }
             } else {
-                ProjectEventBus.showLog('container', name, props.config.environment, false);
+                ProjectEventBus.showLog('container', name, config.environment, false);
                 setPodStatus(new PodStatus({name: name}));
             }
         });
@@ -81,9 +78,8 @@ export const DashboardTab = (props: Props) => {
         return podStatus.phase !== '';
     }
 
-    const {config} = props;
     return (
-        <PageSection className="project-bottom" padding={{default: "padding"}}>
+        <PageSection className="project-tab-panel" padding={{default: "padding"}}>
             <Card className="project-development">
                 <CardBody>
                     <Flex direction={{default: "row"}}
@@ -93,11 +89,11 @@ export const DashboardTab = (props: Props) => {
                         </FlexItem>
                         <Divider orientation={{default: "vertical"}}/>
                         <FlexItem flex={{default: "flex_1"}}>
-                            <RunnerInfoMemory jvm={jvm} memory={memory} config={config} showConsole={showConsole()}/>
+                            <RunnerInfoMemory jvm={jvm} memory={memory} showConsole={showConsole()}/>
                         </FlexItem>
                         <Divider orientation={{default: "vertical"}}/>
                         <FlexItem flex={{default: "flex_1"}}>
-                            <RunnerInfoContext context={context} config={config} showConsole={showConsole()}/>
+                            <RunnerInfoContext context={context} showConsole={showConsole()}/>
                         </FlexItem>
                     </Flex>
                 </CardBody>
diff --git a/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoContext.tsx b/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoContext.tsx
index 62cc67ca..7683be64 100644
--- a/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoContext.tsx
+++ b/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoContext.tsx
@@ -14,12 +14,13 @@ import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
 
 interface Props {
     context: any,
-    config: any,
     showConsole: boolean
 }
 
 export const RunnerInfoContext = (props: Props) => {
 
+
+
     function getContextInfo() {
         return (
             <LabelGroup numLabels={3}>
diff --git a/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoMemory.tsx b/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoMemory.tsx
index 15c37bd0..67e576ad 100644
--- a/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoMemory.tsx
+++ b/karavan-app/src/main/webui/src/project/dashboard/RunnerInfoMemory.tsx
@@ -15,7 +15,6 @@ import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
 interface Props {
     jvm: any,
     memory: any,
-    config: any,
     showConsole: boolean
 }
 
diff --git a/karavan-app/src/main/webui/src/project/files/FilesTab.tsx b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
index feb57616..3ae79da5 100644
--- a/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
+++ b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
@@ -6,14 +6,17 @@ import {
     EmptyState,
     EmptyStateVariant,
     EmptyStateIcon,
-    Title, PageSection, PanelHeader, Flex, FlexItem, Panel,
+    Title, PageSection, PanelHeader, Panel, Tooltip,
 } from '@patternfly/react-core';
 import '../../designer/karavan.css';
 import {TableComposable, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
 import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
 import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
 import {useFilesStore, useFileStore} from "../../api/ProjectStore";
-import {getProjectFileType} from "../../api/ProjectModels";
+import {getProjectFileType, ProjectFile} from "../../api/ProjectModels";
+import {FileToolbar} from "./FilesToolbar";
+import DownloadIcon from "@patternfly/react-icons/dist/esm/icons/download-icon";
+import FileSaver from "file-saver";
 
 
 export const FilesTab = () => {
@@ -28,18 +31,20 @@ export const FilesTab = () => {
             return "N/A"
         }
     }
+
+    function download (file: ProjectFile) {
+        if (file) {
+            const type = file.name.endsWith("yaml") ? "application/yaml;charset=utf-8" : undefined;
+            const f = new File([file.code], file.name, {type: type});
+            FileSaver.saveAs(f);
+        }
+    }
+
     return (
-        <PageSection className="project-bottom" padding={{default: "padding"}}>
+        <PageSection className="project-tab-panel" padding={{default: "padding"}}>
             <Panel>
                 <PanelHeader>
-                    <Flex direction={{default: "row"}} justifyContent={{default:"justifyContentFlexEnd"}}>
-                        <FlexItem>
-
-                        </FlexItem>
-                        <FlexItem>
-
-                        </FlexItem>
-                    </Flex>
+                    <FileToolbar/>
                 </PanelHeader>
             </Panel>
             <TableComposable aria-label="Files" variant={"compact"} className={"table"}>
@@ -79,6 +84,9 @@ export const FilesTab = () => {
                                         <DeleteIcon/>
                                     </Button>
                                 }
+                                <Tooltip content="Download source" position={"bottom-end"}>
+                                    <Button isSmall variant="plain" icon={<DownloadIcon/>} onClick={e => download(file)}/>
+                                </Tooltip>
                             </Td>
                         </Tr>
                     })}
diff --git a/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx b/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
new file mode 100644
index 00000000..2ef4c092
--- /dev/null
+++ b/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import {
+    Button,
+    Flex,
+    FlexItem,
+} from '@patternfly/react-core';
+import '../../designer/karavan.css';
+import UploadIcon from "@patternfly/react-icons/dist/esm/icons/upload-icon";
+import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
+import {useFileStore} from "../../api/ProjectStore";
+
+export const FileToolbar = () => {
+
+    return <Flex className="toolbar" direction={{default: "row"}} justifyContent={{default: "justifyContentFlexEnd"}}>
+        <FlexItem>
+            <Button isSmall variant={"secondary"} icon={<PlusIcon/>}
+                    onClick={e => useFileStore.setState({operation:"create"})}>Create</Button>
+        </FlexItem>
+        <FlexItem>
+            <Button isSmall variant="secondary" icon={<UploadIcon/>}
+                    onClick={e => useFileStore.setState({operation:"upload"})}>Upload</Button>
+        </FlexItem>
+    </Flex>
+}
diff --git a/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx b/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
index 54cd6f99..6cd7e4fd 100644
--- a/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
+++ b/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
@@ -2,36 +2,22 @@ import React from 'react';
 import '../../designer/karavan.css';
 import {ProjectStatus} from "../ProjectStatus";
 import {PageSection} from "@patternfly/react-core";
-import {Project} from "../../api/ProjectModels";
+import {useAppConfigStore, useProjectStore} from "../../api/ProjectStore";
 
 
-interface Props {
-    project: Project,
-    config: any,
-    needCommit: boolean,
-}
-
-interface State {
-    environment: string,
-}
-
-export class ProjectPipelineTab extends React.Component<Props, State> {
+export const ProjectPipelineTab = () => {
 
-    public state: State = {
-        environment: this.props.config.environment
-    };
+    const {config} = useAppConfigStore();
+    const {project} = useProjectStore();
 
-    render() {
-        const {project, config,} = this.props;
-        return (
-            <PageSection className="project-bottom" padding={{default: "padding"}}>
-                <div className="project-operations">
-                    {/*{["dev", "test", "prod"].map(env =>*/}
-                    {["dev"].map(env =>
-                        <ProjectStatus key={env} project={project} config={config} env={env}/>
-                    )}
-                </div>
-            </PageSection>
-        )
-    }
+    return (
+        <PageSection className="project-tab-panel" padding={{default: "padding"}}>
+            <div className="project-operations">
+                {/*{["dev", "test", "prod"].map(env =>*/}
+                {["dev"].map(env =>
+                    <ProjectStatus key={env} project={project} config={config} env={env}/>
+                )}
+            </div>
+        </PageSection>
+    )
 }
diff --git a/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx b/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx
index 144c2b00..31f1c0c9 100644
--- a/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx
+++ b/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx
@@ -6,21 +6,18 @@ import {PodStatus} from "../../api/ProjectModels";
 import {KaravanApi} from "../../api/KaravanApi";
 import {ProjectEventBus} from "../../api/ProjectEventBus";
 import {RunnerInfoTrace} from "./RunnerInfoTrace";
-import {useProjectStore} from "../../api/ProjectStore";
+import {useAppConfigStore, useProjectStore} from "../../api/ProjectStore";
 
 export function isRunning(status: PodStatus): boolean {
     return status.phase === 'Running' && !status.terminating;
 }
 
-interface Props {
-    config: any,
-}
-
-export const TraceTab = (props: Props) => {
+export const TraceTab = () => {
 
     const {project, setProject} = useProjectStore();
     const [trace, setTrace] = useState({});
     const [refreshTrace, setRefreshTrace] = useState(true);
+    const {config} = useAppConfigStore();
 
     useEffect(() => {
         const sub2 = ProjectEventBus.onRefreshTrace()?.subscribe((result: boolean) => {
@@ -50,9 +47,8 @@ export const TraceTab = (props: Props) => {
         }
     }
 
-    const {config} = props;
     return (
-        <PageSection className="project-bottom" padding={{default: "padding"}}>
+        <PageSection className="project-tab-panel" padding={{default: "padding"}}>
             <RunnerInfoTrace trace={trace} refreshTrace={refreshTrace}/>
         </PageSection>
     )
diff --git a/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx b/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx
index 19fa2cb5..7f635a47 100644
--- a/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx
+++ b/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx
@@ -6,23 +6,21 @@ import {
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
-import {useProjectStore} from "../api/ProjectStore";
+import {useAppConfigStore, useProjectStore} from "../api/ProjectStore";
 import {ProjectService} from "../api/ProjectService";
 import {Project} from "../api/ProjectModels";
 import {QuarkusIcon, SpringIcon} from "../designer/utils/KaravanIcons";
 import {CamelUi} from "../designer/utils/CamelUi";
 
-interface Props {
-    config: any,
-}
 
-export const CreateProjectModal = (props: Props) => {
+export const CreateProjectModal = () => {
 
     const {project, operation} = useProjectStore();
     const [name, setName] = useState('');
     const [description, setDescription] = useState('');
     const [runtime, setRuntime] = useState('');
     const [projectId, setProjectId] = useState('');
+    const {config} = useAppConfigStore();
 
     function cleanValues()  {
         setName("");
@@ -48,7 +46,7 @@ export const CreateProjectModal = (props: Props) => {
         }
     }
 
-    const runtimes = props.config.runtimes;
+    const runtimes = config.runtimes;
     const isReady = projectId && name && description && !['templates', 'kamelets'].includes(projectId);
     return (
         <Modal
diff --git a/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx b/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
index dec4ea29..5edf4006 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
@@ -22,14 +22,13 @@ 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 "../common/MainToolbar";
 import {Project} from "../api/ProjectModels";
 
 
 interface Props {
-    config: any,
     toast: (title: string, text: string, variant: 'success' | 'danger' | 'warning' | 'info' | 'default') => void
 }
 
@@ -39,6 +38,7 @@ export const ProjectsPage = (props: Props) => {
     const {operation} = useProjectStore();
     const [filter, setFilter] = useState<string>('');
     const [loading, setLoading] = useState<boolean>(false);
+    const {config} = useAppConfigStore();
 
     useEffect(() => {
         const interval = setInterval(() => {
@@ -119,7 +119,6 @@ export const ProjectsPage = (props: Props) => {
                     {projs.map(project => (
                         <ProjectsTableRow
                             key={project.projectId}
-                            config={props.config}
                             project={project}/>
                     ))}
                     {projs.length === 0 && getEmptyState()}
@@ -136,7 +135,7 @@ export const ProjectsPage = (props: Props) => {
             <PageSection isFilled className="kamelets-page">
                 {getProjectsTable()}
             </PageSection>
-            {["create", "copy"].includes(operation) && <CreateProjectModal config={props.config}/>}
+            {["create", "copy"].includes(operation) && <CreateProjectModal/>}
             {["delete"].includes(operation) && <DeleteProjectModal/>}
         </PageSection>
     )
diff --git a/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx b/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
index 9ce6b07e..701d1424 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
@@ -10,20 +10,20 @@ import { Td, Tr} from "@patternfly/react-table";
 import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
 import CopyIcon from "@patternfly/react-icons/dist/esm/icons/copy-icon";
 import {DeploymentStatus, Project} from '../api/ProjectModels';
-import {useDeploymentStatusesStore, useProjectStore} from "../api/ProjectStore";
+import {useAppConfigStore, useDeploymentStatusesStore, useProjectStore} from "../api/ProjectStore";
 import {ProjectEventBus} from "../api/ProjectEventBus";
 
 interface Props {
-    config: any,
     project: Project
 }
 
 export const ProjectsTableRow = (props: Props) => {
 
-    const {statuses} = useDeploymentStatusesStore()
+    const {statuses} = useDeploymentStatusesStore();
+    const {config} = useAppConfigStore();
 
     function getEnvironments(): string [] {
-        return props.config.environments && Array.isArray(props.config.environments) ? Array.from(props.config.environments) : [];
+        return config.environments && Array.isArray(config.environments) ? Array.from(config.environments) : [];
     }
 
     function getDeploymentByEnvironments(name: string): [string, DeploymentStatus | undefined] [] {