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 e93df034 implemented project(s) upload and download as zip-archive 
(#1547)
e93df034 is described below

commit e93df034de349a7a3a1831ba5f59d2dcdd7d3793
Author: MarcelRosenberger <[email protected]>
AuthorDate: Tue Sep 2 20:13:28 2025 +0200

    implemented project(s) upload and download as zip-archive (#1547)
---
 .../apache/camel/karavan/api/ProjectResource.java  |  69 +++++++++
 .../camel/karavan/service/ProjectService.java      |  95 +++++++++++++
 karavan-app/src/main/webui/src/api/KaravanApi.tsx  |  38 +++++
 karavan-app/src/main/webui/src/api/ProjectStore.ts |  10 +-
 .../src/main/webui/src/projects/ProjectsPage.tsx   |  10 +-
 .../main/webui/src/projects/ProjectsTableRow.tsx   |   9 ++
 .../main/webui/src/projects/ProjectsToolbar.tsx    |  10 +-
 .../main/webui/src/projects/UploadProjectModal.tsx | 158 +++++++++++++++++++++
 8 files changed, 392 insertions(+), 7 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 fb2cadb8..4828a58c 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
@@ -33,7 +33,9 @@ import org.apache.camel.karavan.service.ConfigService;
 import org.apache.camel.karavan.service.GitService;
 import org.apache.camel.karavan.service.ProjectService;
 import org.jboss.logging.Logger;
+import org.jboss.resteasy.reactive.PartType;
 
+import java.io.InputStream;
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
@@ -187,4 +189,71 @@ public class ProjectResource extends AbstractApiResource {
             return Response.serverError().entity(e.getMessage()).build();
         }
     }
+
+    @POST
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.MULTIPART_FORM_DATA)
+    @Path("/upload")
+    public Response upload(ProjectArchiveUploadMultiPart 
projectArchiveUploadMultiPart) {
+        try {
+            
projectService.importProjectFromArchiveFile(projectArchiveUploadMultiPart.getFile(),
 projectArchiveUploadMultiPart.isOverwriteExistingFiles());
+            return Response.ok().build();
+        } catch (Exception e) {
+            return 
Response.status(Response.Status.CONFLICT).entity(e.getMessage()).build();
+        }
+    }
+
+    @GET
+    @Produces(MediaType.APPLICATION_OCTET_STREAM)
+    @Path("/download/{projectId}")
+    public Response download(@PathParam("projectId") String projectId) {
+        try {
+            return 
Response.ok(projectService.downloadProjectArchiveFile(projectId)).header("Content-Disposition",
 "attachment;filename=" + projectId + ".zip").build();
+        } catch (Exception e) {
+            return 
Response.status(Response.Status.CONFLICT).entity(e.getMessage()).build();
+        }
+    }
+
+    public static class ProjectArchiveUploadMultiPart {
+
+        @FormParam("file")
+        @PartType(MediaType.APPLICATION_OCTET_STREAM)
+        public InputStream file;
+
+        @FormParam("overwriteExistingFiles")
+        @PartType(MediaType.TEXT_PLAIN)
+        Boolean overwriteExistingFiles;
+
+        public ProjectArchiveUploadMultiPart(InputStream file, Boolean 
overwriteExistingFiles) {
+            this.file = file;
+            this.overwriteExistingFiles = overwriteExistingFiles;
+        }
+
+        public ProjectArchiveUploadMultiPart() {
+        }
+
+        public InputStream getFile() {
+            return file;
+        }
+
+        public void setFile(InputStream file) {
+            this.file = file;
+        }
+
+        public Boolean isOverwriteExistingFiles() {
+            return overwriteExistingFiles;
+        }
+
+        public void setOverwriteExistingFiles(Boolean overwriteExistingFiles) {
+            this.overwriteExistingFiles = overwriteExistingFiles;
+        }
+
+        @Override
+        public String toString() {
+            return "ProjectArchiveFile{" +
+                    "file=" + file +
+                    ", overwriteExistingFiles=" + overwriteExistingFiles +
+                    '}';
+        }
+    }
 }
