mbkbucket 3.8.2 → 4.0.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 (38) hide show
  1. package/.env.example +15 -14
  2. package/README.md +561 -377
  3. package/index.js +32 -30
  4. package/lib/cli.js +154 -565
  5. package/lib/public/bucketadmin.css +2042 -1708
  6. package/lib/public/bucketadmin.js +1622 -1286
  7. package/lib/public/mbkbucket-helper-demo.html +4 -24
  8. package/lib/public/mbkbucket-helper.css +28 -34
  9. package/lib/public/mbkbucket-helper.js +4 -3
  10. package/lib/src/config/validation.js +86 -73
  11. package/lib/src/controllers/api.controller.js +459 -410
  12. package/lib/src/controllers/dashboard.controller.js +69 -49
  13. package/lib/src/controllers/info.controller.js +34 -3
  14. package/lib/src/controllers/view.controller.js +233 -231
  15. package/lib/src/core/storage/errors.js +62 -0
  16. package/lib/src/core/storage/gdrive-auth.js +193 -0
  17. package/lib/src/core/storage/gdrive-provider.js +656 -0
  18. package/lib/src/core/storage/index.js +14 -0
  19. package/lib/src/core/storage/models.js +80 -0
  20. package/lib/src/core/storage/path-strategy.js +65 -0
  21. package/lib/src/core/storage/s3-provider.js +491 -0
  22. package/lib/src/core/storage/storage-manager.js +203 -0
  23. package/lib/src/core/storage/storage-provider.js +217 -0
  24. package/lib/src/middleware/bucket-resolver.js +37 -37
  25. package/lib/src/routes/api.routes.js +5 -1
  26. package/lib/src/routes/index.js +60 -60
  27. package/lib/src/routes/view.routes.js +28 -25
  28. package/lib/src/services/s3.service.js +42 -594
  29. package/lib/src/services/storage.service.js +213 -0
  30. package/lib/src/utils/errors.js +56 -46
  31. package/package.json +11 -12
  32. package/views/bucket.hbs +47 -71
  33. package/views/bucketadmin_move_modal.hbs +35 -0
  34. package/views/bucketadmin_preview_modal.hbs +108 -40
  35. package/views/bucketadmincontent.hbs +193 -144
  36. package/views/mbkbucket_info.hbs +134 -232
  37. package/.mbkbucket.config.example.json +0 -14
  38. package/lib/src/cli/auth-helper.js +0 -141
