@mongoosejs/studio 0.0.70 → 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.
@@ -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);
@@ -127,6 +170,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
127
170
  getDocuments: function getDocuments(params) {
128
171
  return client.post('/Model/getDocuments', params).then(res => res.data);
129
172
  },
173
+ getIndexes: function getIndexes(params) {
174
+ return client.post('/Model/getIndexes', params).then(res => res.data);
175
+ },
130
176
  listModels: function listModels() {
131
177
  return client.post('/Model/listModels', {}).then(res => res.data);
132
178
  },
@@ -212,6 +258,88 @@ module.exports = app => app.component('async-button', {
212
258
 
213
259
  /***/ }),
214
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
+
215
343
  /***/ "./frontend/src/create-dashboard/create-dashboard.js":
216
344
  /*!***********************************************************!*\
217
345
  !*** ./frontend/src/create-dashboard/create-dashboard.js ***!
@@ -930,7 +1058,7 @@ appendCSS(__webpack_require__(/*! ./document.css */ "./frontend/src/document/doc
930
1058
 
931
1059
  module.exports = app => app.component('document', {
932
1060
  template: template,
933
- props: ['model', 'documentId'],
1061
+ props: ['model', 'documentId', 'user', 'roles'],
934
1062
  data: () => ({
935
1063
  schemaPaths: [],
936
1064
  status: 'init',
@@ -940,7 +1068,8 @@ module.exports = app => app.component('document', {
940
1068
  editting: false,
941
1069
  virtuals: [],
942
1070
  shouldShowConfirmModal: false,
943
- shouldShowDeleteModal: false
1071
+ shouldShowDeleteModal: false,
1072
+ shouldShowCloneModal: false
944
1073
  }),
945
1074
  async mounted() {
946
1075
  window.pageState = this;
@@ -958,6 +1087,14 @@ module.exports = app => app.component('document', {
958
1087
  }).map(key => schemaPaths[key]);
959
1088
  this.status = 'loaded';
960
1089
  },
1090
+ computed: {
1091
+ canManipulate() {
1092
+ if (!this.roles) {
1093
+ return false;
1094
+ }
1095
+ return !this.roles.includes('readonly');
1096
+ }
1097
+ },
961
1098
  methods: {
962
1099
  cancelEdit() {
963
1100
  this.changes = {};
@@ -993,6 +1130,9 @@ module.exports = app => app.component('document', {
993
1130
  });
994
1131
  this.$router.push({ path: `/model/${this.model}`});
995
1132
  }
1133
+ },
1134
+ showClonedDocument(doc) {
1135
+ this.$router.push({ path: `/model/${this.model}/document/${doc._id}`});
996
1136
  }
997
1137
  }
998
1138
  });
@@ -1648,7 +1788,7 @@ const limit = 20;
1648
1788
 
1649
1789
  module.exports = app => app.component('models', {
1650
1790
  template: template,
1651
- props: ['model'],
1791
+ props: ['model', 'user', 'roles'],
1652
1792
  data: () => ({
1653
1793
  models: [],
1654
1794
  currentModel: null,
@@ -1657,6 +1797,8 @@ module.exports = app => app.component('models', {
1657
1797
  filteredPaths: [],
1658
1798
  selectedPaths: [],
1659
1799
  numDocuments: 0,
1800
+ mongoDBIndexes: [],
1801
+ schemaIndexes: [],
1660
1802
  status: 'loading',
1661
1803
  loadedAllDocs: false,
1662
1804
  edittingDoc: null,
@@ -1666,6 +1808,7 @@ module.exports = app => app.component('models', {
1666
1808
  shouldShowExportModal: false,
1667
1809
  shouldShowCreateModal: false,
1668
1810
  shouldShowFieldModal: false,
1811
+ shouldShowIndexModal: false,
1669
1812
  shouldExport: {},
1670
1813
  sortBy: {},
1671
1814
  query: {},
@@ -1712,6 +1855,28 @@ module.exports = app => app.component('models', {
1712
1855
  this.status = 'loaded';
1713
1856
  },
1714
1857
  methods: {
1858
+ clickFilter(path) {
1859
+ if (this.searchText) {
1860
+ if (this.searchText.endsWith('}')) {
1861
+ this.searchText = this.searchText.slice(0, -1) + `, ${path}: }`;
1862
+ } else {
1863
+ this.searchText += `, ${path}: }`;
1864
+ }
1865
+
1866
+ } else {
1867
+ // If this.searchText is empty or undefined, initialize it with a new object
1868
+ this.searchText = `{ ${path}: }`;
1869
+ }
1870
+
1871
+
1872
+ this.$nextTick(() => {
1873
+ const input = this.$refs.searchInput;
1874
+ const cursorIndex = this.searchText.lastIndexOf(":") + 2; // Move cursor after ": "
1875
+
1876
+ input.focus();
1877
+ input.setSelectionRange(cursorIndex, cursorIndex);
1878
+ });
1879
+ },
1715
1880
  async closeCreationModal() {
1716
1881
  this.shouldShowCreateModal = false;
1717
1882
  await this.getDocuments();
@@ -1778,6 +1943,21 @@ module.exports = app => app.component('models', {
1778
1943
  }
1779
1944
  await this.loadMoreDocuments();
1780
1945
  },
1946
+ async openIndexModal() {
1947
+ this.shouldShowIndexModal = true;
1948
+ const { mongoDBIndexes, schemaIndexes } = await api.Model.getIndexes({ model: this.currentModel })
1949
+ this.mongoDBIndexes = mongoDBIndexes;
1950
+ this.schemaIndexes = schemaIndexes;
1951
+ },
1952
+ checkIndexLocation(indexName) {
1953
+ if (this.schemaIndexes.find(x => x.name == indexName) && this.mongoDBIndexes.find(x => x.name == indexName)) {
1954
+ return 'text-gray-500'
1955
+ } else if (this.schemaIndexes.find(x => x.name == indexName)) {
1956
+ return 'text-forest-green-500'
1957
+ } else {
1958
+ return 'text-valencia-500'
1959
+ }
1960
+ },
1781
1961
  async getDocuments() {
1782
1962
  const { docs, schemaPaths, numDocs } = await api.Model.getDocuments({
1783
1963
  model: this.currentModel,
@@ -1804,7 +1984,6 @@ module.exports = app => app.component('models', {
1804
1984
  for (const { path } of this.schemaPaths) {
1805
1985
  this.shouldExport[path] = true;
1806
1986
  }
1807
-
1808
1987
  this.filteredPaths = [...this.schemaPaths];
1809
1988
  this.selectedPaths = [...this.schemaPaths];
1810
1989
  },
@@ -1985,7 +2164,7 @@ exports.hasAPIKey = client.hasAPIKey;
1985
2164
  const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
1986
2165
  const mothership = __webpack_require__(/*! ../mothership */ "./frontend/src/mothership.js");
1987
2166
  const template = __webpack_require__(/*! ./navbar.html */ "./frontend/src/navbar/navbar.html");
1988
- const routes = __webpack_require__(/*! ../routes */ "./frontend/src/routes.js");
2167
+ const { routes, hasAccess } = __webpack_require__(/*! ../routes */ "./frontend/src/routes.js");
1989
2168
 
1990
2169
  const appendCSS = __webpack_require__(/*! ../appendCSS */ "./frontend/src/appendCSS.js");
1991
2170
 
@@ -1996,6 +2175,15 @@ module.exports = app => app.component('navbar', {
1996
2175
  props: ['user', 'roles'],
1997
2176
  inject: ['state'],
1998
2177
  data: () => ({ showFlyout: false }),
2178
+ mounted: function() {
2179
+ // Redirect to first allowed route if current route is not allowed
2180
+ if (!this.hasAccess(this.roles, this.$route.name)) {
2181
+ const firstAllowedRoute = this.allowedRoutes[0];
2182
+ if (firstAllowedRoute) {
2183
+ this.$router.push({ name: firstAllowedRoute.name });
2184
+ }
2185
+ }
2186
+ },
1999
2187
  computed: {
2000
2188
  dashboardView() {
2001
2189
  return routes.filter(x => x.name.startsWith('dashboard')).map(x => x.name).includes(this.$route.name)
@@ -2013,10 +2201,19 @@ module.exports = app => app.component('navbar', {
2013
2201
  return mothership.hasAPIKey;
2014
2202
  },
2015
2203
  canViewTeam() {
2016
- return this.roles?.includes('owner') || this.roles?.includes('admin');
2204
+ return this.hasAccess(this.roles, 'team');
2205
+ },
2206
+ allowedRoutes() {
2207
+ return routes.filter(route => this.hasAccess(this.roles, route.name));
2208
+ },
2209
+ defaultRoute() {
2210
+ return this.allowedRoutes[0]?.name || 'dashboards';
2017
2211
  }
2018
2212
  },
2019
2213
  methods: {
2214
+ hasAccess(roles, routeName) {
2215
+ return hasAccess(roles, routeName);
2216
+ },
2020
2217
  async loginWithGithub() {
2021
2218
  const { url } = await mothership.githubLogin();
2022
2219
  window.location.href = url;
@@ -2062,38 +2259,76 @@ module.exports = app => app.component('navbar', {
2062
2259
  "use strict";
2063
2260
 
2064
2261
 
2065
- module.exports = [
2066
- {
2067
- path: '/',
2068
- name: 'root',
2069
- component: 'models'
2070
- },
2071
- {
2072
- path: '/model/:model',
2073
- name: 'model',
2074
- component: 'models'
2075
- },
2076
- {
2077
- path: '/model/:model/document/:documentId',
2078
- name: 'document',
2079
- component: 'document'
2080
- },
2081
- {
2082
- path: '/dashboards',
2083
- name: 'dashboards',
2084
- component: 'dashboards'
2085
- },
2086
- {
2087
- path: '/dashboard/:dashboardId',
2088
- name: 'dashboard',
2089
- component: 'dashboard'
2090
- },
2091
- {
2092
- path: '/team',
2093
- name: 'team',
2094
- component: 'team'
2095
- }
2096
- ];
2262
+ // Role-based access control configuration
2263
+ const roleAccess = {
2264
+ owner: ['root', 'model', 'document', 'dashboards', 'dashboard', 'team'],
2265
+ admin: ['root', 'model', 'document', 'dashboards', 'dashboard', 'team'],
2266
+ member: ['root', 'model', 'document', 'dashboards', 'dashboard'],
2267
+ readonly: ['root', 'model', 'document'],
2268
+ dashboards: ['dashboards', 'dashboard']
2269
+ };
2270
+
2271
+ // Helper function to check if a role has access to a route
2272
+ function hasAccess(roles, routeName) {
2273
+ // change to true for local development
2274
+ if (!roles) return false;
2275
+ return roles.some(role => roleAccess[role]?.includes(routeName));
2276
+ }
2277
+
2278
+ module.exports = {
2279
+ routes: [
2280
+ {
2281
+ path: '/',
2282
+ name: 'root',
2283
+ component: 'models',
2284
+ meta: {
2285
+ authorized: true
2286
+ }
2287
+ },
2288
+ {
2289
+ path: '/model/:model',
2290
+ name: 'model',
2291
+ component: 'models',
2292
+ meta: {
2293
+ authorized: true
2294
+ }
2295
+ },
2296
+ {
2297
+ path: '/model/:model/document/:documentId',
2298
+ name: 'document',
2299
+ component: 'document',
2300
+ meta: {
2301
+ authorized: true
2302
+ }
2303
+ },
2304
+ {
2305
+ path: '/dashboards',
2306
+ name: 'dashboards',
2307
+ component: 'dashboards',
2308
+ meta: {
2309
+ authorized: true
2310
+ }
2311
+ },
2312
+ {
2313
+ path: '/dashboard/:dashboardId',
2314
+ name: 'dashboard',
2315
+ component: 'dashboard',
2316
+ meta: {
2317
+ authorized: true
2318
+ }
2319
+ },
2320
+ {
2321
+ path: '/team',
2322
+ name: 'team',
2323
+ component: 'team',
2324
+ meta: {
2325
+ authorized: true
2326
+ }
2327
+ }
2328
+ ],
2329
+ roleAccess,
2330
+ hasAccess
2331
+ };
2097
2332
 
2098
2333
 
2099
2334
  /***/ }),
@@ -2831,6 +3066,28 @@ module.exports = "<button v-bind=\"attrsToBind\" :disabled=\"isDisabled\" @click
2831
3066
 
2832
3067
  /***/ }),
2833
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
+
2834
3091
  /***/ "./frontend/src/create-dashboard/create-dashboard.html":
2835
3092
  /*!*************************************************************!*\
2836
3093
  !*** ./frontend/src/create-dashboard/create-dashboard.html ***!
@@ -3058,7 +3315,7 @@ module.exports = ".document {\n max-width: 1200px;\n margin-left: auto;\n mar
3058
3315
  /***/ ((module) => {
3059
3316
 
3060
3317
  "use strict";
3061
- 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 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 @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 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";
3062
3319
 
3063
3320
  /***/ }),
3064
3321
 
@@ -3146,7 +3403,7 @@ module.exports = "";
3146
3403
  /***/ ((module) => {
3147
3404
 
3148
3405
  "use strict";
3149
- 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>";
3150
3407
 
3151
3408
  /***/ }),
3152
3409
 
@@ -3322,7 +3579,7 @@ module.exports = ".models {\n position: relative;\n display: flex;\n flex-dir
3322
3579
  /***/ ((module) => {
3323
3580
 
3324
3581
  "use strict";
3325
- 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 class=\"w-full 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=\"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\">\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\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=\"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";
3326
3583
 
3327
3584
  /***/ }),
3328
3585
 
@@ -3344,7 +3601,7 @@ module.exports = ".navbar {\n width: 100%;\n background-color: #eee;\n}\n\n.ac
3344
3601
  /***/ ((module) => {
3345
3602
 
3346
3603
  "use strict";
3347
- module.exports = "<div class=\"navbar\">\n <div class=\"nav-left flex items-center gap-4 h-full\">\n <router-link to=\"/\">\n <img src=\"images/logo.svg\" alt=\"Mongoose Studio Logo\" />\n </router-link>\n <div v-if=\"!!state.nodeEnv\" class=\"inline-flex items-center rounded-md px-2 py-1 text-sm font-medium text-gray-900\" :class=\"warnEnv ? 'bg-red-300' : 'bg-yellow-300'\">\n {{state.nodeEnv}}\n </div>\n </div>\n <div class=\"nav-right h-full\">\n <div class=\"sm:ml-6 sm:flex sm:space-x-8 h-full\">\n <a\n href=\"#/\"\n class=\"inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium\"\n :class=\"documentView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Documents</a>\n <a\n href=\"#/dashboards\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium\"\n :class=\"dashboardView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Dashboards</a>\n\n <div class=\"h-full flex items-center\" v-if=\"!user && hasAPIKey\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\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 Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"h-full flex items-center relative\" v-clickOutside=\"hideFlyout\">\n <div>\n <button type=\"button\" @click=\"showFlyout = !showFlyout\" class=\"relative flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800\" id=\"user-menu-button\" aria-expanded=\"false\" aria-haspopup=\"true\">\n <span class=\"absolute -inset-1.5\"></span>\n <span class=\"sr-only\">Open user menu</span>\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n </button>\n </div>\n\n <div v-if=\"showFlyout\" class=\"absolute right-0 z-10 top-[90%] w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-none\" role=\"menu\" aria-orientation=\"vertical\" aria-labelledby=\"user-menu-button\" tabindex=\"-1\">\n <router-link to=\"/team\" v-if=\"canViewTeam\" @click=\"showFlyout = false\" class=\"cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Team</router-link>\n <span @click=\"logout\" class=\"cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Sign out</span>\n </div>\n </div>\n\n </div>\n </div>\n <div style=\"clear: both\"></div>\n</div>\n";
3604
+ module.exports = "<div class=\"navbar\">\n <div class=\"nav-left flex items-center gap-4 h-full\">\n <router-link :to=\"{ name: defaultRoute }\">\n <img src=\"images/logo.svg\" alt=\"Mongoose Studio Logo\" />\n </router-link>\n <div v-if=\"!!state.nodeEnv\" class=\"inline-flex items-center rounded-md px-2 py-1 text-sm font-medium text-gray-900\" :class=\"warnEnv ? 'bg-red-300' : 'bg-yellow-300'\">\n {{state.nodeEnv}}\n </div>\n </div>\n <div class=\"nav-right h-full\">\n <div class=\"sm:ml-6 sm:flex sm:space-x-8 h-full\">\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium\"\n :class=\"documentView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Documents</a>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium\"\n :class=\"dashboardView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Dashboards</a>\n\n <div class=\"h-full flex items-center\" v-if=\"!user && hasAPIKey\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\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 Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"h-full flex items-center relative\" v-clickOutside=\"hideFlyout\">\n <div>\n <button type=\"button\" @click=\"showFlyout = !showFlyout\" class=\"relative flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800\" id=\"user-menu-button\" aria-expanded=\"false\" aria-haspopup=\"true\">\n <span class=\"absolute -inset-1.5\"></span>\n <span class=\"sr-only\">Open user menu</span>\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n </button>\n </div>\n\n <div v-if=\"showFlyout\" class=\"absolute right-0 z-10 top-[90%] w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-none\" role=\"menu\" aria-orientation=\"vertical\" aria-labelledby=\"user-menu-button\" tabindex=\"-1\">\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" @click=\"showFlyout = false\" class=\"cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Team</router-link>\n <span @click=\"logout\" class=\"cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Sign out</span>\n </div>\n </div>\n\n </div>\n </div>\n <div style=\"clear: both\"></div>\n</div>\n";
3348
3605
 
3349
3606
  /***/ }),
3350
3607
 
@@ -11207,6 +11464,7 @@ const app = Vue.createApp({
11207
11464
  });
11208
11465
 
11209
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);
11210
11468
  __webpack_require__(/*! ./create-dashboard/create-dashboard */ "./frontend/src/create-dashboard/create-dashboard.js")(app);
11211
11469
  __webpack_require__(/*! ./create-document/create-document */ "./frontend/src/create-document/create-document.js")(app);
11212
11470
  __webpack_require__(/*! ./dashboards/dashboards */ "./frontend/src/dashboards/dashboards.js")(app);
@@ -11252,7 +11510,7 @@ app.component('app-component', {
11252
11510
  <div v-else-if="!hasAPIKey || user">
11253
11511
  <navbar :user="user" :roles="roles" />
11254
11512
  <div class="view">
11255
- <router-view :key="$route.fullPath" />
11513
+ <router-view :key="$route.fullPath" :user="user" :roles="roles" />
11256
11514
  </div>
11257
11515
  </div>
11258
11516
  </div>
@@ -11272,6 +11530,7 @@ app.component('app-component', {
11272
11530
  },
11273
11531
  async mounted() {
11274
11532
  window.$router = this.$router;
11533
+ window.state = this;
11275
11534
 
11276
11535
  if (mothership.hasAPIKey) {
11277
11536
  const href = window.location.href;
@@ -11329,7 +11588,7 @@ app.component('app-component', {
11329
11588
  }
11330
11589
  });
11331
11590
 
11332
- const routes = __webpack_require__(/*! ./routes */ "./frontend/src/routes.js");
11591
+ const { routes } = __webpack_require__(/*! ./routes */ "./frontend/src/routes.js");
11333
11592
  const router = VueRouter.createRouter({
11334
11593
  history: VueRouter.createWebHashHistory(),
11335
11594
  routes: routes.map(route => ({
@@ -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>