@mongoosejs/studio 0.2.12 → 0.3.0

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.
Files changed (92) hide show
  1. package/backend/actions/ChatMessage/executeScript.js +5 -1
  2. package/backend/actions/ChatThread/createChatMessage.js +2 -1
  3. package/backend/actions/ChatThread/streamChatMessage.js +2 -2
  4. package/backend/actions/Model/getEstimatedDocumentCounts.js +38 -0
  5. package/backend/actions/Model/index.js +1 -0
  6. package/backend/actions/Model/streamDocumentChanges.js +8 -7
  7. package/backend/actions/Task/getTasks.js +9 -6
  8. package/backend/authorize.js +1 -0
  9. package/backend/index.js +11 -3
  10. package/eslint.config.js +5 -1
  11. package/express.js +1 -0
  12. package/frontend/public/app.js +25235 -662
  13. package/frontend/public/dark-theme.css +365 -0
  14. package/frontend/public/images/mongoose-studio.svg +4 -0
  15. package/frontend/public/index.html +21 -1
  16. package/frontend/public/style.css +5 -7
  17. package/frontend/public/theme-variables.css +294 -0
  18. package/frontend/public/tw.css +461 -239
  19. package/frontend/src/ace-editor/ace-editor.html +4 -0
  20. package/frontend/src/ace-editor/ace-editor.js +89 -0
  21. package/frontend/src/aceEditor.js +69 -0
  22. package/frontend/src/api.js +6 -0
  23. package/frontend/src/chat/chat-message/chat-message.html +1 -1
  24. package/frontend/src/chat/chat-message/chat-message.js +1 -1
  25. package/frontend/src/chat/chat-message-script/chat-message-script.html +51 -34
  26. package/frontend/src/chat/chat-message-script/chat-message-script.js +12 -55
  27. package/frontend/src/chat/chat.html +72 -37
  28. package/frontend/src/chat/chat.js +26 -2
  29. package/frontend/src/clone-document/clone-document.html +7 -2
  30. package/frontend/src/clone-document/clone-document.js +1 -8
  31. package/frontend/src/create-dashboard/create-dashboard.html +11 -6
  32. package/frontend/src/create-dashboard/create-dashboard.js +0 -7
  33. package/frontend/src/create-document/create-document.html +15 -9
  34. package/frontend/src/create-document/create-document.js +5 -12
  35. package/frontend/src/dashboard/dashboard.html +14 -12
  36. package/frontend/src/dashboard/dashboard.js +12 -4
  37. package/frontend/src/dashboard/edit-dashboard/edit-dashboard.html +13 -7
  38. package/frontend/src/dashboard/edit-dashboard/edit-dashboard.js +13 -21
  39. package/frontend/src/dashboard-result/dashboard-chart/dashboard-chart.html +19 -17
  40. package/frontend/src/dashboard-result/dashboard-chart/dashboard-chart.js +97 -2
  41. package/frontend/src/dashboard-result/dashboard-map/dashboard-map.js +27 -3
  42. package/frontend/src/dashboard-result/dashboard-result.html +3 -3
  43. package/frontend/src/dashboards/dashboards.html +101 -109
  44. package/frontend/src/dashboards/dashboards.js +25 -1
  45. package/frontend/src/detail-default/detail-default.html +2 -2
  46. package/frontend/src/detail-default/detail-default.js +24 -3
  47. package/frontend/src/document/confirm-changes/confirm-changes.html +1 -1
  48. package/frontend/src/document/confirm-delete/confirm-delete.html +1 -1
  49. package/frontend/src/document/document.css +1 -1
  50. package/frontend/src/document/document.html +53 -27
  51. package/frontend/src/document/document.js +27 -1
  52. package/frontend/src/document/execute-script/execute-script.html +20 -21
  53. package/frontend/src/document/execute-script/execute-script.js +1 -43
  54. package/frontend/src/document-details/document-details.css +4 -9
  55. package/frontend/src/document-details/document-details.html +34 -33
  56. package/frontend/src/document-details/document-details.js +2 -53
  57. package/frontend/src/document-details/document-property/document-property.html +12 -12
  58. package/frontend/src/edit-array/edit-array.html +7 -6
  59. package/frontend/src/edit-array/edit-array.js +10 -50
  60. package/frontend/src/edit-boolean/edit-boolean.html +12 -12
  61. package/frontend/src/edit-date/edit-date.html +2 -2
  62. package/frontend/src/edit-default/edit-default.html +1 -1
  63. package/frontend/src/edit-string/edit-string.html +3 -3
  64. package/frontend/src/edit-subdocument/edit-subdocument.html +5 -3
  65. package/frontend/src/edit-subdocument/edit-subdocument.js +1 -15
  66. package/frontend/src/export-query-results/export-query-results.html +3 -3
  67. package/frontend/src/json-node/json-node.html +3 -3
  68. package/frontend/src/list-json/json-node.html +1 -1
  69. package/frontend/src/models/document-search/document-search.html +3 -3
  70. package/frontend/src/models/model-switcher/model-switcher.html +53 -0
  71. package/frontend/src/models/model-switcher/model-switcher.js +123 -0
  72. package/frontend/src/models/models.css +3 -10
  73. package/frontend/src/models/models.html +146 -74
  74. package/frontend/src/models/models.js +142 -4
  75. package/frontend/src/navbar/navbar.html +157 -97
  76. package/frontend/src/navbar/navbar.js +32 -13
  77. package/frontend/src/routes.js +20 -4
  78. package/frontend/src/splash/splash.html +5 -5
  79. package/frontend/src/task-by-name/task-by-name.html +15 -0
  80. package/frontend/src/task-by-name/task-by-name.js +78 -0
  81. package/frontend/src/task-single/task-single.html +157 -0
  82. package/frontend/src/task-single/task-single.js +116 -0
  83. package/frontend/src/tasks/task-details/task-details.html +124 -73
  84. package/frontend/src/tasks/task-details/task-details.js +166 -10
  85. package/frontend/src/tasks/tasks.html +37 -48
  86. package/frontend/src/tasks/tasks.js +11 -50
  87. package/frontend/src/team/new-invitation/new-invitation.html +8 -8
  88. package/frontend/src/team/team.html +27 -27
  89. package/frontend/src/update-document/update-document.html +7 -2
  90. package/frontend/src/update-document/update-document.js +2 -11
  91. package/package.json +3 -1
  92. package/tailwind.config.js +75 -11
