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

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

commit a34cfdee06339365d92540d97214027835017650
Author: Marat Gubaidullin <marat.gubaidul...@gmail.com>
AuthorDate: Tue Jun 27 10:14:26 2023 -0400

    Refactor for #809
---
 karavan-app/src/main/webui/src/Main.tsx            |   9 +-
 .../src/main/webui/src/api/ProjectService.ts       |  55 ++++++-
 karavan-app/src/main/webui/src/api/ProjectStore.ts |  18 +++
 .../src/main/webui/src/project/ProjectPage.tsx     | 165 +--------------------
 .../src/main/webui/src/project/ProjectPanel.tsx    |  14 +-
 .../src/main/webui/src/project/ProjectToolbar.tsx  |  80 +++++-----
 .../main/webui/src/project/PropertiesEditor.tsx    |  42 ------
 .../src/main/webui/src/project/file/FileEditor.tsx | 143 ++++++++++++++++++
 .../src/project/{ => file}/PropertiesTable.tsx     | 103 +++++++------
 .../src/main/webui/src/project/files/FilesTab.tsx  |  19 ++-
 .../main/webui/src/project/files/FilesToolbar.tsx  |  16 ++
 .../main/webui/src/project/files/UploadModal.tsx   |  16 ++
 .../src/project/pipeline/ProjectPipelineTab.tsx    |   2 +-
 .../src/project/{ => pipeline}/ProjectStatus.tsx   |   8 +-
 .../webui/src/project/trace/RunnerInfoTrace.tsx    |  16 ++
 .../src/project/trace/RunnerInfoTraceModal.tsx     |  16 ++
 .../src/project/trace/RunnerInfoTraceNode.tsx      |  16 ++
 .../src/main/webui/src/project/trace/TraceTab.tsx  |  16 ++
 18 files changed, 448 insertions(+), 306 deletions(-)

diff --git a/karavan-app/src/main/webui/src/Main.tsx 
b/karavan-app/src/main/webui/src/Main.tsx
index 39f3192f..c16599bd 100644
--- a/karavan-app/src/main/webui/src/Main.tsx
+++ b/karavan-app/src/main/webui/src/Main.tsx
@@ -30,9 +30,9 @@ import {MainLogin} from "./MainLogin";
 import {DashboardPage} from "./dashboard/DashboardPage";
 import {Subscription} from "rxjs";
 import {ProjectEventBus} from "./api/ProjectEventBus";
-import {Project} from "./api/ProjectModels";
+import {Project, ProjectFile} from "./api/ProjectModels";
 import {ProjectPage} from "./project/ProjectPage";
