@mongoosejs/studio 0.2.12 → 0.2.13

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.
@@ -479,6 +479,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
479
479
  yield { document: doc };
480
480
  }
481
481
  },
482
+ getEstimatedDocumentCounts: function getEstimatedDocumentCounts() {
483
+ return client.post('', { action: 'Model.getEstimatedDocumentCounts' }).then(res => res.data);
484
+ },
482
485
  streamDocumentChanges: async function* streamDocumentChanges(params, options = {}) {
483
486
  const pollIntervalMs = 5000;
484
487
  while (!options.signal?.aborted) {
@@ -729,6 +732,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
729
732
  }
730
733
  }
731
734
  },
735
+ getEstimatedDocumentCounts: function getEstimatedDocumentCounts() {
736
+ return client.post('/Model/getEstimatedDocumentCounts', {}).then(res => res.data);
737
+ },
732
738
  streamDocumentChanges: async function* streamDocumentChanges(params, options = {}) {
733
739
  const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
734
740
  const url = window.MONGOOSE_STUDIO_CONFIG.baseURL + '/Model/streamDocumentChanges?' + new URLSearchParams(params).toString();
@@ -4502,11 +4508,17 @@ module.exports = app => app.component('document', {
4502
4508
  }),
4503
4509
  async mounted() {
4504
4510
  window.pageState = this;
4511
+ if (typeof window !== 'undefined' && window.addEventListener) {
4512
+ window.addEventListener('keydown', this.handleSaveShortcut);
4513
+ }
4505
4514
  // Store query parameters from the route (preserved from models page)
4506
4515
  this.previousQuery = Object.assign({}, this.$route.query);
4507
4516
  await this.refreshDocument({ force: true, source: 'initial' });
4508
4517
  },
4509
- beforeDestroy() {
4518
+ beforeUnmount() {
4519
+ if (typeof window !== 'undefined' && window.removeEventListener) {
4520
+ window.removeEventListener('keydown', this.handleSaveShortcut);
4521
+ }
4510
4522
  this.stopAutoRefresh();
4511
4523
  },
4512
4524
  computed: {
@@ -4536,6 +4548,13 @@ module.exports = app => app.component('document', {
4536
4548
  canEdit() {
4537
4549
  return this.canManipulate && this.viewMode === 'fields';
4538
4550
  },
4551
+ keyboardShortcuts() {
4552
+ const shortcuts = [];
4553
+ if (this.editting && this.canManipulate) {
4554
+ shortcuts.push({ command: 'Ctrl + S', description: 'Save document' });
4555
+ }
4556
+ return shortcuts;
4557
+ },
4539
4558
  isLambda() {
4540
4559
  return !!window?.MONGOOSE_STUDIO_CONFIG?.isLambda;
4541
4560
  }
@@ -4548,6 +4567,19 @@ module.exports = app => app.component('document', {
4548
4567
  }
4549
4568
  },
4550
4569
  methods: {
4570
+ handleSaveShortcut(event) {
4571
+ const key = typeof event?.key === 'string' ? event.key.toLowerCase() : '';
4572
+ const isSaveShortcut = (event.ctrlKey || event.metaKey) && key === 's';
4573
+ if (!isSaveShortcut) {
4574
+ return;
4575
+ }
4576
+ if (!this.editting || !this.canManipulate) {
4577
+ return;
4578
+ }
4579
+
4580
+ event.preventDefault();
4581
+ this.shouldShowConfirmModal = true;
4582
+ },
4551
4583
  cancelEdit() {
4552
4584
  this.changes = {};
4553
4585
  this.editting = false;
@@ -6578,6 +6610,7 @@ module.exports = app => app.component('models', {
6578
6610
  data: () => ({
6579
6611
  models: [],
6580
6612
  currentModel: null,
6613
+ modelDocumentCounts: {},
6581
6614
  documents: [],
6582
6615
  schemaPaths: [],
6583
6616
  filteredPaths: [],
@@ -6642,6 +6675,7 @@ module.exports = app => app.component('models', {
6642
6675
  document.addEventListener('click', this.onOutsideActionsMenuClick, true);
6643
6676
  const { models, readyState } = await api.Model.listModels();
6644
6677
  this.models = models;
6678
+ await this.loadModelCounts();
6645
6679
  if (this.currentModel == null && this.models.length > 0) {
6646
6680
  this.currentModel = this.models[0];
6647
6681
  }
@@ -7147,6 +7181,25 @@ module.exports = app => app.component('models', {
7147
7181
 
7148
7182
  return value.toLocaleString();
7149
7183
  },
7184
+ formatCompactCount(value) {
7185
+ if (typeof value !== 'number') {
7186
+ return '—';
7187
+ }
7188
+ if (value < 1000) {
7189
+ return `${value}`;
7190
+ }
7191
+ const formatValue = (number, suffix) => {
7192
+ const rounded = (Math.round(number * 10) / 10).toFixed(1).replace(/\.0$/, '');
7193
+ return `${rounded}${suffix}`;
7194
+ };
7195
+ if (value < 1000000) {
7196
+ return formatValue(value / 1000, 'k');
7197
+ }
7198
+ if (value < 1000000000) {
7199
+ return formatValue(value / 1000000, 'M');
7200
+ }
7201
+ return formatValue(value / 1000000000, 'B');
7202
+ },
7150
7203
  checkIndexLocation(indexName) {
7151
7204
  if (this.schemaIndexes.find(x => x.name == indexName) && this.mongoDBIndexes.find(x => x.name == indexName)) {
7152
7205
  return 'text-gray-500';
@@ -7400,6 +7453,19 @@ module.exports = app => app.component('models', {
7400
7453
  } else {
7401
7454
  this.selectMultiple = true;
7402
7455
  }
7456
+ },
7457
+ async loadModelCounts() {
7458
+ if (!Array.isArray(this.models) || this.models.length === 0) {
7459
+ return;
7460
+ }
7461
+ try {
7462
+ const { counts } = await api.Model.getEstimatedDocumentCounts();
7463
+ if (counts && typeof counts === 'object') {
7464
+ this.modelDocumentCounts = counts;
7465
+ }
7466
+ } catch (err) {
7467
+ console.error('Failed to load model document counts', err);
7468
+ }
7403
7469
  }
7404
7470
  }
7405
7471
  });
@@ -7693,7 +7759,7 @@ module.exports = app => app.component('navbar', {
7693
7759
  return ['chat index', 'chat'].includes(this.$route.name);
7694
7760
  },
7695
7761
  taskView() {
7696
- return ['tasks'].includes(this.$route.name);
7762
+ return ['tasks', 'taskByName', 'taskSingle'].includes(this.$route.name);
7697
7763
  },
7698
7764
  routeName() {
7699
7765
  return this.$route.name;
@@ -7770,14 +7836,14 @@ module.exports = app => app.component('navbar', {
7770
7836
 
7771
7837
  // Role-based access control configuration
7772
7838
  const roleAccess = {
7773
- owner: ['root', 'model', 'document', 'dashboards', 'dashboard', 'team', 'chat'],
7774
- admin: ['root', 'model', 'document', 'dashboards', 'dashboard', 'team', 'chat'],
7775
- member: ['root', 'model', 'document', 'dashboards', 'dashboard', 'chat'],
7839
+ owner: ['root', 'model', 'document', 'dashboards', 'dashboard', 'team', 'chat', 'tasks', 'taskByName', 'taskSingle'],
7840
+ admin: ['root', 'model', 'document', 'dashboards', 'dashboard', 'team', 'chat', 'tasks', 'taskByName', 'taskSingle'],
7841
+ member: ['root', 'model', 'document', 'dashboards', 'dashboard', 'chat', 'tasks', 'taskByName', 'taskSingle'],
7776
7842
  readonly: ['root', 'model', 'document', 'chat'],
7777
7843
  dashboards: ['dashboards', 'dashboard']
7778
7844
  };
7779
7845
 
7780
- const allowedRoutesForLocalDev = ['document', 'root', 'chat', 'model'];
7846
+ const allowedRoutesForLocalDev = ['document', 'root', 'chat', 'model', 'tasks', 'taskByName', 'taskSingle'];
7781
7847
 
7782
7848
  // Helper function to check if a role has access to a route
7783
7849
  function hasAccess(roles, routeName) {
@@ -7844,6 +7910,22 @@ module.exports = {
7844
7910
  authorized: true
7845
7911
  }
7846
7912
  },
7913
+ {
7914
+ path: '/tasks/:name',
7915
+ name: 'taskByName',
7916
+ component: 'task-by-name',
7917
+ meta: {
7918
+ authorized: true
7919
+ }
7920
+ },
7921
+ {
7922
+ path: '/tasks/:name/:id',
7923
+ name: 'taskSingle',
7924
+ component: 'task-single',
7925
+ meta: {
7926
+ authorized: true
7927
+ }
7928
+ },
7847
7929
  {
7848
7930
  path: '/chat',
7849
7931
  name: 'chat index',
@@ -7903,6 +7985,222 @@ module.exports = app => app.component('splash', {
7903
7985
  });
7904
7986
 
7905
7987
 
7988
+ /***/ },
7989
+
7990
+ /***/ "./frontend/src/task-by-name/task-by-name.js"
7991
+ /*!***************************************************!*\
7992
+ !*** ./frontend/src/task-by-name/task-by-name.js ***!
7993
+ \***************************************************/
7994
+ (module, __unused_webpack_exports, __webpack_require__) {
7995
+
7996
+ "use strict";
7997
+
7998
+
7999
+ // Page: all tasks with a given name. Reuses task-details to render the list (many tasks).
8000
+ const template = __webpack_require__(/*! ./task-by-name.html */ "./frontend/src/task-by-name/task-by-name.html");
8001
+ const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
8002
+
8003
+ function buildTaskGroup(name, tasks) {
8004
+ const statusCounts = { pending: 0, succeeded: 0, failed: 0, cancelled: 0, in_progress: 0, unknown: 0 };
8005
+ let lastRun = null;
8006
+ tasks.forEach(task => {
8007
+ const status = task.status || 'unknown';
8008
+ if (Object.prototype.hasOwnProperty.call(statusCounts, status)) {
8009
+ statusCounts[status]++;
8010
+ } else {
8011
+ statusCounts.unknown++;
8012
+ }
8013
+ const taskTime = new Date(task.scheduledAt || task.createdAt || 0);
8014
+ if (!lastRun || taskTime > lastRun) lastRun = taskTime;
8015
+ });
8016
+ return {
8017
+ name,
8018
+ tasks,
8019
+ statusCounts,
8020
+ totalCount: tasks.length,
8021
+ lastRun
8022
+ };
8023
+ }
8024
+
8025
+ module.exports = app => app.component('task-by-name', {
8026
+ template,
8027
+ data: () => ({
8028
+ status: 'init',
8029
+ taskGroup: null,
8030
+ errorMessage: ''
8031
+ }),
8032
+ computed: {
8033
+ taskName() {
8034
+ return this.$route.params.name || '';
8035
+ }
8036
+ },
8037
+ watch: {
8038
+ taskName: {
8039
+ immediate: true,
8040
+ handler() {
8041
+ this.loadTasks();
8042
+ }
8043
+ }
8044
+ },
8045
+ methods: {
8046
+ async loadTasks() {
8047
+ if (!this.taskName) return;
8048
+ this.status = 'init';
8049
+ this.taskGroup = null;
8050
+ this.errorMessage = '';
8051
+ const start = new Date();
8052
+ start.setHours(start.getHours() - 1);
8053
+ const end = new Date();
8054
+ try {
8055
+ const { tasks } = await api.Task.getTasks({
8056
+ name: this.taskName,
8057
+ start,
8058
+ end
8059
+ });
8060
+ this.taskGroup = buildTaskGroup(this.taskName, tasks);
8061
+ this.status = 'loaded';
8062
+ } catch (err) {
8063
+ this.status = 'error';
8064
+ this.errorMessage = err?.response?.data?.message || err.message || 'Failed to load tasks';
8065
+ }
8066
+ },
8067
+ async onTaskCreated() {
8068
+ await this.loadTasks();
8069
+ },
8070
+ async onTaskCancelled() {
8071
+ await this.loadTasks();
8072
+ }
8073
+ }
8074
+ });
8075
+
8076
+
8077
+ /***/ },
8078
+
8079
+ /***/ "./frontend/src/task-single/task-single.js"
8080
+ /*!*************************************************!*\
8081
+ !*** ./frontend/src/task-single/task-single.js ***!
8082
+ \*************************************************/
8083
+ (module, __unused_webpack_exports, __webpack_require__) {
8084
+
8085
+ "use strict";
8086
+
8087
+
8088
+ // Page: one task by id. Dedicated single-task detail UI (not the list).
8089
+ const template = __webpack_require__(/*! ./task-single.html */ "./frontend/src/task-single/task-single.html");
8090
+ const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
8091
+
8092
+ module.exports = app => app.component('task-single', {
8093
+ template,
8094
+ data: () => ({
8095
+ status: 'init',
8096
+ task: null,
8097
+ errorMessage: '',
8098
+ showRescheduleModal: false,
8099
+ showRunModal: false,
8100
+ showCancelModal: false,
8101
+ selectedTask: null,
8102
+ newScheduledTime: ''
8103
+ }),
8104
+ computed: {
8105
+ taskId() {
8106
+ return this.$route.params.id || '';
8107
+ },
8108
+ taskByNamePath() {
8109
+ const name = this.task?.name ?? this.$route.params.name ?? '';
8110
+ const path = `/tasks/${encodeURIComponent(name || '')}`;
8111
+ const query = this.$route.query?.status ? { status: this.$route.query.status } : {};
8112
+ return { path, query };
8113
+ },
8114
+ },
8115
+ watch: {
8116
+ '$route.params': {
8117
+ deep: true,
8118
+ handler() {
8119
+ this.loadTask();
8120
+ }
8121
+ }
8122
+ },
8123
+ methods: {
8124
+ getStatusColor(status) {
8125
+ if (status === 'succeeded') return 'bg-green-100 text-green-800';
8126
+ if (status === 'pending') return 'bg-yellow-100 text-yellow-800';
8127
+ if (status === 'cancelled') return 'bg-gray-100 text-gray-800';
8128
+ if (status === 'failed') return 'bg-red-100 text-red-800';
8129
+ if (status === 'in_progress') return 'bg-blue-100 text-blue-800';
8130
+ return 'bg-slate-100 text-slate-800';
8131
+ },
8132
+ formatDate(dateString) {
8133
+ if (!dateString) return 'N/A';
8134
+ return new Date(dateString).toLocaleString();
8135
+ },
8136
+ async loadTask() {
8137
+ if (!this.taskId) return;
8138
+ this.status = 'init';
8139
+ this.task = null;
8140
+ this.errorMessage = '';
8141
+ try {
8142
+ const { doc } = await api.Model.getDocument({ model: 'Task', documentId: this.taskId });
8143
+ this.task = doc;
8144
+ this.status = 'loaded';
8145
+ } catch (err) {
8146
+ const status = err?.response?.status;
8147
+ const notFound = status === 404 || err?.response?.data?.name === 'DocumentNotFoundError';
8148
+ this.status = notFound ? 'notfound' : 'error';
8149
+ this.errorMessage = notFound ? '' : (err?.response?.data?.message || err.message || 'Failed to load task');
8150
+ }
8151
+ },
8152
+ showRescheduleConfirmation(task) {
8153
+ this.selectedTask = task;
8154
+ const defaultTime = new Date();
8155
+ defaultTime.setHours(defaultTime.getHours() + 1);
8156
+ this.newScheduledTime = defaultTime.toISOString().slice(0, 16);
8157
+ this.showRescheduleModal = true;
8158
+ },
8159
+ showRunConfirmation(task) {
8160
+ this.selectedTask = task;
8161
+ this.showRunModal = true;
8162
+ },
8163
+ showCancelConfirmation(task) {
8164
+ this.selectedTask = task;
8165
+ this.showCancelModal = true;
8166
+ },
8167
+ async confirmRescheduleTask() {
8168
+ if (!this.newScheduledTime) return;
8169
+ await api.Task.rescheduleTask({ taskId: this.selectedTask.id, scheduledAt: this.newScheduledTime });
8170
+ this.$toast.success({ title: 'Task Rescheduled', text: `Task ${this.selectedTask.id} has been rescheduled`, });
8171
+ this.showRescheduleModal = false;
8172
+ this.selectedTask = null;
8173
+ this.newScheduledTime = '';
8174
+ await this.loadTask();
8175
+ },
8176
+ async confirmRunTask() {
8177
+ await api.Task.runTask({ taskId: this.selectedTask.id });
8178
+ this.$toast.success({ title: 'Task Started', text: `Task ${this.selectedTask.id} is now running`, type: 'success' });
8179
+ this.showRunModal = false;
8180
+ this.selectedTask = null;
8181
+ await this.loadTask();
8182
+ },
8183
+ goBack() {
8184
+ if (window.history.length > 1) {
8185
+ window.history.back();
8186
+ } else {
8187
+ this.$router.push(this.taskByNamePath);
8188
+ }
8189
+ },
8190
+ async confirmCancelTask() {
8191
+ await api.Task.cancelTask({ taskId: this.selectedTask.id });
8192
+ this.$toast.success({ title: 'Task Cancelled', text: `Task ${this.selectedTask.id} has been cancelled` });
8193
+ this.showCancelModal = false;
8194
+ this.selectedTask = null;
8195
+ this.goBack();
8196
+ }
8197
+ },
8198
+ mounted() {
8199
+ this.loadTask();
8200
+ }
8201
+ });
8202
+
8203
+
7906
8204
  /***/ },
7907
8205
 
7908
8206
  /***/ "./frontend/src/tasks/task-details/task-details.js"
@@ -7917,16 +8215,30 @@ module.exports = app => app.component('splash', {
7917
8215
  const template = __webpack_require__(/*! ./task-details.html */ "./frontend/src/tasks/task-details/task-details.html");
7918
8216
  const api = __webpack_require__(/*! ../../api */ "./frontend/src/api.js");
7919
8217
 
8218
+ const STATUS_ORDER = ['pending', 'succeeded', 'failed', 'cancelled'];
8219
+ const PIE_COLORS = ['#eab308', '#22c55e', '#ef4444', '#6b7280'];
8220
+ const PIE_HOVER = ['#ca8a04', '#16a34a', '#dc2626', '#4b5563'];
8221
+
7920
8222
  module.exports = app => app.component('task-details', {
7921
- props: ['taskGroup', 'currentFilter'],
8223
+ props: {
8224
+ taskGroup: { type: Object, required: true },
8225
+ backTo: { type: Object, default: null }
8226
+ },
7922
8227
  data: () => ({
8228
+ currentFilter: null,
7923
8229
  showRescheduleModal: false,
7924
8230
  showRunModal: false,
7925
8231
  showCancelModal: false,
7926
8232
  selectedTask: null,
7927
- newScheduledTime: ''
8233
+ newScheduledTime: '',
8234
+ statusView: 'summary',
8235
+ statusChart: null
7928
8236
  }),
7929
8237
  computed: {
8238
+ backLabel() {
8239
+ if (this.backTo?.path?.startsWith('/tasks/') || this.backTo?.name === 'taskByName') return `Back to ${this.taskGroup?.name || 'tasks'}`;
8240
+ return 'Back to Task Groups';
8241
+ },
7930
8242
  sortedTasks() {
7931
8243
  let tasks = this.taskGroup.tasks;
7932
8244
 
@@ -7940,9 +8252,123 @@ module.exports = app => app.component('task-details', {
7940
8252
  const dateB = new Date(b.scheduledAt || b.createdAt || 0);
7941
8253
  return dateB - dateA; // Most recent first
7942
8254
  });
8255
+ },
8256
+ pieChartData() {
8257
+ const counts = this.taskGroup?.statusCounts || {};
8258
+ return {
8259
+ labels: ['Pending', 'Succeeded', 'Failed', 'Cancelled'],
8260
+ datasets: [{
8261
+ data: STATUS_ORDER.map(s => counts[s] || 0),
8262
+ backgroundColor: PIE_COLORS,
8263
+ hoverBackgroundColor: PIE_HOVER,
8264
+ borderWidth: 2,
8265
+ borderColor: '#fff'
8266
+ }]
8267
+ };
8268
+ },
8269
+ statusOrderForDisplay() {
8270
+ return STATUS_ORDER;
7943
8271
  }
7944
8272
  },
8273
+ watch: {
8274
+ '$route.query.status': {
8275
+ handler(status) {
8276
+ this.currentFilter = status || null;
8277
+ },
8278
+ immediate: true
8279
+ },
8280
+ statusView(val) {
8281
+ if (val !== 'chart') this.destroyStatusChart();
8282
+ else {
8283
+ this.$nextTick(() => {
8284
+ requestAnimationFrame(() => this.ensureStatusChart());
8285
+ });
8286
+ }
8287
+ },
8288
+ taskGroup: {
8289
+ deep: true,
8290
+ handler() {
8291
+ this.$nextTick(() => {
8292
+ requestAnimationFrame(() => this.ensureStatusChart());
8293
+ });
8294
+ }
8295
+ },
8296
+ },
7945
8297
  methods: {
8298
+ destroyStatusChart() {
8299
+ if (this.statusChart) {
8300
+ try {
8301
+ this.statusChart.destroy();
8302
+ } catch (_) {
8303
+ // ignore Chart.js teardown errors
8304
+ }
8305
+ this.statusChart = null;
8306
+ }
8307
+ },
8308
+ isChartCanvasReady(canvas) {
8309
+ return canvas && typeof canvas.getContext === 'function' && canvas.isConnected && canvas.offsetParent != null;
8310
+ },
8311
+ ensureStatusChart() {
8312
+ if (this.statusView !== 'chart' || !this.taskGroup || this.taskGroup.totalCount === 0) {
8313
+ this.destroyStatusChart();
8314
+ return;
8315
+ }
8316
+ const canvas = this.$refs.statusPieChart;
8317
+ if (!canvas || !this.isChartCanvasReady(canvas)) return;
8318
+ const Chart = typeof window !== 'undefined' && window.Chart;
8319
+ if (!Chart) return;
8320
+ const data = this.pieChartData;
8321
+ if (this.statusChart) {
8322
+ try {
8323
+ this.statusChart.data.labels = data.labels;
8324
+ this.statusChart.data.datasets[0].data = data.datasets[0].data;
8325
+ this.statusChart.update('none');
8326
+ } catch (_) {
8327
+ this.destroyStatusChart();
8328
+ }
8329
+ return;
8330
+ }
8331
+ try {
8332
+ this.statusChart = new Chart(canvas, {
8333
+ type: 'doughnut',
8334
+ data,
8335
+ options: {
8336
+ responsive: false,
8337
+ maintainAspectRatio: false,
8338
+ animation: false,
8339
+ layout: {
8340
+ padding: 8
8341
+ },
8342
+ onClick: (_evt, elements) => {
8343
+ if (elements && elements.length > 0) {
8344
+ const status = STATUS_ORDER[elements[0].index];
8345
+ this.$nextTick(() => this.filterByStatus(status));
8346
+ }
8347
+ },
8348
+ plugins: {
8349
+ legend: {
8350
+ display: true,
8351
+ position: 'bottom'
8352
+ }
8353
+ }
8354
+ }
8355
+ });
8356
+ } catch (_) {
8357
+ this.statusChart = null;
8358
+ }
8359
+ },
8360
+ statusLabel(status) {
8361
+ return status.charAt(0).toUpperCase() + status.slice(1).replace('_', ' ');
8362
+ },
8363
+ getStatusPillClass(status) {
8364
+ const classes = {
8365
+ pending: 'bg-yellow-200 text-yellow-900 ring-2 ring-yellow-500',
8366
+ succeeded: 'bg-green-200 text-green-900 ring-2 ring-green-500',
8367
+ failed: 'bg-red-200 text-red-900 ring-2 ring-red-500',
8368
+ cancelled: 'bg-gray-200 text-gray-900 ring-2 ring-gray-500'
8369
+ };
8370
+ return classes[status] || 'bg-slate-200 text-slate-900 ring-2 ring-slate-500';
8371
+ },
7946
8372
  getStatusColor(status) {
7947
8373
  if (status === 'succeeded') {
7948
8374
  return 'bg-green-100 text-green-800';
@@ -7979,15 +8405,35 @@ module.exports = app => app.component('task-details', {
7979
8405
  this.$emit('task-cancelled');
7980
8406
  },
7981
8407
  filterByStatus(status) {
7982
- // If clicking the same status, clear the filter
7983
- if (this.currentFilter === status) {
7984
- this.$emit('update:currentFilter', null);
8408
+ const next = this.currentFilter === status ? null : status;
8409
+ this.currentFilter = next;
8410
+ const query = { ...this.$route.query };
8411
+ if (next) query.status = next;
8412
+ else delete query.status;
8413
+ this.$router.replace({ path: this.$route.path, query });
8414
+ },
8415
+ clearFilter() {
8416
+ this.currentFilter = null;
8417
+ const query = { ...this.$route.query };
8418
+ delete query.status;
8419
+ this.$router.replace({ path: this.$route.path, query });
8420
+ },
8421
+ goBack() {
8422
+ if (this.backTo) {
8423
+ if (window.history.length > 1) {
8424
+ window.history.back();
8425
+ } else {
8426
+ this.$router.push(this.backTo);
8427
+ }
7985
8428
  } else {
7986
- this.$emit('update:currentFilter', status);
8429
+ this.$emit('back');
7987
8430
  }
7988
8431
  },
7989
- clearFilter() {
7990
- this.$emit('update:currentFilter', null);
8432
+ taskDetailRoute(task) {
8433
+ const id = String(task.id || task._id);
8434
+ const path = `/tasks/${encodeURIComponent(this.taskGroup.name || '')}/${id}`;
8435
+ const query = this.currentFilter ? { status: this.currentFilter } : {};
8436
+ return { path, query };
7991
8437
  },
7992
8438
  showRescheduleConfirmation(task) {
7993
8439
  this.selectedTask = task;
@@ -8087,11 +8533,14 @@ module.exports = app => app.component('task-details', {
8087
8533
 
8088
8534
  },
8089
8535
  mounted() {
8090
- // Check if the task group was already filtered when passed from parent
8091
8536
  if (this.taskGroup.filteredStatus && !this.currentFilter) {
8092
- this.$emit('update:currentFilter', this.taskGroup.filteredStatus);
8537
+ this.currentFilter = this.taskGroup.filteredStatus;
8538
+ this.$router.replace({ path: this.$route.path, query: { ...this.$route.query, status: this.taskGroup.filteredStatus } });
8093
8539
  }
8094
8540
  },
8541
+ beforeUnmount() {
8542
+ this.destroyStatusChart();
8543
+ },
8095
8544
  template: template
8096
8545
  });
8097
8546
 
@@ -8115,11 +8564,12 @@ module.exports = app => app.component('tasks', {
8115
8564
  status: 'init',
8116
8565
  tasks: [],
8117
8566
  groupedTasks: {},
8118
- selectedRange: 'today',
8567
+ selectedRange: 'last_hour',
8119
8568
  start: null,
8120
8569
  end: null,
8121
8570
  dateFilters: [
8122
8571
  { value: 'all', label: 'All Time' },
8572
+ { value: 'last_hour', label: 'Last Hour' },
8123
8573
  { value: 'today', label: 'Today' },
8124
8574
  { value: 'yesterday', label: 'Yesterday' },
8125
8575
  { value: 'thisWeek', label: 'This Week' },
@@ -8138,10 +8588,6 @@ module.exports = app => app.component('tasks', {
8138
8588
  ],
8139
8589
  searchQuery: '',
8140
8590
  searchTimeout: null,
8141
- // Task details view state
8142
- showTaskDetails: false,
8143
- selectedTaskGroup: null,
8144
- taskDetailsFilter: null,
8145
8591
  // Create task modal state
8146
8592
  showCreateTaskModal: false,
8147
8593
  newTask: {
@@ -8177,28 +8623,10 @@ module.exports = app => app.component('tasks', {
8177
8623
  this.groupedTasks = groupedTasks;
8178
8624
  },
8179
8625
  openTaskGroupDetails(group) {
8180
- this.selectedTaskGroup = group;
8181
- this.showTaskDetails = true;
8626
+ this.$router.push({ path: `/tasks/${encodeURIComponent(group.name || '')}` });
8182
8627
  },
8183
8628
  openTaskGroupDetailsWithFilter(group, status) {
8184
- // Create a filtered version of the task group with only the specified status
8185
- const filteredGroup = {
8186
- ...group,
8187
- tasks: group.tasks.filter(task => task.status === status),
8188
- filteredStatus: status
8189
- };
8190
- this.selectedTaskGroup = filteredGroup;
8191
- this.taskDetailsFilter = status;
8192
- this.showTaskDetails = true;
8193
- },
8194
- async onTaskCancelled() {
8195
- // Refresh the task data when a task is cancelled
8196
- await this.getTasks();
8197
- },
8198
- hideTaskDetails() {
8199
- this.showTaskDetails = false;
8200
- this.selectedTaskGroup = null;
8201
- this.taskDetailsFilter = null;
8629
+ this.$router.push({ path: `/tasks/${encodeURIComponent(group.name || '')}`, query: status ? { status } : {} });
8202
8630
  },
8203
8631
  async onTaskCreated() {
8204
8632
  // Refresh the task data when a new task is created
@@ -8362,6 +8790,11 @@ module.exports = app => app.component('tasks', {
8362
8790
  let start, end;
8363
8791
 
8364
8792
  switch (this.selectedRange) {
8793
+ case 'last_hour':
8794
+ start = new Date();
8795
+ start.setHours(start.getHours() - 1);
8796
+ end = new Date();
8797
+ break;
8365
8798
  case 'today':
8366
8799
  start = new Date();
8367
8800
  start.setHours(0, 0, 0, 0);
@@ -8880,6 +9313,12 @@ var map = {
8880
9313
  "./splash/splash": "./frontend/src/splash/splash.js",
8881
9314
  "./splash/splash.html": "./frontend/src/splash/splash.html",
8882
9315
  "./splash/splash.js": "./frontend/src/splash/splash.js",
9316
+ "./task-by-name/task-by-name": "./frontend/src/task-by-name/task-by-name.js",
9317
+ "./task-by-name/task-by-name.html": "./frontend/src/task-by-name/task-by-name.html",
9318
+ "./task-by-name/task-by-name.js": "./frontend/src/task-by-name/task-by-name.js",
9319
+ "./task-single/task-single": "./frontend/src/task-single/task-single.js",
9320
+ "./task-single/task-single.html": "./frontend/src/task-single/task-single.html",
9321
+ "./task-single/task-single.js": "./frontend/src/task-single/task-single.js",
8883
9322
  "./tasks/task-details/task-details": "./frontend/src/tasks/task-details/task-details.js",
8884
9323
  "./tasks/task-details/task-details.html": "./frontend/src/tasks/task-details/task-details.html",
8885
9324
  "./tasks/task-details/task-details.js": "./frontend/src/tasks/task-details/task-details.js",
@@ -8983,7 +9422,7 @@ __webpack_require__.r(__webpack_exports__);
8983
9422
  /* harmony export */ });
8984
9423
  /* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
8985
9424
  /**
8986
- * @vue/reactivity v3.5.28
9425
+ * @vue/reactivity v3.5.29
8987
9426
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
8988
9427
  * @license MIT
8989
9428
  **/
@@ -11123,7 +11562,7 @@ __webpack_require__.r(__webpack_exports__);
11123
11562
  /* harmony import */ var _vue_reactivity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/reactivity */ "./node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js");
11124
11563
  /* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
11125
11564
  /**
11126
- * @vue/runtime-core v3.5.28
11565
+ * @vue/runtime-core v3.5.29
11127
11566
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
11128
11567
  * @license MIT
11129
11568
  **/
@@ -12652,6 +13091,7 @@ function resolveTransitionHooks(vnode, props, state, instance, postClone) {
12652
13091
  callHook(hook, [el]);
12653
13092
  },
12654
13093
  enter(el) {
13094
+ if (leavingVNodesCache[key] === vnode) return;
12655
13095
  let hook = onEnter;
12656
13096
  let afterHook = onAfterEnter;
12657
13097
  let cancelHook = onEnterCancelled;
@@ -14752,13 +15192,24 @@ function withAsyncContext(getAwaitable) {
14752
15192
  }
14753
15193
  let awaitable = getAwaitable();
14754
15194
  unsetCurrentInstance();
15195
+ const cleanup = () => {
15196
+ if (getCurrentInstance() !== ctx) ctx.scope.off();
15197
+ unsetCurrentInstance();
15198
+ };
14755
15199
  if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.isPromise)(awaitable)) {
14756
15200
  awaitable = awaitable.catch((e) => {
14757
15201
  setCurrentInstance(ctx);
15202
+ Promise.resolve().then(() => Promise.resolve().then(cleanup));
14758
15203
  throw e;
14759
15204
  });
14760
15205
  }
14761
- return [awaitable, () => setCurrentInstance(ctx)];
15206
+ return [
15207
+ awaitable,
15208
+ () => {
15209
+ setCurrentInstance(ctx);
15210
+ Promise.resolve().then(cleanup);
15211
+ }
15212
+ ];
14762
15213
  }
14763
15214
 
14764
15215
  function createDuplicateChecker() {
@@ -19643,7 +20094,7 @@ function isMemoSame(cached, memo) {
19643
20094
  return true;
19644
20095
  }
19645
20096
 
19646
- const version = "3.5.28";
20097
+ const version = "3.5.29";
19647
20098
  const warn = true ? warn$1 : 0;
19648
20099
  const ErrorTypeStrings = ErrorTypeStrings$1 ;
19649
20100
  const devtools = true ? devtools$1 : 0;
@@ -19854,7 +20305,7 @@ __webpack_require__.r(__webpack_exports__);
19854
20305
  /* harmony import */ var _vue_runtime_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/runtime-core */ "./node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js");
19855
20306
  /* harmony import */ var _vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
19856
20307
  /**
19857
- * @vue/runtime-dom v3.5.28
20308
+ * @vue/runtime-dom v3.5.29
19858
20309
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
19859
20310
  * @license MIT
19860
20311
  **/
@@ -21878,7 +22329,7 @@ __webpack_require__.r(__webpack_exports__);
21878
22329
  /* harmony export */ toTypeString: () => (/* binding */ toTypeString)
21879
22330
  /* harmony export */ });
21880
22331
  /**
21881
- * @vue/shared v3.5.28
22332
+ * @vue/shared v3.5.29
21882
22333
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
21883
22334
  * @license MIT
21884
22335
  **/
@@ -23125,7 +23576,7 @@ module.exports = {
23125
23576
  "use strict";
23126
23577
 
23127
23578
  Object.defineProperty(exports, "__esModule", ({ value: true }));
23128
- exports.toUTF8 = exports.getBigInt64LE = exports.getFloat64LE = exports.getInt32LE = exports.UUID = exports.Timestamp = exports.serialize = exports.ObjectId = exports.MinKey = exports.MaxKey = exports.Long = exports.Int32 = exports.EJSON = exports.Double = exports.deserialize = exports.Decimal128 = exports.DBRef = exports.Code = exports.calculateObjectSize = exports.BSONType = exports.BSONSymbol = exports.BSONRegExp = exports.BSONError = exports.BSON = exports.Binary = void 0;
23579
+ exports.toUTF8 = exports.getBigInt64LE = exports.getFloat64LE = exports.getInt32LE = exports.UUID = exports.Timestamp = exports.serialize = exports.ObjectId = exports.NumberUtils = exports.MinKey = exports.MaxKey = exports.Long = exports.Int32 = exports.EJSON = exports.Double = exports.deserialize = exports.Decimal128 = exports.DBRef = exports.Code = exports.calculateObjectSize = exports.BSONType = exports.BSONSymbol = exports.BSONRegExp = exports.BSONError = exports.BSON = exports.Binary = void 0;
23129
23580
  exports.parseToElementsToArray = parseToElementsToArray;
23130
23581
  exports.pluckBSONSerializeOptions = pluckBSONSerializeOptions;
23131
23582
  exports.resolveBSONOptions = resolveBSONOptions;
@@ -23150,6 +23601,7 @@ Object.defineProperty(exports, "Int32", ({ enumerable: true, get: function () {
23150
23601
  Object.defineProperty(exports, "Long", ({ enumerable: true, get: function () { return bson_2.Long; } }));
23151
23602
  Object.defineProperty(exports, "MaxKey", ({ enumerable: true, get: function () { return bson_2.MaxKey; } }));
23152
23603
  Object.defineProperty(exports, "MinKey", ({ enumerable: true, get: function () { return bson_2.MinKey; } }));
23604
+ Object.defineProperty(exports, "NumberUtils", ({ enumerable: true, get: function () { return bson_2.NumberUtils; } }));
23153
23605
  Object.defineProperty(exports, "ObjectId", ({ enumerable: true, get: function () { return bson_2.ObjectId; } }));
23154
23606
  Object.defineProperty(exports, "serialize", ({ enumerable: true, get: function () { return bson_2.serialize; } }));
23155
23607
  Object.defineProperty(exports, "Timestamp", ({ enumerable: true, get: function () { return bson_2.Timestamp; } }));
@@ -23815,7 +24267,7 @@ __webpack_require__.r(__webpack_exports__);
23815
24267
  /* 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");
23816
24268
  /* 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");
23817
24269
  /**
23818
- * vue v3.5.28
24270
+ * vue v3.5.29
23819
24271
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
23820
24272
  * @license MIT
23821
24273
  **/
@@ -24969,7 +25421,7 @@ module.exports = "<div class=\"relative flex items-start\" :class=\"{'justify-en
24969
25421
  (module) {
24970
25422
 
24971
25423
  "use strict";
24972
- module.exports = "<div class=\"flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <div\n class=\"fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10\"\n @click=\"hideSidebar = false\"\n v-show=\"hideSidebar\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"#5f6368\"><path d=\"M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z\"/></svg>\n </div>\n <button\n class=\"fixed top-[65px] right-4 z-10 p-2 rounded-md shadow bg-white\"\n :class=\"hasWorkspace ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 cursor-not-allowed bg-gray-50'\"\n @click=\"toggleShareThread\"\n :disabled=\"!hasWorkspace || !chatThreadId || sharingThread\"\n aria-label=\"Share thread with workspace\"\n title=\"Share thread with workspace\"\n >\n <svg v-if=\"hasWorkspace\" xmlns=\"http://www.w3.org/2000/svg\" class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7a2.48 2.48 0 0 0 0-1.39l7.02-4.11a2.5 2.5 0 1 0-.87-1.37L8.04 9.94a2.5 2.5 0 1 0 0 4.12l7.12 4.16a2.5 2.5 0 1 0 .84-1.34l-7.05-4.12c-.04-.02-.08-.05-.11-.07a2.48 2.48 0 0 0 0-1.39c.03-.02.07-.04.11-.07l7.11-4.16c.52.47 1.2.76 1.94.76a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0-1.94.94L7.97 8.43a2.5 2.5 0 1 0 0 7.14l9.09 5.3c.52-.47 1.2-.76 1.94-.76a2.5 2.5 0 1 0 0-5z\"/></svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M12 1a5 5 0 00-5 5v3H6a2 2 0 00-2 2v9a2 2 0 002 2h12a2 2 0 002-2v-9a2 2 0 00-2-2h-1V6a5 5 0 00-5-5zm-3 8V6a3 3 0 016 0v3H9zm9 2v9H6v-9h12z\"/></svg>\n </button>\n <!-- Sidebar: Chat Threads -->\n <aside class=\"border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-64 fixed lg:relative\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-64 overflow-x-hidden\">\n <div class=\"p-1 ml-2 font-bold\">Chat Threads</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none\"\n aria-label=\"Close sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"currentColor\"><path d=\"M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z\"/></svg>\n </button>\n </div>\n <div class=\"p-4 w-64\">\n <async-button\n @click=\"createNewThread\"\n class=\"w-full bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700\"\n >\n Create New Thread\n </async-button>\n </div>\n <div v-if=\"status === 'loaded' && chatThreads.length === 0\" class=\"p-4 text-sm text-gray-700\">\n No threads yet\n </div>\n <ul v-if=\"status === 'loaded'\" class=\"w-full\">\n <li\n v-for=\"thread in chatThreads\"\n :key=\"thread._id\"\n @click=\"selectThread(thread._id)\"\n class=\"w-full p-2 hover:bg-ultramarine-100 cursor-pointer text-sm text-gray-700\"\n :class=\"{ 'bg-ultramarine-200 text-gray-900': thread._id === chatThreadId }\"\n >\n {{ thread.title || 'Untitled Thread' }}\n </li>\n </ul>\n </aside>\n\n <!-- Main Chat Area -->\n <main class=\"flex-1 flex flex-col bg-slate-50\">\n <div class=\"flex-1 overflow-y-auto p-6 space-y-4\" ref=\"messagesContainer\">\n <div v-if=\"chatMessages?.length === 0\">\n <div class=\"flex items-center w-full h-full justify-center py-3 mb-4\">\n <p class=\"mx-4 font-bold text-gray-900\">\n Ask Mongoose Studio to analyze your data or generate a script\n </p>\n </div>\n </div>\n <ul role=\"list\" class=\"space-y-4\" v-else>\n <li v-for=\"message in chatMessages\" :key=\"message._id\">\n <chat-message :message=\"message\" :target-dashboard-id=\"currentThread?.dashboardId\"></chat-message>\n </li>\n </ul>\n </div>\n\n\n <!-- Input Area -->\n <div class=\"border-t p-4\">\n <form @submit.prevent=\"sendMessage\" :disabled=\"sendingMessage\" class=\"flex gap-2 items-end justify-end\">\n <textarea\n v-model=\"newMessage\"\n :placeholder=\"sendingMessage ? 'Sending...' : 'Ask about your data, generate a query, or build a chart…'\"\n class=\"flex-1 border rounded px-4 py-2 resize-none overflow-y-auto shadow-sm\"\n :disabled=\"sendingMessage\"\n rows=\"2\"\n ref=\"messageInput\"\n @input=\"adjustTextareaHeight\"\n @keydown.enter.exact.prevent=\"handleEnter\"\n ></textarea>\n <button class=\"bg-blue-600 text-white px-4 h-[42px] rounded disabled:bg-gray-600\" :disabled=\"sendingMessage\">\n <svg v-if=\"sendingMessage\" style=\"height: 1em\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <g>\n <circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" opacity=\"0.3\" />\n <path d=\"M12 2a10 10 0 0 1 10 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 12 12\" to=\"360 12 12\" dur=\"1s\" repeatCount=\"indefinite\" />\n </path>\n </g>\n </svg>\n <span v-else>Send</span>\n </button>\n </form>\n </div>\n </main>\n</div>\n";
25424
+ module.exports = "<div class=\"flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <button\n type=\"button\"\n class=\"fixed top-[65px] left-0 cursor-pointer bg-gray-100 rounded-r-md z-10 p-1 lg:hidden\"\n @click=\"hideSidebar = false\"\n v-show=\"hideSidebar !== false\"\n aria-label=\"Open chat history\"\n title=\"Open chat history\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 -960 960 960\" class=\"w-5 h-5\" fill=\"#5f6368\"><path d=\"M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z\"/></svg>\n </button>\n <button\n class=\"fixed top-[65px] right-4 z-10 p-2 rounded-md shadow bg-white\"\n :class=\"hasWorkspace ? 'text-gray-700 hover:bg-gray-100' : 'text-gray-300 cursor-not-allowed bg-gray-50'\"\n @click=\"toggleShareThread\"\n :disabled=\"!hasWorkspace || !chatThreadId || sharingThread\"\n aria-label=\"Share thread with workspace\"\n title=\"Share thread with workspace\"\n >\n <svg v-if=\"hasWorkspace\" xmlns=\"http://www.w3.org/2000/svg\" class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7a2.48 2.48 0 0 0 0-1.39l7.02-4.11a2.5 2.5 0 1 0-.87-1.37L8.04 9.94a2.5 2.5 0 1 0 0 4.12l7.12 4.16a2.5 2.5 0 1 0 .84-1.34l-7.05-4.12c-.04-.02-.08-.05-.11-.07a2.48 2.48 0 0 0 0-1.39c.03-.02.07-.04.11-.07l7.11-4.16c.52.47 1.2.76 1.94.76a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0-1.94.94L7.97 8.43a2.5 2.5 0 1 0 0 7.14l9.09 5.3c.52-.47 1.2-.76 1.94-.76a2.5 2.5 0 1 0 0-5z\"/></svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M12 1a5 5 0 00-5 5v3H6a2 2 0 00-2 2v9a2 2 0 002 2h12a2 2 0 002-2v-9a2 2 0 00-2-2h-1V6a5 5 0 00-5-5zm-3 8V6a3 3 0 016 0v3H9zm9 2v9H6v-9h12z\"/></svg>\n </button>\n <!-- Sidebar: Chat Threads -->\n <aside\n class=\"bg-white border-r overflow-y-auto overflow-x-hidden h-full transition-transform duration-300 ease-in-out z-20 w-64 fixed lg:relative\"\n :class=\"hideSidebar === false ? 'translate-x-0' : hideSidebar === true ? '-translate-x-full lg:hidden' : '-translate-x-full lg:translate-x-0'\"\n style=\"z-index: 10000\">\n <div class=\"flex items-center border-b border-gray-100 w-64 overflow-x-hidden\">\n <div class=\"p-1 ml-2 font-bold\">Chat Threads</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none lg:hidden\"\n aria-label=\"Close sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"currentColor\"><path d=\"M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z\"/></svg>\n </button>\n </div>\n <div class=\"p-4 w-64\">\n <async-button\n @click=\"createNewThread\"\n class=\"w-full bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700\"\n >\n Create New Thread\n </async-button>\n </div>\n <div v-if=\"status === 'loaded' && chatThreads.length === 0\" class=\"p-4 text-sm text-gray-700\">\n No threads yet\n </div>\n <ul v-if=\"status === 'loaded'\" class=\"w-full\">\n <li\n v-for=\"thread in chatThreads\"\n :key=\"thread._id\"\n @click=\"selectThread(thread._id)\"\n class=\"w-full p-2 hover:bg-ultramarine-100 cursor-pointer text-sm text-gray-700\"\n :class=\"{ 'bg-ultramarine-200 text-gray-900': thread._id === chatThreadId }\"\n >\n {{ thread.title || 'Untitled Thread' }}\n </li>\n </ul>\n </aside>\n\n <!-- Main Chat Area -->\n <main class=\"flex-1 flex flex-col bg-slate-50\">\n <div class=\"flex-1 overflow-y-auto p-6 space-y-4\" ref=\"messagesContainer\">\n <div v-if=\"chatMessages?.length === 0\">\n <div class=\"flex items-center w-full h-full justify-center py-3 mb-4\">\n <p class=\"mx-4 font-bold text-gray-900\">\n Ask Mongoose Studio to analyze your data or generate a script\n </p>\n </div>\n </div>\n <ul role=\"list\" class=\"space-y-4\" v-else>\n <li v-for=\"message in chatMessages\" :key=\"message._id\">\n <chat-message :message=\"message\" :target-dashboard-id=\"currentThread?.dashboardId\"></chat-message>\n </li>\n </ul>\n </div>\n\n\n <!-- Input Area -->\n <div class=\"border-t p-4\">\n <form @submit.prevent=\"sendMessage\" :disabled=\"sendingMessage\" class=\"flex gap-2 items-end justify-end\">\n <textarea\n v-model=\"newMessage\"\n :placeholder=\"sendingMessage ? 'Sending...' : 'Ask about your data, generate a query, or build a chart…'\"\n class=\"flex-1 border rounded px-4 py-2 resize-none overflow-y-auto shadow-sm\"\n :disabled=\"sendingMessage\"\n rows=\"2\"\n ref=\"messageInput\"\n @input=\"adjustTextareaHeight\"\n @keydown.enter.exact.prevent=\"handleEnter\"\n ></textarea>\n <button class=\"bg-blue-600 text-white px-4 h-[42px] rounded disabled:bg-gray-600\" :disabled=\"sendingMessage\">\n <svg v-if=\"sendingMessage\" style=\"height: 1em\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <g>\n <circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" opacity=\"0.3\" />\n <path d=\"M12 2a10 10 0 0 1 10 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 12 12\" to=\"360 12 12\" dur=\"1s\" repeatCount=\"indefinite\" />\n </path>\n </g>\n </svg>\n <span v-else>Send</span>\n </button>\n </form>\n </div>\n </main>\n</div>\n";
24973
25425
 
24974
25426
  /***/ },
24975
25427
 
@@ -25255,7 +25707,7 @@ module.exports = ".document .document-menu {\n display: flex;\n position: stic
25255
25707
  (module) {
25256
25708
 
25257
25709
  "use strict";
25258
- module.exports = "<div class=\"document px-1 pt-4 pb-16 md:px-0 bg-slate-50 w-full\">\n <div class=\"max-w-7xl mx-auto\">\n <div class=\"flex gap-4 items-center sticky top-0 z-50 bg-white p-4 border-b border-gray-200 shadow-sm\">\n <div class=\"font-bold overflow-hidden text-ellipsis\">{{model}}: {{documentId}}</div>\n <div class=\"flex grow\">\n <button\n @click=\"viewMode = 'fields'\"\n :class=\"viewMode === 'fields'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 border-r-0 rounded-l-lg rounded-r-none\"\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=\"M4 6h16M4 10h16M4 14h16M4 18h16\"></path>\n </svg>\n Fields\n </button>\n <button\n @click=\"viewMode = 'json'\"\n :class=\"viewMode === 'json'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 rounded-r-lg rounded-l-none\"\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=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\"></path>\n </svg>\n JSON\n </button>\n </div>\n\n <div class=\"hidden md:flex items-center gap-3 text-sm text-slate-600\">\n <div class=\"text-right leading-tight\">\n <div class=\"text-xs uppercase tracking-wide text-slate-400 flex items-center gap-2 justify-end\">\n <span>Loaded at</span>\n <span class=\"inline-flex items-center gap-1 text-[10px] font-semibold\" :class=\"autoRefreshEnabled ? 'text-forest-green-600' : 'text-slate-400'\">\n <span class=\"inline-block h-1.5 w-1.5 rounded-full\" :class=\"autoRefreshEnabled ? 'bg-forest-green-500' : 'bg-slate-300'\"></span>\n </span>\n </div>\n <div class=\"font-medium text-slate-700\">{{lastUpdatedLabel}}</div>\n </div>\n </div>\n\n <div class=\"gap-2 hidden md:flex items-center\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true\"\n :disabled=\"!canEdit\"\n :class=\"{'cursor-not-allowed opacity-50': !canEdit}\"\n type=\"button\"\n class=\"rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n &times; Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n @click=\"shouldShowConfirmModal=true;\"\n type=\"button\"\n class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n\n <!-- 3-dot menu -->\n <div class=\"relative\">\n <button\n @click=\"desktopMenuOpen = !desktopMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"desktopMenuOpen\"\n aria-label=\"More options\"\n >\n <svg class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"5\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"12\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"19\" r=\"2\"></circle>\n </svg>\n </button>\n <div\n v-show=\"desktopMenuOpen\"\n @click.away=\"desktopMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n @click=\"refreshDocument({ force: true, source: 'manual' }); desktopMenuOpen = false\"\n :disabled=\"isRefreshing\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', isRefreshing ? 'cursor-not-allowed opacity-50' : 'hover:bg-slate-100']\"\n type=\"button\"\n >\n {{isRefreshing ? 'Refreshing...' : 'Refresh'}}\n </button>\n <button\n @click=\"toggleAutoRefresh(); desktopMenuOpen = false\"\n :disabled=\"isLambda\"\n type=\"button\"\n :class=\"['flex items-center px-4 py-2 text-sm', isLambda ? 'text-gray-400 cursor-not-allowed' : 'text-gray-700 hover:bg-slate-100']\"\n :title=\"isLambda ? 'Auto-refresh only available on Express deployments' : ''\"\n >\n {{autoRefreshEnabled ? 'Disable Auto-Refresh' : 'Enable Auto-Refresh'}}\n </button>\n <button\n @click=\"addField(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"copyDocument(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-blue-100\"\n >\n Copy Document\n </button>\n <button\n @click=\"openScriptDrawer()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-indigo-100\"\n >\n Run Script\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n <div class=\"md:hidden flex items-center\">\n <div class=\"relative\">\n <button\n @click=\"mobileMenuOpen = !mobileMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"mobileMenuOpen\"\n aria-label=\"Open menu\"\n >\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\"\n d=\"M4 6h16M4 12h16M4 18h16\"></path>\n </svg>\n </button>\n <div\n v-show=\"mobileMenuOpen\"\n @click.away=\"mobileMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-52 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true; mobileMenuOpen = false\"\n :disabled=\"!canEdit\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canEdit ? 'cursor-not-allowed opacity-50' : 'hover:bg-ultramarine-100']\"\n type=\"button\"\n >\n Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false; mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-slate-100\"\n >\n Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-green-100']\"\n @click=\"shouldShowConfirmModal=true; mobileMenuOpen = false\"\n type=\"button\"\n >\n Save\n </button>\n <button\n @click=\"refreshDocument({ force: true, source: 'manual' }); mobileMenuOpen = false\"\n :disabled=\"isRefreshing\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', isRefreshing ? 'cursor-not-allowed opacity-50' : 'hover:bg-slate-100']\"\n type=\"button\"\n >\n {{isRefreshing ? 'Refreshing...' : 'Refresh'}}\n </button>\n <button\n @click=\"toggleAutoRefresh(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-slate-100\"\n >\n Auto-Refresh {{autoRefreshEnabled ? 'ON' : 'OFF'}}\n </button>\n <button\n @click=\"addField(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"copyDocument(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-blue-100\"\n >\n Copy Document\n </button>\n <button\n @click=\"openScriptDrawer()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-indigo-100\"\n >\n Run Script\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div v-if=\"status === 'loaded'\">\n <document-details\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :virtualPaths=\"virtualPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"\n :viewMode=\"viewMode\"\n :model=\"model\"\n @add-field=\"addField\"\n @view-mode-change=\"updateViewMode\"></document-details>\n <modal v-if=\"shouldShowConfirmModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowConfirmModal = false;\">&times;</div>\n <confirm-changes @close=\"shouldShowConfirmModal = false;\" @save=\"save\" :value=\"changes\"></confirm-changes>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteModal = false;\">&times;</div>\n <confirm-delete @close=\"shouldShowDeleteModal = false;\" @remove=\"remove\" :value=\"document\"></confirm-delete>\n </template>\n </modal>\n <modal v-if=\"shouldShowCloneModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCloneModal = false;\">&times;</div>\n <clone-document :currentModel=\"model\" :doc=\"document\" :schemaPaths=\"schemaPaths\" @close=\"showClonedDocument\"></clone-document>\n </template>\n </modal>\n </div>\n <execute-script\n :visible=\"scriptDrawerOpen\"\n :model=\"model\"\n :document-id=\"documentId\"\n :editting=\"editting\"\n @close=\"closeScriptDrawer\"\n @refresh=\"handleScriptRefresh\"\n ></execute-script>\n </div>\n</div>\n";
25710
+ module.exports = "<div class=\"document px-1 pt-4 pb-16 md:px-0 bg-slate-50 w-full\">\n <div class=\"max-w-7xl mx-auto\">\n <div class=\"flex gap-4 items-center sticky top-0 z-50 bg-white p-4 border-b border-gray-200 shadow-sm\">\n <div class=\"font-bold overflow-hidden text-ellipsis\">{{model}}: {{documentId}}</div>\n <div class=\"flex grow\">\n <button\n @click=\"viewMode = 'fields'\"\n :class=\"viewMode === 'fields'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 border-r-0 rounded-l-lg rounded-r-none\"\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=\"M4 6h16M4 10h16M4 14h16M4 18h16\"></path>\n </svg>\n Fields\n </button>\n <button\n @click=\"viewMode = 'json'\"\n :class=\"viewMode === 'json'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 rounded-r-lg rounded-l-none\"\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=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\"></path>\n </svg>\n JSON\n </button>\n </div>\n\n <div class=\"hidden md:flex items-center gap-3 text-sm text-slate-600\">\n <div class=\"text-right leading-tight\">\n <div class=\"text-xs uppercase tracking-wide text-slate-400 flex items-center gap-2 justify-end\">\n <span>Loaded at</span>\n <span class=\"inline-flex items-center gap-1 text-[10px] font-semibold\" :class=\"autoRefreshEnabled ? 'text-forest-green-600' : 'text-slate-400'\">\n <span class=\"inline-block h-1.5 w-1.5 rounded-full\" :class=\"autoRefreshEnabled ? 'bg-forest-green-500' : 'bg-slate-300'\"></span>\n </span>\n </div>\n <div class=\"font-medium text-slate-700\">{{lastUpdatedLabel}}</div>\n </div>\n </div>\n\n <div class=\"gap-2 hidden md:flex items-center\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true\"\n :disabled=\"!canEdit\"\n :class=\"{'cursor-not-allowed opacity-50': !canEdit}\"\n type=\"button\"\n class=\"rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n &times; Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n @click=\"shouldShowConfirmModal=true;\"\n type=\"button\"\n class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n\n <!-- 3-dot menu -->\n <div class=\"relative\">\n <button\n @click=\"desktopMenuOpen = !desktopMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"desktopMenuOpen\"\n aria-label=\"More options\"\n >\n <svg class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"5\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"12\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"19\" r=\"2\"></circle>\n </svg>\n </button>\n <div\n v-show=\"desktopMenuOpen\"\n @click.away=\"desktopMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n @click=\"refreshDocument({ force: true, source: 'manual' }); desktopMenuOpen = false\"\n :disabled=\"isRefreshing\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', isRefreshing ? 'cursor-not-allowed opacity-50' : 'hover:bg-slate-100']\"\n type=\"button\"\n >\n {{isRefreshing ? 'Refreshing...' : 'Refresh'}}\n </button>\n <button\n @click=\"toggleAutoRefresh(); desktopMenuOpen = false\"\n :disabled=\"isLambda\"\n type=\"button\"\n :class=\"['flex items-center px-4 py-2 text-sm', isLambda ? 'text-gray-400 cursor-not-allowed' : 'text-gray-700 hover:bg-slate-100']\"\n :title=\"isLambda ? 'Auto-refresh only available on Express deployments' : ''\"\n >\n {{autoRefreshEnabled ? 'Disable Auto-Refresh' : 'Enable Auto-Refresh'}}\n </button>\n <button\n @click=\"addField(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"copyDocument(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-blue-100\"\n >\n Copy Document\n </button>\n <button\n @click=\"openScriptDrawer()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-indigo-100\"\n >\n Run Script\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n <div class=\"md:hidden flex items-center\">\n <div class=\"relative\">\n <button\n @click=\"mobileMenuOpen = !mobileMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"mobileMenuOpen\"\n aria-label=\"Open menu\"\n >\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\"\n d=\"M4 6h16M4 12h16M4 18h16\"></path>\n </svg>\n </button>\n <div\n v-show=\"mobileMenuOpen\"\n @click.away=\"mobileMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-52 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true; mobileMenuOpen = false\"\n :disabled=\"!canEdit\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canEdit ? 'cursor-not-allowed opacity-50' : 'hover:bg-ultramarine-100']\"\n type=\"button\"\n >\n Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false; mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-slate-100\"\n >\n Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-green-100']\"\n @click=\"shouldShowConfirmModal=true; mobileMenuOpen = false\"\n type=\"button\"\n >\n Save\n </button>\n <button\n @click=\"refreshDocument({ force: true, source: 'manual' }); mobileMenuOpen = false\"\n :disabled=\"isRefreshing\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', isRefreshing ? 'cursor-not-allowed opacity-50' : 'hover:bg-slate-100']\"\n type=\"button\"\n >\n {{isRefreshing ? 'Refreshing...' : 'Refresh'}}\n </button>\n <button\n @click=\"toggleAutoRefresh(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-slate-100\"\n >\n Auto-Refresh {{autoRefreshEnabled ? 'ON' : 'OFF'}}\n </button>\n <button\n @click=\"addField(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"copyDocument(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-blue-100\"\n >\n Copy Document\n </button>\n <button\n @click=\"openScriptDrawer()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-indigo-100\"\n >\n Run Script\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div v-if=\"status === 'loaded'\">\n <document-details\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :virtualPaths=\"virtualPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"\n :viewMode=\"viewMode\"\n :model=\"model\"\n @add-field=\"addField\"\n @view-mode-change=\"updateViewMode\"></document-details>\n <modal v-if=\"shouldShowConfirmModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowConfirmModal = false;\">&times;</div>\n <confirm-changes @close=\"shouldShowConfirmModal = false;\" @save=\"save\" :value=\"changes\"></confirm-changes>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteModal = false;\">&times;</div>\n <confirm-delete @close=\"shouldShowDeleteModal = false;\" @remove=\"remove\" :value=\"document\"></confirm-delete>\n </template>\n </modal>\n <modal v-if=\"shouldShowCloneModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCloneModal = false;\">&times;</div>\n <clone-document :currentModel=\"model\" :doc=\"document\" :schemaPaths=\"schemaPaths\" @close=\"showClonedDocument\"></clone-document>\n </template>\n </modal>\n </div>\n <execute-script\n :visible=\"scriptDrawerOpen\"\n :model=\"model\"\n :document-id=\"documentId\"\n :editting=\"editting\"\n @close=\"closeScriptDrawer\"\n @refresh=\"handleScriptRefresh\"\n ></execute-script>\n <div\n v-if=\"keyboardShortcuts.length > 0\"\n class=\"fixed bottom-4 left-4 z-40 group\"\n >\n <button\n type=\"button\"\n aria-label=\"Keyboard shortcuts\"\n title=\"Keyboard shortcuts\"\n class=\"rounded-full border border-slate-300 bg-white p-2 text-slate-700 shadow-sm transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-ultramarine-500\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"2.5\" y=\"6.5\" width=\"19\" height=\"11\" rx=\"2\" stroke-width=\"1.5\"></rect>\n <path d=\"M6 10.5h1.5M9 10.5h1.5M12 10.5h1.5M15 10.5h1.5M18 10.5h.01M6 13.5h1.5M9 13.5h6M16.5 13.5h1.5\" stroke-width=\"1.5\" stroke-linecap=\"round\"></path>\n </svg>\n </button>\n <div class=\"pointer-events-none absolute left-0 bottom-full mb-2 min-w-[220px] translate-y-1 rounded-lg bg-slate-900 px-3 py-2.5 text-slate-200 opacity-0 shadow-xl transition-all duration-150 group-hover:pointer-events-auto group-hover:translate-y-0 group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:translate-y-0 group-focus-within:opacity-100\">\n <div\n v-for=\"shortcut in keyboardShortcuts\"\n :key=\"shortcut.command + shortcut.description\"\n class=\"flex items-center justify-between gap-3 text-xs\"\n >\n <span class=\"font-mono font-semibold text-slate-50\">{{shortcut.command}}</span>\n <span class=\"text-slate-300\">{{shortcut.description}}</span>\n </div>\n </div>\n </div>\n </div>\n</div>\n";
25259
25711
 
25260
25712
  /***/ },
25261
25713
 
@@ -25563,7 +26015,7 @@ module.exports = ".models {\n position: relative;\n display: flex;\n flex-dir
25563
26015
  (module) {
25564
26016
 
25565
26017
  "use strict";
25566
- module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <div class=\"fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10\" @click=\"hideSidebar = false\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"#5f6368\"><path d=\"M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z\"/></svg>\n </div>\n <aside class=\"bg-white border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-48 fixed lg:relative shrink-0\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-48' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-48 overflow-x-hidden\">\n <div class=\"p-1 ml-2 font-bold\">Models</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none\"\n aria-label=\"Close sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"currentColor\"><path d=\"M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z\"/></svg>\n </button>\n </div>\n <nav class=\"flex flex-1 flex-col\">\n <ul role=\"list\" class=\"flex flex-1 flex-col gap-y-7 p-1\">\n <li>\n <ul role=\"list\">\n <li v-for=\"model in models\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"block truncate rounded-md py-2 pr-2 pl-2 text-sm text-gray-700\"\n :class=\"model === currentModel ? 'bg-ultramarine-100 font-bold text-gray-900' : 'hover:bg-ultramarine-50'\">\n {{model}}\n </router-link>\n </li>\n </ul>\n </li>\n </ul>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100\">\n No models found\n </div>\n </nav>\n </aside>\n <div class=\"documents bg-slate-50\" ref=\"documentsList\">\n <div class=\"relative h-[42px]\" style=\"z-index: 1000\">\n <div class=\"documents-menu bg-slate-50\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <document-search\n ref=\"documentSearch\"\n :value=\"searchText\"\n :schema-paths=\"schemaPaths\"\n @search=\"search\"\n >\n </document-search>\n <div>\n <span v-if=\"numDocuments == null\">Loading ...</span>\n <span v-else-if=\"typeof numDocuments === 'number'\">{{documents.length}}/{{numDocuments === 1 ? numDocuments + ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{ 'bg-gray-500 ring-inset ring-2 ring-gray-300 hover:bg-gray-600': selectMultiple, 'bg-ultramarine-600 hover:bg-ultramarine-500' : !selectMultiple }\"\n class=\"rounded px-2 py-2 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n >\n {{ selectMultiple ? 'Cancel' : 'Select' }}\n </button>\n <button\n v-show=\"selectMultiple\"\n @click=\"shouldShowUpdateMultipleModal=true;\"\n type=\"button\"\n class=\"rounded bg-green-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\"\n >\n Update\n </button>\n <button\n @click=\"shouldShowDeleteMultipleModal=true;\"\n type=\"button\"\n v-show=\"selectMultiple\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500\"\n >\n Delete\n </button>\n <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-white px-2 py-2 text-sm font-semibold text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\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-white 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-gray-700 hover:bg-gray-100\"\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-gray-700 hover:bg-gray-100\"\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-gray-700 hover:bg-gray-100\"\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-gray-700 hover:bg-gray-100\"\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-gray-700 hover:bg-gray-100\"\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-gray-700 hover:bg-gray-100\"\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-gray-50 focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/table.svg\">\n </button>\n <button\n @click=\"setOutputType('json')\"\n type=\"button\"\n class=\"relative -ml-px inline-flex items-center px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n <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-gray-100' : 'text-gray-400 hover:bg-gray-50',\n outputType === 'map' ? 'bg-gray-200' : (geoJsonFields.length > 0 ? 'bg-white' : '')\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>\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=\"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-white 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-white'\n ]\"\n >\n <button\n type=\"button\"\n class=\"absolute top-2 right-2 z-10 inline-flex items-center rounded bg-ultramarine-600 px-2 py-1 text-xs font-semibold text-white shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n @click.stop=\"openDocument(document)\"\n >\n Open this Document\n </button>\n <list-json :value=\"filterDocument(document)\" :references=\"referenceMap\">\n </list-json>\n </div>\n </div>\n <div v-else-if=\"outputType === 'map'\" class=\"flex flex-col h-full\">\n <div class=\"p-2 bg-white border-b flex items-center gap-2\">\n <label class=\"text-sm font-medium text-gray-700\">GeoJSON Field:</label>\n <select\n :value=\"selectedGeoField\"\n @change=\"setSelectedGeoField($event.target.value)\"\n class=\"rounded-md border border-gray-300 py-1 px-2 text-sm focus:border-ultramarine-500 focus:ring-ultramarine-500\"\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-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n :class=\"loadedAllDocs ? 'bg-gray-400 cursor-not-allowed' : 'bg-ultramarine-600 hover:bg-ultramarine-500'\"\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\">&times;</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\">&times;</div>\n <div class=\"text-xl font-bold mb-2\">Indexes</div>\n <div v-for=\"index in mongoDBIndexes\" class=\"w-full flex items-center\">\n <div class=\"grow shrink text-left flex justify-between items-center\" v-if=\"index.name != '_id_'\">\n <div>\n <div class=\"font-bold flex items-center gap-2\">\n <div>{{ index.name }}</div>\n <div v-if=\"isTTLIndex(index)\" class=\"rounded-full bg-ultramarine-100 px-2 py-0.5 text-xs font-semibold text-ultramarine-700\">\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\">&times;</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-gray-700\">Documents</div>\n <div class=\"text-gray-900\">{{ formatNumber(collectionInfo.documentCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Indexes</div>\n <div class=\"text-gray-900\">{{ formatNumber(collectionInfo.indexCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Total Index Size</div>\n <div class=\"text-gray-900\">{{ formatCollectionSize(collectionInfo.totalIndexSize) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Total Storage Size</div>\n <div class=\"text-gray-900\">{{ 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-gray-700\">Collation</div>\n <div class=\"text-gray-900\">{{ collectionInfo.hasCollation ? 'Yes' : 'No' }}</div>\n </div>\n <div v-if=\"collectionInfo.hasCollation\" class=\"rounded bg-gray-100 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-gray-700\">Capped</div>\n <div class=\"text-gray-900\">{{ 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];\">&times;</div>\n <div v-for=\"(path, index) in schemaPaths\" :key=\"index\" class=\"w-5 flex items-center\">\n <input class=\"mt-0 h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-600 accent-sky-600\" type=\"checkbox\" :id=\"'path.path'+index\" @change=\"addOrRemove(path)\" :value=\"path.path\" :checked=\"isSelected(path.path)\" />\n <div class=\"ml-2 text-gray-700 grow shrink text-left\">\n <label :for=\"'path.path' + index\">{{path.path}}</label>\n </div>\n </div>\n <div class=\"mt-4 flex gap-2\">\n <button type=\"button\" @click=\"filterDocuments()\" class=\"rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">Filter Selection</button>\n <button type=\"button\" @click=\"selectAll()\" class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">Select All</button>\n <button type=\"button\" @click=\"deselectAll()\" class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">Deselect All</button>\n <button type=\"button\" @click=\"resetDocuments()\" class=\"rounded-md bg-gray-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\" >Cancel</button>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">&times;</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;\">&times;</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;\">&times;</div>\n <h2>Are you sure you want to delete {{selectedDocuments.length}} documents?</h2>\n <div>\n <list-json :value=\"selectedDocuments\"></list-json>\n </div>\n <div class=\"flex gap-4\">\n <async-button @click=\"deleteDocuments\" class=\"rounded bg-red-500 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n Confirm\n </async-button>\n <button @click=\"shouldShowDeleteMultipleModal = false;\" class=\"rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n</div>\n";
26018
+ module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <div class=\"fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10\" @click=\"hideSidebar = false\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"#5f6368\"><path d=\"M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z\"/></svg>\n </div>\n <aside class=\"bg-white border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-48 fixed lg:relative shrink-0\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-48' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-48 overflow-x-hidden\">\n <div class=\"p-1 ml-2 font-bold\">Models</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none\"\n aria-label=\"Close sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"currentColor\"><path d=\"M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z\"/></svg>\n </button>\n </div>\n <nav class=\"flex flex-1 flex-col\">\n <ul role=\"list\" class=\"flex flex-1 flex-col gap-y-7 p-1\">\n <li>\n <ul role=\"list\">\n <li v-for=\"model in models\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"flex items-center gap-2 rounded-md py-2 pr-2 pl-2 text-sm text-gray-700\"\n :class=\"model === currentModel ? 'bg-ultramarine-100 font-bold text-gray-900' : 'hover:bg-ultramarine-50'\">\n <span class=\"truncate\">{{model}}</span>\n <span\n v-if=\"modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel\"\n class=\"ml-auto text-xs text-gray-500 bg-gray-100 rounded-md px-1 py-[2px]\"\n >\n {{formatCompactCount(modelDocumentCounts[model])}}\n </span>\n </router-link>\n </li>\n </ul>\n </li>\n </ul>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100\">\n No models found\n </div>\n </nav>\n </aside>\n <div class=\"documents bg-slate-50\" ref=\"documentsList\">\n <div class=\"relative h-[42px]\" style=\"z-index: 1000\">\n <div class=\"documents-menu bg-slate-50\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <document-search\n ref=\"documentSearch\"\n :value=\"searchText\"\n :schema-paths=\"schemaPaths\"\n @search=\"search\"\n >\n </document-search>\n <div>\n <span v-if=\"numDocuments == null\">Loading ...</span>\n <span v-else-if=\"typeof numDocuments === 'number'\">{{documents.length}}/{{numDocuments === 1 ? numDocuments + ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{ 'bg-gray-500 ring-inset ring-2 ring-gray-300 hover:bg-gray-600': selectMultiple, 'bg-ultramarine-600 hover:bg-ultramarine-500' : !selectMultiple }\"\n class=\"rounded px-2 py-2 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n >\n {{ selectMultiple ? 'Cancel' : 'Select' }}\n </button>\n <button\n v-show=\"selectMultiple\"\n @click=\"shouldShowUpdateMultipleModal=true;\"\n type=\"button\"\n class=\"rounded bg-green-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\"\n >\n Update\n </button>\n <button\n @click=\"shouldShowDeleteMultipleModal=true;\"\n type=\"button\"\n v-show=\"selectMultiple\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500\"\n >\n Delete\n </button>\n <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-white px-2 py-2 text-sm font-semibold text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\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-white 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-gray-700 hover:bg-gray-100\"\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-gray-700 hover:bg-gray-100\"\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-gray-700 hover:bg-gray-100\"\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-gray-700 hover:bg-gray-100\"\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-gray-700 hover:bg-gray-100\"\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-gray-700 hover:bg-gray-100\"\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-gray-50 focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/table.svg\">\n </button>\n <button\n @click=\"setOutputType('json')\"\n type=\"button\"\n class=\"relative -ml-px inline-flex items-center px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n <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-gray-100' : 'text-gray-400 hover:bg-gray-50',\n outputType === 'map' ? 'bg-gray-200' : (geoJsonFields.length > 0 ? 'bg-white' : '')\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>\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=\"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-white 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-white'\n ]\"\n >\n <button\n type=\"button\"\n class=\"absolute top-2 right-2 z-10 inline-flex items-center rounded bg-ultramarine-600 px-2 py-1 text-xs font-semibold text-white shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n @click.stop=\"openDocument(document)\"\n >\n Open this Document\n </button>\n <list-json :value=\"filterDocument(document)\" :references=\"referenceMap\">\n </list-json>\n </div>\n </div>\n <div v-else-if=\"outputType === 'map'\" class=\"flex flex-col h-full\">\n <div class=\"p-2 bg-white border-b flex items-center gap-2\">\n <label class=\"text-sm font-medium text-gray-700\">GeoJSON Field:</label>\n <select\n :value=\"selectedGeoField\"\n @change=\"setSelectedGeoField($event.target.value)\"\n class=\"rounded-md border border-gray-300 py-1 px-2 text-sm focus:border-ultramarine-500 focus:ring-ultramarine-500\"\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-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n :class=\"loadedAllDocs ? 'bg-gray-400 cursor-not-allowed' : 'bg-ultramarine-600 hover:bg-ultramarine-500'\"\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\">&times;</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\">&times;</div>\n <div class=\"text-xl font-bold mb-2\">Indexes</div>\n <div v-for=\"index in mongoDBIndexes\" class=\"w-full flex items-center\">\n <div class=\"grow shrink text-left flex justify-between items-center\" v-if=\"index.name != '_id_'\">\n <div>\n <div class=\"font-bold flex items-center gap-2\">\n <div>{{ index.name }}</div>\n <div v-if=\"isTTLIndex(index)\" class=\"rounded-full bg-ultramarine-100 px-2 py-0.5 text-xs font-semibold text-ultramarine-700\">\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\">&times;</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-gray-700\">Documents</div>\n <div class=\"text-gray-900\">{{ formatNumber(collectionInfo.documentCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Indexes</div>\n <div class=\"text-gray-900\">{{ formatNumber(collectionInfo.indexCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Total Index Size</div>\n <div class=\"text-gray-900\">{{ formatCollectionSize(collectionInfo.totalIndexSize) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Total Storage Size</div>\n <div class=\"text-gray-900\">{{ 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-gray-700\">Collation</div>\n <div class=\"text-gray-900\">{{ collectionInfo.hasCollation ? 'Yes' : 'No' }}</div>\n </div>\n <div v-if=\"collectionInfo.hasCollation\" class=\"rounded bg-gray-100 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-gray-700\">Capped</div>\n <div class=\"text-gray-900\">{{ 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];\">&times;</div>\n <div v-for=\"(path, index) in schemaPaths\" :key=\"index\" class=\"w-5 flex items-center\">\n <input class=\"mt-0 h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-600 accent-sky-600\" type=\"checkbox\" :id=\"'path.path'+index\" @change=\"addOrRemove(path)\" :value=\"path.path\" :checked=\"isSelected(path.path)\" />\n <div class=\"ml-2 text-gray-700 grow shrink text-left\">\n <label :for=\"'path.path' + index\">{{path.path}}</label>\n </div>\n </div>\n <div class=\"mt-4 flex gap-2\">\n <button type=\"button\" @click=\"filterDocuments()\" class=\"rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">Filter Selection</button>\n <button type=\"button\" @click=\"selectAll()\" class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">Select All</button>\n <button type=\"button\" @click=\"deselectAll()\" class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">Deselect All</button>\n <button type=\"button\" @click=\"resetDocuments()\" class=\"rounded-md bg-gray-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\" >Cancel</button>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">&times;</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;\">&times;</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;\">&times;</div>\n <h2>Are you sure you want to delete {{selectedDocuments.length}} documents?</h2>\n <div>\n <list-json :value=\"selectedDocuments\"></list-json>\n </div>\n <div class=\"flex gap-4\">\n <async-button @click=\"deleteDocuments\" class=\"rounded bg-red-500 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n Confirm\n </async-button>\n <button @click=\"shouldShowDeleteMultipleModal = false;\" class=\"rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n</div>\n";
25567
26019
 
25568
26020
  /***/ },
25569
26021
 
@@ -25600,6 +26052,28 @@ module.exports = "<div class=\"w-full h-full flex items-center justify-center\">
25600
26052
 
25601
26053
  /***/ },
25602
26054
 
26055
+ /***/ "./frontend/src/task-by-name/task-by-name.html"
26056
+ /*!*****************************************************!*\
26057
+ !*** ./frontend/src/task-by-name/task-by-name.html ***!
26058
+ \*****************************************************/
26059
+ (module) {
26060
+
26061
+ "use strict";
26062
+ 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 <task-details\n v-else-if=\"taskGroup\"\n :task-group=\"taskGroup\"\n :back-to=\"{ name: 'tasks' }\"\n @task-created=\"onTaskCreated\"\n @task-cancelled=\"onTaskCancelled\"\n ></task-details>\n</div>\n";
26063
+
26064
+ /***/ },
26065
+
26066
+ /***/ "./frontend/src/task-single/task-single.html"
26067
+ /*!***************************************************!*\
26068
+ !*** ./frontend/src/task-single/task-single.html ***!
26069
+ \***************************************************/
26070
+ (module) {
26071
+
26072
+ "use strict";
26073
+ 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-gray-500 hover:text-gray-700 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-gray-700 mb-1\">{{ task.name }}</h1>\n <p class=\"text-gray-500 mb-6\">Task details</p>\n\n <div class=\"bg-white 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-gray-900\">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-gray-700 mb-1\">Scheduled At</label>\n <div class=\"text-sm text-gray-900\">{{ formatDate(task.scheduledAt) }}</div>\n </div>\n <div v-if=\"task?.startedAt\">\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Started At</label>\n <div class=\"text-sm text-gray-900\">{{ formatDate(task.startedAt) }}</div>\n </div>\n <div v-if=\"task?.completedAt\">\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Completed At</label>\n <div class=\"text-sm text-gray-900\">{{ 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-gray-700 mb-2\">Params</label>\n <div class=\"bg-gray-50 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-gray-700 mb-2\">Result</label>\n <div class=\"bg-gray-50 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-gray-700 mb-2\">Error</label>\n <div class=\"bg-gray-50 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-gray-200\">\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\">&times;</div>\n <div class=\"p-6\">\n <h3 class=\"text-lg font-medium text-gray-900 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-gray-500 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-gray-700 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-gray-300 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:bg-blue-700 font-medium\">Reschedule</button>\n <button @click=\"showRescheduleModal = false\" class=\"flex-1 bg-gray-300 text-gray-700 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\">&times;</div>\n <div class=\"p-6\">\n <h3 class=\"text-lg font-medium text-gray-900 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-gray-500 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-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 font-medium\">Run Now</button>\n <button @click=\"showRunModal = false\" class=\"flex-1 bg-gray-300 text-gray-700 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\">&times;</div>\n <div class=\"p-6\">\n <h3 class=\"text-lg font-medium text-gray-900 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-gray-500 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-red-600 text-white px-4 py-2 rounded-md hover:bg-red-700 font-medium\">Cancel Task</button>\n <button @click=\"showCancelModal = false\" class=\"flex-1 bg-gray-300 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-400 font-medium\">Keep Task</button>\n </div>\n </div>\n </template>\n </modal>\n</div>\n";
26074
+
26075
+ /***/ },
26076
+
25603
26077
  /***/ "./frontend/src/tasks/task-details/task-details.html"
25604
26078
  /*!***********************************************************!*\
25605
26079
  !*** ./frontend/src/tasks/task-details/task-details.html ***!
@@ -25607,7 +26081,7 @@ module.exports = "<div class=\"w-full h-full flex items-center justify-center\">
25607
26081
  (module) {
25608
26082
 
25609
26083
  "use strict";
25610
- module.exports = "<div class=\"p-4 space-y-6\">\n <div class=\"flex items-center justify-between\">\n <div>\n <button @click=\"$emit('back')\" class=\"text-gray-500 hover:text-gray-700 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 Back to Task Groups\n </button>\n <h1 class=\"text-2xl font-bold text-gray-700\">{{ taskGroup.name }}</h1>\n <p class=\"text-gray-500\">Total: {{ taskGroup.totalCount }} tasks</p>\n </div>\n\n </div>\n\n <!-- Status Summary -->\n <div 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-gray-50 border border-gray-200 rounded-md p-3 text-center hover:bg-gray-100 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-gray-700\">{{ taskGroup.statusCounts.cancelled || 0 }}</div>\n </button>\n </div>\n\n <!-- Task List -->\n <div class=\"bg-white rounded-lg shadow\">\n <div class=\"px-6 py-4 border-b border-gray-200 flex items-center justify-between\">\n <h2 class=\"text-lg font-semibold text-gray-700\">\n Individual Tasks\n <span v-if=\"currentFilter\" class=\"text-sm font-normal text-gray-500 ml-2\">\n (Filtered by {{ currentFilter }})\n </span>\n </h2>\n <button \n v-if=\"currentFilter\"\n @click=\"clearFilter\"\n class=\"text-sm text-ultramarine-600 hover:text-ultramarine-700 font-medium\"\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-gray-900\">Task 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-4 mb-4\">\n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Scheduled At</label>\n <div class=\"text-sm text-gray-900\">{{ formatDate(task.scheduledAt) }}</div>\n </div>\n <div v-if=\"task.startedAt\">\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Started At</label>\n <div class=\"text-sm text-gray-900\">{{ formatDate(task.startedAt) }}</div>\n </div>\n <div v-if=\"task.completedAt\">\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Completed At</label>\n <div class=\"text-sm text-gray-900\">{{ formatDate(task.completedAt) }}</div>\n </div>\n <div v-if=\"task.error\">\n <label class=\"block text-sm font-medium text-gray-700 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-gray-700 mb-2\">Parameters</label>\n <div class=\"bg-gray-50 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\">&times;</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-gray-900\">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-gray-500 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-gray-700 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-gray-300 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-gray-700 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\">&times;</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-gray-900\">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-gray-500 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-gray-700 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\">&times;</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-gray-900\">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-gray-500 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-gray-700 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";
26084
+ module.exports = "<div class=\"p-4 space-y-6\">\n <div class=\"flex items-center justify-between\">\n <div>\n <button @click=\"goBack\" class=\"text-gray-500 hover:text-gray-700 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-gray-700\">{{ taskGroup.name }}</h1>\n <p class=\"text-gray-500\">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-gray-700\">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-ultramarine-600 text-white border-ultramarine-600' : 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'\"\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-ultramarine-600 text-white border-ultramarine-600' : 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'\"\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-gray-50 border border-gray-200 rounded-md p-3 text-center hover:bg-gray-100 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-gray-700\">{{ 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-white border border-gray-200 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-gray-500 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-gray-100 text-gray-500 hover:bg-gray-200'\"\n @click=\"filterByStatus(status)\"\n >\n {{ statusLabel(status) }}\n </button>\n </div>\n </div>\n </div>\n\n <!-- Task List -->\n <div class=\"bg-white rounded-lg shadow\">\n <div class=\"px-6 py-6 border-b border-gray-200 flex items-center justify-between bg-gray-50\">\n <h2 class=\"text-xl font-bold text-gray-900\">\n Individual Tasks\n <span v-if=\"currentFilter\" class=\"ml-3 text-base font-semibold text-ultramarine-700\">\n (Filtered by {{ currentFilter }})\n </span>\n </h2>\n <button \n v-if=\"currentFilter\"\n @click=\"clearFilter\"\n class=\"text-sm font-semibold text-ultramarine-600 hover:text-ultramarine-700\"\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-gray-900\">Task ID: {{ task.id }}</span>\n <router-link\n v-if=\"backTo\"\n :to=\"taskDetailRoute(task)\"\n class=\"text-sm text-ultramarine-600 hover:text-ultramarine-700 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-gray-700 mb-1\">Scheduled At</label>\n <div class=\"text-sm text-gray-900\">{{ formatDate(task.scheduledAt) }}</div>\n </div>\n <div v-if=\"task.startedAt\">\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Started At</label>\n <div class=\"text-sm text-gray-900\">{{ formatDate(task.startedAt) }}</div>\n </div>\n <div v-if=\"task.completedAt\">\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Completed At</label>\n <div class=\"text-sm text-gray-900\">{{ formatDate(task.completedAt) }}</div>\n </div>\n <div v-if=\"task.error\">\n <label class=\"block text-sm font-medium text-gray-700 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-gray-700 mb-2\">Parameters</label>\n <div class=\"bg-gray-50 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\">&times;</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-gray-900\">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-gray-500 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-gray-700 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-gray-300 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-gray-700 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\">&times;</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-gray-900\">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-gray-500 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-gray-700 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\">&times;</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-gray-900\">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-gray-500 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-gray-700 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";
25611
26085
 
25612
26086
  /***/ },
25613
26087
 
@@ -25629,7 +26103,7 @@ module.exports = "";
25629
26103
  (module) {
25630
26104
 
25631
26105
  "use strict";
25632
- module.exports = "<div class=\"p-4 space-y-6\">\n <!-- Task Details View -->\n <task-details \n v-if=\"showTaskDetails && selectedTaskGroup\"\n :task-group=\"selectedTaskGroup\"\n :current-filter=\"taskDetailsFilter\"\n @back=\"hideTaskDetails\"\n @task-created=\"onTaskCreated\"\n @task-cancelled=\"onTaskCancelled\"\n @update:current-filter=\"taskDetailsFilter = $event\"\n ></task-details>\n\n <!-- Main Tasks View -->\n <div v-else>\n <h1 class=\"text-2xl font-bold text-gray-700 mb-4\">Task Overview</h1>\n <div v-if=\"status == 'init'\">\n <img src=\"images/loader.gif\" />\n </div>\n <!-- Task List -->\n <div class=\"bg-white p-4 rounded-lg shadow\" v-if=\"status == 'loaded'\">\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\">\n <option v-for=\"option in dateFilters\" :key=\"option.value\" :value=\"option.value\">\n {{ option.label }}\n </option>\n </select>\n </div>\n <div class=\"mb-4\">\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Filter by Status:</label>\n <select v-model=\"selectedStatus\" @change=\"getTasks\" class=\"border-gray-300 rounded-md shadow-sm w-full p-2\">\n <option v-for=\"option in statusFilters\" :key=\"option.value\" :value=\"option.value\">\n {{ option.label }}\n </option>\n </select>\n </div>\n <div class=\"mb-4\">\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Search by Task Name:</label>\n <input \n v-model=\"searchQuery\" \n type=\"text\" \n @input=\"onSearchInput\"\n class=\"border-gray-300 rounded-md shadow-sm w-full p-2\"\n placeholder=\"Enter task name to search...\"\n >\n </div>\n <div class=\"mb-4\">\n <button\n @click=\"resetFilters\"\n class=\"w-full bg-gray-200 text-gray-700 hover:bg-gray-300 font-medium py-2 px-4 rounded-md transition\"\n >\n Reset Filters\n </button>\n </div>\n <div class=\"mb-6\">\n <button\n @click=\"openCreateTaskModal\"\n class=\"w-full bg-ultramarine-600 text-white hover:bg-ultramarine-700 font-medium py-2 px-4 rounded-md transition\"\n >\n Create New Task\n </button>\n </div>\n <!-- Summary Section -->\n <div class=\"grid grid-cols-2 sm:grid-cols-4 gap-4\">\n <button \n @click=\"setStatusFilter('pending')\"\n :class=\"getStatusColor('pending') + ' p-4 rounded-lg shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200 cursor-pointer border-2 border-yellow-200 hover:border-yellow-300'\"\n >\n <div class=\"text-sm\">Scheduled</div>\n <div class=\"text-2xl font-bold\">{{pendingCount}}</div>\n </button>\n <button \n @click=\"setStatusFilter('succeeded')\"\n :class=\"getStatusColor('succeeded') + ' p-4 rounded-lg shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200 cursor-pointer border-2 border-green-200 hover:border-green-300'\"\n >\n <div class=\"text-sm\">Completed</div>\n <div class=\"text-2xl font-bold\">{{succeededCount}}</div>\n </button>\n <button \n @click=\"setStatusFilter('failed')\"\n :class=\"getStatusColor('failed') + ' p-4 rounded-lg shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200 cursor-pointer border-2 border-red-200 hover:border-red-300'\"\n >\n <div class=\"text-sm\">Failed</div>\n <div class=\"text-2xl font-bold\">{{failedCount}}</div>\n </button>\n <button \n @click=\"setStatusFilter('cancelled')\"\n :class=\"getStatusColor('cancelled') + ' p-4 rounded-lg shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200 cursor-pointer border-2 border-gray-200 hover:border-gray-300'\"\n >\n <div class=\"text-sm\">Cancelled</div>\n <div class=\"text-2xl font-bold\">{{cancelledCount}}</div>\n </button>\n </div>\n \n <!-- Grouped Task List -->\n <div class=\"mt-6\">\n <h2 class=\"text-lg font-semibold text-gray-700 mb-4\">Tasks by Name</h2>\n <ul class=\"divide-y divide-gray-200\">\n <li v-for=\"group in tasksByName\" :key=\"group.name\" class=\"p-4 group hover:border hover:rounded-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200\">\n <div class=\"flex items-center justify-between mb-3 \">\n <div class=\"flex-1 cursor-pointer\" @click=\"openTaskGroupDetails(group)\">\n <div class=\"flex items-center gap-2\">\n <div class=\"font-medium text-lg group-hover:text-ultramarine-600 transition-colors\">{{ group.name }}</div>\n <svg class=\"w-4 h-4 text-gray-400 group-hover:text-ultramarine-600 transition-colors\" 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 </div>\n <div class=\"text-sm text-gray-500 group-hover:text-gray-700 transition-colors\">Total: {{ group.totalCount }} tasks</div>\n <div class=\"text-xs text-ultramarine-600 opacity-0 group-hover:opacity-100 transition-opacity mt-1\">\n Click to view details\n </div>\n </div>\n <div class=\"text-sm text-gray-500\">\n Last run: {{ group.lastRun ? new Date(group.lastRun).toLocaleString() : 'Never' }}\n </div>\n </div>\n \n <!-- Status Counts -->\n <div class=\"grid grid-cols-2 sm:grid-cols-4 gap-2\">\n <button \n @click.stop=\"openTaskGroupDetailsWithFilter(group, 'pending')\"\n class=\"bg-yellow-50 border border-yellow-200 rounded-md p-2 text-center shadow-sm hover:shadow-md transform hover:-translate-y-1 transition-all duration-200 cursor-pointer hover:border-yellow-300\"\n >\n <div class=\"text-xs text-yellow-600 font-medium\">Pending</div>\n <div class=\"text-lg font-bold text-yellow-700\">{{ group.statusCounts.pending || 0 }}</div>\n </button>\n <button \n @click.stop=\"openTaskGroupDetailsWithFilter(group, 'succeeded')\"\n class=\"bg-green-50 border border-green-200 rounded-md p-2 text-center shadow-sm hover:shadow-md transform hover:-translate-y-1 transition-all duration-200 cursor-pointer hover:border-green-300\"\n >\n <div class=\"text-xs text-green-600 font-medium\">Succeeded</div>\n <div class=\"text-lg font-bold text-green-700\">{{ group.statusCounts.succeeded || 0 }}</div>\n </button>\n <button \n @click.stop=\"openTaskGroupDetailsWithFilter(group, 'failed')\"\n class=\"bg-red-50 border border-red-200 rounded-md p-2 text-center shadow-sm hover:shadow-md transform hover:-translate-y-1 transition-all duration-200 cursor-pointer hover:border-red-300\"\n >\n <div class=\"text-xs text-red-600 font-medium\">Failed</div>\n <div class=\"text-lg font-bold text-red-700\">{{ group.statusCounts.failed || 0 }}</div>\n </button>\n <button \n @click.stop=\"openTaskGroupDetailsWithFilter(group, 'cancelled')\"\n class=\"bg-gray-50 border border-gray-200 rounded-md p-2 text-center shadow-sm hover:shadow-md transform hover:-translate-y-1 transition-all duration-200 cursor-pointer hover:border-gray-300\"\n >\n <div class=\"text-xs text-gray-600 font-medium\">Cancelled</div>\n <div class=\"text-lg font-bold text-gray-700\">{{ group.statusCounts.cancelled || 0 }}</div>\n </button>\n </div>\n </li>\n </ul>\n </div>\n </div>\n </div>\n\n <!-- Create Task Modal -->\n <modal v-if=\"showCreateTaskModal\" containerClass=\"!h-[90vh] !w-[90vw]\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"closeCreateTaskModal\" role=\"button\" aria-label=\"Close modal\">&times;</div>\n <div class=\"space-y-4\">\n <h3 class=\"text-lg font-semibold text-gray-700 mb-4\">Create New Task</h3>\n \n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Task Name:</label>\n <input \n v-model=\"newTask.name\" \n type=\"text\" \n class=\"w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-ultramarine-500\"\n placeholder=\"Enter task name\"\n >\n </div>\n \n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Scheduled Time:</label>\n <input \n v-model=\"newTask.scheduledAt\" \n type=\"datetime-local\" \n class=\"w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-ultramarine-500\"\n >\n </div>\n \n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Parameters (JSON):</label>\n <textarea \n ref=\"parametersEditor\"\n class=\"w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-ultramarine-500\"\n placeholder='{\"key\": \"value\"}'\n ></textarea>\n </div>\n \n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Repeat Interval (ms):</label>\n <input \n v-model=\"newTask.repeatInterval\" \n type=\"number\" \n min=\"0\"\n step=\"1000\"\n class=\"w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-ultramarine-500\"\n placeholder=\"0 for no repetition\"\n >\n <p class=\"text-xs text-gray-500 mt-1\">Enter 0 or leave empty for no repetition. Use 1000 for 1 second, 60000 for 1 minute, etc.</p>\n </div>\n \n <div class=\"flex gap-2 pt-4\">\n <button \n @click=\"createTask\" \n class=\"flex-1 bg-ultramarine-600 text-white px-4 py-2 rounded-md hover:bg-ultramarine-700\"\n >\n Create Task\n </button>\n <button \n @click=\"closeCreateTaskModal\" \n class=\"flex-1 bg-gray-300 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-400\"\n >\n Cancel\n </button>\n </div>\n </div>\n </template>\n </modal>\n</div>\n";
26106
+ module.exports = "<div class=\"p-4 space-y-6\">\n <div>\n <h1 class=\"text-2xl font-bold text-gray-700 mb-4\">Task Overview</h1>\n <div v-if=\"status == 'init'\">\n <img src=\"images/loader.gif\" />\n </div>\n <!-- Task List -->\n <div class=\"bg-white p-4 rounded-lg shadow\" v-if=\"status == 'loaded'\">\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\">\n <option v-for=\"option in dateFilters\" :key=\"option.value\" :value=\"option.value\">\n {{ option.label }}\n </option>\n </select>\n </div>\n <div class=\"mb-4\">\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Filter by Status:</label>\n <select v-model=\"selectedStatus\" @change=\"getTasks\" class=\"border-gray-300 rounded-md shadow-sm w-full p-2\">\n <option v-for=\"option in statusFilters\" :key=\"option.value\" :value=\"option.value\">\n {{ option.label }}\n </option>\n </select>\n </div>\n <div class=\"mb-4\">\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Search by Task Name:</label>\n <input \n v-model=\"searchQuery\" \n type=\"text\" \n @input=\"onSearchInput\"\n class=\"border-gray-300 rounded-md shadow-sm w-full p-2\"\n placeholder=\"Enter task name to search...\"\n >\n </div>\n <div class=\"mb-4\">\n <button\n @click=\"resetFilters\"\n class=\"w-full bg-gray-200 text-gray-700 hover:bg-gray-300 font-medium py-2 px-4 rounded-md transition\"\n >\n Reset Filters\n </button>\n </div>\n <div class=\"mb-6\">\n <button\n @click=\"openCreateTaskModal\"\n class=\"w-full bg-ultramarine-600 text-white hover:bg-ultramarine-700 font-medium py-2 px-4 rounded-md transition\"\n >\n Create New Task\n </button>\n </div>\n <!-- Summary Section -->\n <div class=\"grid grid-cols-2 sm:grid-cols-4 gap-4\">\n <button \n @click=\"setStatusFilter('pending')\"\n :class=\"getStatusColor('pending') + ' p-4 rounded-lg shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200 cursor-pointer border-2 border-yellow-200 hover:border-yellow-300'\"\n >\n <div class=\"text-sm\">Scheduled</div>\n <div class=\"text-2xl font-bold\">{{pendingCount}}</div>\n </button>\n <button \n @click=\"setStatusFilter('succeeded')\"\n :class=\"getStatusColor('succeeded') + ' p-4 rounded-lg shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200 cursor-pointer border-2 border-green-200 hover:border-green-300'\"\n >\n <div class=\"text-sm\">Completed</div>\n <div class=\"text-2xl font-bold\">{{succeededCount}}</div>\n </button>\n <button \n @click=\"setStatusFilter('failed')\"\n :class=\"getStatusColor('failed') + ' p-4 rounded-lg shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200 cursor-pointer border-2 border-red-200 hover:border-red-300'\"\n >\n <div class=\"text-sm\">Failed</div>\n <div class=\"text-2xl font-bold\">{{failedCount}}</div>\n </button>\n <button \n @click=\"setStatusFilter('cancelled')\"\n :class=\"getStatusColor('cancelled') + ' p-4 rounded-lg shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200 cursor-pointer border-2 border-gray-200 hover:border-gray-300'\"\n >\n <div class=\"text-sm\">Cancelled</div>\n <div class=\"text-2xl font-bold\">{{cancelledCount}}</div>\n </button>\n </div>\n \n <!-- Grouped Task List -->\n <div class=\"mt-6\">\n <h2 class=\"text-lg font-semibold text-gray-700 mb-4\">Tasks by Name</h2>\n <ul class=\"divide-y divide-gray-200\">\n <li v-for=\"group in tasksByName\" :key=\"group.name\" class=\"p-4 group hover:border hover:rounded-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200\">\n <div class=\"flex items-center justify-between mb-3 \">\n <div class=\"flex-1 cursor-pointer\" @click=\"openTaskGroupDetails(group)\">\n <div class=\"flex items-center gap-2\">\n <div class=\"font-medium text-lg group-hover:text-ultramarine-600 transition-colors\">{{ group.name }}</div>\n <svg class=\"w-4 h-4 text-gray-400 group-hover:text-ultramarine-600 transition-colors\" 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 </div>\n <div class=\"text-sm text-gray-500 group-hover:text-gray-700 transition-colors\">Total: {{ group.totalCount }} tasks</div>\n <div class=\"text-xs text-ultramarine-600 opacity-0 group-hover:opacity-100 transition-opacity mt-1\">\n Click to view details\n </div>\n </div>\n <div class=\"text-sm text-gray-500\">\n Last run: {{ group.lastRun ? new Date(group.lastRun).toLocaleString() : 'Never' }}\n </div>\n </div>\n \n <!-- Status Counts -->\n <div class=\"grid grid-cols-2 sm:grid-cols-4 gap-2\">\n <button \n @click.stop=\"openTaskGroupDetailsWithFilter(group, 'pending')\"\n class=\"bg-yellow-50 border border-yellow-200 rounded-md p-2 text-center shadow-sm hover:shadow-md transform hover:-translate-y-1 transition-all duration-200 cursor-pointer hover:border-yellow-300\"\n >\n <div class=\"text-xs text-yellow-600 font-medium\">Pending</div>\n <div class=\"text-lg font-bold text-yellow-700\">{{ group.statusCounts.pending || 0 }}</div>\n </button>\n <button \n @click.stop=\"openTaskGroupDetailsWithFilter(group, 'succeeded')\"\n class=\"bg-green-50 border border-green-200 rounded-md p-2 text-center shadow-sm hover:shadow-md transform hover:-translate-y-1 transition-all duration-200 cursor-pointer hover:border-green-300\"\n >\n <div class=\"text-xs text-green-600 font-medium\">Succeeded</div>\n <div class=\"text-lg font-bold text-green-700\">{{ group.statusCounts.succeeded || 0 }}</div>\n </button>\n <button \n @click.stop=\"openTaskGroupDetailsWithFilter(group, 'failed')\"\n class=\"bg-red-50 border border-red-200 rounded-md p-2 text-center shadow-sm hover:shadow-md transform hover:-translate-y-1 transition-all duration-200 cursor-pointer hover:border-red-300\"\n >\n <div class=\"text-xs text-red-600 font-medium\">Failed</div>\n <div class=\"text-lg font-bold text-red-700\">{{ group.statusCounts.failed || 0 }}</div>\n </button>\n <button \n @click.stop=\"openTaskGroupDetailsWithFilter(group, 'cancelled')\"\n class=\"bg-gray-50 border border-gray-200 rounded-md p-2 text-center shadow-sm hover:shadow-md transform hover:-translate-y-1 transition-all duration-200 cursor-pointer hover:border-gray-300\"\n >\n <div class=\"text-xs text-gray-600 font-medium\">Cancelled</div>\n <div class=\"text-lg font-bold text-gray-700\">{{ group.statusCounts.cancelled || 0 }}</div>\n </button>\n </div>\n </li>\n </ul>\n </div>\n </div>\n </div>\n\n <!-- Create Task Modal -->\n <modal v-if=\"showCreateTaskModal\" containerClass=\"!h-[90vh] !w-[90vw]\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"closeCreateTaskModal\" role=\"button\" aria-label=\"Close modal\">&times;</div>\n <div class=\"space-y-4\">\n <h3 class=\"text-lg font-semibold text-gray-700 mb-4\">Create New Task</h3>\n \n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Task Name:</label>\n <input \n v-model=\"newTask.name\" \n type=\"text\" \n class=\"w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-ultramarine-500\"\n placeholder=\"Enter task name\"\n >\n </div>\n \n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Scheduled Time:</label>\n <input \n v-model=\"newTask.scheduledAt\" \n type=\"datetime-local\" \n class=\"w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-ultramarine-500\"\n >\n </div>\n \n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Parameters (JSON):</label>\n <textarea \n ref=\"parametersEditor\"\n class=\"w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-ultramarine-500\"\n placeholder='{\"key\": \"value\"}'\n ></textarea>\n </div>\n \n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Repeat Interval (ms):</label>\n <input \n v-model=\"newTask.repeatInterval\" \n type=\"number\" \n min=\"0\"\n step=\"1000\"\n class=\"w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-ultramarine-500\"\n placeholder=\"0 for no repetition\"\n >\n <p class=\"text-xs text-gray-500 mt-1\">Enter 0 or leave empty for no repetition. Use 1000 for 1 second, 60000 for 1 minute, etc.</p>\n </div>\n \n <div class=\"flex gap-2 pt-4\">\n <button \n @click=\"createTask\" \n class=\"flex-1 bg-ultramarine-600 text-white px-4 py-2 rounded-md hover:bg-ultramarine-700\"\n >\n Create Task\n </button>\n <button \n @click=\"closeCreateTaskModal\" \n class=\"flex-1 bg-gray-300 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-400\"\n >\n Cancel\n </button>\n </div>\n </div>\n </template>\n </modal>\n</div>\n";
25633
26107
 
25634
26108
  /***/ },
25635
26109
 
@@ -36943,7 +37417,7 @@ var src_default = VueToastificationPlugin;
36943
37417
  (module) {
36944
37418
 
36945
37419
  "use strict";
36946
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.2.12","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","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","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","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","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"}}');
37420
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.2.13","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","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","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 .","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"}}');
36947
37421
 
36948
37422
  /***/ }
36949
37423
 
@@ -36959,12 +37433,6 @@ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version"
36959
37433
  /******/ if (cachedModule !== undefined) {
36960
37434
  /******/ return cachedModule.exports;
36961
37435
  /******/ }
36962
- /******/ // Check if module exists (development only)
36963
- /******/ if (__webpack_modules__[moduleId] === undefined) {
36964
- /******/ var e = new Error("Cannot find module '" + moduleId + "'");
36965
- /******/ e.code = 'MODULE_NOT_FOUND';
36966
- /******/ throw e;
36967
- /******/ }
36968
37436
  /******/ // Create a new module (and put it into the cache)
36969
37437
  /******/ var module = __webpack_module_cache__[moduleId] = {
36970
37438
  /******/ // no module.id needed
@@ -36973,6 +37441,12 @@ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version"
36973
37441
  /******/ };
36974
37442
  /******/
36975
37443
  /******/ // Execute the module function
37444
+ /******/ if (!(moduleId in __webpack_modules__)) {
37445
+ /******/ delete __webpack_module_cache__[moduleId];
37446
+ /******/ var e = new Error("Cannot find module '" + moduleId + "'");
37447
+ /******/ e.code = 'MODULE_NOT_FOUND';
37448
+ /******/ throw e;
37449
+ /******/ }
36976
37450
  /******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
36977
37451
  /******/
36978
37452
  /******/ // Return the exports of the module