@mongoosejs/studio 0.0.71 → 0.0.73

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.
@@ -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
- return client.post('', { action: 'Model.exportQueryResults', ...params }).then(res => res.data);
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 anchor = document.createElement('a');
118
- anchor.href = window.MONGOOSE_STUDIO_CONFIG.baseURL + '/Model/exportQueryResults?' + (new URLSearchParams(params)).toString();
119
- anchor.target = '_blank';
120
- anchor.download = 'export.csv';
121
- anchor.click();
122
- return;
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 &lsaquo; 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 &times; 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;\">&times;</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;\">&times;</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 &lsaquo; 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 &times; 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;\">&times;</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;\">&times;</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;\">&times;</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 <span>{{schemaPath.path}}</span>\n </div>\n <async-button @click=\"exportQueryResults\">Export</async-button>\n</div>";
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\">&times;</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\">&times;</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];\">&times;</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;\">&times;</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\">&times;</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\">&times;</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];\">&times;</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;\">&times;</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>
@@ -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;">&times;</div>
77
+ <clone-document :currentModel="model" :doc="document" :schemaPaths="schemaPaths" @close="showClonedDocument"></clone-document>
78
+ </template>
79
+ </modal>
66
80
  </div>
67
81
  </div>
@@ -11,7 +11,7 @@ appendCSS(require('./document.css'));
11
11
 
12
12
  module.exports = app => app.component('document', {
13
13
  template: template,
14
- props: ['model', 'documentId', 'user', 'roles'],
14
+ props: ['model', 'documentId', 'user', 'roles', 'hasAPIKey'],
15
15
  data: () => ({
16
16
  schemaPaths: [],
17
17
  status: 'init',
@@ -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;
@@ -41,6 +42,9 @@ module.exports = app => app.component('document', {
41
42
  },
42
43
  computed: {
43
44
  canManipulate() {
45
+ if (!this.hasAPIKey) {
46
+ return true;
47
+ }
44
48
  if (!this.roles) {
45
49
  return false;
46
50
  }
@@ -82,6 +86,9 @@ module.exports = app => app.component('document', {
82
86
  });
83
87
  this.$router.push({ path: `/model/${this.model}`});
84
88
  }
89
+ },
90
+ showClonedDocument(doc) {
91
+ this.$router.push({ path: `/model/${this.model}/document/${doc._id}`});
85
92
  }
86
93
  }
87
94
  });
@@ -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);
@@ -58,7 +59,7 @@ app.component('app-component', {
58
59
  <div v-else-if="!hasAPIKey || user">
59
60
  <navbar :user="user" :roles="roles" />
60
61
  <div class="view">
61
- <router-view :key="$route.fullPath" :user="user" :roles="roles" />
62
+ <router-view :key="$route.fullPath" :user="user" :roles="roles" :hasAPIKey="hasAPIKey" />
62
63
  </div>
63
64
  </div>
64
65
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.0.71",
3
+ "version": "0.0.73",
4
4
  "dependencies": {
5
5
  "archetype": "0.13.1",
6
6
  "csv-stringify": "6.3.0",