@mongoosejs/studio 0.1.16 → 0.1.18
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/backend/actions/Model/getCollectionInfo.js +49 -0
- package/backend/actions/Model/index.js +1 -0
- package/backend/actions/Model/updateDocument.js +3 -0
- package/backend/actions/Model/updateDocuments.js +3 -0
- package/frontend/public/app.js +954 -524
- package/frontend/public/tw.css +105 -19
- package/frontend/src/api.js +7 -1
- package/frontend/src/array-utils.js +66 -0
- package/frontend/src/chat/chat-message/chat-message.js +7 -0
- package/frontend/src/chat/chat-message-script/chat-message-script.js +21 -0
- package/frontend/src/chat/chat.js +22 -0
- package/frontend/src/clone-document/clone-document.js +14 -5
- package/frontend/src/create-dashboard/create-dashboard.js +8 -0
- package/frontend/src/create-document/create-document.js +14 -5
- package/frontend/src/dashboard/dashboard.js +9 -1
- package/frontend/src/dashboard/edit-dashboard/edit-dashboard.js +8 -0
- package/frontend/src/dashboards/dashboards.js +8 -0
- package/frontend/src/detail-array/detail-array.html +25 -3
- package/frontend/src/detail-array/detail-array.js +22 -6
- package/frontend/src/document/document.js +32 -20
- package/frontend/src/document-details/document-details.html +61 -12
- package/frontend/src/document-details/document-details.js +29 -13
- package/frontend/src/document-details/document-property/document-property.html +41 -3
- package/frontend/src/document-details/document-property/document-property.js +47 -2
- package/frontend/src/edit-array/edit-array.html +5 -2
- package/frontend/src/edit-array/edit-array.js +79 -23
- package/frontend/src/export-query-results/export-query-results.js +8 -1
- package/frontend/src/index.js +2 -1
- package/frontend/src/list-array/list-array.html +1 -1
- package/frontend/src/list-array/list-array.js +0 -2
- package/frontend/src/models/models.html +76 -8
- package/frontend/src/models/models.js +112 -1
- package/frontend/src/update-document/update-document.js +28 -27
- package/package.json +1 -1
- package/frontend/src/edit-array/edit-array.css +0 -3
- package/frontend/src/list-array/list-array.css +0 -8
|
@@ -79,13 +79,6 @@
|
|
|
79
79
|
>
|
|
80
80
|
Delete
|
|
81
81
|
</button>
|
|
82
|
-
<button
|
|
83
|
-
@click="openIndexModal"
|
|
84
|
-
type="button"
|
|
85
|
-
v-show="!selectMultiple"
|
|
86
|
-
class="rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600">
|
|
87
|
-
Indexes
|
|
88
|
-
</button>
|
|
89
82
|
<button
|
|
90
83
|
@click="shouldShowCreateModal = true;"
|
|
91
84
|
type="button"
|
|
@@ -100,6 +93,38 @@
|
|
|
100
93
|
class="rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600">
|
|
101
94
|
Fields
|
|
102
95
|
</button>
|
|
96
|
+
<div class="relative" v-show="!selectMultiple" ref="actionsMenuContainer" @keyup.esc.prevent="closeActionsMenu">
|
|
97
|
+
<button
|
|
98
|
+
@click="toggleActionsMenu"
|
|
99
|
+
type="button"
|
|
100
|
+
aria-label="More actions"
|
|
101
|
+
class="rounded bg-white px-2 py-2 text-sm font-semibold text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600">
|
|
102
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
|
103
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Z" />
|
|
104
|
+
</svg>
|
|
105
|
+
</button>
|
|
106
|
+
<div
|
|
107
|
+
v-if="showActionsMenu"
|
|
108
|
+
class="absolute right-0 mt-2 w-48 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none z-20"
|
|
109
|
+
>
|
|
110
|
+
<div class="py-1">
|
|
111
|
+
<button
|
|
112
|
+
@click="openIndexModal"
|
|
113
|
+
type="button"
|
|
114
|
+
class="block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100"
|
|
115
|
+
>
|
|
116
|
+
Indexes
|
|
117
|
+
</button>
|
|
118
|
+
<button
|
|
119
|
+
@click="openCollectionInfo"
|
|
120
|
+
type="button"
|
|
121
|
+
class="block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100"
|
|
122
|
+
>
|
|
123
|
+
Collection Info
|
|
124
|
+
</button>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
103
128
|
<span class="isolate inline-flex rounded-md shadow-sm">
|
|
104
129
|
<button
|
|
105
130
|
@click="setOutputType('table')"
|
|
@@ -193,7 +218,12 @@
|
|
|
193
218
|
<div v-for="index in mongoDBIndexes" class="w-full flex items-center">
|
|
194
219
|
<div class="grow shrink text-left flex justify-between items-center" v-if="index.name != '_id_'">
|
|
195
220
|
<div>
|
|
196
|
-
<div class="font-bold
|
|
221
|
+
<div class="font-bold flex items-center gap-2">
|
|
222
|
+
<div>{{ index.name }}</div>
|
|
223
|
+
<div v-if="isTTLIndex(index)" class="rounded-full bg-ultramarine-100 px-2 py-0.5 text-xs font-semibold text-ultramarine-700">
|
|
224
|
+
TTL: {{ formatTTL(index.expireAfterSeconds) }}
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
197
227
|
<div class="text-sm font-mono">{{ JSON.stringify(index.key) }}</div>
|
|
198
228
|
</div>
|
|
199
229
|
<div>
|
|
@@ -208,6 +238,44 @@
|
|
|
208
238
|
</div>
|
|
209
239
|
</template>
|
|
210
240
|
</modal>
|
|
241
|
+
<modal v-if="shouldShowCollectionInfoModal">
|
|
242
|
+
<template v-slot:body>
|
|
243
|
+
<div class="modal-exit" @click="shouldShowCollectionInfoModal = false">×</div>
|
|
244
|
+
<div class="text-xl font-bold mb-2">Collection Info</div>
|
|
245
|
+
<div v-if="!collectionInfo" class="text-gray-600">Loading collection details...</div>
|
|
246
|
+
<div v-else class="space-y-3">
|
|
247
|
+
<div class="flex justify-between gap-4">
|
|
248
|
+
<div class="font-semibold text-gray-700">Documents</div>
|
|
249
|
+
<div class="text-gray-900">{{ formatNumber(collectionInfo.documentCount) }}</div>
|
|
250
|
+
</div>
|
|
251
|
+
<div class="flex justify-between gap-4">
|
|
252
|
+
<div class="font-semibold text-gray-700">Indexes</div>
|
|
253
|
+
<div class="text-gray-900">{{ formatNumber(collectionInfo.indexCount) }}</div>
|
|
254
|
+
</div>
|
|
255
|
+
<div class="flex justify-between gap-4">
|
|
256
|
+
<div class="font-semibold text-gray-700">Total Index Size</div>
|
|
257
|
+
<div class="text-gray-900">{{ formatCollectionSize(collectionInfo.totalIndexSize) }}</div>
|
|
258
|
+
</div>
|
|
259
|
+
<div class="flex justify-between gap-4">
|
|
260
|
+
<div class="font-semibold text-gray-700">Total Storage Size</div>
|
|
261
|
+
<div class="text-gray-900">{{ formatCollectionSize(collectionInfo.size) }}</div>
|
|
262
|
+
</div>
|
|
263
|
+
<div class="flex flex-col gap-1">
|
|
264
|
+
<div class="flex justify-between gap-4">
|
|
265
|
+
<div class="font-semibold text-gray-700">Collation</div>
|
|
266
|
+
<div class="text-gray-900">{{ collectionInfo.hasCollation ? 'Yes' : 'No' }}</div>
|
|
267
|
+
</div>
|
|
268
|
+
<div v-if="collectionInfo.hasCollation" class="rounded bg-gray-100 p-3 text-sm text-gray-800 overflow-x-auto">
|
|
269
|
+
<pre class="whitespace-pre-wrap">{{ JSON.stringify(collectionInfo.collation, null, 2) }}</pre>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
<div class="flex justify-between gap-4">
|
|
273
|
+
<div class="font-semibold text-gray-700">Capped</div>
|
|
274
|
+
<div class="text-gray-900">{{ collectionInfo.capped ? 'Yes' : 'No' }}</div>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
</template>
|
|
278
|
+
</modal>
|
|
211
279
|
<modal v-if="shouldShowFieldModal">
|
|
212
280
|
<template v-slot:body>
|
|
213
281
|
<div class="modal-exit" @click="shouldShowFieldModal = false; selectedPaths = [...filteredPaths];">×</div>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const api = require('../api');
|
|
4
4
|
const template = require('./models.html');
|
|
5
5
|
const mpath = require('mpath');
|
|
6
|
+
const vanillatoasts = require('vanillatoasts');
|
|
6
7
|
|
|
7
8
|
const appendCSS = require('../appendCSS');
|
|
8
9
|
appendCSS(require('./models.css'));
|
|
@@ -34,6 +35,7 @@ module.exports = app => app.component('models', {
|
|
|
34
35
|
shouldShowCreateModal: false,
|
|
35
36
|
shouldShowFieldModal: false,
|
|
36
37
|
shouldShowIndexModal: false,
|
|
38
|
+
shouldShowCollectionInfoModal: false,
|
|
37
39
|
shouldShowUpdateMultipleModal: false,
|
|
38
40
|
shouldShowDeleteMultipleModal: false,
|
|
39
41
|
shouldExport: {},
|
|
@@ -44,7 +46,9 @@ module.exports = app => app.component('models', {
|
|
|
44
46
|
outputType: 'table', // json, table
|
|
45
47
|
hideSidebar: null,
|
|
46
48
|
lastSelectedIndex: null,
|
|
47
|
-
error: null
|
|
49
|
+
error: null,
|
|
50
|
+
showActionsMenu: false,
|
|
51
|
+
collectionInfo: null
|
|
48
52
|
}),
|
|
49
53
|
created() {
|
|
50
54
|
this.currentModel = this.model;
|
|
@@ -53,12 +57,23 @@ module.exports = app => app.component('models', {
|
|
|
53
57
|
beforeDestroy() {
|
|
54
58
|
document.removeEventListener('scroll', this.onScroll, true);
|
|
55
59
|
window.removeEventListener('popstate', this.onPopState, true);
|
|
60
|
+
document.removeEventListener('click', this.onOutsideActionsMenuClick, true);
|
|
56
61
|
},
|
|
57
62
|
async mounted() {
|
|
58
63
|
this.onScroll = () => this.checkIfScrolledToBottom();
|
|
59
64
|
document.addEventListener('scroll', this.onScroll, true);
|
|
60
65
|
this.onPopState = () => this.initSearchFromUrl();
|
|
61
66
|
window.addEventListener('popstate', this.onPopState, true);
|
|
67
|
+
this.onOutsideActionsMenuClick = event => {
|
|
68
|
+
if (!this.showActionsMenu) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const actionsMenu = this.$refs.actionsMenuContainer;
|
|
72
|
+
if (actionsMenu && !actionsMenu.contains(event.target)) {
|
|
73
|
+
this.closeActionsMenu();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
document.addEventListener('click', this.onOutsideActionsMenuClick, true);
|
|
62
77
|
const { models, readyState } = await api.Model.listModels();
|
|
63
78
|
this.models = models;
|
|
64
79
|
if (this.currentModel == null && this.models.length > 0) {
|
|
@@ -160,6 +175,13 @@ module.exports = app => app.component('models', {
|
|
|
160
175
|
async dropIndex(name) {
|
|
161
176
|
const { mongoDBIndexes } = await api.Model.dropIndex({ model: this.currentModel, name });
|
|
162
177
|
this.mongoDBIndexes = mongoDBIndexes;
|
|
178
|
+
vanillatoasts.create({
|
|
179
|
+
title: 'Index dropped!',
|
|
180
|
+
type: 'success',
|
|
181
|
+
timeout: 3000,
|
|
182
|
+
icon: 'images/success.png',
|
|
183
|
+
positionClass: 'bottomRight'
|
|
184
|
+
});
|
|
163
185
|
},
|
|
164
186
|
async closeCreationModal() {
|
|
165
187
|
this.shouldShowCreateModal = false;
|
|
@@ -232,11 +254,86 @@ module.exports = app => app.component('models', {
|
|
|
232
254
|
}
|
|
233
255
|
},
|
|
234
256
|
async openIndexModal() {
|
|
257
|
+
this.closeActionsMenu();
|
|
235
258
|
this.shouldShowIndexModal = true;
|
|
236
259
|
const { mongoDBIndexes, schemaIndexes } = await api.Model.getIndexes({ model: this.currentModel });
|
|
237
260
|
this.mongoDBIndexes = mongoDBIndexes;
|
|
238
261
|
this.schemaIndexes = schemaIndexes;
|
|
239
262
|
},
|
|
263
|
+
toggleActionsMenu() {
|
|
264
|
+
this.showActionsMenu = !this.showActionsMenu;
|
|
265
|
+
},
|
|
266
|
+
closeActionsMenu() {
|
|
267
|
+
this.showActionsMenu = false;
|
|
268
|
+
},
|
|
269
|
+
async openCollectionInfo() {
|
|
270
|
+
this.closeActionsMenu();
|
|
271
|
+
this.shouldShowCollectionInfoModal = true;
|
|
272
|
+
this.collectionInfo = null;
|
|
273
|
+
const { info } = await api.Model.getCollectionInfo({ model: this.currentModel });
|
|
274
|
+
this.collectionInfo = info;
|
|
275
|
+
},
|
|
276
|
+
isTTLIndex(index) {
|
|
277
|
+
return index != null && index.expireAfterSeconds != null;
|
|
278
|
+
},
|
|
279
|
+
formatTTL(expireAfterSeconds) {
|
|
280
|
+
if (typeof expireAfterSeconds !== 'number') {
|
|
281
|
+
return '';
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
let remaining = expireAfterSeconds;
|
|
285
|
+
const days = Math.floor(remaining / (24 * 60 * 60));
|
|
286
|
+
remaining = remaining % (24 * 60 * 60);
|
|
287
|
+
const hours = Math.floor(remaining / (60 * 60));
|
|
288
|
+
remaining = remaining % (60 * 60);
|
|
289
|
+
const minutes = Math.floor(remaining / 60);
|
|
290
|
+
const seconds = remaining % 60;
|
|
291
|
+
|
|
292
|
+
const parts = [];
|
|
293
|
+
if (days > 0) {
|
|
294
|
+
parts.push(`${days} day${days === 1 ? '' : 's'}`);
|
|
295
|
+
}
|
|
296
|
+
if (hours > 0) {
|
|
297
|
+
parts.push(`${hours} hour${hours === 1 ? '' : 's'}`);
|
|
298
|
+
}
|
|
299
|
+
if (minutes > 0) {
|
|
300
|
+
parts.push(`${minutes} minute${minutes === 1 ? '' : 's'}`);
|
|
301
|
+
}
|
|
302
|
+
if (seconds > 0 || parts.length === 0) {
|
|
303
|
+
parts.push(`${seconds} second${seconds === 1 ? '' : 's'}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return parts.join(', ');
|
|
307
|
+
},
|
|
308
|
+
formatCollectionSize(size) {
|
|
309
|
+
if (typeof size !== 'number') {
|
|
310
|
+
return 'Unknown';
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const KB = 1024;
|
|
314
|
+
const MB = KB * 1024;
|
|
315
|
+
const GB = MB * 1024;
|
|
316
|
+
const TB = GB * 1024;
|
|
317
|
+
|
|
318
|
+
if (size >= TB) {
|
|
319
|
+
return `${(size / TB).toFixed(3)} TB`;
|
|
320
|
+
} else if (size >= GB) {
|
|
321
|
+
return `${(size / GB).toFixed(3)} GB`;
|
|
322
|
+
} else if (size >= MB) {
|
|
323
|
+
return `${(size / MB).toFixed(3)} MB`;
|
|
324
|
+
} else if (size >= KB) {
|
|
325
|
+
return `${(size / KB).toFixed(3)} KB`;
|
|
326
|
+
} else {
|
|
327
|
+
return `${size.toLocaleString()} bytes`;
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
formatNumber(value) {
|
|
331
|
+
if (typeof value !== 'number') {
|
|
332
|
+
return 'Unknown';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return value.toLocaleString();
|
|
336
|
+
},
|
|
240
337
|
checkIndexLocation(indexName) {
|
|
241
338
|
if (this.schemaIndexes.find(x => x.name == indexName) && this.mongoDBIndexes.find(x => x.name == indexName)) {
|
|
242
339
|
return 'text-gray-500';
|
|
@@ -410,6 +507,13 @@ module.exports = app => app.component('models', {
|
|
|
410
507
|
this.documents[index] = res.doc;
|
|
411
508
|
}
|
|
412
509
|
this.edittingDoc = null;
|
|
510
|
+
vanillatoasts.create({
|
|
511
|
+
title: 'Document updated!',
|
|
512
|
+
type: 'success',
|
|
513
|
+
timeout: 3000,
|
|
514
|
+
icon: 'images/success.png',
|
|
515
|
+
positionClass: 'bottomRight'
|
|
516
|
+
});
|
|
413
517
|
},
|
|
414
518
|
handleDocumentClick(document, event) {
|
|
415
519
|
if (this.selectMultiple) {
|
|
@@ -473,6 +577,13 @@ module.exports = app => app.component('models', {
|
|
|
473
577
|
this.lastSelectedIndex = null;
|
|
474
578
|
this.shouldShowDeleteMultipleModal = false;
|
|
475
579
|
this.selectMultiple = false;
|
|
580
|
+
vanillatoasts.create({
|
|
581
|
+
title: 'Documents deleted!',
|
|
582
|
+
type: 'success',
|
|
583
|
+
timeout: 3000,
|
|
584
|
+
icon: 'images/success.png',
|
|
585
|
+
positionClass: 'bottomRight'
|
|
586
|
+
});
|
|
476
587
|
},
|
|
477
588
|
async updateDocuments() {
|
|
478
589
|
await this.getDocuments();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const api = require('../api');
|
|
4
|
+
const vanillatoasts = require('vanillatoasts');
|
|
4
5
|
|
|
5
6
|
const { BSON, EJSON } = require('mongodb/lib/bson');
|
|
6
7
|
|
|
@@ -28,35 +29,35 @@ module.exports = app => app.component('update-document', {
|
|
|
28
29
|
methods: {
|
|
29
30
|
async updateDocument() {
|
|
30
31
|
const data = EJSON.serialize(eval(`(${this.editor.getValue()})`));
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this.errors = message.split(',').map(error => {
|
|
50
|
-
return error.split(': ').slice(1).join(': ').trim();
|
|
51
|
-
});
|
|
52
|
-
throw new Error(err.response?.data?.message);
|
|
53
|
-
}
|
|
54
|
-
throw err;
|
|
32
|
+
try {
|
|
33
|
+
if (this.multiple) {
|
|
34
|
+
const ids = this.document.map(x => x._id);
|
|
35
|
+
await api.Model.updateDocuments({ model: this.currentModel, _id: ids, update: data });
|
|
36
|
+
} else {
|
|
37
|
+
await api.Model.updateDocument({ model: this.currentModel, _id: this.document._id, update: data });
|
|
38
|
+
}
|
|
39
|
+
this.errors.length = 0;
|
|
40
|
+
this.$emit('update');
|
|
41
|
+
this.$emit('close');
|
|
42
|
+
this.$nextTick(() => {
|
|
43
|
+
vanillatoasts.create({
|
|
44
|
+
title: this.multiple ? 'Documents updated!' : 'Document updated!',
|
|
45
|
+
type: 'success',
|
|
46
|
+
timeout: 3000,
|
|
47
|
+
icon: 'images/success.png',
|
|
48
|
+
positionClass: 'bottomRight'
|
|
49
|
+
});
|
|
55
50
|
});
|
|
51
|
+
} catch (err) {
|
|
52
|
+
if (err.response?.data?.message) {
|
|
53
|
+
console.log(err.response.data);
|
|
54
|
+
const message = err.response.data.message.split(': ').slice(1).join(': ');
|
|
55
|
+
this.errors = message.split(',').map(error => {
|
|
56
|
+
return error.split(': ').slice(1).join(': ').trim();
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
throw err;
|
|
56
60
|
}
|
|
57
|
-
this.errors.length = 0;
|
|
58
|
-
this.$emit('update');
|
|
59
|
-
this.$emit('close');
|
|
60
61
|
}
|
|
61
62
|
},
|
|
62
63
|
mounted: function() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.",
|
|
5
5
|
"homepage": "https://studio.mongoosejs.io/",
|
|
6
6
|
"repository": {
|