@mongoosejs/studio 0.1.17 → 0.1.19

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
  },
@@ -2,7 +2,6 @@
2
2
 
3
3
  const api = require('../../api');
4
4
  const marked = require('marked').marked;
5
- const vanillatoasts = require('vanillatoasts');
6
5
  const template = require('./chat-message.html');
7
6
 
8
7
  module.exports = app => app.component('chat-message', {
@@ -62,6 +61,7 @@ module.exports = app => app.component('chat-message', {
62
61
  });
63
62
  message.executionResult = chatMessage.executionResult;
64
63
  console.log(message);
64
+ this.$toast.success('Script executed successfully!');
65
65
  },
66
66
  async copyMessage() {
67
67
  const parts = this.contentSplitByScripts;
@@ -86,13 +86,7 @@ module.exports = app => app.component('chat-message', {
86
86
  }
87
87
  }
88
88
  await navigator.clipboard.writeText(output.trim());
89
- vanillatoasts.create({
90
- title: 'Message output copied!',
91
- type: 'success',
92
- timeout: 3000,
93
- icon: 'images/success.png',
94
- positionClass: 'bottomRight'
95
- });
89
+ this.$toast.success('Message output copied!');
96
90
  }
97
91
  }
98
92
  });
@@ -3,7 +3,6 @@
3
3
 
4
4
  const api = require('../../api');
5
5
  const template = require('./chat-message-script.html');
6
- const vanillatoasts = require('vanillatoasts');
7
6
 
8
7
  module.exports = app => app.component('chat-message-script', {
9
8
  template,
@@ -56,6 +55,7 @@ module.exports = app => app.component('chat-message-script', {
56
55
  this.highlightCode();
57
56
  }
58
57
  this.activeTab = 'output';
58
+ this.$toast.success('Script executed successfully!');
59
59
  return chatMessage;
60
60
  },
61
61
  openDetailModal() {
@@ -157,6 +157,7 @@ module.exports = app => app.component('chat-message-script', {
157
157
  throw err;
158
158
  });
159
159
  this.createError = null;
160
+ this.$toast.success('Dashboard created!');
160
161
  this.showCreateDashboardModal = false;
161
162
  this.$router.push('/dashboard/' + dashboard._id);
162
163
  },
@@ -183,6 +184,7 @@ module.exports = app => app.component('chat-message-script', {
183
184
  });
184
185
 
185
186
  this.overwriteError = null;
187
+ this.$toast.success('Dashboard updated!');
186
188
  this.showOverwriteDashboardConfirmationModal = false;
187
189
  this.$router.push('/dashboard/' + doc._id);
188
190
  },
@@ -203,13 +205,7 @@ module.exports = app => app.component('chat-message-script', {
203
205
  }
204
206
 
205
207
  await navigator.clipboard.writeText(parts.join('\n\n'));
206
- vanillatoasts.create({
207
- title: 'Code output copied!',
208
- type: 'success',
209
- timeout: 3000,
210
- icon: 'images/success.png',
211
- positionClass: 'bottomRight'
212
- });
208
+ this.$toast.success('Code output copied!');
213
209
  }
214
210
  },
