@mongoosejs/studio 0.5.0 → 0.5.2

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.
@@ -117,7 +117,7 @@
117
117
  @click="stagingSelect"
118
118
  type="button"
119
119
  :class="{
120
- 'bg-gray-700 ring-inset ring-2 ring-gray-300 hover:bg-gray-200 text-content-secondary': selectMultiple,
120
+ 'bg-gray-700 ring-inset ring-2 ring-gray-300 text-white hover:bg-gray-600': selectMultiple,
121
121
  'bg-primary hover:bg-primary-hover text-primary-text': !selectMultiple
122
122
  }"
123
123
  class="rounded px-2 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
@@ -190,6 +190,13 @@
190
190
  >
191
191
  Find oldest document
192
192
  </button>
193
+ <button
194
+ @click="openDropCollectionModal"
195
+ type="button"
196
+ class="block w-full px-4 py-2 text-left text-sm text-red-700 hover:bg-red-50"
197
+ >
198
+ Drop Collection
199
+ </button>
193
200
  <button
194
201
  @click="toggleRowNumbers()"
195
202
  type="button"
@@ -404,8 +411,10 @@
404
411
  v-for="(document, docIndex) in documents"
405
412
  :key="document._id"
406
413
  @click="handleDocumentClick(document, $event)"
407
- class="border-b border-edge cursor-pointer transition-colors hover:bg-muted/60"
408
- :class="{ 'bg-primary-subtle/50 hover:bg-primary-subtle/70': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }"
414
+ class="border-b border-edge cursor-pointer transition-colors"
415
+ :class="selectedDocuments.some(x => x._id.toString() === document._id.toString())
416
+ ? 'bg-blue-200'
417
+ : 'hover:bg-muted'"
409
418
  >
410
419
  <td v-if="showRowNumbers" class="px-3 py-2 border-r border-edge align-top text-content-tertiary whitespace-nowrap">
411
420
  {{ docIndex + 1 }}
@@ -448,13 +457,13 @@
448
457
  No documents to show. Use Projection in the menu to choose columns.
449
458
  </p>
450
459
  </div>
451
- <div v-else-if="outputType === 'json'" class="flex flex-col space-y-2 p-1 mt-1">
460
+ <div v-else-if="outputType === 'json'" class="flex min-w-0 flex-col space-y-2 p-1 mt-1">
452
461
  <div
453
462
  v-for="document in documents"
454
463
  :key="document._id"
455
464
  @click="handleDocumentContainerClick(document, $event)"
456
465
  :class="[
457
- 'group relative transition-colors rounded-md border border-slate-100',
466
+ 'group relative min-w-0 max-w-full transition-colors rounded-md border border-slate-100',
458
467
  selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:shadow-sm hover:border-slate-300 bg-surface'
459
468
  ]"
460
469
  >
@@ -609,6 +618,37 @@
609
618
  </div>
610
619
  </template>
611
620
  </modal>
621
+ <modal v-if="shouldShowDropCollectionModal">
622
+ <template v-slot:body>
623
+ <div class="modal-exit" @click="shouldShowDropCollectionModal = false">&times;</div>
624
+ <h2 class="text-xl font-bold mb-3">Are you sure?</h2>
625
+ <p class="text-content-secondary mb-3">
626
+ This will permanently drop the <span class="font-semibold">{{ currentModel }}</span> collection and all its documents.
627
+ </p>
628
+ <div v-if="requiresDropCollectionNameConfirmation()" class="rounded border border-red-300 bg-red-50 px-3 py-2 mb-4">
629
+ <p class="text-sm font-semibold text-red-700">Danger zone</p>
630
+ <p class="text-sm text-red-700 mb-2">Type <span class="font-mono font-semibold">{{ currentModel }}</span> to confirm.</p>
631
+ <input
632
+ v-model="dropCollectionConfirmName"
633
+ type="text"
634
+ :placeholder="currentModel"
635
+ class="w-full rounded border border-red-300 px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-red-200"
636
+ />
637
+ </div>
638
+ <div class="flex gap-4">
639
+ <async-button
640
+ @click="dropCollection"
641
+ :disabled="requiresDropCollectionNameConfirmation() && dropCollectionConfirmName !== currentModel"
642
+ class="rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 disabled:opacity-50 disabled:cursor-not-allowed"
643
+ >
644
+ Drop Collection
645
+ </async-button>
646
+ <button @click="shouldShowDropCollectionModal = false" class="rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-page0">
647
+ Cancel
648
+ </button>
649
+ </div>
650
+ </template>
651
+ </modal>
612
652
  <model-switcher
613
653
  :show="showModelSwitcher"
614
654
  :models="models"
