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

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


The following commit(s) were added to refs/heads/main by this push:
     new 3c5bd25e Redesign of Project pages #809
3c5bd25e is described below

commit 3c5bd25e7452b2aee2c6b77b2ea7050682683f1a
Author: Marat Gubaidullin <[email protected]>
AuthorDate: Sat Jun 24 23:33:42 2023 -0400

    Redesign of Project pages #809
---
 .../camel/karavan/service/KubernetesService.java   |   3 +-
 karavan-app/src/main/webui/package-lock.json       |  36 +-
 karavan-app/src/main/webui/package.json            |   3 +-
 karavan-app/src/main/webui/src/Main.tsx            |  14 +-
 karavan-app/src/main/webui/src/MainToolbar.tsx     |  54 ++-
 .../webui/src/designer/route/DslConnections.tsx    |   1 -
 .../route/property/ComponentParameterField.tsx     |  23 +-
 .../designer/route/property/DslPropertyField.tsx   |   5 +-
 .../src/main/webui/src/designer/utils/CamelUi.tsx  |   6 +-
 .../main/webui/src/projects/CreateFileModal.tsx    | 103 ------
 .../src/main/webui/src/projects/ProjectEventBus.ts |  18 +-
 .../src/main/webui/src/projects/ProjectLogic.ts    |  95 +++++
 .../main/webui/src/projects/ProjectOperations.tsx  |  34 --
 .../src/main/webui/src/projects/ProjectPage.tsx    | 402 ++++++++-------------
 .../src/main/webui/src/projects/ProjectStore.ts    | 100 +++++
 .../src/main/webui/src/projects/ProjectsPage.tsx   | 331 ++++-------------
 .../main/webui/src/projects/ProjectsTableRow.tsx   | 154 ++++----
 .../main/webui/src/projects/RunnerInfoTrace.tsx    |  99 -----
 .../webui/src/projects/modal/CreateFileModal.tsx   |  93 +++++
 .../src/projects/modal/CreateProjectModal.tsx      | 103 ++++++
 .../webui/src/projects/modal/DeleteFileModal.tsx   |  40 ++
 .../src/projects/modal/DeleteProjectModal.tsx      |  53 +++
 .../DashboardTab.tsx}                              |  74 ++--
 .../ProjectFilesTab.tsx}                           |  43 +--
 .../webui/src/projects/tabs/ProjectPipelineTab.tsx |  37 ++
 .../src/projects/{ => tabs}/RunnerInfoContext.tsx  |   8 +-
 .../src/projects/{ => tabs}/RunnerInfoMemory.tsx   |   5 +-
 .../src/projects/{ => tabs}/RunnerInfoPod.tsx      |  63 ++--
 .../webui/src/projects/tabs/RunnerInfoTrace.tsx    | 124 +++++++
 .../projects/{ => tabs}/RunnerInfoTraceModal.tsx   |   4 +-
 .../projects/{ => tabs}/RunnerInfoTraceNode.tsx    |   2 +-
 .../src/main/webui/src/projects/tabs/TraceTab.tsx  |  61 ++++
 .../ProjectToolbar.tsx}                            |  22 +-
 33 files changed, 1192 insertions(+), 1021 deletions(-)

diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java
 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java
index 47caea63..de3dd739 100644
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java
+++ 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/KubernetesService.java
@@ -93,7 +93,6 @@ public class KubernetesService implements HealthCheck{
     @ConfigProperty(name = "karavan.version")
     String version;
 
-
     List<SharedIndexInformer> informers = new ArrayList<>(INFORMERS);
 
     @ConsumeEvent(value = START_INFORMERS, blocking = true)
@@ -547,7 +546,7 @@ public class KubernetesService implements HealthCheck{
     }
 
     public boolean inKubernetes() {
-        return !Objects.equals(getNamespace(), "localhost");
+        return Objects.nonNull(System.getenv("KUBERNETES_SERVICE_HOST"));
     }
 
 }
diff --git a/karavan-app/src/main/webui/package-lock.json 
b/karavan-app/src/main/webui/package-lock.json
index f25d5bff..2e4293f9 100644
--- a/karavan-app/src/main/webui/package-lock.json
+++ b/karavan-app/src/main/webui/package-lock.json
@@ -27,7 +27,8 @@
         "react": "18.2.0",
         "react-dom": "18.2.0",
         "rxjs": "7.8.1",
-        "uuid": "9.0.0"
+        "uuid": "9.0.0",
+        "zustand": "^4.3.8"
       },
       "devDependencies": {
         "@svgr/webpack": "^7.0.0",
@@ -9682,7 +9683,7 @@
       "version": "9.0.21",
       "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz";,
       "integrity": 
"sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
-      "dev": true,
+      "devOptional": true,
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/immer";
@@ -17752,6 +17753,14 @@
         "requires-port": "^1.0.0"
       }
     },
+    "node_modules/use-sync-external-store": {
+      "version": "1.2.0",
+      "resolved": 
"https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz";,
+      "integrity": 
"sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+      }
+    },
     "node_modules/util-deprecate": {
       "version": "1.0.2",
       "resolved": 
"https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz";,
@@ -19110,6 +19119,29 @@
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus";
       }
+    },
+    "node_modules/zustand": {
+      "version": "4.3.8",
+      "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.8.tgz";,
+      "integrity": 
"sha512-4h28KCkHg5ii/wcFFJ5Fp+k1J3gJoasaIbppdgZFO4BPJnsNxL0mQXBSFgOgAdCdBj35aDTPvdAJReTMntFPGg==",
+      "dependencies": {
+        "use-sync-external-store": "1.2.0"
+      },
+      "engines": {
+        "node": ">=12.7.0"
+      },
+      "peerDependencies": {
+        "immer": ">=9.0",
+        "react": ">=16.8"
+      },
+      "peerDependenciesMeta": {
+        "immer": {
+          "optional": true
+        },
+        "react": {
+          "optional": true
+        }
+      }
     }
   }
 }
diff --git a/karavan-app/src/main/webui/package.json 
b/karavan-app/src/main/webui/package.json
index ca29304b..f3d65b03 100644
--- a/karavan-app/src/main/webui/package.json
+++ b/karavan-app/src/main/webui/package.json
@@ -45,7 +45,8 @@
     "react": "18.2.0",
     "react-dom": "18.2.0",
     "rxjs": "7.8.1",