@@ -1,53 +1,41 @@
1
1
  <div class="p-4 space-y-6">
2
- <!-- Task Details View -->
3
- <task-details
4
- v-if="showTaskDetails && selectedTaskGroup"
5
- :task-group="selectedTaskGroup"
6
- :current-filter="taskDetailsFilter"
7
- @back="hideTaskDetails"
8
- @task-created="onTaskCreated"
9
- @task-cancelled="onTaskCancelled"
10
- @update:current-filter="taskDetailsFilter = $event"
11
- ></task-details>
12
-
13
- <!-- Main Tasks View -->
14
- <div v-else>
15
- <h1 class="text-2xl font-bold text-gray-700 mb-4">Task Overview</h1>
2
+ <div>
3
+ <h1 class="text-2xl font-bold text-content-secondary mb-4">Task Overview</h1>
16
4
  <div v-if="status == 'init'">
17
5
  <img src="images/loader.gif" />
18
6
  </div>
19
7
  <!-- Task List -->
20
- <div class="bg-white p-4 rounded-lg shadow" v-if="status == 'loaded'">
8
+ <div class="bg-surface p-4 rounded-lg shadow" v-if="status == 'loaded'">
21
9
  <div class="mb-4">
22
- <label class="block text-sm font-medium text-gray-700 mb-1">Filter by Date:</label>
23
- <select v-model="selectedRange" @change="updateDateRange" class="border-gray-300 rounded-md shadow-sm w-full p-2">
10
+ <label class="block text-sm font-medium text-content-secondary mb-1">Filter by Date:</label>
11
+ <select v-model="selectedRange" @change="updateDateRange" class="border-edge-strong rounded-md shadow-sm w-full p-2">
24
12
  <option v-for="option in dateFilters" :key="option.value" :value="option.value">
25
13
  {{ option.label }}
26
14
  </option>
27
15
  </select>
28
16
  </div>
29
17
  <div class="mb-4">
30
- <label class="block text-sm font-medium text-gray-700 mb-1">Filter by Status:</label>
31
- <select v-model="selectedStatus" @change="getTasks" class="border-gray-300 rounded-md shadow-sm w-full p-2">
18
+ <label class="block text-sm font-medium text-content-secondary mb-1">Filter by Status:</label>
19
+ <select v-model="selectedStatus" @change="getTasks" class="border-edge-strong rounded-md shadow-sm w-full p-2">
32
20
  <option v-for="option in statusFilters" :key="option.value" :value="option.value">
33
21
  {{ option.label }}
34
22
  </option>
35
23
  </select>
36
24
  </div>
37
25
  <div class="mb-4">
38
- <label class="block text-sm font-medium text-gray-700 mb-1">Search by Task Name:</label>
26
+ <label class="block text-sm font-medium text-content-secondary mb-1">Search by Task Name:</label>
39
27
  <input
40
28
  v-model="searchQuery"
41
29
  type="text"
42
30
  @input="onSearchInput"
43
- class="border-gray-300 rounded-md shadow-sm w-full p-2"
31
+ class="border-edge-strong rounded-md shadow-sm w-full p-2"
44
32
  placeholder="Enter task name to search..."
45
33
  >
46
34
  </div>
47
35
  <div class="mb-4">
48
36
  <button
49
37
  @click="resetFilters"
50
- class="w-full bg-gray-200 text-gray-700 hover:bg-gray-300 font-medium py-2 px-4 rounded-md transition"
38
+ class="w-full bg-gray-200 text-content-secondary hover:bg-gray-300 font-medium py-2 px-4 rounded-md transition"
51
39
  >
