mbkbucket 3.5.1 → 3.6.0

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.
Files changed (42) hide show
  1. package/.env.example +14 -14
  2. package/.mbkbucket.config.example.json +14 -14
  3. package/LICENSE +20 -20
  4. package/README.md +377 -350
  5. package/index.js +30 -30
  6. package/lib/cli.js +649 -649
  7. package/lib/public/bucketadmin.css +1420 -1420
  8. package/lib/public/bucketadmin.js +1229 -1229
  9. package/lib/public/mbkbucket-helper-demo.html +1184 -1184
  10. package/lib/public/mbkbucket-helper.css +1235 -1235
  11. package/lib/public/mbkbucket-helper.js +1557 -1557
  12. package/lib/src/app.js +109 -109
  13. package/lib/src/cli/auth-helper.js +150 -150
  14. package/lib/src/config/index.js +112 -112
  15. package/lib/src/config/validation.js +73 -73
  16. package/lib/src/controllers/api.controller.js +410 -410
  17. package/lib/src/controllers/dashboard.controller.js +48 -48
  18. package/lib/src/controllers/info.controller.js +34 -34
  19. package/lib/src/controllers/view.controller.js +233 -233
  20. package/lib/src/middleware/async-handler.js +23 -23
  21. package/lib/src/middleware/bucket-resolver.js +37 -37
  22. package/lib/src/middleware/error-handler.js +54 -54
  23. package/lib/src/middleware/rate-limiter.js +22 -22
  24. package/lib/src/middleware/security.js +27 -27
  25. package/lib/src/routes/api.routes.js +54 -40
  26. package/lib/src/routes/index.js +40 -21
  27. package/lib/src/routes/info.routes.js +9 -9
  28. package/lib/src/routes/view.routes.js +25 -20
  29. package/lib/src/services/s3.service.js +603 -603
  30. package/lib/src/utils/errors.js +54 -54
  31. package/lib/src/utils/helpers.js +251 -251
  32. package/lib/src/utils/logger.js +17 -17
  33. package/package.json +83 -83
  34. package/views/bucket.handlebars +196 -196
  35. package/views/bucketadmin_alerts.handlebars +15 -15
  36. package/views/bucketadmin_delete_modal.handlebars +36 -36
  37. package/views/bucketadmin_filelist_skeleton.handlebars +32 -32
  38. package/views/bucketadmin_newfolder_modal.handlebars +29 -29
  39. package/views/bucketadmin_preview_modal.handlebars +40 -40
  40. package/views/bucketadmin_upload_modal.handlebars +54 -54
  41. package/views/bucketadmincontent.handlebars +141 -141
  42. package/views/mbkbucket_info.handlebars +232 -232
