@mongoosejs/studio 0.0.104 → 0.0.106
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/frontend/public/app.js
CHANGED
|
@@ -330,7 +330,17 @@ const vanillatoasts = __webpack_require__(/*! vanillatoasts */ "./node_modules/v
|
|
|
330
330
|
module.exports = app => app.component('chat-message-script', {
|
|
331
331
|
template,
|
|
332
332
|
props: ['message', 'script', 'language'],
|
|
333
|
-
data
|
|
333
|
+
data() {
|
|
334
|
+
return {
|
|
335
|
+
activeTab: 'code',
|
|
336
|
+
showDetailModal: false,
|
|
337
|
+
showCreateDashboardModal: false,
|
|
338
|
+
newDashboardTitle: '',
|
|
339
|
+
dashboardCode: '',
|
|
340
|
+
createError: null,
|
|
341
|
+
dashboardEditor: null
|
|
342
|
+
};
|
|
343
|
+
},
|
|
334
344
|
computed: {
|
|
335
345
|
styleForMessage() {
|
|
336
346
|
return this.message.role === 'user' ? 'bg-gray-100' : '';
|
|
@@ -348,6 +358,42 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
348
358
|
openDetailModal() {
|
|
349
359
|
this.showDetailModal = true;
|
|
350
360
|
},
|
|
361
|
+
openCreateDashboardModal() {
|
|
362
|
+
this.newDashboardTitle = '';
|
|
363
|
+
this.dashboardCode = this.script;
|
|
364
|
+
this.createErrors = [];
|
|
365
|
+
this.showCreateDashboardModal = true;
|
|
366
|
+
this.$nextTick(() => {
|
|
367
|
+
if (this.dashboardEditor) {
|
|
368
|
+
this.dashboardEditor.toTextArea();
|
|
369
|
+
}
|
|
370
|
+
this.$refs.dashboardCodeEditor.value = this.dashboardCode;
|
|
371
|
+
this.dashboardEditor = CodeMirror.fromTextArea(this.$refs.dashboardCodeEditor, {
|
|
372
|
+
mode: 'javascript',
|
|
373
|
+
lineNumbers: true
|
|
374
|
+
});
|
|
375
|
+
this.dashboardEditor.on('change', () => {
|
|
376
|
+
this.dashboardCode = this.dashboardEditor.getValue();
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
},
|
|
380
|
+
async createDashboardFromScript() {
|
|
381
|
+
this.dashboardCode = this.dashboardEditor.getValue();
|
|
382
|
+
const { dashboard } = await api.Dashboard.createDashboard({
|
|
383
|
+
code: this.dashboardCode,
|
|
384
|
+
title: this.newDashboardTitle
|
|
385
|
+
}).catch(err => {
|
|
386
|
+
if (err.response?.data?.message) {
|
|
387
|
+
const message = err.response.data.message.split(': ').slice(1).join(': ');
|
|
388
|
+
this.createError = message;
|
|
389
|
+
throw new Error(err.response?.data?.message);
|
|
390
|
+
}
|
|
391
|
+
throw err;
|
|
392
|
+
});
|
|
393
|
+
this.createError = null;
|
|
394
|
+
this.showCreateDashboardModal = false;
|
|
395
|
+
this.$router.push('/dashboard/' + dashboard._id);
|
|
396
|
+
},
|
|
351
397
|
async copyOutput() {
|
|
352
398
|
await navigator.clipboard.writeText(this.message.executionResult.output);
|
|
353
399
|
vanillatoasts.create({
|
|
@@ -359,6 +405,14 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
359
405
|
});
|
|
360
406
|
}
|
|
361
407
|
},
|
|
408
|
+
watch: {
|
|
409
|
+
showCreateDashboardModal(val) {
|
|
410
|
+
if (!val && this.dashboardEditor) {
|
|
411
|
+
this.dashboardEditor.toTextArea();
|
|
412
|
+
this.dashboardEditor = null;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
},
|
|
362
416
|
mounted() {
|
|
363
417
|
Prism.highlightElement(this.$refs.code);
|
|
364
418
|
if (this.message.executionResult?.output) {
|
|
@@ -2346,41 +2400,47 @@ module.exports = app => app.component('models', {
|
|
|
2346
2400
|
this.currentModel = this.model;
|
|
2347
2401
|
},
|
|
2348
2402
|
beforeDestroy() {
|
|
2349
|
-
document.removeEventListener('scroll',
|
|
2403
|
+
document.removeEventListener('scroll', this.onScroll, true);
|
|
2404
|
+
window.removeEventListener('popstate', this.onPopState, true);
|
|
2350
2405
|
},
|
|
2351
2406
|
async mounted() {
|
|
2352
|
-
|
|
2407
|
+
this.onScroll = () => this.checkIfScrolledToBottom();
|
|
2408
|
+
document.addEventListener('scroll', this.onScroll, true);
|
|
2409
|
+
this.onPopState = () => this.initSearchFromUrl();
|
|
2410
|
+
window.addEventListener('popstate', this.onPopState, true);
|
|
2353
2411
|
this.models = await api.Model.listModels().then(res => res.models);
|
|
2354
2412
|
if (this.currentModel == null && this.models.length > 0) {
|
|
2355
2413
|
this.currentModel = this.models[0];
|
|
2356
2414
|
}
|
|
2357
2415
|
|
|
2358
|
-
this.
|
|
2359
|
-
if (this.$route.query?.search) {
|
|
2360
|
-
this.searchText = this.$route.query.search;
|
|
2361
|
-
this.filter = eval(`(${this.$route.query.search})`);
|
|
2362
|
-
this.filter = EJSON.stringify(this.filter);
|
|
2363
|
-
}
|
|
2364
|
-
if (this.$route.query?.sort) {
|
|
2365
|
-
const sort = eval(`(${this.$route.query.sort})`);
|
|
2366
|
-
const path = Object.keys(sort)[0];
|
|
2367
|
-
const num = Object.values(sort)[0];
|
|
2368
|
-
this.sortDocs(num, path);
|
|
2369
|
-
}
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
if (this.currentModel != null) {
|
|
2373
|
-
await this.getDocuments();
|
|
2374
|
-
}
|
|
2375
|
-
if (this.$route.query?.fields) {
|
|
2376
|
-
const filter = this.$route.query.fields.split(',');
|
|
2377
|
-
this.filteredPaths = this.filteredPaths.filter(x => filter.includes(x.path));
|
|
2378
|
-
}
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
this.status = 'loaded';
|
|
2416
|
+
await this.initSearchFromUrl();
|
|
2382
2417
|
},
|
|
2383
2418
|
methods: {
|
|
2419
|
+
async initSearchFromUrl() {
|
|
2420
|
+
this.status = 'loading';
|
|
2421
|
+
this.query = Object.assign({}, this.$route.query); // important that this is here before the if statements
|
|
2422
|
+
if (this.$route.query?.search) {
|
|
2423
|
+
this.searchText = this.$route.query.search;
|
|
2424
|
+
this.filter = eval(`(${this.$route.query.search})`);
|
|
2425
|
+
this.filter = EJSON.stringify(this.filter);
|
|
2426
|
+
}
|
|
2427
|
+
if (this.$route.query?.sort) {
|
|
2428
|
+
const sort = eval(`(${this.$route.query.sort})`);
|
|
2429
|
+
const path = Object.keys(sort)[0];
|
|
2430
|
+
const num = Object.values(sort)[0];
|
|
2431
|
+
this.sortDocs(num, path);
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
|
|
2435
|
+
if (this.currentModel != null) {
|
|
2436
|
+
await this.getDocuments();
|
|
2437
|
+
}
|
|
2438
|
+
if (this.$route.query?.fields) {
|
|
2439
|
+
const filter = this.$route.query.fields.split(',');
|
|
2440
|
+
this.filteredPaths = this.filteredPaths.filter(x => filter.includes(x.path));
|
|
2441
|
+
}
|
|
2442
|
+
this.status = 'loaded';
|
|
2443
|
+
},
|
|
2384
2444
|
async dropIndex(name) {
|
|
2385
2445
|
const { mongoDBIndexes } = await api.Model.dropIndex({ model: this.currentModel, name });
|
|
2386
2446
|
this.mongoDBIndexes = mongoDBIndexes;
|
|
@@ -2430,7 +2490,7 @@ module.exports = app => app.component('models', {
|
|
|
2430
2490
|
}
|
|
2431
2491
|
return filteredDoc;
|
|
2432
2492
|
},
|
|
2433
|
-
async
|
|
2493
|
+
async checkIfScrolledToBottom() {
|
|
2434
2494
|
if (this.status === 'loading' || this.loadedAllDocs) {
|
|
2435
2495
|
return;
|
|
2436
2496
|
}
|
|
@@ -2473,13 +2533,20 @@ module.exports = app => app.component('models', {
|
|
|
2473
2533
|
this.filter = eval(`(${this.searchText})`);
|
|
2474
2534
|
this.filter = EJSON.stringify(this.filter);
|
|
2475
2535
|
this.query.search = this.searchText;
|
|
2476
|
-
|
|
2536
|
+
const query = this.query;
|
|
2537
|
+
const newUrl = this.$router.resolve({ query }).href;
|
|
2538
|
+
window.history.pushState(null, '', newUrl);
|
|
2477
2539
|
} else {
|
|
2478
2540
|
this.filter = {};
|
|
2479
2541
|
delete this.query.search;
|
|
2480
|
-
|
|
2542
|
+
const query = this.query;
|
|
2543
|
+
const newUrl = this.$router.resolve({ query }).href;
|
|
2544
|
+
window.history.pushState(null, '', newUrl);
|
|
2481
2545
|
}
|
|
2546
|
+
this.documents = [];
|
|
2547
|
+
this.status = 'loading';
|
|
2482
2548
|
await this.loadMoreDocuments();
|
|
2549
|
+
this.status = 'loaded';
|
|
2483
2550
|
},
|
|
2484
2551
|
async openIndexModal() {
|
|
2485
2552
|
this.shouldShowIndexModal = true;
|
|
@@ -3962,7 +4029,7 @@ module.exports = "<button v-bind=\"attrsToBind\" :disabled=\"isDisabled\" @click
|
|
|
3962
4029
|
/***/ ((module) => {
|
|
3963
4030
|
|
|
3964
4031
|
"use strict";
|
|
3965
|
-
module.exports = "<div class=\"relative border rounded bg-gray-100 text-black text-sm overflow-hidden\">\n <div class=\"flex border-b pt-[1px] text-xs font-medium bg-gray-200\">\n <button\n class=\"px-3 py-1 border-r border-gray-300 hover:bg-green-300\"\n :class=\"{'bg-gray-300': activeTab === 'code', 'bg-green-300': activeTab === 'code'}\"\n @click=\"activeTab = 'code'\">\n Code\n </button>\n <button\n class=\"px-3 py-1 hover:bg-green-300\"\n :class=\"{'bg-green-300': activeTab === 'output'}\"\n @click=\"activeTab = 'output'\">\n Output\n </button>\n <div class=\"ml-auto mr-1 flex\">\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-gray-500 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center\"\n @click=\"copyOutput\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3\" />\n </svg>\n </button>\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-blue-500 text-white border-none rounded cursor-pointer hover:bg-blue-600 transition-colors flex items-center\"\n @click=\"openDetailModal\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5\" />\n </svg>\n </button>\n <async-button\n class=\"px-2 py-1 text-xs bg-green-500 text-white border-none rounded cursor-pointer hover:bg-green-600 transition-colors disabled:bg-gray-400\"\n @click=\"executeScript(message, script)\">\n Execute\n </async-button>\n </div>\n </div>\n\n <pre class=\"p-3 whitespace-pre-wrap max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto\" v-show=\"activeTab === 'code'\"><code v-text=\"script\" ref=\"code\" :class=\"'language-' + language\"></code></pre>\n\n <div class=\"p-3 whitespace-pre-wrap max-h-[50vh] overflow-y-auto bg-white border-t max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] relative\" v-show=\"activeTab === 'output'\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" />\n <pre v-else>{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n\n <modal ref=\"outputModal\" v-if=\"showDetailModal\" containerClass=\"!h-[90vh] !w-[90vw]\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\">×</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" :responsive=\"true\" />\n <pre v-else class=\"whitespace-pre-wrap\">{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
4032
|
+
module.exports = "<div class=\"relative border rounded bg-gray-100 text-black text-sm overflow-hidden\">\n <div class=\"flex border-b pt-[1px] text-xs font-medium bg-gray-200\">\n <button\n class=\"px-3 py-1 border-r border-gray-300 hover:bg-green-300\"\n :class=\"{'bg-gray-300': activeTab === 'code', 'bg-green-300': activeTab === 'code'}\"\n @click=\"activeTab = 'code'\">\n Code\n </button>\n <button\n class=\"px-3 py-1 hover:bg-green-300\"\n :class=\"{'bg-green-300': activeTab === 'output'}\"\n @click=\"activeTab = 'output'\">\n Output\n </button>\n <div class=\"ml-auto mr-1 flex\">\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-gray-500 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center\"\n @click=\"copyOutput\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3\" />\n </svg>\n </button>\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-blue-500 text-white border-none rounded cursor-pointer hover:bg-blue-600 transition-colors flex items-center\"\n @click=\"openDetailModal\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5\" />\n </svg>\n </button>\n <button\n class=\"px-2 py-1 mr-1 text-xs bg-ultramarine-500 text-white border-none rounded cursor-pointer hover:bg-ultramarine-600 transition-colors flex items-center\"\n @click=\"openCreateDashboardModal\">\n Create Dashboard\n </button>\n <async-button\n class=\"px-2 py-1 text-xs bg-green-500 text-white border-none rounded cursor-pointer hover:bg-green-600 transition-colors disabled:bg-gray-400\"\n @click=\"executeScript(message, script)\">\n Execute\n </async-button>\n </div>\n </div>\n\n <pre class=\"p-3 whitespace-pre-wrap max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto\" v-show=\"activeTab === 'code'\"><code v-text=\"script\" ref=\"code\" :class=\"'language-' + language\"></code></pre>\n\n <div class=\"p-3 whitespace-pre-wrap max-h-[50vh] overflow-y-auto bg-white border-t max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] relative\" v-show=\"activeTab === 'output'\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" />\n <pre v-else>{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n\n <modal ref=\"outputModal\" v-if=\"showDetailModal\" containerClass=\"!h-[90vh] !w-[90vw]\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\">×</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" :responsive=\"true\" />\n <pre v-else class=\"whitespace-pre-wrap\">{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n </template>\n </modal>\n <modal v-if=\"showCreateDashboardModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false\">×</div>\n <div>\n <div class=\"mt-4 text-gray-900 font-semibold\">Create Dashboard</div>\n <div class=\"mt-4\">\n <label class=\"block text-sm font-medium leading-6 text-gray-900\">Title</label>\n <div class=\"mt-2\">\n <div class=\"w-full flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-teal-600\">\n <input type=\"text\" v-model=\"newDashboardTitle\" class=\"outline-none block flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6\" placeholder=\"My Dashboard\">\n </div>\n </div>\n </div>\n <div class=\"my-4\">\n <label class=\"block text-sm font-medium leading-6 text-gray-900\">Code</label>\n <div class=\"border border-gray-200\">\n <textarea class=\"p-2 h-[300px] w-full\" ref=\"dashboardCodeEditor\"></textarea>\n </div>\n </div>\n <async-button\n @click=\"createDashboardFromScript\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n Submit\n </async-button>\n <div v-if=\"createErrors.length > 0\" class=\"rounded-md bg-red-50 p-4 mt-1\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">Error</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n {{createError}}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
3966
4033
|
|
|
3967
4034
|
/***/ }),
|
|
3968
4035
|
|
|
@@ -14619,7 +14686,7 @@ var bson = /*#__PURE__*/Object.freeze({
|
|
|
14619
14686
|
/***/ ((module) => {
|
|
14620
14687
|
|
|
14621
14688
|
"use strict";
|
|
14622
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.
|
|
14689
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.106","description":"A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.","homepage":"https://studio.mongoosejs.io/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"dependencies":{"archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"0.0.26","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vanillatoasts":"^1.6.0","vue":"3.x","webpack":"5.x"},"peerDependencies":{"bson":"^5.5.1 || 6.x","express":"4.x","mongoose":"7.x || 8.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"8.x"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js"}}');
|
|
14623
14690
|
|
|
14624
14691
|
/***/ })
|
|
14625
14692
|
|
|
@@ -29,6 +29,11 @@
|
|
|
29
29
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5" />
|
|
30
30
|
</svg>
|
|
31
31
|
</button>
|
|
32
|
+
<button
|
|
33
|
+
class="px-2 py-1 mr-1 text-xs bg-ultramarine-500 text-white border-none rounded cursor-pointer hover:bg-ultramarine-600 transition-colors flex items-center"
|
|
34
|
+
@click="openCreateDashboardModal">
|
|
35
|
+
Create Dashboard
|
|
36
|
+
</button>
|
|
32
37
|
<async-button
|
|
33
38
|
class="px-2 py-1 text-xs bg-green-500 text-white border-none rounded cursor-pointer hover:bg-green-600 transition-colors disabled:bg-gray-400"
|
|
34
39
|
@click="executeScript(message, script)">
|
|
@@ -53,4 +58,46 @@
|
|
|
53
58
|
</div>
|
|
54
59
|
</template>
|
|
55
60
|
</modal>
|
|
61
|
+
<modal v-if="showCreateDashboardModal">
|
|
62
|
+
<template #body>
|
|
63
|
+
<div class="modal-exit" @click="showCreateDashboardModal = false">×</div>
|
|
64
|
+
<div>
|
|
65
|
+
<div class="mt-4 text-gray-900 font-semibold">Create Dashboard</div>
|
|
66
|
+
<div class="mt-4">
|
|
67
|
+
<label class="block text-sm font-medium leading-6 text-gray-900">Title</label>
|
|
68
|
+
<div class="mt-2">
|
|
69
|
+
<div class="w-full flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-teal-600">
|
|
70
|
+
<input type="text" v-model="newDashboardTitle" class="outline-none block flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6" placeholder="My Dashboard">
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="my-4">
|
|
75
|
+
<label class="block text-sm font-medium leading-6 text-gray-900">Code</label>
|
|
76
|
+
<div class="border border-gray-200">
|
|
77
|
+
<textarea class="p-2 h-[300px] w-full" ref="dashboardCodeEditor"></textarea>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
<async-button
|
|
81
|
+
@click="createDashboardFromScript"
|
|
82
|
+
class="rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600">
|
|
83
|
+
Submit
|
|
84
|
+
</async-button>
|
|
85
|
+
<div v-if="createErrors.length > 0" class="rounded-md bg-red-50 p-4 mt-1">
|
|
86
|
+
<div class="flex">
|
|
87
|
+
<div class="flex-shrink-0">
|
|
88
|
+
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
89
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd" />
|
|
90
|
+
</svg>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="ml-3">
|
|
93
|
+
<h3 class="text-sm font-medium text-red-800">Error</h3>
|
|
94
|
+
<div class="mt-2 text-sm text-red-700">
|
|
95
|
+
{{createError}}
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</template>
|
|
102
|
+
</modal>
|
|
56
103
|
</div>
|
|
@@ -7,7 +7,17 @@ const vanillatoasts = require('vanillatoasts');
|
|
|
7
7
|
module.exports = app => app.component('chat-message-script', {
|
|
8
8
|
template,
|
|
9
9
|
props: ['message', 'script', 'language'],
|
|
10
|
-
data
|
|
10
|
+
data() {
|
|
11
|
+
return {
|
|
12
|
+
activeTab: 'code',
|
|
13
|
+
showDetailModal: false,
|
|
14
|
+
showCreateDashboardModal: false,
|
|
15
|
+
newDashboardTitle: '',
|
|
16
|
+
dashboardCode: '',
|
|
17
|
+
createError: null,
|
|
18
|
+
dashboardEditor: null
|
|
19
|
+
};
|
|
20
|
+
},
|
|
11
21
|
computed: {
|
|
12
22
|
styleForMessage() {
|
|
13
23
|
return this.message.role === 'user' ? 'bg-gray-100' : '';
|
|
@@ -25,6 +35,42 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
25
35
|
openDetailModal() {
|
|
26
36
|
this.showDetailModal = true;
|
|
27
37
|
},
|
|
38
|
+
openCreateDashboardModal() {
|
|
39
|
+
this.newDashboardTitle = '';
|
|
40
|
+
this.dashboardCode = this.script;
|
|
41
|
+
this.createErrors = [];
|
|
42
|
+
this.showCreateDashboardModal = true;
|
|
43
|
+
this.$nextTick(() => {
|
|
44
|
+
if (this.dashboardEditor) {
|
|
45
|
+
this.dashboardEditor.toTextArea();
|
|
46
|
+
}
|
|
47
|
+
this.$refs.dashboardCodeEditor.value = this.dashboardCode;
|
|
48
|
+
this.dashboardEditor = CodeMirror.fromTextArea(this.$refs.dashboardCodeEditor, {
|
|
49
|
+
mode: 'javascript',
|
|
50
|
+
lineNumbers: true
|
|
51
|
+
});
|
|
52
|
+
this.dashboardEditor.on('change', () => {
|
|
53
|
+
this.dashboardCode = this.dashboardEditor.getValue();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
async createDashboardFromScript() {
|
|
58
|
+
this.dashboardCode = this.dashboardEditor.getValue();
|
|
59
|
+
const { dashboard } = await api.Dashboard.createDashboard({
|
|
60
|
+
code: this.dashboardCode,
|
|
61
|
+
title: this.newDashboardTitle
|
|
62
|
+
}).catch(err => {
|
|
63
|
+
if (err.response?.data?.message) {
|
|
64
|
+
const message = err.response.data.message.split(': ').slice(1).join(': ');
|
|
65
|
+
this.createError = message;
|
|
66
|
+
throw new Error(err.response?.data?.message);
|
|
67
|
+
}
|
|
68
|
+
throw err;
|
|
69
|
+
});
|
|
70
|
+
this.createError = null;
|
|
71
|
+
this.showCreateDashboardModal = false;
|
|
72
|
+
this.$router.push('/dashboard/' + dashboard._id);
|
|
73
|
+
},
|
|
28
74
|
async copyOutput() {
|
|
29
75
|
await navigator.clipboard.writeText(this.message.executionResult.output);
|
|
30
76
|
vanillatoasts.create({
|
|
@@ -36,6 +82,14 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
36
82
|
});
|
|
37
83
|
}
|
|
38
84
|
},
|
|
85
|
+
watch: {
|
|
86
|
+
showCreateDashboardModal(val) {
|
|
87
|
+
if (!val && this.dashboardEditor) {
|
|
88
|
+
this.dashboardEditor.toTextArea();
|
|
89
|
+
this.dashboardEditor = null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
39
93
|
mounted() {
|
|
40
94
|
Prism.highlightElement(this.$refs.code);
|
|
41
95
|
if (this.message.executionResult?.output) {
|
|
@@ -58,41 +58,47 @@ module.exports = app => app.component('models', {
|
|
|
58
58
|
this.currentModel = this.model;
|
|
59
59
|
},
|
|
60
60
|
beforeDestroy() {
|
|
61
|
-
document.removeEventListener('scroll',
|
|
61
|
+
document.removeEventListener('scroll', this.onScroll, true);
|
|
62
|
+
window.removeEventListener('popstate', this.onPopState, true);
|
|
62
63
|
},
|
|
63
64
|
async mounted() {
|
|
64
|
-
|
|
65
|
+
this.onScroll = () => this.checkIfScrolledToBottom();
|
|
66
|
+
document.addEventListener('scroll', this.onScroll, true);
|
|
67
|
+
this.onPopState = () => this.initSearchFromUrl();
|
|
68
|
+
window.addEventListener('popstate', this.onPopState, true);
|
|
65
69
|
this.models = await api.Model.listModels().then(res => res.models);
|
|
66
70
|
if (this.currentModel == null && this.models.length > 0) {
|
|
67
71
|
this.currentModel = this.models[0];
|
|
68
72
|
}
|
|
69
73
|
|
|
70
|
-
this.
|
|
71
|
-
if (this.$route.query?.search) {
|
|
72
|
-
this.searchText = this.$route.query.search;
|
|
73
|
-
this.filter = eval(`(${this.$route.query.search})`);
|
|
74
|
-
this.filter = EJSON.stringify(this.filter);
|
|
75
|
-
}
|
|
76
|
-
if (this.$route.query?.sort) {
|
|
77
|
-
const sort = eval(`(${this.$route.query.sort})`);
|
|
78
|
-
const path = Object.keys(sort)[0];
|
|
79
|
-
const num = Object.values(sort)[0];
|
|
80
|
-
this.sortDocs(num, path);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (this.currentModel != null) {
|
|
85
|
-
await this.getDocuments();
|
|
86
|
-
}
|
|
87
|
-
if (this.$route.query?.fields) {
|
|
88
|
-
const filter = this.$route.query.fields.split(',');
|
|
89
|
-
this.filteredPaths = this.filteredPaths.filter(x => filter.includes(x.path));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this.status = 'loaded';
|
|
74
|
+
await this.initSearchFromUrl();
|
|
94
75
|
},
|
|
95
76
|
methods: {
|
|
77
|
+
async initSearchFromUrl() {
|
|
78
|
+
this.status = 'loading';
|
|
79
|
+
this.query = Object.assign({}, this.$route.query); // important that this is here before the if statements
|
|
80
|
+
if (this.$route.query?.search) {
|
|
81
|
+
this.searchText = this.$route.query.search;
|
|
82
|
+
this.filter = eval(`(${this.$route.query.search})`);
|
|
83
|
+
this.filter = EJSON.stringify(this.filter);
|
|
84
|
+
}
|
|
85
|
+
if (this.$route.query?.sort) {
|
|
86
|
+
const sort = eval(`(${this.$route.query.sort})`);
|
|
87
|
+
const path = Object.keys(sort)[0];
|
|
88
|
+
const num = Object.values(sort)[0];
|
|
89
|
+
this.sortDocs(num, path);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if (this.currentModel != null) {
|
|
94
|
+
await this.getDocuments();
|
|
95
|
+
}
|
|
96
|
+
if (this.$route.query?.fields) {
|
|
97
|
+
const filter = this.$route.query.fields.split(',');
|
|
98
|
+
this.filteredPaths = this.filteredPaths.filter(x => filter.includes(x.path));
|
|
99
|
+
}
|
|
100
|
+
this.status = 'loaded';
|
|
101
|
+
},
|
|
96
102
|
async dropIndex(name) {
|
|
97
103
|
const { mongoDBIndexes } = await api.Model.dropIndex({ model: this.currentModel, name });
|
|
98
104
|
this.mongoDBIndexes = mongoDBIndexes;
|
|
@@ -142,7 +148,7 @@ module.exports = app => app.component('models', {
|
|
|
142
148
|
}
|
|
143
149
|
return filteredDoc;
|
|
144
150
|
},
|
|
145
|
-
async
|
|
151
|
+
async checkIfScrolledToBottom() {
|
|
146
152
|
if (this.status === 'loading' || this.loadedAllDocs) {
|
|
147
153
|
return;
|
|
148
154
|
}
|
|
@@ -185,13 +191,20 @@ module.exports = app => app.component('models', {
|
|
|
185
191
|
this.filter = eval(`(${this.searchText})`);
|
|
186
192
|
this.filter = EJSON.stringify(this.filter);
|
|
187
193
|
this.query.search = this.searchText;
|
|
188
|
-
|
|
194
|
+
const query = this.query;
|
|
195
|
+
const newUrl = this.$router.resolve({ query }).href;
|
|
196
|
+
window.history.pushState(null, '', newUrl);
|
|
189
197
|
} else {
|
|
190
198
|
this.filter = {};
|
|
191
199
|
delete this.query.search;
|
|
192
|
-
|
|
200
|
+
const query = this.query;
|
|
201
|
+
const newUrl = this.$router.resolve({ query }).href;
|
|
202
|
+
window.history.pushState(null, '', newUrl);
|
|
193
203
|
}
|
|
204
|
+
this.documents = [];
|
|
205
|
+
this.status = 'loading';
|
|
194
206
|
await this.loadMoreDocuments();
|
|
207
|
+
this.status = 'loaded';
|
|
195
208
|
},
|
|
196
209
|
async openIndexModal() {
|
|
197
210
|
this.shouldShowIndexModal = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.106",
|
|
4
4
|
"description": "A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.",
|
|
5
5
|
"homepage": "https://studio.mongoosejs.io/",
|
|
6
6
|
"repository": {
|