215
211
  watch: {
@@ -2,7 +2,6 @@
2
2
 
3
3
  const api = require('../api');
4
4
  const template = require('./chat.html');
5
- const vanillatoasts = require('vanillatoasts');
6
5
 
7
6
  module.exports = app => app.component('chat', {
8
7
  template: template,
@@ -28,6 +27,7 @@ module.exports = app => app.component('chat', {
28
27
  this.chatThreads.unshift(chatThread);
29
28
  this.chatThreadId = chatThread._id;
30
29
  this.chatMessages = [];
30
+ this.$toast.success('Chat thread created!');
31
31
  }
32
32
 
33
33
  this.chatMessages.push({
@@ -121,6 +121,7 @@ module.exports = app => app.component('chat', {
121
121
  },
122
122
  async createNewThread() {
123
123
  const { chatThread } = await api.ChatThread.createChatThread();
124
+ this.$toast.success('Chat thread created!');
124
125
  this.$router.push('/chat/' + chatThread._id);
125
126
  },
126
127
  async toggleShareThread() {
@@ -136,16 +137,12 @@ module.exports = app => app.component('chat', {
136
137
  this.chatThreads.splice(idx, 1, chatThread);
137
138
  }
138
139
 
140
+ this.$toast.success('Chat thread shared!');
141
+
139
142
  // Copy current URL to clipboard and show a toast
140
143
  const url = window.location.href;
141
144
  await navigator.clipboard.writeText(url);
142
- vanillatoasts.create({
143
- title: 'Share link copied!',
144
- type: 'success',
145
- timeout: 3000,
146
- icon: 'images/success.png',
147
- positionClass: 'bottomRight'
148
- });
145
+ this.$toast.success('Share link copied!');
149
146
  } finally {
150
147
  this.sharingThread = false;
151
148
  }
@@ -29,19 +29,21 @@ module.exports = app => app.component('clone-document', {
29
29
  methods: {
30
30
  async cloneDocument() {
31
31
  const data = EJSON.serialize(eval(`(${this.editor.getValue()})`));
32
- const { doc } = await api.Model.createDocument({ model: this.currentModel, data }).catch(err => {
32
+ try {
33
+ const { doc } = await api.Model.createDocument({ model: this.currentModel, data });
34
+ this.errors.length = 0;
35
+ this.$toast.success('Document cloned!');
36
+ this.$emit('close', doc);
37
+ } catch (err) {
33
38
  if (err.response?.data?.message) {
34
39
  console.log(err.response.data);
35
40
  const message = err.response.data.message.split(': ').slice(1).join(': ');
36
41
  this.errors = message.split(',').map(error => {
37
42
  return error.split(': ').slice(1).join(': ').trim();
38
43
  });
39
- throw new Error(err.response?.data?.message);
40
44
  }
41
45
  throw err;
42
- });
43
- this.errors.length = 0;
44
- this.$emit('close', doc);
46
+ }
45
47
  }
46
48
  },
47
49
  mounted: function() {
@@ -28,6 +28,7 @@ module.exports = app => app.component('create-dashboard', {
28
28
  throw err;
29
29
  });
30
30
  this.errors.length = 0;
31
+ this.$toast.success('Dashboard created!');
31
32
  this.$emit('close', dashboard);
32
33
  }
33
34
  },
@@ -29,19 +29,21 @@ module.exports = app => app.component('create-document', {
29
29
  methods: {
30
30
  async createDocument() {
31
31
  const data = EJSON.serialize(eval(`(${this.editor.getValue()})`));
32
- const { doc } = await api.Model.createDocument({ model: this.currentModel, data }).catch(err => {
32
+ try {
33
+ const { doc } = await api.Model.createDocument({ model: this.currentModel, data });
34
+ this.errors.length = 0;
35
+ this.$toast.success('Document created!');
36
+ this.$emit('close', doc);
37
+ } catch (err) {
33
38
  if (err.response?.data?.message) {
34
39
  console.log(err.response.data);
35
40
  const message = err.response.data.message.split(': ').slice(1).join(': ');
36
41
  this.errors = message.split(',').map(error => {
37
42
  return error.split(': ').slice(1).join(': ').trim();
38
43
  });
39
- throw new Error(err.response?.data?.message);
40
44
  }
41
45
  throw err;
42
- });
43
- this.errors.length = 0;
44
- this.$emit('close', doc);
46
+ }
45
47
  }
46
48
  },
47
49
  mounted: function() {
@@ -109,6 +109,7 @@ module.exports = app => app.component('dashboard', {
109
109
  initialMessage,
110
110
  dashboardId: this.dashboard?._id
111
111
  });
112
+ this.$toast.success('Chat thread created!');
112
113
  this.$router.push('/chat/' + chatThread._id);
113
114
  } finally {
114
115
  this.startingChat = false;
@@ -31,6 +31,7 @@ module.exports = app => app.component('edit-dashboard', {
31
31
  });
32
32
  this.$emit('update', { doc });
33
33
  this.editor.setValue(doc.code);
34
+ this.$toast.success('Dashboard updated!');
34
35
  this.closeEditor();
35
36
  } catch (err) {
36
37
  this.$emit('update', { error: { message: err.message } });
@@ -21,6 +21,7 @@ module.exports = app => app.component('dashboards', {
21
21
  const removedDashboard = this.dashboards.findIndex(x => x._id.toString() === dashboard._id.toString());
22
22
  this.dashboards.splice(removedDashboard, 1);
23
23
  this.showDeleteDashboardModal = null;
24
+ this.$toast.success('Dashboard deleted!');
24
25
  },
25
26
  insertNewDashboard(dashboard) {
26
27
  this.dashboards.push(dashboard);
@@ -3,7 +3,6 @@
3
3
  const api = require('../api');
4
4
  const mpath = require('mpath');
5
5
  const template = require('./document.html');
6
- const vanillatoast = require('vanillatoasts');
7
6
 
8
7
  const appendCSS = require('../appendCSS');
9
8
 
@@ -32,20 +31,24 @@ module.exports = app => app.component('document', {
32
31
  window.pageState = this;
33
32
  // Store query parameters from the route (preserved from models page)
34
33
  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';
34
+ try {
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';
49
+ } finally {
50
+ this.status = 'loaded';
51
+ }
49
52
  },
50
53
  computed: {
51
54
  canManipulate() {
@@ -79,6 +82,7 @@ module.exports = app => app.component('document', {
79
82
  this.changes = {};
80
83
  this.editting = false;
81
84
  this.shouldShowConfirmModal = false;
85
+ this.$toast.success('Document saved!');
82
86
  },
83
87
  async remove() {
84
88
  const { doc } = await api.Model.deleteDocument({
@@ -88,12 +92,7 @@ module.exports = app => app.component('document', {
88
92
  if (doc.acknowledged) {
89
93
  this.editting = false;
90
94
  this.document = {};
91
- vanillatoast.create({
92
- title: 'Document Deleted!',
93
- type: 'success',
94
- timeout: 3000,
95
- positionClass: 'bottomRight'
96
- });
95
+ this.$toast.success('Document deleted!');
97
96
  this.$router.push({
98
97
  path: `/model/${this.model}`,
99
98
  query: this.previousQuery || {}
@@ -112,14 +111,7 @@ module.exports = app => app.component('document', {
112
111
  });
113
112
  this.document = doc;
114
113
 
115
- // Show success message
116
- vanillatoast.create({
117
- title: 'Field Added!',
118
- text: `Field "${fieldData.name}" has been added to the document`,
119
- type: 'success',
120
- timeout: 3000,
121
- positionClass: 'bottomRight'
122
- });
114
+ this.$toast.success(`Field added! Field "${fieldData.name}" has been added to the document`);
123
115
  },
124
116
  updateViewMode(mode) {
125
117
  this.viewMode = mode;
@@ -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) {
@@ -30,7 +30,7 @@ module.exports = app => app.component('export-query-results', {
30
30
  params.searchText = this.searchText;
31
31
  }
32
32
  await api.Model.exportQueryResults(params);
33
-
33
+ this.$toast.success('Export completed!');
34
34
  this.$emit('done');
35
35
  }
36
36
  }
@@ -12,12 +12,21 @@ const format = require('./format');
12
12
  const arrayUtils = require('./array-utils');
13
13
  const mothership = require('./mothership');
14
14
  const { routes } = require('./routes');
15
- const vanillatoasts = require('vanillatoasts');
15
+ const Toast = require('vue-toastification').default;
16
+ const { useToast } = require('vue-toastification');
17
+ const appendCSS = require('./appendCSS');
18
+ appendCSS(require('vue-toastification/dist/index.css'));
16
19
 
17
20
  const app = Vue.createApp({
18
21
  template: '<app-component />'
19
22
  });
20
23
 
24
+ // https://github.com/Maronato/vue-toastification/tree/main?tab=readme-ov-file#toast-types
25
+ app.use(Toast, { position: 'bottom-right', timeout: 3000 });
26
+
27
+ // Create a global toast instance for convenience (must be after app.use)
28
+ const toast = useToast();
29
+
21
30
  // Import all components
22
31
  const requireComponents = require.context(
23
32
  '.', // Relative path (current directory)
@@ -56,11 +65,8 @@ app.component('app-component', {
56
65
  </div>
57
66
  `,
58
67
  errorCaptured(err) {
59
- vanillatoasts.create({
60
- title: `Error: ${err?.response?.data?.message || err.message}`,
61
- icon: 'images/failure.jpg',
62
- timeout: 10000,
63
- positionClass: 'bottomRight'
68
+ this.$toast.error(`Error: ${err?.response?.data?.message || err.message}`, {
69
+ timeout: 10000
64
70
  });
65
71
  },
66
72
  computed: {
@@ -150,7 +156,7 @@ router.beforeEach((to, from, next) => {
150
156
  }
151
157
  });
152
158
 
153
- app.config.globalProperties = { format, arrayUtils };
159
+ app.config.globalProperties = { format, arrayUtils, $toast: toast };
154
160
  app.use(router);
155
161
 
156
162
  app.mount('#content');
@@ -2,7 +2,6 @@
2
2
 
3
3
  const template = require('./list-default.html');
4
4
  const appendCSS = require('../appendCSS');
5
- const vanillatoast = require('vanillatoasts');
6
5
 
7
6
  appendCSS(require('./list-default.css'));
8
7
 
@@ -19,13 +18,7 @@ module.exports = app => app.component('list-default', {
19
18
  storage.setSelectionRange(0, 99999);
20
19
  document.execCommand('copy');
21
20
  elem.removeChild(storage);
22
- vanillatoast.create({
23
- title: 'Text copied!',
24
- type: 'success',
25
- timeout: 3000,
26
- icon: 'images/success.png',
27
- positionClass: 'bottomRight'
28
- });
21
+ this.$toast.success('Text copied!');
29
22
  },
30
23
  goToDoc(id) {
31
24
  this.$router.push({ path: `/model/${this.allude}/document/${id}` });
@@ -3,7 +3,6 @@
3
3
  const api = require('../api');
4
4
  const template = require('./list-mixed.html');
5
5
 
6
- const vanillatoast = require('vanillatoasts');
7
6
 
8
7
  require('../appendCSS')(require('./list-mixed.css'));
9
8
 
@@ -25,13 +24,7 @@ module.exports = app => app.component('list-mixed', {
25
24
  storage.setSelectionRange(0, 99999);
26
25
  document.execCommand('copy');
27
26
  elem.removeChild(storage);
28
- vanillatoast.create({
29
- title: 'Text copied!',
30
- type: 'success',
31
- timeout: 3000,
32
- icon: 'images/success.png',
33
- positionClass: 'bottomRight'
34
- });
27
+ this.$toast.success('Text copied!');
35
28
  }
36
29
  },
37
30
  mounted: function() {
@@ -2,7 +2,6 @@
2
2
 
3
3
  const template = require('./list-string.html');
4
4
  const appendCSS = require('../appendCSS');
5
- const vanillatoast = require('vanillatoasts');
6
5
  appendCSS(require('./list-string.css'));
7
6
 
8
7
  module.exports = app => app.component('list-string', {
@@ -18,13 +17,7 @@ module.exports = app => app.component('list-string', {
18
17
  storage.setSelectionRange(0, 99999);
19
18
  document.execCommand('copy');
20
19
  elem.removeChild(storage);
21
- vanillatoast.create({
22
- title: 'Text copied!',
23
- type: 'success',
24
- timeout: 3000,
25
- icon: 'images/success.png',
26
- positionClass: 'bottomRight'
27
- });
20
+ this.$toast.success('Text copied!');
28
21
  }
29
22
  },
30
23
  computed: {
@@ -2,7 +2,6 @@
2
2
 
3
3
  const api = require('../api');
4
4
  const template = require('./list-subdocument.html');
5
- const vanillatoast = require('vanillatoasts');
6
5
 
7
6
  require('../appendCSS')(require('./list-subdocument.css'));
8
7
 
@@ -24,13 +23,7 @@ module.exports = app => app.component('list-subdocument', {
24
23
  storage.setSelectionRange(0, 99999);
25
24
  document.execCommand('copy');
26
25
  elem.removeChild(storage);
27
- vanillatoast.create({
28
- title: 'Text copied!',
29
- type: 'success',
30
- timeout: 3000,
31
- icon: 'images/success.png',
32
- positionClass: 'bottomRight'
33
- });
26
+ this.$toast.success('Text copied!');
34
27
  }
35
28
  },
36
29
  mounted: function() {
@@ -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,45 @@
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
+ <button
126
+ @click="findOldestDocument"
127
+ type="button"
128
+ class="block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100"
129
+ >
130
+ Find oldest document
131
+ </button>
132
+ </div>
133
+ </div>
134
+ </div>
103
135
  <span class="isolate inline-flex rounded-md shadow-sm">
104
136
  <button
105
137
  @click="setOutputType('table')"
@@ -213,6 +245,44 @@
213
245
  </div>
214
246
  </template>
215
247
  </modal>
248
+ <modal v-if="shouldShowCollectionInfoModal">
249
+ <template v-slot:body>
250
+ <div class="modal-exit" @click="shouldShowCollectionInfoModal = false">&times;</div>
251
+ <div class="text-xl font-bold mb-2">Collection Info</div>
252
+ <div v-if="!collectionInfo" class="text-gray-600">Loading collection details...</div>
253
+ <div v-else class="space-y-3">
254
+ <div class="flex justify-between gap-4">
255
+ <div class="font-semibold text-gray-700">Documents</div>
256
+ <div class="text-gray-900">{{ formatNumber(collectionInfo.documentCount) }}</div>
257
+ </div>
258
+ <div class="flex justify-between gap-4">
259
+ <div class="font-semibold text-gray-700">Indexes</div>
260
+ <div class="text-gray-900">{{ formatNumber(collectionInfo.indexCount) }}</div>
261
+ </div>
262
+ <div class="flex justify-between gap-4">
263
+ <div class="font-semibold text-gray-700">Total Index Size</div>
264
+ <div class="text-gray-900">{{ formatCollectionSize(collectionInfo.totalIndexSize) }}</div>
265
+ </div>
266
+ <div class="flex justify-between gap-4">
267
+ <div class="font-semibold text-gray-700">Total Storage Size</div>
268
+ <div class="text-gray-900">{{ formatCollectionSize(collectionInfo.size) }}</div>
269
+ </div>
270
+ <div class="flex flex-col gap-1">
271
+ <div class="flex justify-between gap-4">
272
+ <div class="font-semibold text-gray-700">Collation</div>
273
+ <div class="text-gray-900">{{ collectionInfo.hasCollation ? 'Yes' : 'No' }}</div>
274
+ </div>
275
+ <div v-if="collectionInfo.hasCollation" class="rounded bg-gray-100 p-3 text-sm text-gray-800 overflow-x-auto">
276
+ <pre class="whitespace-pre-wrap">{{ JSON.stringify(collectionInfo.collation, null, 2) }}</pre>
277
+ </div>
278
+ </div>
279
+ <div class="flex justify-between gap-4">
280
+ <div class="font-semibold text-gray-700">Capped</div>
281
+ <div class="text-gray-900">{{ collectionInfo.capped ? 'Yes' : 'No' }}</div>
282
+ </div>
283
+ </div>
284
+ </template>
285
+ </modal>
216
286
  <modal v-if="shouldShowFieldModal">
217
287
  <template v-slot:body>
218
288
  <div class="modal-exit" @click="shouldShowFieldModal = false; selectedPaths = [...filteredPaths];">&times;</div>