diff --git a/src/app/Agent/AgentProbeTemplates.tsx b/src/app/Agent/AgentProbeTemplates.tsx index 24309f1913..160091491b 100644 --- a/src/app/Agent/AgentProbeTemplates.tsx +++ b/src/app/Agent/AgentProbeTemplates.tsx @@ -458,6 +458,9 @@ export const AgentProbeTemplateUploadModal: React.FC = ({ onClose, submitRef={submitRef} abortRef={abortRef} uploading={uploading} + dropZoneAccepts={{ + 'application/octet-stream': ['.jfr'], + }} displayAccepts={['JFR']} onFileSubmit={onFileSubmit} onFilesChange={onFilesChange} diff --git a/src/app/Dashboard/LayoutTemplateUploadModal.tsx b/src/app/Dashboard/LayoutTemplateUploadModal.tsx index f4509188ce..2a8ff56aaf 100644 --- a/src/app/Dashboard/LayoutTemplateUploadModal.tsx +++ b/src/app/Dashboard/LayoutTemplateUploadModal.tsx @@ -255,7 +255,9 @@ export const LayoutTemplateUploadModal: React.FC submitRef={submitRef} abortRef={abortRef} uploading={uploading} - dropZoneAccepts={['application/json']} + dropZoneAccepts={{ + 'application/json': ['.json'], + }} displayAccepts={['JSON']} onFileSubmit={onFileSubmit} onFilesChange={onFilesChange} diff --git a/src/app/Events/EventTemplates.tsx b/src/app/Events/EventTemplates.tsx index 1c129153ea..28bb6785e4 100644 --- a/src/app/Events/EventTemplates.tsx +++ b/src/app/Events/EventTemplates.tsx @@ -519,6 +519,9 @@ export const EventTemplatesUploadModal: React.FC submitRef={submitRef} abortRef={abortRef} uploading={uploading} + dropZoneAccepts={{ + 'application/xml': ['.xml', '.jfc'], + }} displayAccepts={['XML', 'JFC']} onFileSubmit={onFileSubmit} onFilesChange={onFilesChange} diff --git a/src/app/Rules/RulesUploadModal.tsx b/src/app/Rules/RulesUploadModal.tsx index 36f7d7698e..4749a729e3 100644 --- a/src/app/Rules/RulesUploadModal.tsx +++ b/src/app/Rules/RulesUploadModal.tsx @@ -162,7 +162,9 @@ export const RuleUploadModal: React.FC = ({ onClose, ...pr submitRef={submitRef} abortRef={abortRef} uploading={uploading} - dropZoneAccepts={['application/json']} + dropZoneAccepts={{ + 'application/json': ['.json'], + }} displayAccepts={['JSON']} onFileSubmit={onFileSubmit} onFilesChange={onFilesChange} diff --git a/src/app/SecurityPanel/CertificateUploadModal.tsx b/src/app/SecurityPanel/CertificateUploadModal.tsx index e4b1f8a5d0..244e77846f 100644 --- a/src/app/SecurityPanel/CertificateUploadModal.tsx +++ b/src/app/SecurityPanel/CertificateUploadModal.tsx @@ -129,7 +129,10 @@ export const CertificateUploadModal: React.FC = ({ submitRef={submitRef} abortRef={abortRef} uploading={uploading} - dropZoneAccepts={['application/x-x509-ca-cert', 'application/pkix-cert']} + dropZoneAccepts={{ + 'application/x-x509-ca-cert': ['.der'], + 'application/pkix-cert': ['.cer'], + }} displayAccepts={['CER', 'DER']} onFileSubmit={onFileSubmit} onFilesChange={onFilesChange} diff --git a/src/app/Shared/Components/FileUploads.tsx b/src/app/Shared/Components/FileUploads.tsx index 61203e23a3..708545fc50 100644 --- a/src/app/Shared/Components/FileUploads.tsx +++ b/src/app/Shared/Components/FileUploads.tsx @@ -25,6 +25,7 @@ import { import { InProgressIcon, UploadIcon } from '@patternfly/react-icons'; import * as React from 'react'; import { Subject } from 'rxjs'; +import { DropzoneAccept, FileRejection } from './types'; export type ProgressVariant = 'success' | 'danger' | 'warning'; @@ -50,7 +51,7 @@ export interface MultiFileUploadProps { abortRef?: React.RefObject; uploading: boolean; displayAccepts: string[]; - dropZoneAccepts?: string[]; // Infer from displayAccepts, if not specified + dropZoneAccepts: DropzoneAccept; onFilesChange?: (files: FUpload[]) => void; onFileSubmit: (fileUploads: FUpload[], uploadCallbacks: UploadCallbacks) => void; titleIcon?: React.ReactNode; @@ -76,15 +77,8 @@ export const MultiFileUpload: React.FC = ({ const [fileUploads, setFileUploads] = React.useState([]); const [showCancelPrompt, setShowCancelPrompt] = React.useState(false); - const dzAccept = React.useMemo(() => { - if (dropZoneAccepts && dropZoneAccepts.length) { - return dropZoneAccepts.join(','); - } - return displayAccepts.map((t) => `.${t.toLocaleLowerCase()}`).join(','); - }, [dropZoneAccepts, displayAccepts]); - const handleFileDrop = React.useCallback( - (droppedFiles: File[]) => { + (_, droppedFiles: File[]) => { setFileUploads((old) => { // Check for re-uploads const currentFilenames = old.map((fileUpload) => fileUpload.file.name); @@ -108,19 +102,14 @@ export const MultiFileUpload: React.FC = ({ ); const handleFileReject = React.useCallback( - (rejectedFiles: File[]) => { - rejectedFiles.forEach((f) => { - if (!dzAccept.includes(f.type) || f.type === '') { - const message = `Expected file format: ${dzAccept}, but received ${ - f.type === '' ? 'unknown type' : f.type - } for ${f.name}`; - notifications.warning(`Incompatible file format`, message); - } else { - notifications.warning(`Failed to load file`, f.name); - } + (fileRejections: FileRejection[]) => { + fileRejections.forEach(({ file, errors }) => { + errors.forEach(({ message }) => { + notifications.warning(`Rejected file: ${file.name}`, message); + }); }); }, - [notifications, dzAccept], + [notifications], ); const handleFileRemove = React.useCallback( @@ -272,7 +261,7 @@ export const MultiFileUpload: React.FC = ({ { + (_, selected: string) => { if (!selected.length) { onSelect(undefined); } else { diff --git a/src/app/Shared/Components/types.ts b/src/app/Shared/Components/types.tsx similarity index 68% rename from src/app/Shared/Components/types.ts rename to src/app/Shared/Components/types.tsx index 149e9df745..ba7659a747 100644 --- a/src/app/Shared/Components/types.ts +++ b/src/app/Shared/Components/types.tsx @@ -14,6 +14,9 @@ * limitations under the License. */ +import * as React from 'react'; +import { MultipleFileUploadProps } from '@patternfly/react-core'; + export interface LoadingProps { spinnerAriaValueText?: string; // Text describing that current loading status or progress spinnerAriaLabelledBy?: string; // Id of element which describes what is being loaded @@ -24,3 +27,12 @@ export interface LoadingProps { export type DescriptionProps = { children?: React.ReactNode; }; + +export type Unpacked = T extends (infer A)[] ? A : T; + +// FIXME: React drop-zone types cannot be imported +export type DropzoneOptions = NonNullable; + +export type FileRejection = Unpacked>[0]>; + +export type DropzoneAccept = DropzoneOptions['accept'];