@mongoosejs/studio 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/backend/actions/Model/getDocuments.js +17 -4
- package/backend/actions/Model/getDocumentsStream.js +17 -4
- package/frontend/public/app.js +386 -286
- package/frontend/public/tw.css +21 -0
- package/frontend/src/dashboard/dashboard.js +23 -0
- package/frontend/src/list-json/json-node.html +118 -0
- package/frontend/src/list-json/list-json.html +1 -0
- package/frontend/src/list-json/list-json.js +38 -88
- package/frontend/src/models/document-search/document-search.html +23 -0
- package/frontend/src/models/document-search/document-search.js +227 -0
- package/frontend/src/models/models.html +9 -8
- package/frontend/src/models/models.js +21 -184
- package/frontend/src/models/trie.js +44 -18
- package/package.json +3 -3
package/frontend/public/app.js
CHANGED
|
@@ -128,6 +128,7 @@ var map = {
|
|
|
128
128
|
"./list-default/list-default.css": "./frontend/src/list-default/list-default.css",
|
|
129
129
|
"./list-default/list-default.html": "./frontend/src/list-default/list-default.html",
|
|
130
130
|
"./list-default/list-default.js": "./frontend/src/list-default/list-default.js",
|
|
131
|
+
"./list-json/json-node.html": "./frontend/src/list-json/json-node.html",
|
|
131
132
|
"./list-json/list-json": "./frontend/src/list-json/list-json.js",
|
|
132
133
|
"./list-json/list-json.html": "./frontend/src/list-json/list-json.html",
|
|
133
134
|
"./list-json/list-json.js": "./frontend/src/list-json/list-json.js",
|
|
@@ -147,6 +148,9 @@ var map = {
|
|
|
147
148
|
"./modal/modal.css": "./frontend/src/modal/modal.css",
|
|
148
149
|
"./modal/modal.html": "./frontend/src/modal/modal.html",
|
|
149
150
|
"./modal/modal.js": "./frontend/src/modal/modal.js",
|
|
151
|
+
"./models/document-search/document-search": "./frontend/src/models/document-search/document-search.js",
|
|
152
|
+
"./models/document-search/document-search.html": "./frontend/src/models/document-search/document-search.html",
|
|
153
|
+
"./models/document-search/document-search.js": "./frontend/src/models/document-search/document-search.js",
|
|
150
154
|
"./models/models": "./frontend/src/models/models.js",
|
|
151
155
|
"./models/models.css": "./frontend/src/models/models.css",
|
|
152
156
|
"./models/models.html": "./frontend/src/models/models.html",
|
|
@@ -1791,6 +1795,25 @@ module.exports = app => app.component('dashboard', {
|
|
|
1791
1795
|
} finally {
|
|
1792
1796
|
this.status = 'loaded';
|
|
1793
1797
|
}
|
|
1798
|
+
},
|
|
1799
|
+
shouldEvaluateDashboard() {
|
|
1800
|
+
if (this.dashboardResults.length === 0) {
|
|
1801
|
+
return true;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
const finishedEvaluatingAt = this.dashboardResults[0].finishedEvaluatingAt;
|
|
1805
|
+
if (!finishedEvaluatingAt) {
|
|
1806
|
+
return true;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
const sixHoursAgo = Date.now() - 6 * 60 * 60 * 1000;
|
|
1810
|
+
const finishedAt = new Date(finishedEvaluatingAt).getTime();
|
|
1811
|
+
|
|
1812
|
+
if (Number.isNaN(finishedAt)) {
|
|
1813
|
+
return true;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
return finishedAt < sixHoursAgo;
|
|
1794
1817
|
}
|
|
1795
1818
|
},
|
|
1796
1819
|
computed: {
|
|
@@ -1809,6 +1832,10 @@ module.exports = app => app.component('dashboard', {
|
|
|
1809
1832
|
this.title = this.dashboard.title;
|
|
1810
1833
|
this.description = this.dashboard.description ?? '';
|
|
1811
1834
|
this.dashboardResults = dashboardResults;
|
|
1835
|
+
if (this.shouldEvaluateDashboard()) {
|
|
1836
|
+
await this.evaluateDashboard();
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1812
1839
|
this.status = 'loaded';
|
|
1813
1840
|
}
|
|
1814
1841
|
});
|
|
@@ -3888,6 +3915,17 @@ module.exports = app => app.component('list-default', {
|
|
|
3888
3915
|
|
|
3889
3916
|
/***/ }),
|
|
3890
3917
|
|
|
3918
|
+
/***/ "./frontend/src/list-json/json-node.html":
|
|
3919
|
+
/*!***********************************************!*\
|
|
3920
|
+
!*** ./frontend/src/list-json/json-node.html ***!
|
|
3921
|
+
\***********************************************/
|
|
3922
|
+
/***/ ((module) => {
|
|
3923
|
+
|
|
3924
|
+
"use strict";
|
|
3925
|
+
module.exports = "<div>\n <div class=\"flex items-baseline whitespace-pre\" :style=\"indentStyle\">\n <button\n v-if=\"showToggle\"\n type=\"button\"\n class=\"w-4 h-4 mr-1 inline-flex items-center justify-center leading-none text-gray-500 hover:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400 cursor-pointer\"\n @click.stop=\"handleToggle\"\n >\n {{ isCollapsedNode ? '+' : '-' }}\n </button>\n <span v-else class=\"w-4 h-4 mr-1 inline-flex items-center justify-center invisible flex-shrink-0\"></span>\n <template v-if=\"hasKey\">\n <span class=\"text-blue-600\">\"{{ nodeKey }}\"</span><span>: </span>\n </template>\n <template v-if=\"isComplex\">\n <template v-if=\"hasChildren\">\n <span>{{ openingBracket }}</span>\n <span v-if=\"isCollapsedNode\" class=\"mx-1\">…</span>\n <span v-if=\"isCollapsedNode\">{{ closingBracket }}{{ comma }}</span>\n </template>\n <template v-else>\n <span>{{ openingBracket }}{{ closingBracket }}{{ comma }}</span>\n </template>\n </template>\n <template v-else>\n <!--\n If value is a string and overflows its container (i.e. goes over one line), show an ellipsis.\n This is done via CSS ellipsis strategy.\n -->\n <span\n v-if=\"shouldShowReferenceLink\"\n class=\"inline-flex items-baseline group\"\n >\n <span\n :class=\"[...valueClasses, 'underline', 'decoration-dotted', 'underline-offset-2']\"\n :style=\"typeof value === 'string'\n ? {\n display: 'inline-block',\n maxWidth: '100%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n verticalAlign: 'bottom'\n }\n : {}\"\n :title=\"typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined\"\n >\n {{ formattedValue }}\n </span>\n <span>\n {{ comma }}\n </span>\n <a\n href=\"#\"\n class=\"ml-1 text-sm text-sky-700 opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity\"\n @click.stop.prevent=\"goToReference(value)\"\n >\n View Document\n </a>\n </span>\n <span\n v-else\n :class=\"valueClasses\"\n :style=\"typeof value === 'string'\n ? {\n display: 'inline-block',\n maxWidth: '100%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n verticalAlign: 'bottom'\n }\n : {}\"\n :title=\"typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined\"\n >\n {{ formattedValue }}{{ comma }}\n </span>\n </template>\n </div>\n <template v-if=\"isComplex && hasChildren && !isCollapsedNode\">\n <json-node\n v-for=\"child in children\"\n :key=\"child.path\"\n :node-key=\"child.displayKey\"\n :value=\"child.value\"\n :level=\"level + 1\"\n :is-last=\"child.isLast\"\n :path=\"child.path\"\n :toggle-collapse=\"toggleCollapse\"\n :is-collapsed=\"isCollapsed\"\n :create-child-path=\"createChildPath\"\n :indent-size=\"indentSize\"\n :max-top-level-fields=\"maxTopLevelFields\"\n :top-level-expanded=\"topLevelExpanded\"\n :expand-top-level=\"expandTopLevel\"\n :references=\"references\"\n ></json-node>\n <div\n v-if=\"hasHiddenRootChildren\"\n class=\"flex items-baseline whitespace-pre\"\n :style=\"indentStyle\"\n >\n <span class=\"w-4 h-4 mr-1 inline-flex items-center justify-center invisible\"></span>\n <button\n type=\"button\"\n class=\"text-xs inline-flex items-center gap-1 ml-4 text-slate-500 hover:text-slate-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400\"\n :title=\"hiddenChildrenTooltip\"\n @click.stop=\"handleExpandTopLevel\"\n >\n <span aria-hidden=\"true\">{{hiddenChildrenLabel}}…</span>\n </button>\n </div>\n <div class=\"flex items-baseline whitespace-pre\" :style=\"indentStyle\">\n <span class=\"w-4 h-4 mr-1 inline-flex items-center justify-center invisible\"></span>\n <span>{{ closingBracket }}{{ comma }}</span>\n </div>\n </template>\n</div>\n";
|
|
3926
|
+
|
|
3927
|
+
/***/ }),
|
|
3928
|
+
|
|
3891
3929
|
/***/ "./frontend/src/list-json/list-json.html":
|
|
3892
3930
|
/*!***********************************************!*\
|
|
3893
3931
|
!*** ./frontend/src/list-json/list-json.html ***!
|
|
@@ -3895,7 +3933,7 @@ module.exports = app => app.component('list-default', {
|
|
|
3895
3933
|
/***/ ((module) => {
|
|
3896
3934
|
|
|
3897
3935
|
"use strict";
|
|
3898
|
-
module.exports = "<div class=\"tooltip w-full font-mono text-sm py-3 text-slate-800\">\n <div class=\"w-full\">\n <json-node\n :node-key=\"null\"\n :value=\"value\"\n :level=\"0\"\n :is-last=\"true\"\n path=\"root\"\n :toggle-collapse=\"toggleCollapse\"\n :is-collapsed=\"isPathCollapsed\"\n :create-child-path=\"createChildPath\"\n :indent-size=\"indentSize\"\n :max-top-level-fields=\"maxTopLevelFields\"\n :top-level-expanded=\"topLevelExpanded\"\n :expand-top-level=\"expandTopLevel\"\n ></json-node>\n </div>\n</div>\n";
|
|
3936
|
+
module.exports = "<div class=\"tooltip w-full font-mono text-sm py-3 text-slate-800\">\n <div class=\"w-full\">\n <json-node\n :node-key=\"null\"\n :value=\"value\"\n :level=\"0\"\n :is-last=\"true\"\n path=\"root\"\n :toggle-collapse=\"toggleCollapse\"\n :is-collapsed=\"isPathCollapsed\"\n :create-child-path=\"createChildPath\"\n :indent-size=\"indentSize\"\n :max-top-level-fields=\"maxTopLevelFields\"\n :top-level-expanded=\"topLevelExpanded\"\n :expand-top-level=\"expandTopLevel\"\n :references=\"references\"\n ></json-node>\n </div>\n</div>\n";
|
|
3899
3937
|
|
|
3900
3938
|
/***/ }),
|
|
3901
3939
|
|
|
@@ -3910,97 +3948,19 @@ module.exports = "<div class=\"tooltip w-full font-mono text-sm py-3 text-slate-
|
|
|
3910
3948
|
|
|
3911
3949
|
const template = __webpack_require__(/*! ./list-json.html */ "./frontend/src/list-json/list-json.html");
|
|
3912
3950
|
|
|
3913
|
-
const JsonNodeTemplate =
|
|
3914
|
-
<div>
|
|
3915
|
-
<div class="flex items-baseline whitespace-pre" :style="indentStyle">
|
|
3916
|
-
<button
|
|
3917
|
-
v-if="showToggle"
|
|
3918
|
-
type="button"
|
|
3919
|
-
class="w-4 h-4 mr-1 inline-flex items-center justify-center leading-none text-gray-500 hover:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400 cursor-pointer"
|
|
3920
|
-
@click.stop="handleToggle"
|
|
3921
|
-
>
|
|
3922
|
-
{{ isCollapsedNode ? '+' : '-' }}
|
|
3923
|
-
</button>
|
|
3924
|
-
<span v-else class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible flex-shrink-0"></span>
|
|
3925
|
-
<template v-if="hasKey">
|
|
3926
|
-
<span class="text-blue-600">"{{ nodeKey }}"</span><span>: </span>
|
|
3927
|
-
</template>
|
|
3928
|
-
<template v-if="isComplex">
|
|
3929
|
-
<template v-if="hasChildren">
|
|
3930
|
-
<span>{{ openingBracket }}</span>
|
|
3931
|
-
<span v-if="isCollapsedNode" class="mx-1">…</span>
|
|
3932
|
-
<span v-if="isCollapsedNode">{{ closingBracket }}{{ comma }}</span>
|
|
3933
|
-
</template>
|
|
3934
|
-
<template v-else>
|
|
3935
|
-
<span>{{ openingBracket }}{{ closingBracket }}{{ comma }}</span>
|
|
3936
|
-
</template>
|
|
3937
|
-
</template>
|
|
3938
|
-
<template v-else>
|
|
3939
|
-
<!--
|
|
3940
|
-
If value is a string and overflows its container (i.e. goes over one line), show an ellipsis.
|
|
3941
|
-
This is done via CSS ellipsis strategy.
|
|
3942
|
-
-->
|
|
3943
|
-
<span
|
|
3944
|
-
:class="valueClasses"
|
|
3945
|
-
:style="typeof value === 'string'
|
|
3946
|
-
? {
|
|
3947
|
-
display: 'inline-block',
|
|
3948
|
-
maxWidth: '100%',
|
|
3949
|
-
overflow: 'hidden',
|
|
3950
|
-
textOverflow: 'ellipsis',
|
|
3951
|
-
whiteSpace: 'nowrap',
|
|
3952
|
-
verticalAlign: 'bottom'
|
|
3953
|
-
}
|
|
3954
|
-
: {}"
|
|
3955
|
-
:title="typeof value === 'string' && $el && $el.scrollWidth > $el.clientWidth ? value : undefined"
|
|
3956
|
-
>
|
|
3957
|
-
{{ formattedValue }}{{ comma }}
|
|
3958
|
-
</span>
|
|
3959
|
-
</template>
|
|
3960
|
-
</div>
|
|
3961
|
-
<template v-if="isComplex && hasChildren && !isCollapsedNode">
|
|
3962
|
-
<json-node
|
|
3963
|
-
v-for="child in children"
|
|
3964
|
-
:key="child.path"
|
|
3965
|
-
:node-key="child.displayKey"
|
|
3966
|
-
:value="child.value"
|
|
3967
|
-
:level="level + 1"
|
|
3968
|
-
:is-last="child.isLast"
|
|
3969
|
-
:path="child.path"
|
|
3970
|
-
:toggle-collapse="toggleCollapse"
|
|
3971
|
-
:is-collapsed="isCollapsed"
|
|
3972
|
-
:create-child-path="createChildPath"
|
|
3973
|
-
:indent-size="indentSize"
|
|
3974
|
-
:max-top-level-fields="maxTopLevelFields"
|
|
3975
|
-
:top-level-expanded="topLevelExpanded"
|
|
3976
|
-
:expand-top-level="expandTopLevel"
|
|
3977
|
-
></json-node>
|
|
3978
|
-
<div
|
|
3979
|
-
v-if="hasHiddenRootChildren"
|
|
3980
|
-
class="flex items-baseline whitespace-pre"
|
|
3981
|
-
:style="indentStyle"
|
|
3982
|
-
>
|
|
3983
|
-
<span class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible"></span>
|
|
3984
|
-
<button
|
|
3985
|
-
type="button"
|
|
3986
|
-
class="text-xs inline-flex items-center gap-1 ml-4 text-slate-500 hover:text-slate-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-slate-400"
|
|
3987
|
-
:title="hiddenChildrenTooltip"
|
|
3988
|
-
@click.stop="handleExpandTopLevel"
|
|
3989
|
-
>
|
|
3990
|
-
<span aria-hidden="true">{{hiddenChildrenLabel}}…</span>
|
|
3991
|
-
</button>
|
|
3992
|
-
</div>
|
|
3993
|
-
<div class="flex items-baseline whitespace-pre" :style="indentStyle">
|
|
3994
|
-
<span class="w-4 h-4 mr-1 inline-flex items-center justify-center invisible"></span>
|
|
3995
|
-
<span>{{ closingBracket }}{{ comma }}</span>
|
|
3996
|
-
</div>
|
|
3997
|
-
</template>
|
|
3998
|
-
</div>
|
|
3999
|
-
`;
|
|
3951
|
+
const JsonNodeTemplate = __webpack_require__(/*! ./json-node.html */ "./frontend/src/list-json/json-node.html");
|
|
4000
3952
|
|
|
4001
3953
|
module.exports = app => app.component('list-json', {
|
|
4002
3954
|
template: template,
|
|
4003
|
-
props:
|
|
3955
|
+
props: {
|
|
3956
|
+
value: {
|
|
3957
|
+
required: true
|
|
3958
|
+
},
|
|
3959
|
+
references: {
|
|
3960
|
+
type: Object,
|
|
3961
|
+
default: () => ({})
|
|
3962
|
+
}
|
|
3963
|
+
},
|
|
4004
3964
|
data() {
|
|
4005
3965
|
return {
|
|
4006
3966
|
collapsedMap: {},
|
|
@@ -4104,6 +4064,10 @@ module.exports = app => app.component('list-json', {
|
|
|
4104
4064
|
expandTopLevel: {
|
|
4105
4065
|
type: Function,
|
|
4106
4066
|
default: null
|
|
4067
|
+
},
|
|
4068
|
+
references: {
|
|
4069
|
+
type: Object,
|
|
4070
|
+
default: () => ({})
|
|
4107
4071
|
}
|
|
4108
4072
|
},
|
|
4109
4073
|
computed: {
|
|
@@ -4242,6 +4206,24 @@ module.exports = app => app.component('list-json', {
|
|
|
4242
4206
|
},
|
|
4243
4207
|
hiddenChildrenTooltip() {
|
|
4244
4208
|
return this.hiddenChildrenLabel;
|
|
4209
|
+
},
|
|
4210
|
+
normalizedPath() {
|
|
4211
|
+
if (typeof this.path !== 'string') {
|
|
4212
|
+
return '';
|
|
4213
|
+
}
|
|
4214
|
+
return this.path
|
|
4215
|
+
.replace(/^root\.?/, '')
|
|
4216
|
+
.replace(/\[\d+\]/g, '')
|
|
4217
|
+
.replace(/^\./, '');
|
|
4218
|
+
},
|
|
4219
|
+
referenceModel() {
|
|
4220
|
+
if (!this.normalizedPath || !this.references) {
|
|
4221
|
+
return null;
|
|
4222
|
+
}
|
|
4223
|
+
return this.references[this.normalizedPath] || null;
|
|
4224
|
+
},
|
|
4225
|
+
shouldShowReferenceLink() {
|
|
4226
|
+
return Boolean(this.referenceModel) && typeof this.value === 'string';
|
|
4245
4227
|
}
|
|
4246
4228
|
},
|
|
4247
4229
|
methods: {
|
|
@@ -4266,6 +4248,12 @@ module.exports = app => app.component('list-json', {
|
|
|
4266
4248
|
if (this.isRoot && typeof this.expandTopLevel === 'function') {
|
|
4267
4249
|
this.expandTopLevel();
|
|
4268
4250
|
}
|
|
4251
|
+
},
|
|
4252
|
+
goToReference(id) {
|
|
4253
|
+
if (!this.referenceModel) {
|
|
4254
|
+
return;
|
|
4255
|
+
}
|
|
4256
|
+
this.$router.push({ path: `/model/${this.referenceModel}/document/${id}` });
|
|
4269
4257
|
}
|
|
4270
4258
|
}
|
|
4271
4259
|
}
|
|
@@ -4539,43 +4527,28 @@ module.exports = app => app.component('modal', {
|
|
|
4539
4527
|
|
|
4540
4528
|
/***/ }),
|
|
4541
4529
|
|
|
4542
|
-
/***/ "./frontend/src/models/
|
|
4543
|
-
|
|
4544
|
-
!*** ./frontend/src/models/
|
|
4545
|
-
|
|
4546
|
-
/***/ ((module) => {
|
|
4547
|
-
|
|
4548
|
-
"use strict";
|
|
4549
|
-
module.exports = ".models {\n position: relative;\n display: flex;\n flex-direction: row;\n min-height: calc(100% - 56px);\n}\n\n.models button.gray {\n color: black;\n background-color: #eee;\n}\n\n.models .model-selector {\n background-color: #eee;\n flex-grow: 0;\n padding: 15px;\n padding-top: 0px;\n}\n\n.models h1 {\n margin-top: 0px;\n}\n\n.models .documents {\n flex-grow: 1;\n overflow: scroll;\n max-height: calc(100vh - 56px);\n}\n\n.models .documents table {\n /* max-width: -moz-fit-content;\n max-width: fit-content; */\n width: 100%;\n table-layout: auto;\n font-size: small;\n padding: 0;\n margin-right: 1em;\n white-space: nowrap;\n z-index: -1;\n border-collapse: collapse;\n line-height: 1.5em;\n}\n\n.models .documents table th {\n position: sticky;\n top: 42px;\n background-color: white;\n z-index: 1;\n}\n\n.models .documents table th:after {\n content: \"\";\n position: absolute;\n left: 0;\n width: 100%;\n bottom: -1px;\n border-bottom: thin solid rgba(0, 0, 0, 0.12);\n}\n\n.models .documents table tr {\n color: black;\n border-spacing: 0px 0px;\n background-color: white;\n cursor: pointer;\n}\n\n.models .documents table tr:nth-child(even) {\n background-color: #f5f5f5;\n}\n\n.models .documents table tr:hover {\n background-color: #a7b9ff;\n}\n\n.models .documents table th,\ntd {\n border-bottom: thin solid rgba(0, 0, 0, 0.12);\n text-align: left;\n padding: 0 16px;\n height: 48px;\n}\n\n.models textarea {\n font-size: 1.2em;\n}\n\n.models .path-type {\n color: rgba(0, 0, 0, 0.36);\n font-size: 0.8em;\n}\n\n.models .documents-menu {\n position: fixed;\n background-color: white;\n z-index: 1;\n padding: 4px;\n display: flex;\n width: 100vw;\n}\n\n@media (min-width: 1024px) {\n .models .documents-menu {\n width: calc(100vw - 12rem);\n }\n}\n\n.models .documents-menu .search-input {\n flex-grow: 1;\n align-items: center;\n}\n\n.models .search-input input {\n padding: 0.25em 0.5em;\n font-size: 1.1em;\n border: 1px solid #ddd;\n border-radius: 3px;\n width: calc(100% - 1em);\n}\n\n.models .sort-arrow {\n padding-left: 10px;\n padding-right: 10px;\n}\n\n.models .loader {\n width: 100%;\n text-align: center;\n}\n\n.models .loader img {\n height: 4em;\n}\n\n.models .documents .buttons {\n display: inline-flex;\n justify-content: space-around;\n align-items: center;\n}\n\n";
|
|
4550
|
-
|
|
4551
|
-
/***/ }),
|
|
4552
|
-
|
|
4553
|
-
/***/ "./frontend/src/models/models.html":
|
|
4554
|
-
/*!*****************************************!*\
|
|
4555
|
-
!*** ./frontend/src/models/models.html ***!
|
|
4556
|
-
\*****************************************/
|
|
4530
|
+
/***/ "./frontend/src/models/document-search/document-search.html":
|
|
4531
|
+
/*!******************************************************************!*\
|
|
4532
|
+
!*** ./frontend/src/models/document-search/document-search.html ***!
|
|
4533
|
+
\******************************************************************/
|
|
4557
4534
|
/***/ ((module) => {
|
|
4558
4535
|
|
|
4559
4536
|
"use strict";
|
|
4560
|
-
module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <div class=\"fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10\" @click=\"hideSidebar = false\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"#5f6368\"><path d=\"M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z\"/></svg>\n </div>\n <aside class=\"bg-white border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-48 fixed lg:relative shrink-0\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-48' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-48 overflow-x-hidden\">\n <div class=\"p-4 font-bold text-lg\">Models</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none\"\n aria-label=\"Close sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"currentColor\"><path d=\"M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z\"/></svg>\n </button>\n </div>\n <nav class=\"flex flex-1 flex-col\">\n <ul role=\"list\" class=\"flex flex-1 flex-col gap-y-7\">\n <li>\n <ul role=\"list\">\n <li v-for=\"model in models\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"block truncate rounded-md py-2 pr-2 pl-2 text-sm font-semibold text-gray-700\"\n :class=\"model === currentModel ? 'bg-ultramarine-100 font-bold' : 'hover:bg-ultramarine-100'\">\n {{model}}\n </router-link>\n </li>\n </ul>\n </li>\n </ul>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100\">\n No models found\n </div>\n </nav>\n </aside>\n <div class=\"documents\" ref=\"documentsList\">\n <div class=\"relative h-[42px] z-10\">\n <div class=\"documents-menu\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <form @submit.prevent=\"search\" class=\"relative flex-grow m-0\">\n <input ref=\"searchInput\" class=\"w-full font-mono rounded-md p-1 border border-gray-300 outline-gray-300 text-lg focus:ring-1 focus:ring-ultramarine-200 focus:ring-offset-0 focus:outline-none\" type=\"text\" placeholder=\"Filter\" v-model=\"searchText\" @click=\"initFilter\" @input=\"updateAutocomplete\" @keydown=\"handleKeyDown\" />\n <ul v-if=\"autocompleteSuggestions.length\" class=\"absolute z-[9999] bg-white border border-gray-300 rounded mt-1 w-full max-h-40 overflow-y-auto shadow\">\n <li v-for=\"(suggestion, index) in autocompleteSuggestions\" :key=\"suggestion\" class=\"px-2 py-1 cursor-pointer\" :class=\"{ 'bg-ultramarine-100': index === autocompleteIndex }\" @mousedown.prevent=\"applySuggestion(index)\">{{ suggestion }}</li>\n </ul>\n </form>\n <div>\n <span v-if=\"numDocuments == null\">Loading ...</span>\n <span v-else-if=\"typeof numDocuments === 'number'\">{{numDocuments === 1 ? numDocuments+ ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"shouldShowExportModal = true\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Export\n </button>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{ 'bg-gray-500 ring-inset ring-2 ring-gray-300 hover:bg-gray-600': selectMultiple, 'bg-ultramarine-600 hover:bg-ultramarine-500' : !selectMultiple }\"\n class=\"rounded px-2 py-2 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n >\n {{ selectMultiple ? 'Cancel' : 'Select' }}\n </button>\n <button\n v-show=\"selectMultiple\"\n @click=\"shouldShowUpdateMultipleModal=true;\"\n type=\"button\"\n class=\"rounded bg-green-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\"\n >\n Update\n </button>\n <button\n @click=\"shouldShowDeleteMultipleModal=true;\"\n type=\"button\"\n v-show=\"selectMultiple\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500\"\n >\n Delete\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Indexes\n </button>\n <button\n @click=\"shouldShowCreateModal = true;\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Create\n </button>\n <button\n @click=\"openFieldSelection\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Fields\n </button>\n <span class=\"isolate inline-flex rounded-md shadow-sm\">\n <button\n @click=\"setOutputType('table')\"\n type=\"button\"\n class=\"relative inline-flex items-center rounded-none rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/table.svg\">\n </button>\n <button\n @click=\"setOutputType('json')\"\n type=\"button\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n </span>\n </div>\n </div>\n </div>\n <div class=\"documents-container relative\">\n <div v-if=\"error\">\n <div class=\"bg-red-100 border border-red-400 text-red-700 px-4 py-3 relative m-4 rounded-md\" role=\"alert\">\n <span class=\"block font-bold\">Error</span>\n <span class=\"block\">{{ error }}</span>\n </div>\n </div>\n <table v-else-if=\"outputType === 'table'\">\n <thead>\n <th v-for=\"path in filteredPaths\" @click=\"clickFilter(path.path)\" class=\"cursor-pointer\">\n {{path.path}}\n <span class=\"path-type\">\n ({{(path.instance || 'unknown')}})\n </span>\n <span class=\"sort-arrow\" @click=\"sortDocs(1, path.path)\">{{sortBy[path.path] == 1 ? 'X' : '↑'}}</span>\n <span class=\"sort-arrow\" @click=\"sortDocs(-1, path.path)\">{{sortBy[path.path] == -1 ? 'X' : '↓'}}</span>\n </th>\n </thead>\n <tbody>\n <tr v-for=\"document in documents\" @click=\"handleDocumentClick(document, $event)\" :key=\"document._id\">\n <td v-for=\"schemaPath in filteredPaths\" :class=\"{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\">\n </component>\n </td>\n </tr>\n </tbody>\n </table>\n <div v-else-if=\"outputType === 'json'\" class=\"flex flex-col space-y-6\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative transition-colors',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:bg-slate-100'\n ]\"\n >\n <button\n type=\"button\"\n class=\"absolute top-2 right-2 z-10 inline-flex items-center rounded bg-ultramarine-600 px-2 py-1 text-xs font-semibold text-white shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n @click.stop=\"openDocument(document)\"\n >\n Open this Document\n </button>\n <list-json :value=\"filterDocument(document)\">\n </list-json>\n </div>\n </div>\n <div v-if=\"status === 'loading'\" class=\"loader\">\n <img src=\"images/loader.gif\">\n </div>\n </div>\n </div>\n <modal v-if=\"shouldShowExportModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowExportModal = false\">×</div>\n <export-query-results\n :schemaPaths=\"schemaPaths\"\n :search-text=\"searchText\"\n :currentModel=\"currentModel\"\n @done=\"shouldShowExportModal = false\">\n </export-query-results>\n </template>\n </modal>\n <modal v-if=\"shouldShowIndexModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowIndexModal = false\">×</div>\n <div class=\"text-xl font-bold mb-2\">Indexes</div>\n <div v-for=\"index in mongoDBIndexes\" class=\"w-full flex items-center\">\n <div class=\"grow shrink text-left flex justify-between items-center\" v-if=\"index.name != '_id_'\">\n <div>\n <div class=\"font-bold\">{{ index.name }}</div>\n <div class=\"text-sm font-mono\">{{ JSON.stringify(index.key) }}</div>\n </div>\n <div>\n <async-button\n type=\"button\"\n @click=\"dropIndex(index.name)\"\n class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600 disabled:bg-gray-400 disabled:cursor-not-allowed\">\n Drop\n </async-button>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowFieldModal = false; selectedPaths = [...filteredPaths];\">×</div>\n <div v-for=\"(path, index) in schemaPaths\" :key=\"index\" class=\"w-5 flex items-center\">\n <input class=\"mt-0 h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-600 accent-sky-600\" type=\"checkbox\" :id=\"'path.path'+index\" @change=\"addOrRemove(path)\" :value=\"path.path\" :checked=\"isSelected(path.path)\" />\n <div class=\"ml-2 text-gray-700 grow shrink text-left\">\n <label :for=\"'path.path' + index\">{{path.path}}</label>\n </div>\n </div>\n <div class=\"mt-4 flex gap-2\">\n <button type=\"button\" @click=\"filterDocuments()\" class=\"rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">Filter Selection</button>\n <button type=\"button\" @click=\"selectAll()\" class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">Select All</button>\n <button type=\"button\" @click=\"deselectAll()\" class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">Deselect All</button>\n <button type=\"button\" @click=\"resetDocuments()\" class=\"rounded-md bg-gray-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\" >Cancel</button>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">×</div>\n <create-document :currentModel=\"currentModel\" :paths=\"schemaPaths\" @close=\"closeCreationModal\"></create-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowUpdateMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowUpdateMultipleModal = false;\">×</div>\n <update-document :currentModel=\"currentModel\" :document=\"selectedDocuments\" :multiple=\"true\" @update=\"updateDocuments\" @close=\"shouldShowUpdateMultipleModal=false;\"></update-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteMultipleModal = false;\">×</div>\n <h2>Are you sure you want to delete {{selectedDocuments.length}} documents?</h2>\n <div>\n <list-json :value=\"selectedDocuments\"></list-json>\n </div>\n <div class=\"flex gap-4\">\n <async-button @click=\"deleteDocuments\" class=\"rounded bg-red-500 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n Confirm\n </async-button>\n <button @click=\"shouldShowDeleteMultipleModal = false;\" class=\"rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
4537
|
+
module.exports = "<form @submit.prevent=\"emitSearch\" class=\"relative flex-grow m-0\">\n <input\n ref=\"searchInput\"\n 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\"\n type=\"text\"\n placeholder=\"Filter\"\n v-model=\"searchText\"\n @click=\"initFilter\"\n @input=\"updateAutocomplete\"\n @keydown=\"handleKeyDown\"\n />\n <ul v-if=\"autocompleteSuggestions.length\" class=\"absolute z-[9999] bg-white border border-gray-300 rounded mt-1 w-full max-h-40 overflow-y-auto shadow\">\n <li\n v-for=\"(suggestion, index) in autocompleteSuggestions\"\n :key=\"suggestion\"\n class=\"px-2 py-1 cursor-pointer\"\n :class=\"{ 'bg-ultramarine-100': index === autocompleteIndex }\"\n @mousedown.prevent=\"applySuggestion(index)\"\n >\n {{ suggestion }}\n </li>\n </ul>\n</form>\n";
|
|
4561
4538
|
|
|
4562
4539
|
/***/ }),
|
|
4563
4540
|
|
|
4564
|
-
/***/ "./frontend/src/models/
|
|
4565
|
-
|
|
4566
|
-
!*** ./frontend/src/models/
|
|
4567
|
-
|
|
4541
|
+
/***/ "./frontend/src/models/document-search/document-search.js":
|
|
4542
|
+
/*!****************************************************************!*\
|
|
4543
|
+
!*** ./frontend/src/models/document-search/document-search.js ***!
|
|
4544
|
+
\****************************************************************/
|
|
4568
4545
|
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
4569
4546
|
|
|
4570
4547
|
"use strict";
|
|
4571
4548
|
|
|
4572
4549
|
|
|
4573
|
-
const
|
|
4574
|
-
const
|
|
4575
|
-
const mpath = __webpack_require__(/*! mpath */ "./node_modules/mpath/index.js");
|
|
4576
|
-
|
|
4577
|
-
const appendCSS = __webpack_require__(/*! ../appendCSS */ "./frontend/src/appendCSS.js");
|
|
4578
|
-
const { Trie } = __webpack_require__(/*! ./trie */ "./frontend/src/models/trie.js");
|
|
4550
|
+
const template = __webpack_require__(/*! ./document-search.html */ "./frontend/src/models/document-search/document-search.html");
|
|
4551
|
+
const { Trie } = __webpack_require__(/*! ../trie */ "./frontend/src/models/trie.js");
|
|
4579
4552
|
|
|
4580
4553
|
const QUERY_SELECTORS = [
|
|
4581
4554
|
'$eq',
|
|
@@ -4604,165 +4577,59 @@ const QUERY_SELECTORS = [
|
|
|
4604
4577
|
'$mod'
|
|
4605
4578
|
];
|
|
4606
4579
|
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
currentModel: null,
|
|
4619
|
-
documents: [],
|
|
4620
|
-
schemaPaths: [],
|
|
4621
|
-
filteredPaths: [],
|
|
4622
|
-
selectedPaths: [],
|
|
4623
|
-
numDocuments: null,
|
|
4624
|
-
mongoDBIndexes: [],
|
|
4625
|
-
schemaIndexes: [],
|
|
4626
|
-
status: 'loading',
|
|
4627
|
-
loadedAllDocs: false,
|
|
4628
|
-
edittingDoc: null,
|
|
4629
|
-
docEdits: null,
|
|
4630
|
-
selectMultiple: false,
|
|
4631
|
-
selectedDocuments: [],
|
|
4632
|
-
searchText: '',
|
|
4633
|
-
autocompleteSuggestions: [],
|
|
4634
|
-
autocompleteIndex: 0,
|
|
4635
|
-
autocompleteTrie: null,
|
|
4636
|
-
shouldShowExportModal: false,
|
|
4637
|
-
shouldShowCreateModal: false,
|
|
4638
|
-
shouldShowFieldModal: false,
|
|
4639
|
-
shouldShowIndexModal: false,
|
|
4640
|
-
shouldShowUpdateMultipleModal: false,
|
|
4641
|
-
shouldShowDeleteMultipleModal: false,
|
|
4642
|
-
shouldExport: {},
|
|
4643
|
-
sortBy: {},
|
|
4644
|
-
query: {},
|
|
4645
|
-
scrollHeight: 0,
|
|
4646
|
-
interval: null,
|
|
4647
|
-
outputType: 'table', // json, table
|
|
4648
|
-
hideSidebar: null,
|
|
4649
|
-
lastSelectedIndex: null,
|
|
4650
|
-
error: null
|
|
4651
|
-
}),
|
|
4652
|
-
created() {
|
|
4653
|
-
this.currentModel = this.model;
|
|
4654
|
-
this.buildAutocompleteTrie();
|
|
4655
|
-
this.loadOutputPreference();
|
|
4580
|
+
module.exports = app => app.component('document-search', {
|
|
4581
|
+
template,
|
|
4582
|
+
props: {
|
|
4583
|
+
value: {
|
|
4584
|
+
type: String,
|
|
4585
|
+
default: ''
|
|
4586
|
+
},
|
|
4587
|
+
schemaPaths: {
|
|
4588
|
+
type: Array,
|
|
4589
|
+
default: () => []
|
|
4590
|
+
}
|
|
4656
4591
|
},
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4592
|
+
data() {
|
|
4593
|
+
return {
|
|
4594
|
+
autocompleteSuggestions: [],
|
|
4595
|
+
autocompleteIndex: 0,
|
|
4596
|
+
autocompleteTrie: null,
|
|
4597
|
+
searchText: this.value || ''
|
|
4598
|
+
};
|
|
4660
4599
|
},
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
}
|
|
4671
|
-
if (this.models.length === 0) {
|
|
4672
|
-
this.status = 'loaded';
|
|
4673
|
-
this.numDocuments = 0;
|
|
4674
|
-
if (readyState === 0) {
|
|
4675
|
-
this.error = 'No models found and Mongoose is not connected. Check our documentation for more information.';
|
|
4676
|
-
}
|
|
4600
|
+
watch: {
|
|
4601
|
+
value(val) {
|
|
4602
|
+
this.searchText = val || '';
|
|
4603
|
+
},
|
|
4604
|
+
schemaPaths: {
|
|
4605
|
+
handler() {
|
|
4606
|
+
this.buildAutocompleteTrie();
|
|
4607
|
+
},
|
|
4608
|
+
deep: true
|
|
4677
4609
|
}
|
|
4678
|
-
|
|
4679
|
-
|
|
4610
|
+
},
|
|
4611
|
+
created() {
|
|
4612
|
+
this.buildAutocompleteTrie();
|
|
4680
4613
|
},
|
|
4681
4614
|
methods: {
|
|
4615
|
+
emitSearch() {
|
|
4616
|
+
this.$emit('input', this.searchText);
|
|
4617
|
+
this.$emit('search', this.searchText);
|
|
4618
|
+
},
|
|
4682
4619
|
buildAutocompleteTrie() {
|
|
4683
4620
|
this.autocompleteTrie = new Trie();
|
|
4684
|
-
this.autocompleteTrie.bulkInsert(QUERY_SELECTORS, 5);
|
|
4621
|
+
this.autocompleteTrie.bulkInsert(QUERY_SELECTORS, 5, 'operator');
|
|
4685
4622
|
if (Array.isArray(this.schemaPaths) && this.schemaPaths.length > 0) {
|
|
4686
4623
|
const paths = this.schemaPaths
|
|
4687
4624
|
.map(path => path?.path)
|
|
4688
4625
|
.filter(path => typeof path === 'string' && path.length > 0);
|
|
4689
|
-
this.
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
loadOutputPreference() {
|
|
4693
|
-
if (typeof window === 'undefined' || !window.localStorage) {
|
|
4694
|
-
return;
|
|
4695
|
-
}
|
|
4696
|
-
const storedPreference = window.localStorage.getItem(OUTPUT_TYPE_STORAGE_KEY);
|
|
4697
|
-
if (storedPreference === 'json' || storedPreference === 'table') {
|
|
4698
|
-
this.outputType = storedPreference;
|
|
4699
|
-
}
|
|
4700
|
-
},
|
|
4701
|
-
setOutputType(type) {
|
|
4702
|
-
if (type !== 'json' && type !== 'table') {
|
|
4703
|
-
return;
|
|
4704
|
-
}
|
|
4705
|
-
this.outputType = type;
|
|
4706
|
-
if (typeof window !== 'undefined' && window.localStorage) {
|
|
4707
|
-
window.localStorage.setItem(OUTPUT_TYPE_STORAGE_KEY, type);
|
|
4708
|
-
}
|
|
4709
|
-
},
|
|
4710
|
-
buildDocumentFetchParams(options = {}) {
|
|
4711
|
-
const params = {
|
|
4712
|
-
model: this.currentModel,
|
|
4713
|
-
limit
|
|
4714
|
-
};
|
|
4715
|
-
|
|
4716
|
-
if (typeof options.skip === 'number') {
|
|
4717
|
-
params.skip = options.skip;
|
|
4718
|
-
}
|
|
4719
|
-
|
|
4720
|
-
const sortKeys = Object.keys(this.sortBy);
|
|
4721
|
-
if (sortKeys.length > 0) {
|
|
4722
|
-
const key = sortKeys[0];
|
|
4723
|
-
if (typeof key === 'string' && key.length > 0) {
|
|
4724
|
-
params.sortKey = key;
|
|
4725
|
-
const direction = this.sortBy[key];
|
|
4726
|
-
if (direction !== undefined && direction !== null) {
|
|
4727
|
-
params.sortDirection = direction;
|
|
4626
|
+
for (const path of this.schemaPaths) {
|
|
4627
|
+
if (path.schema) {
|
|
4628
|
+
paths.push(...Object.keys(path.schema).map(subpath => `${path.path}.${subpath}`));
|
|
4728
4629
|
}
|
|
4729
4630
|
}
|
|
4631
|
+
this.autocompleteTrie.bulkInsert(paths, 10, 'fieldName');
|
|
4730
4632
|
}
|
|
4731
|
-
|
|
4732
|
-
if (typeof this.searchText === 'string' && this.searchText.trim().length > 0) {
|
|
4733
|
-
params.searchText = this.searchText;
|
|
4734
|
-
}
|
|
4735
|
-
|
|
4736
|
-
return params;
|
|
4737
|
-
},
|
|
4738
|
-
async initSearchFromUrl() {
|
|
4739
|
-
this.status = 'loading';
|
|
4740
|
-
this.query = Object.assign({}, this.$route.query); // important that this is here before the if statements
|
|
4741
|
-
if (this.$route.query?.search) {
|
|
4742
|
-
this.searchText = this.$route.query.search;
|
|
4743
|
-
} else {
|
|
4744
|
-
this.searchText = '';
|
|
4745
|
-
}
|
|
4746
|
-
if (this.$route.query?.sort) {
|
|
4747
|
-
const sort = eval(`(${this.$route.query.sort})`);
|
|
4748
|
-
const path = Object.keys(sort)[0];
|
|
4749
|
-
const num = Object.values(sort)[0];
|
|
4750
|
-
this.sortDocs(num, path);
|
|
4751
|
-
}
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
if (this.currentModel != null) {
|
|
4755
|
-
await this.getDocuments();
|
|
4756
|
-
}
|
|
4757
|
-
if (this.$route.query?.fields) {
|
|
4758
|
-
const filter = this.$route.query.fields.split(',');
|
|
4759
|
-
this.filteredPaths = this.filteredPaths.filter(x => filter.includes(x.path));
|
|
4760
|
-
}
|
|
4761
|
-
this.status = 'loaded';
|
|
4762
|
-
},
|
|
4763
|
-
async dropIndex(name) {
|
|
4764
|
-
const { mongoDBIndexes } = await api.Model.dropIndex({ model: this.currentModel, name });
|
|
4765
|
-
this.mongoDBIndexes = mongoDBIndexes;
|
|
4766
4633
|
},
|
|
4767
4634
|
initFilter(ev) {
|
|
4768
4635
|
if (!this.searchText) {
|
|
@@ -4791,8 +4658,12 @@ module.exports = app => app.component('models', {
|
|
|
4791
4658
|
this.autocompleteSuggestions = [];
|
|
4792
4659
|
return;
|
|
4793
4660
|
}
|
|
4661
|
+
|
|
4662
|
+
const colonMatch = before.match(/:\s*([^,\}\]]*)$/);
|
|
4663
|
+
const role = colonMatch ? 'operator' : 'fieldName';
|
|
4664
|
+
|
|
4794
4665
|
if (this.autocompleteTrie) {
|
|
4795
|
-
const primarySuggestions = this.autocompleteTrie.getSuggestions(term, 10);
|
|
4666
|
+
const primarySuggestions = this.autocompleteTrie.getSuggestions(term, 10, role);
|
|
4796
4667
|
const suggestionsSet = new Set(primarySuggestions);
|
|
4797
4668
|
if (Array.isArray(this.schemaPaths) && this.schemaPaths.length > 0) {
|
|
4798
4669
|
for (const schemaPath of this.schemaPaths) {
|
|
@@ -4877,7 +4748,7 @@ module.exports = app => app.component('models', {
|
|
|
4877
4748
|
});
|
|
4878
4749
|
this.autocompleteSuggestions = [];
|
|
4879
4750
|
},
|
|
4880
|
-
|
|
4751
|
+
addPathFilter(path) {
|
|
4881
4752
|
if (this.searchText) {
|
|
4882
4753
|
if (this.searchText.endsWith('}')) {
|
|
4883
4754
|
this.searchText = this.searchText.slice(0, -1) + `, ${path}: }`;
|
|
@@ -4898,6 +4769,204 @@ module.exports = app => app.component('models', {
|
|
|
4898
4769
|
input.focus();
|
|
4899
4770
|
input.setSelectionRange(cursorIndex, cursorIndex);
|
|
4900
4771
|
});
|
|
4772
|
+
}
|
|
4773
|
+
}
|
|
4774
|
+
});
|
|
4775
|
+
|
|
4776
|
+
|
|
4777
|
+
/***/ }),
|
|
4778
|
+
|
|
4779
|
+
/***/ "./frontend/src/models/models.css":
|
|
4780
|
+
/*!****************************************!*\
|
|
4781
|
+
!*** ./frontend/src/models/models.css ***!
|
|
4782
|
+
\****************************************/
|
|
4783
|
+
/***/ ((module) => {
|
|
4784
|
+
|
|
4785
|
+
"use strict";
|
|
4786
|
+
module.exports = ".models {\n position: relative;\n display: flex;\n flex-direction: row;\n min-height: calc(100% - 56px);\n}\n\n.models button.gray {\n color: black;\n background-color: #eee;\n}\n\n.models .model-selector {\n background-color: #eee;\n flex-grow: 0;\n padding: 15px;\n padding-top: 0px;\n}\n\n.models h1 {\n margin-top: 0px;\n}\n\n.models .documents {\n flex-grow: 1;\n overflow: scroll;\n max-height: calc(100vh - 56px);\n}\n\n.models .documents table {\n /* max-width: -moz-fit-content;\n max-width: fit-content; */\n width: 100%;\n table-layout: auto;\n font-size: small;\n padding: 0;\n margin-right: 1em;\n white-space: nowrap;\n z-index: -1;\n border-collapse: collapse;\n line-height: 1.5em;\n}\n\n.models .documents table th {\n position: sticky;\n top: 42px;\n background-color: white;\n z-index: 1;\n}\n\n.models .documents table th:after {\n content: \"\";\n position: absolute;\n left: 0;\n width: 100%;\n bottom: -1px;\n border-bottom: thin solid rgba(0, 0, 0, 0.12);\n}\n\n.models .documents table tr {\n color: black;\n border-spacing: 0px 0px;\n background-color: white;\n cursor: pointer;\n}\n\n.models .documents table tr:nth-child(even) {\n background-color: #f5f5f5;\n}\n\n.models .documents table tr:hover {\n background-color: #a7b9ff;\n}\n\n.models .documents table th,\ntd {\n border-bottom: thin solid rgba(0, 0, 0, 0.12);\n text-align: left;\n padding: 0 16px;\n height: 48px;\n}\n\n.models textarea {\n font-size: 1.2em;\n}\n\n.models .path-type {\n color: rgba(0, 0, 0, 0.36);\n font-size: 0.8em;\n}\n\n.models .documents-menu {\n position: fixed;\n background-color: white;\n z-index: 1;\n padding: 4px;\n display: flex;\n width: 100vw;\n}\n\n@media (min-width: 1024px) {\n .models .documents-menu {\n width: calc(100vw - 12rem);\n }\n}\n\n.models .documents-menu .search-input {\n flex-grow: 1;\n align-items: center;\n}\n\n.models .search-input input {\n padding: 0.25em 0.5em;\n font-size: 1.1em;\n border: 1px solid #ddd;\n border-radius: 3px;\n width: calc(100% - 1em);\n}\n\n.models .sort-arrow {\n padding-left: 10px;\n padding-right: 10px;\n}\n\n.models .loader {\n width: 100%;\n text-align: center;\n}\n\n.models .loader img {\n height: 4em;\n}\n\n.models .documents .buttons {\n display: inline-flex;\n justify-content: space-around;\n align-items: center;\n}\n\n";
|
|
4787
|
+
|
|
4788
|
+
/***/ }),
|
|
4789
|
+
|
|
4790
|
+
/***/ "./frontend/src/models/models.html":
|
|
4791
|
+
/*!*****************************************!*\
|
|
4792
|
+
!*** ./frontend/src/models/models.html ***!
|
|
4793
|
+
\*****************************************/
|
|
4794
|
+
/***/ ((module) => {
|
|
4795
|
+
|
|
4796
|
+
"use strict";
|
|
4797
|
+
module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <div class=\"fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10\" @click=\"hideSidebar = false\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"#5f6368\"><path d=\"M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z\"/></svg>\n </div>\n <aside class=\"bg-white border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-48 fixed lg:relative shrink-0\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-48' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-48 overflow-x-hidden\">\n <div class=\"p-4 font-bold text-lg\">Models</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none\"\n aria-label=\"Close sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"currentColor\"><path d=\"M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z\"/></svg>\n </button>\n </div>\n <nav class=\"flex flex-1 flex-col\">\n <ul role=\"list\" class=\"flex flex-1 flex-col gap-y-7\">\n <li>\n <ul role=\"list\">\n <li v-for=\"model in models\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"block truncate rounded-md py-2 pr-2 pl-2 text-sm font-semibold text-gray-700\"\n :class=\"model === currentModel ? 'bg-ultramarine-100 font-bold' : 'hover:bg-ultramarine-100'\">\n {{model}}\n </router-link>\n </li>\n </ul>\n </li>\n </ul>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100\">\n No models found\n </div>\n </nav>\n </aside>\n <div class=\"documents\" ref=\"documentsList\">\n <div class=\"relative h-[42px] z-10\">\n <div class=\"documents-menu\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <document-search\n ref=\"documentSearch\"\n :value=\"searchText\"\n :schema-paths=\"schemaPaths\"\n @search=\"search\"\n >\n </document-search>\n <div>\n <span v-if=\"numDocuments == null\">Loading ...</span>\n <span v-else-if=\"typeof numDocuments === 'number'\">{{numDocuments === 1 ? numDocuments+ ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"shouldShowExportModal = true\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Export\n </button>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{ 'bg-gray-500 ring-inset ring-2 ring-gray-300 hover:bg-gray-600': selectMultiple, 'bg-ultramarine-600 hover:bg-ultramarine-500' : !selectMultiple }\"\n class=\"rounded px-2 py-2 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n >\n {{ selectMultiple ? 'Cancel' : 'Select' }}\n </button>\n <button\n v-show=\"selectMultiple\"\n @click=\"shouldShowUpdateMultipleModal=true;\"\n type=\"button\"\n class=\"rounded bg-green-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\"\n >\n Update\n </button>\n <button\n @click=\"shouldShowDeleteMultipleModal=true;\"\n type=\"button\"\n v-show=\"selectMultiple\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500\"\n >\n Delete\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Indexes\n </button>\n <button\n @click=\"shouldShowCreateModal = true;\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Create\n </button>\n <button\n @click=\"openFieldSelection\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Fields\n </button>\n <span class=\"isolate inline-flex rounded-md shadow-sm\">\n <button\n @click=\"setOutputType('table')\"\n type=\"button\"\n class=\"relative inline-flex items-center rounded-none rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/table.svg\">\n </button>\n <button\n @click=\"setOutputType('json')\"\n type=\"button\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n </span>\n </div>\n </div>\n </div>\n <div class=\"documents-container relative\">\n <div v-if=\"error\">\n <div class=\"bg-red-100 border border-red-400 text-red-700 px-4 py-3 relative m-4 rounded-md\" role=\"alert\">\n <span class=\"block font-bold\">Error</span>\n <span class=\"block\">{{ error }}</span>\n </div>\n </div>\n <table v-else-if=\"outputType === 'table'\">\n <thead>\n <th v-for=\"path in filteredPaths\" @click=\"addPathFilter(path.path)\" class=\"cursor-pointer\">\n {{path.path}}\n <span class=\"path-type\">\n ({{(path.instance || 'unknown')}})\n </span>\n <span class=\"sort-arrow\" @click=\"sortDocs(1, path.path)\">{{sortBy[path.path] == 1 ? 'X' : '↑'}}</span>\n <span class=\"sort-arrow\" @click=\"sortDocs(-1, path.path)\">{{sortBy[path.path] == -1 ? 'X' : '↓'}}</span>\n </th>\n </thead>\n <tbody>\n <tr v-for=\"document in documents\" @click=\"handleDocumentClick(document, $event)\" :key=\"document._id\">\n <td v-for=\"schemaPath in filteredPaths\" :class=\"{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\">\n </component>\n </td>\n </tr>\n </tbody>\n </table>\n <div v-else-if=\"outputType === 'json'\" class=\"flex flex-col space-y-6\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative transition-colors',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:bg-slate-100'\n ]\"\n >\n <button\n type=\"button\"\n class=\"absolute top-2 right-2 z-10 inline-flex items-center rounded bg-ultramarine-600 px-2 py-1 text-xs font-semibold text-white shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n @click.stop=\"openDocument(document)\"\n >\n Open this Document\n </button>\n <list-json :value=\"filterDocument(document)\" :references=\"referenceMap\">\n </list-json>\n </div>\n </div>\n <div v-if=\"status === 'loading'\" class=\"loader\">\n <img src=\"images/loader.gif\">\n </div>\n </div>\n </div>\n <modal v-if=\"shouldShowExportModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowExportModal = false\">×</div>\n <export-query-results\n :schemaPaths=\"schemaPaths\"\n :search-text=\"searchText\"\n :currentModel=\"currentModel\"\n @done=\"shouldShowExportModal = false\">\n </export-query-results>\n </template>\n </modal>\n <modal v-if=\"shouldShowIndexModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowIndexModal = false\">×</div>\n <div class=\"text-xl font-bold mb-2\">Indexes</div>\n <div v-for=\"index in mongoDBIndexes\" class=\"w-full flex items-center\">\n <div class=\"grow shrink text-left flex justify-between items-center\" v-if=\"index.name != '_id_'\">\n <div>\n <div class=\"font-bold\">{{ index.name }}</div>\n <div class=\"text-sm font-mono\">{{ JSON.stringify(index.key) }}</div>\n </div>\n <div>\n <async-button\n type=\"button\"\n @click=\"dropIndex(index.name)\"\n class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600 disabled:bg-gray-400 disabled:cursor-not-allowed\">\n Drop\n </async-button>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowFieldModal = false; selectedPaths = [...filteredPaths];\">×</div>\n <div v-for=\"(path, index) in schemaPaths\" :key=\"index\" class=\"w-5 flex items-center\">\n <input class=\"mt-0 h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-600 accent-sky-600\" type=\"checkbox\" :id=\"'path.path'+index\" @change=\"addOrRemove(path)\" :value=\"path.path\" :checked=\"isSelected(path.path)\" />\n <div class=\"ml-2 text-gray-700 grow shrink text-left\">\n <label :for=\"'path.path' + index\">{{path.path}}</label>\n </div>\n </div>\n <div class=\"mt-4 flex gap-2\">\n <button type=\"button\" @click=\"filterDocuments()\" class=\"rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">Filter Selection</button>\n <button type=\"button\" @click=\"selectAll()\" class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">Select All</button>\n <button type=\"button\" @click=\"deselectAll()\" class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">Deselect All</button>\n <button type=\"button\" @click=\"resetDocuments()\" class=\"rounded-md bg-gray-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\" >Cancel</button>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">×</div>\n <create-document :currentModel=\"currentModel\" :paths=\"schemaPaths\" @close=\"closeCreationModal\"></create-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowUpdateMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowUpdateMultipleModal = false;\">×</div>\n <update-document :currentModel=\"currentModel\" :document=\"selectedDocuments\" :multiple=\"true\" @update=\"updateDocuments\" @close=\"shouldShowUpdateMultipleModal=false;\"></update-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteMultipleModal = false;\">×</div>\n <h2>Are you sure you want to delete {{selectedDocuments.length}} documents?</h2>\n <div>\n <list-json :value=\"selectedDocuments\"></list-json>\n </div>\n <div class=\"flex gap-4\">\n <async-button @click=\"deleteDocuments\" class=\"rounded bg-red-500 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n Confirm\n </async-button>\n <button @click=\"shouldShowDeleteMultipleModal = false;\" class=\"rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
4798
|
+
|
|
4799
|
+
/***/ }),
|
|
4800
|
+
|
|
4801
|
+
/***/ "./frontend/src/models/models.js":
|
|
4802
|
+
/*!***************************************!*\
|
|
4803
|
+
!*** ./frontend/src/models/models.js ***!
|
|
4804
|
+
\***************************************/
|
|
4805
|
+
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
4806
|
+
|
|
4807
|
+
"use strict";
|
|
4808
|
+
|
|
4809
|
+
|
|
4810
|
+
const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
|
|
4811
|
+
const template = __webpack_require__(/*! ./models.html */ "./frontend/src/models/models.html");
|
|
4812
|
+
const mpath = __webpack_require__(/*! mpath */ "./node_modules/mpath/index.js");
|
|
4813
|
+
|
|
4814
|
+
const appendCSS = __webpack_require__(/*! ../appendCSS */ "./frontend/src/appendCSS.js");
|
|
4815
|
+
appendCSS(__webpack_require__(/*! ./models.css */ "./frontend/src/models/models.css"));
|
|
4816
|
+
|
|
4817
|
+
const limit = 20;
|
|
4818
|
+
const OUTPUT_TYPE_STORAGE_KEY = 'studio:model-output-type';
|
|
4819
|
+
|
|
4820
|
+
module.exports = app => app.component('models', {
|
|
4821
|
+
template: template,
|
|
4822
|
+
props: ['model', 'user', 'roles'],
|
|
4823
|
+
data: () => ({
|
|
4824
|
+
models: [],
|
|
4825
|
+
currentModel: null,
|
|
4826
|
+
documents: [],
|
|
4827
|
+
schemaPaths: [],
|
|
4828
|
+
filteredPaths: [],
|
|
4829
|
+
selectedPaths: [],
|
|
4830
|
+
numDocuments: null,
|
|
4831
|
+
mongoDBIndexes: [],
|
|
4832
|
+
schemaIndexes: [],
|
|
4833
|
+
status: 'loading',
|
|
4834
|
+
loadedAllDocs: false,
|
|
4835
|
+
edittingDoc: null,
|
|
4836
|
+
docEdits: null,
|
|
4837
|
+
selectMultiple: false,
|
|
4838
|
+
selectedDocuments: [],
|
|
4839
|
+
searchText: '',
|
|
4840
|
+
shouldShowExportModal: false,
|
|
4841
|
+
shouldShowCreateModal: false,
|
|
4842
|
+
shouldShowFieldModal: false,
|
|
4843
|
+
shouldShowIndexModal: false,
|
|
4844
|
+
shouldShowUpdateMultipleModal: false,
|
|
4845
|
+
shouldShowDeleteMultipleModal: false,
|
|
4846
|
+
shouldExport: {},
|
|
4847
|
+
sortBy: {},
|
|
4848
|
+
query: {},
|
|
4849
|
+
scrollHeight: 0,
|
|
4850
|
+
interval: null,
|
|
4851
|
+
outputType: 'table', // json, table
|
|
4852
|
+
hideSidebar: null,
|
|
4853
|
+
lastSelectedIndex: null,
|
|
4854
|
+
error: null
|
|
4855
|
+
}),
|
|
4856
|
+
created() {
|
|
4857
|
+
this.currentModel = this.model;
|
|
4858
|
+
this.loadOutputPreference();
|
|
4859
|
+
},
|
|
4860
|
+
beforeDestroy() {
|
|
4861
|
+
document.removeEventListener('scroll', this.onScroll, true);
|
|
4862
|
+
window.removeEventListener('popstate', this.onPopState, true);
|
|
4863
|
+
},
|
|
4864
|
+
async mounted() {
|
|
4865
|
+
this.onScroll = () => this.checkIfScrolledToBottom();
|
|
4866
|
+
document.addEventListener('scroll', this.onScroll, true);
|
|
4867
|
+
this.onPopState = () => this.initSearchFromUrl();
|
|
4868
|
+
window.addEventListener('popstate', this.onPopState, true);
|
|
4869
|
+
const { models, readyState } = await api.Model.listModels();
|
|
4870
|
+
this.models = models;
|
|
4871
|
+
if (this.currentModel == null && this.models.length > 0) {
|
|
4872
|
+
this.currentModel = this.models[0];
|
|
4873
|
+
}
|
|
4874
|
+
if (this.models.length === 0) {
|
|
4875
|
+
this.status = 'loaded';
|
|
4876
|
+
this.numDocuments = 0;
|
|
4877
|
+
if (readyState === 0) {
|
|
4878
|
+
this.error = 'No models found and Mongoose is not connected. Check our documentation for more information.';
|
|
4879
|
+
}
|
|
4880
|
+
}
|
|
4881
|
+
|
|
4882
|
+
await this.initSearchFromUrl();
|
|
4883
|
+
},
|
|
4884
|
+
computed: {
|
|
4885
|
+
referenceMap() {
|
|
4886
|
+
const map = {};
|
|
4887
|
+
for (const path of this.filteredPaths) {
|
|
4888
|
+
if (path?.ref) {
|
|
4889
|
+
map[path.path] = path.ref;
|
|
4890
|
+
}
|
|
4891
|
+
}
|
|
4892
|
+
return map;
|
|
4893
|
+
}
|
|
4894
|
+
},
|
|
4895
|
+
methods: {
|
|
4896
|
+
loadOutputPreference() {
|
|
4897
|
+
if (typeof window === 'undefined' || !window.localStorage) {
|
|
4898
|
+
return;
|
|
4899
|
+
}
|
|
4900
|
+
const storedPreference = window.localStorage.getItem(OUTPUT_TYPE_STORAGE_KEY);
|
|
4901
|
+
if (storedPreference === 'json' || storedPreference === 'table') {
|
|
4902
|
+
this.outputType = storedPreference;
|
|
4903
|
+
}
|
|
4904
|
+
},
|
|
4905
|
+
setOutputType(type) {
|
|
4906
|
+
if (type !== 'json' && type !== 'table') {
|
|
4907
|
+
return;
|
|
4908
|
+
}
|
|
4909
|
+
this.outputType = type;
|
|
4910
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
4911
|
+
window.localStorage.setItem(OUTPUT_TYPE_STORAGE_KEY, type);
|
|
4912
|
+
}
|
|
4913
|
+
},
|
|
4914
|
+
buildDocumentFetchParams(options = {}) {
|
|
4915
|
+
const params = {
|
|
4916
|
+
model: this.currentModel,
|
|
4917
|
+
limit
|
|
4918
|
+
};
|
|
4919
|
+
|
|
4920
|
+
if (typeof options.skip === 'number') {
|
|
4921
|
+
params.skip = options.skip;
|
|
4922
|
+
}
|
|
4923
|
+
|
|
4924
|
+
const sortKeys = Object.keys(this.sortBy);
|
|
4925
|
+
if (sortKeys.length > 0) {
|
|
4926
|
+
const key = sortKeys[0];
|
|
4927
|
+
if (typeof key === 'string' && key.length > 0) {
|
|
4928
|
+
params.sortKey = key;
|
|
4929
|
+
const direction = this.sortBy[key];
|
|
4930
|
+
if (direction !== undefined && direction !== null) {
|
|
4931
|
+
params.sortDirection = direction;
|
|
4932
|
+
}
|
|
4933
|
+
}
|
|
4934
|
+
}
|
|
4935
|
+
|
|
4936
|
+
if (typeof this.searchText === 'string' && this.searchText.trim().length > 0) {
|
|
4937
|
+
params.searchText = this.searchText;
|
|
4938
|
+
}
|
|
4939
|
+
|
|
4940
|
+
return params;
|
|
4941
|
+
},
|
|
4942
|
+
async initSearchFromUrl() {
|
|
4943
|
+
this.status = 'loading';
|
|
4944
|
+
this.query = Object.assign({}, this.$route.query); // important that this is here before the if statements
|
|
4945
|
+
if (this.$route.query?.search) {
|
|
4946
|
+
this.searchText = this.$route.query.search;
|
|
4947
|
+
} else {
|
|
4948
|
+
this.searchText = '';
|
|
4949
|
+
}
|
|
4950
|
+
if (this.$route.query?.sort) {
|
|
4951
|
+
const sort = eval(`(${this.$route.query.sort})`);
|
|
4952
|
+
const path = Object.keys(sort)[0];
|
|
4953
|
+
const num = Object.values(sort)[0];
|
|
4954
|
+
this.sortDocs(num, path);
|
|
4955
|
+
}
|
|
4956
|
+
|
|
4957
|
+
|
|
4958
|
+
if (this.currentModel != null) {
|
|
4959
|
+
await this.getDocuments();
|
|
4960
|
+
}
|
|
4961
|
+
if (this.$route.query?.fields) {
|
|
4962
|
+
const filter = this.$route.query.fields.split(',');
|
|
4963
|
+
this.filteredPaths = this.filteredPaths.filter(x => filter.includes(x.path));
|
|
4964
|
+
}
|
|
4965
|
+
this.status = 'loaded';
|
|
4966
|
+
},
|
|
4967
|
+
async dropIndex(name) {
|
|
4968
|
+
const { mongoDBIndexes } = await api.Model.dropIndex({ model: this.currentModel, name });
|
|
4969
|
+
this.mongoDBIndexes = mongoDBIndexes;
|
|
4901
4970
|
},
|
|
4902
4971
|
async closeCreationModal() {
|
|
4903
4972
|
this.shouldShowCreateModal = false;
|
|
@@ -4908,9 +4977,10 @@ module.exports = app => app.component('models', {
|
|
|
4908
4977
|
},
|
|
4909
4978
|
filterDocument(doc) {
|
|
4910
4979
|
const filteredDoc = {};
|
|
4911
|
-
console.log(doc, this.filteredPaths);
|
|
4912
4980
|
for (let i = 0; i < this.filteredPaths.length; i++) {
|
|
4913
|
-
|
|
4981
|
+
const path = this.filteredPaths[i].path;
|
|
4982
|
+
const value = mpath.get(path, doc);
|
|
4983
|
+
mpath.set(path, value, filteredDoc);
|
|
4914
4984
|
}
|
|
4915
4985
|
return filteredDoc;
|
|
4916
4986
|
},
|
|
@@ -4947,7 +5017,8 @@ module.exports = app => app.component('models', {
|
|
|
4947
5017
|
}
|
|
4948
5018
|
await this.loadMoreDocuments();
|
|
4949
5019
|
},
|
|
4950
|
-
async search() {
|
|
5020
|
+
async search(searchText) {
|
|
5021
|
+
this.searchText = searchText;
|
|
4951
5022
|
const hasSearch = typeof this.searchText === 'string' && this.searchText.trim().length > 0;
|
|
4952
5023
|
if (hasSearch) {
|
|
4953
5024
|
this.query.search = this.searchText;
|
|
@@ -4962,6 +5033,11 @@ module.exports = app => app.component('models', {
|
|
|
4962
5033
|
await this.loadMoreDocuments();
|
|
4963
5034
|
this.status = 'loaded';
|
|
4964
5035
|
},
|
|
5036
|
+
addPathFilter(path) {
|
|
5037
|
+
if (this.$refs.documentSearch?.addPathFilter) {
|
|
5038
|
+
this.$refs.documentSearch.addPathFilter(path);
|
|
5039
|
+
}
|
|
5040
|
+
},
|
|
4965
5041
|
async openIndexModal() {
|
|
4966
5042
|
this.shouldShowIndexModal = true;
|
|
4967
5043
|
const { mongoDBIndexes, schemaIndexes } = await api.Model.getIndexes({ model: this.currentModel });
|
|
@@ -4981,7 +5057,6 @@ module.exports = app => app.component('models', {
|
|
|
4981
5057
|
// Clear previous data
|
|
4982
5058
|
this.documents = [];
|
|
4983
5059
|
this.schemaPaths = [];
|
|
4984
|
-
this.buildAutocompleteTrie();
|
|
4985
5060
|
this.numDocuments = null;
|
|
4986
5061
|
this.loadedAllDocs = false;
|
|
4987
5062
|
this.lastSelectedIndex = null;
|
|
@@ -5009,7 +5084,6 @@ module.exports = app => app.component('models', {
|
|
|
5009
5084
|
}
|
|
5010
5085
|
this.filteredPaths = [...this.schemaPaths];
|
|
5011
5086
|
this.selectedPaths = [...this.schemaPaths];
|
|
5012
|
-
this.buildAutocompleteTrie();
|
|
5013
5087
|
schemaPathsReceived = true;
|
|
5014
5088
|
}
|
|
5015
5089
|
if (event.numDocs !== undefined) {
|
|
@@ -5239,6 +5313,7 @@ class TrieNode {
|
|
|
5239
5313
|
this.children = Object.create(null);
|
|
5240
5314
|
this.isEnd = false;
|
|
5241
5315
|
this.freq = 0;
|
|
5316
|
+
this.roles = new Set(); // semantic roles like 'fieldName', 'operator'
|
|
5242
5317
|
}
|
|
5243
5318
|
}
|
|
5244
5319
|
|
|
@@ -5247,7 +5322,7 @@ class Trie {
|
|
|
5247
5322
|
this.root = new TrieNode();
|
|
5248
5323
|
}
|
|
5249
5324
|
|
|
5250
|
-
insert(word, freq = 1) {
|
|
5325
|
+
insert(word, freq = 1, role = null) {
|
|
5251
5326
|
if (!word) {
|
|
5252
5327
|
return;
|
|
5253
5328
|
}
|
|
@@ -5260,27 +5335,25 @@ class Trie {
|
|
|
5260
5335
|
}
|
|
5261
5336
|
node.isEnd = true;
|
|
5262
5337
|
node.freq += freq;
|
|
5338
|
+
if (role) {
|
|
5339
|
+
node.roles.add(role);
|
|
5340
|
+
}
|
|
5263
5341
|
}
|
|
5264
5342
|
|
|
5265
|
-
bulkInsert(words, freq = 1) {
|
|
5266
|
-
|
|
5267
|
-
return;
|
|
5268
|
-
}
|
|
5269
|
-
for (const word of words) {
|
|
5270
|
-
this.insert(word, freq);
|
|
5271
|
-
}
|
|
5343
|
+
bulkInsert(words, freq = 1, role = null) {
|
|
5344
|
+
for (const word of words) this.insert(word, freq, role);
|
|
5272
5345
|
}
|
|
5273
5346
|
|
|
5274
|
-
collect(node, prefix, out) {
|
|
5275
|
-
if (node.isEnd) {
|
|
5347
|
+
collect(node, prefix, out, role) {
|
|
5348
|
+
if (node.isEnd && (role == null || node.roles.has(role))) {
|
|
5276
5349
|
out.push([prefix, node.freq]);
|
|
5277
5350
|
}
|
|
5278
5351
|
for (const [ch, child] of Object.entries(node.children)) {
|
|
5279
|
-
this.collect(child, prefix + ch, out);
|
|
5352
|
+
this.collect(child, prefix + ch, out, role);
|
|
5280
5353
|
}
|
|
5281
5354
|
}
|
|
5282
5355
|
|
|
5283
|
-
suggest(prefix, limit = 10) {
|
|
5356
|
+
suggest(prefix, limit = 10, role = null) {
|
|
5284
5357
|
let node = this.root;
|
|
5285
5358
|
for (const ch of prefix) {
|
|
5286
5359
|
if (!node.children[ch]) {
|
|
@@ -5289,19 +5362,19 @@ class Trie {
|
|
|
5289
5362
|
node = node.children[ch];
|
|
5290
5363
|
}
|
|
5291
5364
|
const results = [];
|
|
5292
|
-
this.collect(node, prefix, results);
|
|
5365
|
+
this.collect(node, prefix, results, role);
|
|
5293
5366
|
results.sort((a, b) => b[1] - a[1]);
|
|
5294
5367
|
return results.slice(0, limit).map(([word]) => word);
|
|
5295
5368
|
}
|
|
5296
5369
|
|
|
5297
|
-
fuzzySuggest(prefix, limit = 10) {
|
|
5370
|
+
fuzzySuggest(prefix, limit = 10, role = null) {
|
|
5298
5371
|
const results = new Set();
|
|
5299
5372
|
|
|
5300
5373
|
const dfs = (node, path, edits) => {
|
|
5301
5374
|
if (edits > 1) {
|
|
5302
5375
|
return;
|
|
5303
5376
|
}
|
|
5304
|
-
if (node.isEnd && Math.abs(path.length - prefix.length) <= 1) {
|
|
5377
|
+
if (node.isEnd && Math.abs(path.length - prefix.length) <= 1 && (role == null || node.roles.has(role))) {
|
|
5305
5378
|
const dist = levenshtein(prefix, path);
|
|
5306
5379
|
if (dist <= 1) {
|
|
5307
5380
|
results.add(path);
|
|
@@ -5317,17 +5390,44 @@ class Trie {
|
|
|
5317
5390
|
return Array.from(results).slice(0, limit);
|
|
5318
5391
|
}
|
|
5319
5392
|
|
|
5320
|
-
getSuggestions(prefix, limit = 10) {
|
|
5393
|
+
getSuggestions(prefix, limit = 10, role = null) {
|
|
5321
5394
|
if (!prefix) {
|
|
5322
5395
|
return [];
|
|
5323
5396
|
}
|
|
5324
|
-
const exact = this.suggest(prefix, limit);
|
|
5397
|
+
const exact = this.suggest(prefix, limit, role);
|
|
5325
5398
|
if (exact.length >= limit) {
|
|
5326
5399
|
return exact;
|
|
5327
5400
|
}
|
|
5328
|
-
const fuzzy = this.fuzzySuggest(prefix, limit - exact.length);
|
|
5401
|
+
const fuzzy = this.fuzzySuggest(prefix, limit - exact.length, role);
|
|
5329
5402
|
return [...exact, ...fuzzy];
|
|
5330
5403
|
}
|
|
5404
|
+
|
|
5405
|
+
toString() {
|
|
5406
|
+
const lines = [];
|
|
5407
|
+
function dfs(node, prefix, depth) {
|
|
5408
|
+
let line = ' '.repeat(depth);
|
|
5409
|
+
if (prefix.length > 0) {
|
|
5410
|
+
line += prefix[prefix.length - 1];
|
|
5411
|
+
} else {
|
|
5412
|
+
line += '(root)';
|
|
5413
|
+
}
|
|
5414
|
+
if (node.isEnd) {
|
|
5415
|
+
line += ' *';
|
|
5416
|
+
}
|
|
5417
|
+
if (node.roles.size > 0) {
|
|
5418
|
+
line += ' [' + Array.from(node.roles).join(',') + ']';
|
|
5419
|
+
}
|
|
5420
|
+
if (node.freq > 0) {
|
|
5421
|
+
line += ` {freq:${node.freq}}`;
|
|
5422
|
+
}
|
|
5423
|
+
lines.push(line);
|
|
5424
|
+
for (const ch of Object.keys(node.children).sort()) {
|
|
5425
|
+
dfs(node.children[ch], prefix + ch, depth + 1);
|
|
5426
|
+
}
|
|
5427
|
+
}
|
|
5428
|
+
dfs(this.root, '', 0);
|
|
5429
|
+
return lines.join('\n');
|
|
5430
|
+
}
|
|
5331
5431
|
}
|
|
5332
5432
|
|
|
5333
5433
|
function levenshtein(a, b) {
|
|
@@ -16609,7 +16709,7 @@ module.exports = function stringToParts(str) {
|
|
|
16609
16709
|
/***/ ((module) => {
|
|
16610
16710
|
|
|
16611
16711
|
"use strict";
|
|
16612
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.1.
|
|
16712
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.1.7","description":"A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.","homepage":"https://studio.mongoosejs.io/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vanillatoasts":"^1.6.0","vue":"3.x","webpack":"5.x"},"peerDependencies":{"bson":"^5.5.1 || 6.x","express":"4.x","mongoose":"7.x || 8.x || ^9.0.0-0"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"9.x"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js"}}');
|
|
16613
16713
|
|
|
16614
16714
|
/***/ })
|
|
16615
16715
|
|