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/>}