@mongoosejs/studio 0.0.71 → 0.0.72
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 +163 -11
- package/frontend/public/images/duplicate.svg +1 -0
- package/frontend/public/tw.css +5 -0
- package/frontend/src/clone-document/clone-document.css +0 -0
- package/frontend/src/clone-document/clone-document.html +26 -0
- package/frontend/src/clone-document/clone-document.js +72 -0
- package/frontend/src/document/document.html +14 -0
- package/frontend/src/document/document.js +5 -1
- package/frontend/src/index.js +1 -0
- package/package.json +1 -1
package/frontend/public/app.js
CHANGED
|
@@ -67,7 +67,31 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
67
67
|
return client.post('', { action: 'Model.deleteDocument', ...params}).then(res => res.data);
|
|
68
68
|
},
|
|
69
69
|
exportQueryResults(params) {
|
|
70
|
-
|
|
70
|
+
const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
|
|
71
|
+
|
|
72
|
+
return fetch(window.MONGOOSE_STUDIO_CONFIG.baseURL + new URLSearchParams({ ...params, action: 'Model.exportQueryResults' }).toString(), {
|
|
73
|
+
method: 'GET',
|
|
74
|
+
headers: {
|
|
75
|
+
'Authorization': `${accessToken}`, // Set your authorization token here
|
|
76
|
+
'Accept': 'text/csv'
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
.then(response => {
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
82
|
+
}
|
|
83
|
+
return response.blob();
|
|
84
|
+
})
|
|
85
|
+
.then(blob => {
|
|
86
|
+
const blobURL = window.URL.createObjectURL(blob);
|
|
87
|
+
const anchor = document.createElement('a');
|
|
88
|
+
anchor.href = blobURL;
|
|
89
|
+
anchor.download = 'export.csv';
|
|
90
|
+
document.body.appendChild(anchor);
|
|
91
|
+
anchor.click();
|
|
92
|
+
document.body.removeChild(anchor);
|
|
93
|
+
window.URL.revokeObjectURL(blobURL);
|
|
94
|
+
});
|
|
71
95
|
},
|
|
72
96
|
getDocument: function getDocument(params) {
|
|
73
97
|
return client.post('', { action: 'Model.getDocument', ...params }).then(res => res.data);
|
|
@@ -114,12 +138,31 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
114
138
|
return client.post('/Model/deleteDocument', params).then(res => res.data);
|
|
115
139
|
},
|
|
116
140
|
exportQueryResults(params) {
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
141
|
+
const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
|
|
142
|
+
|
|
143
|
+
return fetch(window.MONGOOSE_STUDIO_CONFIG.baseURL + '/Model/exportQueryResults?' + new URLSearchParams(params).toString(), {
|
|
144
|
+
method: 'GET',
|
|
145
|
+
headers: {
|
|
146
|
+
'Authorization': `${accessToken}`, // Set your authorization token here
|
|
147
|
+
'Accept': 'text/csv'
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
.then(response => {
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
153
|
+
}
|
|
154
|
+
return response.blob();
|
|
155
|
+
})
|
|
156
|
+
.then(blob => {
|
|
157
|
+
const blobURL = window.URL.createObjectURL(blob);
|
|
158
|
+
const anchor = document.createElement('a');
|
|
159
|
+
anchor.href = blobURL;
|
|
160
|
+
anchor.download = 'export.csv';
|
|
161
|
+
document.body.appendChild(anchor);
|
|
162
|
+
anchor.click();
|
|
163
|
+
document.body.removeChild(anchor);
|
|
164
|
+
window.URL.revokeObjectURL(blobURL);
|
|
165
|
+
});
|
|
123
166
|
},
|
|
124
167
|
getDocument: function getDocument(params) {
|
|
125
168
|
return client.post('/Model/getDocument', params).then(res => res.data);
|
|
@@ -215,6 +258,88 @@ module.exports = app => app.component('async-button', {
|
|
|
215
258
|
|
|
216
259
|
/***/ }),
|
|
217
260
|
|
|
261
|
+
/***/ "./frontend/src/clone-document/clone-document.js":
|
|
262
|
+
/*!*******************************************************!*\
|
|
263
|
+
!*** ./frontend/src/clone-document/clone-document.js ***!
|
|
264
|
+
\*******************************************************/
|
|
265
|
+
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
266
|
+
|
|
267
|
+
"use strict";
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
|
|
271
|
+
|
|
272
|
+
const { BSON, EJSON } = __webpack_require__(/*! bson */ "./node_modules/bson/lib/bson.cjs");
|
|
273
|
+
|
|
274
|
+
const ObjectId = new Proxy(BSON.ObjectId, {
|
|
275
|
+
apply (target, thisArg, argumentsList) {
|
|
276
|
+
return new target(...argumentsList);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const appendCSS = __webpack_require__(/*! ../appendCSS */ "./frontend/src/appendCSS.js");
|
|
281
|
+
|
|
282
|
+
appendCSS(__webpack_require__(/*! ./clone-document.css */ "./frontend/src/clone-document/clone-document.css"));
|
|
283
|
+
|
|
284
|
+
const template = __webpack_require__(/*! ./clone-document.html */ "./frontend/src/clone-document/clone-document.html")
|
|
285
|
+
|
|
286
|
+
module.exports = app => app.component('clone-document', {
|
|
287
|
+
props: ['currentModel', 'doc', 'schemaPaths'],
|
|
288
|
+
template,
|
|
289
|
+
data: function() {
|
|
290
|
+
return {
|
|
291
|
+
documentData: '',
|
|
292
|
+
editor: null,
|
|
293
|
+
errors: []
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
methods: {
|
|
297
|
+
async cloneDocument() {
|
|
298
|
+
const data = EJSON.serialize(eval(`(${this.editor.getValue()})`));
|
|
299
|
+
const { doc } = await api.Model.createDocument({ model: this.currentModel, data }).catch(err => {
|
|
300
|
+
if (err.response?.data?.message) {
|
|
301
|
+
console.log(err.response.data);
|
|
302
|
+
const message = err.response.data.message.split(": ").slice(1).join(": ");
|
|
303
|
+
this.errors = message.split(',').map(error => {
|
|
304
|
+
return error.split(': ').slice(1).join(': ').trim();
|
|
305
|
+
})
|
|
306
|
+
throw new Error(err.response?.data?.message);
|
|
307
|
+
}
|
|
308
|
+
throw err;
|
|
309
|
+
});
|
|
310
|
+
this.errors.length = 0;
|
|
311
|
+
this.$emit('close', doc);
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
mounted: function() {
|
|
315
|
+
const pathsToClone = this.schemaPaths.map(x => x.path);
|
|
316
|
+
|
|
317
|
+
// Create a filtered version of the document data
|
|
318
|
+
const filteredDoc = {};
|
|
319
|
+
pathsToClone.forEach(path => {
|
|
320
|
+
const value = this.doc[path];
|
|
321
|
+
if (value !== undefined) {
|
|
322
|
+
filteredDoc[path] = value;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Replace _id with a new ObjectId
|
|
327
|
+
if (pathsToClone.includes('_id')) {
|
|
328
|
+
filteredDoc._id = new ObjectId();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
this.documentData = JSON.stringify(filteredDoc, null, 2);
|
|
332
|
+
this.$refs.codeEditor.value = this.documentData;
|
|
333
|
+
this.editor = CodeMirror.fromTextArea(this.$refs.codeEditor, {
|
|
334
|
+
mode: 'javascript',
|
|
335
|
+
lineNumbers: true,
|
|
336
|
+
smartIndent: false
|
|
337
|
+
});
|
|
338
|
+
},
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
/***/ }),
|
|
342
|
+
|
|
218
343
|
/***/ "./frontend/src/create-dashboard/create-dashboard.js":
|
|
219
344
|
/*!***********************************************************!*\
|
|
220
345
|
!*** ./frontend/src/create-dashboard/create-dashboard.js ***!
|
|
@@ -943,7 +1068,8 @@ module.exports = app => app.component('document', {
|
|
|
943
1068
|
editting: false,
|
|
944
1069
|
virtuals: [],
|
|
945
1070
|
shouldShowConfirmModal: false,
|
|
946
|
-
shouldShowDeleteModal: false
|
|
1071
|
+
shouldShowDeleteModal: false,
|
|
1072
|
+
shouldShowCloneModal: false
|
|
947
1073
|
}),
|
|
948
1074
|
async mounted() {
|
|
949
1075
|
window.pageState = this;
|
|
@@ -1004,6 +1130,9 @@ module.exports = app => app.component('document', {
|
|
|
1004
1130
|
});
|
|
1005
1131
|
this.$router.push({ path: `/model/${this.model}`});
|
|
1006
1132
|
}
|
|
1133
|
+
},
|
|
1134
|
+
showClonedDocument(doc) {
|
|
1135
|
+
this.$router.push({ path: `/model/${this.model}/document/${doc._id}`});
|
|
1007
1136
|
}
|
|
1008
1137
|
}
|
|
1009
1138
|
});
|
|
@@ -2937,6 +3066,28 @@ module.exports = "<button v-bind=\"attrsToBind\" :disabled=\"isDisabled\" @click
|
|
|
2937
3066
|
|
|
2938
3067
|
/***/ }),
|
|
2939
3068
|
|
|
3069
|
+
/***/ "./frontend/src/clone-document/clone-document.css":
|
|
3070
|
+
/*!********************************************************!*\
|
|
3071
|
+
!*** ./frontend/src/clone-document/clone-document.css ***!
|
|
3072
|
+
\********************************************************/
|
|
3073
|
+
/***/ ((module) => {
|
|
3074
|
+
|
|
3075
|
+
"use strict";
|
|
3076
|
+
module.exports = "";
|
|
3077
|
+
|
|
3078
|
+
/***/ }),
|
|
3079
|
+
|
|
3080
|
+
/***/ "./frontend/src/clone-document/clone-document.html":
|
|
3081
|
+
/*!*********************************************************!*\
|
|
3082
|
+
!*** ./frontend/src/clone-document/clone-document.html ***!
|
|
3083
|
+
\*********************************************************/
|
|
3084
|
+
/***/ ((module) => {
|
|
3085
|
+
|
|
3086
|
+
"use strict";
|
|
3087
|
+
module.exports = "<div>\n <div class=\"mb-2\">\n <textarea class=\"border border-gray-200 p-2 h-[300px] w-full\" ref=\"codeEditor\"></textarea>\n </div>\n <button @click=\"cloneDocument()\" class=\"rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">Submit</button>\n <div v-if=\"errors.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\">There were {{errors.length}} errors with your submission</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n <ul role=\"list\" class=\"list-disc space-y-1 pl-5\">\n <li v-for=\"error in errors\">\n {{error}}\n </li>\n </ul>\n </div>\n </div>\n </div>\n </div>\n </div>\n ";
|
|
3088
|
+
|
|
3089
|
+
/***/ }),
|
|
3090
|
+
|
|
2940
3091
|
/***/ "./frontend/src/create-dashboard/create-dashboard.html":
|
|
2941
3092
|
/*!*************************************************************!*\
|
|
2942
3093
|
!*** ./frontend/src/create-dashboard/create-dashboard.html ***!
|
|
@@ -3164,7 +3315,7 @@ module.exports = ".document {\n max-width: 1200px;\n margin-left: auto;\n mar
|
|
|
3164
3315
|
/***/ ((module) => {
|
|
3165
3316
|
|
|
3166
3317
|
"use strict";
|
|
3167
|
-
module.exports = "<div class=\"document\">\n <div class=\"document-menu\">\n <div class=\"left\">\n <button\n @click=\"$router.push('/model/' + this.model)\"\n class=\"rounded-md bg-gray-400 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n ‹ Back\n </button>\n </div>\n\n <div class=\"right\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n type=\"button\"\n 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\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n × Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n @click=\"shouldShowConfirmModal=true;\"\n type=\"button\"\n 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\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n <button\n @click=\"shouldShowDeleteModal=true;\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n type=\"button\"\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\">\n <img src=\"images/delete.svg\" class=\"inline\" /> Delete\n </button>\n </div>\n </div>\n <div v-if=\"status === 'loaded'\">\n <document-details\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"></document-details>\n <modal v-if=\"shouldShowConfirmModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowConfirmModal = false;\">×</div>\n <confirm-changes @close=\"shouldShowConfirmModal = false;\" @save=\"save\" :value=\"changes\"></confirm-changes>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowConfirmModal = false;\">×</div>\n <confirm-delete @close=\"shouldShowConfirmModal = false;\" @remove=\"remove\" :value=\"document\"></confirm-delete>\n </template>\n </modal>\n </div>\n</div>\n";
|
|
3318
|
+
module.exports = "<div class=\"document\">\n <div class=\"document-menu\">\n <div class=\"left\">\n <button\n @click=\"$router.push('/model/' + this.model)\"\n class=\"rounded-md bg-gray-400 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n ‹ Back\n </button>\n </div>\n\n <div class=\"right\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n type=\"button\"\n 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\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n × Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n @click=\"shouldShowConfirmModal=true;\"\n type=\"button\"\n 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\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n <button\n @click=\"shouldShowDeleteModal=true;\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n type=\"button\"\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\">\n <img src=\"images/delete.svg\" class=\"inline\" /> Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true;\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n type=\"button\"\n class=\"rounded-md bg-pink-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\">\n <img src=\"images/duplicate.svg\" class=\"inline\" /> Clone\n </button>\n </div>\n </div>\n <div v-if=\"status === 'loaded'\">\n <document-details\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"></document-details>\n <modal v-if=\"shouldShowConfirmModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowConfirmModal = false;\">×</div>\n <confirm-changes @close=\"shouldShowConfirmModal = false;\" @save=\"save\" :value=\"changes\"></confirm-changes>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowConfirmModal = false;\">×</div>\n <confirm-delete @close=\"shouldShowConfirmModal = false;\" @remove=\"remove\" :value=\"document\"></confirm-delete>\n </template>\n </modal>\n <modal v-if=\"shouldShowCloneModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCloneModal = false;\">×</div>\n <clone-document :currentModel=\"model\" :doc=\"document\" :schemaPaths=\"schemaPaths\" @close=\"showClonedDocument\"></clone-document>\n </template>\n </modal>\n </div>\n</div>\n";
|
|
3168
3319
|
|
|
3169
3320
|
/***/ }),
|
|
3170
3321
|
|
|
@@ -3252,7 +3403,7 @@ module.exports = "";
|
|
|
3252
3403
|
/***/ ((module) => {
|
|
3253
3404
|
|
|
3254
3405
|
"use strict";
|
|
3255
|
-
module.exports = "<div class=\"export-query-results\">\n <h2>Export as CSV</h2>\n <div>\n Choose fields to export\n </div>\n <div v-for=\"schemaPath in schemaPaths\">\n <input type=\"checkbox\" v-model=\"shouldExport[schemaPath.path]\">\n <
|
|
3406
|
+
module.exports = "<div class=\"export-query-results\">\n <h2>Export as CSV</h2>\n <div>\n Choose fields to export\n </div>\n <div v-for=\"(schemaPath,index) in schemaPaths\" class=\"w-5 flex items-center\">\n <input type=\"checkbox\" class=\"mt-0 h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-600 accent-sky-600\" v-model=\"shouldExport[schemaPath.path]\" :id=\"'schemaPath.path'+index\">\n <div class=\"ml-2 text-gray-700 grow shrink text-left\">\n <label :for=\"'schemaPath.path'+index\">{{schemaPath.path}}</label>\n </div>\n </div>\n <async-button 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\" @click=\"exportQueryResults\">Export</async-button>\n</div>";
|
|
3256
3407
|
|
|
3257
3408
|
/***/ }),
|
|
3258
3409
|
|
|
@@ -3428,7 +3579,7 @@ module.exports = ".models {\n position: relative;\n display: flex;\n flex-dir
|
|
|
3428
3579
|
/***/ ((module) => {
|
|
3429
3580
|
|
|
3430
3581
|
"use strict";
|
|
3431
|
-
module.exports = "<div class=\"models\">\n <div>\n <div class=\"flex grow flex-col gap-y-5 overflow-auto border-r border-gray-200 bg-white px-2 h-[calc(100vh-55px)] w-48\">\n <div class=\"flex font-bold font-xl mt-4 pl-2\">\n Models\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 </div>\n\n </div>\n <div class=\"documents\" ref=\"documentsList\">\n <div class=\"relative h-[42px]\">\n <div class=\"documents-menu\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <form @submit.prevent=\"search\" class=\"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 or text\" v-model=\"searchText\" />\n </form>\n <div>\n <span v-if=\"status === 'loading'\">Loading ...</span>\n <span v-if=\"status === 'loaded'\">{{numDocuments === 1 ? numDocuments+ ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"shouldShowExportModal = true\"\n type=\"button\"\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=\"openIndexModal\"\n type=\"button\"\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 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 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=\"outputType = '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=\"outputType = '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=\"$router.push('/model/' + currentModel + '/document/' + document._id)\" :key=\"document._id\">\n <td v-for=\"schemaPath in filteredPaths\">\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'\">\n <div v-for=\"document in documents\" @click=\"$router.push('/model/' + currentModel + '/document/' + document._id)\" :key=\"document._id\">\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 :filter=\"filter\"\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 <button type=\"submit\" @click=\"dropIndex()\" disabled=\"true\" 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\">Drop</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' + index\">{{path.path}}</label>\n </div>\n </div>\n <div class=\"mt-4 flex gap-2\">\n <button type=\"submit\" @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=\"submit\" @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=\"submit\" @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</div>\n";
|
|
3582
|
+
module.exports = "<div class=\"models\">\n <div>\n <div class=\"flex grow flex-col gap-y-5 overflow-auto border-r border-gray-200 bg-white px-2 h-[calc(100vh-55px)] w-48\">\n <div class=\"flex font-bold font-xl mt-4 pl-2\">\n Models\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 </div>\n\n </div>\n <div class=\"documents\" ref=\"documentsList\">\n <div class=\"relative h-[42px]\">\n <div class=\"documents-menu\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <form @submit.prevent=\"search\" class=\"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 or text\" v-model=\"searchText\" />\n </form>\n <div>\n <span v-if=\"status === 'loading'\">Loading ...</span>\n <span v-if=\"status === 'loaded'\">{{numDocuments === 1 ? numDocuments+ ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"shouldShowExportModal = true\"\n type=\"button\"\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=\"openIndexModal\"\n type=\"button\"\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 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 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=\"outputType = '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=\"outputType = '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=\"$router.push('/model/' + currentModel + '/document/' + document._id)\" :key=\"document._id\">\n <td v-for=\"schemaPath in filteredPaths\">\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'\">\n <div v-for=\"document in documents\" @click=\"$router.push('/model/' + currentModel + '/document/' + document._id)\" :key=\"document._id\">\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 :filter=\"filter\"\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 <button type=\"submit\" @click=\"dropIndex()\" disabled=\"true\" 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\">Drop</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=\"submit\" @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=\"submit\" @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=\"submit\" @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</div>\n";
|
|
3432
3583
|
|
|
3433
3584
|
/***/ }),
|
|
3434
3585
|
|
|
@@ -11313,6 +11464,7 @@ const app = Vue.createApp({
|
|
|
11313
11464
|
});
|
|
11314
11465
|
|
|
11315
11466
|
__webpack_require__(/*! ./async-button/async-button */ "./frontend/src/async-button/async-button.js")(app);
|
|
11467
|
+
__webpack_require__(/*! ./clone-document/clone-document */ "./frontend/src/clone-document/clone-document.js")(app);
|
|
11316
11468
|
__webpack_require__(/*! ./create-dashboard/create-dashboard */ "./frontend/src/create-dashboard/create-dashboard.js")(app);
|
|
11317
11469
|
__webpack_require__(/*! ./create-document/create-document */ "./frontend/src/create-document/create-document.js")(app);
|
|
11318
11470
|
__webpack_require__(/*! ./dashboards/dashboards */ "./frontend/src/dashboards/dashboards.js")(app);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFFFFF"><path d="M560-320h80v-120h120v-80H640v-120h-80v120H440v80h120v120ZM240-140Q131-178 65.5-271.5T0-480q0-115 65.5-208.5T240-820v88q-74 35-117 103T80-480q0 81 43 149t117 103v88Zm360 20q-75 0-140.5-28.5t-114-77q-48.5-48.5-77-114T240-480q0-75 28.5-140.5t77-114q48.5-48.5 114-77T600-840q75 0 140.5 28.5t114 77q48.5 48.5 77 114T960-480q0 75-28.5 140.5t-77 114q-48.5 48.5-114 77T600-120Zm0-360Zm0 280q117 0 198.5-81.5T880-480q0-117-81.5-198.5T600-760q-117 0-198.5 81.5T320-480q0 117 81.5 198.5T600-200Z"/></svg>
|
package/frontend/public/tw.css
CHANGED
|
@@ -1199,6 +1199,11 @@ video {
|
|
|
1199
1199
|
background-color: rgb(22 163 74 / var(--tw-bg-opacity));
|
|
1200
1200
|
}
|
|
1201
1201
|
|
|
1202
|
+
.bg-pink-600 {
|
|
1203
|
+
--tw-bg-opacity: 1;
|
|
1204
|
+
background-color: rgb(219 39 119 / var(--tw-bg-opacity));
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1202
1207
|
.bg-red-300 {
|
|
1203
1208
|
--tw-bg-opacity: 1;
|
|
1204
1209
|
background-color: rgb(252 165 165 / var(--tw-bg-opacity));
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<div>
|
|
2
|
+
<div class="mb-2">
|
|
3
|
+
<textarea class="border border-gray-200 p-2 h-[300px] w-full" ref="codeEditor"></textarea>
|
|
4
|
+
</div>
|
|
5
|
+
<button @click="cloneDocument()" class="rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600">Submit</button>
|
|
6
|
+
<div v-if="errors.length > 0" class="rounded-md bg-red-50 p-4 mt-1">
|
|
7
|
+
<div class="flex">
|
|
8
|
+
<div class="flex-shrink-0">
|
|
9
|
+
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
10
|
+
<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" />
|
|
11
|
+
</svg>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="ml-3">
|
|
14
|
+
<h3 class="text-sm font-medium text-red-800">There were {{errors.length}} errors with your submission</h3>
|
|
15
|
+
<div class="mt-2 text-sm text-red-700">
|
|
16
|
+
<ul role="list" class="list-disc space-y-1 pl-5">
|
|
17
|
+
<li v-for="error in errors">
|
|
18
|
+
{{error}}
|
|
19
|
+
</li>
|
|
20
|
+
</ul>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const api = require('../api');
|
|
4
|
+
|
|
5
|
+
const { BSON, EJSON } = require('bson');
|
|
6
|
+
|
|
7
|
+
const ObjectId = new Proxy(BSON.ObjectId, {
|
|
8
|
+
apply (target, thisArg, argumentsList) {
|
|
9
|
+
return new target(...argumentsList);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const appendCSS = require('../appendCSS');
|
|
14
|
+
|
|
15
|
+
appendCSS(require('./clone-document.css'));
|
|
16
|
+
|
|
17
|
+
const template = require('./clone-document.html')
|
|
18
|
+
|
|
19
|
+
module.exports = app => app.component('clone-document', {
|
|
20
|
+
props: ['currentModel', 'doc', 'schemaPaths'],
|
|
21
|
+
template,
|
|
22
|
+
data: function() {
|
|
23
|
+
return {
|
|
24
|
+
documentData: '',
|
|
25
|
+
editor: null,
|
|
26
|
+
errors: []
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
methods: {
|
|
30
|
+
async cloneDocument() {
|
|
31
|
+
const data = EJSON.serialize(eval(`(${this.editor.getValue()})`));
|
|
32
|
+
const { doc } = await api.Model.createDocument({ model: this.currentModel, data }).catch(err => {
|
|
33
|
+
if (err.response?.data?.message) {
|
|
34
|
+
console.log(err.response.data);
|
|
35
|
+
const message = err.response.data.message.split(": ").slice(1).join(": ");
|
|
36
|
+
this.errors = message.split(',').map(error => {
|
|
37
|
+
return error.split(': ').slice(1).join(': ').trim();
|
|
38
|
+
})
|
|
39
|
+
throw new Error(err.response?.data?.message);
|
|
40
|
+
}
|
|
41
|
+
throw err;
|
|
42
|
+
});
|
|
43
|
+
this.errors.length = 0;
|
|
44
|
+
this.$emit('close', doc);
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
mounted: function() {
|
|
48
|
+
const pathsToClone = this.schemaPaths.map(x => x.path);
|
|
49
|
+
|
|
50
|
+
// Create a filtered version of the document data
|
|
51
|
+
const filteredDoc = {};
|
|
52
|
+
pathsToClone.forEach(path => {
|
|
53
|
+
const value = this.doc[path];
|
|
54
|
+
if (value !== undefined) {
|
|
55
|
+
filteredDoc[path] = value;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Replace _id with a new ObjectId
|
|
60
|
+
if (pathsToClone.includes('_id')) {
|
|
61
|
+
filteredDoc._id = new ObjectId();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.documentData = JSON.stringify(filteredDoc, null, 2);
|
|
65
|
+
this.$refs.codeEditor.value = this.documentData;
|
|
66
|
+
this.editor = CodeMirror.fromTextArea(this.$refs.codeEditor, {
|
|
67
|
+
mode: 'javascript',
|
|
68
|
+
lineNumbers: true,
|
|
69
|
+
smartIndent: false
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
})
|
|
@@ -42,6 +42,14 @@
|
|
|
42
42
|
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">
|
|
43
43
|
<img src="images/delete.svg" class="inline" /> Delete
|
|
44
44
|
</button>
|
|
45
|
+
<button
|
|
46
|
+
@click="shouldShowCloneModal=true;"
|
|
47
|
+
:disabled="!canManipulate"
|
|
48
|
+
:class="{'cursor-not-allowed opacity-50': !canManipulate}"
|
|
49
|
+
type="button"
|
|
50
|
+
class="rounded-md bg-pink-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">
|
|
51
|
+
<img src="images/duplicate.svg" class="inline" /> Clone
|
|
52
|
+
</button>
|
|
45
53
|
</div>
|
|
46
54
|
</div>
|
|
47
55
|
<div v-if="status === 'loaded'">
|
|
@@ -63,5 +71,11 @@
|
|
|
63
71
|
<confirm-delete @close="shouldShowConfirmModal = false;" @remove="remove" :value="document"></confirm-delete>
|
|
64
72
|
</template>
|
|
65
73
|
</modal>
|
|
74
|
+
<modal v-if="shouldShowCloneModal">
|
|
75
|
+
<template v-slot:body>
|
|
76
|
+
<div class="modal-exit" @click="shouldShowCloneModal = false;">×</div>
|
|
77
|
+
<clone-document :currentModel="model" :doc="document" :schemaPaths="schemaPaths" @close="showClonedDocument"></clone-document>
|
|
78
|
+
</template>
|
|
79
|
+
</modal>
|
|
66
80
|
</div>
|
|
67
81
|
</div>
|
|
@@ -21,7 +21,8 @@ module.exports = app => app.component('document', {
|
|
|
21
21
|
editting: false,
|
|
22
22
|
virtuals: [],
|
|
23
23
|
shouldShowConfirmModal: false,
|
|
24
|
-
shouldShowDeleteModal: false
|
|
24
|
+
shouldShowDeleteModal: false,
|
|
25
|
+
shouldShowCloneModal: false
|
|
25
26
|
}),
|
|
26
27
|
async mounted() {
|
|
27
28
|
window.pageState = this;
|
|
@@ -82,6 +83,9 @@ module.exports = app => app.component('document', {
|
|
|
82
83
|
});
|
|
83
84
|
this.$router.push({ path: `/model/${this.model}`});
|
|
84
85
|
}
|
|
86
|
+
},
|
|
87
|
+
showClonedDocument(doc) {
|
|
88
|
+
this.$router.push({ path: `/model/${this.model}/document/${doc._id}`});
|
|
85
89
|
}
|
|
86
90
|
}
|
|
87
91
|
});
|
package/frontend/src/index.js
CHANGED
|
@@ -13,6 +13,7 @@ const app = Vue.createApp({
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
require('./async-button/async-button')(app);
|
|
16
|
+
require('./clone-document/clone-document')(app);
|
|
16
17
|
require('./create-dashboard/create-dashboard')(app);
|
|
17
18
|
require('./create-document/create-document')(app);
|
|
18
19
|
require('./dashboards/dashboards')(app);
|