mbkbucket 1.5.0 → 2.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.
@@ -0,0 +1,116 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { parseAndValidateMbkbucketVar, parseAndValidateBucketConnection, normalizeBooleanLike } from '../lib/config/validation.js';
4
+
5
+ test('parseAndValidateMbkbucketVar applies defaults when empty', () => {
6
+ const cfg = parseAndValidateMbkbucketVar(undefined);
7
+ assert.equal(cfg.p_view_inline, true);
8
+ assert.equal(cfg.publiView_enabled, false);
9
+ });
10
+
11
+ test('parseAndValidateMbkbucketVar normalizes boolean-like strings', () => {
12
+ const cfg = parseAndValidateMbkbucketVar(JSON.stringify({
13
+ p_view_inline: 'false',
14
+ publiView_enabled: 'true'
15
+ }));
16
+
17
+ assert.equal(cfg.p_view_inline, false);
18
+ assert.equal(cfg.publiView_enabled, true);
19
+ });
20
+
21
+ test('parseAndValidateMbkbucketVar rejects invalid JSON', () => {
22
+ assert.throws(() => parseAndValidateMbkbucketVar('{oops'), /Invalid JSON/);
23
+ });
24
+
25
+ test('parseAndValidateMbkbucketVar rejects invalid boolean values', () => {
26
+ assert.throws(() => parseAndValidateMbkbucketVar(JSON.stringify({
27
+ p_view_inline: 'not-bool'
28
+ })), /must be a boolean/);
29
+ });
30
+
31
+ test('parseAndValidateMbkbucketVar accepts common boolean aliases', () => {
32
+ const cfg = parseAndValidateMbkbucketVar(JSON.stringify({
33
+ p_view_inline: ' ON ',
34
+ publiView_enabled: '0'
35
+ }));
36
+
37
+ assert.equal(cfg.p_view_inline, true);
38
+ assert.equal(cfg.publiView_enabled, false);
39
+ });
40
+
41
+ test('parseAndValidateMbkbucketVar rejects non-object values', () => {
42
+ assert.throws(() => parseAndValidateMbkbucketVar('null'), /must be a valid object/);
43
+ assert.throws(() => parseAndValidateMbkbucketVar('[]'), /must be a valid object/);
44
+ });
45
+
46
+ test('normalizeBooleanLike returns untouched for unsupported values', () => {
47
+ assert.equal(normalizeBooleanLike('maybe'), 'maybe');
48
+ assert.equal(normalizeBooleanLike(123), 123);
49
+ });
50
+
51
+ test('parseAndValidateBucketConnection accepts multi-bucket config', () => {
52
+ const raw = JSON.stringify({
53
+ R2_Bucket: {
54
+ BUCKET_NAME: 'my-r2-bucket',
55
+ ACCESS_KEY_ID: 'abc',
56
+ SECRET_ACCESS_KEY: 'def',
57
+ ENDPOINT: 'https://example.r2.cloudflarestorage.com'
58
+ },
59
+ S3_Bucket: {
60
+ BUCKET_NAME: 'my-s3-bucket',
61
+ ACCESS_KEY_ID: 'ghi',
62
+ SECRET_ACCESS_KEY: 'jkl',
63
+ ENDPOINT: 'https://s3.ap-southeast-1.amazonaws.com'
64
+ }
65
+ });
66
+
67
+ const cfg = parseAndValidateBucketConnection(raw);
68
+ assert.deepEqual(Object.keys(cfg), ['R2_Bucket', 'S3_Bucket']);
69
+ });
70
+
71
+ test('parseAndValidateBucketConnection returns null when unset', () => {
72
+ assert.equal(parseAndValidateBucketConnection(undefined), null);
73
+ assert.equal(parseAndValidateBucketConnection(''), null);
74
+ });
75
+
76
+ test('parseAndValidateBucketConnection rejects invalid JSON', () => {
77
+ assert.throws(() => parseAndValidateBucketConnection('{oops'), /not valid JSON/);
78
+ });
79
+
80
+ test('parseAndValidateBucketConnection rejects empty object and array', () => {
81
+ assert.throws(() => parseAndValidateBucketConnection('{}'), /non-empty object/);
82
+ assert.throws(() => parseAndValidateBucketConnection('[]'), /non-empty object/);
83
+ });
84
+
85
+ test('parseAndValidateBucketConnection rejects missing required fields', () => {
86
+ const raw = JSON.stringify({
87
+ bad: {
88
+ BUCKET_NAME: 'x',
89
+ ACCESS_KEY_ID: 'y',
90
+ ENDPOINT: 'https://example.com'
91
+ }
92
+ });
93
+
94
+ assert.throws(() => parseAndValidateBucketConnection(raw), /missing required fields/);
95
+ });
96
+
97
+ test('parseAndValidateBucketConnection rejects quoted inner object shape', () => {
98
+ const raw = JSON.stringify({
99
+ bad: '{"BUCKET_NAME":"x"}'
100
+ });
101
+
102
+ assert.throws(() => parseAndValidateBucketConnection(raw), /must be an object/);
103
+ });
104
+
105
+ test('parseAndValidateBucketConnection rejects empty required fields', () => {
106
+ const raw = JSON.stringify({
107
+ bad: {
108
+ BUCKET_NAME: '',
109
+ ACCESS_KEY_ID: 'abc',
110
+ SECRET_ACCESS_KEY: 'def',
111
+ ENDPOINT: 'https://example.com'
112
+ }
113
+ });
114
+
115
+ assert.throws(() => parseAndValidateBucketConnection(raw), /missing required fields/);
116
+ });
@@ -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=Inter:wght@300;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&display=swap" rel="stylesheet">
11
11
  <script src="/mbkauthe/main.js" defer></script>