52
40
  Reset Filters
53
41
  </button>
@@ -55,7 +43,7 @@
55
43
  <div class="mb-6">
56
44
  <button
57
45
  @click="openCreateTaskModal"
58
- class="w-full bg-ultramarine-600 text-white hover:bg-ultramarine-700 font-medium py-2 px-4 rounded-md transition"
46
+ class="w-full bg-primary text-primary-text hover:bg-primary-hover font-medium py-2 px-4 rounded-md transition"
59
47
  >
60
48
  Create New Task
61
49
  </button>
@@ -85,7 +73,7 @@
85
73
  </button>
86
74
  <button
87
75
  @click="setStatusFilter('cancelled')"
88
- :class="getStatusColor('cancelled') + ' p-4 rounded-lg shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200 cursor-pointer border-2 border-gray-200 hover:border-gray-300'"
76
+ :class="getStatusColor('cancelled') + ' p-4 rounded-lg shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200 cursor-pointer border-2 border-edge hover:border-edge-strong'"
89
77
  >
90
78
  <div class="text-sm">Cancelled</div>
91
79
  <div class="text-2xl font-bold">{{cancelledCount}}</div>
@@ -94,23 +82,23 @@
94
82
 
95
83
  <!-- Grouped Task List -->
96
84
  <div class="mt-6">
97
- <h2 class="text-lg font-semibold text-gray-700 mb-4">Tasks by Name</h2>
85
+ <h2 class="text-lg font-semibold text-content-secondary mb-4">Tasks by Name</h2>
98
86
  <ul class="divide-y divide-gray-200">
99
87
  <li v-for="group in tasksByName" :key="group.name" class="p-4 group hover:border hover:rounded-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200">
100
88
  <div class="flex items-center justify-between mb-3 ">
101
89
  <div class="flex-1 cursor-pointer" @click="openTaskGroupDetails(group)">
102
90
  <div class="flex items-center gap-2">
103
- <div class="font-medium text-lg group-hover:text-ultramarine-600 transition-colors">{{ group.name }}</div>
104
- <svg class="w-4 h-4 text-gray-400 group-hover:text-ultramarine-600 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
91
+ <div class="font-medium text-lg group-hover:text-primary transition-colors">{{ group.name }}</div>
92
+ <svg class="w-4 h-4 text-gray-400 group-hover:text-primary transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
105
93
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
106
94
  </svg>
107
95
  </div>
108
- <div class="text-sm text-gray-500 group-hover:text-gray-700 transition-colors">Total: {{ group.totalCount }} tasks</div>
109
- <div class="text-xs text-ultramarine-600 opacity-0 group-hover:opacity-100 transition-opacity mt-1">
96
+ <div class="text-sm text-content-tertiary group-hover:text-content-secondary transition-colors">Total: {{ group.totalCount }} tasks</div>
97
+ <div class="text-xs text-primary opacity-0 group-hover:opacity-100 transition-opacity mt-1">
110
98
  Click to view details
111
99
  </div>
112
100
  </div>
113
- <div class="text-sm text-gray-500">
101
+ <div class="text-sm text-content-tertiary">
114
102
  Last run: {{ group.lastRun ? new Date(group.lastRun).toLocaleString() : 'Never' }}
115
103
  </div>
116
104
  </div>
@@ -140,10 +128,10 @@
140
128
  </button>
141
129
  <button
142
130
  @click.stop="openTaskGroupDetailsWithFilter(group, 'cancelled')"
143
- class="bg-gray-50 border border-gray-200 rounded-md p-2 text-center shadow-sm hover:shadow-md transform hover:-translate-y-1 transition-all duration-200 cursor-pointer hover:border-gray-300"
131
+ class="bg-page border border-edge rounded-md p-2 text-center shadow-sm hover:shadow-md transform hover:-translate-y-1 transition-all duration-200 cursor-pointer hover:border-edge-strong"
144
132
  >
145
133
  <div class="text-xs text-gray-600 font-medium">Cancelled</div>
146
- <div class="text-lg font-bold text-gray-700">{{ group.statusCounts.cancelled || 0 }}</div>
134
+ <div class="text-lg font-bold text-content-secondary">{{ group.statusCounts.cancelled || 0 }}</div>
147
135
  </button>
148
136
  </div>
149
137
  </li>
@@ -157,59 +145,60 @@
157
145
  <template #body>
158
146
  <div class="absolute font-mono right-1 top-1 cursor-pointer text-xl" @click="closeCreateTaskModal" role="button" aria-label="Close modal">&times;</div>
159
147
  <div class="space-y-4">
160
- <h3 class="text-lg font-semibold text-gray-700 mb-4">Create New Task</h3>
148
+ <h3 class="text-lg font-semibold text-content-secondary mb-4">Create New Task</h3>
161
149
 
162
150
  <div>
