@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.
@@ -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)));
@@ -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.response.data === 'string') {
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
- const { doc } = await api.Model.createDocument({ model: this.currentModel, data }).catch(err => {
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
- const { doc } = await api.Model.createDocument({ model: this.currentModel, data }).catch(err => {
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 vanillatoast = require('vanillatoasts');
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
- const { doc, schemaPaths, virtualPaths } = await api.Model.getDocument({ model: this.model, documentId: this.documentId });
36
- window.doc = doc;
37
- this.document = doc;
38
- this.schemaPaths = Object.keys(schemaPaths).sort((k1, k2) => {
39
- if (k1 === '_id' && k2 !== '_id') {
40
- return -1;
41
- }
42
- if (k1 !== '_id' && k2 === '_id') {
43
- return 1;
44
- }
45
- return 0;
46
- }).map(key => schemaPaths[key]);
47
- this.virtualPaths = virtualPaths || [];
48
- this.status = 'loaded';
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
- vanillatoast.create({
92
- title: 'Document Deleted!',
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
- // Show success message
116
- vanillatoast.create({
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.getTransformedFieldName(),
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">&times;</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];">&times;</div>