@@ -1,40 +1,108 @@
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
+ <!-- Enhanced 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 preview-modal-content">
5
+ <div class="modal-header preview-modal-header">
6
+ <div class="preview-header-left">
7
+ <h5 class="modal-title" id="previewModalLabel">
8
+ <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>
9
+ <span id="previewFileName">Preview</span>
10
+ </h5>
11
+ <div class="preview-tabs ms-3" role="tablist">
12
+ <button type="button" class="preview-tab active" id="tab-preview-btn" data-preview-tab="preview">Preview</button>
13
+ <button type="button" class="preview-tab" id="tab-info-btn" data-preview-tab="info">File Info</button>
14
+ </div>
15
+ </div>
16
+ <div class="preview-header-controls">
17
+ <div id="previewExtraControls" class="d-inline-flex align-items-center gap-1"></div>
18
+ <button id="previewFullscreenBtn" class="btn btn-icon btn-outline btn-sm" type="button" aria-label="Toggle fullscreen" title="Fullscreen">
19
+ <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>
20
+ </button>
21
+ <button type="button" class="btn-close" aria-label="Close preview" id="previewCloseBtn" onclick="closePreviewModal()">&times;</button>
22
+ </div>
23
+ </div>
24
+ <div class="modal-body p-0 preview-modal-body">
25
+ <!-- Preview Tab View -->
26
+ <div class="preview-tab-pane active" id="pane-preview">
27
+ <div class="preview-content" id="previewContent" aria-live="polite">
28
+ <div class="empty-preview">
29
+ <div class="spinner-border" role="status"></div>
30
+ <div class="modal-header-spacing mt-2">Loading preview...</div>
31
+ </div>
32
+ </div>
33
+ </div>
34
+
35
+ <!-- Info & Metadata Tab View -->
36
+ <div class="preview-tab-pane" id="pane-info" style="display: none;">
37
+ <div class="p-4 file-info-pane">
38
+ <div class="row g-3">
39
+ <div class="col-md-6">
40
+ <div class="info-card">
41
+ <div class="info-label">File Key</div>
42
+ <div class="info-value font-mono text-break" id="infoFileKey">—</div>
43
+ </div>
44
+ </div>
45
+ <div class="col-md-3">
46
+ <div class="info-card">
47
+ <div class="info-label">Size</div>
48
+ <div class="info-value font-semibold" id="infoFileSize">—</div>
49
+ </div>
50
+ </div>
51
+ <div class="col-md-3">
52
+ <div class="info-card">
53
+ <div class="info-label">MIME Type</div>
54
+ <div class="info-value" id="infoMimeType">—</div>
55
+ </div>
56
+ </div>
57
+ <div class="col-md-6">
58
+ <div class="info-card">
59
+ <div class="info-label">Last Modified</div>
60
+ <div class="info-value" id="infoLastModified">—</div>
61
+ </div>
62
+ </div>
63
+ <div class="col-md-6">
64
+ <div class="info-card">
65
+ <div class="info-label">ETag / Checksum</div>
66
+ <div class="info-value font-mono text-break" id="infoETag">—</div>
67
+ </div>
68
+ </div>
69
+ <div class="col-md-12" id="infoPublicLinkCard" style="{{#unless publicViewEnabled}}display: none;{{/unless}}">
70
+ <div class="info-card">
71
+ <div class="info-label">Public View URL</div>
72
+ <div class="info-value font-mono text-break d-flex align-items-center justify-content-between gap-2">
73
+ <span id="infoPublicUrl" class="text-truncate" style="user-select: all;">—</span>
74
+ <button type="button" class="btn btn-outline btn-sm flex-shrink-0" onclick="copyCurrentPreviewPublicLink()" title="Copy Public URL">
75
+ <svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M5 2h8a1 1 0 011 1v8M3 5h8a1 1 0 011 1v8a1 1 0 01-1 1H3a1 1 0 01-1-1V6a1 1 0 011-1z"/></svg>
76
+ Copy
77
+ </button>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ <div class="modal-footer preview-footer">
86
+ <div class="preview-actions">
87
+ <button type="button" class="btn btn-outline btn-sm" id="previewCopyLinkBtn" onclick="copyCurrentPreviewLink()">
88
+ <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M5 2h8a1 1 0 011 1v8M3 5h8a1 1 0 011 1v8a1 1 0 01-1 1H3a1 1 0 01-1-1V6a1 1 0 011-1z"/></svg>
89
+ Copy Link
90
+ </button>
91
+ <button type="button" class="btn btn-outline btn-sm" id="previewCopyPublicLinkBtn" onclick="copyCurrentPreviewPublicLink()" title="Copy publicly accessible link" style="{{#unless publicViewEnabled}}display: none;{{/unless}}">
92
+ <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6.25"/><path d="M1.75 8h12.5M8 1.75a10.5 10.5 0 010 12.5 10.5 10.5 0 010-12.5z"/></svg>
93
+ Copy Public Link
94
+ </button>
95
+ <a id="previewOpenBtn" class="btn btn-outline btn-sm" target="_blank" rel="noopener noreferrer" href="#">
96
+ <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>
97
+ Open in new tab
98
+ </a>
99
+ <a id="previewDownloadBtn" class="btn btn-primary btn-sm" download href="#">
100
+ <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="M5 8l3 3 3-3"/><line x1="8" y1="2" x2="8" y2="11"/></svg>
101
+ Download
102
+ </a>
103
+ <button type="button" class="btn btn-outline btn-sm" id="previewCloseFooter" onclick="closePreviewModal()">Close</button>
104
+ </div>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ </div>
@@ -1,145 +1,194 @@
1
- {{!-- Gentelella v4 File Management System - Bucket Dashboard --}}
2
- <link rel="stylesheet" href="/mbkbucket/bucketadmin.css{{mbkbucket_cachebuster}}">
3
- <div id="bucketAppData" data-app-name="{{APP_NAME}}" hidden></div>
4
-
5
- <div class="page-wrapper">
6
- <!-- Page Header -->
7
- <div class="page-header">
8
- <div class="page-header-row">
9
- <div>
10
- <div class="page-pretitle">Storage &amp; Buckets</div>
11
- <h1 class="page-title">Files</h1>
12
- </div>
13
- <div class="page-actions">
14
- <button type="button" class="btn btn-outline" id="new-folder-btn" data-action="create-folder">
15
- <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><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>
16
- New folder
17
- </button>
18
- <button type="button" class="btn btn-primary" id="upload-btn" data-action="upload-files">
19
- <svg width="15" height="15" 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>
20
- Upload
21
- </button>
22
- </div>
23
- </div>
24
- </div>
25
-
26
- {{> bucketadmin_alerts }}
27
-
28
- <!-- Main File Manager Card -->
29
- <div class="card file-manager">
30
- <div class="fm-layout">
31
- <!-- Tree sidebar -->
32
- <aside class="fm-tree-wrap">
33
- <div class="fm-tree-header">
34
- <span class="fm-tree-title">Explorer</span>
35
- </div>
36
- <div id="fm-tree" class="fm-tree"></div>
37
-
38
- <div class="fm-storage">
39
- <div class="fm-storage-label">Connected Bucket</div>
40
- <div class="bucket-switcher-wrap mb-2">
41
- <div class="bucket-switcher-control">
42
- <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="bucket-switcher-icon"><path d="M2 4.5C2 3.12 5.13 2 9 2s7 1.12 7 2.5v7c0 1.38-3.13 2.5-7 2.5s-7-1.12-7-2.5v-7z"/><path d="M2 8c0 1.38 3.13 2.5 7 2.5s7-1.12 7-2.5"/></svg>
43
- <select id="bucketSelector" class="bucket-switcher-select" aria-label="Select connected bucket">
44
- {{#if bucketOptions.length}}
45
- {{#each bucketOptions}}
46
- <option value="{{name}}" {{#if isSelected}}selected{{/if}}>{{name}}{{#if isDefault}} (default){{/if}}</option>
47
- {{/each}}
48
- {{else}}
49
- <option value="">No buckets configured</option>
50
- {{/if}}
51
- </select>
52
- </div>
53
- <small id="bucketSwitchStatus" class="bucket-switcher-status is-hidden">Switching bucket...</small>
54
- </div>
55
- <div class="storage-bar"><div id="storageUsageBar" style="width: 100%; background: var(--primary);"></div></div>
56
- <div class="fm-storage-meta d-flex justify-content-between align-items-center">
57
- <span id="totalFilesCount">Loading...</span>
58
- </div>
59
- <div id="filterInfo" class="fm-storage-subinfo"></div>
60
- </div>
61
- </aside>
62
-
63
- <!-- Main area -->
64
- <section class="fm-main">
65
- <div class="fm-toolbar">
66
- <nav class="fm-breadcrumb breadcrumb" id="fm-breadcrumb" aria-label="Path"></nav>
67
-
68
- <div class="fm-toolbar-actions">
69
- <!-- Quick filters -->
70
- <div class="quick-filter-group" role="group" aria-label="Quick filters">
71
- <button type="button" class="quick-filter-btn active" data-filter="all">All</button>
72
- <button type="button" class="quick-filter-btn" data-filter="media">Media</button>
73
- <button type="button" class="quick-filter-btn" data-filter="docs">Docs</button>
74
- <button type="button" class="quick-filter-btn" data-filter="code">Code</button>
75
- </div>
76
-
77
- <!-- Search -->
78
- <div class="search-box">
79
- <svg class="s-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="7" cy="7" r="5"/><path d="M11 11l3.5 3.5"/></svg>
80
- <input type="text" id="searchInput" placeholder="Search files…" aria-label="Search files">
81
- <button type="button" class="search-clear-btn" id="searchClearBtn" style="display: none;" aria-label="Clear search">&times;</button>
82
- </div>
83
-
84
- <!-- View toggle -->
85
- <div class="fm-view-toggle">
86
- <button type="button" class="view-btn active" id="view-grid" aria-label="Grid view" title="Grid view">
87
- <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="2" width="5" height="5" rx="1"/><rect x="9" y="2" width="5" height="5" rx="1"/><rect x="2" y="9" width="5" height="5" rx="1"/><rect x="9" y="9" width="5" height="5" rx="1"/></svg>
88
- </button>
89
- <button type="button" class="view-btn" id="view-list" aria-label="List view" title="List view">
90
- <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><line x1="2" y1="4" x2="14" y2="4"/><line x1="2" y1="8" x2="14" y2="8"/><line x1="2" y1="12" x2="14" y2="12"/></svg>
91
- </button>
92
- <button type="button" class="view-btn" id="view-thumbs" aria-label="Toggle image previews" title="Toggle image previews (Fast Mode saves bandwidth & tokens)">
93
- <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="2" width="12" height="12" rx="2"/><circle cx="5.5" cy="5.5" r="1.5"/><path d="M14 10l-4-4-6 6"/></svg>
94
- </button>
95
- </div>
96
- </div>
97
- </div>
98
-
99
- <!-- File Grid / List Container -->
100
- <div id="fileListContainer" class="fm-grid-wrap">
101
- <div id="fm-grid" class="fm-grid view-grid">
102
- {{> bucketadmin_filelist_skeleton }}
103
- </div>
104
-
105
- <!-- Empty state -->
106
- <div id="fm-empty" class="fm-empty" style="display: none;" hidden>
107
- <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 7a2 2 0 012-2h4l2 2h7a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V7z"/></svg>
108
- <div class="empty-title">This folder is empty</div>
109
- <div class="empty-desc">Drag files into the browser or use the Upload button to add files.</div>
110
- <div class="empty-actions mt-3">
111
- <button type="button" class="btn btn-outline btn-sm me-2" data-action="create-folder">
112
- <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><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>
113
- New Folder
114
- </button>
115
- <button type="button" class="btn btn-primary btn-sm" data-action="upload-files">
116
- <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>
117
- Upload Files
118
- </button>
119
- </div>
120
- </div>
121
- </div>
122
-
123
- <!-- Pagination -->
124
- <div id="paginationContainer" class="pagination-container" style="display: none;">
125
- <nav aria-label="File pagination">
126
- <ul class="pagination" id="paginationList"></ul>
127
- </nav>
128
- </div>
129
- </section>
130
- </div>
131
- </div>
132
- </div>
133
-
134
- {{> bucketadmin_upload_modal }}
135
- {{> bucketadmin_newfolder_modal }}
136
- {{> bucketadmin_delete_modal }}
137
- {{> bucketadmin_preview_modal }}
138
-
139
- <!-- Toast Container -->
140
- <div id="toast-container" class="toast-container"></div>
141
-
142
- <!-- Context Menu Element -->
143
- <div id="fm-context-menu" class="fm-context-menu" style="display: none;"></div>
144
-
1
+ <link rel="stylesheet" href="/mbkbucket/bucketadmin.css{{mbkbucket_cachebuster}}">
2
+ <div id="bucketAppData" data-app-name="{{APP_NAME}}" data-provider-type="{{providerType}}" data-public-view-enabled="{{publicViewEnabled}}" hidden></div>
3
+
4
+ <div class="page-wrapper">
5
+ <!-- Page Header -->
6
+ <div class="page-header">
7
+ <div class="page-header-row">
8
+ <div>
9
+ <div class="page-pretitle">Storage &amp; Buckets</div>
10
+ <h1 class="page-title d-flex align-items-center gap-2">
11
+ <span>Files</span>
12
+ <span class="provider-badge provider-{{providerType}}" id="topProviderBadge">
13
+ <span class="provider-icon"></span>
14
+ <span class="provider-label">{{providerType}}</span>
15
+ </span>
16
+ <span class="health-status-badge healthy" id="topHealthBadge" title="Storage Connection Health">
17
+ <span class="health-dot"></span>
18
+ <span class="health-label" id="topHealthText">Connected</span>
19
+ </span>
20
+ </h1>
21
+ </div>
22
+ <div class="page-actions">
23
+ <button type="button" class="btn btn-outline btn-refresh" id="refresh-btn" title="Refresh files" aria-label="Refresh files">
24
+ <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1 4v4h4M15 12v-4h-4M13.5 6A6 6 0 003 4.5L1 8M2.5 10a6 6 0 0010.5 1.5L15 8"/></svg>
25
+ <span class="d-none d-sm-inline">Refresh</span>
26
+ </button>
27
+ <button type="button" class="btn btn-outline" id="new-folder-btn" data-action="create-folder">
28
+ <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><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>
29
+ New folder
30
+ </button>
31
+ <button type="button" class="btn btn-primary" id="upload-btn" data-action="upload-files">
32
+ <svg width="15" height="15" 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
+ Upload
34
+ </button>
35
+ </div>
36
+ </div>
37
+ </div>
38
+
39
+ {{> bucketadmin_alerts }}
40
+
41
+ <!-- Main File Manager Card -->
42
+ <div class="card file-manager">
43
+ <div class="fm-layout">
44
+ <!-- Tree sidebar -->
45
+ <aside class="fm-tree-wrap">
46
+ <div class="fm-tree-header">
47
+ <span class="fm-tree-title">Explorer</span>
48
+ </div>
49
+ <div id="fm-tree" class="fm-tree"></div>
50
+
51
+ <div class="fm-storage">
52
+ <div class="fm-storage-label d-flex justify-content-between align-items-center">
53
+ <span>Storage Connection</span>
54
+ <button type="button" class="health-check-btn" id="checkHealthBtn" title="Check Connection Health" aria-label="Check Connection Health">
55
+ <svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1 4v4h4M15 12v-4h-4M13.5 6A6 6 0 003 4.5L1 8M2.5 10a6 6 0 0010.5 1.5L15 8"/></svg>
56
+ </button>
57
+ </div>
58
+ <div class="bucket-switcher-wrap mb-2">
59
+ <div class="bucket-switcher-control">
60
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" class="bucket-switcher-icon"><path d="M2 4.5C2 3.12 5.13 2 9 2s7 1.12 7 2.5v7c0 1.38-3.13 2.5-7 2.5s-7-1.12-7-2.5v-7z"/><path d="M2 8c0 1.38 3.13 2.5 7 2.5s7-1.12 7-2.5"/></svg>
61
+ <select id="bucketSelector" class="bucket-switcher-select" aria-label="Select connected bucket">
62
+ {{#if bucketOptions.length}}
63
+ {{#each bucketOptions}}
64
+ <option value="{{name}}" data-type="{{type}}" {{#if isSelected}}selected{{/if}}>{{name}} [{{type}}]{{#if isDefault}} (default){{/if}}</option>
65
+ {{/each}}
66
+ {{else}}
67
+ <option value="">No buckets configured</option>
68
+ {{/if}}
69
+ </select>
70
+ </div>
71
+ <small id="bucketSwitchStatus" class="bucket-switcher-status is-hidden">Switching bucket...</small>
72
+ </div>
73
+ <div class="storage-bar"><div id="storageUsageBar" style="width: 100%; background: var(--primary);"></div></div>
74
+ <div class="fm-storage-meta d-flex justify-content-between align-items-center">
75
+ <span id="totalFilesCount">Loading...</span>
76
+ <span id="storageLatency" class="storage-latency-tag"></span>
77
+ </div>
78
+ <div id="filterInfo" class="fm-storage-subinfo"></div>
79
+ </div>
80
+ </aside>
81
+
82
+ <!-- Main area -->
83
+ <section class="fm-main">
84
+ <div class="fm-toolbar">
85
+ <nav class="fm-breadcrumb breadcrumb" id="fm-breadcrumb" aria-label="Path"></nav>
86
+
87
+ <div class="fm-toolbar-actions">
88
+ <!-- Quick filters -->
89
+ <div class="quick-filter-group" role="group" aria-label="Quick filters">
90
+ <button type="button" class="quick-filter-btn active" data-filter="all">All</button>
91
+ <button type="button" class="quick-filter-btn" data-filter="media">Media</button>
92
+ <button type="button" class="quick-filter-btn" data-filter="docs">Docs</button>
93
+ <button type="button" class="quick-filter-btn" data-filter="code">Code</button>
94
+ </div>
95
+
96
+ <!-- Search -->
97
+ <div class="search-box">
98
+ <svg class="s-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="7" cy="7" r="5"/><path d="M11 11l3.5 3.5"/></svg>
99
+ <input type="text" id="searchInput" placeholder="Search files…" aria-label="Search files">
100
+ <button type="button" class="search-clear-btn" id="searchClearBtn" style="display: none;" aria-label="Clear search">&times;</button>
101
+ </div>
102
+
103
+ <!-- View toggle -->
104
+ <div class="fm-view-toggle">
105
+ <button type="button" class="view-btn active" id="view-grid" aria-label="Grid view" title="Grid view">
106
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="2" width="5" height="5" rx="1"/><rect x="9" y="2" width="5" height="5" rx="1"/><rect x="2" y="9" width="5" height="5" rx="1"/><rect x="9" y="9" width="5" height="5" rx="1"/></svg>
107
+ </button>
108
+ <button type="button" class="view-btn" id="view-list" aria-label="List view" title="List view">
109
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><line x1="2" y1="4" x2="14" y2="4"/><line x1="2" y1="8" x2="14" y2="8"/><line x1="2" y1="12" x2="14" y2="12"/></svg>
110
+ </button>
111
+ <button type="button" class="view-btn" id="view-thumbs" aria-label="Toggle image previews" title="Toggle image previews (Fast Mode saves bandwidth & tokens)">
112
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="2" width="12" height="12" rx="2"/><circle cx="5.5" cy="5.5" r="1.5"/><path d="M14 10l-4-4-6 6"/></svg>
113
+ </button>
114
+ </div>
115
+ </div>
116
+ </div>
117
+
118
+ <!-- File Grid / List Container -->
119
+ <div id="fileListContainer" class="fm-grid-wrap">
120
+ <div id="fm-grid" class="fm-grid view-grid">
121
+ {{> bucketadmin_filelist_skeleton }}
122
+ </div>
123
+
124
+ <!-- Empty state -->
125
+ <div id="fm-empty" class="fm-empty" style="display: none;" hidden>
126
+ <div class="empty-icon-wrap">
127
+ <svg width="54" height="54" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 7a2 2 0 012-2h4l2 2h7a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V7z"/></svg>
128
+ </div>
129
+ <div class="empty-title">This folder is empty</div>
130
+ <div class="empty-desc">Drag files directly into the browser or click Upload to get started.</div>
131
+ <div class="empty-actions mt-3">
132
+ <button type="button" class="btn btn-outline btn-sm me-2" data-action="create-folder">
133
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><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>
134
+ New Folder
135
+ </button>
136
+ <button type="button" class="btn btn-primary btn-sm" data-action="upload-files">
137
+ <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>
138
+ Upload Files
139
+ </button>
140
+ </div>
141
+ </div>
142
+ </div>
143
+
144
+ <!-- Pagination -->
145
+ <div id="paginationContainer" class="pagination-container" style="display: none;">
146
+ <nav aria-label="File pagination">
147
+ <ul class="pagination" id="paginationList"></ul>
148
+ </nav>
149
+ </div>
150
+ </section>
151
+ </div>
152
+ </div>
153
+ </div>
154
+
155
+ <!-- Floating Batch Action Bar -->
156
+ <div id="batchActionBar" class="batch-action-bar" style="display: none;">
157
+ <div class="batch-bar-inner">
158
+ <div class="batch-selection-info">
159
+ <span class="batch-count-badge" id="batchSelectedCount">0</span>
160
+ <span class="batch-count-text">item(s) selected</span>
161
+ </div>
162
+ <div class="batch-actions">
163
+ <button type="button" class="btn btn-sm btn-outline-light" id="batchDownloadBtn">
164
+ <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="M5 8l3 3 3-3"/><line x1="8" y1="2" x2="8" y2="11"/></svg>
165
+ Download
166
+ </button>
167
+ <button type="button" class="btn btn-sm btn-outline-light" id="batchMoveBtn">
168
+ <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M11 2L14 5L11 8M14 5H5C3.34315 5 2 6.34315 2 8V14"/></svg>
169
+ Move
170
+ </button>
171
+ <button type="button" class="btn btn-sm btn-danger" id="batchDeleteBtn">
172
+ <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 4h10M6 4V2h4v2M5 4l1 9h4l1-9"/></svg>
173
+ Delete
174
+ </button>
175
+ <button type="button" class="btn btn-sm btn-link text-white text-decoration-none" id="batchClearBtn">
176
+ Cancel
177
+ </button>
178
+ </div>
179
+ </div>
180
+ </div>
181
+
182
+ {{> bucketadmin_upload_modal }}
183
+ {{> bucketadmin_newfolder_modal }}
184
+ {{> bucketadmin_move_modal }}
185
+ {{> bucketadmin_delete_modal }}
186
+ {{> bucketadmin_preview_modal }}
187
+
188
+ <!-- Toast Container -->
189
+ <div id="toast-container" class="toast-container"></div>
190
+
191
+ <!-- Context Menu Element -->
192
+ <div id="fm-context-menu" class="fm-context-menu" style="display: none;"></div>
193
+
145
194
  <script src="/mbkbucket/bucketadmin.js{{mbkbucket_cachebuster}}"></script>