mbkbucket 3.0.1 → 3.2.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.
- package/package.json +8 -8
- package/public/bucketadmin.css +1139 -2017
- package/public/bucketadmin.js +1065 -1977
- package/src/app.js +3 -5
- package/src/controllers/api.controller.js +19 -7
- package/src/controllers/dashboard.controller.js +11 -10
- package/src/controllers/view.controller.js +7 -3
- package/src/routes/api.routes.js +1 -1
- package/src/routes/view.routes.js +3 -3
- package/src/services/s3.service.js +10 -3
- package/src/utils/helpers.js +24 -0
- package/views/bucket.handlebars +59 -129
- package/views/bucketadmin_delete_modal.handlebars +14 -18
- package/views/bucketadmin_filelist_skeleton.handlebars +28 -16
- package/views/bucketadmin_newfolder_modal.handlebars +29 -0
- package/views/bucketadmin_preview_modal.handlebars +21 -14
- package/views/bucketadmin_upload_modal.handlebars +54 -0
- package/views/bucketadmincontent.handlebars +113 -143
package/src/app.js
CHANGED
|
@@ -9,6 +9,7 @@ import { notFoundHandler, globalErrorHandler } from "./middleware/error-handler.
|
|
|
9
9
|
import bucketRoutes from "./routes/index.js";
|
|
10
10
|
import { packageJson } from "./config/index.js";
|
|
11
11
|
import { createLogger } from "./utils/logger.js";
|
|
12
|
+
import { commonHandlebarsHelpers } from "./utils/helpers.js";
|
|
12
13
|
|
|
13
14
|
const debugServer = createLogger('server');
|
|
14
15
|
|
|
@@ -46,8 +47,7 @@ export function createApp() {
|
|
|
46
47
|
// Handlebars configuration
|
|
47
48
|
server.engine("handlebars", engine({
|
|
48
49
|
extname: ".handlebars",
|
|
49
|
-
defaultLayout:
|
|
50
|
-
layoutsDir: path.join(__dirname, "..", "views", "layouts"),
|
|
50
|
+
defaultLayout: false,
|
|
51
51
|
partialsDir: [
|
|
52
52
|
path.join(__dirname, "..", "views", "templates"),
|
|
53
53
|
path.join(__dirname, "..", "views", "templates", "notice"),
|
|
@@ -58,9 +58,7 @@ export function createApp() {
|
|
|
58
58
|
],
|
|
59
59
|
cache: process.env.NODE_ENV === "production",
|
|
60
60
|
helpers: {
|
|
61
|
-
|
|
62
|
-
return a === b;
|
|
63
|
-
},
|
|
61
|
+
...commonHandlebarsHelpers,
|
|
64
62
|
mbkbucket_cachebuster: function () {
|
|
65
63
|
return "?v=" + packageJson.version;
|
|
66
64
|
}
|
|
@@ -34,7 +34,9 @@ function resolvePrefix(req, res, prefix) {
|
|
|
34
34
|
* Build a full S3 key from a prefix and filename, stripping trailing slashes.
|
|
35
35
|
*/
|
|
36
36
|
function buildKey(prefix, fileName) {
|
|
37
|
-
|
|
37
|
+
const cleanPrefix = String(prefix || '').replace(/^\/+/, '').replace(/\/+$/, '');
|
|
38
|
+
const cleanName = String(fileName || '').replace(/^\/+/, '');
|
|
39
|
+
return cleanPrefix ? `${cleanPrefix}/${cleanName}` : cleanName;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
async function cleanupFolderMarker(folderPath, bucketName) {
|
|
@@ -121,8 +123,8 @@ export async function listFiles(req, res) {
|
|
|
121
123
|
let legacyFallback = false;
|
|
122
124
|
|
|
123
125
|
if (useOptimizedListing) {
|
|
124
|
-
let listPrefix = effectivePrefix;
|
|
125
|
-
if (listPrefix
|
|
126
|
+
let listPrefix = effectivePrefix ? String(effectivePrefix).replace(/^\/+/, '') : '';
|
|
127
|
+
if (listPrefix && !listPrefix.endsWith('/')) {
|
|
126
128
|
listPrefix += '/';
|
|
127
129
|
}
|
|
128
130
|
|
|
@@ -135,11 +137,12 @@ export async function listFiles(req, res) {
|
|
|
135
137
|
|
|
136
138
|
files = (result.Contents || []).filter(f => !f.Key.endsWith('/'));
|
|
137
139
|
folders = (result.CommonPrefixes || [])
|
|
138
|
-
.filter(p => p.Prefix !== effectivePrefix)
|
|
140
|
+
.filter(p => p.Prefix !== listPrefix && p.Prefix !== effectivePrefix)
|
|
139
141
|
.map(p => p.Prefix);
|
|
140
142
|
nextContinuationToken = result.NextContinuationToken;
|
|
143
|
+
}
|
|
141
144
|
|
|
142
|
-
|
|
145
|
+
else {
|
|
143
146
|
let continuationToken = undefined;
|
|
144
147
|
|
|
145
148
|
do {
|
|
@@ -350,7 +353,14 @@ export async function createFolder(req, res) {
|
|
|
350
353
|
const effectivePrefix = resolvePrefix(req, res, prefix);
|
|
351
354
|
if (effectivePrefix === null) return;
|
|
352
355
|
|
|
353
|
-
const
|
|
356
|
+
const cleanName = String(folderName).replace(/^\/+/, '').replace(/\/+$/, '');
|
|
357
|
+
let folderKey;
|
|
358
|
+
if (effectivePrefix && !cleanName.startsWith(effectivePrefix)) {
|
|
359
|
+
const cleanPrefix = effectivePrefix.replace(/\/+$/, '');
|
|
360
|
+
folderKey = `${cleanPrefix}/${cleanName}/`;
|
|
361
|
+
} else {
|
|
362
|
+
folderKey = `${cleanName}/`;
|
|
363
|
+
}
|
|
354
364
|
|
|
355
365
|
const markerExists = await fileExists(folderKey, bucketName);
|
|
356
366
|
if (markerExists) return res.status(409).json({ success: false, error: 'Folder already exists' });
|
|
@@ -415,9 +425,11 @@ export async function downloadFileHandler(req, res) {
|
|
|
415
425
|
|
|
416
426
|
try {
|
|
417
427
|
const bucketName = req.activeBucket;
|
|
428
|
+
const rawKey = req.params.key;
|
|
429
|
+
const key = Array.isArray(rawKey) ? rawKey.join('/') : (rawKey || '');
|
|
418
430
|
let keyToUse;
|
|
419
431
|
try {
|
|
420
|
-
keyToUse = ensureKeyHasAppPrefix(
|
|
432
|
+
keyToUse = ensureKeyHasAppPrefix(key);
|
|
421
433
|
} catch (e) {
|
|
422
434
|
return res.status(400).json({ success: false, error: e.message });
|
|
423
435
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import path from "path";
|
|
1
2
|
import { resolveBucketName, getAvailableBucketNames } from "../services/s3.service.js";
|
|
2
3
|
import { renderPage, mbkautheVar } from "mbkauthe";
|
|
4
|
+
import { commonHandlebarsHelpers } from "../utils/helpers.js";
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Render the main bucket dashboard page.
|
|
@@ -7,13 +9,9 @@ import { renderPage, mbkautheVar } from "mbkauthe";
|
|
|
7
9
|
export function renderBucketDashboard(req, res) {
|
|
8
10
|
const allBuckets = getAvailableBucketNames();
|
|
9
11
|
const configuredDefaultBucket = mbkautheVar?.bucket;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
bucketfilePath = "bucketportal.handlebars";
|
|
14
|
-
} else {
|
|
15
|
-
bucketfilePath = "bucket.handlebars";
|
|
16
|
-
}
|
|
12
|
+
const isPortal = mbkautheVar?.APP_NAME === "portal";
|
|
13
|
+
const bucketfilePath = isPortal ? "bucketportal.handlebars" : "bucket.handlebars";
|
|
14
|
+
const portalLayout = isPortal ? path.resolve(process.cwd(), "views", "layouts", "main") : false;
|
|
17
15
|
|
|
18
16
|
let selectedBucket;
|
|
19
17
|
|
|
@@ -26,9 +24,10 @@ export function renderBucketDashboard(req, res) {
|
|
|
26
24
|
isSelected: name === fallbackSelected,
|
|
27
25
|
isDefault: name === configuredDefaultBucket
|
|
28
26
|
}));
|
|
29
|
-
return renderPage(req, res, bucketfilePath,
|
|
27
|
+
return renderPage(req, res, bucketfilePath, isPortal, {
|
|
30
28
|
page: "Admin Bucket",
|
|
31
|
-
|
|
29
|
+
helpers: commonHandlebarsHelpers,
|
|
30
|
+
...(isPortal ? { layout: portalLayout } : {}),
|
|
32
31
|
bucketvar: mbkautheVar?.bucket,
|
|
33
32
|
selectedBucket: fallbackSelected,
|
|
34
33
|
bucketOptions,
|
|
@@ -45,8 +44,10 @@ export function renderBucketDashboard(req, res) {
|
|
|
45
44
|
isDefault: name === configuredDefaultBucket
|
|
46
45
|
}));
|
|
47
46
|
|
|
48
|
-
renderPage(req, res, bucketfilePath,
|
|
47
|
+
renderPage(req, res, bucketfilePath, isPortal, {
|
|
49
48
|
page: "Admin Bucket",
|
|
49
|
+
helpers: commonHandlebarsHelpers,
|
|
50
|
+
...(isPortal ? { layout: portalLayout } : {}),
|
|
50
51
|
bucketvar: selectedBucket,
|
|
51
52
|
selectedBucket,
|
|
52
53
|
bucketOptions,
|
|
@@ -192,9 +192,11 @@ async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName,
|
|
|
192
192
|
export async function viewFile(req, res) {
|
|
193
193
|
try {
|
|
194
194
|
const bucketName = req.activeBucket;
|
|
195
|
+
const rawKey = req.params.key;
|
|
196
|
+
const key = Array.isArray(rawKey) ? rawKey.join('/') : (rawKey || '');
|
|
195
197
|
let keyToUse;
|
|
196
198
|
try {
|
|
197
|
-
keyToUse = ensureKeyHasAppPrefix(
|
|
199
|
+
keyToUse = ensureKeyHasAppPrefix(key);
|
|
198
200
|
} catch (e) {
|
|
199
201
|
return res.status(400).json({ message: e.message });
|
|
200
202
|
}
|
|
@@ -216,7 +218,8 @@ export async function viewFile(req, res) {
|
|
|
216
218
|
export async function playerPage(req, res) {
|
|
217
219
|
try {
|
|
218
220
|
const bucketName = req.activeBucket;
|
|
219
|
-
const
|
|
221
|
+
const rawKey = req.params.key;
|
|
222
|
+
const key = Array.isArray(rawKey) ? rawKey.join('/') : (rawKey || '');
|
|
220
223
|
let keyToUse;
|
|
221
224
|
try {
|
|
222
225
|
keyToUse = ensureKeyHasAppPrefix(key);
|
|
@@ -240,7 +243,8 @@ export async function playerPage(req, res) {
|
|
|
240
243
|
export async function publicView(req, res) {
|
|
241
244
|
try {
|
|
242
245
|
const bucketName = resolveBucketName();
|
|
243
|
-
const
|
|
246
|
+
const rawKey = req.params.key;
|
|
247
|
+
const key = Array.isArray(rawKey) ? rawKey.join('/') : (rawKey || '');
|
|
244
248
|
|
|
245
249
|
// No directory indexing
|
|
246
250
|
if (!key || key.endsWith('/')) {
|
package/src/routes/api.routes.js
CHANGED
|
@@ -61,6 +61,6 @@ router.post('/create-folder', sessRole('SuperAdmin'), createFolder);
|
|
|
61
61
|
router.post('/delete', sessRole('SuperAdmin'), deleteItems);
|
|
62
62
|
|
|
63
63
|
// Download
|
|
64
|
-
router.get('/download
|
|
64
|
+
router.get('/download/*key', sessRole('SuperAdmin'), downloadFileHandler);
|
|
65
65
|
|
|
66
66
|
export default router;
|
|
@@ -12,14 +12,14 @@ const router = express.Router();
|
|
|
12
12
|
router.use(requireBucketView);
|
|
13
13
|
|
|
14
14
|
// Player page for video/audio
|
|
15
|
-
router.get('/player
|
|
15
|
+
router.get('/player/*key', sessRole('SuperAdmin'), playerPage);
|
|
16
16
|
|
|
17
17
|
// Inline file view
|
|
18
|
-
router.get('/view
|
|
18
|
+
router.get('/view/*key', sessRole('SuperAdmin'), viewFile);
|
|
19
19
|
|
|
20
20
|
// Public view (conditionally enabled)
|
|
21
21
|
if (mbkbucketVar?.publiView_enabled) {
|
|
22
|
-
router.get('/p_view
|
|
22
|
+
router.get('/p_view/*key', pviewRateLimit, pviewSecurity, publicView);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export default router;
|
|
@@ -74,6 +74,9 @@ function isRootModeApp(appName) {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
export function ensureKeyHasAppPrefix(key = '') {
|
|
77
|
+
if (Array.isArray(key)) {
|
|
78
|
+
key = key.join('/');
|
|
79
|
+
}
|
|
77
80
|
const app = getAppName();
|
|
78
81
|
if (!key) throw new Error('Key is required');
|
|
79
82
|
|
|
@@ -103,6 +106,9 @@ export function ensureKeyHasAppPrefix(key = '') {
|
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
export function ensurePrefix(prefix = '') {
|
|
109
|
+
if (Array.isArray(prefix)) {
|
|
110
|
+
prefix = prefix.join('/');
|
|
111
|
+
}
|
|
106
112
|
const app = getAppName();
|
|
107
113
|
|
|
108
114
|
if (prefix && (prefix.includes('../') || prefix.includes('..\\') || /\.\.[\\/]/.test(prefix))) {
|
|
@@ -369,12 +375,13 @@ export async function downloadFile(key, options = {}) {
|
|
|
369
375
|
key: keyToDownload,
|
|
370
376
|
};
|
|
371
377
|
} catch (error) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if (error && error.$metadata && error.$metadata.httpStatusCode === 304) {
|
|
378
|
+
if (error && (error.$metadata?.httpStatusCode === 304 || error.name === '304' || error.name === 'NotModified')) {
|
|
379
|
+
debugS3('File not modified (304) for key %s', keyToDownload);
|
|
375
380
|
return { notModified: true, key: keyToDownload };
|
|
376
381
|
}
|
|
377
382
|
|
|
383
|
+
console.error(`Download failed for key ${key}:`, error);
|
|
384
|
+
|
|
378
385
|
if (error.name === 'NoSuchKey') {
|
|
379
386
|
throw new Error(`File not found: ${key}`);
|
|
380
387
|
} else if (error.name === 'AccessDenied') {
|
package/src/utils/helpers.js
CHANGED
|
@@ -171,3 +171,27 @@ export function renderPlayerPage(safeFileName, encodedKey, bucketQuery) {
|
|
|
171
171
|
</body>
|
|
172
172
|
</html>`;
|
|
173
173
|
}
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Common Handlebars helpers
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
export const commonHandlebarsHelpers = {
|
|
180
|
+
eq: (a, b) => a === b,
|
|
181
|
+
neq: (a, b) => a !== b,
|
|
182
|
+
or: (...args) => args.slice(0, -1).some(Boolean),
|
|
183
|
+
and: (...args) => args.slice(0, -1).every(Boolean),
|
|
184
|
+
not: (val) => !val,
|
|
185
|
+
gt: (a, b) => a > b,
|
|
186
|
+
gte: (a, b) => a >= b,
|
|
187
|
+
lt: (a, b) => a < b,
|
|
188
|
+
lte: (a, b) => a <= b,
|
|
189
|
+
includes: (arr, val) => (Array.isArray(arr) ? arr.includes(val) : false),
|
|
190
|
+
json: (obj) => JSON.stringify(obj, null, 2),
|
|
191
|
+
jsonStringify: (obj) => JSON.stringify(obj),
|
|
192
|
+
getInitials: (username) => {
|
|
193
|
+
if (!username) return '?';
|
|
194
|
+
const parts = username.split(/[._]/);
|
|
195
|
+
return parts.map(p => p.charAt(0)).join('').toUpperCase().substring(0, 2);
|
|
196
|
+
},
|
|
197
|
+
};
|
package/views/bucket.handlebars
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<title>{{#if page}}{{page}} - {{/if}}Web Portal</title>
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
|
-
<link href="https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700;800&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
10
|
+
<link href="https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700;800&family=IBM+Plex+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
11
11
|
<script src="/mbkauthe/main.js" defer></script>
|
|
12
12
|
<script src="https://portal.mbktech.org/Assets/Scripts/auto-ids.js" defer></script>
|
|
13
13
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
|
@@ -26,26 +26,34 @@
|
|
|
26
26
|
--warning-color-ml: #d97706;
|
|
27
27
|
--success-color-ml: #16a34a;
|
|
28
28
|
--info-color-ml: #3b82f6;
|
|
29
|
-
|
|
30
|
-
--
|
|
31
|
-
--
|
|
32
|
-
--
|
|
33
|
-
--
|
|
34
|
-
|
|
35
|
-
--
|
|
36
|
-
--
|
|
37
|
-
--
|
|
38
|
-
--
|
|
39
|
-
|
|
40
|
-
--color
|
|
29
|
+
|
|
30
|
+
--primary: var(--primary-color-ml);
|
|
31
|
+
--primary-lt: var(--primary-light-color-ml);
|
|
32
|
+
--primary-dk: var(--primary-dark-color-ml);
|
|
33
|
+
--primary-hover: #0d635c;
|
|
34
|
+
|
|
35
|
+
--body-bg: var(--light-color-ml);
|
|
36
|
+
--bg-surface: rgba(255, 255, 255, 0.88);
|
|
37
|
+
--bg-surface-secondary: rgba(244, 247, 243, 0.7);
|
|
38
|
+
--bg-surface-tertiary: #e9f2ee;
|
|
39
|
+
|
|
40
|
+
--border-color: rgba(15, 118, 110, 0.15);
|
|
41
|
+
--border-color-light: rgba(15, 118, 110, 0.08);
|
|
42
|
+
|
|
43
|
+
--text: var(--dark-color-ml);
|
|
44
|
+
--text-secondary: #4a5c5a;
|
|
45
|
+
--text-muted: var(--gray-color-ml);
|
|
46
|
+
--text-disabled: #a0b2af;
|
|
47
|
+
|
|
41
48
|
--font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
|
|
42
|
-
--font-family-headings: "Sora", "Segoe UI", sans-serif
|
|
49
|
+
--font-family-headings: "Sora", "Segoe UI", sans-serif;
|
|
50
|
+
--font-mono: 'JetBrains Mono', monospace;
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
* {
|
|
46
54
|
margin: 0;
|
|
47
55
|
padding: 0;
|
|
48
|
-
box-sizing: border-box
|
|
56
|
+
box-sizing: border-box;
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
body {
|
|
@@ -61,7 +69,7 @@
|
|
|
61
69
|
line-height: 1.6;
|
|
62
70
|
font-size: 14px;
|
|
63
71
|
position: relative;
|
|
64
|
-
isolation: isolate
|
|
72
|
+
isolation: isolate;
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
body::before,
|
|
@@ -70,7 +78,7 @@
|
|
|
70
78
|
position: fixed;
|
|
71
79
|
border-radius: 999px;
|
|
72
80
|
pointer-events: none;
|
|
73
|
-
z-index: -1
|
|
81
|
+
z-index: -1;
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
body::before {
|
|
@@ -78,7 +86,7 @@
|
|
|
78
86
|
height: 260px;
|
|
79
87
|
left: -80px;
|
|
80
88
|
top: 18vh;
|
|
81
|
-
background: radial-gradient(circle, rgba(15, 118, 110, 0.16) 0, rgba(15, 118, 110, 0) 70%)
|
|
89
|
+
background: radial-gradient(circle, rgba(15, 118, 110, 0.16) 0, rgba(15, 118, 110, 0) 70%);
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
body::after {
|
|
@@ -86,23 +94,7 @@
|
|
|
86
94
|
height: 300px;
|
|
87
95
|
right: -120px;
|
|
88
96
|
bottom: 10vh;
|
|
89
|
-
background: radial-gradient(circle, rgba(251, 146, 60, 0.18) 0, rgba(251, 146, 60, 0) 72%)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/* Removed unused sidebar/menu styles to reduce CSS size. */
|
|
93
|
-
|
|
94
|
-
.logo {
|
|
95
|
-
font-family: var(--font-family-headings);
|
|
96
|
-
font-size: 1.65rem;
|
|
97
|
-
font-weight: 700;
|
|
98
|
-
color: #0b4f4a;
|
|
99
|
-
display: flex;
|
|
100
|
-
align-items: center;
|
|
101
|
-
gap: .5rem
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.logo img {
|
|
105
|
-
width: 35px
|
|
97
|
+
background: radial-gradient(circle, rgba(251, 146, 60, 0.18) 0, rgba(251, 146, 60, 0) 72%);
|
|
106
98
|
}
|
|
107
99
|
|
|
108
100
|
.main-content {
|
|
@@ -110,7 +102,7 @@
|
|
|
110
102
|
display: flex;
|
|
111
103
|
flex-direction: column;
|
|
112
104
|
overflow: hidden;
|
|
113
|
-
|
|
105
|
+
min-height: 100vh;
|
|
114
106
|
}
|
|
115
107
|
|
|
116
108
|
.top-nav {
|
|
@@ -119,139 +111,77 @@
|
|
|
119
111
|
padding: .75rem 1.5rem;
|
|
120
112
|
display: flex;
|
|
121
113
|
align-items: center;
|
|
122
|
-
justify-content:
|
|
123
|
-
box-shadow:
|
|
114
|
+
justify-content: space-between;
|
|
115
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.06), 0 2px 4px -1px rgba(0, 0, 0, 0.04);
|
|
124
116
|
z-index: 90;
|
|
125
117
|
border-bottom: 1px solid rgba(255, 255, 255, .75);
|
|
126
|
-
gap: 1rem
|
|
118
|
+
gap: 1rem;
|
|
127
119
|
}
|
|
128
120
|
|
|
129
|
-
|
|
121
|
+
.logo {
|
|
122
|
+
font-family: var(--font-family-headings);
|
|
123
|
+
font-size: 1.5rem;
|
|
124
|
+
font-weight: 700;
|
|
125
|
+
color: #0b4f4a;
|
|
126
|
+
display: flex;
|
|
127
|
+
align-items: center;
|
|
128
|
+
gap: .5rem;
|
|
129
|
+
cursor: pointer;
|
|
130
|
+
text-decoration: none;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.logo img {
|
|
134
|
+
width: 32px;
|
|
135
|
+
height: 32px;
|
|
136
|
+
}
|
|
130
137
|
|
|
131
138
|
.content-area {
|
|
132
139
|
flex: 1;
|
|
133
|
-
padding: 1.
|
|
134
|
-
overflow-y: auto
|
|
140
|
+
padding: 1.5rem;
|
|
141
|
+
overflow-y: auto;
|
|
135
142
|
}
|
|
136
143
|
|
|
137
144
|
.content-area::-webkit-scrollbar {
|
|
138
|
-
width: 8px
|
|
145
|
+
width: 8px;
|
|
139
146
|
}
|
|
140
147
|
|
|
141
148
|
.content-area::-webkit-scrollbar-track {
|
|
142
|
-
background:
|
|
149
|
+
background: transparent;
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
.content-area::-webkit-scrollbar-thumb {
|
|
146
153
|
background: var(--gray-color-ml);
|
|
147
|
-
border-radius: 4px
|
|
154
|
+
border-radius: 4px;
|
|
148
155
|
}
|
|
149
156
|
|
|
150
157
|
.content-area::-webkit-scrollbar-thumb:hover {
|
|
151
|
-
background: var(--primary-color-ml)
|
|
158
|
+
background: var(--primary-color-ml);
|
|
152
159
|
}
|
|
153
160
|
|
|
154
|
-
|
|
155
|
-
display: flex;
|
|
156
|
-
align-items: center;
|
|
157
|
-
justify-content: space-between;
|
|
158
|
-
margin-bottom: 1.2rem;
|
|
159
|
-
padding: 1rem 1.2rem;
|
|
160
|
-
border: 1px solid rgba(255, 255, 255, .9);
|
|
161
|
-
border-radius: 1rem;
|
|
162
|
-
background: rgba(255, 255, 255, .74);
|
|
163
|
-
box-shadow: 0 12px 34px rgba(15, 23, 42, .07)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
.page-title {
|
|
167
|
-
font-family: var(--font-family-headings);
|
|
168
|
-
font-size: 1.5rem;
|
|
169
|
-
font-weight: 700;
|
|
170
|
-
color: var(--dark-color-ml);
|
|
171
|
-
margin: 0
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
.breadcrumb {
|
|
175
|
-
display: flex;
|
|
176
|
-
align-items: center;
|
|
177
|
-
color: var(--gray-color-ml);
|
|
178
|
-
font-size: .875rem;
|
|
179
|
-
background: linear-gradient(135deg, rgba(15, 118, 110, .08), rgba(251, 146, 60, .08));
|
|
180
|
-
padding: .4rem .85rem;
|
|
181
|
-
border-radius: 999px;
|
|
182
|
-
border: 1px solid rgba(15, 118, 110, .15)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
.breadcrumb a {
|
|
186
|
-
color: var(--primary-color-ml);
|
|
187
|
-
text-decoration: none;
|
|
188
|
-
transition: var(--transition-base-ml);
|
|
189
|
-
font-weight: 500
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
.breadcrumb a:hover {
|
|
193
|
-
color: var(--primary-dark-color-ml)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
.breadcrumb .separator {
|
|
197
|
-
margin: 0 .5rem;
|
|
198
|
-
color: var(--gray-color-ml)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
@media (max-width:992px) {
|
|
202
|
-
.main-content {
|
|
203
|
-
margin-left: 0 !important
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
.content-area {
|
|
207
|
-
padding: 1.5rem
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
@media (max-width:768px) {
|
|
161
|
+
@media (max-width: 768px) {
|
|
212
162
|
.top-nav {
|
|
213
|
-
padding: .
|
|
163
|
+
padding: .5rem 1rem;
|
|
214
164
|
}
|
|
215
|
-
|
|
216
165
|
.content-area {
|
|
217
|
-
padding:
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
.page-header {
|
|
221
|
-
flex-direction: row;
|
|
222
|
-
align-items: flex-start;
|
|
223
|
-
gap: 1rem
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
.page-title {
|
|
227
|
-
font-size: 1.5rem
|
|
166
|
+
padding: 0.85rem;
|
|
228
167
|
}
|
|
229
168
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
169
|
</style>
|
|
233
170
|
{{> showmessage }}
|
|
234
171
|
</head>
|
|
235
172
|
|
|
236
173
|
<body>
|
|
237
|
-
|
|
238
174
|
<div class="main-content">
|
|
239
175
|
<nav class="top-nav">
|
|
240
|
-
<div class="logo">
|
|
241
|
-
<img
|
|
242
|
-
<span
|
|
176
|
+
<div class="logo" onclick="window.location='/mbkbucket'">
|
|
177
|
+
<img src="/icon.svg" alt="Logo">
|
|
178
|
+
<span>MBKBucket</span>
|
|
243
179
|
</div>
|
|
244
180
|
|
|
245
181
|
{{> profilemenu }}
|
|
246
182
|
</nav>
|
|
247
183
|
|
|
248
184
|
<div class="content-area">
|
|
249
|
-
<div class="page-header">
|
|
250
|
-
<h1 class="page-title">{{#if page}}{{page}}{{else}}Dashboard{{/if}}</h1>
|
|
251
|
-
<div class="breadcrumb">
|
|
252
|
-
<span>{{#if page}}{{page}}{{else}}Home{{/if}}</span>
|
|
253
|
-
</div>
|
|
254
|
-
</div>
|
|
255
185
|
{{> bucketadmincontent }}
|
|
256
186
|
</div>
|
|
257
187
|
</div>
|
|
@@ -1,37 +1,33 @@
|
|
|
1
1
|
<!-- Delete Confirmation Modal -->
|
|
2
2
|
<div class="modal" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
|
3
|
-
<div class="modal-dialog">
|
|
3
|
+
<div class="modal-dialog modal-dialog-centered">
|
|
4
4
|
<div class="modal-content">
|
|
5
5
|
<div class="modal-header">
|
|
6
6
|
<h5 class="modal-title" id="deleteModalLabel">
|
|
7
|
-
<
|
|
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
8
|
Confirm Delete
|
|
9
9
|
</h5>
|
|
10
|
-
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"
|
|
11
|
-
onclick="closeModal()">×</button>
|
|
10
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" onclick="closeModal()">×</button>
|
|
12
11
|
</div>
|
|
13
|
-
<div class="modal-body">
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
<small>This action cannot be undone.</small>
|
|
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>
|
|
21
19
|
</p>
|
|
20
|
+
<p class="text-muted small mt-2">This action cannot be undone.</p>
|
|
22
21
|
</div>
|
|
23
22
|
<div class="modal-footer">
|
|
24
|
-
<button type="button" class="btn btn-
|
|
25
|
-
<i class="fas fa-times"></i>
|
|
26
|
-
Cancel
|
|
27
|
-
</button>
|
|
23
|
+
<button type="button" class="btn btn-outline" onclick="closeModal()">Cancel</button>
|
|
28
24
|
<form id="deleteForm" method="post" action="/mbkbucket/delete" style="display: inline;">
|
|
29
25
|
<input type="hidden" name="key" id="deleteKey">
|
|
30
26
|
<input type="hidden" name="prefix" value="{{prefix}}">
|
|
31
27
|
<input type="hidden" name="folder" id="deleteIsFolder" value="">
|
|
32
28
|
<button type="submit" class="btn btn-danger" id="deleteConfirmButton">
|
|
33
|
-
<
|
|
34
|
-
<span id="deleteConfirmLabel">Delete
|
|
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>
|
|
35
31
|
</button>
|
|
36
32
|
</form>
|
|
37
33
|
</div>
|
|
@@ -1,20 +1,32 @@
|
|
|
1
|
-
<div class="
|
|
2
|
-
<div class="skeleton-
|
|
3
|
-
<div class="skeleton-
|
|
4
|
-
<div class="skeleton-
|
|
5
|
-
<div class="skeleton-
|
|
6
|
-
<div class="skeleton-box s-200"></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>
|
|
7
6
|
</div>
|
|
8
|
-
<div class="skeleton-
|
|
9
|
-
<div class="skeleton-
|
|
10
|
-
<div class="skeleton-
|
|
11
|
-
<div class="skeleton-
|
|
12
|
-
<div class="skeleton-box s-200"></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>
|
|
13
11
|
</div>
|
|
14
|
-
<div class="skeleton-
|
|
15
|
-
<div class="skeleton-
|
|
16
|
-
<div class="skeleton-
|
|
17
|
-
<div class="skeleton-
|
|
18
|
-
|
|
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>
|
|
19
31
|
</div>
|
|
20
32
|
</div>
|