@mongoosejs/studio 0.1.17 → 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/frontend/public/app.js +272 -79
- package/frontend/public/tw.css +6 -0
- package/frontend/src/api.js +7 -1
- 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 +8 -0
- package/frontend/src/dashboard/edit-dashboard/edit-dashboard.js +8 -0
- package/frontend/src/dashboards/dashboards.js +8 -0
- package/frontend/src/document/document.js +32 -20
- package/frontend/src/document-details/document-details.js +1 -13
- package/frontend/src/export-query-results/export-query-results.js +8 -1
- package/frontend/src/models/models.html +70 -7
- package/frontend/src/models/models.js +80 -1
- package/frontend/src/update-document/update-document.js +28 -27
- package/package.json +1 -1
package/frontend/public/tw.css
CHANGED
|
@@ -1326,6 +1326,12 @@ video {
|
|
|
1326
1326
|
margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
|
|
1327
1327
|
}
|
|
1328
1328
|
|
|
1329
|
+
.space-y-3 > :not([hidden]) ~ :not([hidden]) {
|
|
1330
|
+
--tw-space-y-reverse: 0;
|
|
1331
|
+
margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse)));
|
|
1332
|
+
margin-bottom: calc(0.75rem * var(--tw-space-y-reverse));
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1329
1335
|
.space-y-4 > :not([hidden]) ~ :not([hidden]) {
|
|
1330
1336
|
--tw-space-y-reverse: 0;
|
|
1331
1337
|
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
|
package/frontend/src/api.js
CHANGED
|
@@ -21,7 +21,7 @@ client.interceptors.request.use(req => {
|
|
|
21
21
|
client.interceptors.response.use(
|
|
22
22
|
res => res,
|
|
23
23
|
err => {
|
|
24
|
-
if (typeof err
|
|
24
|
+
if (typeof err?.response?.data === 'string') {
|
|
25
25
|
throw new Error(`Error in ${err.config?.method} ${err.config?.url}: ${err.response.data}`);
|
|
26
26
|
}
|
|
27
27
|
throw err;
|
|
@@ -132,6 +132,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
132
132
|
yield { document: doc };
|
|
133
133
|
}
|
|
134
134
|
},
|
|
135
|
+
getCollectionInfo: function getCollectionInfo(params) {
|
|
136
|
+
return client.post('', { action: 'Model.getCollectionInfo', ...params }).then(res => res.data);
|
|
137
|
+
},
|
|
135
138
|
getIndexes: function getIndexes(params) {
|
|
136
139
|
return client.post('', { action: 'Model.getIndexes', ...params }).then(res => res.data);
|
|
137
140
|
},
|
|
@@ -338,6 +341,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
338
341
|
}
|
|
339
342
|
}
|
|
340
343
|
},
|
|
344
|
+
getCollectionInfo: function getCollectionInfo(params) {
|
|
345
|
+
return client.post('/Model/getCollectionInfo', params).then(res => res.data);
|
|
346
|
+
},
|
|
341
347
|
getIndexes: function getIndexes(params) {
|
|
342
348
|
return client.post('/Model/getIndexes', params).then(res => res.data);
|
|
343
349
|
},
|
|
@@ -62,6 +62,13 @@ module.exports = app => app.component('chat-message', {
|
|
|
62
62
|
});
|
|
63
63
|
message.executionResult = chatMessage.executionResult;
|
|
64
64
|
console.log(message);
|
|
65
|
+
vanillatoasts.create({
|
|
66
|
+
title: 'Script executed successfully!',
|
|
67
|
+
type: 'success',
|
|
68
|
+
timeout: 3000,
|
|
69
|
+
icon: 'images/success.png',
|
|
70
|
+
positionClass: 'bottomRight'
|
|
71
|
+
});
|
|
65
72
|
},
|
|
66
73
|
async copyMessage() {
|
|
67
74
|
const parts = this.contentSplitByScripts;
|
|
@@ -56,6 +56,13 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
56
56
|
this.highlightCode();
|
|
57
57
|
}
|
|
58
58
|
this.activeTab = 'output';
|
|
59
|
+
vanillatoasts.create({
|
|
60
|
+
title: 'Script executed successfully!',
|
|
61
|
+
type: 'success',
|
|
62
|
+
timeout: 3000,
|
|
63
|
+
icon: 'images/success.png',
|
|
64
|
+
positionClass: 'bottomRight'
|
|
65
|
+
});
|
|
59
66
|
return chatMessage;
|
|
60
67
|
},
|
|
61
68
|
openDetailModal() {
|
|
@@ -157,6 +164,13 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
157
164
|
throw err;
|
|
158
165
|
});
|
|
159
166
|
this.createError = null;
|
|
167
|
+
vanillatoasts.create({
|
|
168
|
+
title: 'Dashboard created!',
|
|
169
|
+
type: 'success',
|
|
170
|
+
timeout: 3000,
|
|
171
|
+
icon: 'images/success.png',
|
|
172
|
+
positionClass: 'bottomRight'
|
|
173
|
+
});
|
|
160
174
|
this.showCreateDashboardModal = false;
|
|
161
175
|
this.$router.push('/dashboard/' + dashboard._id);
|
|
162
176
|
},
|
|
@@ -183,6 +197,13 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
183
197
|
});
|
|
184
198
|
|
|
185
199
|
this.overwriteError = null;
|
|
200
|
+
vanillatoasts.create({
|
|
201
|
+
title: 'Dashboard updated!',
|
|
202
|
+
type: 'success',
|
|
203
|
+
timeout: 3000,
|
|
204
|
+
icon: 'images/success.png',
|
|
205
|
+
positionClass: 'bottomRight'
|
|
206
|
+
});
|
|
186
207
|
this.showOverwriteDashboardConfirmationModal = false;
|
|
187
208
|
this.$router.push('/dashboard/' + doc._id);
|
|
188
209
|
},
|
|
@@ -28,6 +28,13 @@ module.exports = app => app.component('chat', {
|
|
|
28
28
|
this.chatThreads.unshift(chatThread);
|
|
29
29
|
this.chatThreadId = chatThread._id;
|
|
30
30
|
this.chatMessages = [];
|
|
31
|
+
vanillatoasts.create({
|
|
32
|
+
title: 'Chat thread created!',
|
|
33
|
+
type: 'success',
|
|
34
|
+
timeout: 3000,
|
|
35
|
+
icon: 'images/success.png',
|
|
36
|
+
positionClass: 'bottomRight'
|
|
37
|
+
});
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
this.chatMessages.push({
|
|
@@ -121,6 +128,13 @@ module.exports = app => app.component('chat', {
|
|
|
121
128
|
},
|
|
122
129
|
async createNewThread() {
|
|
123
130
|
const { chatThread } = await api.ChatThread.createChatThread();
|
|
131
|
+
vanillatoasts.create({
|
|
132
|
+
title: 'Chat thread created!',
|
|
133
|
+
type: 'success',
|
|
134
|
+
timeout: 3000,
|
|
135
|
+
icon: 'images/success.png',
|
|
136
|
+
positionClass: 'bottomRight'
|
|
137
|
+
});
|
|
124
138
|
this.$router.push('/chat/' + chatThread._id);
|
|
125
139
|
},
|
|
126
140
|
async toggleShareThread() {
|
|
@@ -136,6 +150,14 @@ module.exports = app => app.component('chat', {
|
|
|
136
150
|
this.chatThreads.splice(idx, 1, chatThread);
|
|
137
151
|
}
|
|
138
152
|
|
|
153
|
+
vanillatoasts.create({
|
|
154
|
+
title: 'Chat thread shared!',
|
|
155
|
+
type: 'success',
|
|
156
|
+
timeout: 3000,
|
|
157
|
+
icon: 'images/success.png',
|
|
158
|
+
positionClass: 'bottomRight'
|
|
159
|
+
});
|
|
160
|
+
|
|
139
161
|
// Copy current URL to clipboard and show a toast
|
|
140
162
|
const url = window.location.href;
|
|
141
163
|
await navigator.clipboard.writeText(url);
|
|
@@ -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
|
|
|
@@ -29,19 +30,27 @@ module.exports = app => app.component('clone-document', {
|
|
|
29
30
|
methods: {
|
|
30
31
|
async cloneDocument() {
|
|
31
32
|
const data = EJSON.serialize(eval(`(${this.editor.getValue()})`));
|
|
32
|
-
|
|
33
|
+
try {
|
|
34
|
+
const { doc } = await api.Model.createDocument({ model: this.currentModel, data });
|
|
35
|
+
this.errors.length = 0;
|
|
36
|
+
vanillatoasts.create({
|
|
37
|
+
title: 'Document cloned!',
|
|
38
|
+
type: 'success',
|
|
39
|
+
timeout: 3000,
|
|
40
|
+
icon: 'images/success.png',
|
|
41
|
+
positionClass: 'bottomRight'
|
|
42
|
+
});
|
|
43
|
+
this.$emit('close', doc);
|
|
44
|
+
} catch (err) {
|
|
33
45
|
if (err.response?.data?.message) {
|
|
34
46
|
console.log(err.response.data);
|
|
35
47
|
const message = err.response.data.message.split(': ').slice(1).join(': ');
|
|
36
48
|
this.errors = message.split(',').map(error => {
|
|
37
49
|
return error.split(': ').slice(1).join(': ').trim();
|
|
38
50
|
});
|
|
39
|
-
throw new Error(err.response?.data?.message);
|
|
40
51
|
}
|
|
41
52
|
throw err;
|
|
42
|
-
}
|
|
43
|
-
this.errors.length = 0;
|
|
44
|
-
this.$emit('close', doc);
|
|
53
|
+
}
|
|
45
54
|
}
|
|
46
55
|
},
|
|
47
56
|
mounted: function() {
|
|
@@ -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 template = require('./create-dashboard.html');
|
|
6
7
|
|
|
@@ -28,6 +29,13 @@ module.exports = app => app.component('create-dashboard', {
|
|
|
28
29
|
throw err;
|
|
29
30
|
});
|
|
30
31
|
this.errors.length = 0;
|
|
32
|
+
vanillatoasts.create({
|
|
33
|
+
title: 'Dashboard created!',
|
|
34
|
+
type: 'success',
|
|
35
|
+
timeout: 3000,
|
|
36
|
+
icon: 'images/success.png',
|
|
37
|
+
positionClass: 'bottomRight'
|
|
38
|
+
});
|
|
31
39
|
this.$emit('close', dashboard);
|
|
32
40
|
}
|
|
33
41
|
},
|
|
@@ -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
|
|
|
@@ -29,19 +30,27 @@ module.exports = app => app.component('create-document', {
|
|
|
29
30
|
methods: {
|
|
30
31
|
async createDocument() {
|
|
31
32
|
const data = EJSON.serialize(eval(`(${this.editor.getValue()})`));
|
|
32
|
-
|
|
33
|
+
try {
|
|
34
|
+
const { doc } = await api.Model.createDocument({ model: this.currentModel, data });
|
|
35
|
+
this.errors.length = 0;
|
|
36
|
+
vanillatoasts.create({
|
|
37
|
+
title: 'Document created!',
|
|
38
|
+
type: 'success',
|
|
39
|
+
timeout: 3000,
|
|
40
|
+
icon: 'images/success.png',
|
|
41
|
+
positionClass: 'bottomRight'
|
|
42
|
+
});
|
|
43
|
+
this.$emit('close', doc);
|
|
44
|
+
} catch (err) {
|
|
33
45
|
if (err.response?.data?.message) {
|
|
34
46
|
console.log(err.response.data);
|
|
35
47
|
const message = err.response.data.message.split(': ').slice(1).join(': ');
|
|
36
48
|
this.errors = message.split(',').map(error => {
|
|
37
49
|
return error.split(': ').slice(1).join(': ').trim();
|
|
38
50
|
});
|
|
39
|
-
throw new Error(err.response?.data?.message);
|
|
40
51
|
}
|
|
41
52
|
throw err;
|
|
42
|
-
}
|
|
43
|
-
this.errors.length = 0;
|
|
44
|
-
this.$emit('close', doc);
|
|
53
|
+
}
|
|
45
54
|
}
|
|
46
55
|
},
|
|
47
56
|
mounted: function() {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const api = require('../api');
|
|
4
4
|
const template = require('./dashboard.html');
|
|
5
|
+
const vanillatoasts = require('vanillatoasts');
|
|
5
6
|
|
|
6
7
|
module.exports = app => app.component('dashboard', {
|
|
7
8
|
template: template,
|
|
@@ -109,6 +110,13 @@ module.exports = app => app.component('dashboard', {
|
|
|
109
110
|
initialMessage,
|
|
110
111
|
dashboardId: this.dashboard?._id
|
|
111
112
|
});
|
|
113
|
+
vanillatoasts.create({
|
|
114
|
+
title: 'Chat thread created!',
|
|
115
|
+
type: 'success',
|
|
116
|
+
timeout: 3000,
|
|
117
|
+
icon: 'images/success.png',
|
|
118
|
+
positionClass: 'bottomRight'
|
|
119
|
+
});
|
|
112
120
|
this.$router.push('/chat/' + chatThread._id);
|
|
113
121
|
} finally {
|
|
114
122
|
this.startingChat = false;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const api = require('../../api');
|
|
4
4
|
const template = require('./edit-dashboard.html');
|
|
5
|
+
const vanillatoasts = require('vanillatoasts');
|
|
5
6
|
|
|
6
7
|
module.exports = app => app.component('edit-dashboard', {
|
|
7
8
|
template: template,
|
|
@@ -31,6 +32,13 @@ module.exports = app => app.component('edit-dashboard', {
|
|
|
31
32
|
});
|
|
32
33
|
this.$emit('update', { doc });
|
|
33
34
|
this.editor.setValue(doc.code);
|
|
35
|
+
vanillatoasts.create({
|
|
36
|
+
title: 'Dashboard updated!',
|
|
37
|
+
type: 'success',
|
|
38
|
+
timeout: 3000,
|
|
39
|
+
icon: 'images/success.png',
|
|
40
|
+
positionClass: 'bottomRight'
|
|
41
|
+
});
|
|
34
42
|
this.closeEditor();
|
|
35
43
|
} catch (err) {
|
|
36
44
|
this.$emit('update', { error: { message: err.message } });
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const api = require('../api');
|
|
4
4
|
const template = require('./dashboards.html');
|
|
5
|
+
const vanillatoasts = require('vanillatoasts');
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
module.exports = app => app.component('dashboards', {
|
|
@@ -21,6 +22,13 @@ module.exports = app => app.component('dashboards', {
|
|
|
21
22
|
const removedDashboard = this.dashboards.findIndex(x => x._id.toString() === dashboard._id.toString());
|
|
22
23
|
this.dashboards.splice(removedDashboard, 1);
|
|
23
24
|
this.showDeleteDashboardModal = null;
|
|
25
|
+
vanillatoasts.create({
|
|
26
|
+
title: 'Dashboard deleted!',
|
|
27
|
+
type: 'success',
|
|
28
|
+
timeout: 3000,
|
|
29
|
+
icon: 'images/success.png',
|
|
30
|
+
positionClass: 'bottomRight'
|
|
31
|
+
});
|
|
24
32
|
},
|
|
25
33
|
insertNewDashboard(dashboard) {
|
|
26
34
|
this.dashboards.push(dashboard);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const api = require('../api');
|
|
4
4
|
const mpath = require('mpath');
|
|
5
5
|
const template = require('./document.html');
|
|
6
|
-
const
|
|
6
|
+
const vanillatoasts = require('vanillatoasts');
|
|
7
7
|
|
|
8
8
|
const appendCSS = require('../appendCSS');
|
|
9
9
|
|
|
@@ -32,20 +32,24 @@ module.exports = app => app.component('document', {
|
|
|
32
32
|
window.pageState = this;
|
|
33
33
|
// Store query parameters from the route (preserved from models page)
|
|
34
34
|
this.previousQuery = Object.assign({}, this.$route.query);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
35
|
+
try {
|
|
36
|
+
const { doc, schemaPaths, virtualPaths } = await api.Model.getDocument({ model: this.model, documentId: this.documentId });
|
|
37
|
+
window.doc = doc;
|
|
38
|
+
this.document = doc;
|
|
39
|
+
this.schemaPaths = Object.keys(schemaPaths).sort((k1, k2) => {
|
|
40
|
+
if (k1 === '_id' && k2 !== '_id') {
|
|
41
|
+
return -1;
|
|
42
|
+
}
|
|
43
|
+
if (k1 !== '_id' && k2 === '_id') {
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
return 0;
|
|
47
|
+
}).map(key => schemaPaths[key]);
|
|
48
|
+
this.virtualPaths = virtualPaths || [];
|
|
49
|
+
this.status = 'loaded';
|
|
50
|
+
} finally {
|
|
51
|
+
this.status = 'loaded';
|
|
52
|
+
}
|
|
49
53
|
},
|
|
50
54
|
computed: {
|
|
51
55
|
canManipulate() {
|
|
@@ -79,6 +83,13 @@ module.exports = app => app.component('document', {
|
|
|
79
83
|
this.changes = {};
|
|
80
84
|
this.editting = false;
|
|
81
85
|
this.shouldShowConfirmModal = false;
|
|
86
|
+
vanillatoasts.create({
|
|
87
|
+
title: 'Document saved!',
|
|
88
|
+
type: 'success',
|
|
89
|
+
timeout: 3000,
|
|
90
|
+
icon: 'images/success.png',
|
|
91
|
+
positionClass: 'bottomRight'
|
|
92
|
+
});
|
|
82
93
|
},
|
|
83
94
|
async remove() {
|
|
84
95
|
const { doc } = await api.Model.deleteDocument({
|
|
@@ -88,10 +99,11 @@ module.exports = app => app.component('document', {
|
|
|
88
99
|
if (doc.acknowledged) {
|
|
89
100
|
this.editting = false;
|
|
90
101
|
this.document = {};
|
|
91
|
-
|
|
92
|
-
title: 'Document
|
|
102
|
+
vanillatoasts.create({
|
|
103
|
+
title: 'Document deleted!',
|
|
93
104
|
type: 'success',
|
|
94
105
|
timeout: 3000,
|
|
106
|
+
icon: 'images/success.png',
|
|
95
107
|
positionClass: 'bottomRight'
|
|
96
108
|
});
|
|
97
109
|
this.$router.push({
|
|
@@ -112,12 +124,12 @@ module.exports = app => app.component('document', {
|
|
|
112
124
|
});
|
|
113
125
|
this.document = doc;
|
|
114
126
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
title: 'Field Added!',
|
|
127
|
+
vanillatoasts.create({
|
|
128
|
+
title: 'Field added!',
|
|
118
129
|
text: `Field "${fieldData.name}" has been added to the document`,
|
|
119
130
|
type: 'success',
|
|
120
131
|
timeout: 3000,
|
|
132
|
+
icon: 'images/success.png',
|
|
121
133
|
positionClass: 'bottomRight'
|
|
122
134
|
});
|
|
123
135
|
},
|
|
@@ -366,7 +366,7 @@ module.exports = app => app.component('document-details', {
|
|
|
366
366
|
|
|
367
367
|
try {
|
|
368
368
|
const fieldData = {
|
|
369
|
-
name: this.
|
|
369
|
+
name: this.fieldData.name,
|
|
370
370
|
type: this.fieldData.type,
|
|
371
371
|
value: this.parseFieldValue(this.fieldData.value, this.fieldData.type)
|
|
372
372
|
};
|
|
@@ -412,18 +412,6 @@ module.exports = app => app.component('document-details', {
|
|
|
412
412
|
this.fieldValueEditor = null;
|
|
413
413
|
}
|
|
414
414
|
},
|
|
415
|
-
toSnakeCase(str) {
|
|
416
|
-
return str
|
|
417
|
-
.trim()
|
|
418
|
-
.replace(/\s+/g, '_') // Replace spaces with underscores
|
|
419
|
-
.replace(/[^a-zA-Z0-9_$]/g, '') // Remove invalid characters
|
|
420
|
-
.replace(/^[0-9]/, '_$&') // Prefix numbers with underscore
|
|
421
|
-
.toLowerCase();
|
|
422
|
-
},
|
|
423
|
-
getTransformedFieldName() {
|
|
424
|
-
if (!this.fieldData.name) return '';
|
|
425
|
-
return this.toSnakeCase(this.fieldData.name.trim());
|
|
426
|
-
},
|
|
427
415
|
getVirtualFieldType(virtual) {
|
|
428
416
|
const value = virtual.value;
|
|
429
417
|
if (value === null || value === undefined) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const api = require('../api');
|
|
4
4
|
const template = require('./export-query-results.html');
|
|
5
|
+
const vanillatoasts = require('vanillatoasts');
|
|
5
6
|
|
|
6
7
|
const appendCSS = require('../appendCSS');
|
|
7
8
|
|
|
@@ -30,7 +31,13 @@ module.exports = app => app.component('export-query-results', {
|
|
|
30
31
|
params.searchText = this.searchText;
|
|
31
32
|
}
|
|
32
33
|
await api.Model.exportQueryResults(params);
|
|
33
|
-
|
|
34
|
+
vanillatoasts.create({
|
|
35
|
+
title: 'Export completed!',
|
|
36
|
+
type: 'success',
|
|
37
|
+
timeout: 3000,
|
|
38
|
+
icon: 'images/success.png',
|
|
39
|
+
positionClass: 'bottomRight'
|
|
40
|
+
});
|
|
34
41
|
this.$emit('done');
|
|
35
42
|
}
|
|
36
43
|
}
|
|
@@ -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')"
|
|
@@ -213,6 +238,44 @@
|
|
|
213
238
|
</div>
|
|
214
239
|
</template>
|
|
215
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>
|
|
216
279
|
<modal v-if="shouldShowFieldModal">
|
|
217
280
|
<template v-slot:body>
|
|
218
281
|
<div class="modal-exit" @click="shouldShowFieldModal = false; selectedPaths = [...filteredPaths];">×</div>
|