163
- <label class="block text-sm font-medium text-gray-700 mb-1">Task Name:</label>
151
+ <label class="block text-sm font-medium text-content-secondary mb-1">Task Name:</label>
164
152
  <input
165
153
  v-model="newTask.name"
166
154
  type="text"
167
- class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-ultramarine-500"
155
+ class="w-full border border-edge-strong rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary"
168
156
  placeholder="Enter task name"
169
157
  >
170
158
  </div>
171
159
 
172
160
  <div>
173
- <label class="block text-sm font-medium text-gray-700 mb-1">Scheduled Time:</label>
161
+ <label class="block text-sm font-medium text-content-secondary mb-1">Scheduled Time:</label>
174
162
  <input
175
163
  v-model="newTask.scheduledAt"
176
164
  type="datetime-local"
177
- class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-ultramarine-500"
165
+ class="w-full border border-edge-strong rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary"
178
166
  >
179
167
  </div>
180
168
 
181
169
  <div>
182
- <label class="block text-sm font-medium text-gray-700 mb-1">Parameters (JSON):</label>
183
- <textarea
184
- ref="parametersEditor"
185
- class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-ultramarine-500"
186
- placeholder='{"key": "value"}'
187
- ></textarea>
170
+ <label class="block text-sm font-medium text-content-secondary mb-1">Parameters (JSON):</label>
171
+ <ace-editor
172
+ v-model="newTask.parameters"
173
+ mode="json"
174
+ :line-numbers="true"
175
+ class="min-h-[120px]"
176
+ />
188
177
  </div>
189
178
 
190
179
  <div>
191
- <label class="block text-sm font-medium text-gray-700 mb-1">Repeat Interval (ms):</label>
180
+ <label class="block text-sm font-medium text-content-secondary mb-1">Repeat Interval (ms):</label>
192
181
  <input
193
182
  v-model="newTask.repeatInterval"
194
183
  type="number"
195
184
  min="0"
196
185
  step="1000"
197
- class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-ultramarine-500"
186
+ class="w-full border border-edge-strong rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary"
198
187
  placeholder="0 for no repetition"
199
188
  >
200
- <p class="text-xs text-gray-500 mt-1">Enter 0 or leave empty for no repetition. Use 1000 for 1 second, 60000 for 1 minute, etc.</p>
189
+ <p class="text-xs text-content-tertiary mt-1">Enter 0 or leave empty for no repetition. Use 1000 for 1 second, 60000 for 1 minute, etc.</p>
201
190
  </div>
202
191
 
203
192
  <div class="flex gap-2 pt-4">
204
193
  <button
205
194
  @click="createTask"
206
- class="flex-1 bg-ultramarine-600 text-white px-4 py-2 rounded-md hover:bg-ultramarine-700"
195
+ class="flex-1 bg-primary text-primary-text px-4 py-2 rounded-md hover:bg-primary-hover"
207
196
  >
208
197
  Create Task
209
198
  </button>
210
199
  <button
211
200
  @click="closeCreateTaskModal"
212
- class="flex-1 bg-gray-300 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-400"
201
+ class="flex-1 bg-gray-300 text-content-secondary px-4 py-2 rounded-md hover:bg-gray-400"
213
202
  >
214
203
  Cancel
215
204
  </button>