@@ -168,6 +168,8 @@ module.exports = app => app.component('models', {
168
168
  showAddFieldDropdown: false,
169
169
  shouldShowIndexModal: false,
170
170
  shouldShowCollectionInfoModal: false,
171
+ shouldShowDropCollectionModal: false,
172
+ dropCollectionConfirmName: '',
171
173
  shouldShowUpdateMultipleModal: false,
172
174
  shouldShowDeleteMultipleModal: false,
173
175
  shouldExport: {},
@@ -923,6 +925,30 @@ module.exports = app => app.component('models', {
923
925
  const { info } = await api.Model.getCollectionInfo({ model: this.currentModel });
924
926
  this.collectionInfo = info;
925
927
  },
928
+ openDropCollectionModal() {
929
+ this.closeActionsMenu();
930
+ this.dropCollectionConfirmName = '';
931
+ this.shouldShowDropCollectionModal = true;
932
+ },
933
+ requiresDropCollectionNameConfirmation() {
934
+ const nodeEnv = window?.state?.nodeEnv;
935
+ return nodeEnv !== 'local';
936
+ },
937
+ async dropCollection() {
938
+ if (this.requiresDropCollectionNameConfirmation() && this.dropCollectionConfirmName !== this.currentModel) {
939
+ this.$toast.error('Please type the collection name exactly to confirm.');
940
+ return;
941
+ }
942
+
943
+ await api.Model.dropCollection({ model: this.currentModel });
944
+ this.shouldShowDropCollectionModal = false;
945
+ this.dropCollectionConfirmName = '';
946
+ this.documents = [];
947
+ this.numDocuments = 0;
948
+ this.selectedDocuments = [];
949
+ this.$toast.success(`Collection ${this.currentModel} dropped.`);
950
+ await this.getDocuments();
951
+ },
926
952
  async findOldestDocument() {
927
953
  this.closeActionsMenu();
928
954
  const { docs } = await api.Model.getDocuments({
@@ -31,10 +31,18 @@
31
31
  <path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
32
32
  </svg>
33
33
  </span>
34
- <a v-if="hasAccess(roles, 'chat')"
34
+ <a v-if="hasAccess(roles, 'chat') && hasLLMAPIKey"
35
35
  href="#/chat"
36
36
  class="inline-flex items-center px-1 border-b-2 text-sm font-medium"
37
37
  :class="chatView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'">Chat</a>
38
+ <span v-else-if="hasAccess(roles, 'chat') && !hasLLMAPIKey"
39
+ class="inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed"
40
+ :title="chatDisabledReason">
41
+ Chat
42
+ <svg class="h-4 w-4 ml-1" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
43
+ <path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
44
+ </svg>
45
+ </span>
38
46
  <span v-else class="inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed">
39
47
  Chat
40
48
  <svg class="h-4 w-4 ml-1" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
@@ -154,7 +162,7 @@
154
162
  <!-- Mobile menu mask -->
155
163
  <div id="mobile-menu-mask" class="fixed inset-0 bg-black bg-opacity-40 z-40 hidden"></div>
156
164
  <!-- Mobile menu drawer -->
157
- <div id="mobile-menu" style="z-index: 10000" class="fixed inset-0 bg-page shadow-lg transform translate-x-full transition-transform duration-200 ease-in-out flex flex-col">
165
+ <div id="mobile-menu" style="z-index: 100000" class="fixed inset-0 bg-page shadow-lg transform translate-x-full transition-transform duration-200 ease-in-out flex flex-col">
158
166
  <div class="flex items-center justify-between px-4 !h-[55px] border-b border-edge">
159
167
  <router-link :to="{ name: defaultRoute }">
160
168
  <svg xmlns="http://www.w3.org/2000/svg" class="h-[32px] text-primary" viewBox="0 0 250 250" fill="currentColor" aria-label="Mongoose Studio Logo">
@@ -201,10 +209,18 @@
201
209
  <path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
202
210
  </svg>
203
211
  </span>
204
- <a v-if="hasAccess(roles, 'chat')"
212
+ <a v-if="hasAccess(roles, 'chat') && hasLLMAPIKey"
205
213
  href="#/chat"
206
214
  class="block px-3 py-2 rounded-md text-base font-medium"
207
215
  :class="chatView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'">Chat</a>
216
+ <span v-else-if="hasAccess(roles, 'chat') && !hasLLMAPIKey"
217
+ class="block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed"
218
+ :title="chatDisabledReason">
219
+ Chat
220
+ <svg class="h-4 w-4 ml-1 inline" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
221
+ <path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z" clip-rule="evenodd" />
222
+ </svg>
223
+ </span>
208
224
  <span v-else class="block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed">
209
225
  Chat
210
226
  <svg class="h-4 w-4 ml-1 inline" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
@@ -68,6 +68,12 @@ module.exports = app => app.component('navbar', {
68
68
  },
69
69
  hasTaskVisualizer() {
70
70
  return !!window.MONGOOSE_STUDIO_CONFIG.enableTaskVisualizer;
71
+ },
72
+ hasLLMAPIKey() {
73
+ return !!window.MONGOOSE_STUDIO_CONFIG.hasLLMAPIKey;
74
+ },
75
+ chatDisabledReason() {
76
+ return 'Chat requires an Anthropic, Gemini, or OpenAI API key.';
71
77
  }
72
78
  },
73
79
  methods: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "AI-Powered Data Workspace for MongoDB: Turn your MongoDB data into dashboards, maps, and interactive workflows - powered by your Mongoose models and AI.",
5
5
  "homepage": "https://mongoosestudio.app/",
6
6
  "repository": {