@mongoosejs/studio 0.3.3 → 0.3.4
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 +16 -7
- package/backend/actions/Model/getDocumentsStream.js +16 -9
- package/backend/actions/Model/getSuggestedProjection.js +33 -0
- package/backend/actions/Model/index.js +1 -0
- package/backend/authorize.js +1 -0
- package/backend/helpers/getSuggestedProjection.js +37 -0
- package/backend/helpers/parseFieldsParam.js +36 -0
- package/frontend/public/app.js +690 -172
- package/frontend/public/dark-theme.css +166 -0
- package/frontend/public/theme-variables.css +30 -0
- package/frontend/public/tw.css +110 -46
- package/frontend/src/api.js +7 -1
- package/frontend/src/dashboard/dashboard.js +1 -1
- package/frontend/src/list-mixed/list-mixed.html +0 -1
- package/frontend/src/list-string/list-string.html +0 -1
- package/frontend/src/models/models.css +25 -90
- package/frontend/src/models/models.html +213 -53
- package/frontend/src/models/models.js +611 -132
- package/frontend/src/navbar/navbar.js +1 -1
- package/frontend/src/task-by-name/task-by-name.html +11 -11
- package/frontend/src/task-single/task-single.html +4 -4
- package/frontend/src/tasks/task-details/task-details.html +6 -6
- package/package.json +1 -1
package/frontend/public/app.js
CHANGED
|
@@ -750,9 +750,12 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
750
750
|
getDocuments: function getDocuments(params) {
|
|
751
751
|
return client.post('', { action: 'Model.getDocuments', ...params }).then(res => res.data);
|
|
752
752
|
},
|
|
753
|
+
getSuggestedProjection: function getSuggestedProjection(params) {
|
|
754
|
+
return client.post('', { action: 'Model.getSuggestedProjection', ...params }).then(res => res.data);
|
|
755
|
+
},
|
|
753
756
|
getDocumentsStream: async function* getDocumentsStream(params) {
|
|
754
757
|
const data = await client.post('', { action: 'Model.getDocuments', ...params }).then(res => res.data);
|
|
755
|
-
yield { schemaPaths: data.schemaPaths };
|
|
758
|
+
yield { schemaPaths: data.schemaPaths, suggestedFields: data.suggestedFields };
|
|
756
759
|
yield { numDocs: data.numDocs };
|
|
757
760
|
for (const doc of data.docs) {
|
|
758
761
|
yield { document: doc };
|
|
@@ -965,6 +968,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
965
968
|
getDocuments: function getDocuments(params) {
|
|
966
969
|
return client.post('/Model/getDocuments', params).then(res => res.data);
|
|
967
970
|
},
|
|
971
|
+
getSuggestedProjection: function getSuggestedProjection(params) {
|
|
972
|
+
return client.post('/Model/getSuggestedProjection', params).then(res => res.data);
|
|
973
|
+
},
|
|
968
974
|
getDocumentsStream: async function* getDocumentsStream(params) {
|
|
969
975
|
const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
|
|
970
976
|
const url = window.MONGOOSE_STUDIO_CONFIG.baseURL + '/Model/getDocumentsStream?' + new URLSearchParams(params).toString();
|
|
@@ -2732,7 +2738,7 @@ module.exports = {
|
|
|
2732
2738
|
return this.dashboardResults.length > 0 ? this.dashboardResults[0] : null;
|
|
2733
2739
|
}
|
|
2734
2740
|
},
|
|
2735
|
-
mounted: async function
|
|
2741
|
+
mounted: async function() {
|
|
2736
2742
|
window.pageState = this;
|
|
2737
2743
|
|
|
2738
2744
|
document.addEventListener('click', this.handleDocumentClick);
|
|
@@ -7056,11 +7062,66 @@ const appendCSS = __webpack_require__(/*! ../appendCSS */ "./frontend/src/append
|
|
|
7056
7062
|
appendCSS(__webpack_require__(/*! ./models.css */ "./frontend/src/models/models.css"));
|
|
7057
7063
|
|
|
7058
7064
|
const limit = 20;
|
|
7065
|
+
const DEFAULT_FIRST_N_FIELDS = 6;
|
|
7059
7066
|
const OUTPUT_TYPE_STORAGE_KEY = 'studio:model-output-type';
|
|
7060
7067
|
const SELECTED_GEO_FIELD_STORAGE_KEY = 'studio:model-selected-geo-field';
|
|
7068
|
+
const PROJECTION_STORAGE_KEY_PREFIX = 'studio:model-projection:';
|
|
7069
|
+
const SHOW_ROW_NUMBERS_STORAGE_KEY = 'studio:model-show-row-numbers';
|
|
7070
|
+
const PROJECTION_MODE_QUERY_KEY = 'projectionMode';
|
|
7061
7071
|
const RECENTLY_VIEWED_MODELS_KEY = 'studio:recently-viewed-models';
|
|
7062
7072
|
const MAX_RECENT_MODELS = 4;
|
|
7063
7073
|
|
|
7074
|
+
/** Parse `fields` from the route (JSON array or inclusion projection object only). */
|
|
7075
|
+
function parseFieldsQueryParam(fields) {
|
|
7076
|
+
if (fields == null || fields === '') {
|
|
7077
|
+
return [];
|
|
7078
|
+
}
|
|
7079
|
+
const s = typeof fields === 'string' ? fields : String(fields);
|
|
7080
|
+
const trimmed = s.trim();
|
|
7081
|
+
if (!trimmed) {
|
|
7082
|
+
return [];
|
|
7083
|
+
}
|
|
7084
|
+
let parsed;
|
|
7085
|
+
try {
|
|
7086
|
+
parsed = JSON.parse(trimmed);
|
|
7087
|
+
} catch (e) {
|
|
7088
|
+
return [];
|
|
7089
|
+
}
|
|
7090
|
+
if (Array.isArray(parsed)) {
|
|
7091
|
+
return parsed.map(x => String(x).trim()).filter(Boolean);
|
|
7092
|
+
}
|
|
7093
|
+
if (parsed != null && typeof parsed === 'object') {
|
|
7094
|
+
return Object.keys(parsed).filter(k =>
|
|
7095
|
+
Object.prototype.hasOwnProperty.call(parsed, k) && parsed[k]
|
|
7096
|
+
);
|
|
7097
|
+
}
|
|
7098
|
+
return [];
|
|
7099
|
+
}
|
|
7100
|
+
|
|
7101
|
+
/** Pass through a valid JSON `fields` string for Model.getDocuments / getDocumentsStream. */
|
|
7102
|
+
function normalizeFieldsParamForApi(fieldsStr) {
|
|
7103
|
+
if (fieldsStr == null || fieldsStr === '') {
|
|
7104
|
+
return null;
|
|
7105
|
+
}
|
|
7106
|
+
const s = typeof fieldsStr === 'string' ? fieldsStr : String(fieldsStr);
|
|
7107
|
+
const trimmed = s.trim();
|
|
7108
|
+
if (!trimmed) {
|
|
7109
|
+
return null;
|
|
7110
|
+
}
|
|
7111
|
+
try {
|
|
7112
|
+
const parsed = JSON.parse(trimmed);
|
|
7113
|
+
if (Array.isArray(parsed)) {
|
|
7114
|
+
return trimmed;
|
|
7115
|
+
}
|
|
7116
|
+
if (parsed != null && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
7117
|
+
return trimmed;
|
|
7118
|
+
}
|
|
7119
|
+
} catch (e) {
|
|
7120
|
+
return null;
|
|
7121
|
+
}
|
|
7122
|
+
return null;
|
|
7123
|
+
}
|
|
7124
|
+
|
|
7064
7125
|
module.exports = app => app.component('models', {
|
|
7065
7126
|
template: template,
|
|
7066
7127
|
props: ['model', 'user', 'roles'],
|
|
@@ -7076,6 +7137,7 @@ module.exports = app => app.component('models', {
|
|
|
7076
7137
|
mongoDBIndexes: [],
|
|
7077
7138
|
schemaIndexes: [],
|
|
7078
7139
|
status: 'loading',
|
|
7140
|
+
loadingMore: false,
|
|
7079
7141
|
loadedAllDocs: false,
|
|
7080
7142
|
edittingDoc: null,
|
|
7081
7143
|
docEdits: null,
|
|
@@ -7084,7 +7146,10 @@ module.exports = app => app.component('models', {
|
|
|
7084
7146
|
searchText: '',
|
|
7085
7147
|
shouldShowExportModal: false,
|
|
7086
7148
|
shouldShowCreateModal: false,
|
|
7087
|
-
|
|
7149
|
+
projectionText: '',
|
|
7150
|
+
isProjectionMenuSelected: false,
|
|
7151
|
+
addFieldFilterText: '',
|
|
7152
|
+
showAddFieldDropdown: false,
|
|
7088
7153
|
shouldShowIndexModal: false,
|
|
7089
7154
|
shouldShowCollectionInfoModal: false,
|
|
7090
7155
|
shouldShowUpdateMultipleModal: false,
|
|
@@ -7106,27 +7171,44 @@ module.exports = app => app.component('models', {
|
|
|
7106
7171
|
collectionInfo: null,
|
|
7107
7172
|
modelSearch: '',
|
|
7108
7173
|
recentlyViewedModels: [],
|
|
7109
|
-
showModelSwitcher: false
|
|
7174
|
+
showModelSwitcher: false,
|
|
7175
|
+
showRowNumbers: true,
|
|
7176
|
+
suppressScrollCheck: false,
|
|
7177
|
+
scrollTopToRestore: null
|
|
7110
7178
|
}),
|
|
7111
7179
|
created() {
|
|
7112
7180
|
this.currentModel = this.model;
|
|
7113
7181
|
this.setSearchTextFromRoute();
|
|
7114
7182
|
this.loadOutputPreference();
|
|
7115
7183
|
this.loadSelectedGeoField();
|
|
7184
|
+
this.loadShowRowNumbersPreference();
|
|
7116
7185
|
this.loadRecentlyViewedModels();
|
|
7186
|
+
this.isProjectionMenuSelected = this.$route?.query?.[PROJECTION_MODE_QUERY_KEY] === '1';
|
|
7117
7187
|
},
|
|
7118
7188
|
beforeDestroy() {
|
|
7119
|
-
document.removeEventListener('scroll', this.onScroll, true);
|
|
7120
7189
|
window.removeEventListener('popstate', this.onPopState, true);
|
|
7121
7190
|
document.removeEventListener('click', this.onOutsideActionsMenuClick, true);
|
|
7191
|
+
document.removeEventListener('click', this.onOutsideAddFieldDropdownClick, true);
|
|
7122
7192
|
document.documentElement.removeEventListener('studio-theme-changed', this.onStudioThemeChanged);
|
|
7123
7193
|
document.removeEventListener('keydown', this.onCtrlP, true);
|
|
7124
7194
|
this.destroyMap();
|
|
7125
7195
|
},
|
|
7126
7196
|
async mounted() {
|
|
7197
|
+
// Persist scroll restoration across remounts.
|
|
7198
|
+
// This component is keyed by `$route.fullPath`, so query changes (e.g. projection updates)
|
|
7199
|
+
// recreate the component and reset scroll position.
|
|
7200
|
+
if (typeof window !== 'undefined') {
|
|
7201
|
+
if (typeof window.__studioModelsScrollTopToRestore === 'number') {
|
|
7202
|
+
this.scrollTopToRestore = window.__studioModelsScrollTopToRestore;
|
|
7203
|
+
}
|
|
7204
|
+
if (window.__studioModelsSuppressScrollCheck === true) {
|
|
7205
|
+
this.suppressScrollCheck = true;
|
|
7206
|
+
}
|
|
7207
|
+
delete window.__studioModelsScrollTopToRestore;
|
|
7208
|
+
delete window.__studioModelsSuppressScrollCheck;
|
|
7209
|
+
}
|
|
7210
|
+
|
|
7127
7211
|
window.pageState = this;
|
|
7128
|
-
this.onScroll = () => this.checkIfScrolledToBottom();
|
|
7129
|
-
document.addEventListener('scroll', this.onScroll, true);
|
|
7130
7212
|
this.onPopState = () => this.initSearchFromUrl();
|
|
7131
7213
|
window.addEventListener('popstate', this.onPopState, true);
|
|
7132
7214
|
this.onOutsideActionsMenuClick = event => {
|
|
@@ -7138,7 +7220,18 @@ module.exports = app => app.component('models', {
|
|
|
7138
7220
|
this.closeActionsMenu();
|
|
7139
7221
|
}
|
|
7140
7222
|
};
|
|
7223
|
+
this.onOutsideAddFieldDropdownClick = event => {
|
|
7224
|
+
if (!this.showAddFieldDropdown) {
|
|
7225
|
+
return;
|
|
7226
|
+
}
|
|
7227
|
+
const container = this.$refs.addFieldContainer;
|
|
7228
|
+
if (container && !container.contains(event.target)) {
|
|
7229
|
+
this.showAddFieldDropdown = false;
|
|
7230
|
+
this.addFieldFilterText = '';
|
|
7231
|
+
}
|
|
7232
|
+
};
|
|
7141
7233
|
document.addEventListener('click', this.onOutsideActionsMenuClick, true);
|
|
7234
|
+
document.addEventListener('click', this.onOutsideAddFieldDropdownClick, true);
|
|
7142
7235
|
this.onStudioThemeChanged = () => this.updateMapTileLayer();
|
|
7143
7236
|
document.documentElement.addEventListener('studio-theme-changed', this.onStudioThemeChanged);
|
|
7144
7237
|
this.onCtrlP = (event) => {
|
|
@@ -7149,6 +7242,8 @@ module.exports = app => app.component('models', {
|
|
|
7149
7242
|
};
|
|
7150
7243
|
document.addEventListener('keydown', this.onCtrlP, true);
|
|
7151
7244
|
this.query = Object.assign({}, this.$route.query);
|
|
7245
|
+
// Keep UI mode in sync with the URL on remounts.
|
|
7246
|
+
this.isProjectionMenuSelected = this.$route?.query?.[PROJECTION_MODE_QUERY_KEY] === '1';
|
|
7152
7247
|
const { models, readyState } = await api.Model.listModels();
|
|
7153
7248
|
this.models = models;
|
|
7154
7249
|
await this.loadModelCounts();
|
|
@@ -7164,8 +7259,27 @@ module.exports = app => app.component('models', {
|
|
|
7164
7259
|
}
|
|
7165
7260
|
|
|
7166
7261
|
await this.initSearchFromUrl();
|
|
7262
|
+
if (this.isProjectionMenuSelected && this.outputType === 'map') {
|
|
7263
|
+
// Projection input is not rendered in map view.
|
|
7264
|
+
this.setOutputType('json');
|
|
7265
|
+
}
|
|
7266
|
+
this.$nextTick(() => {
|
|
7267
|
+
if (!this.isProjectionMenuSelected) return;
|
|
7268
|
+
const input = this.$refs.projectionInput;
|
|
7269
|
+
if (input && typeof input.focus === 'function') {
|
|
7270
|
+
input.focus();
|
|
7271
|
+
}
|
|
7272
|
+
});
|
|
7167
7273
|
},
|
|
7168
7274
|
watch: {
|
|
7275
|
+
model(newModel) {
|
|
7276
|
+
if (newModel !== this.currentModel) {
|
|
7277
|
+
this.currentModel = newModel;
|
|
7278
|
+
if (this.currentModel != null) {
|
|
7279
|
+
this.initSearchFromUrl();
|
|
7280
|
+
}
|
|
7281
|
+
}
|
|
7282
|
+
},
|
|
7169
7283
|
documents: {
|
|
7170
7284
|
handler() {
|
|
7171
7285
|
if (this.outputType === 'map' && this.mapInstance) {
|
|
@@ -7270,6 +7384,19 @@ module.exports = app => app.component('models', {
|
|
|
7270
7384
|
}
|
|
7271
7385
|
|
|
7272
7386
|
return geoFields;
|
|
7387
|
+
},
|
|
7388
|
+
availablePathsToAdd() {
|
|
7389
|
+
const currentPaths = new Set(this.filteredPaths.map(p => p.path));
|
|
7390
|
+
return this.schemaPaths.filter(p => !currentPaths.has(p.path));
|
|
7391
|
+
},
|
|
7392
|
+
filteredPathsToAdd() {
|
|
7393
|
+
const available = this.availablePathsToAdd;
|
|
7394
|
+
const query = (this.addFieldFilterText || '').trim().toLowerCase();
|
|
7395
|
+
if (!query) return available;
|
|
7396
|
+
return available.filter(p => p.path.toLowerCase().includes(query));
|
|
7397
|
+
},
|
|
7398
|
+
tableDisplayPaths() {
|
|
7399
|
+
return this.filteredPaths.length > 0 ? this.filteredPaths : this.schemaPaths;
|
|
7273
7400
|
}
|
|
7274
7401
|
},
|
|
7275
7402
|
methods: {
|
|
@@ -7334,6 +7461,24 @@ module.exports = app => app.component('models', {
|
|
|
7334
7461
|
this.selectedGeoField = storedField;
|
|
7335
7462
|
}
|
|
7336
7463
|
},
|
|
7464
|
+
loadShowRowNumbersPreference() {
|
|
7465
|
+
if (typeof window === 'undefined' || !window.localStorage) {
|
|
7466
|
+
return;
|
|
7467
|
+
}
|
|
7468
|
+
const stored = window.localStorage.getItem(SHOW_ROW_NUMBERS_STORAGE_KEY);
|
|
7469
|
+
if (stored === '0') {
|
|
7470
|
+
this.showRowNumbers = false;
|
|
7471
|
+
} else if (stored === '1') {
|
|
7472
|
+
this.showRowNumbers = true;
|
|
7473
|
+
}
|
|
7474
|
+
},
|
|
7475
|
+
toggleRowNumbers() {
|
|
7476
|
+
this.showRowNumbers = !this.showRowNumbers;
|
|
7477
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
7478
|
+
window.localStorage.setItem(SHOW_ROW_NUMBERS_STORAGE_KEY, this.showRowNumbers ? '1' : '0');
|
|
7479
|
+
}
|
|
7480
|
+
this.showActionsMenu = false;
|
|
7481
|
+
},
|
|
7337
7482
|
setOutputType(type) {
|
|
7338
7483
|
if (type !== 'json' && type !== 'table' && type !== 'map') {
|
|
7339
7484
|
return;
|
|
@@ -7529,6 +7674,22 @@ module.exports = app => app.component('models', {
|
|
|
7529
7674
|
params.searchText = this.searchText;
|
|
7530
7675
|
}
|
|
7531
7676
|
|
|
7677
|
+
// Prefer explicit URL projection (`query.fields`) so the first fetch after
|
|
7678
|
+
// mount/remount respects deep-linked projections before `filteredPaths`
|
|
7679
|
+
// is rehydrated from schema paths.
|
|
7680
|
+
let fieldsParam = normalizeFieldsParamForApi(this.query?.fields);
|
|
7681
|
+
if (!fieldsParam) {
|
|
7682
|
+
const fieldPaths = this.filteredPaths && this.filteredPaths.length > 0
|
|
7683
|
+
? this.filteredPaths.map(p => p.path).filter(Boolean)
|
|
7684
|
+
: null;
|
|
7685
|
+
if (fieldPaths && fieldPaths.length > 0) {
|
|
7686
|
+
fieldsParam = JSON.stringify(fieldPaths);
|
|
7687
|
+
}
|
|
7688
|
+
}
|
|
7689
|
+
if (fieldsParam) {
|
|
7690
|
+
params.fields = fieldsParam;
|
|
7691
|
+
}
|
|
7692
|
+
|
|
7532
7693
|
return params;
|
|
7533
7694
|
},
|
|
7534
7695
|
setSearchTextFromRoute() {
|
|
@@ -7542,20 +7703,35 @@ module.exports = app => app.component('models', {
|
|
|
7542
7703
|
this.status = 'loading';
|
|
7543
7704
|
this.query = Object.assign({}, this.$route.query); // important that this is here before the if statements
|
|
7544
7705
|
this.setSearchTextFromRoute();
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7706
|
+
// Avoid eval() on user-controlled query params.
|
|
7707
|
+
// Use explicit sortKey + sortDirection query params.
|
|
7708
|
+
const sortKey = this.$route.query?.sortKey;
|
|
7709
|
+
const sortDirectionRaw = this.$route.query?.sortDirection;
|
|
7710
|
+
const sortDirection = typeof sortDirectionRaw === 'string' ? Number(sortDirectionRaw) : sortDirectionRaw;
|
|
7711
|
+
|
|
7712
|
+
if (typeof sortKey === 'string' && sortKey.trim().length > 0 &&
|
|
7713
|
+
(sortDirection === 1 || sortDirection === -1)) {
|
|
7714
|
+
for (const key in this.sortBy) {
|
|
7715
|
+
delete this.sortBy[key];
|
|
7716
|
+
}
|
|
7717
|
+
this.sortBy[sortKey] = sortDirection;
|
|
7718
|
+
// Normalize to new params and remove legacy key if present.
|
|
7719
|
+
this.query.sortKey = sortKey;
|
|
7720
|
+
this.query.sortDirection = sortDirection;
|
|
7721
|
+
delete this.query.sort;
|
|
7550
7722
|
}
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
7723
|
if (this.currentModel != null) {
|
|
7554
7724
|
await this.getDocuments();
|
|
7555
7725
|
}
|
|
7556
7726
|
if (this.$route.query?.fields) {
|
|
7557
|
-
const
|
|
7558
|
-
|
|
7727
|
+
const urlPaths = parseFieldsQueryParam(this.$route.query.fields);
|
|
7728
|
+
if (urlPaths.length > 0) {
|
|
7729
|
+
this.filteredPaths = urlPaths.map(path => this.schemaPaths.find(p => p.path === path)).filter(Boolean);
|
|
7730
|
+
if (this.filteredPaths.length > 0) {
|
|
7731
|
+
this.syncProjectionFromPaths();
|
|
7732
|
+
this.saveProjectionPreference();
|
|
7733
|
+
}
|
|
7734
|
+
}
|
|
7559
7735
|
}
|
|
7560
7736
|
this.status = 'loaded';
|
|
7561
7737
|
|
|
@@ -7575,10 +7751,8 @@ module.exports = app => app.component('models', {
|
|
|
7575
7751
|
this.shouldShowCreateModal = false;
|
|
7576
7752
|
await this.getDocuments();
|
|
7577
7753
|
},
|
|
7578
|
-
initializeDocumentData() {
|
|
7579
|
-
this.shouldShowCreateModal = true;
|
|
7580
|
-
},
|
|
7581
7754
|
filterDocument(doc) {
|
|
7755
|
+
if (this.filteredPaths.length === 0) return doc;
|
|
7582
7756
|
const filteredDoc = {};
|
|
7583
7757
|
for (let i = 0; i < this.filteredPaths.length; i++) {
|
|
7584
7758
|
const path = this.filteredPaths[i].path;
|
|
@@ -7600,23 +7774,47 @@ module.exports = app => app.component('models', {
|
|
|
7600
7774
|
if (this.status === 'loading' || this.loadedAllDocs) {
|
|
7601
7775
|
return;
|
|
7602
7776
|
}
|
|
7603
|
-
|
|
7604
|
-
if (
|
|
7605
|
-
|
|
7606
|
-
|
|
7777
|
+
// Infinite scroll only applies to table/json views.
|
|
7778
|
+
if (this.outputType !== 'table' && this.outputType !== 'json') {
|
|
7779
|
+
return;
|
|
7780
|
+
}
|
|
7781
|
+
if (this.documents.length === 0) {
|
|
7782
|
+
return;
|
|
7783
|
+
}
|
|
7784
|
+
const container = this.outputType === 'table'
|
|
7785
|
+
? this.$refs.documentsScrollContainer
|
|
7786
|
+
: this.$refs.documentsContainerScroll;
|
|
7787
|
+
if (!container || container.scrollHeight <= 0) {
|
|
7788
|
+
return;
|
|
7789
|
+
}
|
|
7790
|
+
const threshold = 150;
|
|
7791
|
+
const nearBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - threshold;
|
|
7792
|
+
if (!nearBottom) {
|
|
7793
|
+
return;
|
|
7794
|
+
}
|
|
7795
|
+
this.loadingMore = true;
|
|
7796
|
+
this.status = 'loading';
|
|
7797
|
+
try {
|
|
7798
|
+
const skip = this.documents.length;
|
|
7799
|
+
const params = this.buildDocumentFetchParams({ skip });
|
|
7607
7800
|
const { docs } = await api.Model.getDocuments(params);
|
|
7608
7801
|
if (docs.length < limit) {
|
|
7609
7802
|
this.loadedAllDocs = true;
|
|
7610
7803
|
}
|
|
7611
7804
|
this.documents.push(...docs);
|
|
7805
|
+
} finally {
|
|
7806
|
+
this.loadingMore = false;
|
|
7612
7807
|
this.status = 'loaded';
|
|
7613
7808
|
}
|
|
7809
|
+
this.$nextTick(() => this.checkIfScrolledToBottom());
|
|
7614
7810
|
},
|
|
7615
7811
|
async sortDocs(num, path) {
|
|
7616
7812
|
let sorted = false;
|
|
7617
7813
|
if (this.sortBy[path] == num) {
|
|
7618
7814
|
sorted = true;
|
|
7619
7815
|
delete this.query.sort;
|
|
7816
|
+
delete this.query.sortKey;
|
|
7817
|
+
delete this.query.sortDirection;
|
|
7620
7818
|
this.$router.push({ query: this.query });
|
|
7621
7819
|
}
|
|
7622
7820
|
for (const key in this.sortBy) {
|
|
@@ -7624,9 +7822,13 @@ module.exports = app => app.component('models', {
|
|
|
7624
7822
|
}
|
|
7625
7823
|
if (!sorted) {
|
|
7626
7824
|
this.sortBy[path] = num;
|
|
7627
|
-
this.query.
|
|
7825
|
+
this.query.sortKey = path;
|
|
7826
|
+
this.query.sortDirection = num;
|
|
7827
|
+
delete this.query.sort;
|
|
7628
7828
|
this.$router.push({ query: this.query });
|
|
7629
7829
|
}
|
|
7830
|
+
this.documents = [];
|
|
7831
|
+
this.loadedAllDocs = false;
|
|
7630
7832
|
await this.loadMoreDocuments();
|
|
7631
7833
|
},
|
|
7632
7834
|
async search(searchText) {
|
|
@@ -7663,6 +7865,23 @@ module.exports = app => app.component('models', {
|
|
|
7663
7865
|
closeActionsMenu() {
|
|
7664
7866
|
this.showActionsMenu = false;
|
|
7665
7867
|
},
|
|
7868
|
+
toggleProjectionMenu() {
|
|
7869
|
+
const next = !this.isProjectionMenuSelected;
|
|
7870
|
+
this.isProjectionMenuSelected = next;
|
|
7871
|
+
|
|
7872
|
+
// Because the route-view is keyed on `$route.fullPath`, query changes remount this component.
|
|
7873
|
+
// Persist projection UI state in the URL so Reset/Suggest don't turn the mode off.
|
|
7874
|
+
if (next) {
|
|
7875
|
+
this.query[PROJECTION_MODE_QUERY_KEY] = '1';
|
|
7876
|
+
if (this.outputType === 'map') {
|
|
7877
|
+
this.setOutputType('json');
|
|
7878
|
+
}
|
|
7879
|
+
} else {
|
|
7880
|
+
delete this.query[PROJECTION_MODE_QUERY_KEY];
|
|
7881
|
+
}
|
|
7882
|
+
|
|
7883
|
+
this.$router.push({ query: this.query });
|
|
7884
|
+
},
|
|
7666
7885
|
async openCollectionInfo() {
|
|
7667
7886
|
this.closeActionsMenu();
|
|
7668
7887
|
this.shouldShowCollectionInfoModal = true;
|
|
@@ -7763,160 +7982,401 @@ module.exports = app => app.component('models', {
|
|
|
7763
7982
|
}
|
|
7764
7983
|
return formatValue(value / 1000000000, 'B');
|
|
7765
7984
|
},
|
|
7766
|
-
checkIndexLocation(indexName) {
|
|
7767
|
-
if (this.schemaIndexes.find(x => x.name == indexName) && this.mongoDBIndexes.find(x => x.name == indexName)) {
|
|
7768
|
-
return 'text-gray-500';
|
|
7769
|
-
} else if (this.schemaIndexes.find(x => x.name == indexName)) {
|
|
7770
|
-
return 'text-forest-green-500';
|
|
7771
|
-
} else {
|
|
7772
|
-
return 'text-valencia-500';
|
|
7773
|
-
}
|
|
7774
|
-
},
|
|
7775
7985
|
async getDocuments() {
|
|
7776
|
-
|
|
7777
|
-
this.
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7986
|
+
this.loadingMore = false;
|
|
7987
|
+
this.status = 'loading';
|
|
7988
|
+
try {
|
|
7989
|
+
// Track recently viewed model
|
|
7990
|
+
this.trackRecentModel(this.currentModel);
|
|
7991
|
+
|
|
7992
|
+
// Clear previous data
|
|
7993
|
+
this.documents = [];
|
|
7994
|
+
this.schemaPaths = [];
|
|
7995
|
+
this.numDocuments = null;
|
|
7996
|
+
this.loadedAllDocs = false;
|
|
7997
|
+
this.lastSelectedIndex = null;
|
|
7785
7998
|
|
|
7786
|
-
|
|
7787
|
-
|
|
7999
|
+
let docsCount = 0;
|
|
8000
|
+
let schemaPathsReceived = false;
|
|
7788
8001
|
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
8002
|
+
// Use async generator to stream SSEs
|
|
8003
|
+
const params = this.buildDocumentFetchParams();
|
|
8004
|
+
for await (const event of api.Model.getDocumentsStream(params)) {
|
|
8005
|
+
if (event.schemaPaths && !schemaPathsReceived) {
|
|
7793
8006
|
// Sort schemaPaths with _id first
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
8007
|
+
this.schemaPaths = Object.keys(event.schemaPaths).sort((k1, k2) => {
|
|
8008
|
+
if (k1 === '_id' && k2 !== '_id') {
|
|
8009
|
+
return -1;
|
|
8010
|
+
}
|
|
8011
|
+
if (k1 !== '_id' && k2 === '_id') {
|
|
8012
|
+
return 1;
|
|
8013
|
+
}
|
|
8014
|
+
return 0;
|
|
8015
|
+
}).map(key => event.schemaPaths[key]);
|
|
8016
|
+
this.shouldExport = {};
|
|
8017
|
+
for (const { path } of this.schemaPaths) {
|
|
8018
|
+
this.shouldExport[path] = true;
|
|
8019
|
+
}
|
|
8020
|
+
const shouldUseSavedProjection = this.isProjectionMenuSelected === true;
|
|
8021
|
+
const savedPaths = shouldUseSavedProjection ? this.loadProjectionPreference() : null;
|
|
8022
|
+
if (savedPaths === null) {
|
|
8023
|
+
this.applyDefaultProjection(event.suggestedFields);
|
|
8024
|
+
if (shouldUseSavedProjection) {
|
|
8025
|
+
this.saveProjectionPreference();
|
|
8026
|
+
}
|
|
8027
|
+
} else if (Array.isArray(savedPaths) && savedPaths.length === 0) {
|
|
8028
|
+
this.filteredPaths = [];
|
|
8029
|
+
this.projectionText = '';
|
|
8030
|
+
if (shouldUseSavedProjection) {
|
|
8031
|
+
this.saveProjectionPreference();
|
|
8032
|
+
}
|
|
8033
|
+
} else if (savedPaths && savedPaths.length > 0) {
|
|
8034
|
+
this.filteredPaths = savedPaths
|
|
8035
|
+
.map(path => this.schemaPaths.find(p => p.path === path))
|
|
8036
|
+
.filter(Boolean);
|
|
8037
|
+
if (this.filteredPaths.length === 0) {
|
|
8038
|
+
this.applyDefaultProjection(event.suggestedFields);
|
|
8039
|
+
if (shouldUseSavedProjection) {
|
|
8040
|
+
this.saveProjectionPreference();
|
|
8041
|
+
}
|
|
8042
|
+
}
|
|
8043
|
+
} else {
|
|
8044
|
+
this.applyDefaultProjection(event.suggestedFields);
|
|
8045
|
+
if (shouldUseSavedProjection) {
|
|
8046
|
+
this.saveProjectionPreference();
|
|
8047
|
+
}
|
|
7800
8048
|
}
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
|
|
8049
|
+
this.selectedPaths = [...this.filteredPaths];
|
|
8050
|
+
this.syncProjectionFromPaths();
|
|
8051
|
+
schemaPathsReceived = true;
|
|
8052
|
+
}
|
|
8053
|
+
if (event.numDocs !== undefined) {
|
|
8054
|
+
this.numDocuments = event.numDocs;
|
|
8055
|
+
}
|
|
8056
|
+
if (event.document) {
|
|
8057
|
+
this.documents.push(event.document);
|
|
8058
|
+
docsCount++;
|
|
8059
|
+
}
|
|
8060
|
+
if (event.message) {
|
|
8061
|
+
throw new Error(event.message);
|
|
7806
8062
|
}
|
|
7807
|
-
this.filteredPaths = [...this.schemaPaths];
|
|
7808
|
-
this.selectedPaths = [...this.schemaPaths];
|
|
7809
|
-
schemaPathsReceived = true;
|
|
7810
|
-
}
|
|
7811
|
-
if (event.numDocs !== undefined) {
|
|
7812
|
-
this.numDocuments = event.numDocs;
|
|
7813
|
-
}
|
|
7814
|
-
if (event.document) {
|
|
7815
|
-
this.documents.push(event.document);
|
|
7816
|
-
docsCount++;
|
|
7817
|
-
}
|
|
7818
|
-
if (event.message) {
|
|
7819
|
-
this.status = 'loaded';
|
|
7820
|
-
throw new Error(event.message);
|
|
7821
8063
|
}
|
|
7822
|
-
}
|
|
7823
8064
|
|
|
7824
|
-
|
|
7825
|
-
|
|
8065
|
+
if (docsCount < limit) {
|
|
8066
|
+
this.loadedAllDocs = true;
|
|
8067
|
+
}
|
|
8068
|
+
} finally {
|
|
8069
|
+
this.status = 'loaded';
|
|
7826
8070
|
}
|
|
8071
|
+
this.$nextTick(() => {
|
|
8072
|
+
this.restoreScrollPosition();
|
|
8073
|
+
if (!this.suppressScrollCheck) {
|
|
8074
|
+
this.checkIfScrolledToBottom();
|
|
8075
|
+
}
|
|
8076
|
+
this.suppressScrollCheck = false;
|
|
8077
|
+
});
|
|
7827
8078
|
},
|
|
7828
8079
|
async loadMoreDocuments() {
|
|
7829
|
-
|
|
7830
|
-
|
|
8080
|
+
const isLoadingMore = this.documents.length > 0;
|
|
8081
|
+
if (isLoadingMore) {
|
|
8082
|
+
this.loadingMore = true;
|
|
8083
|
+
}
|
|
8084
|
+
this.status = 'loading';
|
|
8085
|
+
try {
|
|
8086
|
+
let docsCount = 0;
|
|
8087
|
+
let numDocsReceived = false;
|
|
7831
8088
|
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
8089
|
+
// Use async generator to stream SSEs
|
|
8090
|
+
const params = this.buildDocumentFetchParams({ skip: this.documents.length });
|
|
8091
|
+
for await (const event of api.Model.getDocumentsStream(params)) {
|
|
8092
|
+
if (event.numDocs !== undefined && !numDocsReceived) {
|
|
8093
|
+
this.numDocuments = event.numDocs;
|
|
8094
|
+
numDocsReceived = true;
|
|
8095
|
+
}
|
|
8096
|
+
if (event.document) {
|
|
8097
|
+
this.documents.push(event.document);
|
|
8098
|
+
docsCount++;
|
|
8099
|
+
}
|
|
8100
|
+
if (event.message) {
|
|
8101
|
+
throw new Error(event.message);
|
|
8102
|
+
}
|
|
7838
8103
|
}
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
|
|
8104
|
+
|
|
8105
|
+
if (docsCount < limit) {
|
|
8106
|
+
this.loadedAllDocs = true;
|
|
7842
8107
|
}
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
8108
|
+
} finally {
|
|
8109
|
+
this.loadingMore = false;
|
|
8110
|
+
this.status = 'loaded';
|
|
8111
|
+
}
|
|
8112
|
+
this.$nextTick(() => this.checkIfScrolledToBottom());
|
|
8113
|
+
},
|
|
8114
|
+
applyDefaultProjection(suggestedFields) {
|
|
8115
|
+
if (Array.isArray(suggestedFields) && suggestedFields.length > 0) {
|
|
8116
|
+
this.filteredPaths = suggestedFields
|
|
8117
|
+
.map(path => this.schemaPaths.find(p => p.path === path))
|
|
8118
|
+
.filter(Boolean);
|
|
8119
|
+
}
|
|
8120
|
+
if (!this.filteredPaths || this.filteredPaths.length === 0) {
|
|
8121
|
+
this.filteredPaths = this.schemaPaths.slice(0, DEFAULT_FIRST_N_FIELDS);
|
|
8122
|
+
}
|
|
8123
|
+
if (this.filteredPaths.length === 0) {
|
|
8124
|
+
this.filteredPaths = this.schemaPaths.filter(p => p.path === '_id');
|
|
8125
|
+
}
|
|
8126
|
+
},
|
|
8127
|
+
loadProjectionPreference() {
|
|
8128
|
+
if (typeof window === 'undefined' || !window.localStorage || !this.currentModel) {
|
|
8129
|
+
return null;
|
|
8130
|
+
}
|
|
8131
|
+
const key = PROJECTION_STORAGE_KEY_PREFIX + this.currentModel;
|
|
8132
|
+
const stored = window.localStorage.getItem(key);
|
|
8133
|
+
if (stored === null || stored === undefined) {
|
|
8134
|
+
return null;
|
|
8135
|
+
}
|
|
8136
|
+
if (stored === '') {
|
|
8137
|
+
return [];
|
|
8138
|
+
}
|
|
8139
|
+
try {
|
|
8140
|
+
const parsed = JSON.parse(stored);
|
|
8141
|
+
if (Array.isArray(parsed)) {
|
|
8142
|
+
return parsed.map(x => String(x).trim()).filter(Boolean);
|
|
7846
8143
|
}
|
|
8144
|
+
} catch (e) {
|
|
8145
|
+
return null;
|
|
7847
8146
|
}
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
8147
|
+
return null;
|
|
8148
|
+
},
|
|
8149
|
+
saveProjectionPreference() {
|
|
8150
|
+
if (typeof window === 'undefined' || !window.localStorage || !this.currentModel) {
|
|
8151
|
+
return;
|
|
7851
8152
|
}
|
|
8153
|
+
const key = PROJECTION_STORAGE_KEY_PREFIX + this.currentModel;
|
|
8154
|
+
const paths = this.filteredPaths.map(p => p.path);
|
|
8155
|
+
window.localStorage.setItem(key, JSON.stringify(paths));
|
|
7852
8156
|
},
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
8157
|
+
clearProjection() {
|
|
8158
|
+
// Keep current filter input in sync with the URL so projection reset
|
|
8159
|
+
// does not unintentionally wipe the filter on remount.
|
|
8160
|
+
this.syncFilterToQuery();
|
|
8161
|
+
this.filteredPaths = [];
|
|
8162
|
+
this.selectedPaths = [];
|
|
8163
|
+
this.projectionText = '';
|
|
8164
|
+
this.updateProjectionQuery();
|
|
8165
|
+
this.saveProjectionPreference();
|
|
8166
|
+
},
|
|
8167
|
+
resetFilter() {
|
|
8168
|
+
// Reuse the existing "apply filter + update URL" flow.
|
|
8169
|
+
this.search('');
|
|
8170
|
+
},
|
|
8171
|
+
syncFilterToQuery() {
|
|
8172
|
+
if (typeof this.searchText === 'string' && this.searchText.trim().length > 0) {
|
|
8173
|
+
this.query.search = this.searchText;
|
|
8174
|
+
} else {
|
|
8175
|
+
delete this.query.search;
|
|
8176
|
+
}
|
|
8177
|
+
},
|
|
8178
|
+
applyDefaultProjectionColumns() {
|
|
8179
|
+
if (!this.schemaPaths || this.schemaPaths.length === 0) return;
|
|
8180
|
+
const pathNames = this.schemaPaths.map(p => p.path);
|
|
8181
|
+
this.applyDefaultProjection(pathNames.slice(0, DEFAULT_FIRST_N_FIELDS));
|
|
8182
|
+
this.selectedPaths = [...this.filteredPaths];
|
|
8183
|
+
this.syncProjectionFromPaths();
|
|
8184
|
+
this.updateProjectionQuery();
|
|
8185
|
+
this.saveProjectionPreference();
|
|
8186
|
+
},
|
|
8187
|
+
initProjection(ev) {
|
|
8188
|
+
if (!this.projectionText || !this.projectionText.trim()) {
|
|
8189
|
+
this.projectionText = '';
|
|
8190
|
+
this.$nextTick(() => {
|
|
8191
|
+
if (ev && ev.target) {
|
|
8192
|
+
ev.target.setSelectionRange(0, 0);
|
|
7865
8193
|
}
|
|
7866
|
-
|
|
7867
|
-
}).map(key => this.selectedPaths[key]);
|
|
8194
|
+
});
|
|
7868
8195
|
}
|
|
7869
8196
|
},
|
|
7870
|
-
|
|
7871
|
-
if (this
|
|
7872
|
-
this.
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
8197
|
+
syncProjectionFromPaths() {
|
|
8198
|
+
if (this.filteredPaths.length === 0) {
|
|
8199
|
+
this.projectionText = '';
|
|
8200
|
+
return;
|
|
8201
|
+
}
|
|
8202
|
+
// String-only projection syntax: `field1 field2` and `-field` for exclusions.
|
|
8203
|
+
// Since `filteredPaths` represents the final include set, we serialize as space-separated fields.
|
|
8204
|
+
this.projectionText = this.filteredPaths.map(p => p.path).join(' ');
|
|
8205
|
+
},
|
|
8206
|
+
parseProjectionInput(text) {
|
|
8207
|
+
if (!text || typeof text !== 'string') {
|
|
8208
|
+
return [];
|
|
8209
|
+
}
|
|
8210
|
+
const trimmed = text.trim();
|
|
8211
|
+
if (!trimmed) {
|
|
8212
|
+
return [];
|
|
8213
|
+
}
|
|
8214
|
+
const normalizeKey = (key) => String(key).trim();
|
|
8215
|
+
|
|
8216
|
+
// String-only projection syntax:
|
|
8217
|
+
// name email
|
|
8218
|
+
// -password (exclusion-only)
|
|
8219
|
+
// +email (inclusion-only)
|
|
8220
|
+
//
|
|
8221
|
+
// Brace/object syntax is intentionally NOT supported.
|
|
8222
|
+
if (trimmed.startsWith('{') || trimmed.endsWith('}')) {
|
|
8223
|
+
return null;
|
|
8224
|
+
}
|
|
8225
|
+
|
|
8226
|
+
const tokens = trimmed.split(/[,\s]+/).filter(Boolean);
|
|
8227
|
+
if (tokens.length === 0) return [];
|
|
8228
|
+
|
|
8229
|
+
const includeKeys = [];
|
|
8230
|
+
const excludeKeys = [];
|
|
8231
|
+
|
|
8232
|
+
for (const rawToken of tokens) {
|
|
8233
|
+
const token = rawToken.trim();
|
|
8234
|
+
if (!token) continue;
|
|
8235
|
+
|
|
8236
|
+
const prefix = token[0];
|
|
8237
|
+
if (prefix === '-') {
|
|
8238
|
+
const path = token.slice(1).trim();
|
|
8239
|
+
if (!path) return null;
|
|
8240
|
+
excludeKeys.push(path);
|
|
8241
|
+
} else if (prefix === '+') {
|
|
8242
|
+
const path = token.slice(1).trim();
|
|
8243
|
+
if (!path) return null;
|
|
8244
|
+
includeKeys.push(path);
|
|
8245
|
+
} else {
|
|
8246
|
+
includeKeys.push(token);
|
|
7877
8247
|
}
|
|
7878
|
-
} else {
|
|
7879
|
-
this.selectedPaths = [{ path: '_id' }];
|
|
7880
8248
|
}
|
|
7881
|
-
|
|
8249
|
+
|
|
8250
|
+
if (includeKeys.length > 0 && excludeKeys.length > 0) {
|
|
8251
|
+
// Support subtractive edits on an existing projection string, e.g.
|
|
8252
|
+
// `name email createdAt -email` -> `name createdAt`.
|
|
8253
|
+
const includeSet = new Set(includeKeys.map(normalizeKey));
|
|
8254
|
+
for (const path of excludeKeys) {
|
|
8255
|
+
includeSet.delete(normalizeKey(path));
|
|
8256
|
+
}
|
|
8257
|
+
return Array.from(includeSet);
|
|
8258
|
+
}
|
|
8259
|
+
|
|
8260
|
+
if (excludeKeys.length > 0) {
|
|
8261
|
+
const excludeSet = new Set(excludeKeys.map(normalizeKey));
|
|
8262
|
+
return this.schemaPaths.map(p => p.path).filter(p => !excludeSet.has(p));
|
|
8263
|
+
}
|
|
8264
|
+
|
|
8265
|
+
return includeKeys.map(normalizeKey);
|
|
7882
8266
|
},
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
8267
|
+
applyProjectionFromInput() {
|
|
8268
|
+
const paths = this.parseProjectionInput(this.projectionText);
|
|
8269
|
+
if (paths === null) {
|
|
8270
|
+
this.syncProjectionFromPaths();
|
|
8271
|
+
return;
|
|
8272
|
+
}
|
|
8273
|
+
if (paths.length === 0) {
|
|
8274
|
+
this.filteredPaths = this.schemaPaths.filter(p => p.path === '_id');
|
|
8275
|
+
if (this.filteredPaths.length === 0 && this.schemaPaths.length > 0) {
|
|
8276
|
+
const idPath = this.schemaPaths.find(p => p.path === '_id');
|
|
8277
|
+
this.filteredPaths = idPath ? [idPath] : [this.schemaPaths[0]];
|
|
8278
|
+
}
|
|
7886
8279
|
} else {
|
|
7887
|
-
this.filteredPaths
|
|
8280
|
+
this.filteredPaths = paths.map(path => this.schemaPaths.find(p => p.path === path)).filter(Boolean);
|
|
8281
|
+
const validPaths = new Set(this.schemaPaths.map(p => p.path));
|
|
8282
|
+
for (const path of paths) {
|
|
8283
|
+
if (validPaths.has(path) && !this.filteredPaths.find(p => p.path === path)) {
|
|
8284
|
+
this.filteredPaths.push(this.schemaPaths.find(p => p.path === path));
|
|
8285
|
+
}
|
|
8286
|
+
}
|
|
8287
|
+
if (this.filteredPaths.length === 0) {
|
|
8288
|
+
this.filteredPaths = this.schemaPaths.filter(p => p.path === '_id');
|
|
8289
|
+
}
|
|
7888
8290
|
}
|
|
7889
|
-
this.shouldShowFieldModal = false;
|
|
7890
|
-
const selectedParams = this.filteredPaths.map(x => x.path).join(',');
|
|
7891
|
-
this.query.fields = selectedParams;
|
|
7892
|
-
this.$router.push({ query: this.query });
|
|
7893
|
-
},
|
|
7894
|
-
resetDocuments() {
|
|
7895
8291
|
this.selectedPaths = [...this.filteredPaths];
|
|
7896
|
-
this.
|
|
8292
|
+
this.syncProjectionFromPaths();
|
|
8293
|
+
this.updateProjectionQuery();
|
|
8294
|
+
this.saveProjectionPreference();
|
|
8295
|
+
},
|
|
8296
|
+
updateProjectionQuery() {
|
|
8297
|
+
const paths = this.filteredPaths.map(x => x.path).filter(Boolean);
|
|
8298
|
+
if (paths.length > 0) {
|
|
8299
|
+
this.query.fields = JSON.stringify(paths);
|
|
8300
|
+
} else {
|
|
8301
|
+
delete this.query.fields;
|
|
8302
|
+
}
|
|
7897
8303
|
this.$router.push({ query: this.query });
|
|
7898
|
-
this.shouldShowFieldModal = false;
|
|
7899
8304
|
},
|
|
7900
|
-
|
|
7901
|
-
this.
|
|
8305
|
+
removeField(schemaPath) {
|
|
8306
|
+
if (this.outputType === 'table' && this.$refs.documentsScrollContainer) {
|
|
8307
|
+
this.scrollTopToRestore = this.$refs.documentsScrollContainer.scrollTop;
|
|
8308
|
+
this.suppressScrollCheck = true;
|
|
8309
|
+
// Persist for remount caused by query changes.
|
|
8310
|
+
if (typeof window !== 'undefined') {
|
|
8311
|
+
window.__studioModelsScrollTopToRestore = this.scrollTopToRestore;
|
|
8312
|
+
window.__studioModelsSuppressScrollCheck = true;
|
|
8313
|
+
}
|
|
8314
|
+
}
|
|
8315
|
+
const index = this.filteredPaths.findIndex(p => p.path === schemaPath.path);
|
|
8316
|
+
if (index !== -1) {
|
|
8317
|
+
this.filteredPaths.splice(index, 1);
|
|
8318
|
+
if (this.filteredPaths.length === 0) {
|
|
8319
|
+
const idPath = this.schemaPaths.find(p => p.path === '_id');
|
|
8320
|
+
this.filteredPaths = idPath ? [idPath] : [];
|
|
8321
|
+
}
|
|
8322
|
+
this.syncProjectionFromPaths();
|
|
8323
|
+
this.updateProjectionQuery();
|
|
8324
|
+
this.saveProjectionPreference();
|
|
8325
|
+
}
|
|
8326
|
+
},
|
|
8327
|
+
addField(schemaPath) {
|
|
8328
|
+
if (!this.filteredPaths.find(p => p.path === schemaPath.path)) {
|
|
8329
|
+
if (this.outputType === 'table' && this.$refs.documentsScrollContainer) {
|
|
8330
|
+
this.scrollTopToRestore = this.$refs.documentsScrollContainer.scrollTop;
|
|
8331
|
+
this.suppressScrollCheck = true;
|
|
8332
|
+
// Persist for remount caused by query changes.
|
|
8333
|
+
if (typeof window !== 'undefined') {
|
|
8334
|
+
window.__studioModelsScrollTopToRestore = this.scrollTopToRestore;
|
|
8335
|
+
window.__studioModelsSuppressScrollCheck = true;
|
|
8336
|
+
}
|
|
8337
|
+
}
|
|
8338
|
+
this.filteredPaths.push(schemaPath);
|
|
8339
|
+
this.filteredPaths.sort((a, b) => {
|
|
8340
|
+
if (a.path === '_id') return -1;
|
|
8341
|
+
if (b.path === '_id') return 1;
|
|
8342
|
+
return 0;
|
|
8343
|
+
});
|
|
8344
|
+
this.syncProjectionFromPaths();
|
|
8345
|
+
this.updateProjectionQuery();
|
|
8346
|
+
this.saveProjectionPreference();
|
|
8347
|
+
this.showAddFieldDropdown = false;
|
|
8348
|
+
this.addFieldFilterText = '';
|
|
8349
|
+
}
|
|
7902
8350
|
},
|
|
7903
|
-
|
|
7904
|
-
this.
|
|
8351
|
+
restoreScrollPosition() {
|
|
8352
|
+
if (this.outputType !== 'table') return;
|
|
8353
|
+
if (this.scrollTopToRestore == null) return;
|
|
8354
|
+
const container = this.$refs.documentsScrollContainer;
|
|
8355
|
+
if (!container) return;
|
|
8356
|
+
container.scrollTop = this.scrollTopToRestore;
|
|
8357
|
+
this.scrollTopToRestore = null;
|
|
7905
8358
|
},
|
|
7906
|
-
|
|
7907
|
-
|
|
8359
|
+
toggleAddFieldDropdown() {
|
|
8360
|
+
this.showAddFieldDropdown = !this.showAddFieldDropdown;
|
|
8361
|
+
if (this.showAddFieldDropdown) {
|
|
8362
|
+
this.addFieldFilterText = '';
|
|
8363
|
+
this.$nextTick(() => this.$refs.addFieldFilterInput?.focus());
|
|
8364
|
+
}
|
|
7908
8365
|
},
|
|
7909
8366
|
getComponentForPath(schemaPath) {
|
|
8367
|
+
if (!schemaPath || typeof schemaPath !== 'object') {
|
|
8368
|
+
return 'list-mixed';
|
|
8369
|
+
}
|
|
7910
8370
|
if (schemaPath.instance === 'Array') {
|
|
7911
8371
|
return 'list-array';
|
|
7912
8372
|
}
|
|
7913
8373
|
if (schemaPath.instance === 'String') {
|
|
7914
8374
|
return 'list-string';
|
|
7915
8375
|
}
|
|
7916
|
-
if (schemaPath.instance
|
|
8376
|
+
if (schemaPath.instance === 'Embedded') {
|
|
7917
8377
|
return 'list-subdocument';
|
|
7918
8378
|
}
|
|
7919
|
-
if (schemaPath.instance
|
|
8379
|
+
if (schemaPath.instance === 'Mixed') {
|
|
7920
8380
|
return 'list-mixed';
|
|
7921
8381
|
}
|
|
7922
8382
|
return 'list-default';
|
|
@@ -7941,6 +8401,31 @@ module.exports = app => app.component('models', {
|
|
|
7941
8401
|
this.edittingDoc = null;
|
|
7942
8402
|
this.$toast.success('Document updated!');
|
|
7943
8403
|
},
|
|
8404
|
+
copyCellValue(value) {
|
|
8405
|
+
const text = value == null ? '' : (typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
8406
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard && navigator.clipboard.writeText) {
|
|
8407
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
8408
|
+
this.$toast.success('Copied to clipboard');
|
|
8409
|
+
}).catch(() => {
|
|
8410
|
+
this.fallbackCopyText(text);
|
|
8411
|
+
});
|
|
8412
|
+
} else {
|
|
8413
|
+
this.fallbackCopyText(text);
|
|
8414
|
+
}
|
|
8415
|
+
},
|
|
8416
|
+
fallbackCopyText(text) {
|
|
8417
|
+
try {
|
|
8418
|
+
const el = document.createElement('textarea');
|
|
8419
|
+
el.value = text;
|
|
8420
|
+
document.body.appendChild(el);
|
|
8421
|
+
el.select();
|
|
8422
|
+
document.execCommand('copy');
|
|
8423
|
+
document.body.removeChild(el);
|
|
8424
|
+
this.$toast.success('Copied to clipboard');
|
|
8425
|
+
} catch (err) {
|
|
8426
|
+
this.$toast.error('Copy failed');
|
|
8427
|
+
}
|
|
8428
|
+
},
|
|
7944
8429
|
handleDocumentClick(document, event) {
|
|
7945
8430
|
if (this.selectMultiple) {
|
|
7946
8431
|
this.handleDocumentSelection(document, event);
|
|
@@ -8308,7 +8793,7 @@ module.exports = app => app.component('navbar', {
|
|
|
8308
8793
|
showFlyout: false,
|
|
8309
8794
|
darkMode: typeof localStorage !== 'undefined' && localStorage.getItem('studio-theme') === 'dark'
|
|
8310
8795
|
}),
|
|
8311
|
-
mounted: function
|
|
8796
|
+
mounted: function() {
|
|
8312
8797
|
window.navbar = this;
|
|
8313
8798
|
const mobileMenuMask = document.querySelector('#mobile-menu-mask');
|
|
8314
8799
|
const mobileMenu = document.querySelector('#mobile-menu');
|
|
@@ -9985,7 +10470,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
9985
10470
|
/* harmony export */ });
|
|
9986
10471
|
/* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
|
|
9987
10472
|
/**
|
|
9988
|
-
* @vue/reactivity v3.5.
|
|
10473
|
+
* @vue/reactivity v3.5.31
|
|
9989
10474
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
9990
10475
|
* @license MIT
|
|
9991
10476
|
**/
|
|
@@ -11578,16 +12063,16 @@ function toRefs(object) {
|
|
|
11578
12063
|
return ret;
|
|
11579
12064
|
}
|
|
11580
12065
|
class ObjectRefImpl {
|
|
11581
|
-
constructor(_object,
|
|
12066
|
+
constructor(_object, key, _defaultValue) {
|
|
11582
12067
|
this._object = _object;
|
|
11583
|
-
this._key = _key;
|
|
11584
12068
|
this._defaultValue = _defaultValue;
|
|
11585
12069
|
this["__v_isRef"] = true;
|
|
11586
12070
|
this._value = void 0;
|
|
12071
|
+
this._key = (0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isSymbol)(key) ? key : String(key);
|
|
11587
12072
|
this._raw = toRaw(_object);
|
|
11588
12073
|
let shallow = true;
|
|
11589
12074
|
let obj = _object;
|
|
11590
|
-
if (!(0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isArray)(_object) || !(0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isIntegerKey)(
|
|
12075
|
+
if (!(0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isArray)(_object) || (0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isSymbol)(this._key) || !(0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isIntegerKey)(this._key)) {
|
|
11591
12076
|
do {
|
|
11592
12077
|
shallow = !isProxy(obj) || isShallow(obj);
|
|
11593
12078
|
} while (shallow && (obj = obj["__v_raw"]));
|
|
@@ -12132,7 +12617,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
12132
12617
|
/* harmony import */ var _vue_reactivity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/reactivity */ "./node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js");
|
|
12133
12618
|
/* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
|
|
12134
12619
|
/**
|
|
12135
|
-
* @vue/runtime-core v3.5.
|
|
12620
|
+
* @vue/runtime-core v3.5.31
|
|
12136
12621
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
12137
12622
|
* @license MIT
|
|
12138
12623
|
**/
|
|
@@ -12579,6 +13064,13 @@ function checkRecursiveUpdates(seen, fn) {
|
|
|
12579
13064
|
}
|
|
12580
13065
|
|
|
12581
13066
|
let isHmrUpdating = false;
|
|
13067
|
+
const setHmrUpdating = (v) => {
|
|
13068
|
+
try {
|
|
13069
|
+
return isHmrUpdating;
|
|
13070
|
+
} finally {
|
|
13071
|
+
isHmrUpdating = v;
|
|
13072
|
+
}
|
|
13073
|
+
};
|
|
12582
13074
|
const hmrDirtyComponents = /* @__PURE__ */ new Map();
|
|
12583
13075
|
if (true) {
|
|
12584
13076
|
(0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.getGlobalThis)().__VUE_HMR_RUNTIME__ = {
|
|
@@ -13165,9 +13657,10 @@ const TeleportImpl = {
|
|
|
13165
13657
|
mount(container, mainAnchor);
|
|
13166
13658
|
updateCssVars(n2, true);
|
|
13167
13659
|
}
|
|
13168
|
-
if (isTeleportDeferred(n2.props)) {
|
|
13660
|
+
if (isTeleportDeferred(n2.props) || parentSuspense && parentSuspense.pendingBranch) {
|
|
13169
13661
|
n2.el.__isMounted = false;
|
|
13170
13662
|
queuePostRenderEffect(() => {
|
|
13663
|
+
if (n2.el.__isMounted !== false) return;
|
|
13171
13664
|
mountToTarget();
|
|
13172
13665
|
delete n2.el.__isMounted;
|
|
13173
13666
|
}, parentSuspense);
|
|
@@ -13175,7 +13668,12 @@ const TeleportImpl = {
|
|
|
13175
13668
|
mountToTarget();
|
|
13176
13669
|
}
|
|
13177
13670
|
} else {
|
|
13178
|
-
|
|
13671
|
+
n2.el = n1.el;
|
|
13672
|
+
n2.targetStart = n1.targetStart;
|
|
13673
|
+
const mainAnchor = n2.anchor = n1.anchor;
|
|
13674
|
+
const target = n2.target = n1.target;
|
|
13675
|
+
const targetAnchor = n2.targetAnchor = n1.targetAnchor;
|
|
13676
|
+
if (n1.el.__isMounted === false) {
|
|
13179
13677
|
queuePostRenderEffect(() => {
|
|
13180
13678
|
TeleportImpl.process(
|
|
13181
13679
|
n1,
|
|
@@ -13192,11 +13690,6 @@ const TeleportImpl = {
|
|
|
13192
13690
|
}, parentSuspense);
|
|
13193
13691
|
return;
|
|
13194
13692
|
}
|
|
13195
|
-
n2.el = n1.el;
|
|
13196
|
-
n2.targetStart = n1.targetStart;
|
|
13197
|
-
const mainAnchor = n2.anchor = n1.anchor;
|
|
13198
|
-
const target = n2.target = n1.target;
|
|
13199
|
-
const targetAnchor = n2.targetAnchor = n1.targetAnchor;
|
|
13200
13693
|
const wasDisabled = isTeleportDisabled(n1.props);
|
|
13201
13694
|
const currentContainer = wasDisabled ? container : target;
|
|
13202
13695
|
const currentAnchor = wasDisabled ? mainAnchor : targetAnchor;
|
|
@@ -13661,7 +14154,7 @@ function resolveTransitionHooks(vnode, props, state, instance, postClone) {
|
|
|
13661
14154
|
callHook(hook, [el]);
|
|
13662
14155
|
},
|
|
13663
14156
|
enter(el) {
|
|
13664
|
-
if (leavingVNodesCache[key] === vnode) return;
|
|
14157
|
+
if (!isHmrUpdating && leavingVNodesCache[key] === vnode) return;
|
|
13665
14158
|
let hook = onEnter;
|
|
13666
14159
|
let afterHook = onAfterEnter;
|
|
13667
14160
|
let cancelHook = onEnterCancelled;
|
|
@@ -16909,11 +17402,12 @@ function hasPropValueChanged(nextProps, prevProps, key) {
|
|
|
16909
17402
|
}
|
|
16910
17403
|
return nextProp !== prevProp;
|
|
16911
17404
|
}
|
|
16912
|
-
function updateHOCHostEl({ vnode, parent }, el) {
|
|
17405
|
+
function updateHOCHostEl({ vnode, parent, suspense }, el) {
|
|
16913
17406
|
while (parent) {
|
|
16914
17407
|
const root = parent.subTree;
|
|
16915
17408
|
if (root.suspense && root.suspense.activeBranch === vnode) {
|
|
16916
|
-
root.el =
|
|
17409
|
+
root.suspense.vnode.el = root.el = el;
|
|
17410
|
+
vnode = root;
|
|
16917
17411
|
}
|
|
16918
17412
|
if (root === vnode) {
|
|
16919
17413
|
(vnode = parent.vnode).el = el;
|
|
@@ -16922,6 +17416,9 @@ function updateHOCHostEl({ vnode, parent }, el) {
|
|
|
16922
17416
|
break;
|
|
16923
17417
|
}
|
|
16924
17418
|
}
|
|
17419
|
+
if (suspense && suspense.activeBranch === vnode) {
|
|
17420
|
+
suspense.vnode.el = el;
|
|
17421
|
+
}
|
|
16925
17422
|
}
|
|
16926
17423
|
|
|
16927
17424
|
const internalObjectProto = {};
|
|
@@ -17790,10 +18287,17 @@ function baseCreateRenderer(options, createHydrationFns) {
|
|
|
17790
18287
|
}
|
|
17791
18288
|
hostInsert(el, container, anchor);
|
|
17792
18289
|
if ((vnodeHook = props && props.onVnodeMounted) || needCallTransitionHooks || dirs) {
|
|
18290
|
+
const isHmr = true && isHmrUpdating;
|
|
17793
18291
|
queuePostRenderEffect(() => {
|
|
17794
|
-
|
|
17795
|
-
|
|
17796
|
-
|
|
18292
|
+
let prev;
|
|
18293
|
+
if (true) prev = setHmrUpdating(isHmr);
|
|
18294
|
+
try {
|
|
18295
|
+
vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode);
|
|
18296
|
+
needCallTransitionHooks && transition.enter(el);
|
|
18297
|
+
dirs && invokeDirectiveHook(vnode, null, parentComponent, "mounted");
|
|
18298
|
+
} finally {
|
|
18299
|
+
if (true) setHmrUpdating(prev);
|
|
18300
|
+
}
|
|
17797
18301
|
}, parentSuspense);
|
|
17798
18302
|
}
|
|
17799
18303
|
};
|
|
@@ -18713,7 +19217,8 @@ function baseCreateRenderer(options, createHydrationFns) {
|
|
|
18713
19217
|
shapeFlag,
|
|
18714
19218
|
patchFlag,
|
|
18715
19219
|
dirs,
|
|
18716
|
-
cacheIndex
|
|
19220
|
+
cacheIndex,
|
|
19221
|
+
memo
|
|
18717
19222
|
} = vnode;
|
|
18718
19223
|
if (patchFlag === -2) {
|
|
18719
19224
|
optimized = false;
|
|
@@ -18775,10 +19280,14 @@ function baseCreateRenderer(options, createHydrationFns) {
|
|
|
18775
19280
|
remove(vnode);
|
|
18776
19281
|
}
|
|
18777
19282
|
}
|
|
18778
|
-
|
|
19283
|
+
const shouldInvalidateMemo = memo != null && cacheIndex == null;
|
|
19284
|
+
if (shouldInvokeVnodeHook && (vnodeHook = props && props.onVnodeUnmounted) || shouldInvokeDirs || shouldInvalidateMemo) {
|
|
18779
19285
|
queuePostRenderEffect(() => {
|
|
18780
19286
|
vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode);
|
|
18781
19287
|
shouldInvokeDirs && invokeDirectiveHook(vnode, null, parentComponent, "unmounted");
|
|
19288
|
+
if (shouldInvalidateMemo) {
|
|
19289
|
+
vnode.el = null;
|
|
19290
|
+
}
|
|
18782
19291
|
}, parentSuspense);
|
|
18783
19292
|
}
|
|
18784
19293
|
};
|
|
@@ -19332,6 +19841,7 @@ function createSuspenseBoundary(vnode, parentSuspense, parentComponent, containe
|
|
|
19332
19841
|
pendingId: suspenseId++,
|
|
19333
19842
|
timeout: typeof timeout === "number" ? timeout : -1,
|
|
19334
19843
|
activeBranch: null,
|
|
19844
|
+
isFallbackMountPending: false,
|
|
19335
19845
|
pendingBranch: null,
|
|
19336
19846
|
isInFallback: !isHydrating,
|
|
19337
19847
|
isHydrating,
|
|
@@ -19381,7 +19891,7 @@ function createSuspenseBoundary(vnode, parentSuspense, parentComponent, containe
|
|
|
19381
19891
|
}
|
|
19382
19892
|
};
|
|
19383
19893
|
}
|
|
19384
|
-
if (activeBranch) {
|
|
19894
|
+
if (activeBranch && !suspense.isFallbackMountPending) {
|
|
19385
19895
|
if (parentNode(activeBranch.el) === container2) {
|
|
19386
19896
|
anchor = next(activeBranch);
|
|
19387
19897
|
}
|
|
@@ -19394,6 +19904,7 @@ function createSuspenseBoundary(vnode, parentSuspense, parentComponent, containe
|
|
|
19394
19904
|
move(pendingBranch, container2, anchor, 0);
|
|
19395
19905
|
}
|
|
19396
19906
|
}
|
|
19907
|
+
suspense.isFallbackMountPending = false;
|
|
19397
19908
|
setActiveBranch(suspense, pendingBranch);
|
|
19398
19909
|
suspense.pendingBranch = null;
|
|
19399
19910
|
suspense.isInFallback = false;
|
|
@@ -19429,6 +19940,7 @@ function createSuspenseBoundary(vnode, parentSuspense, parentComponent, containe
|
|
|
19429
19940
|
triggerEvent(vnode2, "onFallback");
|
|
19430
19941
|
const anchor2 = next(activeBranch);
|
|
19431
19942
|
const mountFallback = () => {
|
|
19943
|
+
suspense.isFallbackMountPending = false;
|
|
19432
19944
|
if (!suspense.isInFallback) {
|
|
19433
19945
|
return;
|
|
19434
19946
|
}
|
|
@@ -19448,6 +19960,7 @@ function createSuspenseBoundary(vnode, parentSuspense, parentComponent, containe
|
|
|
19448
19960
|
};
|
|
19449
19961
|
const delayEnter = fallbackVNode.transition && fallbackVNode.transition.mode === "out-in";
|
|
19450
19962
|
if (delayEnter) {
|
|
19963
|
+
suspense.isFallbackMountPending = true;
|
|
19451
19964
|
activeBranch.transition.afterLeave = mountFallback;
|
|
19452
19965
|
}
|
|
19453
19966
|
suspense.isInFallback = true;
|
|
@@ -19998,6 +20511,10 @@ function mergeProps(...args) {
|
|
|
19998
20511
|
const incoming = toMerge[key];
|
|
19999
20512
|
if (incoming && existing !== incoming && !((0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.isArray)(existing) && existing.includes(incoming))) {
|
|
20000
20513
|
ret[key] = existing ? [].concat(existing, incoming) : incoming;
|
|
20514
|
+
} else if (incoming == null && existing == null && // mergeProps({ 'onUpdate:modelValue': undefined }) should not retain
|
|
20515
|
+
// the model listener.
|
|
20516
|
+
!(0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.isModelListener)(key)) {
|
|
20517
|
+
ret[key] = incoming;
|
|
20001
20518
|
}
|
|
20002
20519
|
} else if (key !== "") {
|
|
20003
20520
|
ret[key] = toMerge[key];
|
|
@@ -20684,7 +21201,7 @@ function isMemoSame(cached, memo) {
|
|
|
20684
21201
|
return true;
|
|
20685
21202
|
}
|
|
20686
21203
|
|
|
20687
|
-
const version = "3.5.
|
|
21204
|
+
const version = "3.5.31";
|
|
20688
21205
|
const warn = true ? warn$1 : 0;
|
|
20689
21206
|
const ErrorTypeStrings = ErrorTypeStrings$1 ;
|
|
20690
21207
|
const devtools = true ? devtools$1 : 0;
|
|
@@ -20895,7 +21412,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
20895
21412
|
/* harmony import */ var _vue_runtime_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/runtime-core */ "./node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js");
|
|
20896
21413
|
/* harmony import */ var _vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
|
|
20897
21414
|
/**
|
|
20898
|
-
* @vue/runtime-dom v3.5.
|
|
21415
|
+
* @vue/runtime-dom v3.5.31
|
|
20899
21416
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
20900
21417
|
* @license MIT
|
|
20901
21418
|
**/
|
|
@@ -22472,7 +22989,8 @@ const vModelText = {
|
|
|
22472
22989
|
if (elValue === newValue) {
|
|
22473
22990
|
return;
|
|
22474
22991
|
}
|
|
22475
|
-
|
|
22992
|
+
const rootNode = el.getRootNode();
|
|
22993
|
+
if ((rootNode instanceof Document || rootNode instanceof ShadowRoot) && rootNode.activeElement === el && el.type !== "range") {
|
|
22476
22994
|
if (lazy && value === oldValue) {
|
|
22477
22995
|
return;
|
|
22478
22996
|
}
|
|
@@ -22966,7 +23484,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
22966
23484
|
/* harmony export */ toTypeString: () => (/* binding */ toTypeString)
|
|
22967
23485
|
/* harmony export */ });
|
|
22968
23486
|
/**
|
|
22969
|
-
* @vue/shared v3.5.
|
|
23487
|
+
* @vue/shared v3.5.31
|
|
22970
23488
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
22971
23489
|
* @license MIT
|
|
22972
23490
|
**/
|
|
@@ -48489,7 +49007,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
48489
49007
|
/* harmony import */ var _vue_runtime_dom__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/runtime-dom */ "./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js");
|
|
48490
49008
|
/* harmony import */ var _vue_runtime_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/runtime-dom */ "./node_modules/@vue/runtime-dom/dist/runtime-dom.esm-bundler.js");
|
|
48491
49009
|
/**
|
|
48492
|
-
* vue v3.5.
|
|
49010
|
+
* vue v3.5.31
|
|
48493
49011
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
48494
49012
|
* @license MIT
|
|
48495
49013
|
**/
|
|
@@ -50160,7 +50678,7 @@ module.exports = ".list-mixed pre {\n max-height: 6.5em;\n max-width: 30em;\n}
|
|
|
50160
50678
|
(module) {
|
|
50161
50679
|
|
|
50162
50680
|
"use strict";
|
|
50163
|
-
module.exports = "<div class=\"list-mixed tooltip\">\n <pre>\n <code ref=\"MixedCode\" class=\"language-javascript\">{{shortenValue}}</code>\n
|
|
50681
|
+
module.exports = "<div class=\"list-mixed tooltip\">\n <pre>\n <code ref=\"MixedCode\" class=\"language-javascript\">{{shortenValue}}</code>\n </pre>\n</div>\n ";
|
|
50164
50682
|
|
|
50165
50683
|
/***/ },
|
|
50166
50684
|
|
|
@@ -50182,7 +50700,7 @@ module.exports = ".list-string {\n display: inline;\n max-width: 300px;\n}";
|
|
|
50182
50700
|
(module) {
|
|
50183
50701
|
|
|
50184
50702
|
"use strict";
|
|
50185
|
-
module.exports = "<div class=\"list-string tooltip\" ref=\"itemData\">\n {{displayValue}}\n
|
|
50703
|
+
module.exports = "<div class=\"list-string tooltip\" ref=\"itemData\">\n {{displayValue}}\n</div>";
|
|
50186
50704
|
|
|
50187
50705
|
/***/ },
|
|
50188
50706
|
|
|
@@ -50259,7 +50777,7 @@ module.exports = "<div v-if=\"show\" class=\"fixed inset-0 z-[9999]\">\n <div c
|
|
|
50259
50777
|
(module) {
|
|
50260
50778
|
|
|
50261
50779
|
"use strict";
|
|
50262
|
-
module.exports = ".models {\n position: relative;\n display: flex;\n flex-direction: row;\n min-height: calc(100% - 56px);\n}\n\n.models
|
|
50780
|
+
module.exports = ".models {\n position: relative;\n display: flex;\n flex-direction: row;\n min-height: calc(100% - 56px);\n}\n\n.models .documents {\n flex-grow: 1;\n overflow: visible;\n max-height: calc(100vh - 56px);\n display: flex;\n flex-direction: column;\n}\n\n.models .documents-container {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n}\n\n.models .documents-menu {\n position: sticky;\n top: 0;\n z-index: 30;\n padding: 4px;\n display: flex;\n flex-direction: column;\n flex-shrink: 0;\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 .loader-overlay {\n position: absolute;\n inset: 0;\n z-index: 20;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--color-page);\n opacity: 0.9;\n}\n\n/* Table cell: copy icon only copies; rest of cell opens document */\n.models .table-cell-copy {\n cursor: pointer;\n}\n";
|
|
50263
50781
|
|
|
50264
50782
|
/***/ },
|
|
50265
50783
|
|
|
@@ -50270,7 +50788,7 @@ module.exports = ".models {\n position: relative;\n display: flex;\n flex-dir
|
|
|
50270
50788
|
(module) {
|
|
50271
50789
|
|
|
50272
50790
|
"use strict";
|
|
50273
|
-
module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <aside class=\"bg-page border-r overflow-hidden transition-all duration-300 ease-in-out z-20 w-0 lg:w-64 fixed lg:relative shrink-0 flex flex-col top-[55px] bottom-0 lg:top-auto lg:bottom-auto lg:h-full\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''\">\n <!-- Search -->\n <div class=\"p-3 shrink-0\">\n <div class=\"relative\">\n <svg class=\"absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z\" />\n </svg>\n <input\n v-model=\"modelSearch\"\n type=\"text\"\n placeholder=\"Find model...\"\n @keydown.esc=\"modelSearch = ''\"\n class=\"w-full rounded-md border border-edge bg-surface py-1.5 pl-8 pr-3 text-sm text-content placeholder:text-gray-400 focus:border-edge-strong focus:outline-none focus:ring-1 focus:ring-gray-300\"\n />\n </div>\n </div>\n <!-- Model list (scrollable) -->\n <nav class=\"flex-1 overflow-y-auto px-2 pb-2\">\n <!-- Recently Viewed -->\n <div v-if=\"filteredRecentModels.length > 0 && !modelSearch.trim()\">\n <div class=\"px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider\">Recently Viewed</div>\n <ul role=\"list\">\n <li v-for=\"model in filteredRecentModels\" :key=\"'recent-' + model\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"flex items-center rounded-md py-1.5 px-2 text-sm text-content-secondary\"\n :class=\"model === currentModel ? 'bg-gray-200 font-semibold text-content' : 'hover:bg-muted'\">\n <span class=\"truncate\" v-html=\"highlightMatch(model)\"></span>\n <span\n v-if=\"modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel\"\n class=\"ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]\"\n >\n {{formatCompactCount(modelDocumentCounts[model])}}\n </span>\n </router-link>\n </li>\n </ul>\n <div class=\"border-b border-edge my-2\"></div>\n </div>\n <!-- All Models / Search Results -->\n <div class=\"px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider\">{{ modelSearch.trim() ? 'Search Results' : 'All Models' }}</div>\n <ul role=\"list\">\n <li v-for=\"model in filteredModels\" :key=\"'all-' + model\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"flex items-center rounded-md py-1.5 px-2 text-sm text-content-secondary\"\n :class=\"model === currentModel ? 'bg-gray-200 font-semibold text-content' : 'hover:bg-muted'\">\n <span class=\"truncate\" v-html=\"highlightMatch(model)\"></span>\n <span\n v-if=\"modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel\"\n class=\"ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]\"\n >\n {{formatCompactCount(modelDocumentCounts[model])}}\n </span>\n </router-link>\n </li>\n </ul>\n <div v-if=\"filteredModels.length === 0 && modelSearch.trim()\" class=\"px-2 py-2 text-sm text-content-tertiary\">\n No models match \"{{modelSearch}}\"\n </div>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100 rounded-md\">\n No models found\n </div>\n </nav>\n <!-- Bottom toolbar -->\n <div class=\"shrink-0 border-t border-edge bg-page px-2 py-1.5 flex items-center gap-1\">\n <button\n type=\"button\"\n @click=\"hideSidebar = true\"\n class=\"rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n title=\"Hide sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m18.75 4.5-7.5 7.5 7.5 7.5m-6-15L5.25 12l7.5 7.5\" />\n </svg>\n </button>\n <button\n type=\"button\"\n @click=\"openModelSwitcher\"\n class=\"rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n title=\"Quick switch (Ctrl+P)\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z\" />\n </svg>\n </button>\n </div>\n </aside>\n <div class=\"documents bg-slate-50 min-w-0\" ref=\"documentsList\">\n <div class=\"documents-menu bg-slate-50\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <button\n v-if=\"hideSidebar === true || hideSidebar === null\"\n type=\"button\"\n @click=\"hideSidebar = false\"\n class=\"shrink-0 rounded-md p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n :class=\"hideSidebar === null ? 'lg:hidden' : ''\"\n title=\"Show sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\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'\">{{documents.length}}/{{numDocuments === 1 ? numDocuments + ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{ 'bg-page0 ring-inset ring-2 ring-gray-300 hover:bg-gray-600': selectMultiple, 'bg-primary hover:bg-primary-hover' : !selectMultiple }\"\n class=\"rounded px-2 py-2 text-sm font-semibold text-primary-text shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\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 <div class=\"relative\" v-show=\"!selectMultiple\" ref=\"actionsMenuContainer\" @keyup.esc.prevent=\"closeActionsMenu\">\n <button\n @click=\"toggleActionsMenu\"\n type=\"button\"\n aria-label=\"More actions\"\n class=\"rounded bg-surface px-2 py-2 text-sm font-semibold text-content-secondary shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-page focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 6.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Z\" />\n </svg>\n </button>\n <div\n v-if=\"showActionsMenu\"\n class=\"absolute right-0 mt-2 w-48 origin-top-right rounded-md bg-surface shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none z-20\"\n >\n <div class=\"py-1\">\n <button\n @click=\"shouldShowExportModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Export\n </button>\n <button\n @click=\"shouldShowCreateModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Create\n </button>\n <button\n @click=\"openFieldSelection(); showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Projection\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Indexes\n </button>\n <button\n @click=\"openCollectionInfo\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Collection Info\n </button>\n <button\n @click=\"findOldestDocument\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Find oldest document\n </button>\n </div>\n </div>\n </div>\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-page focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-surface'\">\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 px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-page focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-surface'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n <button\n @click=\"setOutputType('map')\"\n :disabled=\"geoJsonFields.length === 0\"\n type=\"button\"\n :title=\"geoJsonFields.length > 0 ? 'Map view' : 'No GeoJSON fields detected'\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 ring-1 ring-inset ring-gray-300 focus:z-10\"\n :class=\"[\n geoJsonFields.length === 0 ? 'text-gray-300 cursor-not-allowed bg-muted' : 'text-gray-400 hover:bg-page',\n outputType === 'map' ? 'bg-gray-200' : (geoJsonFields.length > 0 ? 'bg-surface' : '')\n ]\">\n <svg class=\"h-5 w-5\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 6.75V15m6-6v8.25m.503 3.498 4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 0 0-1.006 0L3.622 5.689C3.24 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 1.006 0l4.994 2.497c.317.158.69.158 1.006 0Z\" />\n </svg>\n </button>\n </span>\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 class=\"bg-slate-50\">\n <th v-for=\"path in filteredPaths\" @click=\"addPathFilter(path.path)\" class=\"bg-slate-50 cursor-pointer p-3\">\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\" class=\"bg-surface hover:bg-slate-50\">\n <td v-for=\"schemaPath in filteredPaths\" class=\"p-3 cursor-pointer\" :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-2 p-1 mt-1\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative transition-colors rounded-md border border-slate-100',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:shadow-sm hover:border-slate-300 bg-surface'\n ]\"\n >\n <button\n type=\"button\"\n class=\"absolute top-2 right-2 z-10 inline-flex items-center rounded bg-primary px-2 py-1 text-xs font-semibold text-primary-text shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\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-else-if=\"outputType === 'map'\" class=\"flex flex-col h-full\">\n <div class=\"p-2 bg-surface border-b flex items-center gap-2\">\n <label class=\"text-sm font-medium text-content-secondary\">GeoJSON Field:</label>\n <select\n :value=\"selectedGeoField\"\n @change=\"setSelectedGeoField($event.target.value)\"\n class=\"rounded-md border border-edge-strong py-1 px-2 text-sm focus:border-primary focus:ring-primary\"\n >\n <option v-for=\"field in geoJsonFields\" :key=\"field.path\" :value=\"field.path\">\n {{ field.label }}\n </option>\n </select>\n <async-button\n @click=\"loadMoreDocuments\"\n :disabled=\"loadedAllDocs\"\n type=\"button\"\n class=\"rounded px-2 py-1 text-xs font-semibold text-primary-text shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n :class=\"loadedAllDocs ? 'bg-gray-400 cursor-not-allowed' : 'bg-primary hover:bg-primary-hover'\"\n >\n Load more\n </async-button>\n </div>\n <div class=\"flex-1 min-h-[400px]\" ref=\"modelsMap\"></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 flex items-center gap-2\">\n <div>{{ index.name }}</div>\n <div v-if=\"isTTLIndex(index)\" class=\"rounded-full bg-primary-subtle px-2 py-0.5 text-xs font-semibold text-primary\">\n TTL: {{ formatTTL(index.expireAfterSeconds) }}\n </div>\n </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=\"shouldShowCollectionInfoModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCollectionInfoModal = false\">×</div>\n <div class=\"text-xl font-bold mb-2\">Collection Info</div>\n <div v-if=\"!collectionInfo\" class=\"text-gray-600\">Loading collection details...</div>\n <div v-else class=\"space-y-3\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Documents</div>\n <div class=\"text-content\">{{ formatNumber(collectionInfo.documentCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Indexes</div>\n <div class=\"text-content\">{{ formatNumber(collectionInfo.indexCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Total Index Size</div>\n <div class=\"text-content\">{{ formatCollectionSize(collectionInfo.totalIndexSize) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Total Storage Size</div>\n <div class=\"text-content\">{{ formatCollectionSize(collectionInfo.size) }}</div>\n </div>\n <div class=\"flex flex-col gap-1\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Collation</div>\n <div class=\"text-content\">{{ collectionInfo.hasCollation ? 'Yes' : 'No' }}</div>\n </div>\n <div v-if=\"collectionInfo.hasCollation\" class=\"rounded bg-muted p-3 text-sm text-gray-800 overflow-x-auto\">\n <pre class=\"whitespace-pre-wrap\">{{ JSON.stringify(collectionInfo.collation, null, 2) }}</pre>\n </div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Capped</div>\n <div class=\"text-content\">{{ collectionInfo.capped ? 'Yes' : 'No' }}</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-edge-strong 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-content-secondary 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-primary px-2.5 py-1.5 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover 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-page0 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-page0 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 <model-switcher\n :show=\"showModelSwitcher\"\n :models=\"models\"\n :recently-viewed-models=\"recentlyViewedModels\"\n :model-document-counts=\"modelDocumentCounts\"\n @close=\"showModelSwitcher = false\"\n @select=\"selectSwitcherModel\"\n ></model-switcher>\n</div>\n";
|
|
50791
|
+
module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <aside class=\"bg-page border-r overflow-hidden transition-all duration-300 ease-in-out z-20 w-0 lg:w-64 fixed lg:relative shrink-0 flex flex-col top-[55px] bottom-0 lg:top-auto lg:bottom-auto lg:h-full\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''\">\n <!-- Search -->\n <div class=\"p-3 shrink-0\">\n <div class=\"relative\">\n <svg class=\"absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z\" />\n </svg>\n <input\n v-model=\"modelSearch\"\n type=\"text\"\n placeholder=\"Find model...\"\n @keydown.esc=\"modelSearch = ''\"\n class=\"w-full rounded-md border border-edge bg-surface py-1.5 pl-8 pr-3 text-sm text-content placeholder:text-gray-400 focus:border-edge-strong focus:outline-none focus:ring-1 focus:ring-gray-300\"\n />\n </div>\n </div>\n <!-- Model list (scrollable) -->\n <nav class=\"flex-1 overflow-y-auto px-2 pb-2\">\n <!-- Recently Viewed -->\n <div v-if=\"filteredRecentModels.length > 0 && !modelSearch.trim()\">\n <div class=\"px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider\">Recently Viewed</div>\n <ul role=\"list\">\n <li v-for=\"model in filteredRecentModels\" :key=\"'recent-' + model\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"flex items-center rounded-md py-1.5 px-2 text-sm text-content-secondary\"\n :class=\"model === currentModel ? 'bg-gray-200 font-semibold text-content' : 'hover:bg-muted'\">\n <span class=\"truncate\" v-html=\"highlightMatch(model)\"></span>\n <span\n v-if=\"modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel\"\n class=\"ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]\"\n >\n {{formatCompactCount(modelDocumentCounts[model])}}\n </span>\n </router-link>\n </li>\n </ul>\n <div class=\"border-b border-edge my-2\"></div>\n </div>\n <!-- All Models / Search Results -->\n <div class=\"px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider\">{{ modelSearch.trim() ? 'Search Results' : 'All Models' }}</div>\n <ul role=\"list\">\n <li v-for=\"model in filteredModels\" :key=\"'all-' + model\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"flex items-center rounded-md py-1.5 px-2 text-sm text-content-secondary\"\n :class=\"model === currentModel ? 'bg-gray-200 font-semibold text-content' : 'hover:bg-muted'\">\n <span class=\"truncate\" v-html=\"highlightMatch(model)\"></span>\n <span\n v-if=\"modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel\"\n class=\"ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]\"\n >\n {{formatCompactCount(modelDocumentCounts[model])}}\n </span>\n </router-link>\n </li>\n </ul>\n <div v-if=\"filteredModels.length === 0 && modelSearch.trim()\" class=\"px-2 py-2 text-sm text-content-tertiary\">\n No models match \"{{modelSearch}}\"\n </div>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100 rounded-md\">\n No models found\n </div>\n </nav>\n <!-- Bottom toolbar -->\n <div class=\"shrink-0 border-t border-edge bg-page px-2 py-1.5 flex items-center gap-1\">\n <button\n type=\"button\"\n @click=\"hideSidebar = true\"\n class=\"rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n title=\"Hide sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m18.75 4.5-7.5 7.5 7.5 7.5m-6-15L5.25 12l7.5 7.5\" />\n </svg>\n </button>\n <button\n type=\"button\"\n @click=\"openModelSwitcher\"\n class=\"rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n title=\"Quick switch (Ctrl+P)\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z\" />\n </svg>\n </button>\n </div>\n </aside>\n <div class=\"documents bg-slate-50 min-w-0\" ref=\"documentsList\">\n <div class=\"documents-menu bg-slate-50\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <button\n v-if=\"hideSidebar === true || hideSidebar === null\"\n type=\"button\"\n @click=\"hideSidebar = false\"\n class=\"shrink-0 rounded-md p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n :class=\"hideSidebar === null ? 'lg:hidden' : ''\"\n title=\"Show sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\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'\">{{documents.length}}/{{numDocuments === 1 ? numDocuments + ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{\n 'bg-page0 ring-inset ring-2 ring-gray-300 hover:bg-gray-200 text-content-secondary': selectMultiple,\n 'bg-primary hover:bg-primary-hover text-primary-text': !selectMultiple\n }\"\n class=\"rounded px-2 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\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 <div class=\"relative\" v-show=\"!selectMultiple\" ref=\"actionsMenuContainer\" @keyup.esc.prevent=\"closeActionsMenu\">\n <button\n @click=\"toggleActionsMenu\"\n type=\"button\"\n aria-label=\"More actions\"\n class=\"rounded bg-surface px-2 py-2 text-sm font-semibold text-content-secondary shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-page focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 6.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Z\" />\n </svg>\n </button>\n <div\n v-if=\"showActionsMenu\"\n class=\"absolute right-0 mt-2 w-48 origin-top-right rounded-md bg-surface shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none z-50\"\n >\n <div class=\"py-1\">\n <button\n @click=\"shouldShowExportModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Export\n </button>\n <button\n @click=\"shouldShowCreateModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Create\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Indexes\n </button>\n <button\n @click=\"openCollectionInfo\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Collection Info\n </button>\n <button\n @click=\"findOldestDocument\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Find oldest document\n </button>\n <button\n @click=\"toggleRowNumbers()\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n {{ showRowNumbers ? 'Hide row numbers' : 'Show row numbers' }}\n </button>\n <button\n @click=\"resetFilter()\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Reset Filter\n </button>\n <button\n @click=\"toggleProjectionMenu(); showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm hover:bg-muted\"\n :class=\"isProjectionMenuSelected ? 'text-primary font-semibold' : 'text-content-secondary'\"\n >\n {{ isProjectionMenuSelected ? 'Projection (On)' : 'Projection' }}\n </button>\n <button\n v-if=\"isProjectionMenuSelected\"\n @click=\"clearProjection()\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Reset Projection\n </button>\n <async-button\n v-if=\"isProjectionMenuSelected\"\n type=\"button\"\n @click=\"applyDefaultProjectionColumns()\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Default\n </async-button>\n </div>\n </div>\n </div>\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-page focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-surface'\">\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 px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-page focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-surface'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n <button\n @click=\"setOutputType('map')\"\n :disabled=\"geoJsonFields.length === 0\"\n type=\"button\"\n :title=\"geoJsonFields.length > 0 ? 'Map view' : 'No GeoJSON fields detected'\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 ring-1 ring-inset ring-gray-300 focus:z-10\"\n :class=\"[\n geoJsonFields.length === 0 ? 'text-gray-300 cursor-not-allowed bg-muted' : 'text-gray-400 hover:bg-page',\n outputType === 'map' ? 'bg-gray-200' : (geoJsonFields.length > 0 ? 'bg-surface' : '')\n ]\">\n <svg class=\"h-5 w-5\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 6.75V15m6-6v8.25m.503 3.498 4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 0 0-1.006 0L3.622 5.689C3.24 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 1.006 0l4.994 2.497c.317.158.69.158 1.006 0Z\" />\n </svg>\n </button>\n </span>\n </div>\n <div v-if=\"isProjectionMenuSelected && (outputType === 'table' || outputType === 'json')\" class=\"flex items-center gap-2 w-full mt-2 flex-shrink-0\">\n <input\n ref=\"projectionInput\"\n v-model=\"projectionText\"\n type=\"text\"\n placeholder=\"Projection: name email, or -password\"\n class=\"flex-1 min-w-0 rounded border border-edge bg-surface px-2 py-1.5 text-sm font-mono placeholder:text-content-tertiary focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\n @focus=\"initProjection($event)\"\n @keydown.enter=\"applyProjectionFromInput()\"\n />\n </div>\n </div>\n <!-- In JSON view, this container is the scrollable element used for infinite scroll. -->\n <div class=\"documents-container relative\" ref=\"documentsContainerScroll\" @scroll=\"checkIfScrolledToBottom\">\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 <div v-else-if=\"outputType === 'table'\" class=\"flex-1 min-h-0 flex flex-col overflow-hidden\">\n <div\n ref=\"documentsScrollContainer\"\n class=\"overflow-x-auto overflow-y-auto flex-1 min-h-0 border border-edge rounded-lg bg-surface\"\n @scroll=\"checkIfScrolledToBottom\"\n >\n <table class=\"min-w-full border-collapse text-sm\">\n <thead class=\"sticky top-0 z-10 bg-slate-100 dark:bg-shark-800 border-b border-edge\">\n <tr>\n <th v-if=\"showRowNumbers\" class=\"px-3 py-2.5 text-left font-medium text-content border-r border-edge whitespace-nowrap align-middle w-0\">\n #\n </th>\n <th\n v-for=\"path in tableDisplayPaths\"\n :key=\"path.path\"\n class=\"px-3 py-2.5 text-left font-medium text-content border-r border-edge last:border-r-0 whitespace-nowrap align-middle\"\n >\n <div class=\"flex items-center gap-2\">\n <span\n @click=\"addPathFilter(path.path)\"\n class=\"cursor-pointer hover:text-primary truncate min-w-0\"\n :title=\"path.path\"\n >\n {{ path.path }}\n </span>\n <span class=\"text-xs text-content-tertiary shrink-0\">({{ path.instance || 'unknown' }})</span>\n <span class=\"inline-flex shrink-0 gap-0.5 items-center\">\n <button\n type=\"button\"\n @click.stop=\"sortDocs(1, path.path)\"\n class=\"p-0.5 rounded text-content-tertiary hover:text-content hover:bg-muted\"\n :class=\"{ 'text-primary font-semibold': sortBy[path.path] === 1 }\"\n :title=\"sortBy[path.path] === 1 ? 'Clear sort' : 'Sort ascending'\"\n >\n ↑\n </button>\n <button\n type=\"button\"\n @click.stop=\"sortDocs(-1, path.path)\"\n class=\"p-0.5 rounded text-content-tertiary hover:text-content hover:bg-muted\"\n :class=\"{ 'text-primary font-semibold': sortBy[path.path] === -1 }\"\n :title=\"sortBy[path.path] === -1 ? 'Clear sort' : 'Sort descending'\"\n >\n ↓\n </button>\n <button\n v-if=\"filteredPaths.length > 0\"\n type=\"button\"\n @click.stop=\"removeField(path)\"\n class=\"p-1.5 rounded-md border border-transparent text-content-tertiary hover:text-valencia-600 hover:bg-valencia-50 hover:border-valencia-200 focus:outline-none focus:ring-2 focus:ring-valencia-500/30\"\n title=\"Remove column\"\n aria-label=\"Remove column\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18 18 6M6 6l12 12\" />\n </svg>\n </button>\n </span>\n </div>\n </th>\n <th v-if=\"filteredPaths.length > 0\" class=\"px-2 py-2.5 border-r border-edge last:border-r-0 align-middle w-0 bg-slate-50 dark:bg-shark-800/80\">\n <div class=\"relative\" ref=\"addFieldContainer\">\n <button\n type=\"button\"\n @click=\"toggleAddFieldDropdown()\"\n class=\"flex items-center justify-center w-8 h-8 rounded border border-dashed border-edge text-content-tertiary hover:border-primary hover:text-primary hover:bg-primary-subtle/30\"\n title=\"Add column\"\n aria-label=\"Add column\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" class=\"w-5 h-5\"> <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 4.5v15m7.5-7.5h-15\" /> </svg>\n </button>\n <div\n v-if=\"showAddFieldDropdown\"\n class=\"absolute right-0 top-full mt-1 z-[100] min-w-[180px] max-w-[280px] rounded-md border border-edge bg-surface shadow-lg py-1 max-h-48 overflow-y-auto\"\n >\n <input\n v-if=\"availablePathsToAdd.length > 5\"\n ref=\"addFieldFilterInput\"\n v-model=\"addFieldFilterText\"\n type=\"text\"\n placeholder=\"Filter fields...\"\n class=\"mx-2 mb-1 w-[calc(100%-1rem)] rounded border border-edge px-2 py-1 text-sm\"\n @click.stop\n />\n <button\n v-for=\"p in filteredPathsToAdd\"\n :key=\"p.path\"\n type=\"button\"\n class=\"w-full px-3 py-1.5 text-left text-sm hover:bg-muted\"\n @click.stop=\"addField(p)\"\n >\n {{ p.path }}\n </button>\n <p v-if=\"filteredPathsToAdd.length === 0\" class=\"px-3 py-2 text-sm text-content-tertiary\">\n {{ addFieldFilterText.trim() ? 'No matching fields' : 'All fields added' }}\n </p>\n </div>\n </div>\n </th>\n </tr>\n </thead>\n <tbody class=\"bg-surface\">\n <tr\n v-for=\"(document, docIndex) in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentClick(document, $event)\"\n class=\"border-b border-edge cursor-pointer transition-colors hover:bg-muted/60\"\n :class=\"{ 'bg-primary-subtle/50 hover:bg-primary-subtle/70': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }\"\n >\n <td v-if=\"showRowNumbers\" class=\"px-3 py-2 border-r border-edge align-top text-content-tertiary whitespace-nowrap\">\n {{ docIndex + 1 }}\n </td>\n <td\n v-for=\"schemaPath in tableDisplayPaths\"\n :key=\"schemaPath.path\"\n class=\"px-3 py-2 border-r border-edge last:border-r-0 align-top max-w-[280px]\"\n >\n <div class=\"table-cell-content flex items-center gap-1.5 min-w-0 group\">\n <span class=\"min-w-0 overflow-hidden text-ellipsis flex-1\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\"\n />\n </span>\n <button\n type=\"button\"\n class=\"table-cell-copy shrink-0 p-1 rounded text-content-tertiary hover:text-content hover:bg-muted focus:outline-none focus:ring-1 focus:ring-edge\"\n aria-label=\"Copy cell value\"\n @click.stop=\"copyCellValue(getValueForPath(document, schemaPath.path))\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8 5H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1M8 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M8 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m0 0h2a2 2 0 0 1 2 2v3m2 4H10m0 0l3-3m-3 3l3 3\" />\n </svg>\n </button>\n </div>\n </td>\n <td v-if=\"filteredPaths.length > 0\" class=\"w-0 px-0 py-0 border-r border-edge last:border-r-0 bg-slate-50/50 dark:bg-shark-800/30\"></td>\n </tr>\n </tbody>\n </table>\n </div>\n <div v-if=\"outputType === 'table' && (loadingMore || (status === 'loading' && documents.length > 0))\" class=\"flex items-center justify-center gap-2 py-3 text-sm text-content-tertiary border-t border-edge bg-surface\">\n <img src=\"images/loader.gif\" alt=\"\" class=\"h-5 w-5\">\n <span>Loading documents…</span>\n </div>\n <p v-if=\"outputType === 'table' && documents.length === 0 && status === 'loaded'\" class=\"mt-2 text-sm text-content-tertiary px-1\">\n No documents to show. Use Projection in the menu to choose columns.\n </p>\n </div>\n <div v-else-if=\"outputType === 'json'\" class=\"flex flex-col space-y-2 p-1 mt-1\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative transition-colors rounded-md border border-slate-100',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:shadow-sm hover:border-slate-300 bg-surface'\n ]\"\n >\n <button\n type=\"button\"\n class=\"absolute top-2 right-2 z-10 inline-flex items-center rounded bg-primary px-2 py-1 text-xs font-semibold text-primary-text shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\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 v-if=\"outputType === 'json' && (loadingMore || (status === 'loading' && documents.length > 0))\" class=\"flex items-center justify-center gap-2 py-3 text-sm text-content-tertiary\">\n <img src=\"images/loader.gif\" alt=\"\" class=\"h-5 w-5\">\n <span>Loading documents…</span>\n </div>\n </div>\n <div v-else-if=\"outputType === 'map'\" class=\"flex flex-col h-full\">\n <div class=\"p-2 bg-surface border-b flex items-center gap-2\">\n <label class=\"text-sm font-medium text-content-secondary\">GeoJSON Field:</label>\n <select\n :value=\"selectedGeoField\"\n @change=\"setSelectedGeoField($event.target.value)\"\n class=\"rounded-md border border-edge-strong py-1 px-2 text-sm focus:border-primary focus:ring-primary\"\n >\n <option v-for=\"field in geoJsonFields\" :key=\"field.path\" :value=\"field.path\">\n {{ field.label }}\n </option>\n </select>\n <async-button\n @click=\"loadMoreDocuments\"\n :disabled=\"loadedAllDocs\"\n type=\"button\"\n class=\"rounded px-2 py-1 text-xs font-semibold text-primary-text shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n :class=\"loadedAllDocs ? 'bg-gray-400 cursor-not-allowed' : 'bg-primary hover:bg-primary-hover'\"\n >\n Load more\n </async-button>\n </div>\n <div class=\"flex-1 min-h-[400px]\" ref=\"modelsMap\"></div>\n </div>\n <div v-if=\"status === 'loading' && !loadingMore && documents.length === 0\" class=\"loader loader-overlay\" aria-busy=\"true\">\n <img src=\"images/loader.gif\" alt=\"Loading\">\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 flex items-center gap-2\">\n <div>{{ index.name }}</div>\n <div v-if=\"isTTLIndex(index)\" class=\"rounded-full bg-primary-subtle px-2 py-0.5 text-xs font-semibold text-primary\">\n TTL: {{ formatTTL(index.expireAfterSeconds) }}\n </div>\n </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=\"shouldShowCollectionInfoModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCollectionInfoModal = false\">×</div>\n <div class=\"text-xl font-bold mb-2\">Collection Info</div>\n <div v-if=\"!collectionInfo\" class=\"text-gray-600\">Loading collection details...</div>\n <div v-else class=\"space-y-3\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Documents</div>\n <div class=\"text-content\">{{ formatNumber(collectionInfo.documentCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Indexes</div>\n <div class=\"text-content\">{{ formatNumber(collectionInfo.indexCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Total Index Size</div>\n <div class=\"text-content\">{{ formatCollectionSize(collectionInfo.totalIndexSize) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Total Storage Size</div>\n <div class=\"text-content\">{{ formatCollectionSize(collectionInfo.size) }}</div>\n </div>\n <div class=\"flex flex-col gap-1\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Collation</div>\n <div class=\"text-content\">{{ collectionInfo.hasCollation ? 'Yes' : 'No' }}</div>\n </div>\n <div v-if=\"collectionInfo.hasCollation\" class=\"rounded bg-muted p-3 text-sm text-gray-800 overflow-x-auto\">\n <pre class=\"whitespace-pre-wrap\">{{ JSON.stringify(collectionInfo.collation, null, 2) }}</pre>\n </div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Capped</div>\n <div class=\"text-content\">{{ collectionInfo.capped ? 'Yes' : 'No' }}</div>\n </div>\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-page0 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 <model-switcher\n :show=\"showModelSwitcher\"\n :models=\"models\"\n :recently-viewed-models=\"recentlyViewedModels\"\n :model-document-counts=\"modelDocumentCounts\"\n @close=\"showModelSwitcher = false\"\n @select=\"selectSwitcherModel\"\n ></model-switcher>\n</div>\n";
|
|
50274
50792
|
|
|
50275
50793
|
/***/ },
|
|
50276
50794
|
|
|
@@ -50314,7 +50832,7 @@ module.exports = "<div class=\"w-full h-full flex items-center justify-center\">
|
|
|
50314
50832
|
(module) {
|
|
50315
50833
|
|
|
50316
50834
|
"use strict";
|
|
50317
|
-
module.exports = "<div class=\"p-4 space-y-6\">\n <div v-if=\"status === 'init'\">\n <img src=\"images/loader.gif\" alt=\"Loading\" />\n </div>\n <div v-else-if=\"status === 'error'\" class=\"text-red-600\">\n {{ errorMessage }}\n </div>\n <template v-else-if=\"taskGroup\">\n <div class=\"pb-24\">\n <router-link :to=\"{ name: 'tasks' }\" class=\"inline-flex items-center gap-1 text-gray-500 hover:text-gray-700 mb-4\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 19l-7-7 7-7\"></path>\n </svg>\n Back to Task Groups\n </router-link>\n <div class=\"mb-4\">\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Filter by Date:</label>\n <select v-model=\"selectedRange\" @change=\"updateDateRange\" class=\"border-gray-300 rounded-md shadow-sm w-full p-2 max-w-xs\">\n <option v-for=\"option in dateFilters\" :key=\"option.value\" :value=\"option.value\">\n {{ option.label }}\n </option>\n </select>\n </div>\n <task-details\n :task-group=\"taskGroup\"\n :back-to=\"{ name: 'tasks' }\"\n :show-back-button=\"false\"\n @task-created=\"onTaskCreated\"\n @task-cancelled=\"onTaskCancelled\"\n ></task-details>\n </div>\n <div\n v-if=\"numDocs > 0\"\n class=\"fixed bottom-0 left-0 right-0 z-10 px-4 py-4 bg-
|
|
50835
|
+
module.exports = "<div class=\"p-4 space-y-6\">\n <div v-if=\"status === 'init'\">\n <img src=\"images/loader.gif\" alt=\"Loading\" />\n </div>\n <div v-else-if=\"status === 'error'\" class=\"text-red-600\">\n {{ errorMessage }}\n </div>\n <template v-else-if=\"taskGroup\">\n <div class=\"pb-24\">\n <router-link :to=\"{ name: 'tasks' }\" class=\"inline-flex items-center gap-1 text-gray-500 hover:text-gray-700 mb-4\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 19l-7-7 7-7\"></path>\n </svg>\n Back to Task Groups\n </router-link>\n <div class=\"mb-4\">\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Filter by Date:</label>\n <select v-model=\"selectedRange\" @change=\"updateDateRange\" class=\"border-gray-300 rounded-md shadow-sm w-full p-2 max-w-xs\">\n <option v-for=\"option in dateFilters\" :key=\"option.value\" :value=\"option.value\">\n {{ option.label }}\n </option>\n </select>\n </div>\n <task-details\n :task-group=\"taskGroup\"\n :back-to=\"{ name: 'tasks' }\"\n :show-back-button=\"false\"\n @task-created=\"onTaskCreated\"\n @task-cancelled=\"onTaskCancelled\"\n ></task-details>\n </div>\n <div\n v-if=\"numDocs > 0\"\n class=\"fixed bottom-0 left-0 right-0 z-10 px-4 py-4 bg-surface border-t border-edge shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)]\"\n >\n <div class=\"flex flex-wrap items-center justify-between gap-4 max-w-6xl mx-auto\">\n <div class=\"flex items-center gap-6\">\n <p class=\"text-sm text-content-tertiary\">\n <span class=\"font-medium text-content\">{{ Math.min((page - 1) * pageSize + 1, numDocs) }}–{{ Math.min(page * pageSize, numDocs) }}</span>\n <span class=\"mx-1\">of</span>\n <span class=\"font-medium text-content\">{{ numDocs }}</span>\n <span class=\"ml-1 text-content-tertiary\">tasks</span>\n </p>\n <div class=\"flex items-center gap-2\">\n <label class=\"text-sm font-medium text-content-secondary\">Per page</label>\n <select\n v-model.number=\"pageSize\"\n @change=\"onPageSizeChange\"\n class=\"border border-edge rounded-md shadow-sm px-3 py-2 text-sm text-content-secondary bg-surface hover:border-edge-strong focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary\"\n >\n <option v-for=\"n in pageSizeOptions\" :key=\"n\" :value=\"n\">{{ n }}</option>\n </select>\n </div>\n </div>\n <div class=\"flex items-center gap-1\">\n <button\n type=\"button\"\n :disabled=\"page <= 1\"\n @click=\"goToPage(page - 1)\"\n class=\"inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium rounded-md border transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-surface bg-surface text-content-secondary border-edge hover:bg-page hover:border-edge-strong\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 19l-7-7 7-7\"></path>\n </svg>\n Previous\n </button>\n <span class=\"px-4 py-2 text-sm text-content-tertiary min-w-[7rem] text-center\">\n Page <span class=\"font-semibold text-content\">{{ page }}</span> of <span class=\"font-semibold text-content\">{{ Math.max(1, Math.ceil(numDocs / pageSize)) }}</span>\n </span>\n <button\n type=\"button\"\n :disabled=\"page >= Math.ceil(numDocs / pageSize)\"\n @click=\"goToPage(page + 1)\"\n class=\"inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium rounded-md border transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-surface bg-surface text-content-secondary border-edge hover:bg-page hover:border-edge-strong\"\n >\n Next\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n </button>\n </div>\n </div>\n </div>\n </template>\n</div>\n";
|
|
50318
50836
|
|
|
50319
50837
|
/***/ },
|
|
50320
50838
|
|
|
@@ -50325,7 +50843,7 @@ module.exports = "<div class=\"p-4 space-y-6\">\n <div v-if=\"status === 'init'
|
|
|
50325
50843
|
(module) {
|
|
50326
50844
|
|
|
50327
50845
|
"use strict";
|
|
50328
|
-
module.exports = "<div class=\"p-4 space-y-6\">\n <div v-if=\"status === 'init'\">\n <img src=\"images/loader.gif\" alt=\"Loading\" />\n </div>\n <div v-else-if=\"status === 'error'\" class=\"text-red-600\">\n {{ errorMessage }}\n </div>\n <div v-else-if=\"status === 'notfound'\" class=\"text-gray-600\">\n Task not found.\n </div>\n <div v-else-if=\"task\" class=\"max-w-4xl\">\n <button @click=\"goBack\" class=\"text-content-tertiary hover:text-content-secondary mb-4 flex items-center gap-1\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 19l-7-7 7-7\"></path>\n </svg>\n Back to {{ task.name }}\n </button>\n <h1 class=\"text-2xl font-bold text-content-secondary mb-1\">{{ task.name }}</h1>\n <p class=\"text-content-tertiary mb-6\">Task details</p>\n\n <div class=\"bg-surface rounded-lg shadow p-6 md:p-8\">\n <div class=\"flex items-center gap-3 mb-6\">\n <span class=\"text-sm font-medium text-content\">ID: {{ task.id }}</span>\n <span\n class=\"text-xs px-2 py-1 rounded-full font-medium\"\n :class=\"getStatusColor(task.status)\"\n >\n {{ task?.status }}\n </span>\n </div>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6 mb-6\">\n <div>\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Scheduled At</label>\n <div class=\"text-sm text-content\">{{ formatDate(task.scheduledAt) }}</div>\n </div>\n <div v-if=\"task?.startedAt\">\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Started At</label>\n <div class=\"text-sm text-content\">{{ formatDate(task.startedAt) }}</div>\n </div>\n <div v-if=\"task?.completedAt\">\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Completed At</label>\n <div class=\"text-sm text-content\">{{ formatDate(task.completedAt) }}</div>\n </div>\n </div>\n\n <div v-if=\"task?.params\" class=\"mb-6\">\n <label class=\"block text-sm font-medium text-content-secondary mb-2\">Params</label>\n <div class=\"bg-page rounded-md p-4\">\n <list-json :value=\"task.params\"></list-json>\n </div>\n </div>\n\n <div v-if=\"task?.result\" class=\"mb-6\">\n <label class=\"block text-sm font-medium text-content-secondary mb-2\">Result</label>\n <div class=\"bg-page rounded-md p-4\">\n <list-json :value=\"task.result\"></list-json>\n </div>\n </div>\n\n <div v-if=\"task?.error\" class=\"mb-6\">\n <label class=\"block text-sm font-medium text-content-secondary mb-2\">Error</label>\n <div class=\"bg-page rounded-md p-4\">\n <list-json :value=\"task.error\"></list-json>\n </div>\n </div>\n\n <div class=\"flex flex-wrap gap-3 pt-4 border-t border-edge\">\n <button\n @click=\"showRescheduleConfirmation(task)\"\n class=\"flex items-center justify-center gap-2 bg-gradient-to-r from-blue-500 to-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-blue-600 hover:to-blue-700 disabled:opacity-50 disabled:cursor-not-allowed\"\n :disabled=\"task.status === 'in_progress'\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\n </svg>\n Reschedule\n </button>\n <button\n @click=\"showRunConfirmation(task)\"\n class=\"flex items-center justify-center gap-2 bg-gradient-to-r from-green-500 to-green-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-green-600 hover:to-green-700 disabled:opacity-50 disabled:cursor-not-allowed\"\n :disabled=\"task.status === 'in_progress'\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 3l14 9-14 9V3z\"></path>\n </svg>\n Run Now\n </button>\n <button\n v-if=\"task.status === 'pending'\"\n @click=\"showCancelConfirmation(task)\"\n class=\"flex items-center justify-center gap-2 bg-gradient-to-r from-red-500 to-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-red-600 hover:to-red-700\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\"></path>\n </svg>\n Cancel Task\n </button>\n </div>\n </div>\n </div>\n\n <!-- Reschedule Modal -->\n <modal v-if=\"showRescheduleModal\" containerClass=\"!max-w-md\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showRescheduleModal = false\" role=\"button\" aria-label=\"Close modal\">×</div>\n <div class=\"p-6\">\n <h3 class=\"text-lg font-medium text-content mb-4\">Reschedule Task</h3>\n <p class=\"text-sm text-gray-600 mb-2\">Reschedule task <strong>{{ selectedTask?.id }}</strong>?</p>\n <p class=\"text-sm text-content-tertiary mb-4\">This will reset the task's status and schedule it to run again.</p>\n <label for=\"newScheduledTime\" class=\"block text-sm font-medium text-content-secondary mb-2\">New Scheduled Time</label>\n <input\n id=\"newScheduledTime\"\n v-model=\"newScheduledTime\"\n type=\"datetime-local\"\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-4\"\n />\n <div class=\"flex gap-3\">\n <button @click=\"confirmRescheduleTask\" class=\"flex-1 bg-blue-600 text-white px-4 py-2 rounded-md hover:
|
|
50846
|
+
module.exports = "<div class=\"p-4 space-y-6\">\n <div v-if=\"status === 'init'\">\n <img src=\"images/loader.gif\" alt=\"Loading\" />\n </div>\n <div v-else-if=\"status === 'error'\" class=\"text-red-600\">\n {{ errorMessage }}\n </div>\n <div v-else-if=\"status === 'notfound'\" class=\"text-gray-600\">\n Task not found.\n </div>\n <div v-else-if=\"task\" class=\"max-w-4xl\">\n <button @click=\"goBack\" class=\"text-content-tertiary hover:text-content-secondary mb-4 flex items-center gap-1\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 19l-7-7 7-7\"></path>\n </svg>\n Back to {{ task.name }}\n </button>\n <h1 class=\"text-2xl font-bold text-content-secondary mb-1\">{{ task.name }}</h1>\n <p class=\"text-content-tertiary mb-6\">Task details</p>\n\n <div class=\"bg-surface rounded-lg shadow p-6 md:p-8\">\n <div class=\"flex items-center gap-3 mb-6\">\n <span class=\"text-sm font-medium text-content\">ID: {{ task.id }}</span>\n <span\n class=\"text-xs px-2 py-1 rounded-full font-medium\"\n :class=\"getStatusColor(task.status)\"\n >\n {{ task?.status }}\n </span>\n </div>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6 mb-6\">\n <div>\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Scheduled At</label>\n <div class=\"text-sm text-content\">{{ formatDate(task.scheduledAt) }}</div>\n </div>\n <div v-if=\"task?.startedAt\">\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Started At</label>\n <div class=\"text-sm text-content\">{{ formatDate(task.startedAt) }}</div>\n </div>\n <div v-if=\"task?.completedAt\">\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Completed At</label>\n <div class=\"text-sm text-content\">{{ formatDate(task.completedAt) }}</div>\n </div>\n </div>\n\n <div v-if=\"task?.params\" class=\"mb-6\">\n <label class=\"block text-sm font-medium text-content-secondary mb-2\">Params</label>\n <div class=\"bg-page rounded-md p-4\">\n <list-json :value=\"task.params\"></list-json>\n </div>\n </div>\n\n <div v-if=\"task?.result\" class=\"mb-6\">\n <label class=\"block text-sm font-medium text-content-secondary mb-2\">Result</label>\n <div class=\"bg-page rounded-md p-4\">\n <list-json :value=\"task.result\"></list-json>\n </div>\n </div>\n\n <div v-if=\"task?.error\" class=\"mb-6\">\n <label class=\"block text-sm font-medium text-content-secondary mb-2\">Error</label>\n <div class=\"bg-page rounded-md p-4\">\n <list-json :value=\"task.error\"></list-json>\n </div>\n </div>\n\n <div class=\"flex flex-wrap gap-3 pt-4 border-t border-edge\">\n <button\n @click=\"showRescheduleConfirmation(task)\"\n class=\"flex items-center justify-center gap-2 bg-gradient-to-r from-blue-500 to-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-blue-600 hover:to-blue-700 disabled:opacity-50 disabled:cursor-not-allowed\"\n :disabled=\"task.status === 'in_progress'\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\n </svg>\n Reschedule\n </button>\n <button\n @click=\"showRunConfirmation(task)\"\n class=\"flex items-center justify-center gap-2 bg-gradient-to-r from-green-500 to-green-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-green-600 hover:to-green-700 disabled:opacity-50 disabled:cursor-not-allowed\"\n :disabled=\"task.status === 'in_progress'\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 3l14 9-14 9V3z\"></path>\n </svg>\n Run Now\n </button>\n <button\n v-if=\"task.status === 'pending'\"\n @click=\"showCancelConfirmation(task)\"\n class=\"flex items-center justify-center gap-2 bg-gradient-to-r from-red-500 to-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-red-600 hover:to-red-700\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\"></path>\n </svg>\n Cancel Task\n </button>\n </div>\n </div>\n </div>\n\n <!-- Reschedule Modal -->\n <modal v-if=\"showRescheduleModal\" containerClass=\"!max-w-md\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showRescheduleModal = false\" role=\"button\" aria-label=\"Close modal\">×</div>\n <div class=\"p-6\">\n <h3 class=\"text-lg font-medium text-content mb-4\">Reschedule Task</h3>\n <p class=\"text-sm text-gray-600 mb-2\">Reschedule task <strong>{{ selectedTask?.id }}</strong>?</p>\n <p class=\"text-sm text-content-tertiary mb-4\">This will reset the task's status and schedule it to run again.</p>\n <label for=\"newScheduledTime\" class=\"block text-sm font-medium text-content-secondary mb-2\">New Scheduled Time</label>\n <input\n id=\"newScheduledTime\"\n v-model=\"newScheduledTime\"\n type=\"datetime-local\"\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-4\"\n />\n <div class=\"flex gap-3\">\n <button @click=\"confirmRescheduleTask\" class=\"flex-1 bg-gradient-to-r from-blue-500 to-blue-600 text-white px-4 py-2 rounded-md hover:from-blue-600 hover:to-blue-700 font-medium\">Reschedule</button>\n <button @click=\"showRescheduleModal = false\" class=\"flex-1 bg-gray-300 text-content-secondary px-4 py-2 rounded-md hover:bg-gray-400 font-medium\">Cancel</button>\n </div>\n </div>\n </template>\n </modal>\n\n <!-- Run Modal -->\n <modal v-if=\"showRunModal\" containerClass=\"!max-w-md\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showRunModal = false\" role=\"button\" aria-label=\"Close modal\">×</div>\n <div class=\"p-6\">\n <h3 class=\"text-lg font-medium text-content mb-4\">Run Task Now</h3>\n <p class=\"text-sm text-gray-600 mb-2\">Run task <strong>{{ selectedTask?.id }}</strong> immediately?</p>\n <p class=\"text-sm text-content-tertiary mb-4\">This will execute the task right away, bypassing its scheduled time.</p>\n <div class=\"flex gap-3\">\n <button @click=\"confirmRunTask\" class=\"flex-1 bg-gradient-to-r from-green-500 to-green-600 text-white px-4 py-2 rounded-md hover:from-green-600 hover:to-green-700 font-medium\">Run Now</button>\n <button @click=\"showRunModal = false\" class=\"flex-1 bg-gray-300 text-content-secondary px-4 py-2 rounded-md hover:bg-gray-400 font-medium\">Cancel</button>\n </div>\n </div>\n </template>\n </modal>\n\n <!-- Cancel Task Modal -->\n <modal v-if=\"showCancelModal\" containerClass=\"!max-w-md\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showCancelModal = false\" role=\"button\" aria-label=\"Close modal\">×</div>\n <div class=\"p-6\">\n <h3 class=\"text-lg font-medium text-content mb-4\">Cancel Task</h3>\n <p class=\"text-sm text-gray-600 mb-2\">Cancel task <strong>{{ selectedTask?.id }}</strong>?</p>\n <p class=\"text-sm text-content-tertiary mb-4\">This will permanently cancel the task and it cannot be undone.</p>\n <div class=\"flex gap-3\">\n <button @click=\"confirmCancelTask\" class=\"flex-1 bg-gradient-to-r from-red-500 to-red-600 text-white px-4 py-2 rounded-md hover:from-red-600 hover:to-red-700 font-medium\">Cancel Task</button>\n <button @click=\"showCancelModal = false\" class=\"flex-1 bg-muted hover:bg-page text-content-secondary px-4 py-2 rounded-md border border-edge-strong font-medium\">Keep Task</button>\n </div>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
50329
50847
|
|
|
50330
50848
|
/***/ },
|
|
50331
50849
|
|
|
@@ -50336,7 +50854,7 @@ module.exports = "<div class=\"p-4 space-y-6\">\n <div v-if=\"status === 'init'
|
|
|
50336
50854
|
(module) {
|
|
50337
50855
|
|
|
50338
50856
|
"use strict";
|
|
50339
|
-
module.exports = "<div class=\"p-4 space-y-6\">\n <div class=\"flex items-center justify-between\">\n <div>\n <button v-if=\"showBackButton\" @click=\"goBack\" class=\"text-content-tertiary hover:text-content-secondary mb-2\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 19l-7-7 7-7\"></path>\n </svg>\n {{ backLabel }}\n </button>\n <h1 class=\"text-2xl font-bold text-content-secondary\">{{ taskGroup.name }}</h1>\n <p class=\"text-content-tertiary\">Total: {{ taskGroup.totalCount }} tasks</p>\n </div>\n\n </div>\n\n <!-- Status Summary -->\n <div class=\"space-y-3\">\n <div class=\"flex items-center justify-between\">\n <span class=\"text-sm font-medium text-content-secondary\">Status</span>\n <div class=\"flex rounded-md shadow-sm\" role=\"group\">\n <button\n type=\"button\"\n @click=\"statusView = 'summary'\"\n class=\"px-3 py-1.5 text-sm font-medium rounded-l-md border transition-colors\"\n :class=\"statusView === 'summary' ? 'bg-primary text-primary-text border-primary' : 'bg-surface text-content-secondary border-edge-strong hover:bg-page'\"\n >\n Summary\n </button>\n <button\n type=\"button\"\n @click=\"statusView = 'chart'\"\n class=\"px-3 py-1.5 text-sm font-medium rounded-r-md border border-l-0 transition-colors\"\n :class=\"statusView === 'chart' ? 'bg-primary text-primary-text border-primary' : 'bg-surface text-content-secondary border-edge-strong hover:bg-page'\"\n >\n Chart\n </button>\n </div>\n </div>\n <!-- Summary view -->\n <div v-show=\"statusView === 'summary'\" class=\"grid grid-cols-2 sm:grid-cols-4 gap-4\">\n <button \n @click=\"filterByStatus('pending')\"\n class=\"bg-yellow-50 border border-yellow-200 rounded-md p-3 text-center hover:bg-yellow-100 transition-colors cursor-pointer\"\n :class=\"{ 'ring-2 ring-yellow-400': currentFilter === 'pending' }\"\n >\n <div class=\"text-xs text-yellow-600 font-medium\">Pending</div>\n <div class=\"text-lg font-bold text-yellow-700\">{{ taskGroup.statusCounts.pending || 0 }}</div>\n </button>\n <button \n @click=\"filterByStatus('succeeded')\"\n class=\"bg-green-50 border border-green-200 rounded-md p-3 text-center hover:bg-green-100 transition-colors cursor-pointer\"\n :class=\"{ 'ring-2 ring-green-400': currentFilter === 'succeeded' }\"\n >\n <div class=\"text-xs text-green-600 font-medium\">Succeeded</div>\n <div class=\"text-lg font-bold text-green-700\">{{ taskGroup.statusCounts.succeeded || 0 }}</div>\n </button>\n <button \n @click=\"filterByStatus('failed')\"\n class=\"bg-red-50 border border-red-200 rounded-md p-3 text-center hover:bg-red-100 transition-colors cursor-pointer\"\n :class=\"{ 'ring-2 ring-red-400': currentFilter === 'failed' }\"\n >\n <div class=\"text-xs text-red-600 font-medium\">Failed</div>\n <div class=\"text-lg font-bold text-red-700\">{{ taskGroup.statusCounts.failed || 0 }}</div>\n </button>\n <button \n @click=\"filterByStatus('cancelled')\"\n class=\"bg-page border border-edge rounded-md p-3 text-center hover:bg-muted transition-colors cursor-pointer\"\n :class=\"{ 'ring-2 ring-gray-400': currentFilter === 'cancelled' }\"\n >\n <div class=\"text-xs text-gray-600 font-medium\">Cancelled</div>\n <div class=\"text-lg font-bold text-content-secondary\">{{ taskGroup.statusCounts.cancelled || 0 }}</div>\n </button>\n </div>\n <!-- Chart view -->\n <div v-show=\"statusView === 'chart'\" class=\"flex flex-col items-center justify-center bg-surface border border-edge rounded-lg p-4 gap-3\" style=\"min-height: 280px;\">\n <div v-if=\"taskGroup.totalCount > 0\" class=\"w-[240px] h-[240px] shrink-0\">\n <canvas ref=\"statusPieChart\" width=\"240\" height=\"240\" class=\"block\"></canvas>\n </div>\n <p v-else class=\"text-content-tertiary text-sm py-8\">No tasks to display</p>\n <!-- Selection labels: show which segment is selected (click to filter) -->\n <div v-if=\"taskGroup.totalCount > 0\" class=\"flex flex-wrap justify-center gap-2\">\n <button\n v-for=\"status in statusOrderForDisplay\"\n :key=\"status\"\n type=\"button\"\n class=\"text-xs px-2 py-1 rounded-full font-medium transition-all cursor-pointer\"\n :class=\"currentFilter === status ? getStatusPillClass(status) : 'bg-muted text-content-tertiary hover:bg-muted'\"\n @click=\"filterByStatus(status)\"\n >\n {{ statusLabel(status) }}\n </button>\n </div>\n </div>\n </div>\n\n <!-- Task List -->\n <div class=\"bg-surface rounded-lg shadow\">\n <div class=\"px-6 py-6 border-b border-edge flex items-center justify-between bg-page\">\n <h2 class=\"text-xl font-bold text-content\">\n Individual Tasks\n <span v-if=\"currentFilter\" class=\"ml-3 text-base font-semibold text-primary\">\n (Filtered by {{ currentFilter }})\n </span>\n </h2>\n <button \n v-if=\"currentFilter\"\n @click=\"clearFilter\"\n class=\"text-sm font-semibold text-primary hover:text-primary\"\n >\n Show All\n </button>\n </div>\n <div class=\"divide-y divide-gray-200\">\n <div v-for=\"task in sortedTasks\" :key=\"task.id\" class=\"p-6\">\n <div class=\"flex items-start justify-between\">\n <div class=\"flex-1\">\n <div class=\"flex items-center gap-3 mb-2\">\n <span class=\"text-sm font-medium text-content\">Task ID: {{ task.id }}</span>\n <router-link\n v-if=\"backTo\"\n :to=\"taskDetailRoute(task)\"\n class=\"text-sm text-primary hover:text-primary font-medium\"\n >\n View details\n </router-link>\n <span\n class=\"text-xs px-2 py-1 rounded-full font-medium\"\n :class=\"getStatusColor(task.status)\"\n >\n {{ task.status }}\n </span>\n </div>\n \n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4\">\n <div>\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Scheduled At</label>\n <div class=\"text-sm text-content\">{{ formatDate(task.scheduledAt) }}</div>\n </div>\n <div v-if=\"task.startedAt\">\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Started At</label>\n <div class=\"text-sm text-content\">{{ formatDate(task.startedAt) }}</div>\n </div>\n <div v-if=\"task.completedAt\">\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Completed At</label>\n <div class=\"text-sm text-content\">{{ formatDate(task.completedAt) }}</div>\n </div>\n <div v-if=\"task.error\">\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Error</label>\n <div class=\"text-sm text-red-600\">{{ task.error }}</div>\n </div>\n </div>\n\n <!-- Task Parameters -->\n <div v-if=\"task.parameters && Object.keys(task.parameters).length > 0\">\n <label class=\"block text-sm font-medium text-content-secondary mb-2\">Parameters</label>\n <div class=\"bg-page rounded-md p-3\">\n <pre class=\"text-sm text-gray-800 whitespace-pre-wrap\">{{ JSON.stringify(task.parameters, null, 2) }}</pre>\n </div>\n </div>\n </div>\n \n <div class=\"flex flex-col gap-3 ml-6\">\n <button \n @click=\"showRescheduleConfirmation(task)\"\n class=\"flex items-center justify-center gap-2 bg-gradient-to-r from-blue-500 to-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-blue-600 hover:to-blue-700 transform hover:scale-105 transition-all duration-200 shadow-md hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none\"\n :disabled=\"task.status === 'in_progress'\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\n </svg>\n Reschedule\n </button>\n <button \n @click=\"showRunConfirmation(task)\"\n class=\"flex items-center justify-center gap-2 bg-gradient-to-r from-green-500 to-green-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-green-600 hover:to-green-700 transform hover:scale-105 transition-all duration-200 shadow-md hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none\"\n :disabled=\"task.status === 'in_progress'\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 3l14 9-14 9V3z\"></path>\n </svg>\n Run Now\n </button>\n <button \n v-if=\"task.status === 'pending'\"\n @click=\"showCancelConfirmation(task)\"\n class=\"flex items-center justify-center gap-2 bg-gradient-to-r from-red-500 to-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-red-600 hover:to-red-700 transform hover:scale-105 transition-all duration-200 shadow-md hover:shadow-lg\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\"></path>\n </svg>\n Cancel\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Reschedule Confirmation Modal -->\n <modal v-if=\"showRescheduleModal\" containerClass=\"!max-w-md\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showRescheduleModal = false;\" role=\"button\" aria-label=\"Close modal\">×</div>\n <div class=\"p-6\">\n <div class=\"flex items-center mb-4\">\n <div class=\"flex-shrink-0\">\n <svg class=\"w-6 h-6 text-blue-600\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-lg font-medium text-content\">Reschedule Task</h3>\n </div>\n </div>\n <div class=\"mb-4\">\n <p class=\"text-sm text-gray-600\">\n Are you sure you want to reschedule task <strong>{{ selectedTask?.id }}</strong>?\n </p>\n <p class=\"text-sm text-content-tertiary mt-2\">\n This will reset the task's status and schedule it to run again.\n </p>\n \n <div class=\"mt-4\">\n <label for=\"newScheduledTime\" class=\"block text-sm font-medium text-content-secondary mb-2\">\n New Scheduled Time\n </label>\n <input\n id=\"newScheduledTime\"\n v-model=\"newScheduledTime\"\n type=\"datetime-local\"\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500\"\n required\n />\n </div>\n </div>\n <div class=\"flex gap-3\">\n <button \n @click=\"confirmRescheduleTask\"\n class=\"flex-1 bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 font-medium\"\n >\n Reschedule\n </button>\n <button \n @click=\"showRescheduleModal = false\"\n class=\"flex-1 bg-gray-300 text-content-secondary px-4 py-2 rounded-md hover:bg-gray-400 font-medium\"\n >\n Cancel\n </button>\n </div>\n </div>\n </template>\n </modal>\n\n <!-- Run Task Confirmation Modal -->\n <modal v-if=\"showRunModal\" containerClass=\"!max-w-md\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showRunModal = false;\" role=\"button\" aria-label=\"Close modal\">×</div>\n <div class=\"p-6\">\n <div class=\"flex items-center mb-4\">\n <div class=\"flex-shrink-0\">\n <svg class=\"w-6 h-6 text-green-600\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M14.828 14.828a4 4 0 01-5.656 0M9 10h1m4 0h1m-6 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-lg font-medium text-content\">Run Task Now</h3>\n </div>\n </div>\n <div class=\"mb-4\">\n <p class=\"text-sm text-gray-600\">\n Are you sure you want to run task <strong>{{ selectedTask?.id }}</strong> immediately?\n </p>\n <p class=\"text-sm text-content-tertiary mt-2\">\n This will execute the task right away, bypassing its scheduled time.\n </p>\n </div>\n <div class=\"flex gap-3\">\n <button \n @click=\"confirmRunTask\"\n class=\"flex-1 bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 font-medium\"\n >\n Run Now\n </button>\n <button \n @click=\"showRunModal = false\"\n class=\"flex-1 bg-gray-300 text-content-secondary px-4 py-2 rounded-md hover:bg-gray-400 font-medium\"\n >\n Cancel\n </button>\n </div>\n </div>\n </template>\n </modal>\n\n <!-- Cancel Task Confirmation Modal -->\n <modal v-if=\"showCancelModal\" containerClass=\"!max-w-md\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showCancelModal = false;\" role=\"button\" aria-label=\"Close modal\">×</div>\n <div class=\"p-6\">\n <div class=\"flex items-center mb-4\">\n <div class=\"flex-shrink-0\">\n <svg class=\"w-6 h-6 text-red-600\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\"></path>\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-lg font-medium text-content\">Cancel Task</h3>\n </div>\n </div>\n <div class=\"mb-4\">\n <p class=\"text-sm text-gray-600\">\n Are you sure you want to cancel task <strong>{{ selectedTask?.id }}</strong>?\n </p>\n <p class=\"text-sm text-content-tertiary mt-2\">\n This will permanently cancel the task and it cannot be undone.\n </p>\n </div>\n <div class=\"flex gap-3\">\n <button \n @click=\"confirmCancelTask\"\n class=\"flex-1 bg-red-600 text-white px-4 py-2 rounded-md hover:bg-red-700 font-medium\"\n >\n Cancel Task\n </button>\n <button \n @click=\"showCancelModal = false\"\n class=\"flex-1 bg-gray-300 text-content-secondary px-4 py-2 rounded-md hover:bg-gray-400 font-medium\"\n >\n Keep Task\n </button>\n </div>\n </div>\n </template>\n </modal>\n</div>\n\n";
|
|
50857
|
+
module.exports = "<div class=\"p-4 space-y-6\">\n <div class=\"flex items-center justify-between\">\n <div>\n <button v-if=\"showBackButton\" @click=\"goBack\" class=\"text-content-tertiary hover:text-content-secondary mb-2\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 19l-7-7 7-7\"></path>\n </svg>\n {{ backLabel }}\n </button>\n <h1 class=\"text-2xl font-bold text-content-secondary\">{{ taskGroup.name }}</h1>\n <p class=\"text-content-tertiary\">Total: {{ taskGroup.totalCount }} tasks</p>\n </div>\n\n </div>\n\n <!-- Status Summary -->\n <div class=\"space-y-3\">\n <div class=\"flex items-center justify-between\">\n <span class=\"text-sm font-medium text-content-secondary\">Status</span>\n <div class=\"flex rounded-md shadow-sm\" role=\"group\">\n <button\n type=\"button\"\n @click=\"statusView = 'summary'\"\n class=\"px-3 py-1.5 text-sm font-medium rounded-l-md border transition-colors\"\n :class=\"statusView === 'summary' ? 'bg-primary text-primary-text border-primary' : 'bg-surface text-content-secondary border-edge-strong hover:bg-page'\"\n >\n Summary\n </button>\n <button\n type=\"button\"\n @click=\"statusView = 'chart'\"\n class=\"px-3 py-1.5 text-sm font-medium rounded-r-md border border-l-0 transition-colors\"\n :class=\"statusView === 'chart' ? 'bg-primary text-primary-text border-primary' : 'bg-surface text-content-secondary border-edge-strong hover:bg-page'\"\n >\n Chart\n </button>\n </div>\n </div>\n <!-- Summary view -->\n <div v-show=\"statusView === 'summary'\" class=\"grid grid-cols-2 sm:grid-cols-4 gap-4\">\n <button \n @click=\"filterByStatus('pending')\"\n class=\"bg-yellow-50 border border-yellow-200 rounded-md p-3 text-center hover:bg-yellow-100 transition-colors cursor-pointer\"\n :class=\"{ 'ring-2 ring-yellow-400': currentFilter === 'pending' }\"\n >\n <div class=\"text-xs text-yellow-600 font-medium\">Pending</div>\n <div class=\"text-lg font-bold text-yellow-700\">{{ taskGroup.statusCounts.pending || 0 }}</div>\n </button>\n <button \n @click=\"filterByStatus('succeeded')\"\n class=\"bg-green-50 border border-green-200 rounded-md p-3 text-center hover:bg-green-100 transition-colors cursor-pointer\"\n :class=\"{ 'ring-2 ring-green-400': currentFilter === 'succeeded' }\"\n >\n <div class=\"text-xs text-green-600 font-medium\">Succeeded</div>\n <div class=\"text-lg font-bold text-green-700\">{{ taskGroup.statusCounts.succeeded || 0 }}</div>\n </button>\n <button \n @click=\"filterByStatus('failed')\"\n class=\"bg-red-50 border border-red-200 rounded-md p-3 text-center hover:bg-red-100 transition-colors cursor-pointer\"\n :class=\"{ 'ring-2 ring-red-400': currentFilter === 'failed' }\"\n >\n <div class=\"text-xs text-red-600 font-medium\">Failed</div>\n <div class=\"text-lg font-bold text-red-700\">{{ taskGroup.statusCounts.failed || 0 }}</div>\n </button>\n <button \n @click=\"filterByStatus('cancelled')\"\n class=\"bg-page border border-edge rounded-md p-3 text-center hover:bg-muted transition-colors cursor-pointer\"\n :class=\"{ 'ring-2 ring-gray-400': currentFilter === 'cancelled' }\"\n >\n <div class=\"text-xs text-gray-600 font-medium\">Cancelled</div>\n <div class=\"text-lg font-bold text-content-secondary\">{{ taskGroup.statusCounts.cancelled || 0 }}</div>\n </button>\n </div>\n <!-- Chart view -->\n <div v-show=\"statusView === 'chart'\" class=\"flex flex-col items-center justify-center bg-surface border border-edge rounded-lg p-4 gap-3\" style=\"min-height: 280px;\">\n <div v-if=\"taskGroup.totalCount > 0\" class=\"w-[240px] h-[240px] shrink-0\">\n <canvas ref=\"statusPieChart\" width=\"240\" height=\"240\" class=\"block\"></canvas>\n </div>\n <p v-else class=\"text-content-tertiary text-sm py-8\">No tasks to display</p>\n <!-- Selection labels: show which segment is selected (click to filter) -->\n <div v-if=\"taskGroup.totalCount > 0\" class=\"flex flex-wrap justify-center gap-2\">\n <button\n v-for=\"status in statusOrderForDisplay\"\n :key=\"status\"\n type=\"button\"\n class=\"text-xs px-2 py-1 rounded-full font-medium transition-all cursor-pointer\"\n :class=\"currentFilter === status ? getStatusPillClass(status) : 'bg-muted text-content-tertiary hover:bg-muted'\"\n @click=\"filterByStatus(status)\"\n >\n {{ statusLabel(status) }}\n </button>\n </div>\n </div>\n </div>\n\n <!-- Task List -->\n <div class=\"bg-surface rounded-lg shadow\">\n <div class=\"px-6 py-6 border-b border-edge flex items-center justify-between bg-page\">\n <h2 class=\"text-xl font-bold text-content\">\n Individual Tasks\n <span v-if=\"currentFilter\" class=\"ml-3 text-base font-semibold text-primary\">\n (Filtered by {{ currentFilter }})\n </span>\n </h2>\n <button \n v-if=\"currentFilter\"\n @click=\"clearFilter\"\n class=\"text-sm font-semibold text-primary hover:text-primary\"\n >\n Show All\n </button>\n </div>\n <div class=\"divide-y divide-gray-200\">\n <div v-for=\"task in sortedTasks\" :key=\"task.id\" class=\"p-6\">\n <div class=\"flex items-start justify-between\">\n <div class=\"flex-1\">\n <div class=\"flex items-center gap-3 mb-2\">\n <span class=\"text-sm font-medium text-content\">Task ID: {{ task.id }}</span>\n <router-link\n v-if=\"backTo\"\n :to=\"taskDetailRoute(task)\"\n class=\"text-sm text-primary hover:text-primary font-medium\"\n >\n View details\n </router-link>\n <span\n class=\"text-xs px-2 py-1 rounded-full font-medium\"\n :class=\"getStatusColor(task.status)\"\n >\n {{ task.status }}\n </span>\n </div>\n \n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4\">\n <div>\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Scheduled At</label>\n <div class=\"text-sm text-content\">{{ formatDate(task.scheduledAt) }}</div>\n </div>\n <div v-if=\"task.startedAt\">\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Started At</label>\n <div class=\"text-sm text-content\">{{ formatDate(task.startedAt) }}</div>\n </div>\n <div v-if=\"task.completedAt\">\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Completed At</label>\n <div class=\"text-sm text-content\">{{ formatDate(task.completedAt) }}</div>\n </div>\n <div v-if=\"task.error\">\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Error</label>\n <div class=\"text-sm text-red-600\">{{ task.error }}</div>\n </div>\n </div>\n\n <!-- Task Parameters -->\n <div v-if=\"task.parameters && Object.keys(task.parameters).length > 0\">\n <label class=\"block text-sm font-medium text-content-secondary mb-2\">Parameters</label>\n <div class=\"bg-page rounded-md p-3\">\n <pre class=\"text-sm text-gray-800 whitespace-pre-wrap\">{{ JSON.stringify(task.parameters, null, 2) }}</pre>\n </div>\n </div>\n </div>\n \n <div class=\"flex flex-col gap-3 ml-6\">\n <button \n @click=\"showRescheduleConfirmation(task)\"\n class=\"flex items-center justify-center gap-2 bg-gradient-to-r from-blue-500 to-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-blue-600 hover:to-blue-700 transform hover:scale-105 transition-all duration-200 shadow-md hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none\"\n :disabled=\"task.status === 'in_progress'\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\n </svg>\n Reschedule\n </button>\n <button \n @click=\"showRunConfirmation(task)\"\n class=\"flex items-center justify-center gap-2 bg-gradient-to-r from-green-500 to-green-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-green-600 hover:to-green-700 transform hover:scale-105 transition-all duration-200 shadow-md hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none\"\n :disabled=\"task.status === 'in_progress'\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 3l14 9-14 9V3z\"></path>\n </svg>\n Run Now\n </button>\n <button \n v-if=\"task.status === 'pending'\"\n @click=\"showCancelConfirmation(task)\"\n class=\"flex items-center justify-center gap-2 bg-gradient-to-r from-red-500 to-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-red-600 hover:to-red-700 transform hover:scale-105 transition-all duration-200 shadow-md hover:shadow-lg\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\"></path>\n </svg>\n Cancel\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Reschedule Confirmation Modal -->\n <modal v-if=\"showRescheduleModal\" containerClass=\"!max-w-md\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showRescheduleModal = false;\" role=\"button\" aria-label=\"Close modal\">×</div>\n <div class=\"p-6\">\n <div class=\"flex items-center mb-4\">\n <div class=\"flex-shrink-0\">\n <svg class=\"w-6 h-6 text-blue-600\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-lg font-medium text-content\">Reschedule Task</h3>\n </div>\n </div>\n <div class=\"mb-4\">\n <p class=\"text-sm text-gray-600\">\n Are you sure you want to reschedule task <strong>{{ selectedTask?.id }}</strong>?\n </p>\n <p class=\"text-sm text-content-tertiary mt-2\">\n This will reset the task's status and schedule it to run again.\n </p>\n \n <div class=\"mt-4\">\n <label for=\"newScheduledTime\" class=\"block text-sm font-medium text-content-secondary mb-2\">\n New Scheduled Time\n </label>\n <input\n id=\"newScheduledTime\"\n v-model=\"newScheduledTime\"\n type=\"datetime-local\"\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500\"\n required\n />\n </div>\n </div>\n <div class=\"flex gap-3\">\n <button \n @click=\"confirmRescheduleTask\"\n class=\"flex-1 bg-gradient-to-r from-blue-500 to-blue-600 text-white px-4 py-2 rounded-md hover:from-blue-600 hover:to-blue-700 font-medium\"\n >\n Reschedule\n </button>\n <button \n @click=\"showRescheduleModal = false\"\n class=\"flex-1 bg-muted hover:bg-page text-content-secondary px-4 py-2 rounded-md border border-edge-strong font-medium\"\n >\n Cancel\n </button>\n </div>\n </div>\n </template>\n </modal>\n\n <!-- Run Task Confirmation Modal -->\n <modal v-if=\"showRunModal\" containerClass=\"!max-w-md\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showRunModal = false;\" role=\"button\" aria-label=\"Close modal\">×</div>\n <div class=\"p-6\">\n <div class=\"flex items-center mb-4\">\n <div class=\"flex-shrink-0\">\n <svg class=\"w-6 h-6 text-green-600\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M14.828 14.828a4 4 0 01-5.656 0M9 10h1m4 0h1m-6 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-lg font-medium text-content\">Run Task Now</h3>\n </div>\n </div>\n <div class=\"mb-4\">\n <p class=\"text-sm text-gray-600\">\n Are you sure you want to run task <strong>{{ selectedTask?.id }}</strong> immediately?\n </p>\n <p class=\"text-sm text-content-tertiary mt-2\">\n This will execute the task right away, bypassing its scheduled time.\n </p>\n </div>\n <div class=\"flex gap-3\">\n <button \n @click=\"confirmRunTask\"\n class=\"flex-1 bg-gradient-to-r from-green-500 to-green-600 text-white px-4 py-2 rounded-md hover:from-green-600 hover:to-green-700 font-medium\"\n >\n Run Now\n </button>\n <button \n @click=\"showRunModal = false\"\n class=\"flex-1 bg-muted hover:bg-page text-content-secondary px-4 py-2 rounded-md border border-edge-strong font-medium\"\n >\n Cancel\n </button>\n </div>\n </div>\n </template>\n </modal>\n\n <!-- Cancel Task Confirmation Modal -->\n <modal v-if=\"showCancelModal\" containerClass=\"!max-w-md\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showCancelModal = false;\" role=\"button\" aria-label=\"Close modal\">×</div>\n <div class=\"p-6\">\n <div class=\"flex items-center mb-4\">\n <div class=\"flex-shrink-0\">\n <svg class=\"w-6 h-6 text-red-600\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\"></path>\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-lg font-medium text-content\">Cancel Task</h3>\n </div>\n </div>\n <div class=\"mb-4\">\n <p class=\"text-sm text-gray-600\">\n Are you sure you want to cancel task <strong>{{ selectedTask?.id }}</strong>?\n </p>\n <p class=\"text-sm text-content-tertiary mt-2\">\n This will permanently cancel the task and it cannot be undone.\n </p>\n </div>\n <div class=\"flex gap-3\">\n <button \n @click=\"confirmCancelTask\"\n class=\"flex-1 bg-gradient-to-r from-red-500 to-red-600 text-white px-4 py-2 rounded-md hover:from-red-600 hover:to-red-700 font-medium\"\n >\n Cancel Task\n </button>\n <button \n @click=\"showCancelModal = false\"\n class=\"flex-1 bg-muted hover:bg-page text-content-secondary px-4 py-2 rounded-md border border-edge-strong font-medium\"\n >\n Keep Task\n </button>\n </div>\n </div>\n </template>\n </modal>\n</div>\n\n";
|
|
50340
50858
|
|
|
50341
50859
|
/***/ },
|
|
50342
50860
|
|
|
@@ -61672,7 +62190,7 @@ var src_default = VueToastificationPlugin;
|
|
|
61672
62190
|
(module) {
|
|
61673
62191
|
|
|
61674
62192
|
"use strict";
|
|
61675
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.3.
|
|
62193
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.3.4","description":"A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ace-builds":"^1.43.6","ai":"5.x","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","regexp.escape":"^2.0.1","tailwindcss":"3.4.0","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x","xss":"^1.0.15"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"optionalPeerDependencies":{"@mongoosejs/task":"0.5.x || 0.6.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongodb-memory-server":"^11.0.1","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","seed":"node seed/index.js","start":"node ./local.js","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js","test:frontend":"mocha test/frontend/*.test.js"}}');
|
|
61676
62194
|
|
|
61677
62195
|
/***/ }
|
|
61678
62196
|
|