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 0be5a10e #973 - Don't copy project if existing project-id is used (#992) 0be5a10e is described below commit 0be5a10ed26c5dc2da6e3e14385c95961856a0f7 Author: Mario Volf <mv...@users.noreply.github.com> AuthorDate: Fri Dec 8 16:17:47 2023 +0100 #973 - Don't copy project if existing project-id is used (#992) * mvolf - web - Implement copy project functionality * mvolf - web - Implement copy project functionality * #973 - Copy project * #973 - Copy project * #973 - Don't copy project if existing project-id is used. * #973 - Fixed code formatting * #973 - Handle duplicate project id on project create and copy. Some additional small modifications and fixes. --------- Co-authored-by: mvolf <mario.v...@lateral-thinking.co> --- karavan-designer/src/designer/utils/CamelUi.tsx | 2 +- karavan-space/src/designer/utils/CamelUi.tsx | 2 +- .../apache/camel/karavan/api/ProjectResource.java | 27 ++++++++++++--- .../camel/karavan/service/ProjectService.java | 32 ++++++++++++------ .../shared/exception/ProjectExistsException.java | 7 ++++ .../src/main/webui/src/api/KaravanApi.tsx | 39 ++++++++++++++++------ .../src/main/webui/src/api/ProjectService.ts | 23 +++---------- .../src/main/webui/src/designer/utils/CamelUi.tsx | 2 +- .../main/webui/src/projects/CreateProjectModal.tsx | 39 ++++++++++++++++------ .../main/webui/src/services/CreateServiceModal.tsx | 34 ++++++++++++++----- .../webui/src/shared/error/ProjectExistsError.ts | 6 ++++ .../webui/src/templates/CreateProjectModal.tsx | 39 ++++++++++++++++------ .../src/main/webui/src/util/StringUtils.ts | 2 +- 13 files changed, 176 insertions(+), 78 deletions(-) diff --git a/karavan-designer/src/designer/utils/CamelUi.tsx b/karavan-designer/src/designer/utils/CamelUi.tsx index 318c729a..c9090e18 100644 --- a/karavan-designer/src/designer/utils/CamelUi.tsx +++ b/karavan-designer/src/designer/utils/CamelUi.tsx @@ -303,7 +303,7 @@ export class CamelUi { } static nameFromTitle = (title: string): string => { - return title.replace(/[^a-z0-9+]+/gi, "-").toLowerCase(); + return title.trim().replace(/[^a-z0-9+]+/gi, "-").toLowerCase(); } static javaNameFromTitle = (title: string): string => { diff --git a/karavan-space/src/designer/utils/CamelUi.tsx b/karavan-space/src/designer/utils/CamelUi.tsx index 318c729a..c9090e18 100644 --- a/karavan-space/src/designer/utils/CamelUi.tsx +++ b/karavan-space/src/designer/utils/CamelUi.tsx @@ -303,7 +303,7 @@ export class CamelUi { } static nameFromTitle = (title: string): string => { - return title.replace(/[^a-z0-9+]+/gi, "-").toLowerCase(); + return title.trim().replace(/[^a-z0-9+]+/gi, "-").toLowerCase(); } static javaNameFromTitle = (title: string): string => { 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 52cfe36e..6acc57f5 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 @@ -29,6 +29,7 @@ import org.apache.camel.karavan.infinispan.model.Project; import org.apache.camel.karavan.kubernetes.KubernetesService; import org.apache.camel.karavan.service.ConfigService; import org.apache.camel.karavan.service.ProjectService; +import org.apache.camel.karavan.shared.exception.ProjectExistsException; import org.jboss.logging.Logger; import java.net.URLDecoder; @@ -71,8 +72,17 @@ public class ProjectResource { @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) - public Project save(Project project) throws Exception { - return projectService.save(project); + public Response save(Project project) throws Exception { + try { + Project createdProject = projectService.save(project); + return Response.ok().entity(createdProject).build(); + } catch (ProjectExistsException exception) { + LOGGER.error(exception.getMessage()); + return Response.status(Response.Status.CONFLICT).entity(exception.getMessage()).build(); + } catch (Exception exception) { + LOGGER.error(exception.getMessage()); + return Response.serverError().entity(exception.getMessage()).build(); + } } @DELETE @@ -153,7 +163,16 @@ public class ProjectResource { @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @Path("/copy/{sourceProject}") - public Project copy(@PathParam("sourceProject") String sourceProject, Project project) throws Exception { - return projectService.copy(sourceProject, project); + public Response copy(@PathParam("sourceProject") String sourceProject, Project project) throws Exception { + try { + Project copiedProject = projectService.copy(sourceProject, project); + return Response.ok().entity(copiedProject).build(); + } catch (ProjectExistsException exception) { + LOGGER.error(exception.getMessage()); + return Response.status(Response.Status.CONFLICT).entity(exception.getMessage()).build(); + } catch (Exception exception) { + LOGGER.error(exception.getMessage()); + return Response.serverError().entity(exception.getMessage()).build(); + } } } \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java index eeeb6c08..3634fee3 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java @@ -35,6 +35,7 @@ import org.apache.camel.karavan.infinispan.model.ProjectFile; import org.apache.camel.karavan.kubernetes.KubernetesService; import org.apache.camel.karavan.registry.RegistryService; import org.apache.camel.karavan.shared.Property; +import org.apache.camel.karavan.shared.exception.ProjectExistsException; import org.apache.commons.lang3.StringUtils; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.microprofile.config.inject.ConfigProperty; @@ -168,24 +169,33 @@ public class ProjectService implements HealthCheck { } public Project save(Project project) throws Exception { - boolean isNew = infinispanService.getProject(project.getProjectId()) == null; + boolean projectExists = infinispanService.getProject(project.getProjectId()) != null; + if(projectExists) { + throw new ProjectExistsException("Project with project id [" + project.getProjectId() + "] already exists"); + } + infinispanService.saveProject(project); - if (isNew) { - ProjectFile appProp = codeService.getApplicationProperties(project); - infinispanService.saveProjectFile(appProp); - if (!ConfigService.inKubernetes()) { - ProjectFile projectCompose = codeService.createInitialProjectCompose(project); - infinispanService.saveProjectFile(projectCompose); - } else if (kubernetesService.isOpenshift()){ - ProjectFile projectCompose = codeService.createInitialDeployment(project); - infinispanService.saveProjectFile(projectCompose); - } + + ProjectFile appProp = codeService.getApplicationProperties(project); + infinispanService.saveProjectFile(appProp); + if (!ConfigService.inKubernetes()) { + ProjectFile projectCompose = codeService.createInitialProjectCompose(project); + infinispanService.saveProjectFile(projectCompose); + } else if (kubernetesService.isOpenshift()){ + ProjectFile projectCompose = codeService.createInitialDeployment(project); + infinispanService.saveProjectFile(projectCompose); } + return project; } public Project copy(String sourceProjectId, Project project) throws Exception { + boolean projectExists = infinispanService.getProject(project.getProjectId()) != null; + if(projectExists) { + throw new ProjectExistsException("Project with project id [" + project.getProjectId() + "] already exists"); + } Project sourceProject = infinispanService.getProject(sourceProjectId); + // Save project infinispanService.saveProject(project); diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/exception/ProjectExistsException.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/exception/ProjectExistsException.java new file mode 100644 index 00000000..478028bb --- /dev/null +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/exception/ProjectExistsException.java @@ -0,0 +1,7 @@ +package org.apache.camel.karavan.shared.exception; + +public class ProjectExistsException extends RuntimeException { + public ProjectExistsException(String message) { + super(message); + } +} 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 00b3441f..5c01bd6f 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 @@ -15,7 +15,7 @@ * limitations under the License. */ -import axios, {AxiosResponse } from "axios"; +import axios, {AxiosResponse} from "axios"; import { AppConfig, CamelStatus, @@ -28,6 +28,7 @@ import {Buffer} from 'buffer'; import {SsoApi} from "./SsoApi"; import {EventStreamContentType, fetchEventSource} from "@microsoft/fetch-event-source"; import {ProjectEventBus} from "./ProjectEventBus"; +import {ProjectExistsError} from "../shared/error/ProjectExistsError"; axios.defaults.headers.common['Accept'] = 'application/json'; axios.defaults.headers.common['Content-Type'] = 'application/json'; @@ -219,22 +220,38 @@ export class KaravanApi { }); } - static async postProject(project: Project, after: (res: AxiosResponse<any>) => void) { - instance.post('/api/project', project) + static async postProject(project: Project): Promise<[Error | null, Project | null]> { + return instance.post('/api/project', project) .then(res => { - after(res); + if(res.status === 200 || res.status === 201) { + return [null, res.data as Project] as [null, Project] + } else { + return [Error("Error while creating project"), null] as [Error, null] + } }).catch(err => { - after(err); - }); + if(err.response?.status === 409) { + return [new ProjectExistsError("Project with id " + project.projectId + " already exists."), null] as [Error, null] + } else { + return [err as Error, null] as [Error, null]; + } + }); } - static async copyProject(sourceProject: string, project: Project, after: (res: AxiosResponse<any>) => void) { - instance.post('/api/project/copy/' + sourceProject, project) + static async copyProject(sourceProject: string, project: Project): Promise<[Error | null, Project | null]> { + return instance.post('/api/project/copy/' + sourceProject, project) .then(res => { - after(res); + if(res.status === 200 || res.status === 201) { + return [null, res.data as Project] as [null, Project] + } else { + return [Error("Error while copying project"), null] as [Error, null] + } }).catch(err => { - after(err); - }); + if(err.response?.status === 409) { + return [new ProjectExistsError("Project with id " + project.projectId + " already exists."), null] as [Error, null] + } else { + return [err as Error, null] as [Error, null]; + } + }); } static async deleteProject(project: Project, after: (res: AxiosResponse<any>) => void) { 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 66f9c88f..9f64e7f0 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 @@ -236,27 +236,12 @@ export class ProjectService { }); } - public static createProject(project: Project) { - KaravanApi.postProject(project, res => { - if (res.status === 200 || res.status === 201) { - ProjectService.refreshProjectData(project.projectId); - ProjectService.refreshProjects(); - // this.props.toast?.call(this, 'Success', 'Project created', 'success'); - } else { - // this.props.toast?.call(this, 'Error', res.status + ', ' + res.statusText, 'danger'); - } - }); + public static async createProject(project: Project) { + return KaravanApi.postProject(project); } - public static copyProject(sourceProject: string, project: Project) { - KaravanApi.copyProject(sourceProject, project, res => { - if (res.status === 200 || res.status === 201) { - EventBus.sendAlert( 'Success', 'Project copied', 'success'); - ProjectService.refreshProjects(); - } else { - EventBus.sendAlert( 'Warning', 'Error when copying project:' + res.statusText, 'warning'); - } - }); + public static async copyProject(sourceProject: string, project: Project) { + return KaravanApi.copyProject(sourceProject, project); } public static createFile(file: ProjectFile) { diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx index 318c729a..c9090e18 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/utils/CamelUi.tsx @@ -303,7 +303,7 @@ export class CamelUi { } static nameFromTitle = (title: string): string => { - return title.replace(/[^a-z0-9+]+/gi, "-").toLowerCase(); + return title.trim().replace(/[^a-z0-9+]+/gi, "-").toLowerCase(); } static javaNameFromTitle = (title: string): string => { diff --git a/karavan-web/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx b/karavan-web/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx index d35b9fdc..b92a2218 100644 --- a/karavan-web/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/projects/CreateProjectModal.tsx @@ -19,7 +19,7 @@ import React, {useState} from 'react'; import { Button, Form, FormGroup, FormHelperText, HelperText, HelperTextItem, Modal, - ModalVariant, TextInput + ModalVariant, TextInput, Text } from '@patternfly/react-core'; import '../designer/karavan.css'; import {useProjectStore} from "../api/ProjectStore"; @@ -28,6 +28,8 @@ import {Project} from "../api/ProjectModels"; import {CamelUi} from "../designer/utils/CamelUi"; import {shallow} from "zustand/shallow"; import {isEmpty} from "../util/StringUtils"; +import {EventBus} from "../designer/utils/EventBus"; +import {ProjectExistsError} from "../shared/error/ProjectExistsError"; export function CreateProjectModal () { @@ -35,6 +37,7 @@ export function CreateProjectModal () { const [name, setName] = useState(''); const [description, setDescription] = useState(''); const [projectId, setProjectId] = useState(''); + const [isValidationError, setIsValidationError] = useState(false); function cleanValues() { setName(""); @@ -47,17 +50,30 @@ export function CreateProjectModal () { cleanValues(); } - function confirmAndCloseModal() { - operation !== 'copy' ? - ProjectService.createProject(new Project({name: name, description: description, projectId: projectId})) : - ProjectService.copyProject(project?.projectId, new Project({name: name, description: description, projectId: projectId})); - setOperation('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(); + setOperation('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' && !isEmpty(name) && !isEmpty(description) && !isEmpty(projectId)) { - confirmAndCloseModal(); + handleFormSubmit(); } } @@ -71,7 +87,7 @@ export function CreateProjectModal () { onKeyDown={onKeyDown} actions={[ <Button key="confirm" variant="primary" isDisabled={!isReady} - onClick={confirmAndCloseModal}>Save</Button>, + onClick={handleFormSubmit}>Save</Button>, <Button key="cancel" variant="secondary" onClick={closeModal}>Cancel</Button> ]} className="new-project" @@ -91,7 +107,10 @@ export function CreateProjectModal () { <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))}/> + 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> diff --git a/karavan-web/karavan-app/src/main/webui/src/services/CreateServiceModal.tsx b/karavan-web/karavan-app/src/main/webui/src/services/CreateServiceModal.tsx index 80f34c5e..38977138 100644 --- a/karavan-web/karavan-app/src/main/webui/src/services/CreateServiceModal.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/services/CreateServiceModal.tsx @@ -19,14 +19,15 @@ import React, {useState} from 'react'; import { Button, Form, FormGroup, FormHelperText, HelperText, HelperTextItem, Modal, - ModalVariant, Radio, TextInput, + ModalVariant, TextInput, Text } from '@patternfly/react-core'; import '../designer/karavan.css'; -import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; import {useAppConfigStore, 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 CreateServiceModal () { @@ -37,6 +38,7 @@ export function CreateServiceModal () { const [runtime, setRuntime] = useState(''); const [projectId, setProjectId] = useState(''); const {config} = useAppConfigStore(); + const [isValidationError, setIsValidationError] = useState(false); function cleanValues() { setName(""); @@ -50,15 +52,26 @@ export function CreateServiceModal () { cleanValues(); } - function confirmAndCloseModal () { - ProjectService.createProject(new Project({name: name, description: description, projectId: projectId})); - useProjectStore.setState({operation: "none"}); - cleanValues(); + async function handleFormSubmit () { + setIsValidationError(false); + const [ err, createdProject ] = await ProjectService.createProject(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 { + EventBus.sendAlert( 'Warning', 'Error when creating project:' + err?.message, 'warning'); + } } function onKeyDown (event: React.KeyboardEvent<HTMLDivElement>): void { if (event.key === 'Enter' && name !== undefined && description !== undefined && projectId !== undefined) { - confirmAndCloseModal(); + handleFormSubmit(); } } @@ -72,7 +85,7 @@ export function CreateServiceModal () { onKeyDown={onKeyDown} actions={[ <Button key="confirm" variant="primary" isDisabled={!isReady} - onClick={confirmAndCloseModal}>Save</Button>, + onClick={handleFormSubmit}>Save</Button>, <Button key="cancel" variant="secondary" onClick={closeModal}>Cancel</Button> ]} className="new-project" @@ -92,7 +105,10 @@ export function CreateServiceModal () { <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))}/> + 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 service name</HelperTextItem> diff --git a/karavan-web/karavan-app/src/main/webui/src/shared/error/ProjectExistsError.ts b/karavan-web/karavan-app/src/main/webui/src/shared/error/ProjectExistsError.ts new file mode 100644 index 00000000..6adee70b --- /dev/null +++ b/karavan-web/karavan-app/src/main/webui/src/shared/error/ProjectExistsError.ts @@ -0,0 +1,6 @@ +export class ProjectExistsError extends Error { + constructor(message: string) { + super(message); + this.name = 'ProjectExistsError'; + } +} \ 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 index 09850a90..b6300be9 100644 --- a/karavan-web/karavan-app/src/main/webui/src/templates/CreateProjectModal.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/templates/CreateProjectModal.tsx @@ -19,13 +19,15 @@ import React, {useState} from 'react'; import { Button, Form, FormGroup, FormHelperText, HelperText, HelperTextItem, Modal, - ModalVariant, TextInput + 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 () { @@ -34,6 +36,7 @@ export function CreateProjectModal () { const [name, setName] = useState(''); const [description, setDescription] = useState(''); const [projectId, setProjectId] = useState(''); + const [isValidationError, setIsValidationError] = useState(false); function cleanValues() { setName(""); @@ -46,17 +49,30 @@ export function CreateProjectModal () { cleanValues(); } - function confirmAndCloseModal() { - operation !== 'copy' ? - ProjectService.createProject(new Project({name: name, description: description, projectId: projectId})) : - ProjectService.copyProject(project?.projectId, new Project({name: name, description: description, projectId: projectId})); - 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) { - confirmAndCloseModal(); + handleFormSubmit(); } } @@ -70,7 +86,7 @@ export function CreateProjectModal () { onKeyDown={onKeyDown} actions={[ <Button key="confirm" variant="primary" isDisabled={!isReady} - onClick={confirmAndCloseModal}>Save</Button>, + onClick={handleFormSubmit}>Save</Button>, <Button key="cancel" variant="secondary" onClick={closeModal}>Cancel</Button> ]} className="new-project" @@ -90,7 +106,10 @@ export function CreateProjectModal () { <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))}/> + 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> diff --git a/karavan-web/karavan-app/src/main/webui/src/util/StringUtils.ts b/karavan-web/karavan-app/src/main/webui/src/util/StringUtils.ts index 5bf90a49..407778f1 100644 --- a/karavan-web/karavan-app/src/main/webui/src/util/StringUtils.ts +++ b/karavan-web/karavan-app/src/main/webui/src/util/StringUtils.ts @@ -1,3 +1,3 @@ export function isEmpty(str: string) { - return str === null || str === undefined || str.trim() === ''; + return !str?.trim(); } \ No newline at end of file