@oneuptime/common 8.0.5579 → 8.0.5581
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/AlertInternalNote.ts +58 -1
- package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +1 -0
- package/Models/DatabaseModels/File.ts +1 -1
- package/Models/DatabaseModels/IncidentInternalNote.ts +58 -1
- package/Models/DatabaseModels/IncidentPublicNote.ts +58 -1
- package/Models/DatabaseModels/ScheduledMaintenanceInternalNote.ts +58 -1
- package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +58 -1
- package/Models/DatabaseModels/StatusPageAnnouncement.ts +49 -0
- package/Server/API/AlertInternalNoteAPI.ts +96 -0
- package/Server/API/IncidentInternalNoteAPI.ts +96 -0
- package/Server/API/IncidentPublicNoteAPI.ts +96 -0
- package/Server/API/ScheduledMaintenanceInternalNoteAPI.ts +100 -0
- package/Server/API/ScheduledMaintenancePublicNoteAPI.ts +100 -0
- package/Server/API/StatusPageAPI.ts +585 -59
- package/Server/API/StatusPageAnnouncementAPI.ts +98 -0
- package/Server/API/UserAPI.ts +95 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1763471659817-MigrationName.ts +79 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1763477560906-MigrationName.ts +81 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1763480947474-MigrationName.ts +79 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Middleware/ProjectAuthorization.ts +3 -1
- package/Server/Services/AlertInternalNoteService.ts +75 -2
- package/Server/Services/IncidentInternalNoteService.ts +76 -2
- package/Server/Services/IncidentPublicNoteService.ts +76 -2
- package/Server/Services/ScheduledMaintenanceInternalNoteService.ts +76 -2
- package/Server/Services/ScheduledMaintenancePublicNoteService.ts +76 -2
- package/Server/Services/ScheduledMaintenanceService.ts +10 -7
- package/Server/Services/StatusPagePrivateUserService.ts +10 -7
- package/Server/Services/StatusPageService.ts +12 -7
- package/Server/Services/StatusPageSubscriberService.ts +19 -13
- package/Server/Utils/FileAttachmentMarkdownUtil.ts +98 -0
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +9 -1
- package/Server/Utils/Monitor/Criteria/ExceptionMonitorCriteria.ts +34 -0
- package/Server/Utils/Monitor/DataToProcess.ts +3 -1
- package/Server/Utils/Monitor/MonitorCriteriaDataExtractor.ts +13 -0
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +13 -0
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +20 -0
- package/Server/Utils/Monitor/MonitorResource.ts +18 -0
- package/Server/Utils/Response.ts +13 -0
- package/Server/Utils/Telemetry.ts +15 -0
- package/Types/File/MimeType.ts +18 -0
- package/Types/Monitor/CriteriaFilter.ts +3 -0
- package/Types/Monitor/ExceptionMonitor/ExceptionMonitorResponse.ts +12 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +67 -0
- package/Types/Monitor/MonitorStep.ts +30 -0
- package/Types/Monitor/MonitorStepExceptionMonitor.ts +94 -0
- package/Types/Monitor/MonitorType.ts +10 -1
- package/Types/Telemetry/TelemetryQuery.ts +2 -1
- package/Types/Telemetry/TelemetryType.ts +1 -0
- package/UI/Components/AttachmentList/EventAttachmentList.tsx +121 -0
- package/UI/Components/EventItem/EventItem.tsx +22 -0
- package/UI/Components/Feed/FeedItem.tsx +9 -16
- package/UI/Components/FilePicker/FilePicker.tsx +441 -145
- package/UI/Components/Forms/Fields/FormField.tsx +32 -15
- package/UI/Components/Forms/FormSummary.tsx +168 -1
- package/UI/Components/Forms/ModelForm.tsx +46 -24
- package/UI/Components/Forms/Types/FormFieldSchemaType.ts +1 -0
- package/UI/Components/Icon/Icon.tsx +1 -1
- package/UI/Utils/API/RequestOptions.ts +2 -0
- package/UI/Utils/ModelAPI/ModelAPI.ts +18 -0
- package/UI/Utils/User.ts +8 -0
- package/Utils/API.ts +11 -1
- package/build/dist/Models/DatabaseModels/AlertInternalNote.js +49 -1
- package/build/dist/Models/DatabaseModels/AlertInternalNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
- package/build/dist/Models/DatabaseModels/File.js +1 -1
- package/build/dist/Models/DatabaseModels/File.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentInternalNote.js +49 -1
- package/build/dist/Models/DatabaseModels/IncidentInternalNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +49 -1
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceInternalNote.js +49 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceInternalNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +49 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +48 -0
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
- package/build/dist/Server/API/AlertInternalNoteAPI.js +68 -0
- package/build/dist/Server/API/AlertInternalNoteAPI.js.map +1 -0
- package/build/dist/Server/API/IncidentInternalNoteAPI.js +68 -0
- package/build/dist/Server/API/IncidentInternalNoteAPI.js.map +1 -0
- package/build/dist/Server/API/IncidentPublicNoteAPI.js +68 -0
- package/build/dist/Server/API/IncidentPublicNoteAPI.js.map +1 -0
- package/build/dist/Server/API/ScheduledMaintenanceInternalNoteAPI.js +68 -0
- package/build/dist/Server/API/ScheduledMaintenanceInternalNoteAPI.js.map +1 -0
- package/build/dist/Server/API/ScheduledMaintenancePublicNoteAPI.js +68 -0
- package/build/dist/Server/API/ScheduledMaintenancePublicNoteAPI.js.map +1 -0
- package/build/dist/Server/API/StatusPageAPI.js +488 -85
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAnnouncementAPI.js +68 -0
- package/build/dist/Server/API/StatusPageAnnouncementAPI.js.map +1 -0
- package/build/dist/Server/API/UserAPI.js +66 -0
- package/build/dist/Server/API/UserAPI.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1763471659817-MigrationName.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1763471659817-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1763477560906-MigrationName.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1763477560906-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1763480947474-MigrationName.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1763480947474-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +4 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Services/AlertInternalNoteService.js +54 -2
- package/build/dist/Server/Services/AlertInternalNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentInternalNoteService.js +54 -2
- package/build/dist/Server/Services/IncidentInternalNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentPublicNoteService.js +54 -2
- package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceInternalNoteService.js +54 -2
- package/build/dist/Server/Services/ScheduledMaintenanceInternalNoteService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +54 -2
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +6 -5
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserService.js +6 -4
- package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +7 -4
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +11 -7
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Utils/FileAttachmentMarkdownUtil.js +67 -0
- package/build/dist/Server/Utils/FileAttachmentMarkdownUtil.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +6 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ExceptionMonitorCriteria.js +34 -0
- package/build/dist/Server/Utils/Monitor/Criteria/ExceptionMonitorCriteria.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaDataExtractor.js +6 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaDataExtractor.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +10 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +10 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Response.js +8 -0
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry.js +8 -1
- package/build/dist/Server/Utils/Telemetry.js.map +1 -1
- package/build/dist/Types/File/MimeType.js +18 -0
- package/build/dist/Types/File/MimeType.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +2 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/ExceptionMonitor/ExceptionMonitorResponse.js +2 -0
- package/build/dist/Types/Monitor/ExceptionMonitor/ExceptionMonitorResponse.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +62 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +20 -1
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepExceptionMonitor.js +58 -0
- package/build/dist/Types/Monitor/MonitorStepExceptionMonitor.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorType.js +9 -1
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/Telemetry/TelemetryType.js +1 -0
- package/build/dist/Types/Telemetry/TelemetryType.js.map +1 -1
- package/build/dist/UI/Components/AttachmentList/EventAttachmentList.js +42 -0
- package/build/dist/UI/Components/AttachmentList/EventAttachmentList.js.map +1 -0
- package/build/dist/UI/Components/EventItem/EventItem.js +5 -1
- package/build/dist/UI/Components/EventItem/EventItem.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedItem.js +6 -4
- package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
- package/build/dist/UI/Components/FilePicker/FilePicker.js +262 -77
- package/build/dist/UI/Components/FilePicker/FilePicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +24 -12
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Forms/FormSummary.js +77 -1
- package/build/dist/UI/Components/Forms/FormSummary.js.map +1 -1
- package/build/dist/UI/Components/Forms/ModelForm.js +32 -18
- package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js +1 -0
- package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +1 -1
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +30 -45
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/User.js +7 -0
- package/build/dist/UI/Utils/User.js.map +1 -1
- package/build/dist/Utils/API.js +3 -0
- package/build/dist/Utils/API.js.map +1 -1
- package/package.json +6 -6
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { FILE_URL } from "../../Config";
|
|
2
2
|
import API from "../../Utils/API/API";
|
|
3
3
|
import ModelAPI from "../../Utils/ModelAPI/ModelAPI";
|
|
4
|
-
import
|
|
5
|
-
import Icon, { SizeProp } from "../Icon/Icon";
|
|
4
|
+
import Icon from "../Icon/Icon";
|
|
6
5
|
import HTTPResponse from "../../../Types/API/HTTPResponse";
|
|
7
6
|
import CommonURL from "../../../Types/API/URL";
|
|
8
7
|
import Dictionary from "../../../Types/Dictionary";
|
|
@@ -15,7 +14,8 @@ import React, {
|
|
|
15
14
|
useEffect,
|
|
16
15
|
useState,
|
|
17
16
|
} from "react";
|
|
18
|
-
import { useDropzone } from "react-dropzone";
|
|
17
|
+
import { useDropzone, type FileRejection } from "react-dropzone";
|
|
18
|
+
import type { AxiosProgressEvent } from "axios";
|
|
19
19
|
|
|
20
20
|
export interface ComponentProps {
|
|
21
21
|
initialValue?: undefined | Array<FileModel> | FileModel;
|
|
@@ -34,6 +34,31 @@ export interface ComponentProps {
|
|
|
34
34
|
error?: string | undefined;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
type UploadStatus = {
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
progress: number;
|
|
41
|
+
status: "uploading" | "error";
|
|
42
|
+
errorMessage?: string | undefined;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type AddUploadStatusFunction = (status: UploadStatus) => void;
|
|
46
|
+
type UpdateUploadStatusFunction = (
|
|
47
|
+
id: string,
|
|
48
|
+
updates: Partial<UploadStatus>,
|
|
49
|
+
) => void;
|
|
50
|
+
type UpdateUploadProgressFunction = (
|
|
51
|
+
id: string,
|
|
52
|
+
total?: number,
|
|
53
|
+
loaded?: number,
|
|
54
|
+
) => void;
|
|
55
|
+
type RemoveUploadStatusFunction = (id: string) => void;
|
|
56
|
+
type BuildFileSizeErrorFunction = (fileNames: Array<string>) => string;
|
|
57
|
+
type ResolveMimeTypeFunction = (file: File) => MimeType | undefined;
|
|
58
|
+
type FormatFileSizeFunction = (file: FileModel) => string | null;
|
|
59
|
+
|
|
60
|
+
const MAX_FILE_SIZE_BYTES: number = 10 * 1024 * 1024; // 10MB limit
|
|
61
|
+
|
|
37
62
|
const FilePicker: FunctionComponent<ComponentProps> = (
|
|
38
63
|
props: ComponentProps,
|
|
39
64
|
): ReactElement => {
|
|
@@ -42,6 +67,65 @@ const FilePicker: FunctionComponent<ComponentProps> = (
|
|
|
42
67
|
const [filesModel, setFilesModel] = useState<Array<FileModel>>([]);
|
|
43
68
|
|
|
44
69
|
const [acceptTypes, setAcceptTypes] = useState<Dictionary<Array<string>>>({});
|
|
70
|
+
const [uploadStatuses, setUploadStatuses] = useState<Array<UploadStatus>>([]);
|
|
71
|
+
|
|
72
|
+
const addUploadStatus: AddUploadStatusFunction = (
|
|
73
|
+
status: UploadStatus,
|
|
74
|
+
): void => {
|
|
75
|
+
setUploadStatuses((current: Array<UploadStatus>) => {
|
|
76
|
+
return [...current, status];
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const updateUploadStatus: UpdateUploadStatusFunction = (
|
|
81
|
+
id: string,
|
|
82
|
+
updates: Partial<UploadStatus>,
|
|
83
|
+
): void => {
|
|
84
|
+
setUploadStatuses((current: Array<UploadStatus>) => {
|
|
85
|
+
return current.map((upload: UploadStatus) => {
|
|
86
|
+
return upload.id === id
|
|
87
|
+
? {
|
|
88
|
+
...upload,
|
|
89
|
+
...updates,
|
|
90
|
+
}
|
|
91
|
+
: upload;
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const updateUploadProgress: UpdateUploadProgressFunction = (
|
|
97
|
+
id: string,
|
|
98
|
+
total?: number,
|
|
99
|
+
loaded?: number,
|
|
100
|
+
): void => {
|
|
101
|
+
setUploadStatuses((current: Array<UploadStatus>) => {
|
|
102
|
+
return current.map((upload: UploadStatus) => {
|
|
103
|
+
if (upload.id !== id || upload.status === "error") {
|
|
104
|
+
return upload;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const hasTotal: boolean = Boolean(total && total > 0);
|
|
108
|
+
const progressFromEvent: number | null = hasTotal
|
|
109
|
+
? Math.min(100, Math.round(((loaded || 0) / (total as number)) * 100))
|
|
110
|
+
: null;
|
|
111
|
+
const fallbackProgress: number = Math.min(upload.progress + 5, 95);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
...upload,
|
|
115
|
+
progress:
|
|
116
|
+
progressFromEvent !== null ? progressFromEvent : fallbackProgress,
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const removeUploadStatus: RemoveUploadStatusFunction = (id: string): void => {
|
|
123
|
+
setUploadStatuses((current: Array<UploadStatus>) => {
|
|
124
|
+
return current.filter((upload: UploadStatus) => {
|
|
125
|
+
return upload.id !== id;
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
};
|
|
45
129
|
|
|
46
130
|
useEffect(() => {
|
|
47
131
|
const _acceptTypes: Dictionary<Array<string>> = {};
|
|
@@ -77,194 +161,406 @@ const FilePicker: FunctionComponent<ComponentProps> = (
|
|
|
77
161
|
}
|
|
78
162
|
}, [props.value]);
|
|
79
163
|
|
|
80
|
-
const
|
|
164
|
+
const buildFileSizeError: BuildFileSizeErrorFunction = (
|
|
165
|
+
fileNames: Array<string>,
|
|
166
|
+
): string => {
|
|
167
|
+
if (fileNames.length === 0) {
|
|
168
|
+
return "";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (fileNames.length === 1) {
|
|
172
|
+
return `"${fileNames[0]}" exceeds the 10MB limit.`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return `These files exceed the 10MB limit: ${fileNames.join(", ")}.`;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
81
179
|
accept: acceptTypes,
|
|
82
180
|
multiple: props.isMultiFilePicker,
|
|
83
181
|
noClick: true,
|
|
182
|
+
disabled: props.readOnly || isLoading,
|
|
183
|
+
maxSize: MAX_FILE_SIZE_BYTES,
|
|
184
|
+
onDropRejected: (fileRejections: Array<FileRejection>) => {
|
|
185
|
+
const oversizedFiles: Array<string> = fileRejections
|
|
186
|
+
.filter((rejection: FileRejection) => {
|
|
187
|
+
return rejection.file.size > MAX_FILE_SIZE_BYTES;
|
|
188
|
+
})
|
|
189
|
+
.map((rejection: FileRejection) => {
|
|
190
|
+
return rejection.file.name;
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (oversizedFiles.length > 0) {
|
|
194
|
+
setError(buildFileSizeError(oversizedFiles));
|
|
195
|
+
}
|
|
196
|
+
},
|
|
84
197
|
onDrop: async (acceptedFiles: Array<File>) => {
|
|
198
|
+
if (props.readOnly) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
85
202
|
setIsLoading(true);
|
|
86
|
-
|
|
87
|
-
if (props.readOnly) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
203
|
+
setError("");
|
|
90
204
|
|
|
205
|
+
try {
|
|
91
206
|
// Upload these files.
|
|
92
207
|
const filesResult: Array<FileModel> = [];
|
|
208
|
+
const resolveMimeType: ResolveMimeTypeFunction = (
|
|
209
|
+
file: File,
|
|
210
|
+
): MimeType | undefined => {
|
|
211
|
+
const direct: string | undefined = file.type || undefined;
|
|
212
|
+
if (direct && Object.values(MimeType).includes(direct as MimeType)) {
|
|
213
|
+
return direct as MimeType;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// fallback based on extension
|
|
217
|
+
const ext: string | undefined = file.name
|
|
218
|
+
.split(".")
|
|
219
|
+
.pop()
|
|
220
|
+
?.toLowerCase();
|
|
221
|
+
if (!ext) {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
const map: { [key: string]: MimeType } = {
|
|
225
|
+
png: MimeType.png,
|
|
226
|
+
jpg: MimeType.jpg,
|
|
227
|
+
jpeg: MimeType.jpeg,
|
|
228
|
+
svg: MimeType.svg,
|
|
229
|
+
gif: MimeType.gif,
|
|
230
|
+
webp: MimeType.webp,
|
|
231
|
+
pdf: MimeType.pdf,
|
|
232
|
+
doc: MimeType.doc,
|
|
233
|
+
docx: MimeType.docx,
|
|
234
|
+
txt: MimeType.txt,
|
|
235
|
+
log: MimeType.txt,
|
|
236
|
+
md: MimeType.md,
|
|
237
|
+
markdown: MimeType.md,
|
|
238
|
+
csv: MimeType.csv,
|
|
239
|
+
json: MimeType.json,
|
|
240
|
+
zip: MimeType.zip,
|
|
241
|
+
rtf: MimeType.rtf,
|
|
242
|
+
odt: MimeType.odt,
|
|
243
|
+
xls: MimeType.xls,
|
|
244
|
+
xlsx: MimeType.xlsx,
|
|
245
|
+
ods: MimeType.ods,
|
|
246
|
+
ppt: MimeType.ppt,
|
|
247
|
+
pptx: MimeType.pptx,
|
|
248
|
+
odp: MimeType.odp,
|
|
249
|
+
};
|
|
250
|
+
return map[ext];
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const oversizedFiles: Array<string> = [];
|
|
254
|
+
|
|
93
255
|
for (const acceptedFile of acceptedFiles) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
256
|
+
if (acceptedFile.size > MAX_FILE_SIZE_BYTES) {
|
|
257
|
+
oversizedFiles.push(acceptedFile.name);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const uploadId: string = `${acceptedFile.name}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
262
|
+
addUploadStatus({
|
|
263
|
+
id: uploadId,
|
|
264
|
+
name: acceptedFile.name,
|
|
265
|
+
progress: 0,
|
|
266
|
+
status: "uploading",
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const fileModel: FileModel = new FileModel();
|
|
271
|
+
fileModel.name = acceptedFile.name;
|
|
272
|
+
const arrayBuffer: ArrayBuffer = await acceptedFile.arrayBuffer();
|
|
273
|
+
const fileBuffer: Uint8Array = new Uint8Array(arrayBuffer);
|
|
274
|
+
fileModel.file = Buffer.from(fileBuffer);
|
|
275
|
+
fileModel.isPublic = false;
|
|
276
|
+
fileModel.fileType = resolveMimeType(acceptedFile) || MimeType.txt; // default to text/plain to satisfy required field
|
|
277
|
+
|
|
278
|
+
const result: HTTPResponse<FileModel> =
|
|
279
|
+
(await ModelAPI.create<FileModel>({
|
|
280
|
+
model: fileModel,
|
|
281
|
+
modelType: FileModel,
|
|
282
|
+
requestOptions: {
|
|
283
|
+
overrideRequestUrl: CommonURL.fromURL(FILE_URL),
|
|
284
|
+
apiRequestOptions: {
|
|
285
|
+
onUploadProgress: (progressEvent: AxiosProgressEvent) => {
|
|
286
|
+
updateUploadProgress(
|
|
287
|
+
uploadId,
|
|
288
|
+
progressEvent.total,
|
|
289
|
+
progressEvent.loaded,
|
|
290
|
+
);
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
})) as HTTPResponse<FileModel>;
|
|
295
|
+
filesResult.push(result.data as FileModel);
|
|
296
|
+
removeUploadStatus(uploadId);
|
|
297
|
+
} catch (uploadErr) {
|
|
298
|
+
const friendlyMessage: string = API.getFriendlyMessage(uploadErr);
|
|
299
|
+
updateUploadStatus(uploadId, {
|
|
300
|
+
status: "error",
|
|
301
|
+
errorMessage: friendlyMessage,
|
|
302
|
+
progress: 100,
|
|
303
|
+
});
|
|
304
|
+
setError(friendlyMessage);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (oversizedFiles.length > 0) {
|
|
309
|
+
setError(buildFileSizeError(oversizedFiles));
|
|
113
310
|
}
|
|
114
311
|
|
|
115
|
-
|
|
312
|
+
if (filesResult.length > 0) {
|
|
313
|
+
const updatedFiles: Array<FileModel> = props.isMultiFilePicker
|
|
314
|
+
? [...filesModel, ...filesResult]
|
|
315
|
+
: filesResult;
|
|
116
316
|
|
|
117
|
-
|
|
118
|
-
|
|
317
|
+
setFilesModel(updatedFiles);
|
|
318
|
+
|
|
319
|
+
props.onBlur?.();
|
|
320
|
+
props.onChange?.(updatedFiles);
|
|
321
|
+
}
|
|
119
322
|
} catch (err) {
|
|
120
323
|
setError(API.getFriendlyMessage(err));
|
|
324
|
+
} finally {
|
|
325
|
+
setIsLoading(false);
|
|
121
326
|
}
|
|
122
|
-
setIsLoading(false);
|
|
123
327
|
},
|
|
124
328
|
});
|
|
125
329
|
|
|
126
330
|
type GetThumbsFunction = () => Array<ReactElement>;
|
|
127
331
|
|
|
332
|
+
const formatFileSize: FormatFileSizeFunction = (
|
|
333
|
+
file: FileModel,
|
|
334
|
+
): string | null => {
|
|
335
|
+
const buffer: Buffer | undefined = file.file;
|
|
336
|
+
if (!buffer) {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const sizeInKB: number = buffer.byteLength / 1024;
|
|
341
|
+
if (sizeInKB < 1024) {
|
|
342
|
+
return `${sizeInKB.toFixed(1)} KB`;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return `${(sizeInKB / 1024).toFixed(2)} MB`;
|
|
346
|
+
};
|
|
347
|
+
|
|
128
348
|
const getThumbs: GetThumbsFunction = (): Array<ReactElement> => {
|
|
129
349
|
return filesModel.map((file: FileModel, i: number) => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
350
|
+
const key: string = file._id?.toString() || `${file.name || "file"}-${i}`;
|
|
351
|
+
const removeFile: VoidFunction = (): void => {
|
|
352
|
+
const tempFileModel: Array<FileModel> = [...filesModel];
|
|
353
|
+
tempFileModel.splice(i, 1);
|
|
354
|
+
setFilesModel(tempFileModel);
|
|
355
|
+
props.onChange?.(tempFileModel);
|
|
356
|
+
};
|
|
133
357
|
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
358
|
+
const metadata: Array<string> = [];
|
|
359
|
+
if (file.fileType) {
|
|
360
|
+
metadata.push(file.fileType);
|
|
361
|
+
}
|
|
362
|
+
const readableSize: string | null = formatFileSize(file);
|
|
363
|
+
if (readableSize) {
|
|
364
|
+
metadata.push(readableSize);
|
|
365
|
+
}
|
|
138
366
|
|
|
139
367
|
return (
|
|
140
|
-
<div
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
src={url}
|
|
157
|
-
className="rounded"
|
|
158
|
-
style={{
|
|
159
|
-
height: "100px",
|
|
160
|
-
}}
|
|
161
|
-
/>
|
|
368
|
+
<div
|
|
369
|
+
key={key}
|
|
370
|
+
className="flex w-full items-center justify-between gap-4 rounded-lg border border-gray-200 bg-white px-3 py-2 shadow-sm"
|
|
371
|
+
>
|
|
372
|
+
<div className="flex items-start gap-3 text-left">
|
|
373
|
+
<div className="flex h-10 w-10 items-center justify-center rounded border border-gray-200 bg-gray-50">
|
|
374
|
+
<Icon icon={IconProp.File} className="text-gray-500" />
|
|
375
|
+
</div>
|
|
376
|
+
<div className="flex flex-col">
|
|
377
|
+
<p className="text-sm font-medium text-gray-900">
|
|
378
|
+
{file.name || `File ${i + 1}`}
|
|
379
|
+
</p>
|
|
380
|
+
{metadata.length > 0 && (
|
|
381
|
+
<p className="text-xs text-gray-500">{metadata.join(" • ")}</p>
|
|
382
|
+
)}
|
|
383
|
+
</div>
|
|
162
384
|
</div>
|
|
385
|
+
<button
|
|
386
|
+
type="button"
|
|
387
|
+
className="rounded-md border border-gray-200 px-3 py-1 text-xs font-medium text-gray-700 transition hover:bg-gray-50"
|
|
388
|
+
onClick={removeFile}
|
|
389
|
+
>
|
|
390
|
+
Remove
|
|
391
|
+
</button>
|
|
163
392
|
</div>
|
|
164
393
|
);
|
|
165
394
|
});
|
|
166
395
|
};
|
|
167
396
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
);
|
|
174
|
-
}
|
|
397
|
+
const hasActiveUploads: boolean = uploadStatuses.some(
|
|
398
|
+
(upload: UploadStatus) => {
|
|
399
|
+
return upload.status === "uploading";
|
|
400
|
+
},
|
|
401
|
+
);
|
|
175
402
|
|
|
176
403
|
return (
|
|
177
|
-
<div>
|
|
404
|
+
<div className="space-y-4 w-full">
|
|
178
405
|
<div
|
|
179
406
|
onClick={() => {
|
|
180
407
|
props.onClick?.();
|
|
181
408
|
props.onFocus?.();
|
|
182
409
|
}}
|
|
183
410
|
data-testid={props.dataTestId}
|
|
184
|
-
className=
|
|
411
|
+
className={`flex w-full justify-center rounded-md border-2 border-dashed px-6 py-8 transition ${props.readOnly ? "cursor-not-allowed bg-gray-50 border-gray-200" : "bg-white border-gray-300"} ${hasActiveUploads ? "ring-1 ring-indigo-200" : ""} ${isDragActive ? "border-indigo-400" : ""}`}
|
|
185
412
|
>
|
|
186
|
-
|
|
187
|
-
(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
{error && (
|
|
413
|
+
<div
|
|
414
|
+
{...getRootProps({
|
|
415
|
+
className:
|
|
416
|
+
"w-full flex flex-col items-center justify-center space-y-3 text-center",
|
|
417
|
+
"aria-busy": hasActiveUploads || isLoading,
|
|
418
|
+
})}
|
|
419
|
+
>
|
|
420
|
+
{(filesModel.length === 0 || props.isMultiFilePicker) && (
|
|
421
|
+
<>
|
|
422
|
+
<div className="flex flex-col items-center space-y-2">
|
|
423
|
+
<svg
|
|
424
|
+
className="mx-auto h-12 w-12 text-gray-400"
|
|
425
|
+
stroke="currentColor"
|
|
426
|
+
fill="none"
|
|
427
|
+
viewBox="0 0 48 48"
|
|
428
|
+
aria-hidden="true"
|
|
429
|
+
>
|
|
430
|
+
<path
|
|
431
|
+
d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
|
|
432
|
+
strokeWidth="2"
|
|
433
|
+
strokeLinecap="round"
|
|
434
|
+
strokeLinejoin="round"
|
|
435
|
+
></path>
|
|
436
|
+
</svg>
|
|
437
|
+
<div className="flex flex-col items-center text-sm text-gray-600 space-y-1">
|
|
438
|
+
<label className="relative cursor-pointer rounded-md bg-white px-4 py-2 font-medium text-indigo-600 hover:text-indigo-500">
|
|
214
439
|
<span>
|
|
215
|
-
|
|
440
|
+
{props.placeholder
|
|
441
|
+
? props.placeholder
|
|
442
|
+
: filesModel.length > 0
|
|
443
|
+
? "Add more files"
|
|
444
|
+
: "Upload files"}
|
|
216
445
|
</span>
|
|
446
|
+
<input
|
|
447
|
+
tabIndex={props.tabIndex}
|
|
448
|
+
{...(getInputProps() as any)}
|
|
449
|
+
id="file-upload"
|
|
450
|
+
name="file-upload"
|
|
451
|
+
type="file"
|
|
452
|
+
className="sr-only"
|
|
453
|
+
/>
|
|
454
|
+
</label>
|
|
455
|
+
<p className="text-gray-500">
|
|
456
|
+
{isDragActive
|
|
457
|
+
? "Release to start uploading"
|
|
458
|
+
: filesModel.length === 0
|
|
459
|
+
? "Click to choose files"
|
|
460
|
+
: "Click to add more"}{" "}
|
|
461
|
+
or drag & drop.
|
|
462
|
+
</p>
|
|
463
|
+
<p className="text-xs text-gray-500">
|
|
464
|
+
{props.mimeTypes && props.mimeTypes?.length > 0 && (
|
|
465
|
+
<span>Types: </span>
|
|
466
|
+
)}
|
|
467
|
+
{props.mimeTypes &&
|
|
468
|
+
props.mimeTypes
|
|
469
|
+
.map((type: MimeType) => {
|
|
470
|
+
const enumKey: string | undefined =
|
|
471
|
+
Object.keys(MimeType)[
|
|
472
|
+
Object.values(MimeType).indexOf(type)
|
|
473
|
+
];
|
|
474
|
+
return enumKey?.toUpperCase() || "";
|
|
475
|
+
})
|
|
476
|
+
.filter(
|
|
477
|
+
(
|
|
478
|
+
item: string | undefined,
|
|
479
|
+
pos: number,
|
|
480
|
+
array: Array<string | undefined>,
|
|
481
|
+
) => {
|
|
482
|
+
return array.indexOf(item) === pos;
|
|
483
|
+
},
|
|
484
|
+
)
|
|
485
|
+
.join(", ")}
|
|
486
|
+
{props.mimeTypes && props.mimeTypes?.length > 0 && (
|
|
487
|
+
<span>.</span>
|
|
488
|
+
)}{" "}
|
|
489
|
+
Max 10MB each.
|
|
490
|
+
</p>
|
|
491
|
+
{error && (
|
|
492
|
+
<p className="text-xs text-red-500 font-medium">{error}</p>
|
|
217
493
|
)}
|
|
218
|
-
|
|
219
|
-
{props.placeholder && !error && (
|
|
220
|
-
<span>{props.placeholder}</span>
|
|
221
|
-
)}
|
|
222
|
-
|
|
223
|
-
<input
|
|
224
|
-
tabIndex={props.tabIndex}
|
|
225
|
-
{...(getInputProps() as any)}
|
|
226
|
-
id="file-upload"
|
|
227
|
-
name="file-upload"
|
|
228
|
-
type="file"
|
|
229
|
-
className="sr-only"
|
|
230
|
-
/>
|
|
231
|
-
</label>
|
|
232
|
-
<p className="pl-1">or drag and drop</p>
|
|
494
|
+
</div>
|
|
233
495
|
</div>
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
)}
|
|
238
|
-
{props.mimeTypes &&
|
|
239
|
-
props.mimeTypes
|
|
240
|
-
.map((type: MimeType) => {
|
|
241
|
-
const enumKey: string | undefined =
|
|
242
|
-
Object.keys(MimeType)[
|
|
243
|
-
Object.values(MimeType).indexOf(type)
|
|
244
|
-
];
|
|
245
|
-
return enumKey?.toUpperCase() || "";
|
|
246
|
-
})
|
|
247
|
-
.filter(
|
|
248
|
-
(
|
|
249
|
-
item: string | undefined,
|
|
250
|
-
pos: number,
|
|
251
|
-
array: Array<string | undefined>,
|
|
252
|
-
) => {
|
|
253
|
-
return array.indexOf(item) === pos;
|
|
254
|
-
},
|
|
255
|
-
)
|
|
256
|
-
.join(", ")}
|
|
257
|
-
{props.mimeTypes && props.mimeTypes?.length > 0 && (
|
|
258
|
-
<span>.</span>
|
|
259
|
-
)}
|
|
260
|
-
10 MB or less.
|
|
261
|
-
</p>
|
|
262
|
-
</div>
|
|
263
|
-
))}
|
|
264
|
-
<aside>{getThumbs()}</aside>
|
|
496
|
+
</>
|
|
497
|
+
)}
|
|
498
|
+
</div>
|
|
265
499
|
</div>
|
|
500
|
+
{uploadStatuses.length > 0 && (
|
|
501
|
+
<div className="space-y-2 w-full">
|
|
502
|
+
<p className="text-sm font-medium text-gray-700 text-left">
|
|
503
|
+
{hasActiveUploads ? "Uploading files" : "Upload status"}
|
|
504
|
+
</p>
|
|
505
|
+
<div className="space-y-2">
|
|
506
|
+
{uploadStatuses.map((upload: UploadStatus) => {
|
|
507
|
+
return (
|
|
508
|
+
<div
|
|
509
|
+
key={upload.id}
|
|
510
|
+
className={`rounded border px-3 py-2 ${upload.status === "error" ? "border-red-200 bg-red-50" : "border-gray-200 bg-white"}`}
|
|
511
|
+
>
|
|
512
|
+
<div className="flex items-center justify-between text-sm">
|
|
513
|
+
<p className="font-medium text-gray-800 truncate">
|
|
514
|
+
{upload.name}
|
|
515
|
+
</p>
|
|
516
|
+
<span
|
|
517
|
+
className={`text-xs ${upload.status === "error" ? "text-red-600" : "text-gray-500"}`}
|
|
518
|
+
>
|
|
519
|
+
{upload.status === "error"
|
|
520
|
+
? "Failed"
|
|
521
|
+
: `${upload.progress}%`}
|
|
522
|
+
</span>
|
|
523
|
+
</div>
|
|
524
|
+
<div className="mt-2 h-2 rounded bg-gray-200 overflow-hidden">
|
|
525
|
+
<div
|
|
526
|
+
className={`h-full transition-all duration-300 ${upload.status === "error" ? "bg-red-400" : "bg-indigo-500"}`}
|
|
527
|
+
style={{ width: `${Math.min(upload.progress, 100)}%` }}
|
|
528
|
+
></div>
|
|
529
|
+
</div>
|
|
530
|
+
{upload.status === "error" && upload.errorMessage && (
|
|
531
|
+
<p className="mt-2 text-xs text-red-600 text-left">
|
|
532
|
+
{upload.errorMessage}
|
|
533
|
+
</p>
|
|
534
|
+
)}
|
|
535
|
+
{upload.status === "error" && (
|
|
536
|
+
<div className="mt-2 text-right">
|
|
537
|
+
<button
|
|
538
|
+
type="button"
|
|
539
|
+
className="text-xs font-medium text-gray-600 hover:text-gray-800"
|
|
540
|
+
onClick={() => {
|
|
541
|
+
removeUploadStatus(upload.id);
|
|
542
|
+
}}
|
|
543
|
+
>
|
|
544
|
+
Dismiss
|
|
545
|
+
</button>
|
|
546
|
+
</div>
|
|
547
|
+
)}
|
|
548
|
+
</div>
|
|
549
|
+
);
|
|
550
|
+
})}
|
|
551
|
+
</div>
|
|
552
|
+
</div>
|
|
553
|
+
)}
|
|
554
|
+
{filesModel.length > 0 && (
|
|
555
|
+
<div className="space-y-2 w-full">
|
|
556
|
+
<p className="text-sm font-medium text-gray-700 text-left">
|
|
557
|
+
Uploaded files
|
|
558
|
+
</p>
|
|
559
|
+
<div className="flex flex-wrap gap-4">{getThumbs()}</div>
|
|
560
|
+
</div>
|
|
561
|
+
)}
|
|
266
562
|
{props.error && (
|
|
267
|
-
<p data-testid="error-message" className="
|
|
563
|
+
<p data-testid="error-message" className="text-sm text-red-400">
|
|
268
564
|
{props.error}
|
|
269
565
|
</p>
|
|
270
566
|
)}
|