12
12
  <script src="https://portal.mbktech.org/Assets/Scripts/timeoutlogout.js" defer></script>
13
13
  <script src="https://portal.mbktech.org/Assets/Scripts/auto-ids.js" defer></script>
@@ -15,14 +15,14 @@
15
15
  <link rel="icon" type="image/x-icon" href="/icon.svg">
16
16
  <style>
17
17
  :root {
18
- --primary-color-ml: #0d9488;
19
- --primary-light-color-ml: #ccfbf1;
20
- --primary-dark-color-ml: #0f766e;
21
- --secondary-color-ml: #2c7a7b;
22
- --dark-color-ml: #1a202c;
23
- --light-color-ml: #f7fafc;
24
- --gray-color-ml: #718096;
25
- --gray-light-color-ml: #e2e8f0;
18
+ --primary-color-ml: #0f766e;
19
+ --primary-light-color-ml: #d7f4ee;
20
+ --primary-dark-color-ml: #0b4f4a;
21
+ --secondary-color-ml: #1f8f84;
22
+ --dark-color-ml: #1f2a2a;
23
+ --light-color-ml: #f4f7f3;
24
+ --gray-color-ml: #667c79;
25
+ --gray-light-color-ml: #dce8e3;
26
26
  --danger-color-ml: #dc2626;
27
27
  --warning-color-ml: #d97706;
28
28
  --success-color-ml: #16a34a;
@@ -39,8 +39,8 @@
39
39
  --color-accent: var(--primary-color-ml);
40
40
  --color-text: var(--dark-color-ml);
41
41
  --color-bg: var(--light-color-ml);
42
- --font-family: "Inter", sans-serif;
43
- --font-family-headings: "Inter", sans-serif
42
+ --font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
43
+ --font-family-headings: "Sora", "Segoe UI", sans-serif
44
44
  }
45
45
 
46
46
  * {
@@ -50,25 +50,56 @@
50
50
  }
51
51
 
