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 2bc3324a359a0f6d53769d1292307381080e6bb1 Author: Marat Gubaidullin <marat.gubaidul...@gmail.com> AuthorDate: Fri Nov 18 19:43:16 2022 -0500 Templates and Kamelets project list --- .../apache/camel/karavan/api/ProjectResource.java | 10 +- karavan-app/src/main/webui/src/Main.tsx | 9 +- karavan-app/src/main/webui/src/api/KaravanApi.tsx | 11 - .../src/main/webui/src/projects/ProjectsPage.tsx | 74 +---- .../main/webui/src/projects/ProjectsTableRow.tsx | 324 ++++----------------- 5 files changed, 74 insertions(+), 354 deletions(-) diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java index c6620cf..77b2dc6 100644 --- a/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java +++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java @@ -48,9 +48,13 @@ public class ProjectResource { @Produces(MediaType.APPLICATION_JSON) public List<Project> getAll() throws Exception { return infinispanService.getProjects().stream() - .filter(project -> !project.getProjectId().equalsIgnoreCase(Project.NAME_TEMPLATES)) - .filter(project -> !project.getProjectId().equalsIgnoreCase(Project.NAME_KAMELETS)) - .sorted(Comparator.comparing(Project::getProjectId)) + .sorted((p1, p2) -> { + if (p1.getProjectId().equalsIgnoreCase(Project.NAME_TEMPLATES)) return 1; + if (p2.getProjectId().equalsIgnoreCase(Project.NAME_TEMPLATES)) return 1; + if (p1.getProjectId().equalsIgnoreCase(Project.NAME_KAMELETS)) return 1; + if (p2.getProjectId().equalsIgnoreCase(Project.NAME_KAMELETS)) return 1; + return (p1.getProjectId().compareTo(p2.getProjectId())); + }) .collect(Collectors.toList()); } diff --git a/karavan-app/src/main/webui/src/Main.tsx b/karavan-app/src/main/webui/src/Main.tsx index 5975cd2..95ebab2 100644 --- a/karavan-app/src/main/webui/src/Main.tsx +++ b/karavan-app/src/main/webui/src/Main.tsx @@ -66,7 +66,6 @@ interface State { pageId: string, projects: Project[], project?: Project, - templates?: Project, isModalOpen: boolean, projectToDelete?: Project, openapi: string, @@ -125,9 +124,6 @@ export class Main extends React.Component<Props, State> { getData() { KaravanApi.getConfiguration((config: any) => { this.setState({config: config, request: uuidv4()}); - KaravanApi.getTemplatesProject((templates: Project) => { - this.setState({templates: templates}); - }); }); this.updateKamelets(); this.updateComponents(); @@ -167,8 +163,7 @@ export class Main extends React.Component<Props, State> { new MenuItem("projects", "Projects", <ProjectsIcon/>), new MenuItem("eip", "Enterprise Integration Patterns", <EipIcon/>), new MenuItem("kamelets", "Kamelets", <KameletsIcon/>), - new MenuItem("components", "Components", <ComponentsIcon/>), - new MenuItem("templates", "Templates", <PficonTemplateIcon/>) + new MenuItem("components", "Components", <ComponentsIcon/>) ] return (<Flex className="nav-buttons" direction={{default: "column"}} style={{height: "100%"}} spaceItems={{default: "spaceItemsNone"}}> @@ -246,8 +241,6 @@ export class Main extends React.Component<Props, State> { onSelect={this.onProjectSelect} toast={this.toast} config={this.state.config}/>} - {this.state.pageId === 'templates' && this.state.templates && - <ProjectPage key="templates" project={this.state.templates} isTemplates={true} config={this.state.config}/>} {this.state.pageId === 'kamelets' && <KameletsPage dark={false} onRefresh={this.updateKamelets}/>} {this.state.pageId === 'components' && diff --git a/karavan-app/src/main/webui/src/api/KaravanApi.tsx b/karavan-app/src/main/webui/src/api/KaravanApi.tsx index c446efb..78feec2 100644 --- a/karavan-app/src/main/webui/src/api/KaravanApi.tsx +++ b/karavan-app/src/main/webui/src/api/KaravanApi.tsx @@ -277,17 +277,6 @@ export class KaravanApi { }); } - static async getTemplatesProject( after: (project: Project) => void) { - instance.get('/api/template') - .then(res => { - if (res.status === 200) { - after(res.data); - } - }).catch(err => { - console.log(err); - }); - } - static async getTemplatesFiles( after: (files: []) => void) { instance.get('/api/template/files') .then(res => { diff --git a/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx b/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx index fbb23d9..b3a274e 100644 --- a/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx +++ b/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx @@ -12,18 +12,12 @@ import { FormGroup, ModalVariant, Form, - Badge, - Tooltip, Bullseye, EmptyState, EmptyStateVariant, EmptyStateIcon, Title, - OverflowMenu, - OverflowMenuContent, - OverflowMenuGroup, - OverflowMenuItem, - Flex, FlexItem, Radio, Spinner + Radio, Spinner } from '@patternfly/react-core'; import '../designer/karavan.css'; import {MainToolbar} from "../MainToolbar"; @@ -31,13 +25,12 @@ 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} 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 CopyIcon from "@patternfly/react-icons/dist/esm/icons/copy-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"; interface Props { config: any, @@ -240,14 +233,6 @@ export class ProjectsPage extends React.Component<Props, State> { return this.props.config.environments && Array.isArray(this.props.config.environments) ? Array.from(this.props.config.environments) : []; } - getDeploymentByEnvironments(name: string): [string, DeploymentStatus | undefined] [] { - const deps = this.state.deploymentStatuses; - return this.getEnvironments().map(e => { - const env: string = e as string; - const dep = deps.find(d => d.name === name && d.env === env); - return [env, dep]; - }); - } getEmptyState() { const {loading} = this.state; @@ -270,6 +255,7 @@ export class ProjectsPage extends React.Component<Props, State> { ) } + getProjectsTable() { const {projects, filter} = this.state; const projs = projects.filter(p => p.name.toLowerCase().includes(filter) || p.description.toLowerCase().includes(filter)); @@ -288,53 +274,12 @@ export class ProjectsPage extends React.Component<Props, State> { </Thead> <Tbody> {projs.map(project => ( - <Tr key={project.projectId}> - <Td modifier={"fitContent"}> - <Tooltip content={project.runtime} position={"left"}> - <Badge className="runtime-badge">{project.runtime.substring(0, 1).toUpperCase()}</Badge> - </Tooltip> - </Td> - <Td> - <Button style={{padding: '6px'}} variant={"link"} onClick={e => this.props.onSelect?.call(this, 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"}}> - <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 isActionCell> - <OverflowMenu breakpoint="md"> - <OverflowMenuContent> - <OverflowMenuGroup groupType="button"> - <OverflowMenuItem> - <Tooltip content={"Copy project"} position={"bottom"}> - <Button variant={"plain"} icon={<CopyIcon/>} - onClick={e => this.setState({isCreateModalOpen: true, isCopy: true, projectToCopy: project})}></Button> - </Tooltip> - </OverflowMenuItem> - <OverflowMenuItem> - <Tooltip content={"Delete project"} position={"bottom"}> - <Button variant={"plain"} icon={<DeleteIcon/>} onClick={e => this.onProjectDelete(project)}></Button> - </Tooltip> - </OverflowMenuItem> - </OverflowMenuGroup> - </OverflowMenuContent> - </OverflowMenu> - </Td> - </Tr> + <ProjectsTableRow + config={this.props.config} + onSelect={this.props.onSelect} + onProjectDelete={this.onProjectDelete} + project={project} + deploymentStatuses={this.state.deploymentStatuses}/> ))} {projs.length === 0 && this.getEmptyState()} </Tbody> @@ -343,7 +288,6 @@ export class ProjectsPage extends React.Component<Props, State> { } render() { - const projects = this.state.projects.filter(p => p.name.toLowerCase().includes(this.state.filter) || p.description.toLowerCase().includes(this.state.filter)); return ( <PageSection className="kamelet-section projects-page" padding={{default: 'noPadding'}}> <PageSection className="tools-section" padding={{default: 'noPadding'}}> diff --git a/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx b/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx index a3be55d..86723b5 100644 --- a/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx +++ b/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx @@ -42,7 +42,9 @@ import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; interface Props { config: any, onSelect: (project: Project) => void - toast: (title: string, text: string, variant: 'success' | 'danger' | 'warning' | 'info' | 'default') => void + onProjectDelete: (project: Project) => void + project: Project + deploymentStatuses: DeploymentStatus[], } interface State { @@ -61,7 +63,7 @@ interface State { runtime: string, } -export class ProjectsPage extends React.Component<Props, State> { +export class ProjectsTableRow extends React.Component<Props, State> { public state: State = { projects: [], @@ -76,165 +78,6 @@ export class ProjectsPage extends React.Component<Props, State> { projectId: '', runtime: this.props.config.runtime }; - interval: any; - - componentDidMount() { - this.interval = setInterval(() => this.onGetProjects(), 1300); - } - - componentWillUnmount() { - clearInterval(this.interval); - } - - onProjectDelete = (project: Project) => { - this.setState({isDeleteModalOpen: 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"> - {runtime === 'quarkus' ? QuarkusIcon() : SpringIcon()} - <div className="runtime-label">{CamelUtil.capitalizeName(r)}</div> - </div>} - /> - ))} - </FormGroup> - </Form> - </Modal> - ) - } - - deleteModalForm() { - return ( - <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> - ) - } getEnvironments(): string [] { return this.props.config.environments && Array.isArray(this.props.config.environments) ? Array.from(this.props.config.environments) : []; @@ -249,115 +92,62 @@ export class ProjectsPage extends React.Component<Props, State> { }); } - getEmptyState() { - const {loading} = this.state; + render() { + const {project, onProjectDelete, onSelect} = 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> - <Td colSpan={8}> - <Bullseye> - {loading && <Spinner className="progress-stepper" isSVG diameter="80px" aria-label="Loading..."/>} - {!loading && - <EmptyState variant={EmptyStateVariant.small}> - <EmptyStateIcon icon={SearchIcon}/> - <Title headingLevel="h2" size="lg"> - No results found - </Title> - </EmptyState> - } - </Bullseye> + <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 => onSelect?.call(this, 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 isActionCell> + {!isBuildIn && + <OverflowMenu breakpoint="md"> + <OverflowMenuContent> + <OverflowMenuGroup groupType="button"> + <OverflowMenuItem> + <Tooltip content={"Copy project"} position={"bottom"}> + <Button variant={"plain"} icon={<CopyIcon/>} + onClick={e => this.setState({isCreateModalOpen: true, isCopy: true, projectToCopy: 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> ) } - - isBuildIn(project: Project): boolean { - return ['kamelets', 'templates'].includes(project.projectId); - } - - getProjectsTable() { - const {projects, filter} = this.state; - const projs = projects.filter(p => p.name.toLowerCase().includes(filter) || p.description.toLowerCase().includes(filter)); - return ( - <TableComposable aria-label="Projects" variant={"compact"}> - <Thead> - <Tr> - <Th key='type'>Runtime</Th> - <Th key='projectId'>Project ID</Th> - <Th key='name'>Name</Th> - <Th key='description'>Description</Th> - <Th key='commit'>Commit</Th> - <Th key='deployment'>Environment</Th> - <Th key='action'></Th> - </Tr> - </Thead> - <Tbody> - {projs.map(project => ( - <Tr key={project.projectId}> - <Td modifier={"fitContent"}> - <Tooltip content={project.runtime} position={"left"}> - <Badge className="runtime-badge">{project.runtime.substring(0, 1).toUpperCase()}</Badge> - </Tooltip> - </Td> - <Td> - <Button style={{padding: '6px'}} variant={"link"} onClick={e => this.props.onSelect?.call(this, 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"}}> - <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 isActionCell> - <OverflowMenu breakpoint="md"> - <OverflowMenuContent> - <OverflowMenuGroup groupType="button"> - <OverflowMenuItem> - <Tooltip content={"Copy project"} position={"bottom"}> - <Button variant={"plain"} icon={<CopyIcon/>} - onClick={e => this.setState({isCreateModalOpen: true, isCopy: true, projectToCopy: project})}></Button> - </Tooltip> - </OverflowMenuItem> - <OverflowMenuItem> - <Tooltip content={"Delete project"} position={"bottom"}> - <Button variant={"plain"} icon={<DeleteIcon/>} onClick={e => this.onProjectDelete(project)}></Button> - </Tooltip> - </OverflowMenuItem> - </OverflowMenuGroup> - </OverflowMenuContent> - </OverflowMenu> - </Td> - </Tr> - ))} - {projs.length === 0 && this.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()} - </PageSection> - ) - } } \ No newline at end of file