\ No newline at end of file
diff --git 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
index a8c25e41..64e9ea19 100644
--- 
a/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
+++ 
b/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java
@@ -27,11 +27,17 @@ import 
org.apache.camel.karavan.docker.DockerComposeConverter;
 import org.apache.camel.karavan.docker.DockerForKaravan;
 import org.apache.camel.karavan.kubernetes.KubernetesService;
 import org.apache.camel.karavan.model.*;
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
 import org.apache.commons.lang3.StringUtils;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.jboss.logging.Logger;
 
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.time.Instant;
 import java.util.*;
 import java.util.stream.Collectors;
@@ -154,6 +160,72 @@ public class ProjectService {
         importProjectFromRepo(repo);
     }
 
+    public void importProjectFromArchiveFile(InputStream 
projectArchiveInputStream, boolean overwriteExistingFiles) throws Exception {
+        LOGGER.info("Import project(s) from archive file");
+        try {
+            ZipArchiveInputStream zipArchiveInputStream = new 
ZipArchiveInputStream(projectArchiveInputStream);
+            ZipArchiveEntry zipArchiveEntry = 
zipArchiveInputStream.getNextEntry();
+            Map<String, String> projects = new HashMap<>();
+            Map<String, List<ProjectFile>> files = new HashMap<>();
+            while (zipArchiveEntry != null) {
+                if (zipArchiveInputStream.canReadEntryData(zipArchiveEntry)) {
+                    String zipArchiveEntryName = 
zipArchiveEntry.getName().replace("\\", "/");
+                    if (!zipArchiveEntry.isDirectory() && 
StringUtils.countMatches(zipArchiveEntryName, "/") == 1) {
+                        String[] nameParts = zipArchiveEntryName.split("/");
+                        if (Arrays.stream(nameParts).allMatch(name -> 
!name.isBlank() && !name.equals(".") && !name.equals(".."))) {
+                            String parentFolderName = 
nameParts[nameParts.length - 2];
+                            String fileName = nameParts[nameParts.length - 1];
+                            if (parentFolderName.matches("[a-zA-Z0-9-]{5,}")) {
+                                boolean projectFileExists = 
karavanCache.getProjectFile(parentFolderName, fileName) != null;
+                                if (!projectFileExists || 
overwriteExistingFiles) {
+                                    LOGGER.debug("Importing file: " + 
zipArchiveEntryName);
+                                    ByteArrayOutputStream 
byteArrayOutputStream = new ByteArrayOutputStream();
+                                    byte[] buffer = new byte[4096];
+                                    int len;
+                                    while ((len = 
zipArchiveInputStream.read(buffer)) != -1) {
+                                        byteArrayOutputStream.write(buffer, 0, 
len);
+                                    }
+                                    String fileContent = 
byteArrayOutputStream.toString(zipArchiveInputStream.getCharset());
+                                    if 
(fileName.equals(APPLICATION_PROPERTIES_FILENAME)) {
+                                        String projectName = 
codeService.getProjectName(fileContent);
+                                        projects.put(parentFolderName, 
projectName);
+                                    } else if 
(!projects.containsKey(parentFolderName)) {
+                                        projects.put(parentFolderName, 
parentFolderName);
+                                    }
+                                    List<ProjectFile> projectFiles = new 
ArrayList<>();
+                                    if (files.containsKey(parentFolderName)) {
+                                        
projectFiles.addAll(files.get(parentFolderName));
+                                    }
+                                    projectFiles.add(new ProjectFile(fileName, 
fileContent, parentFolderName, null));
+                                    files.put(parentFolderName, projectFiles);
+                                } else {
+                                    LOGGER.debug("Skipping file: " + 
zipArchiveEntryName);
+                                }
+                            }
+                        }
+                    }
+                    zipArchiveEntry = zipArchiveInputStream.getNextEntry();
+                }
+            }
+            zipArchiveInputStream.close();
+
+            for (Map.Entry<String, String> entry : projects.entrySet()) {
+                Project project = new Project(entry.getKey(), 
entry.getValue());
+                karavanCache.saveProject(project, false);
+            }
+
+            for (Map.Entry<String, List<ProjectFile>> entry : 
files.entrySet()) {
+                for (ProjectFile projectFile : entry.getValue()) {
+                    karavanCache.saveProjectFile(projectFile, false, false);
+                }
+            }
+
+        } catch (Exception e) {
+            LOGGER.error("Error during project import", e);
+            throw e;
+        }
+    }
+
     private void importProjectFromRepo(GitRepo repo) {
         LOGGER.info("Import project from GitRepo " + repo.getName());
         try {
@@ -169,6 +241,29 @@ public class ProjectService {
         }
     }
 
+    public byte[] downloadProjectArchiveFile(String projectId) throws 
Exception {
+        Project project = karavanCache.getProject(projectId);
+        if (project != null) {
+
+            ByteArrayOutputStream byteArrayOutputStream = new 
ByteArrayOutputStream();
+            ZipArchiveOutputStream zipOutputStream = new 
ZipArchiveOutputStream(byteArrayOutputStream);
+            List<ProjectFile> projectFiles = 
karavanCache.getProjectFiles(projectId);
+
+            for (ProjectFile projectFile : projectFiles) {
+                ZipArchiveEntry entry = new ZipArchiveEntry(projectId + "/" + 
projectFile.getName());
+                byte[] data = 
projectFile.getCode().getBytes(StandardCharsets.UTF_8);
+                entry.setSize(data.length);
+                zipOutputStream.putArchiveEntry(entry);
+                zipOutputStream.write(data);
+                zipOutputStream.closeArchiveEntry();
+            }
+
+            zipOutputStream.finish();
+            return byteArrayOutputStream.toByteArray();
+        }
+        return null;
+    }
+
     public Project getProjectFromRepo(GitRepo repo) {
         String folderName = repo.getName();
         String propertiesFile = codeService.getPropertiesFile(repo);
diff --git a/karavan-app/src/main/webui/src/api/KaravanApi.tsx 
b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
index 8cb77607..70f7fd46 100644
--- a/karavan-app/src/main/webui/src/api/KaravanApi.tsx
+++ b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
@@ -377,6 +377,44 @@ export class KaravanApi {
         });
     }
 
