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 <marat.gubaidul...@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>