@mongoosejs/studio 0.0.139 → 0.0.140
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/backend/actions/ChatMessage/executeScript.js +40 -0
- package/backend/actions/Model/listModels.js +4 -1
- package/frontend/public/app.js +105 -14
- package/frontend/public/tw.css +43 -0
- package/frontend/src/chat/chat-message-script/chat-message-script.html +35 -2
- package/frontend/src/chat/chat-message-script/chat-message-script.js +91 -9
- package/frontend/src/models/models.html +11 -2
- package/frontend/src/models/models.js +11 -2
- package/package.json +1 -1
|
@@ -56,7 +56,9 @@ module.exports = ({ db, studioConnection }) => async function executeScript(para
|
|
|
56
56
|
// Execute the script in the sandbox
|
|
57
57
|
output = await vm.runInContext(wrappedScript(script), context);
|
|
58
58
|
|
|
59
|
+
const updatedContent = updateContentWithScript(chatMessage.content, chatMessage.script, script);
|
|
59
60
|
chatMessage.script = script;
|
|
61
|
+
chatMessage.content = updatedContent;
|
|
60
62
|
chatMessage.executionResult = { output, logs: logs.join('\n'), error: null };
|
|
61
63
|
await chatMessage.save();
|
|
62
64
|
|
|
@@ -65,10 +67,12 @@ module.exports = ({ db, studioConnection }) => async function executeScript(para
|
|
|
65
67
|
error = err.message;
|
|
66
68
|
|
|
67
69
|
// Update the chat message with the error
|
|
70
|
+
const updatedContent = updateContentWithScript(chatMessage.content, chatMessage.script, script);
|
|
68
71
|
await ChatMessage.updateOne(
|
|
69
72
|
{ _id: chatMessageId },
|
|
70
73
|
{
|
|
71
74
|
script,
|
|
75
|
+
content: updatedContent,
|
|
72
76
|
executionResult: {
|
|
73
77
|
output: null,
|
|
74
78
|
logs: logs.join('\n'),
|
|
@@ -84,3 +88,39 @@ module.exports = ({ db, studioConnection }) => async function executeScript(para
|
|
|
84
88
|
const wrappedScript = script => `(async () => {
|
|
85
89
|
${script}
|
|
86
90
|
})()`;
|
|
91
|
+
|
|
92
|
+
function updateContentWithScript(content, previousScript, newScript) {
|
|
93
|
+
if (typeof content !== 'string') {
|
|
94
|
+
return content;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const matches = Array.from(content.matchAll(/```(\w*)\n([\s\S]*?)\n```/g));
|
|
98
|
+
let targetMatch = null;
|
|
99
|
+
|
|
100
|
+
if (matches.length > 0) {
|
|
101
|
+
if (previousScript != null) {
|
|
102
|
+
const trimmedPrevious = previousScript.trim();
|
|
103
|
+
targetMatch = matches.find(match => match[2].trim() === trimmedPrevious) || null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (targetMatch == null) {
|
|
107
|
+
targetMatch = matches[0];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (targetMatch != null) {
|
|
112
|
+
const language = targetMatch[1];
|
|
113
|
+
const fenceStart = language ? '```' + language : '```';
|
|
114
|
+
const replacement = fenceStart + '\n' + newScript + '\n```';
|
|
115
|
+
return (
|
|
116
|
+
content.slice(0, targetMatch.index) +
|
|
117
|
+
replacement +
|
|
118
|
+
content.slice(targetMatch.index + targetMatch[0].length)
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const trimmedContent = content.trimEnd();
|
|
123
|
+
const prefix = trimmedContent.length > 0 ? trimmedContent + '\n\n' : '';
|
|
124
|
+
|
|
125
|
+
return prefix + '```\n' + newScript + '\n```';
|
|
126
|
+
}
|
|
@@ -13,7 +13,10 @@ module.exports = ({ db }) => async function listModels(params) {
|
|
|
13
13
|
const { roles } = new ListModelsParams(params);
|
|
14
14
|
await authorize('Model.listModels', roles);
|
|
15
15
|
|
|
16
|
+
const readyState = db.connection?.readyState ?? db.readyState;
|
|
17
|
+
|
|
16
18
|
return {
|
|
17
|
-
models: Object.keys(db.models).filter(key => !key.startsWith('__Studio_')).sort()
|
|
19
|
+
models: Object.keys(db.models).filter(key => !key.startsWith('__Studio_')).sort(),
|
|
20
|
+
readyState
|
|
18
21
|
};
|
|
19
22
|
};
|
package/frontend/public/app.js
CHANGED
|
@@ -605,7 +605,7 @@ module.exports = app => app.component('async-button', {
|
|
|
605
605
|
/***/ ((module) => {
|
|
606
606
|
|
|
607
607
|
"use strict";
|
|
608
|
-
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
|
|
608
|
+
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 v-if=\"activeTab === 'code' && !isEditing\"\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.stop=\"startEditing\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM4 13.5V16h2.5l7.086-7.086-2.828-2.828L4 13.5z\" />\n </svg>\n </button>\n <async-button\n v-if=\"!isEditing\"\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\">\n Execute\n </async-button>\n <div class=\"relative ml-1\" ref=\"dropdown\">\n <button\n @click.stop=\"toggleDropdown\"\n class=\"px-1 py-1 text-xs hover:bg-gray-300 rounded flex items-center\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4z\" />\n </svg>\n </button>\n <div\n v-if=\"showDropdown\"\n class=\"absolute right-0 z-10 mt-1 w-64 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5\">\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100\"\n @click=\"openCreateDashboardModal(); showDropdown = false\">\n Create Dashboard\n </button>\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-gray-100\"\n @click=\"$emit('copyMessage'); showDropdown = false\">\n Copy Full Message\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"p-3 max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto\" v-show=\"activeTab === 'code'\">\n <div v-if=\"isEditing\" class=\"flex flex-col space-y-2\">\n <div class=\"border border-gray-200\">\n <textarea ref=\"scriptEditor\" class=\"w-full h-[45vh]\" @input=\"handleScriptInput\"></textarea>\n </div>\n <div class=\"flex justify-end gap-2\">\n <button\n class=\"px-2 py-1 text-xs bg-gray-300 text-gray-800 border-none rounded cursor-pointer hover:bg-gray-400 transition-colors\"\n @click.stop=\"cancelEditing\">\n Cancel\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\">\n Execute\n </async-button>\n </div>\n </div>\n <pre v-else class=\"whitespace-pre-wrap\"><code v-text=\"script\" ref=\"code\" :class=\"'language-' + language\"></code></pre>\n </div>\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 <dashboard-map v-else-if=\"message.executionResult?.output?.$featureCollection\" :value=\"message.executionResult?.output\" />\n <pre v-else>{{ message.executionResult?.output || 'No output' }}</pre>\n\n <div v-if=\"message.executionResult?.logs?.length\" class=\"mt-3 pt-3 border-t border-gray-200\">\n <div class=\"text-xs font-semibold text-gray-600 uppercase tracking-wide\">Console</div>\n <pre class=\"mt-1 bg-gray-100 text-gray-900 p-3 rounded whitespace-pre-wrap overflow-x-auto max-h-[280px]\">{{ message.executionResult.logs }}</pre>\n </div>\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";
|
|
609
609
|
|
|
610
610
|
/***/ }),
|
|
611
611
|
|
|
@@ -616,6 +616,7 @@ module.exports = "<div class=\"relative border rounded bg-gray-100 text-black te
|
|
|
616
616
|
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
617
617
|
|
|
618
618
|
"use strict";
|
|
619
|
+
/* global CodeMirror, Prism */
|
|
619
620
|
|
|
620
621
|
|
|
621
622
|
const api = __webpack_require__(/*! ../../api */ "./frontend/src/api.js");
|
|
@@ -635,7 +636,10 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
635
636
|
newDashboardTitle: '',
|
|
636
637
|
dashboardCode: '',
|
|
637
638
|
createError: null,
|
|
638
|
-
dashboardEditor: null
|
|
639
|
+
dashboardEditor: null,
|
|
640
|
+
isEditing: false,
|
|
641
|
+
codeEditor: null,
|
|
642
|
+
editedScript: null
|
|
639
643
|
};
|
|
640
644
|
},
|
|
641
645
|
computed: {
|
|
@@ -644,13 +648,27 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
644
648
|
}
|
|
645
649
|
},
|
|
646
650
|
methods: {
|
|
647
|
-
async executeScript(
|
|
651
|
+
async executeScript() {
|
|
652
|
+
let scriptToRun = this.script;
|
|
653
|
+
if (this.isEditing) {
|
|
654
|
+
scriptToRun = this.codeEditor ? this.codeEditor.getValue() : this.editedScript;
|
|
655
|
+
}
|
|
656
|
+
this.editedScript = scriptToRun;
|
|
648
657
|
const { chatMessage } = await api.ChatMessage.executeScript({
|
|
649
|
-
chatMessageId: message._id,
|
|
650
|
-
script
|
|
658
|
+
chatMessageId: this.message._id,
|
|
659
|
+
script: scriptToRun
|
|
651
660
|
});
|
|
652
|
-
message.executionResult = chatMessage.executionResult;
|
|
661
|
+
this.message.executionResult = chatMessage.executionResult;
|
|
662
|
+
this.message.script = chatMessage.script;
|
|
663
|
+
this.message.content = chatMessage.content;
|
|
664
|
+
this.editedScript = chatMessage.script;
|
|
665
|
+
if (this.isEditing) {
|
|
666
|
+
this.finishEditing();
|
|
667
|
+
} else {
|
|
668
|
+
this.highlightCode();
|
|
669
|
+
}
|
|
653
670
|
this.activeTab = 'output';
|
|
671
|
+
return chatMessage;
|
|
654
672
|
},
|
|
655
673
|
openDetailModal() {
|
|
656
674
|
this.showDetailModal = true;
|
|
@@ -677,6 +695,52 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
677
695
|
toggleDropdown() {
|
|
678
696
|
this.showDropdown = !this.showDropdown;
|
|
679
697
|
},
|
|
698
|
+
startEditing() {
|
|
699
|
+
this.isEditing = true;
|
|
700
|
+
this.editedScript = this.script;
|
|
701
|
+
this.$nextTick(() => {
|
|
702
|
+
if (!this.$refs.scriptEditor) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
this.$refs.scriptEditor.value = this.editedScript;
|
|
706
|
+
if (typeof CodeMirror === 'undefined') {
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
this.destroyCodeMirror();
|
|
710
|
+
this.codeEditor = CodeMirror.fromTextArea(this.$refs.scriptEditor, {
|
|
711
|
+
mode: 'javascript',
|
|
712
|
+
lineNumbers: true,
|
|
713
|
+
smartIndent: false
|
|
714
|
+
});
|
|
715
|
+
});
|
|
716
|
+
},
|
|
717
|
+
cancelEditing() {
|
|
718
|
+
this.isEditing = false;
|
|
719
|
+
this.destroyCodeMirror();
|
|
720
|
+
this.editedScript = this.script;
|
|
721
|
+
this.highlightCode();
|
|
722
|
+
},
|
|
723
|
+
finishEditing() {
|
|
724
|
+
this.isEditing = false;
|
|
725
|
+
this.destroyCodeMirror();
|
|
726
|
+
this.highlightCode();
|
|
727
|
+
},
|
|
728
|
+
destroyCodeMirror() {
|
|
729
|
+
if (this.codeEditor) {
|
|
730
|
+
this.codeEditor.toTextArea();
|
|
731
|
+
this.codeEditor = null;
|
|
732
|
+
}
|
|
733
|
+
},
|
|
734
|
+
handleScriptInput(event) {
|
|
735
|
+
this.editedScript = event?.target?.value || '';
|
|
736
|
+
},
|
|
737
|
+
highlightCode() {
|
|
738
|
+
this.$nextTick(() => {
|
|
739
|
+
if (this.$refs.code) {
|
|
740
|
+
Prism.highlightElement(this.$refs.code);
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
},
|
|
680
744
|
handleBodyClick(event) {
|
|
681
745
|
const dropdown = this.$refs.dropdown;
|
|
682
746
|
if (dropdown && typeof dropdown.contains === 'function' && !dropdown.contains(event.target)) {
|
|
@@ -701,11 +765,22 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
701
765
|
this.$router.push('/dashboard/' + dashboard._id);
|
|
702
766
|
},
|
|
703
767
|
async copyOutput() {
|
|
704
|
-
|
|
768
|
+
const executionResult = this.message.executionResult || {};
|
|
769
|
+
let output = executionResult.output;
|
|
705
770
|
if (output != null && typeof output === 'object') {
|
|
706
771
|
output = JSON.stringify(output, null, 2);
|
|
707
772
|
}
|
|
708
|
-
|
|
773
|
+
|
|
774
|
+
const logs = executionResult.logs;
|
|
775
|
+
const parts = [];
|
|
776
|
+
if (output != null) {
|
|
777
|
+
parts.push(output);
|
|
778
|
+
}
|
|
779
|
+
if (logs) {
|
|
780
|
+
parts.push(logs);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
await navigator.clipboard.writeText(parts.join('\n\n'));
|
|
709
784
|
vanillatoasts.create({
|
|
710
785
|
title: 'Code output copied!',
|
|
711
786
|
type: 'success',
|
|
@@ -721,18 +796,25 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
721
796
|
this.dashboardEditor.toTextArea();
|
|
722
797
|
this.dashboardEditor = null;
|
|
723
798
|
}
|
|
799
|
+
},
|
|
800
|
+
script(newScript) {
|
|
801
|
+
if (!this.isEditing) {
|
|
802
|
+
this.editedScript = newScript;
|
|
803
|
+
this.highlightCode();
|
|
804
|
+
}
|
|
724
805
|
}
|
|
725
806
|
},
|
|
726
807
|
mounted() {
|
|
727
|
-
|
|
808
|
+
this.highlightCode();
|
|
728
809
|
this.$nextTick(() => {
|
|
729
810
|
document.body.addEventListener('click', this.handleBodyClick);
|
|
730
811
|
});
|
|
731
|
-
if (this.message.executionResult?.output) {
|
|
812
|
+
if (this.message.executionResult?.output || this.message.executionResult?.logs) {
|
|
732
813
|
this.activeTab = 'output';
|
|
733
814
|
}
|
|
734
815
|
},
|
|
735
816
|
unmounted() {
|
|
817
|
+
this.destroyCodeMirror();
|
|
736
818
|
document.body.removeEventListener('click', this.handleBodyClick);
|
|
737
819
|
}
|
|
738
820
|
});
|
|
@@ -4233,7 +4315,7 @@ module.exports = ".models {\n position: relative;\n display: flex;\n flex-dir
|
|
|
4233
4315
|
/***/ ((module) => {
|
|
4234
4316
|
|
|
4235
4317
|
"use strict";
|
|
4236
|
-
module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <div class=\"fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10\" @click=\"hideSidebar = false\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"#5f6368\"><path d=\"M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z\"/></svg>\n </div>\n <aside class=\"bg-white border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-48 fixed lg:relative shrink-0\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-48' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-48 overflow-x-hidden\">\n <div class=\"p-4 font-bold text-lg\">Models</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none\"\n aria-label=\"Close sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"currentColor\"><path d=\"M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z\"/></svg>\n </button>\n </div>\n <nav class=\"flex flex-1 flex-col\">\n <ul role=\"list\" class=\"flex flex-1 flex-col gap-y-7\">\n <li>\n <ul role=\"list\">\n <li v-for=\"model in models\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"block truncate rounded-md py-2 pr-2 pl-2 text-sm font-semibold text-gray-700\"\n :class=\"model === currentModel ? 'bg-ultramarine-100 font-bold' : 'hover:bg-ultramarine-100'\">\n {{model}}\n </router-link>\n </li>\n </ul>\n </li>\n </ul>\n </nav>\n </aside>\n <div class=\"documents\" ref=\"documentsList\">\n <div class=\"relative h-[42px] z-10\">\n <div class=\"documents-menu\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <form @submit.prevent=\"search\" class=\"relative flex-grow m-0\">\n <input ref=\"searchInput\" class=\"w-full font-mono rounded-md p-1 border border-gray-300 outline-gray-300 text-lg focus:ring-1 focus:ring-ultramarine-200 focus:ring-offset-0 focus:outline-none\" type=\"text\" placeholder=\"Filter\" v-model=\"searchText\" @click=\"initFilter\" @input=\"updateAutocomplete\" @keydown=\"handleKeyDown\" />\n <ul v-if=\"autocompleteSuggestions.length\" class=\"absolute z-[9999] bg-white border border-gray-300 rounded mt-1 w-full max-h-40 overflow-y-auto shadow\">\n <li v-for=\"(suggestion, index) in autocompleteSuggestions\" :key=\"suggestion\" class=\"px-2 py-1 cursor-pointer\" :class=\"{ 'bg-ultramarine-100': index === autocompleteIndex }\" @mousedown.prevent=\"applySuggestion(index)\">{{ suggestion }}</li>\n </ul>\n </form>\n <div>\n <span v-if=\"numDocuments == null\">Loading ...</span>\n <span v-else-if=\"typeof numDocuments === 'number'\">{{numDocuments === 1 ? numDocuments+ ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"shouldShowExportModal = true\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n 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\">\n Export\n </button>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{ 'bg-gray-500 ring-inset ring-2 ring-gray-300 hover:bg-gray-600': selectMultiple, 'bg-ultramarine-600 hover:bg-ultramarine-500' : !selectMultiple }\"\n class=\"rounded px-2 py-2 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n >\n {{ selectMultiple ? 'Cancel' : 'Select' }}\n </button>\n <button\n v-show=\"selectMultiple\"\n @click=\"shouldShowUpdateMultipleModal=true;\"\n type=\"button\"\n class=\"rounded bg-green-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\"\n >\n Update\n </button>\n <button\n @click=\"shouldShowDeleteMultipleModal=true;\"\n type=\"button\"\n v-show=\"selectMultiple\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500\"\n >\n Delete\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n 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\">\n Indexes\n </button>\n <button\n @click=\"shouldShowCreateModal = true;\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n 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\">\n Create\n </button>\n <button\n @click=\"openFieldSelection\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n 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\">\n Fields\n </button>\n <span class=\"isolate inline-flex rounded-md shadow-sm\">\n <button\n @click=\"setOutputType('table')\"\n type=\"button\"\n class=\"relative inline-flex items-center rounded-none rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/table.svg\">\n </button>\n <button\n @click=\"setOutputType('json')\"\n type=\"button\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n </span>\n </div>\n </div>\n </div>\n <div class=\"documents-container relative\">\n <table v-if=\"outputType === 'table'\">\n <thead>\n <th v-for=\"path in filteredPaths\" @click=\"clickFilter(path.path)\" class=\"cursor-pointer\">\n {{path.path}}\n <span class=\"path-type\">\n ({{(path.instance || 'unknown')}})\n </span>\n <span class=\"sort-arrow\" @click=\"sortDocs(1, path.path)\">{{sortBy[path.path] == 1 ? 'X' : '↑'}}</span>\n <span class=\"sort-arrow\" @click=\"sortDocs(-1, path.path)\">{{sortBy[path.path] == -1 ? 'X' : '↓'}}</span>\n </th>\n </thead>\n <tbody>\n <tr v-for=\"document in documents\" @click=\"handleDocumentClick(document, $event)\" :key=\"document._id\">\n <td v-for=\"schemaPath in filteredPaths\" :class=\"{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\">\n </component>\n </td>\n </tr>\n </tbody>\n </table>\n <div v-if=\"outputType === 'json'\" class=\"flex flex-col space-y-6\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative transition-colors',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:bg-slate-100'\n ]\"\n >\n <button\n type=\"button\"\n class=\"absolute top-2 right-2 z-10 inline-flex items-center rounded bg-ultramarine-600 px-2 py-1 text-xs font-semibold text-white shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n @click.stop=\"openDocument(document)\"\n >\n Open this Document\n </button>\n <list-json :value=\"filterDocument(document)\">\n </list-json>\n </div>\n </div>\n <div v-if=\"status === 'loading'\" class=\"loader\">\n <img src=\"images/loader.gif\">\n </div>\n </div>\n </div>\n <modal v-if=\"shouldShowExportModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowExportModal = false\">×</div>\n <export-query-results\n :schemaPaths=\"schemaPaths\"\n :search-text=\"searchText\"\n :currentModel=\"currentModel\"\n @done=\"shouldShowExportModal = false\">\n </export-query-results>\n </template>\n </modal>\n <modal v-if=\"shouldShowIndexModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowIndexModal = false\">×</div>\n <div class=\"text-xl font-bold mb-2\">Indexes</div>\n <div v-for=\"index in mongoDBIndexes\" class=\"w-full flex items-center\">\n <div class=\"grow shrink text-left flex justify-between items-center\" v-if=\"index.name != '_id_'\">\n <div>\n <div class=\"font-bold\">{{ index.name }}</div>\n <div class=\"text-sm font-mono\">{{ JSON.stringify(index.key) }}</div>\n </div>\n <div>\n <async-button\n type=\"button\"\n @click=\"dropIndex(index.name)\"\n class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600 disabled:bg-gray-400 disabled:cursor-not-allowed\">\n Drop\n </async-button>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowFieldModal = false; selectedPaths = [...filteredPaths];\">×</div>\n <div v-for=\"(path, index) in schemaPaths\" :key=\"index\" class=\"w-5 flex items-center\">\n <input class=\"mt-0 h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-600 accent-sky-600\" type=\"checkbox\" :id=\"'path.path'+index\" @change=\"addOrRemove(path)\" :value=\"path.path\" :checked=\"isSelected(path.path)\" />\n <div class=\"ml-2 text-gray-700 grow shrink text-left\">\n <label :for=\"'path.path' + index\">{{path.path}}</label>\n </div>\n </div>\n <div class=\"mt-4 flex gap-2\">\n <button type=\"button\" @click=\"filterDocuments()\" 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\">Filter Selection</button>\n <button type=\"button\" @click=\"selectAll()\" class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">Select All</button>\n <button type=\"button\" @click=\"deselectAll()\" class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">Deselect All</button>\n <button type=\"button\" @click=\"resetDocuments()\" class=\"rounded-md bg-gray-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\" >Cancel</button>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">×</div>\n <create-document :currentModel=\"currentModel\" :paths=\"schemaPaths\" @close=\"closeCreationModal\"></create-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowUpdateMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowUpdateMultipleModal = false;\">×</div>\n <update-document :currentModel=\"currentModel\" :document=\"selectedDocuments\" :multiple=\"true\" @update=\"updateDocuments\" @close=\"shouldShowUpdateMultipleModal=false;\"></update-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteMultipleModal = false;\">×</div>\n <h2>Are you sure you want to delete {{selectedDocuments.length}} documents?</h2>\n <div>\n <list-json :value=\"selectedDocuments\"></list-json>\n </div>\n <div class=\"flex gap-4\">\n <async-button @click=\"deleteDocuments\" class=\"rounded bg-red-500 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n Confirm\n </async-button>\n <button @click=\"shouldShowDeleteMultipleModal = false;\" class=\"rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
4318
|
+
module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <div class=\"fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10\" @click=\"hideSidebar = false\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"#5f6368\"><path d=\"M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z\"/></svg>\n </div>\n <aside class=\"bg-white border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-48 fixed lg:relative shrink-0\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-48' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-48 overflow-x-hidden\">\n <div class=\"p-4 font-bold text-lg\">Models</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none\"\n aria-label=\"Close sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"currentColor\"><path d=\"M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z\"/></svg>\n </button>\n </div>\n <nav class=\"flex flex-1 flex-col\">\n <ul role=\"list\" class=\"flex flex-1 flex-col gap-y-7\">\n <li>\n <ul role=\"list\">\n <li v-for=\"model in models\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"block truncate rounded-md py-2 pr-2 pl-2 text-sm font-semibold text-gray-700\"\n :class=\"model === currentModel ? 'bg-ultramarine-100 font-bold' : 'hover:bg-ultramarine-100'\">\n {{model}}\n </router-link>\n </li>\n </ul>\n </li>\n </ul>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100\">\n No models found\n </div>\n </nav>\n </aside>\n <div class=\"documents\" ref=\"documentsList\">\n <div class=\"relative h-[42px] z-10\">\n <div class=\"documents-menu\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <form @submit.prevent=\"search\" class=\"relative flex-grow m-0\">\n <input ref=\"searchInput\" class=\"w-full font-mono rounded-md p-1 border border-gray-300 outline-gray-300 text-lg focus:ring-1 focus:ring-ultramarine-200 focus:ring-offset-0 focus:outline-none\" type=\"text\" placeholder=\"Filter\" v-model=\"searchText\" @click=\"initFilter\" @input=\"updateAutocomplete\" @keydown=\"handleKeyDown\" />\n <ul v-if=\"autocompleteSuggestions.length\" class=\"absolute z-[9999] bg-white border border-gray-300 rounded mt-1 w-full max-h-40 overflow-y-auto shadow\">\n <li v-for=\"(suggestion, index) in autocompleteSuggestions\" :key=\"suggestion\" class=\"px-2 py-1 cursor-pointer\" :class=\"{ 'bg-ultramarine-100': index === autocompleteIndex }\" @mousedown.prevent=\"applySuggestion(index)\">{{ suggestion }}</li>\n </ul>\n </form>\n <div>\n <span v-if=\"numDocuments == null\">Loading ...</span>\n <span v-else-if=\"typeof numDocuments === 'number'\">{{numDocuments === 1 ? numDocuments+ ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"shouldShowExportModal = true\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n 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\">\n Export\n </button>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{ 'bg-gray-500 ring-inset ring-2 ring-gray-300 hover:bg-gray-600': selectMultiple, 'bg-ultramarine-600 hover:bg-ultramarine-500' : !selectMultiple }\"\n class=\"rounded px-2 py-2 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n >\n {{ selectMultiple ? 'Cancel' : 'Select' }}\n </button>\n <button\n v-show=\"selectMultiple\"\n @click=\"shouldShowUpdateMultipleModal=true;\"\n type=\"button\"\n class=\"rounded bg-green-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\"\n >\n Update\n </button>\n <button\n @click=\"shouldShowDeleteMultipleModal=true;\"\n type=\"button\"\n v-show=\"selectMultiple\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500\"\n >\n Delete\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n 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\">\n Indexes\n </button>\n <button\n @click=\"shouldShowCreateModal = true;\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n 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\">\n Create\n </button>\n <button\n @click=\"openFieldSelection\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n 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\">\n Fields\n </button>\n <span class=\"isolate inline-flex rounded-md shadow-sm\">\n <button\n @click=\"setOutputType('table')\"\n type=\"button\"\n class=\"relative inline-flex items-center rounded-none rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/table.svg\">\n </button>\n <button\n @click=\"setOutputType('json')\"\n type=\"button\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n </span>\n </div>\n </div>\n </div>\n <div class=\"documents-container relative\">\n <div v-if=\"error\">\n <div class=\"bg-red-100 border border-red-400 text-red-700 px-4 py-3 relative m-4 rounded-md\" role=\"alert\">\n <span class=\"block font-bold\">Error</span>\n <span class=\"block\">{{ error }}</span>\n </div>\n </div>\n <table v-else-if=\"outputType === 'table'\">\n <thead>\n <th v-for=\"path in filteredPaths\" @click=\"clickFilter(path.path)\" class=\"cursor-pointer\">\n {{path.path}}\n <span class=\"path-type\">\n ({{(path.instance || 'unknown')}})\n </span>\n <span class=\"sort-arrow\" @click=\"sortDocs(1, path.path)\">{{sortBy[path.path] == 1 ? 'X' : '↑'}}</span>\n <span class=\"sort-arrow\" @click=\"sortDocs(-1, path.path)\">{{sortBy[path.path] == -1 ? 'X' : '↓'}}</span>\n </th>\n </thead>\n <tbody>\n <tr v-for=\"document in documents\" @click=\"handleDocumentClick(document, $event)\" :key=\"document._id\">\n <td v-for=\"schemaPath in filteredPaths\" :class=\"{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\">\n </component>\n </td>\n </tr>\n </tbody>\n </table>\n <div v-else-if=\"outputType === 'json'\" class=\"flex flex-col space-y-6\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative transition-colors',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:bg-slate-100'\n ]\"\n >\n <button\n type=\"button\"\n class=\"absolute top-2 right-2 z-10 inline-flex items-center rounded bg-ultramarine-600 px-2 py-1 text-xs font-semibold text-white shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n @click.stop=\"openDocument(document)\"\n >\n Open this Document\n </button>\n <list-json :value=\"filterDocument(document)\">\n </list-json>\n </div>\n </div>\n <div v-if=\"status === 'loading'\" class=\"loader\">\n <img src=\"images/loader.gif\">\n </div>\n </div>\n </div>\n <modal v-if=\"shouldShowExportModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowExportModal = false\">×</div>\n <export-query-results\n :schemaPaths=\"schemaPaths\"\n :search-text=\"searchText\"\n :currentModel=\"currentModel\"\n @done=\"shouldShowExportModal = false\">\n </export-query-results>\n </template>\n </modal>\n <modal v-if=\"shouldShowIndexModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowIndexModal = false\">×</div>\n <div class=\"text-xl font-bold mb-2\">Indexes</div>\n <div v-for=\"index in mongoDBIndexes\" class=\"w-full flex items-center\">\n <div class=\"grow shrink text-left flex justify-between items-center\" v-if=\"index.name != '_id_'\">\n <div>\n <div class=\"font-bold\">{{ index.name }}</div>\n <div class=\"text-sm font-mono\">{{ JSON.stringify(index.key) }}</div>\n </div>\n <div>\n <async-button\n type=\"button\"\n @click=\"dropIndex(index.name)\"\n class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600 disabled:bg-gray-400 disabled:cursor-not-allowed\">\n Drop\n </async-button>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowFieldModal = false; selectedPaths = [...filteredPaths];\">×</div>\n <div v-for=\"(path, index) in schemaPaths\" :key=\"index\" class=\"w-5 flex items-center\">\n <input class=\"mt-0 h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-600 accent-sky-600\" type=\"checkbox\" :id=\"'path.path'+index\" @change=\"addOrRemove(path)\" :value=\"path.path\" :checked=\"isSelected(path.path)\" />\n <div class=\"ml-2 text-gray-700 grow shrink text-left\">\n <label :for=\"'path.path' + index\">{{path.path}}</label>\n </div>\n </div>\n <div class=\"mt-4 flex gap-2\">\n <button type=\"button\" @click=\"filterDocuments()\" 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\">Filter Selection</button>\n <button type=\"button\" @click=\"selectAll()\" class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">Select All</button>\n <button type=\"button\" @click=\"deselectAll()\" class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">Deselect All</button>\n <button type=\"button\" @click=\"resetDocuments()\" class=\"rounded-md bg-gray-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\" >Cancel</button>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">×</div>\n <create-document :currentModel=\"currentModel\" :paths=\"schemaPaths\" @close=\"closeCreationModal\"></create-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowUpdateMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowUpdateMultipleModal = false;\">×</div>\n <update-document :currentModel=\"currentModel\" :document=\"selectedDocuments\" :multiple=\"true\" @update=\"updateDocuments\" @close=\"shouldShowUpdateMultipleModal=false;\"></update-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteMultipleModal = false;\">×</div>\n <h2>Are you sure you want to delete {{selectedDocuments.length}} documents?</h2>\n <div>\n <list-json :value=\"selectedDocuments\"></list-json>\n </div>\n <div class=\"flex gap-4\">\n <async-button @click=\"deleteDocuments\" class=\"rounded bg-red-500 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n Confirm\n </async-button>\n <button @click=\"shouldShowDeleteMultipleModal = false;\" class=\"rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
4237
4319
|
|
|
4238
4320
|
/***/ }),
|
|
4239
4321
|
|
|
@@ -4322,7 +4404,8 @@ module.exports = app => app.component('models', {
|
|
|
4322
4404
|
interval: null,
|
|
4323
4405
|
outputType: 'table', // json, table
|
|
4324
4406
|
hideSidebar: null,
|
|
4325
|
-
lastSelectedIndex: null
|
|
4407
|
+
lastSelectedIndex: null,
|
|
4408
|
+
error: null
|
|
4326
4409
|
}),
|
|
4327
4410
|
created() {
|
|
4328
4411
|
this.currentModel = this.model;
|
|
@@ -4338,10 +4421,18 @@ module.exports = app => app.component('models', {
|
|
|
4338
4421
|
document.addEventListener('scroll', this.onScroll, true);
|
|
4339
4422
|
this.onPopState = () => this.initSearchFromUrl();
|
|
4340
4423
|
window.addEventListener('popstate', this.onPopState, true);
|
|
4341
|
-
|
|
4424
|
+
const { models, readyState } = await api.Model.listModels();
|
|
4425
|
+
this.models = models;
|
|
4342
4426
|
if (this.currentModel == null && this.models.length > 0) {
|
|
4343
4427
|
this.currentModel = this.models[0];
|
|
4344
4428
|
}
|
|
4429
|
+
if (this.models.length === 0) {
|
|
4430
|
+
this.status = 'loaded';
|
|
4431
|
+
this.numDocuments = 0;
|
|
4432
|
+
if (readyState === 0) {
|
|
4433
|
+
this.error = 'No models found and Mongoose is not connected. Check our documentation for more information.';
|
|
4434
|
+
}
|
|
4435
|
+
}
|
|
4345
4436
|
|
|
4346
4437
|
await this.initSearchFromUrl();
|
|
4347
4438
|
},
|
|
@@ -16276,7 +16367,7 @@ module.exports = function stringToParts(str) {
|
|
|
16276
16367
|
/***/ ((module) => {
|
|
16277
16368
|
|
|
16278
16369
|
"use strict";
|
|
16279
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.
|
|
16370
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.140","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"},"license":"Apache-2.0","dependencies":{"archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.1.0","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","dedent":"^1.6.0","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"}}');
|
|
16280
16371
|
|
|
16281
16372
|
/***/ })
|
|
16282
16373
|
|
package/frontend/public/tw.css
CHANGED
|
@@ -703,6 +703,10 @@ video {
|
|
|
703
703
|
margin: 0px;
|
|
704
704
|
}
|
|
705
705
|
|
|
706
|
+
.m-4 {
|
|
707
|
+
margin: 1rem;
|
|
708
|
+
}
|
|
709
|
+
|
|
706
710
|
.-mx-4 {
|
|
707
711
|
margin-left: -1rem;
|
|
708
712
|
margin-right: -1rem;
|
|
@@ -817,6 +821,10 @@ video {
|
|
|
817
821
|
margin-top: 0.5rem;
|
|
818
822
|
}
|
|
819
823
|
|
|
824
|
+
.mt-3 {
|
|
825
|
+
margin-top: 0.75rem;
|
|
826
|
+
}
|
|
827
|
+
|
|
820
828
|
.mt-4 {
|
|
821
829
|
margin-top: 1rem;
|
|
822
830
|
}
|
|
@@ -940,6 +948,10 @@ video {
|
|
|
940
948
|
height: 42px;
|
|
941
949
|
}
|
|
942
950
|
|
|
951
|
+
.h-\[45vh\] {
|
|
952
|
+
height: 45vh;
|
|
953
|
+
}
|
|
954
|
+
|
|
943
955
|
.h-full {
|
|
944
956
|
height: 100%;
|
|
945
957
|
}
|
|
@@ -952,6 +964,10 @@ video {
|
|
|
952
964
|
max-height: 10rem;
|
|
953
965
|
}
|
|
954
966
|
|
|
967
|
+
.max-h-\[280px\] {
|
|
968
|
+
max-height: 280px;
|
|
969
|
+
}
|
|
970
|
+
|
|
955
971
|
.max-h-\[50vh\] {
|
|
956
972
|
max-height: 50vh;
|
|
957
973
|
}
|
|
@@ -1455,6 +1471,11 @@ video {
|
|
|
1455
1471
|
border-color: rgb(209 213 219 / var(--tw-border-opacity));
|
|
1456
1472
|
}
|
|
1457
1473
|
|
|
1474
|
+
.border-red-400 {
|
|
1475
|
+
--tw-border-opacity: 1;
|
|
1476
|
+
border-color: rgb(248 113 113 / var(--tw-border-opacity));
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1458
1479
|
.border-red-500 {
|
|
1459
1480
|
--tw-border-opacity: 1;
|
|
1460
1481
|
border-color: rgb(239 68 68 / var(--tw-border-opacity));
|
|
@@ -1574,6 +1595,11 @@ video {
|
|
|
1574
1595
|
background-color: rgb(219 39 119 / var(--tw-bg-opacity));
|
|
1575
1596
|
}
|
|
1576
1597
|
|
|
1598
|
+
.bg-red-100 {
|
|
1599
|
+
--tw-bg-opacity: 1;
|
|
1600
|
+
background-color: rgb(254 226 226 / var(--tw-bg-opacity));
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1577
1603
|
.bg-red-300 {
|
|
1578
1604
|
--tw-bg-opacity: 1;
|
|
1579
1605
|
background-color: rgb(252 165 165 / var(--tw-bg-opacity));
|
|
@@ -1804,6 +1830,10 @@ video {
|
|
|
1804
1830
|
padding-top: 0.25rem;
|
|
1805
1831
|
}
|
|
1806
1832
|
|
|
1833
|
+
.pt-3 {
|
|
1834
|
+
padding-top: 0.75rem;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1807
1837
|
.pt-4 {
|
|
1808
1838
|
padding-top: 1rem;
|
|
1809
1839
|
}
|
|
@@ -1883,6 +1913,10 @@ video {
|
|
|
1883
1913
|
font-weight: 600;
|
|
1884
1914
|
}
|
|
1885
1915
|
|
|
1916
|
+
.uppercase {
|
|
1917
|
+
text-transform: uppercase;
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1886
1920
|
.capitalize {
|
|
1887
1921
|
text-transform: capitalize;
|
|
1888
1922
|
}
|
|
@@ -1903,6 +1937,10 @@ video {
|
|
|
1903
1937
|
line-height: 1.25;
|
|
1904
1938
|
}
|
|
1905
1939
|
|
|
1940
|
+
.tracking-wide {
|
|
1941
|
+
letter-spacing: 0.025em;
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1906
1944
|
.text-amber-600 {
|
|
1907
1945
|
--tw-text-opacity: 1;
|
|
1908
1946
|
color: rgb(217 119 6 / var(--tw-text-opacity));
|
|
@@ -2288,6 +2326,11 @@ video {
|
|
|
2288
2326
|
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
|
|
2289
2327
|
}
|
|
2290
2328
|
|
|
2329
|
+
.hover\:bg-gray-400:hover {
|
|
2330
|
+
--tw-bg-opacity: 1;
|
|
2331
|
+
background-color: rgb(156 163 175 / var(--tw-bg-opacity));
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2291
2334
|
.hover\:bg-gray-50:hover {
|
|
2292
2335
|
--tw-bg-opacity: 1;
|
|
2293
2336
|
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
|
|
@@ -29,9 +29,18 @@
|
|
|
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
|
+
v-if="activeTab === 'code' && !isEditing"
|
|
34
|
+
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"
|
|
35
|
+
@click.stop="startEditing">
|
|
36
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" viewBox="0 0 20 20" fill="currentColor">
|
|
37
|
+
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM4 13.5V16h2.5l7.086-7.086-2.828-2.828L4 13.5z" />
|
|
38
|
+
</svg>
|
|
39
|
+
</button>
|
|
32
40
|
<async-button
|
|
41
|
+
v-if="!isEditing"
|
|
33
42
|
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
|
-
@click="executeScript
|
|
43
|
+
@click="executeScript">
|
|
35
44
|
Execute
|
|
36
45
|
</async-button>
|
|
37
46
|
<div class="relative ml-1" ref="dropdown">
|
|
@@ -60,12 +69,36 @@
|
|
|
60
69
|
</div>
|
|
61
70
|
</div>
|
|
62
71
|
|
|
63
|
-
<
|
|
72
|
+
<div class="p-3 max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto" v-show="activeTab === 'code'">
|
|
73
|
+
<div v-if="isEditing" class="flex flex-col space-y-2">
|
|
74
|
+
<div class="border border-gray-200">
|
|
75
|
+
<textarea ref="scriptEditor" class="w-full h-[45vh]" @input="handleScriptInput"></textarea>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="flex justify-end gap-2">
|
|
78
|
+
<button
|
|
79
|
+
class="px-2 py-1 text-xs bg-gray-300 text-gray-800 border-none rounded cursor-pointer hover:bg-gray-400 transition-colors"
|
|
80
|
+
@click.stop="cancelEditing">
|
|
81
|
+
Cancel
|
|
82
|
+
</button>
|
|
83
|
+
<async-button
|
|
84
|
+
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"
|
|
85
|
+
@click="executeScript">
|
|
86
|
+
Execute
|
|
87
|
+
</async-button>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
<pre v-else class="whitespace-pre-wrap"><code v-text="script" ref="code" :class="'language-' + language"></code></pre>
|
|
91
|
+
</div>
|
|
64
92
|
|
|
65
93
|
<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'">
|
|
66
94
|
<dashboard-chart v-if="message.executionResult?.output?.$chart" :value="message.executionResult?.output" />
|
|
67
95
|
<dashboard-map v-else-if="message.executionResult?.output?.$featureCollection" :value="message.executionResult?.output" />
|
|
68
96
|
<pre v-else>{{ message.executionResult?.output || 'No output' }}</pre>
|
|
97
|
+
|
|
98
|
+
<div v-if="message.executionResult?.logs?.length" class="mt-3 pt-3 border-t border-gray-200">
|
|
99
|
+
<div class="text-xs font-semibold text-gray-600 uppercase tracking-wide">Console</div>
|
|
100
|
+
<pre class="mt-1 bg-gray-100 text-gray-900 p-3 rounded whitespace-pre-wrap overflow-x-auto max-h-[280px]">{{ message.executionResult.logs }}</pre>
|
|
101
|
+
</div>
|
|
69
102
|
</div>
|
|
70
103
|
|
|
71
104
|
<modal ref="outputModal" v-if="showDetailModal" containerClass="!h-[90vh] !w-[90vw]">
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* global CodeMirror, Prism */
|
|
1
2
|
'use strict';
|
|
2
3
|
|
|
3
4
|
const api = require('../../api');
|
|
@@ -17,7 +18,10 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
17
18
|
newDashboardTitle: '',
|
|
18
19
|
dashboardCode: '',
|
|
19
20
|
createError: null,
|
|
20
|
-
dashboardEditor: null
|
|
21
|
+
dashboardEditor: null,
|
|
22
|
+
isEditing: false,
|
|
23
|
+
codeEditor: null,
|
|
24
|
+
editedScript: null
|
|
21
25
|
};
|
|
22
26
|
},
|
|
23
27
|
computed: {
|
|
@@ -26,13 +30,27 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
26
30
|
}
|
|
27
31
|
},
|
|
28
32
|
methods: {
|
|
29
|
-
async executeScript(
|
|
33
|
+
async executeScript() {
|
|
34
|
+
let scriptToRun = this.script;
|
|
35
|
+
if (this.isEditing) {
|
|
36
|
+
scriptToRun = this.codeEditor ? this.codeEditor.getValue() : this.editedScript;
|
|
37
|
+
}
|
|
38
|
+
this.editedScript = scriptToRun;
|
|
30
39
|
const { chatMessage } = await api.ChatMessage.executeScript({
|
|
31
|
-
chatMessageId: message._id,
|
|
32
|
-
script
|
|
40
|
+
chatMessageId: this.message._id,
|
|
41
|
+
script: scriptToRun
|
|
33
42
|
});
|
|
34
|
-
message.executionResult = chatMessage.executionResult;
|
|
43
|
+
this.message.executionResult = chatMessage.executionResult;
|
|
44
|
+
this.message.script = chatMessage.script;
|
|
45
|
+
this.message.content = chatMessage.content;
|
|
46
|
+
this.editedScript = chatMessage.script;
|
|
47
|
+
if (this.isEditing) {
|
|
48
|
+
this.finishEditing();
|
|
49
|
+
} else {
|
|
50
|
+
this.highlightCode();
|
|
51
|
+
}
|
|
35
52
|
this.activeTab = 'output';
|
|
53
|
+
return chatMessage;
|
|
36
54
|
},
|
|
37
55
|
openDetailModal() {
|
|
38
56
|
this.showDetailModal = true;
|
|
@@ -59,6 +77,52 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
59
77
|
toggleDropdown() {
|
|
60
78
|
this.showDropdown = !this.showDropdown;
|
|
61
79
|
},
|
|
80
|
+
startEditing() {
|
|
81
|
+
this.isEditing = true;
|
|
82
|
+
this.editedScript = this.script;
|
|
83
|
+
this.$nextTick(() => {
|
|
84
|
+
if (!this.$refs.scriptEditor) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.$refs.scriptEditor.value = this.editedScript;
|
|
88
|
+
if (typeof CodeMirror === 'undefined') {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
this.destroyCodeMirror();
|
|
92
|
+
this.codeEditor = CodeMirror.fromTextArea(this.$refs.scriptEditor, {
|
|
93
|
+
mode: 'javascript',
|
|
94
|
+
lineNumbers: true,
|
|
95
|
+
smartIndent: false
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
cancelEditing() {
|
|
100
|
+
this.isEditing = false;
|
|
101
|
+
this.destroyCodeMirror();
|
|
102
|
+
this.editedScript = this.script;
|
|
103
|
+
this.highlightCode();
|
|
104
|
+
},
|
|
105
|
+
finishEditing() {
|
|
106
|
+
this.isEditing = false;
|
|
107
|
+
this.destroyCodeMirror();
|
|
108
|
+
this.highlightCode();
|
|
109
|
+
},
|
|
110
|
+
destroyCodeMirror() {
|
|
111
|
+
if (this.codeEditor) {
|
|
112
|
+
this.codeEditor.toTextArea();
|
|
113
|
+
this.codeEditor = null;
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
handleScriptInput(event) {
|
|
117
|
+
this.editedScript = event?.target?.value || '';
|
|
118
|
+
},
|
|
119
|
+
highlightCode() {
|
|
120
|
+
this.$nextTick(() => {
|
|
121
|
+
if (this.$refs.code) {
|
|
122
|
+
Prism.highlightElement(this.$refs.code);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
},
|
|
62
126
|
handleBodyClick(event) {
|
|
63
127
|
const dropdown = this.$refs.dropdown;
|
|
64
128
|
if (dropdown && typeof dropdown.contains === 'function' && !dropdown.contains(event.target)) {
|
|
@@ -83,11 +147,22 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
83
147
|
this.$router.push('/dashboard/' + dashboard._id);
|
|
84
148
|
},
|
|
85
149
|
async copyOutput() {
|
|
86
|
-
|
|
150
|
+
const executionResult = this.message.executionResult || {};
|
|
151
|
+
let output = executionResult.output;
|
|
87
152
|
if (output != null && typeof output === 'object') {
|
|
88
153
|
output = JSON.stringify(output, null, 2);
|
|
89
154
|
}
|
|
90
|
-
|
|
155
|
+
|
|
156
|
+
const logs = executionResult.logs;
|
|
157
|
+
const parts = [];
|
|
158
|
+
if (output != null) {
|
|
159
|
+
parts.push(output);
|
|
160
|
+
}
|
|
161
|
+
if (logs) {
|
|
162
|
+
parts.push(logs);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
await navigator.clipboard.writeText(parts.join('\n\n'));
|
|
91
166
|
vanillatoasts.create({
|
|
92
167
|
title: 'Code output copied!',
|
|
93
168
|
type: 'success',
|
|
@@ -103,18 +178,25 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
103
178
|
this.dashboardEditor.toTextArea();
|
|
104
179
|
this.dashboardEditor = null;
|
|
105
180
|
}
|
|
181
|
+
},
|
|
182
|
+
script(newScript) {
|
|
183
|
+
if (!this.isEditing) {
|
|
184
|
+
this.editedScript = newScript;
|
|
185
|
+
this.highlightCode();
|
|
186
|
+
}
|
|
106
187
|
}
|
|
107
188
|
},
|
|
108
189
|
mounted() {
|
|
109
|
-
|
|
190
|
+
this.highlightCode();
|
|
110
191
|
this.$nextTick(() => {
|
|
111
192
|
document.body.addEventListener('click', this.handleBodyClick);
|
|
112
193
|
});
|
|
113
|
-
if (this.message.executionResult?.output) {
|
|
194
|
+
if (this.message.executionResult?.output || this.message.executionResult?.logs) {
|
|
114
195
|
this.activeTab = 'output';
|
|
115
196
|
}
|
|
116
197
|
},
|
|
117
198
|
unmounted() {
|
|
199
|
+
this.destroyCodeMirror();
|
|
118
200
|
document.body.removeEventListener('click', this.handleBodyClick);
|
|
119
201
|
}
|
|
120
202
|
});
|
|
@@ -28,6 +28,9 @@
|
|
|
28
28
|
</ul>
|
|
29
29
|
</li>
|
|
30
30
|
</ul>
|
|
31
|
+
<div v-if="models.length === 0 && status === 'loaded'" class="p-2 bg-red-100">
|
|
32
|
+
No models found
|
|
33
|
+
</div>
|
|
31
34
|
</nav>
|
|
32
35
|
</aside>
|
|
33
36
|
<div class="documents" ref="documentsList">
|
|
@@ -116,7 +119,13 @@
|
|
|
116
119
|
</div>
|
|
117
120
|
</div>
|
|
118
121
|
<div class="documents-container relative">
|
|
119
|
-
<
|
|
122
|
+
<div v-if="error">
|
|
123
|
+
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 relative m-4 rounded-md" role="alert">
|
|
124
|
+
<span class="block font-bold">Error</span>
|
|
125
|
+
<span class="block">{{ error }}</span>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
<table v-else-if="outputType === 'table'">
|
|
120
129
|
<thead>
|
|
121
130
|
<th v-for="path in filteredPaths" @click="clickFilter(path.path)" class="cursor-pointer">
|
|
122
131
|
{{path.path}}
|
|
@@ -139,7 +148,7 @@
|
|
|
139
148
|
</tr>
|
|
140
149
|
</tbody>
|
|
141
150
|
</table>
|
|
142
|
-
<div v-if="outputType === 'json'" class="flex flex-col space-y-6">
|
|
151
|
+
<div v-else-if="outputType === 'json'" class="flex flex-col space-y-6">
|
|
143
152
|
<div
|
|
144
153
|
v-for="document in documents"
|
|
145
154
|
:key="document._id"
|
|
@@ -76,7 +76,8 @@ module.exports = app => app.component('models', {
|
|
|
76
76
|
interval: null,
|
|
77
77
|
outputType: 'table', // json, table
|
|
78
78
|
hideSidebar: null,
|
|
79
|
-
lastSelectedIndex: null
|
|
79
|
+
lastSelectedIndex: null,
|
|
80
|
+
error: null
|
|
80
81
|
}),
|
|
81
82
|
created() {
|
|
82
83
|
this.currentModel = this.model;
|
|
@@ -92,10 +93,18 @@ module.exports = app => app.component('models', {
|
|
|
92
93
|
document.addEventListener('scroll', this.onScroll, true);
|
|
93
94
|
this.onPopState = () => this.initSearchFromUrl();
|
|
94
95
|
window.addEventListener('popstate', this.onPopState, true);
|
|
95
|
-
|
|
96
|
+
const { models, readyState } = await api.Model.listModels();
|
|
97
|
+
this.models = models;
|
|
96
98
|
if (this.currentModel == null && this.models.length > 0) {
|
|
97
99
|
this.currentModel = this.models[0];
|
|
98
100
|
}
|
|
101
|
+
if (this.models.length === 0) {
|
|
102
|
+
this.status = 'loaded';
|
|
103
|
+
this.numDocuments = 0;
|
|
104
|
+
if (readyState === 0) {
|
|
105
|
+
this.error = 'No models found and Mongoose is not connected. Check our documentation for more information.';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
99
108
|
|
|
100
109
|
await this.initSearchFromUrl();
|
|
101
110
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.140",
|
|
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": {
|