+    static async uploadProjectArchiveFile(formData: FormData, after: (result: 
boolean, data: any) => void) {
+        try {
+            instance.post('/ui/project/upload', formData, { headers: 
{'Content-Type': 'multipart/form-data'} })
+                .then(res => {
+                    if (res.status === 200) {
+                        after(true, res.data);
+                    } else {
+                        after(false, res?.data);
+                    }
+                }).catch(err => {
+                after(false, err);
+            });
+        } catch (error: any) {
+            after(false, error);
+        }
+    }
+
+    static downloadProjectArchiveFile(project: Project, after: (result: 
boolean, res: AxiosResponse<Project> | any) => void) {
+        try {
+            instance.get('/ui/project/download/' + project.projectId, { 
headers: {'Accept' : 'application/octet-stream'},  responseType: 'blob' })
+                .then(res => {
+                    if (res.status === 200) {
+                        const url = window.URL.createObjectURL(new 
Blob([res.data]));
+                        const link = document.createElement("a");
+                        link.href = url;
+                        link.setAttribute("download", project.projectId + 
".zip");
+                        link.click();
+                        setTimeout(() => window.URL.revokeObjectURL(url), 0);
+                        after(true, res);
+                    }
+                }).catch(err => {
+                after(false, err);
+            });
+        } catch (error: any) {
+            after(false, error);
+        }
+    }
+
     static async push(params: {}, after: (res: AxiosResponse<any>) => void) {
         instance.post('/ui/git', params)
             .then(res => {
diff --git a/karavan-app/src/main/webui/src/api/ProjectStore.ts 
b/karavan-app/src/main/webui/src/api/ProjectStore.ts
index 457f4305..4bc36c3f 100644
--- a/karavan-app/src/main/webui/src/api/ProjectStore.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectStore.ts
@@ -124,11 +124,11 @@ interface ProjectState {
     images: ContainerImage [],
     setImages: (images: ContainerImage []) => void;
     project: Project;
-    setProject: (project: Project, operation:  "create" | "select" | "delete"| 
"none" | "copy") => void;
-    operation: "create" | "select" | "delete" | "none" | "copy";
+    setProject: (project: Project, operation:  "create" | "select" | "delete"| 
"none" | "copy" | "upload" | "download") => void;
+    operation: "create" | "select" | "delete" | "none" | "copy" | "upload" | 
"download";
     tabIndex: string | number;
     setTabIndex: (tabIndex: string | number) => void;
-    setOperation: (o: "create" | "select" | "delete"| "none" | "copy") => void;
+    setOperation: (o: "create" | "select" | "delete"| "none" | "copy" | 
"upload" | "download") => void;
     camelStatuses: CamelStatus[],
     setCamelStatuses: (camelStatuses: CamelStatus[]) => void;
     camelTraces: CamelStatus[],
@@ -145,7 +145,7 @@ export const useProjectStore = 
createWithEqualityFn<ProjectState>((set) => ({
     isPushing: false,
     isPulling: false,
     isRunning: false,
-    setProject: (project: Project, operation:  "create" | "select" | "delete"| 
"none" | "copy") => {
+    setProject: (project: Project, operation:  "create" | "select" | "delete"| 
"none" | "copy" | "upload" | "download") => {
         set((state: ProjectState) => ({
             project: project,
             operation: operation,
@@ -157,7 +157,7 @@ export const useProjectStore = 
createWithEqualityFn<ProjectState>((set) => ({
             tabIndex: state.tabIndex
         }));
     },
-    setOperation: (o: "create" | "select" | "delete"| "none" | "copy") => {
+    setOperation: (o: "create" | "select" | "delete"| "none" | "copy" | 
"upload" | "download") => {
         set((state: ProjectState) => ({
             operation: o
         }));
diff --git a/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx 
b/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
index c7abfbe0..8415e06b 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectsPage.tsx
@@ -47,6 +47,7 @@ import {shallow} from "zustand/shallow";
 import {KaravanApi} from "../api/KaravanApi";
 import {ProjectsToolbar} from "./ProjectsToolbar";
 import {ProjectService} from "../api/ProjectService";
+import {UploadProjectModal} from "./UploadProjectModal";
 
 interface Props {
     tools?: React.ReactNode
@@ -56,7 +57,7 @@ export function ProjectsPage (props: Props) {
 
     const [projects, setProjects, filter, setFilter]
         = useProjectsStore((s) => [s.projects, s.setProjects, s.filter, 
s.setFilter], shallow)
-    const [operation] = useProjectStore((s) => [s.operation], shallow)
+    const [project, operation, setOperation] = useProjectStore((s) => 
[s.project, s.operation, s.setOperation], shallow)
 
     useEffect(() => {
         KaravanApi.getProjects((projects: Project[]) => {
@@ -69,6 +70,12 @@ export function ProjectsPage (props: Props) {
         return () => clearInterval(interval);
     }, []);
 
+    useEffect(() => {
+        if (["download"].includes(operation)) {
+            KaravanApi.downloadProjectArchiveFile(project, () => 
setOperation("none"));
+        }
+    }, [operation]);
+
     function title() {
         return <TextContent>
             <Text component="h2">Projects</Text>
@@ -127,6 +134,7 @@ export function ProjectsPage (props: Props) {
             </PageSection>
             {["create", "copy"].includes(operation) && <CreateProjectModal/>}
             {["delete"].includes(operation) && <DeleteProjectModal/>}
+            {["upload"].includes(operation) && <UploadProjectModal/>}
         </PageSection>
 
     )
diff --git a/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx 
b/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
index 10f98124..a6500bf1 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectsTableRow.tsx
@@ -21,6 +21,7 @@ import '../designer/karavan.css';
 import {Td, Tr} from "@patternfly/react-table";
 import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon";
 import CopyIcon from "@patternfly/react-icons/dist/esm/icons/copy-icon";
+import DownloadIcon from "@patternfly/react-icons/dist/js/icons/download-icon";
 import {Project} from '../api/ProjectModels';
 import {useAppConfigStore, useLogStore, useProjectStore, useStatusesStore,} 
from "../api/ProjectStore";
 import {shallow} from "zustand/shallow";
@@ -89,6 +90,14 @@ export function ProjectsTableRow (props: Props) {
                 {!isBuildIn &&
                     <Flex direction={{default: "row"}} 
justifyContent={{default: "justifyContentFlexEnd"}}
                           spaceItems={{default: 'spaceItemsNone'}}>
+                        <FlexItem>
+                            <Tooltip content={"Download project"} 
position={"bottom"}>
+                                <Button className="dev-action-button" 
variant={"plain"} icon={<DownloadIcon/>}
+                                        onClick={e => {
+                                            setProject(project, "download");
+                                        }}></Button>
+                            </Tooltip>
+                        </FlexItem>
                         <FlexItem>
                             <Tooltip content={"Copy project"} 
position={"bottom"}>
                                 <Button className="dev-action-button" 
variant={"plain"} icon={<CopyIcon/>}
diff --git a/karavan-app/src/main/webui/src/projects/ProjectsToolbar.tsx 
b/karavan-app/src/main/webui/src/projects/ProjectsToolbar.tsx
index 530fa348..3e752104 100644
--- a/karavan-app/src/main/webui/src/projects/ProjectsToolbar.tsx
+++ b/karavan-app/src/main/webui/src/projects/ProjectsToolbar.tsx
@@ -25,15 +25,17 @@ import {
 } from '@patternfly/react-core';
 import './ProjectsPage.css';
 import PlusIcon from '@patternfly/react-icons/dist/esm/icons/plus-icon';
-import {useProjectsStore, useProjectStore} from "../api/ProjectStore";
+import {useFileStore, useProjectsStore, useProjectStore} from 
"../api/ProjectStore";
 import {Project} from "../api/ProjectModels";
 import {shallow} from "zustand/shallow";
 import RefreshIcon from "@patternfly/react-icons/dist/esm/icons/sync-alt-icon";
 import {ProjectService} from "../api/ProjectService";
+import UploadIcon from "@patternfly/react-icons/dist/esm/icons/upload-icon";
 
 export function ProjectsToolbar () {
 
     const [filter, setFilter] = useProjectsStore((s) => [s.filter, 
s.setFilter], shallow)
+    const [file, setFile] = useFileStore((s) => [s.file, s.setFile], shallow )
     const [setProject] = useProjectStore((s) => [s.setProject], shallow)
 
     return (
@@ -58,6 +60,12 @@ export function ProjectsToolbar () {
                                 setProject(new Project(), 'create')}
                     >Create</Button>
                 </ToolbarItem>
+                <ToolbarItem>
+                    <Button className="dev-action-button"
+                            size="sm" variant="secondary"
+                            icon={<UploadIcon/>}
+                            onClick={e => setProject(new Project(), 
"upload")}>Upload</Button>
+                </ToolbarItem>
             </ToolbarContent>
         </Toolbar>
     )
diff --git a/karavan-app/src/main/webui/src/projects/UploadProjectModal.tsx 
b/karavan-app/src/main/webui/src/projects/UploadProjectModal.tsx
new file mode 100644
index 00000000..8566916a
--- /dev/null
+++ b/karavan-app/src/main/webui/src/projects/UploadProjectModal.tsx
@@ -0,0 +1,158 @@
+/*
+ * 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, {useEffect, useState} from 'react';
+import {
+    Button,
+    Modal,
+    FormGroup,
+    ModalVariant,
+    Form,
+    FileUpload,
+    FormAlert,
+    Alert, Checkbox,
+} from '@patternfly/react-core';
+import '../designer/karavan.css';
+import {useProjectStore} from "../api/ProjectStore";
+import {Accept, DropEvent, FileRejection} from "react-dropzone";
+import {EventBus} from "../designer/utils/EventBus";
+import {shallow} from "zustand/shallow";
+import {ProjectService} from "../api/ProjectService";
+import {SubmitHandler, useForm} from "react-hook-form";
+import {KaravanApi} from "../api/KaravanApi";
+import {AxiosResponse} from "axios";
+
+class ProjectArchiveFileUploadForm {
+    file?: File = undefined;
+    overwriteExistingFiles: boolean = false;
+
+    constructor(overwriteExistingFiles: boolean, file?: File) {
+        this.overwriteExistingFiles = overwriteExistingFiles;
+        this.file = file;
+    }
+}
+
+export function UploadProjectModal() {
+
+    const [operation] = useProjectStore((s) => [s.operation], shallow);
+    const [isLoading, setIsLoading] = useState(false);
+    const [isRejected, setIsRejected] = useState(false);
+    const [isReset, setReset] = useState(false);
+    const [backendError, setBackendError] = useState<string>();
+    const formContext = useForm<ProjectArchiveFileUploadForm>({mode: "all"});
+    const {
+        formState: {errors},
+        handleSubmit,
+        reset,
+        trigger,
+        setValue,
+        getValues
+    } = formContext;
+
+    useEffect(() => {
+        reset(new ProjectArchiveFileUploadForm(false));
+        setBackendError(undefined);
+        setReset(true);
+    }, [reset, operation]);
+
+    React.useEffect(() => {
+        isReset && trigger();
+    }, [trigger, isReset]);
+
+    const onSubmit: SubmitHandler<ProjectArchiveFileUploadForm> = 
(projectArchiveFile) => {
+        if (projectArchiveFile.file) {
+            const data = new FormData();
+            data.append('overwriteExistingFiles', 
projectArchiveFile.overwriteExistingFiles.toString());
+            data.append('file', projectArchiveFile.file);
+            KaravanApi.uploadProjectArchiveFile(data, after);
+        }
+    }
+
+    function after (result: boolean, data: AxiosResponse | any) {
+        if (result) {
+            onSuccess(data);
+        } else {
+            setBackendError(JSON.stringify(data?.response?.data));
+        }
+    }
+
+    function onSuccess (data: any) {
+        EventBus.sendAlert( "Success", "File successfully uploaded", 
"success");
+        ProjectService.refreshProjects();
+        closeModal();
+    }
+
+    function closeModal () {
+        useProjectStore.setState({operation: "none"})
+    }
+
+    const handleFileInputChange = (_: DropEvent, file: File) => 
setValue('file', file, { shouldValidate: true });
+    const handleFileRejected = (fileRejections: FileRejection[], event: 
DropEvent) => setIsRejected(true);
+    const handleClear = (event: React.MouseEvent<HTMLButtonElement>) => {
+        setValue('file', undefined);
+        setValue('overwriteExistingFiles', false);
+        setIsRejected(false);
+        setBackendError(undefined);
+        reset(new ProjectArchiveFileUploadForm(false));
+    };
+    const handleFileOverwriteCheckboxChange = (event: 
React.FormEvent<HTMLInputElement>, checked: boolean) => 
setValue('overwriteExistingFiles', checked, { shouldValidate: true });
+
+    const fileNotUploaded = typeof getValues('file') === 'undefined';
+    const accept : Accept = {};
+
+    return (
+        <Modal
+            title="Upload"
+            variant={ModalVariant.small}
+            isOpen={operation === 'upload'}
+            onClose={closeModal}
+            actions={[
+                <Button key="confirm" variant="primary"
+                        onClick={handleSubmit(onSubmit)}
+                        isDisabled={Object.getOwnPropertyNames(errors).length 
> 0 || fileNotUploaded}
+                >
+                    Upload
+                </Button>,
+                <Button key="cancel" variant="secondary" 
onClick={closeModal}>Cancel</Button>
+            ]}
+        >
+            <Form>
+                <FormGroup fieldId="upload">
+                    <FileUpload
+                        id="file-upload"
+                        value={getValues('file')}
+                        filename={getValues('file')?.name}
+                        isLoading={isLoading}
+                        onFileInputChange={handleFileInputChange}
+                        onClearClick={handleClear}
+                        dropzoneProps={{accept: accept, onDropRejected: 
handleFileRejected}}
+                    />
+                    <Checkbox
+                        id="file-overwrite"
+                        onChange={handleFileOverwriteCheckboxChange}
+                        label="Overwrite existing files"
+                        isChecked={getValues('overwriteExistingFiles')}
+                    />
+                </FormGroup>
+                {backendError &&
+                    <FormAlert>
+                        <Alert variant="danger" title={backendError} 
aria-live="polite" isInline />
+                    </FormAlert>
+                }
+            </Form>
+        </Modal>
+    )
+}
\ No newline at end of file

Reply via email to