52
52
  body {
53
- font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
54
- background: linear-gradient(135deg, #f5f7fb 0, #f0f4f8 100%);
53
+ font-family: var(--font-family);
54
+ background:
55
+ radial-gradient(circle at 12% 14%, rgba(16, 185, 129, 0.12), transparent 44%),
56
+ radial-gradient(circle at 86% 8%, rgba(244, 114, 38, 0.12), transparent 40%),
57
+ linear-gradient(160deg, #eef7f2 0, #f8fbf7 52%, #eaf2ef 100%);
55
58
  color: var(--dark-color-ml);
56
59
  display: flex;
57
60
  min-height: 100vh;
58
61
  overflow-x: hidden;
59
62
  line-height: 1.6;
60
- font-size: 14px
63
+ font-size: 14px;
64
+ position: relative;
65
+ isolation: isolate
66
+ }
67
+
68
+ body::before,
69
+ body::after {
70
+ content: "";
71
+ position: fixed;
72
+ border-radius: 999px;
73
+ pointer-events: none;
74
+ z-index: -1
75
+ }
76
+
77
+ body::before {
78
+ width: 260px;
79
+ height: 260px;
80
+ left: -80px;
81
+ top: 18vh;
82
+ background: radial-gradient(circle, rgba(15, 118, 110, 0.16) 0, rgba(15, 118, 110, 0) 70%)
83
+ }
84
+
85
+ body::after {
86
+ width: 300px;
87
+ height: 300px;
88
+ right: -120px;
89
+ bottom: 10vh;
90
+ background: radial-gradient(circle, rgba(251, 146, 60, 0.18) 0, rgba(251, 146, 60, 0) 72%)
61
91
  }
62
92
 
63
93
  /* Removed unused sidebar/menu styles to reduce CSS size. */
64
94
 
65
95
  .logo {
66
- font-size: 2rem;
96
+ font-family: var(--font-family-headings);
97
+ font-size: 1.65rem;
67
98
  font-weight: 700;
68
- color: var(--primary-color-ml);
99
+ color: #0b4f4a;
69
100
  display: flex;
70
101
  align-items: center;
71
- gap: .3rem
102
+ gap: .5rem
72
103
  }
73
104
 
74
105
  .logo img {
@@ -84,15 +115,15 @@
84
115
  }
85
116
 
86
117
  .top-nav {
87
- background: #fff;
118
+ background: rgba(255, 255, 255, .85);
88
119
  backdrop-filter: blur(10px);
89
- padding: .5rem 2rem;
120
+ padding: .75rem 1.5rem;
90
121
  display: flex;
91
122
  align-items: center;
92
123
  justify-content: flex-end;
93
124
  box-shadow: var(--shadow-md-ml);
94
125
  z-index: 90;
95
- border-bottom: 1px solid var(--gray-light-color-ml);
126
+ border-bottom: 1px solid rgba(255, 255, 255, .75);
96
127
  gap: 1rem
97
128
  }
98
129
 
@@ -100,7 +131,7 @@
100
131
 
101
132
  .content-area {
102
133
  flex: 1;
103
- padding: 2rem;
134
+ padding: 1.6rem;
104
135
  overflow-y: auto
105
136
  }
106
137
 
@@ -125,13 +156,17 @@
125
156
  display: flex;
126
157
  align-items: center;
127
158
  justify-content: space-between;
128
- margin-bottom: 1rem;
129
- padding-bottom: 1rem;
130
- border-bottom: 2px solid var(--gray-light-color-ml)
159
+ margin-bottom: 1.2rem;
160
+ padding: 1rem 1.2rem;
161
+ border: 1px solid rgba(255, 255, 255, .9);
162
+ border-radius: 1rem;
163
+ background: rgba(255, 255, 255, .74);
164
+ box-shadow: 0 12px 34px rgba(15, 23, 42, .07)
131
165
  }
132
166
 
133
167
  .page-title {
134
- font-size: 2rem;
168
+ font-family: var(--font-family-headings);
169
+ font-size: 1.5rem;
135
170
  font-weight: 700;
136
171
  color: var(--dark-color-ml);
137
172
  margin: 0
@@ -142,10 +177,10 @@
142
177
  align-items: center;
143
178
  color: var(--gray-color-ml);
144
179
  font-size: .875rem;
145
- background: var(--light-color-ml);
146
- padding: .5rem 1rem;
147
- border-radius: var(--border-radius-r-ml);
148
- border: 1px solid var(--gray-light-color-ml)
180
+ background: linear-gradient(135deg, rgba(15, 118, 110, .08), rgba(251, 146, 60, .08));
181
+ padding: .4rem .85rem;
182
+ border-radius: 999px;
183
+ border: 1px solid rgba(15, 118, 110, .15)
149
184
  }
150
185
 
151
186
  .breadcrumb a {
@@ -204,8 +239,8 @@
204
239
  <div class="main-content">
205
240
  <nav class="top-nav">
206
241
  <div class="logo">
207
- <img onclick="window.location='/'" src="/icon.svg" alt="Logo">
208
- <span onclick="window.location='/'" style="cursor:pointer;">BKBucket</span>
242
+ <img onclick="window.location='/mbkbucket'" src="/icon.svg" alt="Logo">
243
+ <span onclick="window.location='/mbkbucket'" style="cursor:pointer;">BKBucket</span>
209
244
  </div>
210
245
 
211
246
  {{> profilemenu }}
@@ -1,8 +1,8 @@
1
1
  {{!-- File Management System - Bucket Dashboard --}}
2
- <link rel="stylesheet" href="/mbkbucket/bucketadmin.css?v={{assetVersion}}">
2
+ <link rel="stylesheet" href="/mbkbucket/bucketadmin.css{{mbkbucket_cachebuster}}">
3
3
  <div id="bucketAppData" data-app-name="{{APP_NAME}}" hidden></div>
4
4
  <!-- File Statistics -->
5
- <div class="dashboard-card file-stats">
5
+ <div class="dashboard-card file-stats reveal-item reveal-delay-1">
6
6
  <div class="card-body">
7
7
  <div class="stats-content">
8
8
  <div class="stats-icon">
@@ -12,10 +12,24 @@
12
12
  <h2 id="totalFilesCount">Loading...</h2>
13
13
  <p id="filterInfo">Loading file information...</p>
14
14
  </div>
15
- <div style="margin-left: auto;">
16
- <span class="stats-bucket-badge">
17
- <i class="fas fa-database" style="margin-right:0.4rem;"></i>{{bucketvar}}
18
- </span>
15
+ <div class="bucket-switcher-wrap">
16
+ <div class="bucket-switcher" role="group" aria-label="Bucket selection">
17
+ <label for="bucketSelector" class="bucket-switcher-label">Active Bucket</label>
18
+ <div class="bucket-switcher-control">
19
+ <i class="fas fa-database bucket-switcher-icon" aria-hidden="true"></i>
20
+ <select id="bucketSelector" class="form-control-sm bucket-switcher-select" aria-label="Select connected bucket">
21
+ {{#if bucketOptions.length}}
22
+ {{#each bucketOptions}}
23
+ <option value="{{name}}" {{#if isSelected}}selected{{/if}}>{{name}}{{#if isDefault}} (defualt){{/if}}</option>
24
+ {{/each}}
25
+ {{else}}
26
+ <option value="">No buckets configured</option>
27
+ {{/if}}
28
+ </select>
29
+ <i class="fas fa-chevron-down bucket-switcher-caret" aria-hidden="true"></i>
30
+ </div>
31
+ <small id="bucketSwitchStatus" class="bucket-switcher-status" style="display:none;">Switching bucket...</small>
32
+ </div>
19
33
  </div>
20
34
  </div>
21
35
  </div>
@@ -23,7 +37,7 @@
23
37
 
24
38
  {{> bucketadmin_alerts }}
25
39
 
26
- <div class="mb-4">
40
+ <div class="mb-4 reveal-item reveal-delay-2">
27
41
  <!-- Upload Form -->
28
42
  <div class="col-lg-12">
29
43
  <div class="dashboard-card h-100">
@@ -92,7 +106,7 @@
92
106
  </div>
93
107
 
94
108
  <!-- File List -->
95
- <div class="dashboard-card">
109
+ <div class="dashboard-card reveal-item reveal-delay-3">
96
110
  <div class="card-header file-list-header">
97
111
  <h5 class="card-title">
98
112
  <div>
@@ -108,6 +122,13 @@
108
122
  <button type="button" class="btn btn-sm btn-outline-secondary" onclick="clearSearch()" aria-label="Clear search"><i class="fas fa-times"></i></button>
109
123
  </div>
110
124
 
125
+ <div class="quick-filter-group" role="group" aria-label="Quick filters">
126
+ <button type="button" class="btn btn-sm quick-filter-btn active" data-filter="all" onclick="setQuickFilter('all')">All</button>
127
+ <button type="button" class="btn btn-sm quick-filter-btn" data-filter="media" onclick="setQuickFilter('media')">Media</button>
128
+ <button type="button" class="btn btn-sm quick-filter-btn" data-filter="docs" onclick="setQuickFilter('docs')">Docs</button>
129
+ <button type="button" class="btn btn-sm quick-filter-btn" data-filter="code" onclick="setQuickFilter('code')">Code</button>
130
+ </div>
131
+
111
132
  <div class="btn-group" role="group" aria-label="Actions">
112
133
  <button type="button" class="btn btn-sm btn-success" onclick="createFile()" title="New Text File">
113
134
  <i class="fas fa-file-alt"></i> New File
@@ -148,4 +169,4 @@
148
169
  <!-- Toast Container -->
149
170
  <div id="toast-container" class="toast-container"></div>
150
171
 
151
- <script src="/mbkbucket/bucketadmin.js?v={{assetVersion}}"></script>
172
+ <script src="/mbkbucket/bucketadmin.js{{mbkbucket_cachebuster}}"></script>
@@ -0,0 +1 @@
1
+ {{> bucketadmincontent }}