@@ -1,36 +1,36 @@
1
- <!-- Delete Confirmation Modal -->
2
- <div class="modal" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
3
- <div class="modal-dialog modal-dialog-centered">
4
- <div class="modal-content">
5
- <div class="modal-header">
6
- <h5 class="modal-title" id="deleteModalLabel">
7
- <svg width="18" height="18" viewBox="0 0 16 16" fill="none" stroke="var(--red)" stroke-width="1.5" class="modal-title-icon"><path d="M3 4h10M6 4V2h4v2M5 4l1 9h4l1-9"/></svg>
8
- Confirm Delete
9
- </h5>
10
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" onclick="closeModal()">&times;</button>
11
- </div>
12
- <div class="modal-body text-center py-4">
13
- <div class="delete-icon-wrap mb-3">
14
- <svg width="44" height="44" viewBox="0 0 16 16" fill="none" stroke="var(--red)" stroke-width="1.5"><path d="M3 4h10M6 4V2h4v2M5 4l1 9h4l1-9"/></svg>
15
- </div>
16
- <h6 class="modal-confirm-heading">Are you sure you want to delete this?</h6>
17
- <p class="modal-item-name-preview mt-2">
18
- <code id="deleteFileName"></code>
19
- </p>
20
- <p class="text-muted small mt-2">This action cannot be undone.</p>
21
- </div>
22
- <div class="modal-footer">
23
- <button type="button" class="btn btn-outline" onclick="closeModal()">Cancel</button>
24
- <form id="deleteForm" method="post" action="/mbkbucket/delete" style="display: inline;">
25
- <input type="hidden" name="key" id="deleteKey">
26
- <input type="hidden" name="prefix" value="{{prefix}}">
27
- <input type="hidden" name="folder" id="deleteIsFolder" value="">
28
- <button type="submit" class="btn btn-danger" id="deleteConfirmButton">
29
- <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 4h10M6 4V2h4v2M5 4l1 9h4l1-9"/></svg>
30
- <span id="deleteConfirmLabel">Delete</span>
31
- </button>
32
- </form>
33
- </div>
34
- </div>
35
- </div>
36
- </div>
1
+ <!-- Delete Confirmation Modal -->
2
+ <div class="modal" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
3
+ <div class="modal-dialog modal-dialog-centered">
4
+ <div class="modal-content">
5
+ <div class="modal-header">
6
+ <h5 class="modal-title" id="deleteModalLabel">
7
+ <svg width="18" height="18" viewBox="0 0 16 16" fill="none" stroke="var(--red)" stroke-width="1.5" class="modal-title-icon"><path d="M3 4h10M6 4V2h4v2M5 4l1 9h4l1-9"/></svg>
8
+ Confirm Delete
9
+ </h5>
10
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" onclick="closeModal()">&times;</button>
11
+ </div>
12
+ <div class="modal-body text-center py-4">
13
+ <div class="delete-icon-wrap mb-3">
14
+ <svg width="44" height="44" viewBox="0 0 16 16" fill="none" stroke="var(--red)" stroke-width="1.5"><path d="M3 4h10M6 4V2h4v2M5 4l1 9h4l1-9"/></svg>
15
+ </div>
16
+ <h6 class="modal-confirm-heading">Are you sure you want to delete this?</h6>
17
+ <p class="modal-item-name-preview mt-2">
18
+ <code id="deleteFileName"></code>
19
+ </p>
20
+ <p class="text-muted small mt-2">This action cannot be undone.</p>
21
+ </div>
22
+ <div class="modal-footer">
23
+ <button type="button" class="btn btn-outline" onclick="closeModal()">Cancel</button>
24
+ <form id="deleteForm" method="post" action="/mbkbucket/delete" style="display: inline;">
25
+ <input type="hidden" name="key" id="deleteKey">
26
+ <input type="hidden" name="prefix" value="{{prefix}}">
27
+ <input type="hidden" name="folder" id="deleteIsFolder" value="">
28
+ <button type="submit" class="btn btn-danger" id="deleteConfirmButton">
29
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 4h10M6 4V2h4v2M5 4l1 9h4l1-9"/></svg>
30
+ <span id="deleteConfirmLabel">Delete</span>
31
+ </button>
32
+ </form>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ </div>
@@ -1,32 +1,32 @@
1
- <div class="fm-skeleton-grid" aria-label="Loading files" aria-busy="true">
2
- <div class="fm-skeleton-card">
3
- <div class="fm-skeleton-icon"></div>
4
- <div class="fm-skeleton-line w-75"></div>
5
- <div class="fm-skeleton-line w-50"></div>
6
- </div>
7
- <div class="fm-skeleton-card">
8
- <div class="fm-skeleton-icon"></div>
9
- <div class="fm-skeleton-line w-75"></div>
10
- <div class="fm-skeleton-line w-50"></div>
11
- </div>
12
- <div class="fm-skeleton-card">
13
- <div class="fm-skeleton-icon"></div>
14
- <div class="fm-skeleton-line w-75"></div>
15
- <div class="fm-skeleton-line w-50"></div>
16
- </div>
17
- <div class="fm-skeleton-card">
18
- <div class="fm-skeleton-icon"></div>
19
- <div class="fm-skeleton-line w-75"></div>
20
- <div class="fm-skeleton-line w-50"></div>
21
- </div>
22
- <div class="fm-skeleton-card">
23
- <div class="fm-skeleton-icon"></div>
24
- <div class="fm-skeleton-line w-75"></div>
25
- <div class="fm-skeleton-line w-50"></div>
26
- </div>
27
- <div class="fm-skeleton-card">
28
- <div class="fm-skeleton-icon"></div>
29
- <div class="fm-skeleton-line w-75"></div>
30
- <div class="fm-skeleton-line w-50"></div>
31
- </div>
32
- </div>
1
+ <div class="fm-skeleton-grid" aria-label="Loading files" aria-busy="true">
2
+ <div class="fm-skeleton-card">
3
+ <div class="fm-skeleton-icon"></div>
4
+ <div class="fm-skeleton-line w-75"></div>
5
+ <div class="fm-skeleton-line w-50"></div>
6
+ </div>
7
+ <div class="fm-skeleton-card">
8
+ <div class="fm-skeleton-icon"></div>
9
+ <div class="fm-skeleton-line w-75"></div>
10
+ <div class="fm-skeleton-line w-50"></div>
11
+ </div>
12
+ <div class="fm-skeleton-card">
13
+ <div class="fm-skeleton-icon"></div>
14
+ <div class="fm-skeleton-line w-75"></div>
15
+ <div class="fm-skeleton-line w-50"></div>
16
+ </div>
17
+ <div class="fm-skeleton-card">
18
+ <div class="fm-skeleton-icon"></div>
19
+ <div class="fm-skeleton-line w-75"></div>
20
+ <div class="fm-skeleton-line w-50"></div>
21
+ </div>
22
+ <div class="fm-skeleton-card">
23
+ <div class="fm-skeleton-icon"></div>
24
+ <div class="fm-skeleton-line w-75"></div>
25
+ <div class="fm-skeleton-line w-50"></div>
26
+ </div>
27
+ <div class="fm-skeleton-card">
28
+ <div class="fm-skeleton-icon"></div>
29
+ <div class="fm-skeleton-line w-75"></div>
30
+ <div class="fm-skeleton-line w-50"></div>
31
+ </div>
32
+ </div>
@@ -1,29 +1,29 @@
1
- <!-- New Folder Modal -->
2
- <div class="modal" id="newFolderModal" tabindex="-1" aria-labelledby="newFolderModalLabel" aria-hidden="true">
3
- <div class="modal-dialog modal-dialog-centered">
4
- <div class="modal-content">
5
- <div class="modal-header">
6
- <h5 class="modal-title" id="newFolderModalLabel">
7
- <svg width="18" height="18" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="modal-title-icon"><path d="M2 4a1 1 0 011-1h3l2 2h5a1 1 0 011 1v6a1 1 0 01-1 1H3a1 1 0 01-1-1V4z"/><path d="M8 7v4M6 9h4"/></svg>
8
- New Folder
9
- </h5>
10
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" onclick="closeNewFolderModal()">&times;</button>
11
- </div>
12
- <div class="modal-body">
13
- <form id="newFolderForm" onsubmit="event.preventDefault(); submitCreateFolder();">
14
- <div class="modal-form-row">
15
- <label for="newFolderNameInput" class="form-label">Folder Name</label>
16
- <input type="text" id="newFolderNameInput" class="form-control" placeholder="e.g. documents, images, backup" autocomplete="off" required>
17
- <div class="form-text">Folders are created in the current directory path.</div>
18
- </div>
19
- </form>
20
- </div>
21
- <div class="modal-footer">
22
- <button type="button" class="btn btn-outline" onclick="closeNewFolderModal()">Cancel</button>
23
- <button type="button" class="btn btn-primary" id="submitNewFolderBtn" onclick="submitCreateFolder()">
24
- Create Folder
25
- </button>
26
- </div>
27
- </div>
28
- </div>
29
- </div>
1
+ <!-- New Folder Modal -->
2
+ <div class="modal" id="newFolderModal" tabindex="-1" aria-labelledby="newFolderModalLabel" aria-hidden="true">
3
+ <div class="modal-dialog modal-dialog-centered">
4
+ <div class="modal-content">
5
+ <div class="modal-header">
6
+ <h5 class="modal-title" id="newFolderModalLabel">
7
+ <svg width="18" height="18" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="modal-title-icon"><path d="M2 4a1 1 0 011-1h3l2 2h5a1 1 0 011 1v6a1 1 0 01-1 1H3a1 1 0 01-1-1V4z"/><path d="M8 7v4M6 9h4"/></svg>
8
+ New Folder
9
+ </h5>
10
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" onclick="closeNewFolderModal()">&times;</button>
11
+ </div>
12
+ <div class="modal-body">
13
+ <form id="newFolderForm" onsubmit="event.preventDefault(); submitCreateFolder();">
14
+ <div class="modal-form-row">
15
+ <label for="newFolderNameInput" class="form-label">Folder Name</label>
16
+ <input type="text" id="newFolderNameInput" class="form-control" placeholder="e.g. documents, images, backup" autocomplete="off" required>
17
+ <div class="form-text">Folders are created in the current directory path.</div>
18
+ </div>
19
+ </form>
20
+ </div>
21
+ <div class="modal-footer">
22
+ <button type="button" class="btn btn-outline" onclick="closeNewFolderModal()">Cancel</button>
23
+ <button type="button" class="btn btn-primary" id="submitNewFolderBtn" onclick="submitCreateFolder()">
24
+ Create Folder
25
+ </button>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </div>
@@ -1,40 +1,40 @@
1
- <!-- Preview Modal -->
2
- <div class="modal" id="previewModal" role="dialog" aria-modal="true" aria-labelledby="previewModalLabel" tabindex="-1">
3
- <div class="modal-dialog modal-dialog-centered preview-modal-dialog" role="document">
4
- <div class="modal-content">
5
- <div class="modal-header">
6
- <h5 class="modal-title" id="previewModalLabel">
7
- <svg width="18" height="18" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="modal-title-icon"><path d="M1 8s3-5 7-5 7 5 7 5-3 5-7 5-7-5-7-5z"/><circle cx="8" cy="8" r="2.5"/></svg>
8
- <span id="previewFileName">Preview</span>
9
- </h5>
10
- <div class="preview-header-controls">
11
- <button id="previewFullscreenBtn" class="btn btn-icon btn-outline btn-sm" type="button" aria-label="Toggle fullscreen" title="Fullscreen">
12
- <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 5V2h3M14 5V2h-3M2 11v3h3M14 11v3h-3"/></svg>
13
- </button>
14
- <button type="button" class="btn-close" aria-label="Close preview" id="previewCloseBtn" onclick="closePreviewModal()">&times;</button>
15
- </div>
16
- </div>
17
- <div class="modal-body p-0">
18
- <div class="preview-content" id="previewContent" aria-live="polite">
19
- <div class="empty-preview">
20
- <div class="spinner-border" role="status"></div>
21
- <div class="modal-header-spacing mt-2">Loading preview...</div>
22
- </div>
23
- </div>
24
- </div>
25
- <div class="modal-footer preview-footer">
26
- <div class="preview-actions">
27
- <a id="previewOpenBtn" class="btn btn-outline btn-sm" target="_blank" rel="noopener noreferrer" href="#">
28
- <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 3H3a1 1 0 00-1 1v9a1 1 0 001 1h9a1 1 0 001-1v-3M10 2h4v4M9 7l5-5"/></svg>
29
- Open in new tab
30
- </a>
31
- <a id="previewDownloadBtn" class="btn btn-primary btn-sm" download href="#">
32
- <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 11v3H2v-3"/><path d="M11 6L8 3 5 6"/><line x1="8" y1="3" x2="8" y2="11"/></svg>
33
- Download
34
- </a>
35
- <button type="button" class="btn btn-outline btn-sm" id="previewCloseFooter" onclick="closePreviewModal()">Close</button>
36
- </div>
37
- </div>
38
- </div>
39
- </div>
40
- </div>
1
+ <!-- Preview Modal -->
2
+ <div class="modal" id="previewModal" role="dialog" aria-modal="true" aria-labelledby="previewModalLabel" tabindex="-1">
3
+ <div class="modal-dialog modal-dialog-centered preview-modal-dialog" role="document">
4
+ <div class="modal-content">
5
+ <div class="modal-header">
6
+ <h5 class="modal-title" id="previewModalLabel">
7
+ <svg width="18" height="18" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="modal-title-icon"><path d="M1 8s3-5 7-5 7 5 7 5-3 5-7 5-7-5-7-5z"/><circle cx="8" cy="8" r="2.5"/></svg>
8
+ <span id="previewFileName">Preview</span>
9
+ </h5>
10
+ <div class="preview-header-controls">
11
+ <button id="previewFullscreenBtn" class="btn btn-icon btn-outline btn-sm" type="button" aria-label="Toggle fullscreen" title="Fullscreen">
12
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 5V2h3M14 5V2h-3M2 11v3h3M14 11v3h-3"/></svg>
13
+ </button>
14
+ <button type="button" class="btn-close" aria-label="Close preview" id="previewCloseBtn" onclick="closePreviewModal()">&times;</button>
15
+ </div>
16
+ </div>
17
+ <div class="modal-body p-0">
18
+ <div class="preview-content" id="previewContent" aria-live="polite">
19
+ <div class="empty-preview">
20
+ <div class="spinner-border" role="status"></div>
21
+ <div class="modal-header-spacing mt-2">Loading preview...</div>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ <div class="modal-footer preview-footer">
26
+ <div class="preview-actions">
27
+ <a id="previewOpenBtn" class="btn btn-outline btn-sm" target="_blank" rel="noopener noreferrer" href="#">
28
+ <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 3H3a1 1 0 00-1 1v9a1 1 0 001 1h9a1 1 0 001-1v-3M10 2h4v4M9 7l5-5"/></svg>
29
+ Open in new tab
30
+ </a>
31
+ <a id="previewDownloadBtn" class="btn btn-primary btn-sm" download href="#">
32
+ <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 11v3H2v-3"/><path d="M11 6L8 3 5 6"/><line x1="8" y1="3" x2="8" y2="11"/></svg>
33
+ Download
34
+ </a>
35
+ <button type="button" class="btn btn-outline btn-sm" id="previewCloseFooter" onclick="closePreviewModal()">Close</button>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ </div>
@@ -1,54 +1,54 @@
1
- <!-- Upload Modal -->
2
- <div class="modal" id="uploadModal" tabindex="-1" aria-labelledby="uploadModalLabel" aria-hidden="true">
3
- <div class="modal-dialog modal-dialog-centered modal-lg">
4
- <div class="modal-content">
5
- <div class="modal-header">
6
- <h5 class="modal-title" id="uploadModalLabel">
7
- <svg width="18" height="18" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="modal-title-icon"><path d="M14 11v3H2v-3"/><path d="M11 6L8 3 5 6"/><line x1="8" y1="3" x2="8" y2="11"/></svg>
8
- Upload Files
9
- </h5>
10
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" onclick="closeUploadModal()">&times;</button>
11
- </div>
12
- <div class="modal-body">
13
- <form id="uploadForm" action="/mbkbucket/upload" method="post" enctype="multipart/form-data">
14
- <input type="hidden" name="prefix" value="{{prefix}}" id="uploadPrefixInput">
15
-
16
- <!-- Drop Zone -->
17
- <div id="dropZone" class="upload-drop-zone">
18
- <div class="drop-zone-content">
19
- <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="drop-icon"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
20
- <div class="drop-text">Drag files here, or <span class="drop-browse-link">browse</span></div>
21
- <div class="drop-hint">Supports multiple files &amp; large chunked uploads</div>
22
- </div>
23
- <input type="file" class="form-control d-none" id="fileInput" name="files" multiple>
24
- </div>
25
-
26
- <!-- Upload Queue List -->
27
- <div id="uploadQueue" class="upload-queue mt-3"></div>
28
-
29
- <!-- Upload Progress -->
30
- <div id="uploadProgress" class="upload-progress-container mt-3" style="display: none;">
31
- <div class="d-flex justify-content-between align-items-center mb-1">
32
- <span class="upload-status-label" id="uploadStatusLabel">Uploading...</span>
33
- <span id="uploadPercent" class="upload-percent">0%</span>
34
- </div>
35
- <div class="progress-bar-wrap">
36
- <div id="progressBar" class="progress-bar-inner" style="width: 0%;"></div>
37
- </div>
38
- <div class="upload-meta-info mt-1">
39
- <small id="uploadDetails" class="text-muted"></small>
40
- </div>
41
- </div>
42
- </form>
43
- </div>
44
- <div class="modal-footer">
45
- <button type="button" class="btn btn-outline" onclick="closeUploadModal()">Cancel</button>
46
- <button type="button" class="btn btn-outline" id="clearQueueBtn" style="display: none;">Clear Queue</button>
47
- <button type="button" class="btn btn-primary" id="startUploadBtn">
48
- <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 11v3H2v-3"/><path d="M11 6L8 3 5 6"/><line x1="8" y1="3" x2="8" y2="11"/></svg>
49
- Start Upload
50
- </button>
51
- </div>
52
- </div>
53
- </div>
54
- </div>
1
+ <!-- Upload Modal -->
2
+ <div class="modal" id="uploadModal" tabindex="-1" aria-labelledby="uploadModalLabel" aria-hidden="true">
3
+ <div class="modal-dialog modal-dialog-centered modal-lg">
4
+ <div class="modal-content">
5
+ <div class="modal-header">
6
+ <h5 class="modal-title" id="uploadModalLabel">
7
+ <svg width="18" height="18" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="modal-title-icon"><path d="M14 11v3H2v-3"/><path d="M11 6L8 3 5 6"/><line x1="8" y1="3" x2="8" y2="11"/></svg>
8
+ Upload Files
9
+ </h5>
10
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" onclick="closeUploadModal()">&times;</button>
11
+ </div>
12
+ <div class="modal-body">
13
+ <form id="uploadForm" action="/mbkbucket/upload" method="post" enctype="multipart/form-data">
14
+ <input type="hidden" name="prefix" value="{{prefix}}" id="uploadPrefixInput">
15
+
16
+ <!-- Drop Zone -->
17
+ <div id="dropZone" class="upload-drop-zone">
18
+ <div class="drop-zone-content">
19
+ <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="drop-icon"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
20
+ <div class="drop-text">Drag files here, or <span class="drop-browse-link">browse</span></div>
21
+ <div class="drop-hint">Supports multiple files &amp; large chunked uploads</div>
22
+ </div>
23
+ <input type="file" class="form-control d-none" id="fileInput" name="files" multiple>
24
+ </div>
25
+
26
+ <!-- Upload Queue List -->
27
+ <div id="uploadQueue" class="upload-queue mt-3"></div>
28
+
29
+ <!-- Upload Progress -->
30
+ <div id="uploadProgress" class="upload-progress-container mt-3" style="display: none;">
31
+ <div class="d-flex justify-content-between align-items-center mb-1">
32
+ <span class="upload-status-label" id="uploadStatusLabel">Uploading...</span>
33
+ <span id="uploadPercent" class="upload-percent">0%</span>
34
+ </div>
35
+ <div class="progress-bar-wrap">
36
+ <div id="progressBar" class="progress-bar-inner" style="width: 0%;"></div>
37
+ </div>
38
+ <div class="upload-meta-info mt-1">
39
+ <small id="uploadDetails" class="text-muted"></small>
40
+ </div>
41
+ </div>
42
+ </form>
43
+ </div>
44
+ <div class="modal-footer">
45
+ <button type="button" class="btn btn-outline" onclick="closeUploadModal()">Cancel</button>
46
+ <button type="button" class="btn btn-outline" id="clearQueueBtn" style="display: none;">Clear Queue</button>
47
+ <button type="button" class="btn btn-primary" id="startUploadBtn">
48
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 11v3H2v-3"/><path d="M11 6L8 3 5 6"/><line x1="8" y1="3" x2="8" y2="11"/></svg>
49
+ Start Upload
50
+ </button>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </div>