You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ma...@apache.org on 2022/11/19 02:03:44 UTC
[camel-karavan] 12/12: Templates and Kamelets project panel
This is an automated email from the ASF dual-hosted git repository.
marat pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git
commit 83ab986f070a7ec7a853e07e0fa282ab0d9c88e0
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Fri Nov 18 21:03:18 2022 -0500
Templates and Kamelets project panel
---
.../main/webui/src/projects/ProjectFilesTable.tsx | 116 +++++++++++++++
.../src/main/webui/src/projects/ProjectModels.ts | 13 +-
.../src/main/webui/src/projects/ProjectPage.tsx | 165 +++++++--------------
.../src/main/webui/src/projects/ProjectsPage.tsx | 1 +
.../main/webui/src/projects/ProjectsTableRow.tsx | 32 +---
5 files changed, 185 insertions(+), 142 deletions(-)
diff --git a/karavan-app/src/main/webui/src/projects/ProjectFilesTable.tsx b/karavan-app/src/main/webui/src/projects/ProjectFilesTable.tsx
new file mode 100644
index 0000000..f1269e4
--- /dev/null
+++ b/karavan-app/src/main/webui/src/projects/ProjectFilesTable.tsx
@@ -0,0 +1,116 @@
+import React from 'react';
+import {
+ Badge,
+ Breadcrumb,
+ BreadcrumbItem,
+ Button,
+ PageSection,
+ Text,
+ TextContent,
+ Bullseye,
+ EmptyState,
+ EmptyStateVariant,
+ EmptyStateIcon,
+ Title,
+ ModalVariant,
+ Modal,
+ Flex,
+ FlexItem,
+ CodeBlockCode,
+ CodeBlock, Skeleton, Tabs, Tab
+} from '@patternfly/react-core';
+import '../designer/karavan.css';
+import {MainToolbar} from "../MainToolbar";
+import {KaravanApi} from "../api/KaravanApi";
+import {getProjectFileType, Project, ProjectFile, ProjectFileTypes} from "./ProjectModels";
+import {CamelUi} from "../designer/utils/CamelUi";
+import UploadIcon from "@patternfly/react-icons/dist/esm/icons/upload-icon";
+import {TableComposable, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
+import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
+import {KaravanDesigner} from "../designer/KaravanDesigner";
+import DownloadIcon from "@patternfly/react-icons/dist/esm/icons/download-icon";
+import DownloadImageIcon from "@patternfly/react-icons/dist/esm/icons/image-icon";
+import FileSaver from "file-saver";
+import Editor from "@monaco-editor/react";
+import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
+import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
+import {CreateFileModal} from "./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 {ProjectInfo} from "./ProjectInfo";
+import {ProjectOperations} from "./ProjectOperations";
+import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
+import PushIcon from "@patternfly/react-icons/dist/esm/icons/code-branch-icon";
+import {ProjectPageToolbar} from "./ProjectPageToolbar";
+
+interface Props {
+ files: ProjectFile[],
+ onOpenDeleteConfirmation: (file: ProjectFile) => void,
+ onSelect: (file: ProjectFile) => void,
+}
+
+interface State {
+
+}
+
+export class ProjectFilesTable extends React.Component<Props, State> {
+
+ public state: State = {};
+
+ render() {
+ const {files, onOpenDeleteConfirmation, onSelect} = this.props;
+ return (
+ <TableComposable aria-label="Files" variant={"compact"} className={"table"}>
+ <Thead>
+ <Tr>
+ <Th key='type' width={10}>Type</Th>
+ <Th key='filename' width={50}>Filename</Th>
+ <Th key='action'></Th>
+ </Tr>
+ </Thead>
+ <Tbody>
+ {files.map(file => {
+ const type = getProjectFileType(file)
+ return <Tr key={file.name}>
+ <Td>
+ <Badge>{type}</Badge>
+ </Td>
+ <Td>
+ <Button style={{padding: '6px'}} variant={"link"}
+ onClick={e => onSelect.call(this, file)}>
+ {file.name}
+ </Button>
+ </Td>
+ <Td modifier={"fitContent"}>
+ {file.projectId !== 'templates' &&
+ <Button style={{padding: '0'}} variant={"plain"}
+ isDisabled={file.name === 'application.properties'}
+ onClick={e => onOpenDeleteConfirmation.call(this, file)}>
+ <DeleteIcon/>
+ </Button>
+ }
+ </Td>
+ </Tr>
+ })}
+ {files.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>
+ )
+ }
+}
diff --git a/karavan-app/src/main/webui/src/projects/ProjectModels.ts b/karavan-app/src/main/webui/src/projects/ProjectModels.ts
index 9e8e522..5764869 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectModels.ts
+++ b/karavan-app/src/main/webui/src/projects/ProjectModels.ts
@@ -98,9 +98,20 @@ export class ProjectFileType {
export const ProjectFileTypes: ProjectFileType[] = [
new ProjectFileType("INTEGRATION", "Integration", "camel.yaml"),
+ new ProjectFileType("KAMELET", "Kamelet", "kamelet.yaml"),
new ProjectFileType("CODE", "Code", "java"),
new ProjectFileType("PROPERTIES", "Properties", "properties"),
new ProjectFileType("OPENAPI_JSON", "OpenAPI JSON", "json"),
new ProjectFileType("OPENAPI_YAML", "OpenAPI YAML", "yaml"),
new ProjectFileType("LOG", "Log", "log"),
-];
\ No newline at end of file
+];
+
+
+export function getProjectFileType (file: ProjectFile) {
+ if (file.name.endsWith(".camel.yaml")) return ProjectFileTypes.filter(p => p.name === "INTEGRATION").map(p => p.title)[0];
+ if (file.name.endsWith(".kamelet.yaml")) return ProjectFileTypes.filter(p => p.name === "KAMELET").map(p => p.title)[0];
+ if (file.name.endsWith(".json")) return ProjectFileTypes.filter(p => p.name === "OPENAPI_JSON").map(p => p.title)[0];
+ if (file.name.endsWith(".yaml")) return ProjectFileTypes.filter(p => p.name === "OPENAPI_YAML").map(p => p.title)[0];
+ const extension = file.name.substring(file.name.lastIndexOf('.') + 1);
+ return ProjectFileTypes.filter(p => p.extension === extension).map(p => p.title)[0];
+}
\ No newline at end of file
diff --git a/karavan-app/src/main/webui/src/projects/ProjectPage.tsx b/karavan-app/src/main/webui/src/projects/ProjectPage.tsx
index b748442..8b71766 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectPage.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectPage.tsx
@@ -7,11 +7,6 @@ import {
PageSection,
Text,
TextContent,
- Bullseye,
- EmptyState,
- EmptyStateVariant,
- EmptyStateIcon,
- Title,
ModalVariant,
Modal,
Flex,
@@ -22,18 +17,10 @@ import {
import '../designer/karavan.css';
import {MainToolbar} from "../MainToolbar";
import {KaravanApi} from "../api/KaravanApi";
-import {Project, ProjectFile, ProjectFileTypes} from "./ProjectModels";
-import {CamelUi} from "../designer/utils/CamelUi";
-import UploadIcon from "@patternfly/react-icons/dist/esm/icons/upload-icon";
-import {TableComposable, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
-import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
+import {getProjectFileType, Project, ProjectFile, ProjectFileTypes} from "./ProjectModels";
import {KaravanDesigner} from "../designer/KaravanDesigner";
-import DownloadIcon from "@patternfly/react-icons/dist/esm/icons/download-icon";
-import DownloadImageIcon from "@patternfly/react-icons/dist/esm/icons/image-icon";
import FileSaver from "file-saver";
import Editor from "@monaco-editor/react";
-import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
-import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
import {CreateFileModal} from "./CreateFileModal";
import {PropertiesEditor} from "./PropertiesEditor";
import {ProjectModel, ProjectProperty} from "karavan-core/lib/model/ProjectModel";
@@ -43,12 +30,11 @@ import {UploadModal} from "./UploadModal";
import {ProjectInfo} from "./ProjectInfo";
import {ProjectOperations} from "./ProjectOperations";
import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
-import PushIcon from "@patternfly/react-icons/dist/esm/icons/code-branch-icon";
import {ProjectPageToolbar} from "./ProjectPageToolbar";
+import {ProjectFilesTable} from "./ProjectFilesTable";
interface Props {
project: Project,
- isTemplates?: boolean,
config: any,
}
@@ -100,7 +86,7 @@ export class ProjectPage extends React.Component<Props, State> {
this.setState({files: files})
});
KubernetesAPI.inKubernetes = true;
- if (!this.props.isTemplates){
+ if (!this.isBuildIn()){
KaravanApi.getConfigMaps(this.state.environment, (any: []) => {
KubernetesAPI.setConfigMaps(any);
});
@@ -114,6 +100,10 @@ export class ProjectPage extends React.Component<Props, State> {
}
}
+ isBuildIn():boolean {
+ return ['kamelets', 'templates'].includes(this.props.project.projectId);
+ }
+
post = (file: ProjectFile) => {
KaravanApi.postProjectFile(file, res => {
if (res.status === 200) {
@@ -168,7 +158,7 @@ export class ProjectPage extends React.Component<Props, State> {
project={this.props.project}
file={this.state.file}
mode={this.state.mode}
- isTemplates={this.props.isTemplates}
+ isTemplates={this.isBuildIn()}
config={this.props.config}
addProperty={() => this.addProperty()}
download={() => this.download()}
@@ -180,17 +170,10 @@ export class ProjectPage extends React.Component<Props, State> {
setUploadModalOpen={() => this.setState({isUploadModalOpen: true})}
/>
}
-
- getType = (file: ProjectFile) => {
- if (file.name.endsWith(".camel.yaml")) return ProjectFileTypes.filter(p => p.name === "INTEGRATION").map(p => p.title)[0];
- if (file.name.endsWith(".json")) return ProjectFileTypes.filter(p => p.name === "OPENAPI_JSON").map(p => p.title)[0];
- if (file.name.endsWith(".yaml")) return ProjectFileTypes.filter(p => p.name === "OPENAPI_YAML").map(p => p.title)[0];
- const extension = file.name.substring(file.name.lastIndexOf('.') + 1);
- return ProjectFileTypes.filter(p => p.extension === extension).map(p => p.title)[0];
- }
+
title = () => {
- const {project, isTemplates} = this.props;
+ const {project} = this.props;
const file = this.state.file;
const isFile = file !== undefined;
const isLog = file !== undefined && file.name.endsWith("log");
@@ -202,7 +185,7 @@ export class ProjectPage extends React.Component<Props, State> {
<BreadcrumbItem to="#" onClick={event => this.setState({file: undefined})}>
{"Project: " + project?.projectId}
</BreadcrumbItem>
- <BreadcrumbItem to="#" isActive>{this.getType(file)}</BreadcrumbItem>
+ <Badge>{getProjectFileType(file)}</Badge>
</Breadcrumb>
<TextContent className="title">
<Text component="h1">{isLog ? filename : file.name}</Text>
@@ -210,7 +193,7 @@ export class ProjectPage extends React.Component<Props, State> {
</div>
}
{!isFile && <TextContent className="title">
- <Text component="h2">{isTemplates ? 'Templates' : 'Project: ' + project?.projectId}</Text>
+ <Text component="h2">{project?.name}</Text>
</TextContent>}
</div>)
};
@@ -243,58 +226,6 @@ export class ProjectPage extends React.Component<Props, State> {
}
}
- getProjectFiles = () => {
- const files = this.state.files;
- return (
- <TableComposable aria-label="Files" variant={"compact"} className={"table"}>
- <Thead>
- <Tr>
- <Th key='type' width={10}>Type</Th>
- <Th key='filename' width={50}>Filename</Th>
- <Th key='action'></Th>
- </Tr>
- </Thead>
- <Tbody>
- {files.map(file => {
- const type = this.getType(file)
- return <Tr key={file.name}>
- <Td>
- <Badge>{type}</Badge>
- </Td>
- <Td>
- <Button style={{padding: '6px'}} variant={"link"}
- onClick={e => this.select(file)}>
- {file.name}
- </Button>
- </Td>
- <Td modifier={"fitContent"}>
- <Button style={{padding: '0'}} variant={"plain"}
- isDisabled={file.name === 'application.properties'}
- onClick={e => this.openDeleteConfirmation(file)}>
- <DeleteIcon/>
- </Button>
- </Td>
- </Tr>
- })}
- {files.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>
- )
- }
-
getDesigner = () => {
const file = this.state.file;
return (
@@ -402,43 +333,50 @@ export class ProjectPage extends React.Component<Props, State> {
)
}
- getTemplatePanel() {
- const {tab} = this.state;
+ getProjectPanel() {
+ const isBuildIn = this.isBuildIn();
return (
<Flex direction={{default: "column"}} spaceItems={{default: "spaceItemsNone"}}>
- {/*<FlexItem className="project-tabs">*/}
- {/* <Tabs activeKey={tab} onSelect={(event, tabIndex) => this.setState({tab: tabIndex})}>*/}
- {/* <Tab eventKey="templates" title="Templates"/>*/}
- {/* <Tab eventKey="kamelets" title="Kamelets"/>*/}
- {/* </Tabs>*/}
- {/*</FlexItem>*/}
- <FlexItem>
- <PageSection padding={{default: "padding"}}>
- {this.getProjectFiles()}
- </PageSection>
- </FlexItem>
+ {!isBuildIn && this.getProjectPanelTabs()}
+ {this.getProjectPanelFiles()}
</Flex>
)
}
- getProjectPanel() {
+ getProjectPanelTabs() {
const {tab} = this.state;
return (
- <Flex direction={{default: "column"}} spaceItems={{default: "spaceItemsNone"}}>
- <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>
- </FlexItem>
- <FlexItem>
+ <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>
+ </FlexItem>
+ )
+ }
+
+ getProjectPanelFiles() {
+ const {tab, files} = this.state;
+ const isBuildIn = this.isBuildIn();
+ return (
+ <FlexItem>
+ {isBuildIn &&
+ <PageSection padding={{default: "padding"}}>
+ {tab === 'development' && <ProjectFilesTable files={files}
+ onOpenDeleteConfirmation={this.openDeleteConfirmation}
+ onSelect={this.select}/>}
+ </PageSection>
+ }
+ {!isBuildIn &&
<PageSection padding={{default: "padding"}}>
{tab === 'development' && <ProjectInfo project={this.props.project} config={this.props.config} deleteEntity={this.deleteEntity} showLog={this.showLogs}/>}
- {tab === 'development' && this.getProjectFiles()}
+ {tab === 'development' && <ProjectFilesTable files={files}
+ onOpenDeleteConfirmation={this.openDeleteConfirmation}
+ onSelect={this.select}/>}
{tab === 'operations' && <ProjectOperations environments={this.state.environments} project={this.props.project} config={this.props.config}/>}
</PageSection>
- </FlexItem>
- </Flex>
+ }
+ </FlexItem>
)
}
@@ -462,23 +400,22 @@ export class ProjectPage extends React.Component<Props, State> {
}
render() {
- const {isTemplates} = this.props;
- const {file} = this.state;
+ const {file, isDeleteModalOpen, fileToDelete, isUploadModalOpen, isCreateModalOpen} = this.state;
+ const {project} = this.props;
return (
<PageSection 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 && isTemplates && this.getTemplatePanel()}
- {file === undefined && !isTemplates && this.getProjectPanel()}
+ {file === undefined && this.getProjectPanel()}
{file !== undefined && this.getFilePanel()}
- <CreateFileModal project={this.props.project} isOpen={this.state.isCreateModalOpen}
+ <CreateFileModal project={project} isOpen={isCreateModalOpen}
onClose={this.closeModal}/>
<Modal
title="Confirmation"
variant={ModalVariant.small}
- isOpen={this.state.isDeleteModalOpen}
+ isOpen={isDeleteModalOpen}
onClose={() => this.setState({isDeleteModalOpen: false})}
actions={[
<Button key="confirm" variant="primary" onClick={e => this.delete()}>Delete</Button>,
@@ -486,9 +423,9 @@ export class ProjectPage extends React.Component<Props, State> {
onClick={e => this.setState({isDeleteModalOpen: false})}>Cancel</Button>
]}
onEscapePress={e => this.setState({isDeleteModalOpen: false})}>
- <div>{"Are you sure you want to delete the file " + this.state.fileToDelete?.name + "?"}</div>
+ <div>{"Are you sure you want to delete the file " + fileToDelete?.name + "?"}</div>
</Modal>
- <UploadModal projectId={this.props.project.projectId} isOpen={this.state.isUploadModalOpen} onClose={this.closeModal}/>
+ <UploadModal projectId={project.projectId} isOpen={isUploadModalOpen} onClose={this.closeModal}/>
</PageSection>
)
}
diff --git a/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx b/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
index b3a274e..63f056a 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
@@ -278,6 +278,7 @@ export class ProjectsPage extends React.Component<Props, State> {
config={this.props.config}
onSelect={this.props.onSelect}
onProjectDelete={this.onProjectDelete}
+ onProjectCopy={project1 => this.setState({isCreateModalOpen: true, isCopy: true, projectToCopy: project1})}
project={project}
deploymentStatuses={this.state.deploymentStatuses}/>
))}
diff --git a/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx b/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
index 86723b5..8b8107a 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
@@ -43,40 +43,18 @@ interface Props {
config: any,
onSelect: (project: Project) => void
onProjectDelete: (project: Project) => void
+ onProjectCopy: (project: Project) => void
project: Project
deploymentStatuses: DeploymentStatus[],
}
interface State {
- projects: Project[],
- deploymentStatuses: DeploymentStatus[],
- isCreateModalOpen: boolean,
- isDeleteModalOpen: boolean,
- isCopy: boolean,
- loading: boolean,
- projectToCopy?: Project,
- projectToDelete?: Project,
- filter: string,
- name: string,
- description: string,
- projectId: string,
- runtime: string,
+
}
export class ProjectsTableRow extends React.Component<Props, State> {
public state: State = {
- projects: [],
- deploymentStatuses: [],
- isCreateModalOpen: false,
- isDeleteModalOpen: false,
- isCopy: false,
- loading: true,
- filter: '',
- name: '',
- description: '',
- projectId: '',
- runtime: this.props.config.runtime
};
getEnvironments(): string [] {
@@ -84,7 +62,7 @@ export class ProjectsTableRow extends React.Component<Props, State> {
}
getDeploymentByEnvironments(name: string): [string, DeploymentStatus | undefined] [] {
- const deps = this.state.deploymentStatuses;
+ const deps = this.props.deploymentStatuses;
return this.getEnvironments().map(e => {
const env: string = e as string;
const dep = deps.find(d => d.name === name && d.env === env);
@@ -93,7 +71,7 @@ export class ProjectsTableRow extends React.Component<Props, State> {
}
render() {
- const {project, onProjectDelete, onSelect} = this.props;
+ const {project, onProjectDelete, onSelect, 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 (
@@ -134,7 +112,7 @@ export class ProjectsTableRow extends React.Component<Props, State> {
<OverflowMenuItem>
<Tooltip content={"Copy project"} position={"bottom"}>
<Button variant={"plain"} icon={<CopyIcon/>}
- onClick={e => this.setState({isCreateModalOpen: true, isCopy: true, projectToCopy: project})}></Button>
+ onClick={e => onProjectCopy.call(this, project)}></Button>
</Tooltip>
</OverflowMenuItem>
<OverflowMenuItem>