@@ -8,11 +8,12 @@ module.exports = app => app.component('tasks', {
8
8
  status: 'init',
9
9
  tasks: [],
10
10
  groupedTasks: {},
11
- selectedRange: 'today',
11
+ selectedRange: 'last_hour',
12
12
  start: null,
13
13
  end: null,
14
14
  dateFilters: [
15
15
  { value: 'all', label: 'All Time' },
16
+ { value: 'last_hour', label: 'Last Hour' },
16
17
  { value: 'today', label: 'Today' },
17
18
  { value: 'yesterday', label: 'Yesterday' },
18
19
  { value: 'thisWeek', label: 'This Week' },
@@ -31,10 +32,6 @@ module.exports = app => app.component('tasks', {
31
32
  ],
32
33
  searchQuery: '',
33
34
  searchTimeout: null,
34
- // Task details view state
35
- showTaskDetails: false,
36
- selectedTaskGroup: null,
37
- taskDetailsFilter: null,
38
35
  // Create task modal state
39
36
  showCreateTaskModal: false,
40
37
  newTask: {
@@ -42,8 +39,7 @@ module.exports = app => app.component('tasks', {
42
39
  scheduledAt: '',
43
40
  parameters: '',
44
41
  repeatInterval: ''
45
- },
46
- parametersEditor: null
42
+ }
47
43
  }),
48
44
  methods: {
49
45
  async getTasks() {
@@ -70,28 +66,10 @@ module.exports = app => app.component('tasks', {
70
66
  this.groupedTasks = groupedTasks;
71
67
  },
72
68
  openTaskGroupDetails(group) {
73
- this.selectedTaskGroup = group;
74
- this.showTaskDetails = true;
69
+ this.$router.push({ path: `/tasks/${encodeURIComponent(group.name || '')}` });
75
70
  },
76
71
  openTaskGroupDetailsWithFilter(group, status) {
77
- // Create a filtered version of the task group with only the specified status
78
- const filteredGroup = {
79
- ...group,
80
- tasks: group.tasks.filter(task => task.status === status),
81
- filteredStatus: status
82
- };
83
- this.selectedTaskGroup = filteredGroup;
84
- this.taskDetailsFilter = status;
85
- this.showTaskDetails = true;
86
- },
87
- async onTaskCancelled() {
88
- // Refresh the task data when a task is cancelled
89
- await this.getTasks();
90
- },
91
- hideTaskDetails() {
92
- this.showTaskDetails = false;
93
- this.selectedTaskGroup = null;
94
- this.taskDetailsFilter = null;
72
+ this.$router.push({ path: `/tasks/${encodeURIComponent(group.name || '')}`, query: status ? { status } : {} });
95
73
  },
96
74
  async onTaskCreated() {
97
75
  // Refresh the task data when a new task is created
@@ -104,7 +82,7 @@ module.exports = app => app.component('tasks', {
104
82
  async createTask() {
105
83
  try {
106
84
  let parameters = {};
107
- const parametersText = this.parametersEditor ? this.parametersEditor.getValue() : '';
85
+ const parametersText = this.newTask.parameters || '';
108
86
  if (parametersText.trim()) {
109
87
  try {
110
88
  parameters = JSON.parse(parametersText);
@@ -181,9 +159,6 @@ module.exports = app => app.component('tasks', {
181
159
  parameters: '',
182
160
  repeatInterval: ''
183
161
  };
184
- if (this.parametersEditor) {
185
- this.parametersEditor.setValue('');
186
- }
187
162
  },
188
163
  setDefaultCreateTaskValues() {
189
164
  // Set default scheduled time to 1 hour from now
@@ -196,21 +171,8 @@ module.exports = app => app.component('tasks', {
196
171
  this.resetCreateTaskForm();
197
172
  this.setDefaultCreateTaskValues();
198
173
  },
199
- initializeParametersEditor() {
200
- if (this.$refs.parametersEditor && !this.parametersEditor) {
201
- this.parametersEditor = CodeMirror.fromTextArea(this.$refs.parametersEditor, {
202
- mode: 'javascript',
203
- lineNumbers: true,
204
- smartIndent: false,
205
- theme: 'default'
206
- });
207
- }
208
- },
209
174
  openCreateTaskModal() {
210
175
  this.showCreateTaskModal = true;
211
- this.$nextTick(() => {
212
- this.initializeParametersEditor();
213
- });
214
176
  },
215
177
  getStatusColor(status) {
216
178
  if (status === 'succeeded') {
@@ -255,6 +217,11 @@ module.exports = app => app.component('tasks', {
255
217
  let start, end;
256
218
 
257
219
  switch (this.selectedRange) {
220
+ case 'last_hour':
221
+ start = new Date();
222
+ start.setHours(start.getHours() - 1);
223
+ end = new Date();
224
+ break;
258
225
  case 'today':
259
226
  start = new Date();
260
227
  start.setHours(0, 0, 0, 0);
@@ -362,11 +329,5 @@ module.exports = app => app.component('tasks', {
362
329
  this.status = 'loaded';
363
330
  this.setDefaultCreateTaskValues();
364
331
  },
365
- beforeDestroy() {
366
- if (this.parametersEditor) {
367
- this.parametersEditor.toTextArea();
368
- }
369
- },
370
-
371
332
  template: template
372
333
  });
@@ -5,33 +5,33 @@
5
5
  </div>
6
6
 
7
7
  <div>
8
- <label for="githubUsername" class="block text-sm/6 font-medium text-gray-900">GitHub Username</label>
8
+ <label for="githubUsername" class="block text-sm/6 font-medium text-content">GitHub Username</label>
9
9
  <div class="mt-2">
10
- <input type="githubUsername" name="githubUsername" id="githubUsername" v-model="githubUsername" class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-ultramarine-600 sm:text-sm/6" placeholder="johnsmith12">
10
+ <input type="githubUsername" name="githubUsername" id="githubUsername" v-model="githubUsername" class="block w-full rounded-md bg-surface px-3 py-1.5 text-base text-content outline outline-1 -outline-offset-1 outline-edge-strong placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-primary sm:text-sm/6" placeholder="johnsmith12">
11
11
  </div>
12
12
  </div>
13
13
 
14
14
  <div>
15
- <label for="email" class="block text-sm/6 font-medium text-gray-900">Email (Optional)</label>
15
+ <label for="email" class="block text-sm/6 font-medium text-content">Email (Optional)</label>
16
16
  <div class="mt-2">
17
- <input type="email" name="email" id="email" v-model="email" class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-ultramarine-600 sm:text-sm/6" placeholder="you@example.com">
17
+ <input type="email" name="email" id="email" v-model="email" class="block w-full rounded-md bg-surface px-3 py-1.5 text-base text-content outline outline-1 -outline-offset-1 outline-edge-strong placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-primary sm:text-sm/6" placeholder="you@example.com">
18
18
  </div>
19
19
  </div>
20
20
 
21
21
  <div>
22
- <label for="location" class="block text-sm/6 font-medium text-gray-900">Role</label>
22
+ <label for="location" class="block text-sm/6 font-medium text-content">Role</label>
23
23
  <div class="mt-2 grid grid-cols-1">
24
- <select id="role" :disabled="tier == null" name="role" v-model="role" class="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pl-3 pr-8 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
24
+ <select id="role" :disabled="tier == null" name="role" v-model="role" class="col-start-1 row-start-1 w-full appearance-none rounded-md bg-surface py-1.5 pl-3 pr-8 text-base text-content outline outline-1 -outline-offset-1 outline-edge-strong focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
25
25
  <option value="admin">Admin</option>
26
26
  <option value="member">Member</option>
27
27
  <option value="readonly">Read-only</option>
28
28
  <option value="dashboards">Dashboards Only</option>
29
29
  </select>
30
- <svg class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-gray-500 sm:size-4" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon">
30
+ <svg class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-content-tertiary sm:size-4" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon">
31
31
  <path fill-rule="evenodd" d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
32
32
  </svg>
33
33
  </div>
34
- <div v-if="tier == null" class="text-sm text-gray-700">
34
+ <div v-if="tier == null" class="text-sm text-content-secondary">
35
35
  You can only invite "Dashboards Only" users until you set up a subscription.
36
36
  </div>
37
37
  </div>
@@ -14,7 +14,7 @@
14
14
  <async-button
15
15
  type="submit"
16
16
  @click="getWorkspaceCustomerPortalLink"
17
- class="inline-flex items-center justify-center rounded-md border border-transparent bg-ultramarine-600 py-1 px-2 text-sm font-medium text-white shadow-sm hover:bg-ultramarine-500 focus:outline-none focus:ring-2 focus:ring-forest-green-500 focus:ring-offset-2">
17
+ class="inline-flex items-center justify-center rounded-md border border-transparent bg-primary py-1 px-2 text-sm font-medium text-primary-text shadow-sm hover:bg-primary-hover focus:outline-none focus:ring-2 focus:ring-forest-green-500 focus:ring-offset-2">
18
18
  View in Stripe
19
19
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 ml-1">
20
20
  <path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
@@ -25,7 +25,7 @@
25
25
  <div v-else-if="workspace && !workspace.subscriptionTier" class="mt-4 flex justify-between items-center">
26
26
  <div>
27
27
  <span class="font-bold">No active subscription</span>
28
- <div class="text-sm text-gray-700">
28
+ <div class="text-sm text-content-secondary">
29
29
  You won't be able to invite your team until you activate a subscription
30
30
  </div>
31
31
  </div>
@@ -33,7 +33,7 @@
33
33
  <a
34
34
  :href="paymentLink"
35
35
  target="_blank"
36
- class="inline-flex items-center justify-center rounded-md border border-transparent bg-ultramarine-600 py-1 px-2 text-sm font-medium text-white shadow-sm hover:bg-ultramarine-500 focus:outline-none focus:ring-2 focus:ring-ultramarine-500 focus:ring-offset-2">
36
+ class="inline-flex items-center justify-center rounded-md border border-transparent bg-primary py-1 px-2 text-sm font-medium text-primary-text shadow-sm hover:bg-primary-hover focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2">
37
37
  Subscribe With Stripe
38
38
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 ml-1">
39
39
  <path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
@@ -52,21 +52,21 @@
52
52
  <ul v-else role="list" class="divide-y divide-gray-100">
53
53
  <li class="flex justify-between gap-x-6 py-5" v-for="user in users">
54
54
  <div class="flex min-w-0 gap-x-4">
55
- <img class="size-12 flex-none rounded-full bg-gray-50" :src="user.picture ?? 'images/logo.svg'" alt="">
55
+ <img class="size-12 flex-none rounded-full bg-page" :src="user.picture ?? 'images/logo.svg'" alt="">
56
56
  <div class="min-w-0 flex-auto">
57
- <p class="text-sm/6 font-semibold text-gray-900">
57
+ <p class="text-sm/6 font-semibold text-content">
58
58
  {{user.name || user.githubUsername}}
59
59
  <span v-if="user.isFreeUser" class="ml-1 inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20">Free</span>
60
60
  </p>
61
- <p class="mt-1 truncate text-xs/5 text-gray-500">{{user.email ?? 'No Email'}}</p>
61
+ <p class="mt-1 truncate text-xs/5 text-content-tertiary">{{user.email ?? 'No Email'}}</p>
62
62
  </div>
63
63
  </div>
64
64
  <div class="hidden shrink-0 sm:flex sm:flex-col sm:items-end">
65
- <p class="text-sm/6 text-gray-900 capitalize">{{getRolesForUser(user).join(', ')}}</p>
65
+ <p class="text-sm/6 text-content capitalize">{{getRolesForUser(user).join(', ')}}</p>
66
66
  <div class="flex gap-3">
67
67
  <button
68
68
  type="button"
69
- class="mt-1 text-xs/5 text-gray-500 cursor-pointer disabled:cursor-not-allowed disabled:text-gray-300"
69
+ class="mt-1 text-xs/5 text-content-tertiary cursor-pointer disabled:cursor-not-allowed disabled:text-gray-300"
70
70
  :disabled="getRolesForUser(user).includes('owner')"
71
71
  @click="openEditModal(user)">
72
72
  Edit
@@ -93,7 +93,7 @@
93
93
  @click="showNewInvitationModal = true"
94
94
  :disabled="status === 'loading'"
95
95
  :tier="workspace?.subscriptionTier"
96
- class="block rounded-md bg-ultramarine-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 disabled:bg-gray-500 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600">
96
+ class="block rounded-md bg-primary px-3 py-2 text-center text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover disabled:bg-page0 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary">
97
97
  New Invitation
98
98
  <svg class="inline w-4 h-4 ml-1" v-if="workspace && !workspace.subscriptionTier" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
99
99
  <path fill-rule="evenodd" d="M12 1.5a5.25 5.25 0 00-5.25 5.25v3a3 3 0 00-3 3v6.75a3 3 0 003 3h10.5a3 3 0 003-3v-6.75a3 3 0 00-3-3v-3c0-2.9-2.35-5.25-5.25-5.25zm3.75 8.25v-3a3.75 3.75 0 10-7.5 0v3h7.5z" clip-rule="evenodd" />
@@ -110,26 +110,26 @@
110
110
  <table class="min-w-full divide-y divide-gray-300">
111
111
  <thead>
112
112
  <tr>
113
- <th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0">GitHub Username</th>
114
- <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Email</th>
115
- <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Status</th>
116
- <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Role</th>
113
+ <th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-content sm:pl-0">GitHub Username</th>
114
+ <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-content">Email</th>
115
+ <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-content">Status</th>
116
+ <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-content">Role</th>
117
117
  </tr>
118
118
  </thead>
119
- <tbody class="divide-y divide-gray-200 bg-white">
119
+ <tbody class="divide-y divide-gray-200 bg-surface">
120
120
  <tr v-for="invitation in invitations">
121
121
  <td class="whitespace-nowrap py-5 pl-4 pr-3 text-sm sm:pl-0">
122
122
  {{invitation.githubUsername}}
123
123
  </td>
124
- <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500">
124
+ <td class="whitespace-nowrap px-3 py-5 text-sm text-content-tertiary">
125
125
  {{invitation.email}}
126
126
  </td>
127
- <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500">
128
- <span class="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-600/20">
127
+ <td class="whitespace-nowrap px-3 py-5 text-sm text-content-tertiary">
128
+ <span class="inline-flex items-center rounded-md bg-page px-2 py-1 text-xs font-medium text-content-secondary ring-1 ring-inset ring-gray-600/20">
129
129
  Pending
130
130
  </span>
131
131
  </td>
132
- <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500">
132
+ <td class="whitespace-nowrap px-3 py-5 text-sm text-content-tertiary">
133
133
  {{invitation.roles.join(', ')}}
134
134
  </td>
135
135
  </tr>
@@ -143,8 +143,8 @@
143
143
  <svg class="mx-auto size-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
144
144
  <path vector-effect="non-scaling-stroke" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z" />
145
145
  </svg>
146
- <h3 class="mt-2 text-sm font-semibold text-gray-900">No invitations</h3>
147
- <p class="mt-1 text-sm text-gray-500">You have no outstanding invitations</p>
146
+ <h3 class="mt-2 text-sm font-semibold text-content">No invitations</h3>
147
+ <p class="mt-1 text-sm text-content-tertiary">You have no outstanding invitations</p>
148
148
  </div>
149
149
  </div>
150
150
  </div>
@@ -165,32 +165,32 @@
165
165
  </div>
166
166
 
167
167
  <div>
168
- <div class="text-sm/6 font-semibold text-gray-900">
168
+ <div class="text-sm/6 font-semibold text-content">
169
169
  {{showEditModal.user.name || showEditModal.user.githubUsername}}
170
170
  </div>
171
- <div class="text-xs/5 text-gray-500">
171
+ <div class="text-xs/5 text-content-tertiary">
172
172
  {{showEditModal.user.email ?? 'No Email'}}
173
173
  </div>
174
174
  </div>
175
175
 
176
176
  <div>
177
- <label for="editRole" class="block text-sm/6 font-medium text-gray-900">Role</label>
177
+ <label for="editRole" class="block text-sm/6 font-medium text-content">Role</label>
178
178
  <div class="mt-2 grid grid-cols-1">
179
179
  <select
180
180
  id="editRole"
181
181
  name="editRole"
182
182
  v-model="showEditModal.role"
183
- class="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pl-3 pr-8 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-ultramarine-600 sm:text-sm/6">
183
+ class="col-start-1 row-start-1 w-full appearance-none rounded-md bg-surface py-1.5 pl-3 pr-8 text-base text-content outline outline-1 -outline-offset-1 outline-edge-strong focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-primary sm:text-sm/6">
184
184
  <option value="admin" :disabled="disableRoleOption('admin')">Admin</option>
185
185
  <option value="member" :disabled="disableRoleOption('member')">Member</option>
186
186
  <option value="readonly" :disabled="disableRoleOption('readonly')">Read-only</option>
187
187
  <option value="dashboards" :disabled="disableRoleOption('dashboards')">Dashboards Only</option>
188
188
  </select>
189
- <svg class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-gray-500 sm:size-4" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon">
189
+ <svg class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-content-tertiary sm:size-4" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon">
190
190
  <path fill-rule="evenodd" d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
191
191
  </svg>
192
192
  </div>
193
- <div v-if="!workspace?.subscriptionTier" class="mt-2 text-sm text-gray-700">
193
+ <div v-if="!workspace?.subscriptionTier" class="mt-2 text-sm text-content-secondary">
194
194
  You can only assign the "Dashboards Only" role until you activate a subscription.
195
195
  </div>
196
196
  </div>
@@ -199,7 +199,7 @@
199
199
  <async-button
200
200
  @click="updateWorkspaceMember"
201
201
  :disabled="showEditModal.role === showEditModal.originalRole"
202
- class="border-0 mt-0 flex w-full items-center justify-center gap-3 rounded-md bg-ultramarine-600 hover:bg-ultramarine-500 px-3 py-1.5 text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-500">
202
+ class="border-0 mt-0 flex w-full items-center justify-center gap-3 rounded-md bg-primary hover:bg-primary-hover px-3 py-1.5 text-primary-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary">
203
203
  <span class="text-sm font-semibold leading-6">Save</span>
204
204
  </async-button>
205
205
 
@@ -1,8 +1,13 @@
1
1
  <div>
2
2
  <div class="mb-2">
3
- <textarea class="border border-gray-200 p-2 h-[300px] w-full" ref="codeEditor"></textarea>
3
+ <ace-editor
4
+ v-model="editorValue"
5
+ mode="javascript"
6
+ :line-numbers="true"
7
+ class="h-[300px] w-full"
8
+ />
4
9
  </div>
5
- <button @click="updateDocument()" class="rounded-md bg-ultramarine-600 px-2.5 py-1.5 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-teal-600">Submit</button>
10
+ <button @click="updateDocument()" class="rounded-md bg-primary px-2.5 py-1.5 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600">Submit</button>
6
11
  <div v-if="errors.length > 0" class="rounded-md bg-red-50 p-4 mt-1">
7
12
  <div class="flex">
8
13
  <div class="flex-shrink-0">
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const api = require('../api');
4
-
5
4
  const { BSON, EJSON } = require('mongodb/lib/bson');
6
5
 
7
6
  const ObjectId = new Proxy(BSON.ObjectId, {
@@ -21,13 +20,13 @@ module.exports = app => app.component('update-document', {
21
20
  template,
22
21
  data: function() {
23
22
  return {
24
- editor: null,
23
+ editorValue: '{\n \n}',
25
24
  errors: []
26
25
  };
27
26
  },
28
27
  methods: {
29
28
  async updateDocument() {
30
- const data = EJSON.serialize(eval(`(${this.editor.getValue()})`));
29
+ const data = EJSON.serialize(eval(`(${this.editorValue})`));
31
30
  try {
32
31
  if (this.multiple) {
33
32
  const ids = this.document.map(x => x._id);
@@ -52,13 +51,5 @@ module.exports = app => app.component('update-document', {
52
51
  throw err;
53
52
  }
54
53
  }
55
- },
56
- mounted: function() {
57
- this.$refs.codeEditor.value = '{\n \n}';
58
- this.editor = CodeMirror.fromTextArea(this.$refs.codeEditor, {
59
- mode: 'javascript',
60
- lineNumbers: true,
61
- smartIndent: false
62
- });
63
54
  }
64
55
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.2.12",
3
+ "version": "0.3.0",
4
4
  "description": "A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.",
5
5
  "homepage": "https://mongoosestudio.app/",
6
6
  "repository": {
@@ -12,6 +12,7 @@
12
12
  "@ai-sdk/anthropic": "2.x",
13
13
  "@ai-sdk/google": "2.x",
14
14
  "@ai-sdk/openai": "2.x",
15
+ "ace-builds": "^1.43.6",
15
16
  "ai": "5.x",
16
17
  "archetype": "0.13.1",
17
18
  "csv-stringify": "6.3.0",
@@ -38,6 +39,7 @@
38
39
  "eslint": "9.30.0",
39
40
  "express": "4.x",
40
41
  "mocha": "10.2.0",
42
+ "mongodb-memory-server": "^11.0.1",
41
43
  "mongoose": "9.x",
42
44
  "sinon": "^21.0.1"
43
45
  },