-    "uuid": "9.0.0"
+    "uuid": "9.0.0",
+    "zustand": "^4.3.8"
   },
   "devDependencies": {
     "@svgr/webpack": "^7.0.0",
diff --git a/karavan-app/src/main/webui/src/Main.tsx 
b/karavan-app/src/main/webui/src/Main.tsx
index af07dc78..759e77d1 100644
--- a/karavan-app/src/main/webui/src/Main.tsx
+++ b/karavan-app/src/main/webui/src/Main.tsx
@@ -65,10 +65,7 @@ interface Props {
 interface State {
     config: any,
     pageId: string,
-    projects: Project[],
-    project?: Project,
     isModalOpen: boolean,
-    projectToDelete?: Project,
     openapi: string,
     alerts: ToastMessage[],
     request: string,
@@ -82,7 +79,6 @@ export class Main extends React.Component<Props, State> {
     public state: State = {
         config: {},
         pageId: "projects",
-        projects: [],
         isModalOpen: false,
         alerts: [],
         request: uuidv4(),
@@ -96,7 +92,7 @@ export class Main extends React.Component<Props, State> {
 
     componentDidMount() {
         this.sub = ProjectEventBus.onSelectProject()?.subscribe((project: 
Project | undefined) => {
-            if (project) this.onProjectSelect(project);
+            if (project) this.setState({pageId: "project"});
         });
         KaravanApi.getAuthType((authType: string) => {
             console.log("authType", authType);
@@ -235,10 +231,6 @@ export class Main extends React.Component<Props, State> {
         this.setState({alerts: mess})
     }
 
-    onProjectSelect = (project: Project) => {
-        this.setState({pageId: 'project', project: project});
-    };
-
     getMain() {
         return (
             <>
@@ -252,8 +244,8 @@ export class Main extends React.Component<Props, State> {
                             <ProjectsPage key={this.state.request}
                                           toast={this.toast}
                                           config={this.state.config}/>}
-                        {this.state.pageId === 'project' && this.state.project 
&&
-                            <ProjectPage key="projects" 
project={this.state.project} config={this.state.config}/>}
+                        {this.state.pageId === 'project' &&
+                            <ProjectPage key="projects" 
config={this.state.config}/>}
                         {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/MainToolbar.tsx 
b/karavan-app/src/main/webui/src/MainToolbar.tsx
index 74cb93d9..c312ae8c 100644
--- a/karavan-app/src/main/webui/src/MainToolbar.tsx
+++ b/karavan-app/src/main/webui/src/MainToolbar.tsx
@@ -1,29 +1,51 @@
-import React from 'react';
+import React, {useEffect, useState} from 'react';
 import {
     PageSectionVariants, Flex, PageSection, FlexItem
 } from '@patternfly/react-core';
 import './designer/karavan.css';
+import {ProjectEventBus} from "./projects/ProjectEventBus";
+import {Project, ProjectFile} from "./projects/ProjectModels";
 
 interface Props {
     title: React.ReactNode;
     tools: React.ReactNode;
+    file?: ProjectFile;
 }
 
-export class MainToolbar extends React.PureComponent<Props> {
-    render() {
-        const { title, tools } = this.props;
+export const MainToolbar = (props: Props) => {
+    const {title, tools, file} = props;
 
-        return (
-            <PageSection className="tools-section" 
variant={PageSectionVariants.light}>
-                <Flex className="tools" justifyContent={{default: 
'justifyContentSpaceBetween'}} alignItems={{default:'alignItemsCenter'}}>
-                    <FlexItem>
-                        {title}
-                    </FlexItem>
-                    <FlexItem>
-                        {tools}
-                    </FlexItem>
-                </Flex>
-            </PageSection>
-        );
+    const [project, setProject] = useState<Project | undefined>(undefined);
+    const [mode, setMode] = useState<"design" | "code">("design");
+
+    useEffect(() => {
+        const sub1 = ProjectEventBus.onSelectProject()?.subscribe((result) => 
setProject(result));
+        const sub2 = ProjectEventBus.onSetMode()?.subscribe((result) => 
setMode(result));
+        return () => {
+            sub1.unsubscribe();
+            sub2.unsubscribe();
+        };
+    });
+
+    function isKameletsProject(): boolean {
+        return project?.projectId === 'kamelets';
     }
+
+    function isTemplatesProject(): boolean {
+        return project?.projectId === 'templates';
+    }
+
+    return (
+        <PageSection className="tools-section" 
variant={PageSectionVariants.light}>
+            <Flex className="tools" justifyContent={{default: 
'justifyContentSpaceBetween'}}
+                  alignItems={{default: 'alignItemsCenter'}}>
+                <FlexItem>
+                    {title}
+                </FlexItem>
+                <FlexItem>
+                    {tools}
+                </FlexItem>
+            </Flex>
+        </PageSection>
+    );
 }
diff --git a/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx 
b/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
index f18866e1..7ccfe8fe 100644
--- a/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
+++ b/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx
@@ -147,7 +147,6 @@ export class DslConnections extends React.Component<Props, 
State> {
         while (this.hasOverlap(outs)) {
             outs = this.addGap(outs);
         }
-        // console.log(outs);
         return outs;
     }
 
diff --git 
a/karavan-app/src/main/webui/src/designer/route/property/ComponentParameterField.tsx
 
b/karavan-app/src/main/webui/src/designer/route/property/ComponentParameterField.tsx
index faad51c8..e9c6d02f 100644
--- 
a/karavan-app/src/main/webui/src/designer/route/property/ComponentParameterField.tsx
+++ 
b/karavan-app/src/main/webui/src/designer/route/property/ComponentParameterField.tsx
@@ -59,6 +59,7 @@ interface State {
     showKubernetesSelector: boolean
     kubernetesSelectorProperty?: string
     ref: any
+    id: string
 }
 
 export class ComponentParameterField extends React.Component<Props, State> {
@@ -69,6 +70,7 @@ export class ComponentParameterField extends 
React.Component<Props, State> {
         showPassword: false,
         showKubernetesSelector: false,
         ref: React.createRef(),
+        id: prefix + "-" + this.props.property.name
     }
 
     parametersChanged = (parameter: string, value: string | number | boolean | 
any, pathParameter?: boolean, newRoute?: RouteToCreate) => {
@@ -93,6 +95,7 @@ export class ComponentParameterField extends 
React.Component<Props, State> {
         }
         return (
             <Select
+                id={this.state.id} name={this.state.id}
                 variant={SelectVariant.single}
                 aria-label={property.name}
                 onToggle={isExpanded => {
@@ -142,8 +145,9 @@ export class ComponentParameterField extends 
React.Component<Props, State> {
             selectOptions.push(...uris.map((value: string) =>
                 <SelectOption key={value} value={value.trim()}/>));
         }
-        return <InputGroup>
+        return <InputGroup id={this.state.id} name={this.state.id}>
             <Select
+                id={this.state.id} name={this.state.id}
                 placeholderText="Select or type an URI"
                 variant={SelectVariant.typeahead}
                 aria-label={property.name}
@@ -212,7 +216,6 @@ export class ComponentParameterField extends 
React.Component<Props, State> {
     getStringInput(property: ComponentProperty, value: any) {
         const {showEditor, showPassword} = this.state;
         const inKubernetes = KubernetesAPI.inKubernetes;
-        const id = prefix + "-" + property.name;
         const noKubeSelectorButton = ["uri", "id", "description", 
"group"].includes(property.name);
         return <InputGroup>
             {inKubernetes && !showEditor && !noKubeSelectorButton &&
@@ -224,14 +227,14 @@ export class ComponentParameterField extends 
React.Component<Props, State> {
             {(!showEditor || property.secret) &&
                 <TextInput className="text-field" isRequired 
ref={this.state.ref}
                            type={property.secret && !showPassword ? "password" 
: "text"}
-                           id={id} name={id}
+                           id={this.state.id} name={this.state.id}
                            value={value !== undefined ? value : 
property.defaultValue}
                            onChange={e => 
this.parametersChanged(property.name, e, property.kind === 'path')}/>}
             {showEditor && !property.secret &&
                 <TextArea autoResize={true} ref={this.state.ref}
                           className="text-field" isRequired
                           type="text"
-                          id={id} name={id}
+                          id={this.state.id} name={this.state.id}
                           value={value !== undefined ? value : 
property.defaultValue}
                           onChange={e => this.parametersChanged(property.name, 
e, property.kind === 'path')}/>}
             {!property.secret &&
@@ -252,12 +255,11 @@ export class ComponentParameterField extends 
React.Component<Props, State> {
     }
 
     getTextInput = (property: ComponentProperty, value: any) => {
-        const id = prefix + "-" + property.name;
         return (
             <TextInput
                 className="text-field" isRequired
                 type={['integer', 'int', 'number'].includes(property.type) ? 
'number' : (property.secret ? "password" : "text")}
-                id={id} name={id}
+                id={this.state.id} name={this.state.id}
                 value={value !== undefined ? value : property.defaultValue}
                 onChange={e => this.parametersChanged(property.name, 
['integer', 'int', 'number'].includes(property.type) ? Number(e) : e, 
property.kind === 'path')}/>
         )
@@ -271,6 +273,7 @@ export class ComponentParameterField extends 
React.Component<Props, State> {
         }
         return (
             <Select
+                id={this.state.id} name={this.state.id}
                 variant={SelectVariant.single}
                 aria-label={property.name}
                 onToggle={isExpanded => {
@@ -288,12 +291,11 @@ export class ComponentParameterField extends 
React.Component<Props, State> {
     }
 
     getSwitch = (property: ComponentProperty, value: any) => {
-        const id = prefix + "-" + property.name;
         return (
             <Switch
-                id={id} name={id}
+                id={this.state.id} name={this.state.id}
                 value={value?.toString()}
-                aria-label={id}
+                aria-label={this.state.id}
                 isChecked={value !== undefined ? Boolean(value) : 
property.defaultValue !== undefined && property.defaultValue === 'true'}
                 onChange={e => this.parametersChanged(property.name, 
!Boolean(value))}/>
         )
@@ -302,12 +304,11 @@ export class ComponentParameterField extends 
React.Component<Props, State> {
     render() {
         const property: ComponentProperty = this.props.property;
         const value = this.props.value;
-        const id = prefix + "-" + property.name;
+        const id = this.state.id;
         return (
             <FormGroup
                 key={id}
                 label={property.displayName}
-                fieldId={id}
                 isRequired={property.required}
                 labelIcon={
                     <Popover
diff --git 
a/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx 
b/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx
index 48f10440..fb81df12 100644
--- 
a/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx
+++ 
b/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx
@@ -301,7 +301,8 @@ export class DslPropertyField extends 
React.Component<Props, State> {
                     autoResize
                     className="text-field" isRequired
                     type={"text"}
-                    id={property.name} name={property.name}
+                    id={property.name}
+                    name={property.name}
                     height={"100px"}
                     value={value?.toString()}
                     onChange={e => this.propertyChanged(property.name, e)}/>
@@ -403,6 +404,7 @@ export class DslPropertyField extends 
React.Component<Props, State> {
                 onSelect={(e, value, isPlaceholder) => 
this.propertyChanged(property.name, (!isPlaceholder ? value : undefined))}
                 selections={value}
                 isOpen={this.isSelectOpen(property.name)}
+                id={property.name}
                 aria-labelledby={property.name}
                 direction={SelectDirection.down}
             >
@@ -697,7 +699,6 @@ export class DslPropertyField extends 
React.Component<Props, State> {
                 <FormGroup
                     label={this.props.hideLabel ? undefined : 
this.getLabel(property, value)}
                     isRequired={property.required}
-                    fieldId={property.name}
                     labelIcon={this.getLabelIcon(property)}>
                     {value && ["ExpressionDefinition", 
"ExpressionSubElementDefinition"].includes(property.type)
                         && this.getExpressionField(property, value)}
diff --git a/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx 
b/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
index 419db7a8..a577a069 100644
--- a/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
+++ b/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx
@@ -248,8 +248,10 @@ export class CamelUi {
         integration.spec.flows?.filter(f => f.dslName === 'RouteDefinition')
             .filter((r: RouteDefinition) => 
r.from.uri.startsWith(componentName))
             .forEach((r: RouteDefinition) => {
-                if (showComponentName) result.push(r.from.uri)
-                else result.push(r.from.uri.replace(componentName+":", ""));
+                const uri = r.from.uri;
+                const name = r.from.parameters.name;
+                if (showComponentName) result.push(uri + ":" + name);
+                else result.push(name);
             });
         return result;
     }
diff --git a/karavan-app/src/main/webui/src/projects/CreateFileModal.tsx 
b/karavan-app/src/main/webui/src/projects/CreateFileModal.tsx
deleted file mode 100644
index 0bbcd836..00000000
--- a/karavan-app/src/main/webui/src/projects/CreateFileModal.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import React from 'react';
-import {
-    Button,
-    Modal,
-    FormGroup,
-    ModalVariant,
-    Form,
-    ToggleGroupItem, ToggleGroup, FormHelperText, HelperText, HelperTextItem, 
TextInput
-} from '@patternfly/react-core';
-import '../designer/karavan.css';
-import {KaravanApi} from "../api/KaravanApi";
-import {Project, ProjectFile, ProjectFileTypes} from "./ProjectModels";
-import {CamelUi} from "../designer/utils/CamelUi";
-import {Integration} from "karavan-core/lib/model/IntegrationDefinition";
-import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
-
-interface Props {
-    isOpen: boolean,
-    project: Project,
-    onClose: any,
-    types: string[]
-}
-
-interface State {
-    name: string
-    fileType: string
-}
-
-export class CreateFileModal extends React.Component<Props, State> {
-
-    public state: State = {
-        name: '',
-        fileType: this.props.types.at(0) || 'INTEGRATION',
-    };
-
-    closeModal = () => {
-        this.props.onClose?.call(this);
-    }
-
-    saveAndCloseModal = () => {
-        const {name, fileType} = this.state;
-        const extension = ProjectFileTypes.filter(value => value.name === 
fileType)[0].extension;
-        const filename = (extension !== 'java') ? CamelUi.nameFromTitle(name) 
: CamelUi.javaNameFromTitle(name);
-        const code = fileType === 'INTEGRATION'
-            ? 
CamelDefinitionYaml.integrationToYaml(Integration.createNew(name, 'plain'))
-            : '';
-        if (filename && extension) {
-            const file = new ProjectFile(filename + '.' + extension, 
this.props.project.projectId, code, Date.now());
-            KaravanApi.postProjectFile(file, res => {
-                if (res.status === 200) {
-                    // console.log(res) //TODO show notification
-                    this.props.onClose?.call(this);
-                } else {
-                    // console.log(res) //TODO show notification
-                    this.props.onClose?.call(this);
-                }
-            })
-        }
-    }
-
-    render() {
-        const {fileType} = this.state;
-        const {types} = this.props;
-        const extension = ProjectFileTypes.filter(value => value.name === 
fileType)[0].extension;
-        const filename = (extension !== 'java')
-            ? CamelUi.nameFromTitle(this.state.name)
-            : CamelUi.javaNameFromTitle(this.state.name)
-        return (
-            <Modal
-                title="Create"
-                variant={ModalVariant.small}
-                isOpen={this.props.isOpen}
-                onClose={this.closeModal}
-                actions={[
-                    <Button key="confirm" variant="primary" 
onClick={this.saveAndCloseModal}>Save</Button>,
-                    <Button key="cancel" variant="secondary" 
onClick={this.closeModal}>Cancel</Button>
-                ]}
-            >
-                <Form autoComplete="off" isHorizontal 
className="create-file-form">
-                    <FormGroup label="Type" fieldId="type" isRequired>
-                        <ToggleGroup aria-label="Type" isCompact>
-                            {ProjectFileTypes.filter(p => 
types.includes(p.name))
-                                .map(p => {
-                                    const title = p.title + ' (' + p.extension 
+ ')';
-                                    return <ToggleGroupItem key={title} 
text={title} buttonId={p.name}
-                                                            
isSelected={fileType === p.name}
-                                                            onChange={selected 
=> this.setState({fileType: p.name})}/>
-                                })}
-                        </ToggleGroup>
-                    </FormGroup>
-                    <FormGroup label="Name" fieldId="name" isRequired>
-                        <TextInput value={this.state.name} onChange={value => 
this.setState({name: value})}/>
-                        <FormHelperText isHidden={false} component="div">
-                            <HelperText id="helper-text1">
-                                <HelperTextItem variant={'default'}>{filename 
+ '.' + extension}</HelperTextItem>
-                            </HelperText>
-                        </FormHelperText>
-                    </FormGroup>
-                </Form>
-            </Modal>
-        )
-    }
-}
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/projects/ProjectEventBus.ts 
b/karavan-app/src/main/webui/src/projects/ProjectEventBus.ts
index b98fbd20..142785b8 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectEventBus.ts
+++ b/karavan-app/src/main/webui/src/projects/ProjectEventBus.ts
@@ -17,12 +17,15 @@
 import {BehaviorSubject, Subject} from 'rxjs';
 import {Project} from "./ProjectModels";
 
-const currentProject = new BehaviorSubject<Project | undefined>(undefined);
+const selectedProject = new BehaviorSubject<Project | undefined>(undefined);
 const currentRunner = new BehaviorSubject<string | undefined>(undefined);
 const currentFile = new BehaviorSubject<string | undefined>(undefined);
 const showLog = new BehaviorSubject<ShowLogCommand | undefined>(undefined);
 const showTrace = new BehaviorSubject<ShowTraceCommand | undefined>(undefined);
 const refreshTrace = new BehaviorSubject<boolean>(false);
+const mode = new BehaviorSubject<"design" | "code">("design");
+const config = new BehaviorSubject<any>({});
+const showCreateProjectModal = new Subject<boolean>();
 
 export class ShowLogCommand {
     type: 'container' | 'pipeline'
@@ -49,8 +52,8 @@ export class ShowTraceCommand {
 }
 export const ProjectEventBus = {
 
-    selectProject: (project: Project) => currentProject.next(project),
-    onSelectProject: () => currentProject.asObservable(),
+    selectProject: (project: Project) => selectedProject.next(project),
+    onSelectProject: () => selectedProject.asObservable(),
 
     setCurrentRunner: (name: string | undefined) => currentRunner.next(name),
     onCurrentRunner: () => currentRunner.asObservable(),
@@ -67,4 +70,13 @@ export const ProjectEventBus = {
 
     refreshTrace: (refresh: boolean) => refreshTrace.next(refresh),
     onRefreshTrace: () => refreshTrace.asObservable(),
+
+    setMode: (m: 'design' | 'code') =>  mode.next(m),
+    onSetMode: () => mode.asObservable(),
+
+    setConfig: (c: any) =>  config.next(c),
+    onSetConfig: () => config.asObservable(),
+
+    showCreateProjectModal: (show: boolean) => 
showCreateProjectModal.next(show),
+    onShowCreateProjectModal: () => showCreateProjectModal.asObservable(),
 }
diff --git a/karavan-app/src/main/webui/src/projects/ProjectLogic.ts 
b/karavan-app/src/main/webui/src/projects/ProjectLogic.ts
new file mode 100644
index 00000000..0b924478
--- /dev/null
+++ b/karavan-app/src/main/webui/src/projects/ProjectLogic.ts
@@ -0,0 +1,95 @@
+import {KaravanApi} from "../api/KaravanApi";
+import {DeploymentStatus, Project, ProjectFile} from "./ProjectModels";
+import {TemplateApi} from "karavan-core/lib/api/TemplateApi";
+import {KubernetesAPI} from "../designer/utils/KubernetesAPI";
+import {useDeploymentStatusesStore, useFilesStore, useProjectsStore, 
useProjectStore} from "./ProjectStore";
+
+export class ProjectLogic {
+
+    public static refreshProjects() {
+        KaravanApi.getProjects((projects: Project[]) => {
+            useProjectsStore.setState({projects: projects});
+        });
+    }
+
+    public static refreshDeploymentStatuses(environment: string) {
+        KaravanApi.getDeploymentStatuses(environment, (statuses: 
DeploymentStatus[]) => {
+            useDeploymentStatusesStore.setState({statuses: statuses});
+        });
+    }
+
+    public static deleteProject(project: Project) {
+        KaravanApi.deleteProject(project, res => {
+            if (res.status === 204) {
+                // this.props.toast?.call(this, "Success", "Project deleted", 
"success");
+                ProjectLogic.refreshProjectData();
+            } else {
+                // this.props.toast?.call(this, "Error", res.statusText, 
"danger");
+            }
+        });
+    }
+
+    public static createProject(project: Project) {
+        KaravanApi.postProject(project, res => {
+            console.log(res.status)
+            if (res.status === 200 || res.status === 201) {
+                ProjectLogic.refreshProjectData();
+                // this.props.toast?.call(this, "Success", "Project created", 
"success");
+            } else {
+                // this.props.toast?.call(this, "Error", res.status + ", " + 
res.statusText, "danger");
+            }
+        });
+    }
+
+    public static createFile(file: ProjectFile) {
+        KaravanApi.postProjectFile(file, res => {
+            if (res.status === 200) {
+                // console.log(res) //TODO show notification
+                ProjectLogic.refreshProjectData();
+            } else {
+                // console.log(res) //TODO show notification
+            }
+        })
+    }
+
+    public static deleteFile(file: ProjectFile) {
+        KaravanApi.deleteProjectFile(file, res => {
+            if (res.status === 204) {
+                ProjectLogic.refreshProjectData();
+            } else {
+            }
+        });
+    }
+
+
+    public static refreshProjectData(environment?: string) {
+        const project = useProjectStore.getState().project;
+        KaravanApi.getProject(project.projectId, (project: Project) => {
+            // ProjectEventBus.selectProject(project);
+            KaravanApi.getTemplatesFiles((files: ProjectFile[]) => {
+                files.filter(f => f.name.endsWith("java"))
+                    .filter(f => f.name.startsWith(project.runtime))
+                    .forEach(f => {
+                        const name = f.name.replace(project.runtime + "-", 
'').replace(".java", '');
+                        TemplateApi.saveTemplate(name, f.code);
+                    })
+            });
+        });
+        KaravanApi.getFiles(project.projectId, (files: []) => {
+            useFilesStore.setState({files: files});
+        });
+
+        KubernetesAPI.inKubernetes = true;
+        if (environment) {
+            KaravanApi.getConfigMaps(environment, (any: []) => {
+                KubernetesAPI.setConfigMaps(any);
+            });
+            KaravanApi.getSecrets(environment, (any: []) => {
+                KubernetesAPI.setSecrets(any);
+            });
+            KaravanApi.getServices(environment, (any: []) => {
+                KubernetesAPI.setServices(any);
+            });
+        }
+    }
+}
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/projects/ProjectOperations.tsx 
b/karavan-app/src/main/webui/src/projects/ProjectOperations.tsx
deleted file mode 100644
index 38c68857..00000000
--- a/karavan-app/src/main/webui/src/projects/ProjectOperations.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import React from 'react';
-import '../designer/karavan.css';
-import {Project} from "./ProjectModels";
-import {ProjectStatus} from "./ProjectStatus";
-
-
-interface Props {
-    project: Project,
-    config: any,
-    needCommit: boolean,
-}
-
-interface State {
-    environment: string,
-}
-
-export class ProjectOperations extends React.Component<Props, State> {
-
-    public state: State = {
-        environment: this.props.config.environment
-    };
-
-    render() {
-        const {project, config,} = this.props;
-        return (
-            <div className="project-operations">
-                {/*{["dev", "test", "prod"].map(env =>*/}
-                {["dev"].map(env =>
-                    <ProjectStatus key={env} project={project} config={config} 
env={env}/>
-                )}
-            </div>
-        )
-    }
-}
diff --git a/karavan-app/src/main/webui/src/projects/ProjectPage.tsx 
b/karavan-app/src/main/webui/src/projects/ProjectPage.tsx
index 88a28833..77a0ce85 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectPage.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectPage.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, {useEffect, useState} from 'react';
 import {
     Badge,
     Breadcrumb,
@@ -21,121 +21,72 @@ import {getProjectFileType, Project, ProjectFile, 
ProjectFileTypes} from "./Proj
 import {KaravanDesigner} from "../designer/KaravanDesigner";
 import FileSaver from "file-saver";
 import Editor from "@monaco-editor/react";
-import {CreateFileModal} from "./CreateFileModal";
+import {CreateFileModal} from "./modal/CreateFileModal";
 import {PropertiesEditor} from "./PropertiesEditor";
 import {ProjectModel, ProjectProperty} from 
"karavan-core/lib/model/ProjectModel";
 import {ProjectModelApi} from "karavan-core/lib/api/ProjectModelApi";
-import {KubernetesAPI} from "../designer/utils/KubernetesAPI";
-import {UploadModal} from "./UploadModal";
-import {ProjectOperations} from "./ProjectOperations";
+import {ProjectPipelineTab} from "./tabs/ProjectPipelineTab";
 import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
-import {ProjectPageToolbar} from "./ProjectPageToolbar";
-import {ProjectFilesTable} from "./ProjectFilesTable";
-import {TemplateApi} from "karavan-core/lib/api/TemplateApi";
+import {ProjectToolbar} from "./toolbar/ProjectToolbar";
+import {ProjectFilesTab} from "./tabs/ProjectFilesTab";
 import {EventBus} from "../designer/utils/EventBus";
-import {ProjectEventBus} from "./ProjectEventBus";
-import {ProjectDevelopment} from "./ProjectDevelopment";
 import {ProjectLog} from "./ProjectLog";
+import {ProjectLogic} from "./ProjectLogic";
+import {useProjectStore} from "./ProjectStore";
+import {DeleteFileModal} from "./modal/DeleteFileModal";
+import {DashboardTab} from "./tabs/DashboardTab";
+import {TraceTab} from "./tabs/TraceTab";
 
 interface Props {
-    project: Project,
     config: any,
 }
 
-interface State {
-    file?: ProjectFile,
-    files: ProjectFile[],
-    isUploadModalOpen: boolean,
-    isDeleteModalOpen: boolean,
-    isCreateModalOpen: boolean,
-    fileToDelete?: ProjectFile,
-    mode: "design" | "code",
-    editAdvancedProperties: boolean
-    key: string
-    environments: string[],
-    environment: string,
-    tab: string | number;
-}
-
-export class ProjectPage extends React.Component<Props, State> {
-
-    public state: State = {
-        isUploadModalOpen: false,
-        isCreateModalOpen: false,
-        isDeleteModalOpen: false,
-        files: [],
-        mode: "design",
-        editAdvancedProperties: false,
-        key: '',
-        tab: "development",
-        environments: this.props.config.environments && 
Array.isArray(this.props.config.environments)
-            ? Array.from(this.props.config.environments) : [],
-        environment: this.props.config.environment,
-    };
-
-    componentDidMount() {
-        this.onRefresh();
-    }
-
-    needCommit(): boolean {
-        const {files} = this.state;
-        const {project} = this.props;
+export const ProjectPage = (props: Props) => {
+
+    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 [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();
+
+    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;
     }
 
-    onRefresh = () => {
-        if (this.props.project) {
-            KaravanApi.getProject(this.props.project.projectId, (project: 
Project) => {
-                ProjectEventBus.selectProject(project);
-                KaravanApi.getTemplatesFiles((files: ProjectFile[]) => {
-                    files.filter(f => f.name.endsWith("java"))
-                        .filter(f => f.name.startsWith(project.runtime))
-                        .forEach(f => {
-                            const name = f.name.replace(project.runtime + "-", 
'').replace(".java", '');
-                            TemplateApi.saveTemplate(name, f.code);
-                        })
-                });
-            });
-            KaravanApi.getFiles(this.props.project.projectId, (files: []) => {
-                this.setState({files: files, key: Math.random().toString()})
-            });
-
-            KubernetesAPI.inKubernetes = true;
-            if (!this.isBuildIn()) {
-                KaravanApi.getConfigMaps(this.state.environment, (any: []) => {
-                    KubernetesAPI.setConfigMaps(any);
-                });
-                KaravanApi.getSecrets(this.state.environment, (any: []) => {
-                    KubernetesAPI.setSecrets(any);
-                });
-                KaravanApi.getServices(this.state.environment, (any: []) => {
-                    KubernetesAPI.setServices(any);
-                });
-            }
-        }
+    function onRefresh () {
+        ProjectLogic.refreshProjectData(environment);
     }
 
-    isBuildIn(): boolean {
-        return ['kamelets', 
'templates'].includes(this.props.project.projectId);
-    }
-
-    isKameletsProject(): boolean {
-        return this.props.project.projectId === 'kamelets';
-    }
-
-    isTemplatesProject(): boolean {
-        return this.props.project.projectId === 'templates';
-    }
-
-    post = (file: ProjectFile) => {
+    function post (file: ProjectFile)  {
         KaravanApi.postProjectFile(file, res => {
             if (res.status === 200) {
                 const newFile = res.data;
-                this.setState((state => {
-                    const index = state.files.findIndex(f => f.name === 
newFile.name);
-                    if (index !== -1) state.files.splice(index, 1, newFile)
-                    else state.files.push(newFile);
-                    return state
+                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
@@ -143,21 +94,19 @@ export class ProjectPage extends React.Component<Props, 
State> {
         })
     }
 
-    copyToClipboard = (data: string) => {
+    function copyToClipboard (data: string) {
         navigator.clipboard.writeText(data);
     }
 
-    save = (name: string, code: string) => {
-        const file = this.state.file;
+    function save (name: string, code: string) {
         if (file) {
             file.code = code;
-            this.setState({file: file});
-            this.post(file);
+            setFile(file);
+            post(file);
         }
     }
 
-    download = () => {
-        const file = this.state.file;
+    function download () {
         if (file) {
             const type = file.name.endsWith("yaml") ? 
"application/yaml;charset=utf-8" : undefined;
             const f = new File([file.code], file.name, {type: type});
@@ -165,46 +114,42 @@ export class ProjectPage extends React.Component<Props, 
State> {
         }
     }
 
-    downloadImage = () => {
+    function downloadImage () {
         EventBus.sendCommand("downloadImage");
     }
 
-    addProperty() {
-        const file = this.state.file;
+    function addProperty() {
         if (file) {
             const project = file ? 
ProjectModelApi.propertiesToProject(file?.code) : ProjectModel.createNew();
             const props = project.properties;
             props.push(ProjectProperty.createNew("", ""))
-            this.save(file.name, ProjectModelApi.propertiesToString(props));
-            this.setState({key: Math.random().toString()});
+            save(file.name, ProjectModelApi.propertiesToString(props));
+            setKey(Math.random().toString());
         }
     }
 
-    tools = () => {
-        return <ProjectPageToolbar key={this.state.key}
-                                   project={this.props.project}
-                                   file={this.state.file}
-                                   mode={this.state.mode}
-                                   isTemplates={this.isTemplatesProject()}
-                                   isKamelets={this.isKameletsProject()}
-                                   config={this.props.config}
-                                   addProperty={() => this.addProperty()}
-                                   download={() => this.download()}
-                                   downloadImage={() => this.downloadImage()}
-                                   
editAdvancedProperties={this.state.editAdvancedProperties}
-                                   setEditAdvancedProperties={checked => 
this.setState({editAdvancedProperties: checked})}
-                                   setMode={mode => this.setState({mode: 
mode})}
-                                   setCreateModalOpen={() => 
this.setState({isCreateModalOpen: true})}
-                                   setUploadModalOpen={() => 
this.setState({isUploadModalOpen: true})}
-                                   needCommit={this.needCommit()}
-                                   onRefresh={this.onRefresh}
+    function tools () {
+        return <ProjectToolbar key={key}
+                               project={project}
+                               file={file}
+                               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()}
+                               onRefresh={onRefresh}
         />
     }
 
 
-    title = () => {
-        const {project} = this.props;
-        const file = this.state.file;
+    function title ()  {
         const isFile = file !== undefined;
         const isLog = file !== undefined && file.name.endsWith("log");
         const filename = file ? file.name.substring(0, 
file.name.lastIndexOf('.')) : "";
@@ -213,8 +158,8 @@ export class ProjectPage extends React.Component<Props, 
State> {
                 <FlexItem>
                     <Breadcrumb>
                         <BreadcrumbItem to="#" onClick={event => {
-                            this.setState({file: undefined})
-                            this.onRefresh();
+                            setFile(undefined)
+                            onRefresh();
                         }}>
                             <div 
className={"project-breadcrumb"}>{project?.name + " (" + project?.projectId + 
")"}</div>
                         </BreadcrumbItem>
@@ -248,37 +193,20 @@ export class ProjectPage extends React.Component<Props, 
State> {
         </div>)
     };
 
-    closeModal = (isPushing: boolean = false) => {
-        this.setState({
-            isUploadModalOpen: false,
-            isCreateModalOpen: false,
-        });
-        this.onRefresh();
-    }
-
-    select = (file: ProjectFile) => {
-        this.setState({file: file});
+    function closeModal (isPushing: boolean = false) {
+        setIsUploadModalOpen(false);
     }
 
-    openDeleteConfirmation = (file: ProjectFile) => {
-        this.setState({isDeleteModalOpen: true, fileToDelete: file})
+    function select (file: ProjectFile) {
+        setFile(file);
     }
 
-    delete = () => {
-        if (this.state.fileToDelete) {
-            KaravanApi.deleteProjectFile(this.state.fileToDelete, res => {
-                if (res.status === 204) {
-                    this.onRefresh();
-                } else {
-                }
-            });
-            this.setState({isDeleteModalOpen: false, fileToDelete: undefined})
-        }
+    function openDeleteConfirmation (file: ProjectFile) {
+        setIsDeleteModalOpen(true)
+        setFileToDelete(file);
     }
 
-    getDesigner = () => {
-        const {file, files} = this.state;
-        const {project} = this.props;
+    function getDesigner () {
         return (
             file !== undefined &&
             <KaravanDesigner
@@ -286,8 +214,8 @@ export class ProjectPage extends React.Component<Props, 
State> {
                 key={"key"}
                 filename={file.name}
                 yaml={file.code}
-                onSave={(name, yaml) => this.save(name, yaml)}
-                onSaveCustomCode={(name, code) => this.post(new 
ProjectFile(name + ".java", project.projectId, code, Date.now()))}
+                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))
                 }}
@@ -295,8 +223,7 @@ export class ProjectPage extends React.Component<Props, 
State> {
         )
     }
 
-    getEditor = () => {
-        const file = this.state.file;
+    function getEditor () {
         const language = file?.name.split('.').pop();
         return (
             file !== undefined &&
@@ -308,38 +235,37 @@ export class ProjectPage extends React.Component<Props, 
State> {
                 className={'code-editor'}
                 onChange={(value, ev) => {
                     if (value) {
-                        this.save(file?.name, value)
+                        save(file?.name, value)
                     }
                 }}
             />
         )
     }
 
-    deleteEntity = (type: 'pod' | 'deployment' | 'pipelinerun', name: string, 
environment: string) => {
+    function deleteEntity  (type: 'pod' | 'deployment' | 'pipelinerun', name: 
string, environment: string)  {
         switch (type) {
             case "deployment":
                 KaravanApi.deleteDeployment(environment, name, (res: any) => {
                     if (Array.isArray(res) && Array.from(res).length > 0)
-                        this.onRefresh();
+                        onRefresh();
                 });
                 break;
             case "pod":
                 KaravanApi.deletePod(environment, name, (res: any) => {
                     if (Array.isArray(res) && Array.from(res).length > 0)
-                        this.onRefresh();
+                        onRefresh();
                 });
                 break;
             case "pipelinerun":
                 KaravanApi.stopPipelineRun(environment, name, (res: any) => {
                     if (Array.isArray(res) && Array.from(res).length > 0)
-                        this.onRefresh();
+                        onRefresh();
                 });
                 break;
         }
     }
 
-    getLogView = () => {
-        const file = this.state.file;
+    function getLogView ()  {
         return (
             <div>
                 {file !== undefined && file.code.length !== 0 &&
@@ -364,79 +290,71 @@ export class ProjectPage extends React.Component<Props, 
State> {
         )
     }
 
-    getPropertiesEditor = () => {
-        const file = this.state.file;
+    function getPropertiesEditor  ()  {
         return (
             file !== undefined &&
-            <PropertiesEditor key={this.state.key}
-                              editAdvanced={this.state.editAdvancedProperties}
+            <PropertiesEditor key={key}
+                              editAdvanced={editAdvancedProperties}
                               file={file}
-                              onSave={(name, code) => this.save(name, code)}
+                              onSave={(name, code) => save(name, code)}
             />
         )
     }
 
-    getProjectPanel() {
-        const isBuildIn = this.isBuildIn();
+    function getProjectPanel() {
         return (
             <Flex direction={{default: "column"}} spaceItems={{default: 
"spaceItemsNone"}}>
-                {!isBuildIn && this.getProjectPanelTabs()}
-                {this.getProjectPanelDetails()}
+                {getProjectPanelTabs()}
+                {getProjectPanelDetails()}
             </Flex>
         )
     }
 
-    getProjectPanelTabs() {
-        const {tab} = this.state;
+    function getProjectPanelTabs() {
         return (
             <FlexItem className="project-tabs">
-                <Tabs activeKey={tab} onSelect={(event, tabIndex) => 
this.setState({tab: tabIndex})}>
-                    <Tab eventKey="development" title="Development"/>
-                    <Tab eventKey="operations" title="Operations"/>
+                <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);
+    }
+
+    function isKameletsProject(): boolean {
+        return project.projectId === 'kamelets';
+    }
+
+    function isTemplatesProject(): boolean {
+        return project.projectId === 'templates';
+    }
 
-    getProjectPanelDetails() {
-        const {tab, files} = this.state;
-        const {project} = this.props;
-        const isBuildIn = this.isBuildIn();
+    function getProjectPanelDetails() {
+        const buildIn = isBuildIn();
         return (
             <FlexItem>
-                {isBuildIn &&
-                    <PageSection className="project-tabs" padding={{default: 
"padding"}}>
-                        {tab === 'development' && <ProjectFilesTable 
files={files}
-                                                                     
onOpenDeleteConfirmation={this.openDeleteConfirmation}
-                                                                     
onSelect={this.select}/>}
-                    </PageSection>
-                }
-                {!isBuildIn &&
-                    <PageSection className="project-bottom" padding={{default: 
"padding"}}>
-                        {tab === 'development' && project && 
<ProjectDevelopment project={project}
-                                                                               
  config={this.props.config}/>}
-                        {tab === 'development' && <ProjectFilesTable 
files={files}
-                                                                     
onOpenDeleteConfirmation={this.openDeleteConfirmation}
-                                                                     
onSelect={this.select}/>}
-                        {tab === 'operations' && <ProjectOperations 
project={project}
-                                                                    
needCommit={this.needCommit()}
-                                                                    
config={this.props.config}/>}
-                    </PageSection>
+                {buildIn && tab === 'files' && <ProjectFilesTab/>}
+                {!buildIn &&
+                    <>
+                        {tab === 'files' && <ProjectFilesTab/>}
+                        {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>
         )
     }
 
-    getProjectPanelLogs() {
-        const {tab, files} = this.state;
-        const {project} = this.props;
-        const isBuildIn = this.isBuildIn();
-        return (<ProjectLog/>)
-    }
-
-    getFilePanel() {
-        const {file, mode} = this.state;
+    function getFilePanel() {
         const isYaml = file !== undefined && file.name.endsWith("yaml");
         const isIntegration = isYaml && file?.code && 
CamelDefinitionYaml.yamlIsIntegration(file.code);
         const isProperties = file !== undefined && 
file.name.endsWith("properties");
@@ -446,48 +364,26 @@ export class ProjectPage extends React.Component<Props, 
State> {
         const showEditor = isCode || (isYaml && !isIntegration) || (isYaml && 
mode === 'code');
         return (
             <>
-                {showDesigner && this.getDesigner()}
-                {showEditor && this.getEditor()}
-                {isLog && this.getLogView()}
-                {isProperties && this.getPropertiesEditor()}
+                {showDesigner && getDesigner()}
+                {showEditor && getEditor()}
+                {isLog && getLogView()}
+                {isProperties && getPropertiesEditor()}
             </>
         )
     }
-
-    render() {
-        const {file, isDeleteModalOpen, fileToDelete, isUploadModalOpen, 
isCreateModalOpen, key} = this.state;
-        const {project} = this.props;
-        const types = this.isBuildIn()
-            ? (this.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={this.title()} tools={this.tools()}/>
-                </PageSection>
-                {file === undefined && this.getProjectPanel()}
-                {file !== undefined && this.getFilePanel()}
-                {this.getProjectPanelLogs()}
-
-                <CreateFileModal project={project}
-                                 isOpen={isCreateModalOpen}
-                                 types={types}
-                                 onClose={this.closeModal}/>
-                <Modal
-                    title="Confirmation"
-                    variant={ModalVariant.small}
-                    isOpen={isDeleteModalOpen}
-                    onClose={() => this.setState({isDeleteModalOpen: false})}
-                    actions={[
-                        <Button key="confirm" variant="primary" onClick={e => 
this.delete()}>Delete</Button>,
-                        <Button key="cancel" variant="link"
-                                onClick={e => 
this.setState({isDeleteModalOpen: false})}>Cancel</Button>
-                    ]}
-                    onEscapePress={e => this.setState({isDeleteModalOpen: 
false})}>
-                    <div>{"Are you sure you want to delete the file " + 
fileToDelete?.name + "?"}</div>
-                </Modal>
-                <UploadModal projectId={project.projectId} 
isOpen={isUploadModalOpen} onClose={this.closeModal}/>
+    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()} file={file}/>
             </PageSection>
-        )
-    }
+            {file === undefined && getProjectPanel()}
+            {/*{file !== undefined && getFilePanel()}*/}
+            <ProjectLog/>
+            <CreateFileModal types={types}/>
+            <DeleteFileModal />
+        </PageSection>
+    )
 }
diff --git a/karavan-app/src/main/webui/src/projects/ProjectStore.ts 
b/karavan-app/src/main/webui/src/projects/ProjectStore.ts
new file mode 100644
index 00000000..ca385656
--- /dev/null
+++ b/karavan-app/src/main/webui/src/projects/ProjectStore.ts
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {create} from 'zustand'
+import {DeploymentStatus, Project, ProjectFile} from "./ProjectModels";
+
+const projects: Project[] = [];
+var project: Project = new Project();
+const files: ProjectFile[] = [];
+var file: ProjectFile | undefined = undefined;
+
+interface ProjectsState {
+    projects: Project[];
+    setProjects: (projects: Project[]) => void;
+}
+
+export const useProjectsStore = create<ProjectsState>((set) => ({
+    projects: [],
+    setProjects: (ps: Project[]) => {
+        set((state: ProjectsState) => ({
+            projects: ps,
+        }), true);
+    },
+}))
+
+interface ProjectState {
+    project: Project;
+    operation: "create" | "select" | "delete" | "none" | "copy";
+    setProject: (project: Project, operation:  "create" | "select" | "delete"| 
"none" | "copy") => void;
+}
+
+export const useProjectStore = create<ProjectState>((set) => ({
+    project: new Project(),
+    operation: "none",
+    setProject: (p: Project, o: "create" | "select" | "delete"| "none" | 
"copy") => {
+        set((state: ProjectState) => ({
+            project: p,
+            operation: o,
+        }), true);
+    },
+}))
+
+interface FilesState {
+    files: ProjectFile[];
+    setFiles: (files: ProjectFile[]) => void;
+}
+
+export const useFilesStore = create<FilesState>((set) => ({
+    files: [],
+    setFiles: (files: ProjectFile[]) => {
+        set((state: FilesState) => ({
+            files: files,
+        }), true);
+    },
+}))
+
+interface FileState {
+    file?: ProjectFile;
+    operation: "create" | "select" | "delete" | "none" | "copy";
+    setFile: (file: ProjectFile, operation:  "create" | "select" | "delete"| 
"none" | "copy") => void;
+}
+
+export const useFileStore = create<FileState>((set) => ({
+    file: undefined,
+    operation: "none",
+    setFile: (file: ProjectFile, operation:  "create" | "select" | "delete"| 
"none" | "copy") => {
+        set((state: FileState) => ({
+            file: file,
+            operation: operation,
+        }), true);
+    },
+}))
+
+interface DeploymentStatusesState {
+    statuses: DeploymentStatus[];
+    setDeploymentStatuses: (statuses: DeploymentStatus[]) => void;
+}
+
+export const useDeploymentStatusesStore = 
create<DeploymentStatusesState>((set) => ({
+    statuses: [],
+    setDeploymentStatuses: (statuses: DeploymentStatus[]) => {
+        set((state: DeploymentStatusesState) => ({
+            statuses: statuses,
+        }), true);
+    },
+}))
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx 
b/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
index 5eb4b231..ff1423cc 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
@@ -1,6 +1,5 @@
-import React from 'react';
+import React, {useEffect, useState} from 'react';
 import {
-    Alert,
     Toolbar,
     ToolbarContent,
     ToolbarItem,
@@ -9,270 +8,85 @@ import {
     TextContent,
     Text,
     Button,
-    Modal,
-    FormGroup,
-    ModalVariant,
-    Form,
     Bullseye,
     EmptyState,
     EmptyStateVariant,
     EmptyStateIcon,
-    Title,
-    Radio, Spinner
+    Title, Spinner
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {MainToolbar} from "../MainToolbar";
 import RefreshIcon from '@patternfly/react-icons/dist/esm/icons/sync-alt-icon';
 import PlusIcon from '@patternfly/react-icons/dist/esm/icons/plus-icon';
-import {DeploymentStatus, Project, PipelineStatus} from "./ProjectModels";
+import {Project} from "./ProjectModels";
 import {TableComposable, Tbody, Td, Th, Thead, Tr} from 
"@patternfly/react-table";
 import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
-import {CamelUi} from "../designer/utils/CamelUi";
-import {KaravanApi} from "../api/KaravanApi";
-import {QuarkusIcon, SpringIcon} from "../designer/utils/KaravanIcons";
-import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {ProjectsTableRow} from "./ProjectsTableRow";
+import {useProjectStore} from "./ProjectStore";
+import {ProjectLogic} from "./ProjectLogic";
+import {useProjectsStore} from "./ProjectStore";
+import {DeleteProjectModal} from "./modal/DeleteProjectModal";
+import {CreateProjectModal} from "./modal/CreateProjectModal";
+
 
 interface Props {
     config: any,
     toast: (title: string, text: string, variant: 'success' | 'danger' | 
'warning' | 'info' | 'default') => void
 }
 
-interface State {
-    projects: Project[],
-    deploymentStatuses: DeploymentStatus[],
-    isCreateModalOpen: boolean,
-    isDeleteModalOpen: boolean,
-    isProjectDeploymentModalOpen: boolean,
-    isCopy: boolean,
-    loading: boolean,
-    projectToCopy?: Project,
-    projectToDelete?: Project,
-    filter: string,
-    name: string,
-    description: string,
-    projectId: string,
-    runtime: string,
-}
-
-export class ProjectsPage extends React.Component<Props, State> {
-
-    public state: State = {
-        projects: [],
-        deploymentStatuses: [],
-        isCreateModalOpen: false,
-        isDeleteModalOpen: false,
-        isProjectDeploymentModalOpen: false,
-        isCopy: false,
-        loading: true,
-        filter: '',
-        name: '',
-        description: '',
-        projectId: '',
-        runtime: this.props.config.runtime
-    };
-    interval: any;
-
-    componentDidMount() {
-        this.interval = setInterval(() => this.onGetProjects(), 1300);
+export const ProjectsPage = (props: Props) => {
+
+    const {projects, setProjects} = useProjectsStore();
+    const {operation} = useProjectStore();
+    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)) 
ProjectLogic.refreshProjects();
+        }, 1300);
+        return () => {
+            clearInterval(interval)
+        };
+    });
+
+    function getTools() {
+        return <Toolbar id="toolbar-group-types">
+            <ToolbarContent>
+                <ToolbarItem>
+                    <Button variant="link" icon={<RefreshIcon/>} onClick={e =>
+                        ProjectLogic.refreshProjects()}/>
+                </ToolbarItem>
+                <ToolbarItem>
+                    <TextInput className="text-field" type="search" 
id="search" name="search"
+                               autoComplete="off" placeholder="Search by name"
+                               value={filter}
+                               onChange={e => setFilter(e)}/>
+                </ToolbarItem>
+                <ToolbarItem>
+                    <Button icon={<PlusIcon/>}
+                            onClick={e =>
+                                useProjectStore.setState({operation:"create", 
project: new Project()})}
+                    >Create</Button>
+                </ToolbarItem>
+            </ToolbarContent>
+        </Toolbar>
     }
 
-    componentWillUnmount() {
-        clearInterval(this.interval);
+    function title() {
+        return <TextContent>
+            <Text component="h2">Projects</Text>
+        </TextContent>
     }
 
-    onProjectDelete = (project: Project) => {
-        KaravanApi.getProjectPipelineStatus(project.projectId, 
this.props.config.environment, (status?: PipelineStatus) => {
-            if (status?.result === "Running" || status?.result === "Started") {
-                this.setState({ isProjectDeploymentModalOpen: true, 
projectToDelete: project })
-            } else {
-                KaravanApi.getProjectDeploymentStatus(project.projectId, 
this.props.config.environment, (status?: DeploymentStatus) => {
-                    if (status === undefined) {
-                        this.setState({ isDeleteModalOpen: true, 
projectToDelete: project })
-                    }
-                    else {
-                        this.setState({ isProjectDeploymentModalOpen: true, 
projectToDelete: project })
-                    }
-                });
-            }
-        });
-    };
-
-
-    deleteProject = () => {
-        if (this.state.projectToDelete)
-            KaravanApi.deleteProject(this.state.projectToDelete, res => {
-                if (res.status === 204) {
-                    this.props.toast?.call(this, "Success", "Project deleted", 
"success");
-                    this.onGetProjects();
-                } else {
-                    this.props.toast?.call(this, "Error", res.statusText, 
"danger");
-                }
-            });
-        this.setState({isDeleteModalOpen: false})
-    }
-
-    onProjectCreate = (project: Project) => {
-        KaravanApi.postProject(project, res => {
-            console.log(res.status)
-            if (res.status === 200 || res.status === 201) {
-                this.props.toast?.call(this, "Success", "Project created", 
"success");
-            } else {
-                this.props.toast?.call(this, "Error", res.status + ", " + 
res.statusText, "danger");
-            }
-        });
-    };
-
-    onGetProjects = () => {
-        this.setState({loading: true});
-        KaravanApi.getProjects((projects: Project[]) => {
-            this.setState({projects: projects, loading: false})
-        });
-        KaravanApi.getDeploymentStatuses(this.props.config.environment, 
(statuses: DeploymentStatus[]) => {
-            this.setState({deploymentStatuses: statuses});
-        });
-    }
-
-    tools = () => (<Toolbar id="toolbar-group-types">
-        <ToolbarContent>
-            <ToolbarItem>
-                <Button variant="link" icon={<RefreshIcon/>} onClick={e => 
this.onGetProjects()}/>
-            </ToolbarItem>
-            <ToolbarItem>
-                <TextInput className="text-field" type="search" id="search" 
name="search"
-                           autoComplete="off" placeholder="Search by name"
-                           value={this.state.filter}
-                           onChange={e => this.setState({filter: e})}/>
-            </ToolbarItem>
-            <ToolbarItem>
-                <Button icon={<PlusIcon/>} onClick={e => 
this.setState({isCreateModalOpen: true, isCopy: false})}>Create</Button>
-            </ToolbarItem>
-        </ToolbarContent>
-    </Toolbar>);
-
-    title = () => (<TextContent>
-        <Text component="h2">Projects</Text>
-    </TextContent>);
-
-    closeModal = () => {
-        this.setState({isCreateModalOpen: false, isCopy: false, name: 
this.props.config.groupId, description: '', projectId: '', runtime: 
this.props.config.runtime});
-        this.onGetProjects();
-    }
-
-    saveAndCloseCreateModal = () => {
-        const {name, description, projectId, runtime} = this.state;
-        const p = new Project(projectId, name, description, runtime, '');
-        this.onProjectCreate(p);
-        this.setState({isCreateModalOpen: false, isCopy: false, name: 
this.props.config.groupId, description: '', projectId: ''});
-    }
-
-    onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
-        if (event.key === 'Enter' && this.state.name !== undefined && 
this.state.description !== undefined && this.state.projectId !== undefined) {
-            this.saveAndCloseCreateModal();
-        }
-    }
-
-    createModalForm() {
-        const {isCopy, projectToCopy, projectId, name, isCreateModalOpen, 
description, runtime} = this.state;
-        const {runtimes} = this.props.config;
-        const isReady = projectId && name && description && !['templates', 
'kamelets'].includes(projectId);
-        return (
-            <Modal
-                title={!isCopy ? "Create new project" : "Copy project from " + 
projectToCopy?.projectId}
-                variant={ModalVariant.small}
-                isOpen={isCreateModalOpen}
-                onClose={this.closeModal}
-                onKeyDown={this.onKeyDown}
-                actions={[
-                    <Button key="confirm" variant="primary" 
isDisabled={!isReady} onClick={this.saveAndCloseCreateModal}>Save</Button>,
-                    <Button key="cancel" variant="secondary" 
onClick={this.closeModal}>Cancel</Button>
-                ]}
-                className="new-project"
-            >
-                <Form isHorizontal={true} autoComplete="off">
-                    <FormGroup label="Name" fieldId="name" isRequired>
-                        <TextInput className="text-field" type="text" 
id="name" name="name"
-                                   value={name}
-                                   onChange={e => this.setState({name: e})}/>
-                    </FormGroup>
-                    <FormGroup label="Description" fieldId="description" 
isRequired>
-                        <TextInput className="text-field" type="text" 
id="description" name="description"
-                                   value={description}
-                                   onChange={e => this.setState({description: 
e})}/>
-                    </FormGroup>
-                    <FormGroup label="Project ID" fieldId="projectId" 
isRequired helperText="Unique project name">
-                        <TextInput className="text-field" type="text" 
id="projectId" name="projectId"
-                                   value={projectId}
-                                   onFocus={e => this.setState({projectId: 
projectId === '' ? CamelUi.nameFromTitle(name) : projectId})}
-                                   onChange={e => this.setState({projectId: 
CamelUi.nameFromTitle(e)})}/>
-                    </FormGroup>
-                    <FormGroup label="Runtime" fieldId="runtime" isRequired>
-                        {runtimes?.map((r: string) => (
-                            <Radio key={r} id={r} name={r} className="radio"
-                                   isChecked={r === runtime}
-                                   onChange={checked => {
-                                       if (checked) this.setState({runtime: r})
-                                   }}
-                                   body={
-                                       <div className="runtime-radio">
-                                           {r === 'quarkus' ? QuarkusIcon() : 
SpringIcon()}
-                                           <div 
className="runtime-label">{CamelUtil.capitalizeName(r)}</div>
-                                       </div>}
-                            />
-                        ))}
-                    </FormGroup>
-                </Form>
-            </Modal>
-        )
-    }
-
-    deleteModalForm() {
-        return (
-            <div>
-                {(this.state.isDeleteModalOpen === true) && <Modal
-                    title="Confirmation"
-                    variant={ModalVariant.small}
-                    isOpen={this.state.isDeleteModalOpen}
-                    onClose={() => this.setState({ isDeleteModalOpen: false })}
-                    actions={[
-                        <Button key="confirm" variant="primary" onClick={e => 
this.deleteProject()}>Delete</Button>,
-                        <Button key="cancel" variant="link"
-                            onClick={e => this.setState({ isDeleteModalOpen: 
false })}>Cancel</Button>
-                    ]}
-                    onEscapePress={e => this.setState({ isDeleteModalOpen: 
false })}>
-                    <div>{"Are you sure you want to delete the project " + 
this.state.projectToDelete?.projectId + "?"}</div>
-                </Modal>
-                }
-                {(this.state.isProjectDeploymentModalOpen === true) && <Modal
-                    variant={ModalVariant.small}
-                    isOpen={this.state.isProjectDeploymentModalOpen}
-                    onClose={() => this.setState({ 
isProjectDeploymentModalOpen: false })}
-                    onEscapePress={e => this.setState({ 
isProjectDeploymentModalOpen: false })}>
-                    <div>
-                        <Alert key={this.state.projectToDelete?.projectId} 
className="main-alert" variant="warning"
-                            title={"Deployment is Running!!"} isInline={true} 
isPlain={true}>
-                            {"Delete the deployment (" + 
this.state.projectToDelete?.projectId + ")" + " first."}
-                        </Alert>
-                    </div>
-                </Modal>
-
-                }
-            </div>
-        )
-    }
-
-    getEnvironments(): string [] {
-        return this.props.config.environments && 
Array.isArray(this.props.config.environments) ? 
Array.from(this.props.config.environments) : [];
-    }
-
-
-    getEmptyState() {
-        const {loading} = this.state;
+    function getEmptyState() {
         return (
             <Tr>
                 <Td colSpan={8}>
                     <Bullseye>
-                        {loading && <Spinner className="progress-stepper" 
isSVG diameter="80px" aria-label="Loading..."/>}
+                        {loading &&
+                            <Spinner className="progress-stepper" isSVG 
diameter="80px" aria-label="Loading..."/>}
                         {!loading &&
                             <EmptyState variant={EmptyStateVariant.small}>
                                 <EmptyStateIcon icon={SearchIcon}/>
@@ -287,9 +101,7 @@ export class ProjectsPage extends React.Component<Props, 
State> {
         )
     }
 
-
-    getProjectsTable() {
-        const {projects, filter} = this.state;
+    function getProjectsTable() {
         const projs = projects.filter(p => 
p.name.toLowerCase().includes(filter) || 
p.description.toLowerCase().includes(filter));
         return (
             <TableComposable aria-label="Projects" variant={"compact"}>
@@ -308,30 +120,25 @@ export class ProjectsPage extends React.Component<Props, 
State> {
                     {projs.map(project => (
                         <ProjectsTableRow
                             key={project.projectId}
-                            config={this.props.config}
-                            onProjectDelete={this.onProjectDelete}
-                            onProjectCopy={project1 => 
this.setState({isCreateModalOpen: true, isCopy: true, projectToCopy: project1})}
-                            project={project}
-                            
deploymentStatuses={this.state.deploymentStatuses}/>
+                            config={props.config}
+                            project={project}/>
                     ))}
-                    {projs.length === 0 && this.getEmptyState()}
+                    {projs.length === 0 && getEmptyState()}
                 </Tbody>
             </TableComposable>
         )
     }
 
-    render() {
-        return (
-            <PageSection className="kamelet-section projects-page" 
padding={{default: 'noPadding'}}>
-                <PageSection className="tools-section" padding={{default: 
'noPadding'}}>
-                    <MainToolbar title={this.title()} tools={this.tools()}/>
-                </PageSection>
-                <PageSection isFilled className="kamelets-page">
-                    {this.getProjectsTable()}
-                </PageSection>
-                {this.createModalForm()}
-                {this.deleteModalForm()}
+    return (
+        <PageSection className="kamelet-section projects-page" 
padding={{default: 'noPadding'}}>
+            <PageSection className="tools-section" padding={{default: 
'noPadding'}}>
+                <MainToolbar title={title()} tools={getTools()}/>
             </PageSection>
-        )
-    }
+            <PageSection isFilled className="kamelets-page">
+                {getProjectsTable()}
+            </PageSection>
+            {["create", "copy"].includes(operation) && <CreateProjectModal 
config={props.config}/>}
+            {["delete"].includes(operation) && <DeleteProjectModal/>}
+        </PageSection>
+    )
 }
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx 
b/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
index 1eea44be..76d264a3 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
@@ -39,107 +39,87 @@ import {KaravanApi} from "../api/KaravanApi";
 import {QuarkusIcon, SpringIcon} from "../designer/utils/KaravanIcons";
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {ProjectEventBus} from "./ProjectEventBus";
+import {useDeploymentStatusesStore, useProjectsStore, useProjectStore} from 
"./ProjectStore";
 
 interface Props {
     config: any,
-    onProjectDelete: (project: Project) => void
-    onProjectCopy: (project: Project) => void
     project: Project
-    deploymentStatuses: DeploymentStatus[],
 }
 
-interface State {
+export const ProjectsTableRow = (props: Props) => {
 
-}
-
-export class ProjectsTableRow extends React.Component<Props, State> {
-
-    public state: State = {
-    };
+    const {statuses} = useDeploymentStatusesStore()
 
-    getEnvironments(): string [] {
-        return this.props.config.environments && 
Array.isArray(this.props.config.environments) ? 
Array.from(this.props.config.environments) : [];
+    function getEnvironments(): string [] {
+        return props.config.environments && 
Array.isArray(props.config.environments) ? 
Array.from(props.config.environments) : [];
     }
 
-    getDeploymentByEnvironments(name: string): [string, DeploymentStatus | 
undefined] [] {
-        const deps = this.props.deploymentStatuses;
-        return this.getEnvironments().map(e => {
+    function getDeploymentByEnvironments(name: string): [string, 
DeploymentStatus | undefined] [] {
+        return getEnvironments().map(e => {
             const env: string = e as string;
-            const dep = deps.find(d => d.name === name && d.env === env);
+            const dep = statuses.find(d => d.name === name && d.env === env);
             return [env, dep];
         });
     }
 
-    render() {
-        const {project, onProjectDelete, onProjectCopy} = this.props;
-        const isBuildIn = ['kamelets', 
'templates'].includes(project.projectId);
-        const badge = isBuildIn ? project.projectId.toUpperCase().charAt(0) : 
project.runtime.substring(0, 1).toUpperCase();
-        return (
-            <Tr key={project.projectId}>
-                <Td modifier={"fitContent"}>
-                    <Tooltip content={project.runtime} position={"left"}>
-                        <Badge isRead={isBuildIn} 
className="runtime-badge">{badge}</Badge>
-                    </Tooltip>
-                </Td>
-                <Td>
-                    <Button style={{padding: '6px'}} variant={"link"} 
onClick={e => ProjectEventBus.selectProject(project)}>
-                        {project.projectId}
-                    </Button>
-                </Td>
-                <Td>{project.name}</Td>
-                <Td>{project.description}</Td>
-                <Td isActionCell>
-                    <Tooltip content={project.lastCommit} position={"bottom"}>
-                        <Badge>{project.lastCommit?.substr(0, 7)}</Badge>
-                    </Tooltip>
-                </Td>
-                <Td noPadding style={{width: "180px"}}>
-                    {!isBuildIn &&
-                        <Flex direction={{default: "row"}}>
-                            
{this.getDeploymentByEnvironments(project.projectId).map(value => (
-                                <FlexItem className="badge-flex-item" 
key={value[0]}>
-                                    <Badge className="badge" 
isRead={!value[1]}>{value[0]}</Badge>
-                                </FlexItem>
-                            ))}
-                        </Flex>
-                    }
-                </Td>
-                <Td className="project-action-buttons">
-                    {!isBuildIn &&
-                        <Flex direction={{default: "row"}} 
justifyContent={{default: "justifyContentFlexEnd"}} spaceItems={{ default: 
'spaceItemsNone' }}>
-                            <FlexItem>
-                                <Tooltip content={"Copy project"} 
position={"bottom"}>
-                                    <Button variant={"plain"} 
icon={<CopyIcon/>}
-                                            onClick={e => 
onProjectCopy.call(this, project)}></Button>
-                                </Tooltip>
+    const project = props.project;
+    const isBuildIn = ['kamelets', 'templates'].includes(project.projectId);
+    const badge = isBuildIn ? project.projectId.toUpperCase().charAt(0) : 
project.runtime.substring(0, 1).toUpperCase();
+    return (
+        <Tr key={project.projectId}>
+            <Td modifier={"fitContent"}>
+                <Tooltip content={project.runtime} position={"left"}>
+                    <Badge isRead={isBuildIn} 
className="runtime-badge">{badge}</Badge>
+                </Tooltip>
+            </Td>
+            <Td>
+                <Button style={{padding: '6px'}} variant={"link"} onClick={e 
=> {
+                    useProjectStore.setState({project: project, operation: 
"select"});
+                    ProjectEventBus.selectProject(project);
+                }}>
+                    {project.projectId}
+                </Button>
+            </Td>
+            <Td>{project.name}</Td>
+            <Td>{project.description}</Td>
+            <Td isActionCell>
+                <Tooltip content={project.lastCommit} position={"bottom"}>
+                    <Badge>{project.lastCommit?.substr(0, 7)}</Badge>
+                </Tooltip>
+            </Td>
+            <Td noPadding style={{width: "180px"}}>
+                {!isBuildIn &&
+                    <Flex direction={{default: "row"}}>
+                        
{getDeploymentByEnvironments(project.projectId).map(value => (
+                            <FlexItem className="badge-flex-item" 
key={value[0]}>
+                                <Badge className="badge" 
isRead={!value[1]}>{value[0]}</Badge>
                             </FlexItem>
-                            <FlexItem>
-                                <Tooltip content={"Delete project"} 
position={"bottom"}>
-                                    <Button variant={"plain"} 
icon={<DeleteIcon/>} onClick={e => onProjectDelete.call(this, 
project)}></Button>
-                                </Tooltip>
-                            </FlexItem>
-                        </Flex>
-
-                        // <OverflowMenu breakpoint="md">
-                        //     <OverflowMenuContent >
-                        //         <OverflowMenuGroup groupType="button">
-                        //             <OverflowMenuItem>
-                        //                 <Tooltip content={"Copy project"} 
position={"bottom"}>
-                        //                     <Button variant={"plain"} 
icon={<CopyIcon/>}
-                        //                             onClick={e => 
onProjectCopy.call(this, project)}></Button>
-                        //                 </Tooltip>
-                        //             </OverflowMenuItem>
-                        //             <OverflowMenuItem>
-                        //                 <Tooltip content={"Delete project"} 
position={"bottom"}>
-                        //                     <Button variant={"plain"} 
icon={<DeleteIcon/>} onClick={e => onProjectDelete.call(this, 
project)}></Button>
-                        //                 </Tooltip>
-                        //             </OverflowMenuItem>
-                        //         </OverflowMenuGroup>
-                        //     </OverflowMenuContent>
-                        // </OverflowMenu>
-                    }
-                </Td>
-            </Tr>
-        )
-    }
+                        ))}
+                    </Flex>
+                }
+            </Td>
+            <Td className="project-action-buttons">
+                {!isBuildIn &&
+                    <Flex direction={{default: "row"}} 
justifyContent={{default: "justifyContentFlexEnd"}}
+                          spaceItems={{default: 'spaceItemsNone'}}>
+                        <FlexItem>
+                            <Tooltip content={"Copy project"} 
position={"bottom"}>
+                                <Button variant={"plain"} icon={<CopyIcon/>}
+                                        onClick={e => {
+                                            useProjectStore.setState({project: 
project, operation: "copy"});
+                                        }}></Button>
+                            </Tooltip>
+                        </FlexItem>
+                        <FlexItem>
+                            <Tooltip content={"Delete project"} 
position={"bottom"}>
+                                <Button variant={"plain"} icon={<DeleteIcon/>} 
onClick={e => {
+                                    useProjectStore.setState({project: 
project, operation: "delete"});
+                                }}></Button>
+                            </Tooltip>
+                        </FlexItem>
+                    </Flex>
+                }
+            </Td>
+        </Tr>
+    )
 }
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/projects/RunnerInfoTrace.tsx 
b/karavan-app/src/main/webui/src/projects/RunnerInfoTrace.tsx
deleted file mode 100644
index 2f9bc76b..00000000
--- a/karavan-app/src/main/webui/src/projects/RunnerInfoTrace.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import React, {useState} from 'react';
-import {
-    Button, DataList, DataListCell, DataListItem, DataListItemCells, 
DataListItemRow,
-    DescriptionList,
-    DescriptionListGroup,
-    DescriptionListTerm, Panel, PanelHeader, PanelMain, PanelMainBody, Switch,
-    Tooltip, TooltipPosition
-} from '@patternfly/react-core';
-import '../designer/karavan.css';
-import {ProjectEventBus} from "./ProjectEventBus";
-import {RunnerInfoTraceModal} from "./RunnerInfoTraceModal";
-
-
-interface Props {
-    trace: any
-    refreshTrace: boolean
-}
-
-export const RunnerInfoTrace = (props: Props) => {
-
-    const [trace, setTrace] = useState({});
-    const [nodes, setNodes] = useState([{}]);
-    const [isOpen, setIsOpen] = useState(false);
-
-    function closeModal() {
-        setIsOpen(false);
-    }
-
-    function getNodes(exchangeId: string): any[] {
-        const traces: any[] = props.trace?.trace?.traces || [];
-        return traces
-            .filter(t => t.message?.exchangeId === exchangeId)
-            .sort((a, b) => a.uid > b.uid ? 1 : -1);
-    }
-
-    function getNode(exchangeId: string): any {
-        const traces: any[] = props.trace?.trace?.traces || [];
-        return traces
-            .filter(t => t.message?.exchangeId === exchangeId)
-            .sort((a, b) => a.uid > b.uid ? 1 : -1)
-            .at(0);
-    }
-
-    const traces: any[] = (props.trace?.trace?.traces || []).sort((a: any, b: 
any) => b.uid > a.uid ? 1 : -1);
-    const exchanges: any[] = Array.from(new Set((traces).map((item: any) => 
item?.message?.exchangeId)));
-    return (
-        <Panel isScrollable>
-            {isOpen && <RunnerInfoTraceModal isOpen={isOpen} trace={trace} 
nodes={nodes} onClose={closeModal}/>}
-            <PanelHeader>
-                <div style={{display: "flex", flexDirection: "row", 
justifyContent: "space-between"}}>
-                    <DescriptionList>
-                        <DescriptionListGroup>
-                            <DescriptionListTerm>Trace routed 
messages</DescriptionListTerm>
-                        </DescriptionListGroup>
-                    </DescriptionList>
-                    <div style={{marginRight: "16px"}}>
-                        <Tooltip content="Auto refresh" 
position={TooltipPosition.left}>
-                            <Switch aria-label="refresh"
-                                    id="refresh"
-                                    isChecked={props.refreshTrace}
-                                    onChange={checked => 
ProjectEventBus.refreshTrace(checked)}
-                            />
-                        </Tooltip>
-                    </div>
-                </div>
-            </PanelHeader>
-            <PanelMain tabIndex={0}>
-                <PanelMainBody style={{padding: "0"}}>
-                    <DataList aria-label="Compact data list example" isCompact>
-                        {exchanges.map(exchangeId => {
-                            const node = getNode(exchangeId);
-                            return <DataListItem key={exchangeId} 
aria-labelledby="compact-item1">
-                                <DataListItemRow>
-                                    <DataListItemCells
-                                        dataListCells={[
-                                            <DataListCell 
key="uid">{node?.uid}</DataListCell>,
-                                            <DataListCell key="exchangeId">
-                                                <Button style={{padding: '0'}} 
variant={"link"}
-                                                        onClick={e => {
-                                                            setTrace(trace);
-                                                            
setNodes(getNodes(exchangeId));
-                                                            setIsOpen(true);
-                                                        }}>
-                                                    {exchangeId}
-                                                </Button>
-
-                                            </DataListCell>,
-                                            <DataListCell 
key="timestamp">{node ? new Date(node?.timestamp).toISOString() : 
""}</DataListCell>
-                                        ]}
-                                    />
-                                </DataListItemRow>
-                            </DataListItem>
-                        })}
-                    </DataList>
-                </PanelMainBody>
-            </PanelMain>
-        </Panel>
-    );
-}
diff --git a/karavan-app/src/main/webui/src/projects/modal/CreateFileModal.tsx 
b/karavan-app/src/main/webui/src/projects/modal/CreateFileModal.tsx
new file mode 100644
index 00000000..fa29401c
--- /dev/null
+++ b/karavan-app/src/main/webui/src/projects/modal/CreateFileModal.tsx
@@ -0,0 +1,93 @@
+import React, {useState} from 'react';
+import {
+    Button,
+    Modal,
+    FormGroup,
+    ModalVariant,
+    Form,
+    ToggleGroupItem, ToggleGroup, FormHelperText, HelperText, HelperTextItem, 
TextInput
+} from '@patternfly/react-core';
+import '../../designer/karavan.css';
+import {ProjectFile, ProjectFileTypes} from "../ProjectModels";
+import {CamelUi} from "../../designer/utils/CamelUi";
+import {Integration} from "karavan-core/lib/model/IntegrationDefinition";
+import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
+import {useFileStore, useProjectStore} from "../ProjectStore";
+import {ProjectLogic} from "../ProjectLogic";
+
+interface Props {
+    types: string[]
+}
+
+export const CreateFileModal = (props: Props) => {
+
+    const {file, operation} = useFileStore();
+    const {project, setProject} = useProjectStore();
+    const [name, setName] = useState<string>( '');
+    const [fileType, setFileType] = useState<string>(props.types.at(0) || 
'INTEGRATION');
+
+    function cleanValues()  {
+        setName("");
+        setFileType(props.types.at(0) || 'INTEGRATION');
+    }
+
+    function closeModal () {
+        useFileStore.setState({operation: "none"});
+        cleanValues();
+    }
+
+    function confirmAndCloseModal () {
+        const extension = ProjectFileTypes.filter(value => value.name === 
fileType)[0].extension;
+        const filename = (extension !== 'java') ? CamelUi.nameFromTitle(name) 
: CamelUi.javaNameFromTitle(name);
+        const code = fileType === 'INTEGRATION'
+            ? 
CamelDefinitionYaml.integrationToYaml(Integration.createNew(name, 'plain'))
+            : '';
+        if (filename && extension) {
+            const file = new ProjectFile(filename + '.' + extension, 
project.projectId, code, Date.now());
+            ProjectLogic.createFile(file);
+            useFileStore.setState({operation: "none"});
+            cleanValues();
+        }
+    }
+
+    const extension = ProjectFileTypes.filter(value => value.name === 
fileType)[0].extension;
+    const filename = (extension !== 'java')
+        ? CamelUi.nameFromTitle(name)
+        : CamelUi.javaNameFromTitle(name)
+    return (
+        <Modal
+            title="Create"
+            variant={ModalVariant.small}
+            isOpen={["create", "copy"].includes(operation)}
+            onClose={closeModal}
+            actions={[
+                <Button key="confirm" variant="primary" onClick={event => 
confirmAndCloseModal()}>Save</Button>,
+                <Button key="cancel" variant="secondary" onClick={event => 
closeModal()}>Cancel</Button>
+            ]}
+        >
+            <Form autoComplete="off" isHorizontal className="create-file-form">
+                <FormGroup label="Type" fieldId="type" isRequired>
+                    <ToggleGroup aria-label="Type" isCompact>
+                        {ProjectFileTypes.filter(p => 
props.types.includes(p.name))
+                            .map(p => {
+                                const title = p.title + ' (' + p.extension + 
')';
+                                return <ToggleGroupItem key={title} 
text={title} buttonId={p.name}
+                                                        isSelected={fileType 
=== p.name}
+                                                        onChange={selected => {
+                                                            
setFileType(p.name);
+                                                        }}/>
+                            })}
+                    </ToggleGroup>
+                </FormGroup>
+                <FormGroup label="Name" fieldId="name" isRequired>
+                    <TextInput id="name" aria-label="name" value={name} 
onChange={value => setName(value)}/>
+                    <FormHelperText isHidden={false} component="div">
+                        <HelperText id="helper-text1">
+                            <HelperTextItem variant={'default'}>{filename + 
'.' + extension}</HelperTextItem>
+                        </HelperText>
+                    </FormHelperText>
+                </FormGroup>
+            </Form>
+        </Modal>
+    )
+}
\ No newline at end of file
diff --git 
a/karavan-app/src/main/webui/src/projects/modal/CreateProjectModal.tsx 
b/karavan-app/src/main/webui/src/projects/modal/CreateProjectModal.tsx
new file mode 100644
index 00000000..6312c117
--- /dev/null
+++ b/karavan-app/src/main/webui/src/projects/modal/CreateProjectModal.tsx
@@ -0,0 +1,103 @@
+import React, {useEffect, useState} from 'react';
+import {
+    Button, Form, FormGroup,
+    Modal,
+    ModalVariant, Radio, TextInput,
+} from '@patternfly/react-core';
+import '../../designer/karavan.css';
+import {useProjectStore} from "../ProjectStore";
+import {ProjectLogic} from "../ProjectLogic";
+import {QuarkusIcon, SpringIcon} from "../../designer/utils/KaravanIcons";
+import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
+import {CamelUi} from "../../designer/utils/CamelUi";
+import {Project} from "../ProjectModels";
+import {ProjectEventBus} from "../ProjectEventBus";
+
+interface Props {
+    config: any,
+}
+
+export const CreateProjectModal = (props: Props) => {
+
+    const {project, operation} = useProjectStore();
+    const [name, setName] = useState('');
+    const [description, setDescription] = useState('');
+    const [runtime, setRuntime] = useState('');
+    const [projectId, setProjectId] = useState('');
+
+    function cleanValues()  {
+        setName("");
+        setDescription("");
+        setRuntime("");
+        setProjectId("");
+    }
+
+    function closeModal () {
+        useProjectStore.setState({operation: "none"});
+        cleanValues();
+    }
+
+    function confirmAndCloseModal () {
+        ProjectLogic.createProject(new Project({name: name, description: 
description, runtime: runtime, projectId: projectId}));
+        useProjectStore.setState({operation: "none"});
+        cleanValues();
+    }
+
+    function onKeyDown (event: React.KeyboardEvent<HTMLDivElement>): void {
+        if (event.key === 'Enter' && name !== undefined && description !== 
undefined && projectId !== undefined) {
+            confirmAndCloseModal();
+        }
+    }
+
+    const runtimes = props.config.runtimes;
+    const isReady = projectId && name && description && !['templates', 
'kamelets'].includes(projectId);
+    return (
+        <Modal
+            title={operation !== 'copy' ? "Create new project" : "Copy project 
from " + project?.projectId}
+            variant={ModalVariant.small}
+            isOpen={["create", "copy"].includes(operation)}
+            onClose={closeModal}
+            onKeyDown={onKeyDown}
+            actions={[
+                <Button key="confirm" variant="primary" isDisabled={!isReady}
+                        onClick={confirmAndCloseModal}>Save</Button>,
+                <Button key="cancel" variant="secondary" 
onClick={closeModal}>Cancel</Button>
+            ]}
+            className="new-project"
+        >
+            <Form isHorizontal={true} autoComplete="off">
+                <FormGroup label="Name" fieldId="name" isRequired>
+                    <TextInput className="text-field" type="text" id="name" 
name="name"
+                               value={name}
+                               onChange={e => setName(e)}/>
+                </FormGroup>
+                <FormGroup label="Description" fieldId="description" 
isRequired>
+                    <TextInput className="text-field" type="text" 
id="description" name="description"
+                               value={description}
+                               onChange={e => setDescription(e)}/>
+                </FormGroup>
+                <FormGroup label="Project ID" fieldId="projectId" isRequired 
helperText="Unique project name">
+                    <TextInput className="text-field" type="text" 
id="projectId" name="projectId"
+                               value={projectId}
+                               onFocus={e => setProjectId(projectId === '' ? 
CamelUi.nameFromTitle(name) : projectId)}
+                               onChange={e => 
setProjectId(CamelUi.nameFromTitle(e))}/>
+                </FormGroup>
+                <FormGroup label="Runtime" fieldId="runtime" isRequired>
+                    {runtimes?.map((r: string) => (
+                        <Radio key={r} id={r} name={r} className="radio" 
aria-label="runtime"
+                               isChecked={r === runtime}
+                               onChange={checked => {
+                                   if (checked) setRuntime(r)
+                               }}
+                               body={
+                                   <div className="runtime-radio">
+                                       {r === 'quarkus' ? QuarkusIcon() : 
SpringIcon()}
+                                       <div 
className="runtime-label">{CamelUtil.capitalizeName(r)}</div>
+                                   </div>}
+                        />
+                    ))}
+                </FormGroup>
+            </Form>
+        </Modal>
+    )
+}
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/projects/modal/DeleteFileModal.tsx 
b/karavan-app/src/main/webui/src/projects/modal/DeleteFileModal.tsx
new file mode 100644
index 00000000..85dc7acf
--- /dev/null
+++ b/karavan-app/src/main/webui/src/projects/modal/DeleteFileModal.tsx
@@ -0,0 +1,40 @@
+import React from 'react';
+import {
+    Button,
+    Modal,
+    ModalVariant,
+} from '@patternfly/react-core';
+import '../../designer/karavan.css';
+import {useFileStore, useProjectStore} from "../ProjectStore";
+import {ProjectLogic} from "../ProjectLogic";
+
+export const DeleteFileModal = () => {
+
+    const {file, operation} = useFileStore();
+
+    function closeModal () {
+        useFileStore.setState({operation: "none"})
+    }
+
+    function confirmAndCloseModal () {
+        if (file) ProjectLogic.deleteFile(file);
+        useFileStore.setState({operation: "none"});
+    }
+
+    const isOpen= operation === "delete";
+    return (
+            <Modal
+                title="Confirmation"
+                variant={ModalVariant.small}
+                isOpen={isOpen}
+                onClose={() => closeModal()}
+                actions={[
+                    <Button key="confirm" variant="primary" onClick={e => 
confirmAndCloseModal()}>Delete</Button>,
+                    <Button key="cancel" variant="link"
+                            onClick={e => closeModal()}>Cancel</Button>
+                ]}
+                onEscapePress={e => closeModal()}>
+                <div>{"Are you sure you want to delete file " + file?.name + 
"?"}</div>
+            </Modal>
+    )
+}
\ No newline at end of file
diff --git 
a/karavan-app/src/main/webui/src/projects/modal/DeleteProjectModal.tsx 
b/karavan-app/src/main/webui/src/projects/modal/DeleteProjectModal.tsx
new file mode 100644
index 00000000..ad07326c
--- /dev/null
+++ b/karavan-app/src/main/webui/src/projects/modal/DeleteProjectModal.tsx
@@ -0,0 +1,53 @@
+import React from 'react';
+import {
+    Button,
+    Modal,
+    ModalVariant,
+} from '@patternfly/react-core';
+import '../../designer/karavan.css';
+import {useProjectStore} from "../ProjectStore";
+import {ProjectLogic} from "../ProjectLogic";
+
+export const DeleteProjectModal = () => {
+
+    const {project, operation} = useProjectStore();
+
+    function closeModal () {
+        useProjectStore.setState({operation: "none"})
+    }
+
+    function confirmAndCloseModal () {
+        ProjectLogic.deleteProject(project);
+        useProjectStore.setState({operation: "none"});
+    }
+
+    const isOpen= operation === "delete";
+    return (
+            <Modal
+                title="Confirmation"
+                variant={ModalVariant.small}
+                isOpen={isOpen}
+                onClose={() => closeModal()}
+                actions={[
+                    <Button key="confirm" variant="primary" onClick={e => 
confirmAndCloseModal()}>Delete</Button>,
+                    <Button key="cancel" variant="link"
+                            onClick={e => closeModal()}>Cancel</Button>
+                ]}
+                onEscapePress={e => closeModal()}>
+                <div>{"Are you sure you want to delete the project " + 
project?.projectId + "?"}</div>
+            </Modal>
+            // }
+            // {(this.state.isProjectDeploymentModalOpen === true) && <Modal
+            //     variant={ModalVariant.small}
+            //     isOpen={this.state.isProjectDeploymentModalOpen}
+            //     onClose={() => this.setState({ 
isProjectDeploymentModalOpen: false })}
+            //     onEscapePress={e => this.setState({ 
isProjectDeploymentModalOpen: false })}>
+            //     <div>
+            //         <Alert key={this.state.projectToDelete?.projectId} 
className="main-alert" variant="warning"
+            //                title={"Deployment is Running!!"} 
isInline={true} isPlain={true}>
+            //             {"Delete the deployment (" + 
this.state.projectToDelete?.projectId + ")" + " first."}
+            //         </Alert>
+            //     </div>
+            // </Modal>
+    )
+}
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/projects/ProjectDevelopment.tsx 
b/karavan-app/src/main/webui/src/projects/tabs/DashboardTab.tsx
similarity index 55%
rename from karavan-app/src/main/webui/src/projects/ProjectDevelopment.tsx
rename to karavan-app/src/main/webui/src/projects/tabs/DashboardTab.tsx
index db13e68b..a31954f2 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectDevelopment.tsx
+++ b/karavan-app/src/main/webui/src/projects/tabs/DashboardTab.tsx
@@ -1,61 +1,47 @@
 import React, {useEffect, useRef, useState} from 'react';
 import {
     Card,
-    CardBody, Flex, FlexItem, Divider
+    CardBody, Flex, FlexItem, Divider, PageSection
 } from '@patternfly/react-core';
-import '../designer/karavan.css';
-import {PodStatus, Project} from "./ProjectModels";
-import {RunnerToolbar} from "./RunnerToolbar";
+import '../../designer/karavan.css';
+import {PodStatus} from "../ProjectModels";
 import {RunnerInfoPod} from "./RunnerInfoPod";
 import {RunnerInfoContext} from "./RunnerInfoContext";
 import {RunnerInfoMemory} from "./RunnerInfoMemory";
-import {KaravanApi} from "../api/KaravanApi";
-import {ProjectEventBus} from "./ProjectEventBus";
-import {RunnerInfoTrace} from "./RunnerInfoTrace";
+import {KaravanApi} from "../../api/KaravanApi";
+import {ProjectEventBus} from "../ProjectEventBus";
+import {useProjectStore} from "../ProjectStore";
 
 export function isRunning(status: PodStatus): boolean {
     return status.phase === 'Running' && !status.terminating;
 }
 
-
 interface Props {
-    project: Project,
     config: any,
 }
 
-export const ProjectDevelopment = (props: Props) => {
+export const DashboardTab = (props: Props) => {
 
+    const {project, setProject} = useProjectStore();
     const [podStatus, setPodStatus] = useState(new PodStatus());
     const previousValue = useRef(new PodStatus());
     const [memory, setMemory] = useState({});
     const [jvm, setJvm] = useState({});
     const [context, setContext] = useState({});
-    const [trace, setTrace] = useState({});
-    const [showTrace, setShowTrace] = useState(false);
-    const [refreshTrace, setRefreshTrace] = useState(true);
-
 
     useEffect(() => {
         previousValue.current = podStatus;
-        const sub1 = ProjectEventBus.onShowTrace()?.subscribe((result) => {
-            if (result) setShowTrace(result.show);
-        });
-        const sub2 = ProjectEventBus.onRefreshTrace()?.subscribe((result) => {
-            setRefreshTrace(result);
-        });
         const interval = setInterval(() => {
             onRefreshStatus();
         }, 1000);
         return () => {
-            sub1.unsubscribe();
-            sub2.unsubscribe();
             clearInterval(interval)
         };
 
     }, [podStatus]);
 
     function onRefreshStatus() {
-        const projectId = props.project.projectId;
+        const projectId = project.projectId;
         const name = projectId + "-runner";
         KaravanApi.getRunnerPodStatus(projectId, name, res => {
             if (res.status === 200) {
@@ -89,31 +75,22 @@ export const ProjectDevelopment = (props: Props) => {
                 setContext({});
             }
         })
-        if (refreshTrace) {
-            KaravanApi.getRunnerConsoleStatus(projectId, "trace", res => {
-                if (res.status === 200) {
-                    setTrace(res.data);
-                } else {
-                    setTrace({});
-                }
-            })
-        }
     }
 
     function showConsole(): boolean {
         return podStatus.phase !== '';
     }
 
-    const {project, config} = props;
+    const {config} = props;
     return (
-        <Card className="project-development">
-            <CardBody>
-                <Flex direction={{default: "row"}}
-                      justifyContent={{default: "justifyContentSpaceBetween"}}>
-                    {!showTrace && <FlexItem flex={{default: "flex_1"}}>
-                        <RunnerInfoPod podStatus={podStatus} config={config} 
showConsole={showConsole()}/>
-                    </FlexItem>}
-                    {showConsole() && !showTrace && <>
+        <PageSection className="project-bottom" padding={{default: "padding"}}>
+            <Card className="project-development">
+                <CardBody>
+                    <Flex direction={{default: "row"}}
+                          justifyContent={{default: 
"justifyContentSpaceBetween"}}>
+                        <FlexItem flex={{default: "flex_1"}}>
+                            <RunnerInfoPod podStatus={podStatus} 
config={config}/>
+                        </FlexItem>
                         <Divider orientation={{default: "vertical"}}/>
                         <FlexItem flex={{default: "flex_1"}}>
                             <RunnerInfoMemory jvm={jvm} memory={memory} 
config={config} showConsole={showConsole()}/>
@@ -122,16 +99,9 @@ export const ProjectDevelopment = (props: Props) => {
                         <FlexItem flex={{default: "flex_1"}}>
                             <RunnerInfoContext context={context} 
config={config} showConsole={showConsole()}/>
                         </FlexItem>
-                    </>}
-                    {showConsole() && showTrace && <FlexItem flex={{default: 
"flex_1"}} style={{margin:"0"}}>
-                        <RunnerInfoTrace trace={trace} 
refreshTrace={refreshTrace}/>
-                    </FlexItem>}
-                    <Divider orientation={{default: "vertical"}}/>
-                    <FlexItem>
-                        <RunnerToolbar project={project} config={config} 
showConsole={showConsole()} reloadOnly={false}/>
-                    </FlexItem>
-                </Flex>
-            </CardBody>
-        </Card>
+                    </Flex>
+                </CardBody>
+            </Card>
+        </PageSection>
     )
 }
diff --git a/karavan-app/src/main/webui/src/projects/ProjectFilesTable.tsx 
b/karavan-app/src/main/webui/src/projects/tabs/ProjectFilesTab.tsx
similarity index 76%
rename from karavan-app/src/main/webui/src/projects/ProjectFilesTable.tsx
rename to karavan-app/src/main/webui/src/projects/tabs/ProjectFilesTab.tsx
index 1fd609a4..aafc1bc5 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectFilesTable.tsx
+++ b/karavan-app/src/main/webui/src/projects/tabs/ProjectFilesTab.tsx
@@ -6,30 +6,21 @@ import {
     EmptyState,
     EmptyStateVariant,
     EmptyStateIcon,
-    Title,
+    Title, PageSection,
 } from '@patternfly/react-core';
-import '../designer/karavan.css';
-import {getProjectFileType, ProjectFile} from "./ProjectModels";
+import '../../designer/karavan.css';
+import {getProjectFileType} from "../ProjectModels";
 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, useProjectStore} from "../ProjectStore";
 
 
-interface Props {
-    files: ProjectFile[],
-    onOpenDeleteConfirmation: (file: ProjectFile) => void,
-    onSelect: (file: ProjectFile) => void,
-}
-
-interface State {
-
-}
+export const ProjectFilesTab = () => {
 
-export class ProjectFilesTable extends React.Component<Props, State> {
+    const {files} = useFilesStore();
 
-    public state: State = {};
-
-    getDate(lastUpdate: number):string {
+    function getDate(lastUpdate: number): string {
         if (lastUpdate) {
             const date = new Date(lastUpdate);
             return date.toDateString() + ' ' + date.toLocaleTimeString();
@@ -37,10 +28,8 @@ export class ProjectFilesTable extends 
React.Component<Props, State> {
             return "N/A"
         }
     }
-
-    render() {
-        const {files, onOpenDeleteConfirmation, onSelect} = this.props;
-        return (
+    return (
+        <PageSection className="project-bottom" padding={{default: "padding"}}>
             <TableComposable aria-label="Files" variant={"compact"} 
className={"table"}>
                 <Thead>
                     <Tr>
@@ -59,18 +48,22 @@ export class ProjectFilesTable extends 
React.Component<Props, State> {
                             </Td>
                             <Td>
                                 <Button style={{padding: '6px'}} 
variant={"link"}
-                                        onClick={e => onSelect.call(this, 
file)}>
+                                        onClick={e =>
+                                            useFileStore.setState({file: file, 
operation: "select"})
+                                }>
                                     {file.name}
                                 </Button>
                             </Td>
                             <Td>
-                                {this.getDate(file.lastUpdate)}
+                                {getDate(file.lastUpdate)}
                             </Td>
                             <Td modifier={"fitContent"}>
                                 {file.projectId !== 'templates' &&
                                     <Button style={{padding: '0'}} 
variant={"plain"}
                                             isDisabled={file.name === 
'application.properties'}
-                                            onClick={e => 
onOpenDeleteConfirmation.call(this, file)}>
+                                            onClick={e =>
+                                                useFileStore.setState({file: 
file, operation: "delete"})
+                                    }>
                                         <DeleteIcon/>
                                     </Button>
                                 }
@@ -93,6 +86,6 @@ export class ProjectFilesTable extends React.Component<Props, 
State> {
                     }
                 </Tbody>
             </TableComposable>
-        )
-    }
+        </PageSection>
+    )
 }
diff --git 
a/karavan-app/src/main/webui/src/projects/tabs/ProjectPipelineTab.tsx 
b/karavan-app/src/main/webui/src/projects/tabs/ProjectPipelineTab.tsx
new file mode 100644
index 00000000..7eb5ca69
--- /dev/null
+++ b/karavan-app/src/main/webui/src/projects/tabs/ProjectPipelineTab.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import '../../designer/karavan.css';
+import {Project} from "../ProjectModels";
+import {ProjectStatus} from "../ProjectStatus";
+import {PageSection} from "@patternfly/react-core";
+
+
+interface Props {
+    project: Project,
+    config: any,
+    needCommit: boolean,
+}
+
+interface State {
+    environment: string,
+}
+
+export class ProjectPipelineTab extends React.Component<Props, State> {
+
+    public state: State = {
+        environment: this.props.config.environment
+    };
+
+    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>
+        )
+    }
+}
diff --git a/karavan-app/src/main/webui/src/projects/RunnerInfoContext.tsx 
b/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoContext.tsx
similarity index 96%
rename from karavan-app/src/main/webui/src/projects/RunnerInfoContext.tsx
rename to karavan-app/src/main/webui/src/projects/tabs/RunnerInfoContext.tsx
index 79624695..62cc67ca 100644
--- a/karavan-app/src/main/webui/src/projects/RunnerInfoContext.tsx
+++ b/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoContext.tsx
@@ -1,6 +1,5 @@
-import React, {useEffect, useRef, useState} from 'react';
+import React from 'react';
 import {
-    Button,
     DescriptionList,
     DescriptionListDescription,
     DescriptionListGroup,
@@ -8,10 +7,7 @@ import {
     Label, LabelGroup,
     Tooltip
 } from '@patternfly/react-core';
-import '../designer/karavan.css';
-import {PodStatus, Project} from "./ProjectModels";
-import {KaravanApi} from "../api/KaravanApi";
-import {ProjectEventBus} from "./ProjectEventBus";
+import '../../designer/karavan.css';
 import DownIcon from 
"@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
 import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
 
diff --git a/karavan-app/src/main/webui/src/projects/RunnerInfoMemory.tsx 
b/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoMemory.tsx
similarity index 96%
rename from karavan-app/src/main/webui/src/projects/RunnerInfoMemory.tsx
rename to karavan-app/src/main/webui/src/projects/tabs/RunnerInfoMemory.tsx
index e90af48a..f2eb27ce 100644
--- a/karavan-app/src/main/webui/src/projects/RunnerInfoMemory.tsx
+++ b/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoMemory.tsx
@@ -8,10 +8,7 @@ import {
     Label, LabelGroup,
     Tooltip
 } from '@patternfly/react-core';
-import '../designer/karavan.css';
-import {PodStatus, Project} from "./ProjectModels";
-import {KaravanApi} from "../api/KaravanApi";
-import {ProjectEventBus} from "./ProjectEventBus";
+import '../../designer/karavan.css';
 import DownIcon from 
"@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
 import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
 
diff --git a/karavan-app/src/main/webui/src/projects/RunnerInfoPod.tsx 
b/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoPod.tsx
similarity index 66%
rename from karavan-app/src/main/webui/src/projects/RunnerInfoPod.tsx
rename to karavan-app/src/main/webui/src/projects/tabs/RunnerInfoPod.tsx
index 3e05256a..0c31d40c 100644
--- a/karavan-app/src/main/webui/src/projects/RunnerInfoPod.tsx
+++ b/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoPod.tsx
@@ -8,17 +8,20 @@ import {
     Label,
     Tooltip
 } from '@patternfly/react-core';
-import '../designer/karavan.css';
-import {PodStatus} from "./ProjectModels";
-import {ProjectEventBus} from "./ProjectEventBus";
+import '../../designer/karavan.css';
+import {PodStatus} from "../ProjectModels";
+import {ProjectEventBus} from "../ProjectEventBus";
 import DownIcon from 
"@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
 import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
-import {isRunning} from "./ProjectDevelopment";
+
+
+export function isRunning(status: PodStatus): boolean {
+    return status.phase === 'Running' && !status.terminating;
+}
 
 interface Props {
     podStatus: PodStatus,
     config: any,
-    showConsole: boolean
 }
 
 export const RunnerInfoPod = (props: Props) => {
@@ -98,32 +101,30 @@ export const RunnerInfoPod = (props: Props) => {
                     {getPodInfo()}
                 </DescriptionListDescription>
             </DescriptionListGroup>
-            {props.showConsole && <>
-                <DescriptionListGroup>
-                    <DescriptionListTerm>Status</DescriptionListTerm>
-                    <DescriptionListDescription>
-                        {getPodStatus()}
-                    </DescriptionListDescription>
-                </DescriptionListGroup>
-                <DescriptionListGroup>
-                    <DescriptionListTerm>Requests</DescriptionListTerm>
-                    <DescriptionListDescription>
-                        {getPodRequests()}
-                    </DescriptionListDescription>
-                </DescriptionListGroup>
-                <DescriptionListGroup>
-                    <DescriptionListTerm>Limits</DescriptionListTerm>
-                    <DescriptionListDescription>
-                        {getPodLimits()}
-                    </DescriptionListDescription>
-                </DescriptionListGroup>
-                <DescriptionListGroup>
-                    <DescriptionListTerm>Created</DescriptionListTerm>
-                    <DescriptionListDescription>
-                        {getPodCreation()}
-                    </DescriptionListDescription>
-                </DescriptionListGroup>
-            </>}
+            <DescriptionListGroup>
+                <DescriptionListTerm>Status</DescriptionListTerm>
+                <DescriptionListDescription>
+                    {getPodStatus()}
+                </DescriptionListDescription>
+            </DescriptionListGroup>
+            <DescriptionListGroup>
+                <DescriptionListTerm>Requests</DescriptionListTerm>
+                <DescriptionListDescription>
+                    {getPodRequests()}
+                </DescriptionListDescription>
+            </DescriptionListGroup>
+            <DescriptionListGroup>
+                <DescriptionListTerm>Limits</DescriptionListTerm>
+                <DescriptionListDescription>
+                    {getPodLimits()}
+                </DescriptionListDescription>
+            </DescriptionListGroup>
+            <DescriptionListGroup>
+                <DescriptionListTerm>Created</DescriptionListTerm>
+                <DescriptionListDescription>
+                    {getPodCreation()}
+                </DescriptionListDescription>
+            </DescriptionListGroup>
         </DescriptionList>
     );
 }
diff --git a/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoTrace.tsx 
b/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoTrace.tsx
new file mode 100644
index 00000000..a168b89e
--- /dev/null
+++ b/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoTrace.tsx
@@ -0,0 +1,124 @@
+import React, {useState} from 'react';
+import {
+    Bullseye,
+    Button,
+    EmptyState,
+    EmptyStateIcon,
+    EmptyStateVariant, Flex, FlexItem,
+    Panel,
+    PanelHeader,
+    Text,
+    Switch, TextContent, TextVariants, Title,
+    Tooltip,
+    TooltipPosition
+} from '@patternfly/react-core';
+import '../../designer/karavan.css';
+import {ProjectEventBus} from "../ProjectEventBus";
+import {RunnerInfoTraceModal} from "./RunnerInfoTraceModal";
+import {TableComposable, Tbody, Td, Th, Thead, Tr} from 
"@patternfly/react-table";
+import SearchIcon from "@patternfly/react-icons/dist/esm/icons/search-icon";
+
+
+interface Props {
+    trace: any
+    refreshTrace: boolean
+}
+
+export const RunnerInfoTrace = (props: Props) => {
+
+    const [trace, setTrace] = useState({});
+    const [nodes, setNodes] = useState([{}]);
+    const [isOpen, setIsOpen] = useState(false);
+
+    function closeModal() {
+        setIsOpen(false);
+    }
+
+    function getNodes(exchangeId: string): any[] {
+        const traces: any[] = props.trace?.trace?.traces || [];
+        return traces
+            .filter(t => t.message?.exchangeId === exchangeId)
+            .sort((a, b) => a.uid > b.uid ? 1 : -1);
+    }
+
+    function getNode(exchangeId: string): any {
+        const traces: any[] = props.trace?.trace?.traces || [];
+        return traces
+            .filter(t => t.message?.exchangeId === exchangeId)
+            .sort((a, b) => a.uid > b.uid ? 1 : -1)
+            .at(0);
+    }
+
+    const traces: any[] = (props.trace?.trace?.traces || []).sort((a: any, b: 
any) => b.uid > a.uid ? 1 : -1);
+    const exchanges: any[] = Array.from(new Set((traces).map((item: any) => 
item?.message?.exchangeId)));
+    return (
+        <div>
+            {isOpen && <RunnerInfoTraceModal isOpen={isOpen} trace={trace} 
nodes={nodes} onClose={closeModal}/>}
+            <Panel>
+                <PanelHeader>
+                    <Flex direction={{default: "row"}} 
justifyContent={{default:"justifyContentFlexEnd"}}>
+                        <FlexItem>
+                            <TextContent>
+                                <Text component={TextVariants.h6}>Auto 
refresh</Text>
+                            </TextContent>
+                        </FlexItem>
+                        <FlexItem>
+                            <Switch aria-label="refresh"
+                                    id="refresh"
+                                    isChecked={props.refreshTrace}
+                                    onChange={checked => 
ProjectEventBus.refreshTrace(checked)}
+                            />
+                        </FlexItem>
+                    </Flex>
+                </PanelHeader>
+            </Panel>
+            <TableComposable aria-label="Files" variant={"compact"} 
className={"table"}>
+                <Thead>
+                    <Tr>
+                        <Th key='uid' width={30}>Type</Th>
+                        <Th key='exchangeId' width={40}>Filename</Th>
+                        <Th key='timestamp' width={30}>Updated</Th>
+                    </Tr>
+                </Thead>
+                <Tbody>
+                    {exchanges.map(exchangeId => {
+                        const node = getNode(exchangeId);
+                        return <Tr key={node?.uid}>
+                            <Td>
+                                {node?.uid}
+                            </Td>
+                            <Td>
+                                <Button style={{padding: '0'}} variant={"link"}
+                                        onClick={e => {
+                                            setTrace(trace);
+                                            setNodes(getNodes(exchangeId));
+                                            setIsOpen(true);
+                                        }}>
+                                    {exchangeId}
+                                </Button>
+                            </Td>
+                            <Td>
+                                {node ? new 
Date(node?.timestamp).toISOString() : ""}
+                            </Td>
+
+                        </Tr>
+                    })}
+                    {exchanges.length === 0 &&
+                        <Tr>
+                            <Td colSpan={8}>
+                                <Bullseye>
+                                    <EmptyState 
variant={EmptyStateVariant.small}>
+                                        <EmptyStateIcon icon={SearchIcon}/>
+                                        <Title headingLevel="h2" size="lg">
+                                            No results found
+                                        </Title>
+                                    </EmptyState>
+                                </Bullseye>
+                            </Td>
+                        </Tr>
+                    }
+                </Tbody>
+            </TableComposable>
+        </div>
+    );
+}
diff --git a/karavan-app/src/main/webui/src/projects/RunnerInfoTraceModal.tsx 
b/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoTraceModal.tsx
similarity index 94%
rename from karavan-app/src/main/webui/src/projects/RunnerInfoTraceModal.tsx
rename to karavan-app/src/main/webui/src/projects/tabs/RunnerInfoTraceModal.tsx
index a9e4e1d9..4f83cedc 100644
--- a/karavan-app/src/main/webui/src/projects/RunnerInfoTraceModal.tsx
+++ b/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoTraceModal.tsx
@@ -1,9 +1,9 @@
 import React, {useState} from 'react';
 import {
     Flex, FlexItem,
-    Modal, ModalVariant, DescriptionListGroup, DescriptionListTerm, 
DescriptionList, Nav, NavList, NavItem, Menu, MenuContent, MenuList, MenuItem, 
MenuGroup, Button
+    Modal, ModalVariant, DescriptionListGroup, DescriptionListTerm, 
DescriptionList, Button
 } from '@patternfly/react-core';
-import '../designer/karavan.css';
+import '../../designer/karavan.css';
 import {RunnerInfoTraceNode} from "./RunnerInfoTraceNode";
 import ArrowRightIcon from 
"@patternfly/react-icons/dist/esm/icons/arrow-right-icon";
 
diff --git a/karavan-app/src/main/webui/src/projects/RunnerInfoTraceNode.tsx 
b/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoTraceNode.tsx
similarity index 98%
rename from karavan-app/src/main/webui/src/projects/RunnerInfoTraceNode.tsx
rename to karavan-app/src/main/webui/src/projects/tabs/RunnerInfoTraceNode.tsx
index dbf0187c..7dbb4c45 100644
--- a/karavan-app/src/main/webui/src/projects/RunnerInfoTraceNode.tsx
+++ b/karavan-app/src/main/webui/src/projects/tabs/RunnerInfoTraceNode.tsx
@@ -6,7 +6,7 @@ import {
     DescriptionListGroup,
     DescriptionListTerm, Panel, PanelHeader, PanelMain, PanelMainBody
 } from '@patternfly/react-core';
-import '../designer/karavan.css';
+import '../../designer/karavan.css';
 
 interface Props {
     trace: any
diff --git a/karavan-app/src/main/webui/src/projects/tabs/TraceTab.tsx 
b/karavan-app/src/main/webui/src/projects/tabs/TraceTab.tsx
new file mode 100644
index 00000000..46c26403
--- /dev/null
+++ b/karavan-app/src/main/webui/src/projects/tabs/TraceTab.tsx
@@ -0,0 +1,61 @@
+import React, {useEffect, useState} from 'react';
+import {
+    Card,
+    CardBody, PageSection
+} from '@patternfly/react-core';
+import '../../designer/karavan.css';
+import {PodStatus} from "../ProjectModels";
+import {KaravanApi} from "../../api/KaravanApi";
+import {ProjectEventBus} from "../ProjectEventBus";
+import {RunnerInfoTrace} from "./RunnerInfoTrace";
+import {useProjectStore} from "../ProjectStore";
+
+export function isRunning(status: PodStatus): boolean {
+    return status.phase === 'Running' && !status.terminating;
+}
+
+interface Props {
+    config: any,
+}
+
+export const TraceTab = (props: Props) => {
+
+    const {project, setProject} = useProjectStore();
+    const [trace, setTrace] = useState({});
+    const [refreshTrace, setRefreshTrace] = useState(true);
+
+    useEffect(() => {
+        const sub2 = ProjectEventBus.onRefreshTrace()?.subscribe((result: 
boolean) => {
+            setRefreshTrace(result);
+        });
+        const interval = setInterval(() => {
+            onRefreshStatus();
+        }, 1000);
+        return () => {
+            sub2.unsubscribe();
+            clearInterval(interval)
+        };
+
+    }, []);
+
+    function onRefreshStatus() {
+        const projectId = project.projectId;
+        const name = projectId + "-runner";
+        if (refreshTrace) {
+            KaravanApi.getRunnerConsoleStatus(projectId, "trace", res => {
+                if (res.status === 200) {
+                    setTrace(res.data);
+                } else {
+                    setTrace({});
+                }
+            })
+        }
+    }
+
+    const {config} = props;
+    return (
+        <PageSection className="project-bottom" padding={{default: "padding"}}>
+            <RunnerInfoTrace trace={trace} refreshTrace={refreshTrace}/>
+        </PageSection>
+    )
+}
diff --git a/karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx 
b/karavan-app/src/main/webui/src/projects/toolbar/ProjectToolbar.tsx
similarity index 94%
rename from karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx
rename to karavan-app/src/main/webui/src/projects/toolbar/ProjectToolbar.tsx
index cb498579..39cf4c84 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectPageToolbar.tsx
+++ b/karavan-app/src/main/webui/src/projects/toolbar/ProjectToolbar.tsx
@@ -19,18 +19,19 @@ import {
     Tooltip,
     TooltipPosition
 } from '@patternfly/react-core';
-import '../designer/karavan.css';
-import {Project, ProjectFile} from "./ProjectModels";
+import '../../designer/karavan.css';
+import {Project, ProjectFile} from "../ProjectModels";
 import UploadIcon from "@patternfly/react-icons/dist/esm/icons/upload-icon";
 import DownloadIcon from 
"@patternfly/react-icons/dist/esm/icons/download-icon";
 import DownloadImageIcon from 
"@patternfly/react-icons/dist/esm/icons/image-icon";
 import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
 import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
 import PushIcon from "@patternfly/react-icons/dist/esm/icons/code-branch-icon";
-import {KaravanApi} from "../api/KaravanApi";
+import {KaravanApi} from "../../api/KaravanApi";
 import ReloadIcon from "@patternfly/react-icons/dist/esm/icons/bolt-icon";
-import {RunnerToolbar} from "./RunnerToolbar";
-import {ProjectEventBus} from "./ProjectEventBus";
+import {RunnerToolbar} from "../RunnerToolbar";
+import {ProjectEventBus} from "../ProjectEventBus";
+import {useFileStore, useProjectStore} from "../ProjectStore";
 
 interface Props {
     project: Project,
@@ -44,14 +45,13 @@ interface Props {
     addProperty: () => void,
     download: () => void,
     downloadImage: () => void,
-    setCreateModalOpen: () => void,
     setUploadModalOpen: () => void,
     setEditAdvancedProperties: (checked: boolean) => void,
     setMode: (mode: "design" | "code") => void,
     onRefresh: () => void,
 }
 
-export const ProjectPageToolbar = (props: Props) => {
+export const ProjectToolbar = (props: Props) => {
 
     const [isPushing, setIsPushing] = useState(false);
     const [commitMessageIsOpen, setCommitMessageIsOpen] = useState(false);
@@ -117,7 +117,7 @@ export const ProjectPageToolbar = (props: Props) => {
     }
 
     function getTemplatesToolbar() {
-        const {file,needCommit, editAdvancedProperties, download, 
setCreateModalOpen, setUploadModalOpen} = props;
+        const {file,needCommit, editAdvancedProperties, download, 
setUploadModalOpen} = props;
         const isFile = file !== undefined;
         const isProperties = file !== undefined && 
file.name.endsWith("properties");
         return <Toolbar id="toolbar-group-types">
@@ -154,7 +154,7 @@ export const ProjectPageToolbar = (props: Props) => {
                         </FlexItem>}
                         {!isFile && <FlexItem>
                             <Button isSmall variant={"secondary"} 
icon={<PlusIcon/>}
-                                    onClick={e => 
setCreateModalOpen()}>Create</Button>
+                                    onClick={e => 
ProjectEventBus.showCreateProjectModal(true)}>Create</Button>
                         </FlexItem>}
                         {!isFile && <FlexItem>
                             <Button isSmall variant="secondary" 
icon={<UploadIcon/>}
@@ -168,7 +168,7 @@ export const ProjectPageToolbar = (props: Props) => {
 
     function getProjectToolbar() {
         const {file,needCommit, mode, editAdvancedProperties, project, config,
-            addProperty, setEditAdvancedProperties, download, downloadImage, 
setCreateModalOpen, setUploadModalOpen} = props;
+            addProperty, setEditAdvancedProperties, download, downloadImage, 
setUploadModalOpen} = props;
         const isFile = file !== undefined;
         const isYaml = file !== undefined && file.name.endsWith("yaml");
         const isIntegration = isYaml && file?.code && 
CamelDefinitionYaml.yamlIsIntegration(file.code);
@@ -227,7 +227,7 @@ export const ProjectPageToolbar = (props: Props) => {
                     </FlexItem>}
                     {!isFile && <FlexItem>
                         <Button isSmall variant={"secondary"} 
icon={<PlusIcon/>}
-                                onClick={e => 
setCreateModalOpen()}>Create</Button>
+                                onClick={e => 
useFileStore.setState({operation:"create"})}>Create</Button>
                     </FlexItem>}
                     {!isFile && <FlexItem>
                         <Button isSmall variant="secondary" 
icon={<UploadIcon/>}


Reply via email to