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 adac8323 Fix #951 adac8323 is described below commit adac832308195e5bd8364ee5238c7474f3e51c7d Author: Marat Gubaidullin <ma...@talismancloud.io> AuthorDate: Wed Dec 27 12:12:20 2023 -0500 Fix #951 --- .../apache/camel/karavan/api/ProjectResource.java | 25 ++++- .../src/main/webui/src/api/KaravanApi.tsx | 5 +- .../src/main/webui/src/api/ProjectService.ts | 4 +- .../main/webui/src/projects/DeleteProjectModal.tsx | 30 ++--- .../webui/src/templates/CreateProjectModal.tsx | 122 --------------------- .../webui/src/templates/DeleteProjectModal.tsx | 70 ------------ .../src/main/webui/src/templates/TemplatesPage.tsx | 9 +- 7 files changed, 39 insertions(+), 226 deletions(-) diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java index 6acc57f5..29541520 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ProjectResource.java @@ -25,6 +25,7 @@ import org.apache.camel.karavan.git.GitService; import org.apache.camel.karavan.infinispan.InfinispanService; import org.apache.camel.karavan.infinispan.model.CamelStatus; import org.apache.camel.karavan.infinispan.model.CamelStatusValue; +import org.apache.camel.karavan.infinispan.model.ContainerStatus; import org.apache.camel.karavan.infinispan.model.Project; import org.apache.camel.karavan.kubernetes.KubernetesService; import org.apache.camel.karavan.service.ConfigService; @@ -53,6 +54,15 @@ public class ProjectResource { @Inject GitService gitService; + @Inject + DevModeResource devModeResource; + + @Inject + ContainerResource containerResource; + + @Inject + InfrastructureResource infrastructureResource; + @Inject ProjectService projectService; @@ -88,12 +98,20 @@ public class ProjectResource { @DELETE @Produces(MediaType.APPLICATION_JSON) @Path("/{project}") - public void delete(@HeaderParam("username") String username, - @PathParam("project") String project) throws Exception { + public void delete(@PathParam("project") String project, @QueryParam("deleteContainers") boolean deleteContainers) throws Exception { String projectId = URLDecoder.decode(project, StandardCharsets.UTF_8); + if (deleteContainers) { + LOGGER.info("Deleting containers"); + Response res1 = devModeResource.deleteDevMode(projectId, true); + Response res2 = containerResource.deleteContainer(projectId, ContainerStatus.ContainerType.devmode.name(), projectId); + Response res3 = containerResource.deleteContainer(projectId, ContainerStatus.ContainerType.project.name(), projectId); + LOGGER.info("Deleting deployments"); + Response res4 = infrastructureResource.deleteDeployment(null, projectId); + } gitService.deleteProject(projectId, infinispanService.getProjectFiles(projectId)); infinispanService.getProjectFiles(projectId).forEach(file -> infinispanService.deleteProjectFile(projectId, file.getName())); infinispanService.deleteProject(projectId); + LOGGER.info("Project deleted"); } @POST @@ -113,8 +131,7 @@ public class ProjectResource { @DELETE @Produces(MediaType.APPLICATION_JSON) @Path("/build/{env}/{buildName}") - public Response deleteBuild(@HeaderParam("username") String username, - @PathParam("env") String env, @PathParam("buildName") String buildName) { + public Response deleteBuild(@PathParam("env") String env, @PathParam("buildName") String buildName) { buildName = URLDecoder.decode(buildName, StandardCharsets.UTF_8); if (ConfigService.inKubernetes()) { kubernetesService.deletePod(buildName); diff --git a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx index 0d07f0da..70d34a60 100644 --- a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx @@ -254,9 +254,8 @@ export class KaravanApi { }); } - static async deleteProject(project: Project, after: (res: AxiosResponse<any>) => void) { - instance.delete('/api/project/' + encodeURI(project.projectId), - {headers: {'username': 'cameleer'}}) + static async deleteProject(project: Project, deleteContainers: boolean, after: (res: AxiosResponse<any>) => void) { + instance.delete('/api/project/' + encodeURI(project.projectId) + (deleteContainers ? '?deleteContainers=true' : '')) .then(res => { after(res); }).catch(err => { diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts index 9f64e7f0..6f567937 100644 --- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts +++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectService.ts @@ -225,8 +225,8 @@ export class ProjectService { }); } - public static deleteProject(project: Project) { - KaravanApi.deleteProject(project, res => { + public static deleteProject(project: Project, deleteContainers?: boolean) { + KaravanApi.deleteProject(project, deleteContainers === true, res => { if (res.status === 204) { EventBus.sendAlert( 'Success', 'Project deleted', 'success'); ProjectService.refreshProjects(); diff --git a/karavan-web/karavan-app/src/main/webui/src/projects/DeleteProjectModal.tsx b/karavan-web/karavan-app/src/main/webui/src/projects/DeleteProjectModal.tsx index 02bda7b1..574486cb 100644 --- a/karavan-web/karavan-app/src/main/webui/src/projects/DeleteProjectModal.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/projects/DeleteProjectModal.tsx @@ -15,11 +15,11 @@ * limitations under the License. */ -import React from 'react'; +import React, {useState} from 'react'; import { Button, Modal, - ModalVariant, + ModalVariant, Switch, } from '@patternfly/react-core'; import '../designer/karavan.css'; import {useProjectStore} from "../api/ProjectStore"; @@ -28,20 +28,21 @@ import {ProjectService} from "../api/ProjectService"; export function DeleteProjectModal () { const {project, operation} = useProjectStore(); + const [deleteContainers, setDeleteContainers] = useState(false); function closeModal () { useProjectStore.setState({operation: "none"}) } function confirmAndCloseModal () { - ProjectService.deleteProject(project); + ProjectService.deleteProject(project, deleteContainers); useProjectStore.setState({operation: "none"}); } const isOpen= operation === "delete"; return ( <Modal - title="Confirmation" + title="Project delete confirmation" variant={ModalVariant.small} isOpen={isOpen} onClose={() => closeModal()} @@ -51,20 +52,13 @@ export function DeleteProjectModal () { onClick={e => closeModal()}>Cancel</Button> ]} onEscapePress={e => closeModal()}> - <div>{"Are you sure you want to delete the project " + project?.projectId + "?"}</div> + {/*<div>{"Are you sure you want to delete the project " + project?.projectId + "?"}</div>*/} + <Switch + label={"Delete container and/or deployments?"} + isChecked={deleteContainers} + onChange={(_, checked) => setDeleteContainers(checked)} + isReversed + /> </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-web/karavan-app/src/main/webui/src/templates/CreateProjectModal.tsx b/karavan-web/karavan-app/src/main/webui/src/templates/CreateProjectModal.tsx deleted file mode 100644 index b6300be9..00000000 --- a/karavan-web/karavan-app/src/main/webui/src/templates/CreateProjectModal.tsx +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, {useState} from 'react'; -import { - Button, Form, FormGroup, FormHelperText, HelperText, HelperTextItem, - Modal, - ModalVariant, TextInput, Text -} from '@patternfly/react-core'; -import '../designer/karavan.css'; -import {useProjectStore} from "../api/ProjectStore"; -import {ProjectService} from "../api/ProjectService"; -import {Project} from "../api/ProjectModels"; -import {CamelUi} from "../designer/utils/CamelUi"; -import {EventBus} from "../designer/utils/EventBus"; -import {ProjectExistsError} from "../shared/error/ProjectExistsError"; - - -export function CreateProjectModal () { - - const {project, operation} = useProjectStore(); - const [name, setName] = useState(''); - const [description, setDescription] = useState(''); - const [projectId, setProjectId] = useState(''); - const [isValidationError, setIsValidationError] = useState(false); - - function cleanValues() { - setName(""); - setDescription(""); - setProjectId(""); - } - - function closeModal() { - useProjectStore.setState({operation: "none"}); - cleanValues(); - } - - async function handleFormSubmit() { - setIsValidationError(false); - const [ err, createdProject ] = operation !== 'copy' ? - await ProjectService.createProject(new Project({name: name, description: description, projectId: projectId})) : - await ProjectService.copyProject(project?.projectId, new Project({name: name, description: description, projectId: projectId})); - - if (createdProject !== null) { - EventBus.sendAlert( 'Success', 'Project created', 'success'); - ProjectService.refreshProjectData(project.projectId); - ProjectService.refreshProjects(); - useProjectStore.setState({operation: "none"}); - cleanValues(); - } else if (err !== null && err instanceof ProjectExistsError) { - setIsValidationError(true); - } else { - operation !== 'copy' ? - EventBus.sendAlert( 'Warning', 'Error when creating project:' + err?.message, 'warning') : - EventBus.sendAlert( 'Warning', 'Error when copying project:' + err?.message, 'warning'); - } - } - - function onKeyDown(event: React.KeyboardEvent<HTMLDivElement>): void { - if (event.key === 'Enter' && name !== undefined && description !== undefined && projectId !== undefined) { - handleFormSubmit(); - } - } - - 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={handleFormSubmit}>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> - <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))} - validated={isValidationError ? 'error' : 'default'} - /> - {isValidationError && <Text style={{ color: 'red', fontStyle: 'italic'}}>Project ID must be unique</Text>} - <FormHelperText> - <HelperText> - <HelperTextItem>Unique project name</HelperTextItem> - </HelperText> - </FormHelperText> - </FormGroup> - </Form> - </Modal> - ) -} \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/webui/src/templates/DeleteProjectModal.tsx b/karavan-web/karavan-app/src/main/webui/src/templates/DeleteProjectModal.tsx deleted file mode 100644 index 02bda7b1..00000000 --- a/karavan-web/karavan-app/src/main/webui/src/templates/DeleteProjectModal.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { - Button, - Modal, - ModalVariant, -} from '@patternfly/react-core'; -import '../designer/karavan.css'; -import {useProjectStore} from "../api/ProjectStore"; -import {ProjectService} from "../api/ProjectService"; - -export function DeleteProjectModal () { - - const {project, operation} = useProjectStore(); - - function closeModal () { - useProjectStore.setState({operation: "none"}) - } - - function confirmAndCloseModal () { - ProjectService.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-web/karavan-app/src/main/webui/src/templates/TemplatesPage.tsx b/karavan-web/karavan-app/src/main/webui/src/templates/TemplatesPage.tsx index ee6c7063..96588eab 100644 --- a/karavan-web/karavan-app/src/main/webui/src/templates/TemplatesPage.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/templates/TemplatesPage.tsx @@ -45,17 +45,14 @@ import { } from '@patternfly/react-table/deprecated'; import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon'; import {TemplatesTableRow} from "./TemplatesTableRow"; -import {DeleteProjectModal} from "./DeleteProjectModal"; -import {CreateProjectModal} from "./CreateProjectModal"; -import {useProjectsStore, useProjectStore} from "../api/ProjectStore"; +import {useProjectsStore} from "../api/ProjectStore"; import {MainToolbar} from "../designer/MainToolbar"; -import {Project, ProjectType} from "../api/ProjectModels"; +import {ProjectType} from "../api/ProjectModels"; import {shallow} from "zustand/shallow"; export function TemplatesPage () { const [projects] = useProjectsStore((state) => [state.projects], shallow) - const [operation] = useProjectStore((state) => [state.operation], shallow) const [filter, setFilter] = useState<string>(''); function getTools() { @@ -126,8 +123,6 @@ export function TemplatesPage () { <PageSection isFilled className="kamelets-page"> {getProjectsTable()} </PageSection> - {["create", "copy"].includes(operation) && <CreateProjectModal/>} - {["delete"].includes(operation) && <DeleteProjectModal/>} </PageSection> )