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