-import {useAppConfigStore} from "./api/ProjectStore";
+import {useAppConfigStore, useFileStore} from "./api/ProjectStore";
 
 class ToastMessage {
     id: string = ''
@@ -194,7 +194,10 @@ export class Main extends React.Component<Props, State> {
                     <Tooltip content={page.tooltip} position={"right"}>
                         <Button id={page.pageId} icon={page.icon} 
variant={"plain"}
                                 className={this.state.pageId === page.pageId ? 
"nav-button-selected" : ""}
-                                onClick={event => this.setState({pageId: 
page.pageId})}
+                                onClick={event => {
+                                    useFileStore.setState({operation:'none', 
file: undefined})
+                                    this.setState({pageId: page.pageId});
+                                }}
                         />
                     </Tooltip>
                 </FlexItem>
diff --git a/karavan-app/src/main/webui/src/api/ProjectService.ts 
b/karavan-app/src/main/webui/src/api/ProjectService.ts
index e346c5bd..feca6ec3 100644
--- a/karavan-app/src/main/webui/src/api/ProjectService.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectService.ts
@@ -2,10 +2,59 @@ import {KaravanApi} from "./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";
+import { unstable_batchedUpdates } from 'react-dom'
+import {
+    useAppConfigStore,
+    useDeploymentStatusesStore,
+    useFilesStore,
+    useFileStore,
+    useProjectsStore,
+    useProjectStore
+} from "./ProjectStore";
 
 export class ProjectService {
 
+    public static pushProject (project: Project, commitMessage: string) {
+        useProjectStore.setState({isPushing: true})
+        const params = {
+            "projectId": project.projectId,
+            "message": commitMessage
+        };
+        KaravanApi.push(params, res => {
+            if (res.status === 200 || res.status === 201) {
+                useProjectStore.setState({isPushing: false})
+                ProjectService.refreshProject(project.projectId);
+                ProjectService.refreshProjectData();
+            } else {
+                // Todo notification
+            }
+        });
+    }
+
+    public static saveFile (file: ProjectFile) {
+        console.log(file)
+        KaravanApi.postProjectFile(file, res => {
+            if (res.status === 200) {
+                const newFile = res.data;
+                useFileStore.setState({file: newFile});
+                unstable_batchedUpdates(() => {
+                    useFilesStore.getState().upsertFile(newFile);
+                })
+            } else {
+                // console.log(res) //TODO show notification
+            }
+        })
+    }
+
+    public static refreshProject(projectId: string) {
+        KaravanApi.getProject(projectId , (project: Project)=> {
+            useProjectStore.setState({project: project});
+            unstable_batchedUpdates(() => {
+                useProjectsStore.getState().upsertProject(project);
+            })
+        });
+    }
+
     public static refreshProjects() {
         KaravanApi.getProjects((projects: Project[]) => {
             useProjectsStore.setState({projects: projects});
@@ -61,9 +110,9 @@ export class ProjectService {
         });
     }
 
-
-    public static refreshProjectData(environment?: string) {
+    public static refreshProjectData() {
         const project = useProjectStore.getState().project;
+        const environment = useAppConfigStore.getState().config.environment;
         KaravanApi.getProject(project.projectId, (project: Project) => {
             // ProjectEventBus.selectProject(project);
             KaravanApi.getTemplatesFiles((files: ProjectFile[]) => {
diff --git a/karavan-app/src/main/webui/src/api/ProjectStore.ts 
b/karavan-app/src/main/webui/src/api/ProjectStore.ts
index a524f6bc..4edce361 100644
--- a/karavan-app/src/main/webui/src/api/ProjectStore.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectStore.ts
@@ -34,6 +34,7 @@ export const useAppConfigStore = create<AppConfigState>((set) 
=> ({
 interface ProjectsState {
     projects: Project[];
     setProjects: (projects: Project[]) => void;
+    upsertProject: (project: Project) => void;
 }
 
 export const useProjectsStore = create<ProjectsState>((set) => ({
@@ -43,10 +44,18 @@ export const useProjectsStore = create<ProjectsState>((set) 
=> ({
             projects: ps,
         }), true);
     },
+    upsertProject: (project: Project) => {
+        set((state: ProjectsState) => ({
+            projects: state.projects.find(f => f.projectId === 
project.projectId) === undefined
+                ? [...state.projects, project]
+                : [...state.projects.filter(f => f.projectId !== 
project.projectId), project],
+        }), true);
+    }
 }))
 
 interface ProjectState {
     project: Project;
+    isPushing: boolean,
     operation: "create" | "select" | "delete" | "none" | "copy";
     setProject: (project: Project, operation:  "create" | "select" | "delete"| 
"none" | "copy") => void;
 }
@@ -54,6 +63,7 @@ interface ProjectState {
 export const useProjectStore = create<ProjectState>((set) => ({
     project: new Project(),
     operation: "none",
+    isPushing: false,
     setProject: (p: Project, o: "create" | "select" | "delete"| "none" | 
"copy") => {
         set((state: ProjectState) => ({
             project: p,
@@ -65,6 +75,7 @@ export const useProjectStore = create<ProjectState>((set) => 
({
 interface FilesState {
     files: ProjectFile[];
     setFiles: (files: ProjectFile[]) => void;
+    upsertFile: (file: ProjectFile) => void;
 }
 
 export const useFilesStore = create<FilesState>((set) => ({
@@ -74,6 +85,13 @@ export const useFilesStore = create<FilesState>((set) => ({
             files: files,
         }), true);
     },
+    upsertFile: (file: ProjectFile) => {
+        set((state: FilesState) => ({
+            files: state.files.find(f => f.name === file.name) === undefined
+                ? [...state.files, file]
+                : [...state.files.filter(f => f.name !== file.name), file],
+        }), true);
+    }
 }))
 
 interface FileState {
diff --git a/karavan-app/src/main/webui/src/project/ProjectPage.tsx 
b/karavan-app/src/main/webui/src/project/ProjectPage.tsx
index edf0ef6c..d23d86c1 100644
--- a/karavan-app/src/main/webui/src/project/ProjectPage.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectPage.tsx
@@ -1,28 +1,20 @@
 import React, {useEffect, useState} from 'react';
 import {
     PageSection,
-    CodeBlockCode,
-    CodeBlock, Skeleton
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {KaravanApi} from "../api/KaravanApi";
 import FileSaver from "file-saver";
-import Editor from "@monaco-editor/react";
-import {PropertiesEditor} from "./PropertiesEditor";
-import {ProjectModel, ProjectProperty} from 
"karavan-core/lib/model/ProjectModel";
-import {ProjectModelApi} from "karavan-core/lib/api/ProjectModelApi";
-import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
 import {ProjectToolbar} from "./ProjectToolbar";
-import {EventBus} from "../designer/utils/EventBus";
 import {ProjectLog} from "./ProjectLog";
-import {AppConfig, ProjectFile, ProjectFileTypes} from "../api/ProjectModels";
-import {useAppConfigStore, useFileStore, useProjectStore} from 
"../api/ProjectStore";
-import {ProjectService} from "../api/ProjectService";
+import {ProjectFile, ProjectFileTypes} from "../api/ProjectModels";
+import {useFileStore, useProjectStore} from "../api/ProjectStore";
 import {MainToolbar} from "../common/MainToolbar";
 import {CreateFileModal} from "./CreateFileModal";
 import {DeleteFileModal} from "./DeleteFileModal";
 import {ProjectTitle} from "./ProjectTitle";
 import {ProjectPanel} from "./ProjectPanel";
+import {FileEditor} from "./file/FileEditor";
 
 export const ProjectPage = () => {
 
@@ -31,17 +23,7 @@ export const ProjectPage = () => {
     const {file, operation} = useFileStore();
     const [mode, setMode] = useState<"design" | "code">("design");
     const [key, setKey] = useState<string>('');
-    const [tab, setTab] = useState<string | number>('files');
     const {project} = useProjectStore();
-    const {config} = useAppConfigStore();
-
-    useEffect(() => {
-        onRefresh();
-    });
-
-    function onRefresh () {
-        ProjectService.refreshProjectData(config.environment);
-    }
 
     function post (file: ProjectFile)  {
         KaravanApi.postProjectFile(file, res => {
@@ -75,133 +57,18 @@ export const ProjectPage = () => {
         }
     }
 
-    function downloadImage () {
-        EventBus.sendCommand("downloadImage");
-    }
-
-    function addProperty() {
-        if (file) {
-            const project = file ? 
ProjectModelApi.propertiesToProject(file?.code) : ProjectModel.createNew();
-            const props = project.properties;
-            props.push(ProjectProperty.createNew("", ""))
-            save(file.name, ProjectModelApi.propertiesToString(props));
-            setKey(Math.random().toString());
-        }
-    }
 
     function tools () {
-        return <ProjectToolbar key={key}
-                               project={project}
+        return <ProjectToolbar
                                file={file}
                                mode={mode}
-                               isTemplates={false}
-                               isKamelets={false}
-                               addProperty={() => addProperty()}
-                               downloadImage={() => downloadImage()}
                                editAdvancedProperties={editAdvancedProperties}
                                setEditAdvancedProperties={checked => 
setEditAdvancedProperties(checked)}
                                setMode={mode => setMode(mode)}
                                setUploadModalOpen={() => 
setIsUploadModalOpen(isUploadModalOpen)}
-                               needCommit={false}
-                               onRefresh={onRefresh}
         />
     }
 
-    // function getDesigner () {
-    //     return (
-    //         file !== undefined &&
-    //         <KaravanDesigner
-    //             dark={false}
-    //             key={"key"}
-    //             filename={file.name}
-    //             yaml={file.code}
-    //             onSave={(name, yaml) => save(name, yaml)}
-    //             onSaveCustomCode={(name, code) => post(new ProjectFile(name 
+ ".java", project.projectId, code, Date.now()))}
-    //             onGetCustomCode={(name, javaType) => {
-    //                 return new Promise<string | undefined>(resolve => 
resolve(files.filter(f => f.name === name + ".java")?.at(0)?.code))
-    //             }}
-    //         />
-    //     )
-    // }
-
-    function getEditor () {
-        const language = file?.name.split('.').pop();
-        return (
-            file !== undefined &&
-            <Editor
-                height="100vh"
-                defaultLanguage={language}
-                theme={'light'}
-                value={file.code}
-                className={'code-editor'}
-                onChange={(value, ev) => {
-                    if (value) {
-                        save(file?.name, value)
-                    }
-                }}
-            />
-        )
-    }
-
-    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)
-                        onRefresh();
-                });
-                break;
-            case "pod":
-                KaravanApi.deletePod(environment, name, (res: any) => {
-                    if (Array.isArray(res) && Array.from(res).length > 0)
-                        onRefresh();
-                });
-                break;
-            case "pipelinerun":
-                KaravanApi.stopPipelineRun(environment, name, (res: any) => {
-                    if (Array.isArray(res) && Array.from(res).length > 0)
-                        onRefresh();
-                });
-                break;
-        }
-    }
-
-    function getLogView ()  {
-        return (
-            <div>
-                {file !== undefined && file.code.length !== 0 &&
-                    <CodeBlock>
-                        <CodeBlockCode id="code-content" 
className="log-code">{file.code}</CodeBlockCode>
-                    </CodeBlock>}
-                {(file === undefined || file.code.length === 0) &&
-                    <div>
-                        <Skeleton width="25%" screenreaderText="Loading 
contents"/>
-                        <br/>
-                        <Skeleton width="33%"/>
-                        <br/>
-                        <Skeleton width="50%"/>
-                        <br/>
-                        <Skeleton width="66%"/>
-                        <br/>
-                        <Skeleton width="75%"/>
-                        <br/>
-                        <Skeleton/>
-                    </div>}
-            </div>
-        )
-    }
-
-    function getPropertiesEditor  ()  {
-        return (
-            file !== undefined &&
-            <PropertiesEditor key={key}
-                              editAdvanced={editAdvancedProperties}
-                              file={file}
-                              onSave={(name, code) => save(name, code)}
-            />
-        )
-    }
-
     function isBuildIn(): boolean {
         return ['kamelets', 'templates'].includes(project.projectId);
     }
@@ -210,28 +77,6 @@ export const ProjectPage = () => {
         return project.projectId === 'kamelets';
     }
 
-    function isTemplatesProject(): boolean {
-        return project.projectId === 'templates';
-    }
-
-    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");
-        const isLog = file !== undefined && file.name.endsWith("log");
-        const isCode = file !== undefined && (file.name.endsWith("java") || 
file.name.endsWith("groovy") || file.name.endsWith("json"));
-        const showDesigner = isYaml && isIntegration && mode === 'design';
-        const showEditor = isCode || (isYaml && !isIntegration) || (isYaml && 
mode === 'code');
-        return (
-            <>
-                {/*{showDesigner && getDesigner()}*/}
-                {showEditor && getEditor()}
-                {isLog && getLogView()}
-                {isProperties && getPropertiesEditor()}
-            </>
-        )
-    }
-    console.log(operation, file)
     const types = isBuildIn()
         ? (isKameletsProject() ? ['KAMELET'] : ['CODE', 'PROPERTIES'])
         : ProjectFileTypes.filter(p => !['PROPERTIES', 'LOG', 
'KAMELET'].includes(p.name)).map(p => p.name);
@@ -241,7 +86,7 @@ export const ProjectPage = () => {
                 <MainToolbar title={<ProjectTitle/>} tools={tools()}/>
             </PageSection>
             {file === undefined && operation !== 'select' && <ProjectPanel/>}
-            {file !== undefined && operation === 'select' && getFilePanel()}
+            {file !== undefined && operation === 'select' && <FileEditor/>}
             <ProjectLog/>
             <CreateFileModal types={types}/>
             <DeleteFileModal />
diff --git a/karavan-app/src/main/webui/src/project/ProjectPanel.tsx 
b/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
index 8fa32f2d..fe21736d 100644
--- a/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectPanel.tsx
@@ -1,19 +1,29 @@
-import React, {useState} from 'react';
+import React, {useEffect, useState} from 'react';
 import {
     Flex,
     FlexItem, Tabs, Tab
 } from '@patternfly/react-core';
 import '../designer/karavan.css';
 import {FilesTab} from "./files/FilesTab";
-import {useProjectStore} from "../api/ProjectStore";
+import {useAppConfigStore, useProjectStore} from "../api/ProjectStore";
 import {DashboardTab} from "./dashboard/DashboardTab";
 import {TraceTab} from "./trace/TraceTab";
 import {ProjectPipelineTab} from "./pipeline/ProjectPipelineTab";
+import {ProjectService} from "../api/ProjectService";
 
 export const ProjectPanel = () => {
 
     const [tab, setTab] = useState<string | number>('files');
     const {project} = useProjectStore();
+    const {config} = useAppConfigStore();
+
+    useEffect(() => {
+        onRefresh();
+    });
+
+    function onRefresh () {
+        ProjectService.refreshProjectData();
+    }
 
     function isBuildIn(): boolean {
         return ['kamelets', 'templates'].includes(project.projectId);
diff --git a/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx 
b/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
index 0e7a75c4..8c27e40c 100644
--- a/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
+++ b/karavan-app/src/main/webui/src/project/ProjectToolbar.tsx
@@ -32,59 +32,70 @@ import {RunnerToolbar} from "./RunnerToolbar";
 import {Project, ProjectFile} from "../api/ProjectModels";
 import {ProjectEventBus} from "../api/ProjectEventBus";
 import {useAppConfigStore, useFilesStore, useFileStore, useProjectStore} from 
"../api/ProjectStore";
+import {EventBus} from "../designer/utils/EventBus";
+import {ProjectService} from "../api/ProjectService";
 
 interface Props {
-    project: Project,
-    needCommit: boolean,
-    isTemplates: boolean,
-    isKamelets: boolean,
     file?: ProjectFile,
     mode: "design" | "code",
     editAdvancedProperties: boolean,
-    addProperty: () => void,
-    downloadImage: () => void,
     setUploadModalOpen: () => void,
     setEditAdvancedProperties: (checked: boolean) => void,
     setMode: (mode: "design" | "code") => void,
-    onRefresh: () => void,
 }
 
 export const ProjectToolbar = (props: Props) => {
 
-    const [isPushing, setIsPushing] = useState(false);
     const [commitMessageIsOpen, setCommitMessageIsOpen] = useState(false);
     const [commitMessage, setCommitMessage] = useState('');
     const [currentRunner, setCurrentRunner] = useState('');
-    const [podName, setPodName] = useState(props.project.projectId + 
'-runner');
     const [isJbangRunning, setJbangIsRunning] = useState(false);
     const [isRunning, setIsRunning] = useState(false);
     const [isDeletingPod, setIsDeletingPod] = useState(false);
     const [isReloadingPod, setIsReloadingPod] = useState(false);
-    const {project} = useProjectStore();
+    const {project, isPushing} = useProjectStore();
     const {files} = useFilesStore();
     const {config} = useAppConfigStore();
 
     useEffect(() => {
+        console.log("ProjectToolbar useEffect", isPushing, 
project.lastCommitTimestamp)
         const sub1 = ProjectEventBus.onCurrentRunner()?.subscribe((result) => {
             setCurrentRunner(result || '');
-            setJbangIsRunning(result === props.project.name);
+            setJbangIsRunning(result === project.name);
         });
         return () => {
             sub1.unsubscribe();
         };
     });
 
+    function podName() {
+        return project.projectId + '-runner';
+    }
+
     function needCommit(): boolean {
         return project ? files.filter(f => f.lastUpdate > 
project.lastCommitTimestamp).length > 0 : false;
     }
 
+    function downloadImage () {
+        EventBus.sendCommand("downloadImage");
+    }
+
+    function addProperty() {
+        // if (file) {
+        //     const project = file ? 
ProjectModelApi.propertiesToProject(file?.code) : ProjectModel.createNew();
+        //     const props = project.properties;
+        //     props.push(ProjectProperty.createNew("", ""))
+        //     save(file.name, ProjectModelApi.propertiesToString(props));
+        //     setKey(Math.random().toString());
+        // }
+    }
+
     function jbangRun() {
         setJbangIsRunning(true);
-        KaravanApi.runProject(props.project, res => {
+        KaravanApi.runProject(project, res => {
             if (res.status === 200 || res.status === 201) {
-                ProjectEventBus.setCurrentRunner(props.project.name);
+                ProjectEventBus.setCurrentRunner(project.name);
                 setJbangIsRunning(false);
-                setPodName(res.data);
                 ProjectEventBus.showLog('container', res.data, 
config.environment)
             } else {
                 // Todo notification
@@ -96,7 +107,7 @@ export const ProjectToolbar = (props: Props) => {
 
     function reloadRunner() {
         setIsReloadingPod(true);
-        KaravanApi.getRunnerReload(props.project.projectId, res => {
+        KaravanApi.getRunnerReload(project.projectId, res => {
             if (res.status === 200 || res.status === 201) {
                 setIsReloadingPod(false);
             } else {
@@ -109,7 +120,7 @@ export const ProjectToolbar = (props: Props) => {
     function deleteRunner() {
         ProjectEventBus.setCurrentRunner(undefined);
         setIsDeletingPod(true);
-        KaravanApi.deleteRunner(podName, false, res => {
+        KaravanApi.deleteRunner(podName(), false, res => {
             if (res.status === 202) {
                 setIsDeletingPod(false);
             } else {
@@ -120,20 +131,8 @@ export const ProjectToolbar = (props: Props) => {
     }
 
     function push () {
-        setIsPushing(true);
         setCommitMessageIsOpen(false);
-        const params = {
-            "projectId": props.project.projectId,
-            "message": commitMessage
-        };
-        KaravanApi.push(params, res => {
-            if (res.status === 200 || res.status === 201) {
-                setIsPushing(false);
-                props.onRefresh();
-            } else {
-                // Todo notification
-            }
-        });
+        ProjectService.pushProject(project, commitMessage);
     }
 
     function getDate(lastUpdate: number): string {
@@ -146,8 +145,7 @@ export const ProjectToolbar = (props: Props) => {
     }
 
     function getLastUpdatePanel() {
-        const {project, needCommit} = props;
-        const color = needCommit ? "grey" : "green";
+        const color = needCommit() ? "grey" : "green";
         const commit = project?.lastCommit;
         return (
             <Flex direction={{default: "row"}} justifyContent={{default: 
"justifyContentFlexStart"}}>
@@ -169,7 +167,7 @@ export const ProjectToolbar = (props: Props) => {
     }
 
     function getTemplatesToolbar() {
-        const {file,needCommit, editAdvancedProperties, setUploadModalOpen} = 
props;
+        const {file, editAdvancedProperties, setUploadModalOpen} = props;
         const isFile = file !== undefined;
         const isProperties = file !== undefined && 
file.name.endsWith("properties");
         return <Toolbar id="toolbar-group-types">
@@ -183,7 +181,7 @@ export const ProjectToolbar = (props: Props) => {
                             <Tooltip content="Commit and push to git" 
position={"bottom"}>
                                 <Button isLoading={isPushing ? true : 
undefined}
                                         isSmall
-                                        variant={needCommit ? "primary" : 
"secondary"}
+                                        variant={needCommit() ? "primary" : 
"secondary"}
                                         className="project-button"
                                         icon={!isPushing ? <PushIcon/> : 
<div></div>}
                                         onClick={() => 
setCommitMessageIsOpen(true)}>
@@ -215,8 +213,8 @@ export const ProjectToolbar = (props: Props) => {
     }
 
     function getProjectToolbar() {
-        const {file,needCommit, mode, editAdvancedProperties, project,
-            addProperty, setEditAdvancedProperties, downloadImage, 
setUploadModalOpen} = props;
+        const {file, mode, editAdvancedProperties,
+             setEditAdvancedProperties, setUploadModalOpen} = props;
         const isFile = file !== undefined;
         const isYaml = file !== undefined && file.name.endsWith("yaml");
         const isIntegration = isYaml && file?.code && 
CamelDefinitionYaml.yamlIsIntegration(file.code);
@@ -231,7 +229,7 @@ export const ProjectToolbar = (props: Props) => {
                         <Tooltip content="Commit and push to git" 
position={"bottom-end"}>
                             <Button isLoading={isPushing ? true : undefined}
                                     isSmall
-                                    variant={needCommit ? "primary" : 
"secondary"}
+                                    variant={needCommit() ? "primary" : 
"secondary"}
                                     className="project-button"
                                     icon={!isPushing ? <PushIcon/> : 
<div></div>}
                                     onClick={() => {
@@ -299,7 +297,15 @@ export const ProjectToolbar = (props: Props) => {
         )
     }
 
-    const {isTemplates} = props;
+    function isKameletsProject(): boolean {
+        return project.projectId === 'kamelets';
+    }
+
+    function isTemplatesProject(): boolean {
+        return project.projectId === 'templates';
+    }
+
+    const isTemplates = isTemplatesProject();
     return  (
          <>
             {isTemplates && getTemplatesToolbar()}
diff --git a/karavan-app/src/main/webui/src/project/PropertiesEditor.tsx 
b/karavan-app/src/main/webui/src/project/PropertiesEditor.tsx
deleted file mode 100644
index bf2f4609..00000000
--- a/karavan-app/src/main/webui/src/project/PropertiesEditor.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react';
-import {
-    PageSection,
-} from '@patternfly/react-core';
-import '../designer/karavan.css';
-import {PropertiesTable} from "./PropertiesTable";
-import {ProjectModel} from "karavan-core/lib/model/ProjectModel";
-import {ProjectModelApi} from "karavan-core/lib/api/ProjectModelApi";
-import {ProjectFile} from "../api/ProjectModels";
-
-interface Props {
-    file: ProjectFile,
-    editAdvanced: boolean,
-    onSave?: (filename: string, code: string) => void
-}
-
-interface State {
-    project: ProjectModel,
-    file: ProjectFile,
-}
-
-export class PropertiesEditor extends React.Component<Props, State> {
-
-    public state: State = {
-        project: this.props.file ? 
ProjectModelApi.propertiesToProject(this.props.file?.code) : 
ProjectModel.createNew(),
-        file: this.props.file,
-    }
-
-    render() {
-        const file = this.state.file;
-        const project = file ? ProjectModelApi.propertiesToProject(file?.code) 
: ProjectModel.createNew();
-        return (
-            <PageSection isFilled className="kamelets-page" padding={{default: 
file !== undefined ? 'noPadding' : 'padding'}}>
-                <PropertiesTable
-                    editAdvanced={this.props.editAdvanced}
-                    properties={project.properties}
-                    onChange={properties => this.props.onSave?.call(this, 
file.name, ProjectModelApi.propertiesToString(properties))}
-                />
-            </PageSection>
-        )
-    }
-}
diff --git a/karavan-app/src/main/webui/src/project/file/FileEditor.tsx 
b/karavan-app/src/main/webui/src/project/file/FileEditor.tsx
new file mode 100644
index 00000000..c58008e2
--- /dev/null
+++ b/karavan-app/src/main/webui/src/project/file/FileEditor.tsx
@@ -0,0 +1,143 @@
+/*
+ * 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 React, {useState} from 'react';
+import {
+    CodeBlockCode,
+    CodeBlock, Skeleton
+} from '@patternfly/react-core';
+import '../../designer/karavan.css';
+import Editor from "@monaco-editor/react";
+import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
+import {ProjectFile, ProjectFileTypes} from "../../api/ProjectModels";
+import {useAppConfigStore, useFilesStore, useFileStore, useProjectStore} from 
"../../api/ProjectStore";
+import {KaravanDesigner} from "../../designer/KaravanDesigner";
+import {ProjectService} from "../../api/ProjectService";
+import {PropertiesTable} from "./PropertiesTable";
+
+export const FileEditor = () => {
+
+    const [editAdvancedProperties] = useState<boolean>(false);
+    const {file, operation} = useFileStore();
+    const [mode, setMode] = useState<"design" | "code">("design");
+    const [key, setKey] = useState<string>('');
+    const {project} = useProjectStore();
+    const {config} = useAppConfigStore();
+
+    function save (name: string, code: string) {
+        if (file) {
+            file.code = code;
+            ProjectService.saveFile(file);
+        }
+    }
+
+    function onGetCustomCode (name: string, javaType: string): Promise<string 
| undefined> {
+        const files = useFilesStore.getState().files;
+        return new Promise<string | undefined>(resolve => 
resolve(files.filter(f => f.name === name + ".java")?.at(0)?.code));
+    }
+
+    function getDesigner () {
+        return (
+            file !== undefined &&
+            <KaravanDesigner
+                dark={false}
+                key={"key"}
+                filename={file.name}
+                yaml={file.code}
+                onSave={(name, yaml) => save(name, yaml)}
+                onSaveCustomCode={(name, code) =>
+                    ProjectService.saveFile(new ProjectFile(name + ".java", 
project.projectId, code, Date.now()))}
+                onGetCustomCode={onGetCustomCode}
+            />
+        )
+    }
+
+    function getEditor () {
+        const language = file?.name.split('.').pop();
+        return (
+            file !== undefined &&
+            <Editor
+                height="100vh"
+                defaultLanguage={language}
+                theme={'light'}
+                value={file.code}
+                className={'code-editor'}
+                onChange={(value, ev) => {
+                    if (value) {
+                        save(file?.name, value)
+                    }
+                }}
+            />
+        )
+    }
+
+    function getLogView ()  {
+        return (
+            <div>
+                {file !== undefined && file.code.length !== 0 &&
+                    <CodeBlock>
+                        <CodeBlockCode id="code-content" 
className="log-code">{file.code}</CodeBlockCode>
+                    </CodeBlock>}
+                {(file === undefined || file.code.length === 0) &&
+                    <div>
+                        <Skeleton width="25%" screenreaderText="Loading 
contents"/>
+                        <br/>
+                        <Skeleton width="33%"/>
+                        <br/>
+                        <Skeleton width="50%"/>
+                        <br/>
+                        <Skeleton width="66%"/>
+                        <br/>
+                        <Skeleton width="75%"/>
+                        <br/>
+                        <Skeleton/>
+                    </div>}
+            </div>
+        )
+    }
+
+
+    function isBuildIn(): boolean {
+        return ['kamelets', 'templates'].includes(project.projectId);
+    }
+
+    function isKameletsProject(): boolean {
+        return project.projectId === 'kamelets';
+    }
+
+    function isTemplatesProject(): boolean {
+        return project.projectId === 'templates';
+    }
+
+    const types = isBuildIn()
+        ? (isKameletsProject() ? ['KAMELET'] : ['CODE', 'PROPERTIES'])
+        : ProjectFileTypes.filter(p => !['PROPERTIES', 'LOG', 
'KAMELET'].includes(p.name)).map(p => p.name);
+    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");
+    const isLog = file !== undefined && file.name.endsWith("log");
+    const isCode = file !== undefined && (file.name.endsWith("java") || 
file.name.endsWith("groovy") || file.name.endsWith("json"));
+    const showDesigner = isYaml && isIntegration && mode === 'design';
+    const showEditor = isCode || (isYaml && !isIntegration) || (isYaml && mode 
=== 'code');
+    return (
+        <>
+            {showDesigner && getDesigner()}
+            {showEditor && getEditor()}
+            {isLog && getLogView()}
+            {isProperties && file !== undefined && <PropertiesTable/>}
+        </>
+    )
+}
diff --git a/karavan-app/src/main/webui/src/project/PropertiesTable.tsx 
b/karavan-app/src/main/webui/src/project/file/PropertiesTable.tsx
similarity index 53%
rename from karavan-app/src/main/webui/src/project/PropertiesTable.tsx
rename to karavan-app/src/main/webui/src/project/file/PropertiesTable.tsx
index 72287a35..5b58cfeb 100644
--- a/karavan-app/src/main/webui/src/project/PropertiesTable.tsx
+++ b/karavan-app/src/main/webui/src/project/file/PropertiesTable.tsx
@@ -14,85 +14,90 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
+import React, {useState} from 'react';
 import {
     Button,
     Modal,
     PageSection,
     TextInput
 } from '@patternfly/react-core';
-import '../designer/karavan.css';
+import '../../designer/karavan.css';
 import {TableComposable, Tbody, Td, Th, Thead, Tr} from 
"@patternfly/react-table";
 import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
-import {ProjectProperty} from "karavan-core/lib/model/ProjectModel";
+import {ProjectModel, ProjectProperty} from 
"karavan-core/lib/model/ProjectModel";
+import {useFileStore} from "../../api/ProjectStore";
+import {ProjectService} from "../../api/ProjectService";
+import {ProjectModelApi} from "karavan-core/lib/api/ProjectModelApi";
 
-interface Props {
-    properties: ProjectProperty[]
-    onChange?: (properties: ProjectProperty[]) => void
-    editAdvanced: boolean
-}
+export const PropertiesTable = () => {
 
-interface State {
-    properties: ProjectProperty[]
-    showDeleteConfirmation: boolean
-    deleteId?: string
-}
+    const {file, operation} = useFileStore();
+    const [showDeleteConfirmation, setShowDeleteConfirmation] = 
useState<boolean>(false);
+    const [editAdvanced, setEditAdvanced] = useState<boolean>(false);
+    const [deleteId, setDeleteId] = useState<string | undefined>(undefined);
+    const [properties, setProperties] = useState<ProjectProperty[]>([]);
 
-export class PropertiesTable extends React.Component<Props, State> {
-
-    public state: State = {
-        properties: this.props.properties,
-        showDeleteConfirmation: false,
+    function save (props: ProjectProperty[]) {
+        console.log("save")
+        if (file) {
+            file.code = ProjectModelApi.propertiesToString(props);
+            console.log("save", file)
+            ProjectService.saveFile(file);
+        }
     }
 
-    sendUpdate = (props: ProjectProperty[]) => {
-        this.props.onChange?.call(this, props);
+    function getProjectModel (): ProjectModel {
+        return file ? ProjectModelApi.propertiesToProject(file?.code) : 
ProjectModel.createNew()
     }
 
-    changeProperty(p: ProjectProperty, field: "key" | "value", val?: string) {
+    function changeProperty(p: ProjectProperty, field: "key" | "value", val?: 
string) {
         const key: string = field === 'key' && val !== undefined ? val : p.key;
         const value: any = field === 'value' ? val : p.value;
         const property: ProjectProperty = {id: p.id, key: key, value: value};
-        const props = this.state.properties.map(prop => prop.id === 
property.id ? property : prop);
-        this.setState({properties: props});
-        this.sendUpdate(props);
+        const properties = getProjectModel().properties;
+        const props = properties.map(prop => prop.id === property.id ? 
property : prop);
+        save(props);
     }
 
-    startDelete(id: string) {
-        this.setState({showDeleteConfirmation: true, deleteId: id});
+    function startDelete(id: string) {
+        console.log("startDelete", id)
+        setShowDeleteConfirmation(true);
+        setDeleteId(id);
     }
 
-    confirmDelete() {
-        const props = this.state.properties.filter(p => p.id !== 
this.state.deleteId);
-        this.setState({properties: props, showDeleteConfirmation: false, 
deleteId: undefined});
-        this.sendUpdate(props);
+    function confirmDelete() {
+        console.log("confirmDelete")
+        const properties = getProjectModel().properties;
+        const props = properties.filter(p => p.id !== deleteId);
+        save(props);
+        setShowDeleteConfirmation(false);
+        setDeleteId(undefined);
     }
 
-    getDeleteConfirmation() {
+    function getDeleteConfirmation() {
         return (<Modal
             className="modal-delete"
             title="Confirmation"
-            isOpen={this.state.showDeleteConfirmation}
-            onClose={() => this.setState({showDeleteConfirmation: false})}
+            isOpen={showDeleteConfirmation}
+            onClose={() => setShowDeleteConfirmation(false)}
             actions={[
-                <Button key="confirm" variant="primary" onClick={e => 
this.confirmDelete()}>Delete</Button>,
+                <Button key="confirm" variant="primary" onClick={e => 
confirmDelete()}>Delete</Button>,
                 <Button key="cancel" variant="link"
-                        onClick={e => this.setState({showDeleteConfirmation: 
false})}>Cancel</Button>
+                        onClick={e => 
setShowDeleteConfirmation(false)}>Cancel</Button>
             ]}
-            onEscapePress={e => this.setState({showDeleteConfirmation: 
false})}>
+            onEscapePress={e => setShowDeleteConfirmation(false)}>
             <div>Delete property?</div>
         </Modal>)
     }
 
-    getTextInputField(property: ProjectProperty, field: "key" | "value", 
readOnly: boolean) {
-        return (<TextInput isDisabled={readOnly} isRequired={true} 
className="text-field" type={"text"} id={"key"} name={"key"}
+    function getTextInputField(property: ProjectProperty, field: "key" | 
"value", readOnly: boolean) {
+        return (<TextInput isDisabled={readOnly} isRequired={true} 
className="text-field" type={"text"} id={field + "-" + property.key}
                            value={field === "key" ? property.key : 
property.value}
-                           onChange={val => this.changeProperty(property, 
field, val)}/>)
+                           onChange={val => changeProperty(property, field, 
val)}/>)
     }
 
-    render() {
-        const properties = this.state.properties;
-        return (
+    return (
+        <PageSection isFilled className="kamelets-page" padding={{default: 
file !== undefined ? 'noPadding' : 'padding'}}>
             <PageSection padding={{default: "noPadding"}}>
                 {properties.length > 0 &&
                     <TableComposable aria-label="Property table" 
variant='compact' borders={false}
@@ -106,21 +111,21 @@ export class PropertiesTable extends 
React.Component<Props, State> {
                         </Thead>
                         <Tbody>
                             {properties.map((property, idx: number) => {
-                                const readOnly = 
(property.key.startsWith("camel.jbang") || 
property.key.startsWith("camel.karavan")) && !this.props.editAdvanced;
+                                const readOnly = 
(property.key.startsWith("camel.jbang") || 
property.key.startsWith("camel.karavan")) && !editAdvanced;
                                 return (
                                     <Tr key={property.id}>
-                                        <Td noPadding width={10} 
dataLabel="key">{this.getTextInputField(property, "key", readOnly)}</Td>
-                                        <Td noPadding width={20} 
dataLabel="value">{this.getTextInputField(property, "value", readOnly)}</Td>
+                                        <Td noPadding width={10} 
dataLabel="key">{getTextInputField(property, "key", readOnly)}</Td>
+                                        <Td noPadding width={20} 
dataLabel="value">{getTextInputField(property, "value", readOnly)}</Td>
                                         <Td noPadding isActionCell 
dataLabel="delete" className="delete-cell">
                                             {!readOnly && <Button 
variant={"plain"} icon={<DeleteIcon/>} className={"delete-button"}
-                                                                  
onClick={event => this.startDelete(property.id)}/>}
+                                                                  
onClick={event => startDelete(property.id)}/>}
                                         </Td>
                                     </Tr>
                                 )})}
                         </Tbody>
                     </TableComposable>}
-                {this.state.showDeleteConfirmation && 
this.getDeleteConfirmation()}
+                {showDeleteConfirmation && getDeleteConfirmation()}
             </PageSection>
-        )
-    }
+        </PageSection>
+    )
 }
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/project/files/FilesTab.tsx 
b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
index 3ae79da5..28e25c10 100644
--- a/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
+++ b/karavan-app/src/main/webui/src/project/files/FilesTab.tsx
@@ -6,32 +6,36 @@ import {
     EmptyState,
     EmptyStateVariant,
     EmptyStateIcon,
-    Title, PageSection, PanelHeader, Panel, Tooltip,
+    Title, PageSection, PanelHeader, Panel, Tooltip, Label,
 } from '@patternfly/react-core';
 import '../../designer/karavan.css';
 import {TableComposable, Tbody, Td, Th, Thead, Tr} from 
"@patternfly/react-table";
 import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
 import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
-import {useFilesStore, useFileStore} from "../../api/ProjectStore";
+import {useFilesStore, useFileStore, useProjectStore} from 
"../../api/ProjectStore";
 import {getProjectFileType, ProjectFile} from "../../api/ProjectModels";
 import {FileToolbar} from "./FilesToolbar";
 import DownloadIcon from 
"@patternfly/react-icons/dist/esm/icons/download-icon";
 import FileSaver from "file-saver";
 
-
 export const FilesTab = () => {
 
     const {files} = useFilesStore();
+    const {project} = useProjectStore();
 
     function getDate(lastUpdate: number): string {
         if (lastUpdate) {
             const date = new Date(lastUpdate);
-            return date.toDateString() + ' ' + date.toLocaleTimeString();
+            return date.toISOString().slice(0, 19).replace('T',' ');
         } else {
             return "N/A"
         }
     }
 
+    function needCommit(lastUpdate: number): boolean {
+        return lastUpdate > project.lastCommitTimestamp;
+    }
+
     function download (file: ProjectFile) {
         if (file) {
             const type = file.name.endsWith("yaml") ? 
"application/yaml;charset=utf-8" : undefined;
@@ -72,7 +76,12 @@ export const FilesTab = () => {
                                 </Button>
                             </Td>
                             <Td>
-                                {getDate(file.lastUpdate)}
+                                {needCommit(file.lastUpdate) &&
+                                    <Tooltip content="Updated after last 
commit" position={"right"}>
+                                        <Label 
color="grey">{getDate(file.lastUpdate)}</Label>
+                                    </Tooltip>
+                                }
+                                {!needCommit(file.lastUpdate) && 
getDate(file.lastUpdate)}
                             </Td>
                             <Td modifier={"fitContent"}>
                                 {file.projectId !== 'templates' &&
diff --git a/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx 
b/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
index 2ef4c092..00835fe4 100644
--- a/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
+++ b/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx
@@ -1,3 +1,19 @@
+/*
+ * 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 React from 'react';
 import {
     Button,
diff --git a/karavan-app/src/main/webui/src/project/files/UploadModal.tsx 
b/karavan-app/src/main/webui/src/project/files/UploadModal.tsx
index a2af3243..d0d73e65 100644
--- a/karavan-app/src/main/webui/src/project/files/UploadModal.tsx
+++ b/karavan-app/src/main/webui/src/project/files/UploadModal.tsx
@@ -1,3 +1,19 @@
+/*
+ * 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 React from 'react';
 import {
     TextInput,
diff --git 
a/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx 
b/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
index 6cd7e4fd..e2209f89 100644
--- a/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
+++ b/karavan-app/src/main/webui/src/project/pipeline/ProjectPipelineTab.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import '../../designer/karavan.css';
-import {ProjectStatus} from "../ProjectStatus";
+import {ProjectStatus} from "./ProjectStatus";
 import {PageSection} from "@patternfly/react-core";
 import {useAppConfigStore, useProjectStore} from "../../api/ProjectStore";
 
diff --git a/karavan-app/src/main/webui/src/project/ProjectStatus.tsx 
b/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
similarity index 98%
rename from karavan-app/src/main/webui/src/project/ProjectStatus.tsx
rename to karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
index 3e57ebe9..27c898dc 100644
--- a/karavan-app/src/main/webui/src/project/ProjectStatus.tsx
+++ b/karavan-app/src/main/webui/src/project/pipeline/ProjectStatus.tsx
@@ -6,16 +6,16 @@ import {
     DescriptionListGroup,
     DescriptionListDescription, Spinner, Tooltip, Flex, FlexItem, LabelGroup, 
Label, Modal, Badge, CardBody, Card
 } from '@patternfly/react-core';
-import '../designer/karavan.css';
-import {KaravanApi} from "../api/KaravanApi";
+import '../../designer/karavan.css';
+import {KaravanApi} from "../../api/KaravanApi";
 import BuildIcon from "@patternfly/react-icons/dist/esm/icons/build-icon";
 import RolloutIcon from 
"@patternfly/react-icons/dist/esm/icons/process-automation-icon";
 import UpIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
 import DownIcon from 
"@patternfly/react-icons/dist/esm/icons/error-circle-o-icon";
 import ClockIcon from "@patternfly/react-icons/dist/esm/icons/clock-icon";
 import DeleteIcon from 
"@patternfly/react-icons/dist/esm/icons/times-circle-icon";
-import {CamelStatus, DeploymentStatus, PipelineStatus, PodStatus, Project} 
from "../api/ProjectModels";
-import {ProjectEventBus} from "../api/ProjectEventBus";
+import {CamelStatus, DeploymentStatus, PipelineStatus, PodStatus, Project} 
from "../../api/ProjectModels";
+import {ProjectEventBus} from "../../api/ProjectEventBus";
 
 interface Props {
     project: Project,
diff --git a/karavan-app/src/main/webui/src/project/trace/RunnerInfoTrace.tsx 
b/karavan-app/src/main/webui/src/project/trace/RunnerInfoTrace.tsx
index 8f0f655d..cf2a37ce 100644
--- a/karavan-app/src/main/webui/src/project/trace/RunnerInfoTrace.tsx
+++ b/karavan-app/src/main/webui/src/project/trace/RunnerInfoTrace.tsx
@@ -1,3 +1,19 @@
+/*
+ * 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 React, {useState} from 'react';
 import {
     Bullseye,
diff --git 
a/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceModal.tsx 
b/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceModal.tsx
index 4f83cedc..6940f8a8 100644
--- a/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceModal.tsx
+++ b/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceModal.tsx
@@ -1,3 +1,19 @@
+/*
+ * 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 React, {useState} from 'react';
 import {
     Flex, FlexItem,
diff --git 
a/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceNode.tsx 
b/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceNode.tsx
index 7dbb4c45..975d7df5 100644
--- a/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceNode.tsx
+++ b/karavan-app/src/main/webui/src/project/trace/RunnerInfoTraceNode.tsx
@@ -1,3 +1,19 @@
+/*
+ * 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 React from 'react';
 import {
     CodeBlock, CodeBlockCode, DataList, DataListCell, DataListItem, 
DataListItemCells, DataListItemRow, DataListWrapModifier,
diff --git a/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx 
b/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx
index 31f1c0c9..1c108f8b 100644
--- a/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx
+++ b/karavan-app/src/main/webui/src/project/trace/TraceTab.tsx
@@ -1,3 +1,19 @@
+/*
+ * 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 React, {useEffect, useState} from 'react';
 import { PageSection
 } from '@patternfly/react-core';

Reply via email to