@mongoosejs/studio 0.2.11 → 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.27
9425
+ * @vue/reactivity v3.5.29
8987
9426
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
8988
9427
  * @license MIT
8989
9428
  **/
@@ -8995,6 +9434,7 @@ function warn(msg, ...args) {
8995
9434
 
8996
9435
  let activeEffectScope;
8997
9436
  class EffectScope {
9437
+ // TODO isolatedDeclarations "__v_skip"
8998
9438
  constructor(detached = false) {
8999
9439
  this.detached = detached;
9000
9440
  /**
@@ -9014,6 +9454,7 @@ class EffectScope {
9014
9454
  */
9015
9455
  this.cleanups = [];
9016
9456
  this._isPaused = false;
9457
+ this.__v_skip = true;
9017
9458
  this.parent = activeEffectScope;
9018
9459
  if (!detached && activeEffectScope) {
9019
9460
  this.index = (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(
@@ -11121,7 +11562,7 @@ __webpack_require__.r(__webpack_exports__);
11121
11562
  /* harmony import */ var _vue_reactivity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/reactivity */ "./node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js");
11122
11563
  /* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
11123
11564
  /**
11124
- * @vue/runtime-core v3.5.27
11565
+ * @vue/runtime-core v3.5.29
11125
11566
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
11126
11567
  * @license MIT
11127
11568
  **/
@@ -12326,7 +12767,22 @@ function moveTeleport(vnode, container, parentAnchor, { o: { insert }, m: move }
12326
12767
  function hydrateTeleport(node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized, {
12327
12768
  o: { nextSibling, parentNode, querySelector, insert, createText }
12328
12769
  }, hydrateChildren) {
12329
- function hydrateDisabledTeleport(node2, vnode2, targetStart, targetAnchor) {
12770
+ function hydrateAnchor(target2, targetNode) {
12771
+ let targetAnchor = targetNode;
12772
+ while (targetAnchor) {
12773
+ if (targetAnchor && targetAnchor.nodeType === 8) {
12774
+ if (targetAnchor.data === "teleport start anchor") {
12775
+ vnode.targetStart = targetAnchor;
12776
+ } else if (targetAnchor.data === "teleport anchor") {
12777
+ vnode.targetAnchor = targetAnchor;
12778
+ target2._lpa = vnode.targetAnchor && nextSibling(vnode.targetAnchor);
12779
+ break;
12780
+ }
12781
+ }
12782
+ targetAnchor = nextSibling(targetAnchor);
12783
+ }
12784
+ }
12785
+ function hydrateDisabledTeleport(node2, vnode2) {
12330
12786
  vnode2.anchor = hydrateChildren(
12331
12787
  nextSibling(node2),
12332
12788
  vnode2,
@@ -12336,8 +12792,6 @@ function hydrateTeleport(node, vnode, parentComponent, parentSuspense, slotScope
12336
12792
  slotScopeIds,
12337
12793
  optimized
12338
12794
  );
12339
- vnode2.targetStart = targetStart;
12340
- vnode2.targetAnchor = targetAnchor;
12341
12795
  }
12342
12796
  const target = vnode.target = resolveTarget(
12343
12797
  vnode.props,
@@ -12348,27 +12802,22 @@ function hydrateTeleport(node, vnode, parentComponent, parentSuspense, slotScope
12348
12802
  const targetNode = target._lpa || target.firstChild;
12349
12803
  if (vnode.shapeFlag & 16) {
12350
12804
  if (disabled) {
12351
- hydrateDisabledTeleport(
12352
- node,
12353
- vnode,
12354
- targetNode,
12355
- targetNode && nextSibling(targetNode)
12356
- );
12805
+ hydrateDisabledTeleport(node, vnode);
12806
+ hydrateAnchor(target, targetNode);
12807
+ if (!vnode.targetAnchor) {
12808
+ prepareAnchor(
12809
+ target,
12810
+ vnode,
12811
+ createText,
12812
+ insert,
12813
+ // if target is the same as the main view, insert anchors before current node
12814
+ // to avoid hydrating mismatch
12815
+ parentNode(node) === target ? node : null
12816
+ );
12817
+ }
12357
12818
  } else {
12358
12819
  vnode.anchor = nextSibling(node);
12359
- let targetAnchor = targetNode;
12360
- while (targetAnchor) {
12361
- if (targetAnchor && targetAnchor.nodeType === 8) {
12362
- if (targetAnchor.data === "teleport start anchor") {
12363
- vnode.targetStart = targetAnchor;
12364
- } else if (targetAnchor.data === "teleport anchor") {
12365
- vnode.targetAnchor = targetAnchor;
12366
- target._lpa = vnode.targetAnchor && nextSibling(vnode.targetAnchor);
12367
- break;
12368
- }
12369
- }
12370
- targetAnchor = nextSibling(targetAnchor);
12371
- }
12820
+ hydrateAnchor(target, targetNode);
12372
12821
  if (!vnode.targetAnchor) {
12373
12822
  prepareAnchor(target, vnode, createText, insert);
12374
12823
  }
@@ -12386,7 +12835,9 @@ function hydrateTeleport(node, vnode, parentComponent, parentSuspense, slotScope
12386
12835
  updateCssVars(vnode, disabled);
12387
12836
  } else if (disabled) {
12388
12837
  if (vnode.shapeFlag & 16) {
12389
- hydrateDisabledTeleport(node, vnode, node, nextSibling(node));
12838
+ hydrateDisabledTeleport(node, vnode);
12839
+ vnode.targetStart = node;
12840
+ vnode.targetAnchor = nextSibling(node);
12390
12841
  }
12391
12842
  }
12392
12843
  return vnode.anchor && nextSibling(vnode.anchor);
@@ -12410,13 +12861,13 @@ function updateCssVars(vnode, isDisabled) {
12410
12861
  ctx.ut();
12411
12862
  }
12412
12863
  }
12413
- function prepareAnchor(target, vnode, createText, insert) {
12864
+ function prepareAnchor(target, vnode, createText, insert, anchor = null) {
12414
12865
  const targetStart = vnode.targetStart = createText("");
12415
12866
  const targetAnchor = vnode.targetAnchor = createText("");
12416
12867
  targetStart[TeleportEndKey] = targetAnchor;
12417
12868
  if (target) {
12418
- insert(targetStart, target);
12419
- insert(targetAnchor, target);
12869
+ insert(targetStart, target, anchor);
12870
+ insert(targetAnchor, target, anchor);
12420
12871
  }
12421
12872
  return targetAnchor;
12422
12873
  }
@@ -12640,6 +13091,7 @@ function resolveTransitionHooks(vnode, props, state, instance, postClone) {
12640
13091
  callHook(hook, [el]);
12641
13092
  },
12642
13093
  enter(el) {
13094
+ if (leavingVNodesCache[key] === vnode) return;
12643
13095
  let hook = onEnter;
12644
13096
  let afterHook = onAfterEnter;
12645
13097
  let cancelHook = onEnterCancelled;
@@ -12653,7 +13105,7 @@ function resolveTransitionHooks(vnode, props, state, instance, postClone) {
12653
13105
  }
12654
13106
  }
12655
13107
  let called = false;
12656
- const done = el[enterCbKey] = (cancelled) => {
13108
+ el[enterCbKey] = (cancelled) => {
12657
13109
  if (called) return;
12658
13110
  called = true;
12659
13111
  if (cancelled) {
@@ -12666,6 +13118,7 @@ function resolveTransitionHooks(vnode, props, state, instance, postClone) {
12666
13118
  }
12667
13119
  el[enterCbKey] = void 0;
12668
13120
  };
13121
+ const done = el[enterCbKey].bind(null, false);
12669
13122
  if (hook) {
12670
13123
  callAsyncHook(hook, [el, done]);
12671
13124
  } else {
@@ -12685,7 +13138,7 @@ function resolveTransitionHooks(vnode, props, state, instance, postClone) {
12685
13138
  }
12686
13139
  callHook(onBeforeLeave, [el]);
12687
13140
  let called = false;
12688
- const done = el[leaveCbKey] = (cancelled) => {
13141
+ el[leaveCbKey] = (cancelled) => {
12689
13142
  if (called) return;
12690
13143
  called = true;
12691
13144
  remove();
@@ -12699,6 +13152,7 @@ function resolveTransitionHooks(vnode, props, state, instance, postClone) {
12699
13152
  delete leavingVNodesCache[key2];
12700
13153
  }
12701
13154
  };
13155
+ const done = el[leaveCbKey].bind(null, false);
12702
13156
  leavingVNodesCache[key2] = vnode;
12703
13157
  if (onLeave) {
12704
13158
  callAsyncHook(onLeave, [el, done]);
@@ -12811,8 +13265,7 @@ function useTemplateRef(key) {
12811
13265
  const r = (0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_0__.shallowRef)(null);
12812
13266
  if (i) {
12813
13267
  const refs = i.refs === _vue_shared__WEBPACK_IMPORTED_MODULE_1__.EMPTY_OBJ ? i.refs = {} : i.refs;
12814
- let desc;
12815
- if ( true && (desc = Object.getOwnPropertyDescriptor(refs, key)) && !desc.configurable) {
13268
+ if ( true && isTemplateRefKey(refs, key)) {
12816
13269
  warn$1(`useTemplateRef('${key}') already exists.`);
12817
13270
  } else {
12818
13271
  Object.defineProperty(refs, key, {
@@ -12832,6 +13285,10 @@ function useTemplateRef(key) {
12832
13285
  }
12833
13286
  return ret;
12834
13287
  }
13288
+ function isTemplateRefKey(refs, key) {
13289
+ let desc;
13290
+ return !!((desc = Object.getOwnPropertyDescriptor(refs, key)) && !desc.configurable);
13291
+ }
12835
13292
 
12836
13293
  const pendingSetRefMap = /* @__PURE__ */ new WeakMap();
12837
13294
  function setRef(rawRef, oldRawRef, parentSuspense, vnode, isUnmount = false) {
@@ -12877,10 +13334,19 @@ function setRef(rawRef, oldRawRef, parentSuspense, vnode, isUnmount = false) {
12877
13334
  return false;
12878
13335
  }
12879
13336
  }
13337
+ if (isTemplateRefKey(refs, key)) {
13338
+ return false;
13339
+ }
12880
13340
  return (0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.hasOwn)(rawSetupState, key);
12881
13341
  };
12882
- const canSetRef = (ref2) => {
12883
- return false || !knownTemplateRefs.has(ref2);
13342
+ const canSetRef = (ref2, key) => {
13343
+ if ( true && knownTemplateRefs.has(ref2)) {
13344
+ return false;
13345
+ }
13346
+ if (key && isTemplateRefKey(refs, key)) {
13347
+ return false;
13348
+ }
13349
+ return true;
12884
13350
  };
12885
13351
  if (oldRef != null && oldRef !== ref) {
12886
13352
  invalidatePendingSetRef(oldRawRef);
@@ -12890,10 +13356,10 @@ function setRef(rawRef, oldRawRef, parentSuspense, vnode, isUnmount = false) {
12890
13356
  setupState[oldRef] = null;
12891
13357
  }
12892
13358
  } else if ((0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_0__.isRef)(oldRef)) {
12893
- if (canSetRef(oldRef)) {
13359
+ const oldRawRefAtom = oldRawRef;
13360
+ if (canSetRef(oldRef, oldRawRefAtom.k)) {
12894
13361
  oldRef.value = null;
12895
13362
  }
12896
- const oldRawRefAtom = oldRawRef;
12897
13363
  if (oldRawRefAtom.k) refs[oldRawRefAtom.k] = null;
12898
13364
  }
12899
13365
  }
@@ -12917,7 +13383,7 @@ function setRef(rawRef, oldRawRef, parentSuspense, vnode, isUnmount = false) {
12917
13383
  }
12918
13384
  } else {
12919
13385
  const newVal = [refValue];
12920
- if (canSetRef(ref)) {
13386
+ if (canSetRef(ref, rawRef.k)) {
12921
13387
  ref.value = newVal;
12922
13388
  }
12923
13389
  if (rawRef.k) refs[rawRef.k] = newVal;
@@ -12932,7 +13398,7 @@ function setRef(rawRef, oldRawRef, parentSuspense, vnode, isUnmount = false) {
12932
13398
  setupState[ref] = value;
12933
13399
  }
12934
13400
  } else if (_isRef) {
12935
- if (canSetRef(ref)) {
13401
+ if (canSetRef(ref, rawRef.k)) {
12936
13402
  ref.value = value;
12937
13403
  }
12938
13404
  if (rawRef.k) refs[rawRef.k] = value;
@@ -14726,13 +15192,24 @@ function withAsyncContext(getAwaitable) {
14726
15192
  }
14727
15193
  let awaitable = getAwaitable();
14728
15194
  unsetCurrentInstance();
15195
+ const cleanup = () => {
15196
+ if (getCurrentInstance() !== ctx) ctx.scope.off();
15197
+ unsetCurrentInstance();
15198
+ };
14729
15199
  if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.isPromise)(awaitable)) {
14730
15200
  awaitable = awaitable.catch((e) => {
14731
15201
  setCurrentInstance(ctx);
15202
+ Promise.resolve().then(() => Promise.resolve().then(cleanup));
14732
15203
  throw e;
14733
15204
  });
14734
15205
  }
14735
- return [awaitable, () => setCurrentInstance(ctx)];
15206
+ return [
15207
+ awaitable,
15208
+ () => {
15209
+ setCurrentInstance(ctx);
15210
+ Promise.resolve().then(cleanup);
15211
+ }
15212
+ ];
14736
15213
  }
14737
15214
 
14738
15215
  function createDuplicateChecker() {
@@ -15800,7 +16277,7 @@ function shouldUpdateComponent(prevVNode, nextVNode, optimized) {
15800
16277
  const dynamicProps = nextVNode.dynamicProps;
15801
16278
  for (let i = 0; i < dynamicProps.length; i++) {
15802
16279
  const key = dynamicProps[i];
15803
- if (nextProps[key] !== prevProps[key] && !isEmitListener(emits, key)) {
16280
+ if (hasPropValueChanged(nextProps, prevProps, key) && !isEmitListener(emits, key)) {
15804
16281
  return true;
15805
16282
  }
15806
16283
  }
@@ -15831,12 +16308,20 @@ function hasPropsChanged(prevProps, nextProps, emitsOptions) {
15831
16308
  }
15832
16309
  for (let i = 0; i < nextKeys.length; i++) {
15833
16310
  const key = nextKeys[i];
15834
- if (nextProps[key] !== prevProps[key] && !isEmitListener(emitsOptions, key)) {
16311
+ if (hasPropValueChanged(nextProps, prevProps, key) && !isEmitListener(emitsOptions, key)) {
15835
16312
  return true;
15836
16313
  }
15837
16314
  }
15838
16315
  return false;
15839
16316
  }
16317
+ function hasPropValueChanged(nextProps, prevProps, key) {
16318
+ const nextProp = nextProps[key];
16319
+ const prevProp = prevProps[key];
16320
+ if (key === "style" && (0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.isObject)(nextProp) && (0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.isObject)(prevProp)) {
16321
+ return !(0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.looseEqual)(nextProp, prevProp);
16322
+ }
16323
+ return nextProp !== prevProp;
16324
+ }
15840
16325
  function updateHOCHostEl({ vnode, parent }, el) {
15841
16326
  while (parent) {
15842
16327
  const root = parent.subTree;
@@ -16567,15 +17052,7 @@ function baseCreateRenderer(options, createHydrationFns) {
16567
17052
  } else {
16568
17053
  const el = n2.el = n1.el;
16569
17054
  if (n2.children !== n1.children) {
16570
- if ( true && isHmrUpdating && n2.patchFlag === -1 && "__elIndex" in n1) {
16571
- const childNodes = container.childNodes;
16572
- const newChild = hostCreateText(n2.children);
16573
- const oldChild = childNodes[n2.__elIndex = n1.__elIndex];
16574
- hostInsert(newChild, container, oldChild);
16575
- hostRemove(oldChild);
16576
- } else {
16577
- hostSetText(el, n2.children);
16578
- }
17055
+ hostSetText(el, n2.children);
16579
17056
  }
16580
17057
  }
16581
17058
  };
@@ -16651,7 +17128,7 @@ function baseCreateRenderer(options, createHydrationFns) {
16651
17128
  optimized
16652
17129
  );
16653
17130
  } else {
16654
- const customElement = !!(n1.el && n1.el._isVueCE) ? n1.el : null;
17131
+ const customElement = n1.el && n1.el._isVueCE ? n1.el : null;
16655
17132
  try {
16656
17133
  if (customElement) {
16657
17134
  customElement._beginPatch();
@@ -17132,8 +17609,7 @@ function baseCreateRenderer(options, createHydrationFns) {
17132
17609
  hydrateSubTree();
17133
17610
  }
17134
17611
  } else {
17135
- if (root.ce && // @ts-expect-error _def is private
17136
- root.ce._def.shadowRoot !== false) {
17612
+ if (root.ce && root.ce._hasShadowRoot()) {
17137
17613
  root.ce._injectChildStyle(type);
17138
17614
  }
17139
17615
  if (true) {
@@ -17188,9 +17664,9 @@ function baseCreateRenderer(options, createHydrationFns) {
17188
17664
  updateComponentPreRender(instance, next, optimized);
17189
17665
  }
17190
17666
  nonHydratedAsyncRoot.asyncDep.then(() => {
17191
- if (!instance.isUnmounted) {
17192
- componentUpdateFn();
17193
- }
17667
+ queuePostRenderEffect(() => {
17668
+ if (!instance.isUnmounted) update();
17669
+ }, parentSuspense);
17194
17670
  });
17195
17671
  return;
17196
17672
  }
@@ -17887,12 +18363,10 @@ function traverseStaticChildren(n1, n2, shallow = false) {
17887
18363
  traverseStaticChildren(c1, c2);
17888
18364
  }
17889
18365
  if (c2.type === Text) {
17890
- if (c2.patchFlag !== -1) {
17891
- c2.el = c1.el;
17892
- } else {
17893
- c2.__elIndex = i + // take fragment start anchor into account
17894
- (n1.type === Fragment ? 1 : 0);
18366
+ if (c2.patchFlag === -1) {
18367
+ c2 = ch2[i] = cloneIfMounted(c2);
17895
18368
  }
18369
+ c2.el = c1.el;
17896
18370
  }
17897
18371
  if (c2.type === Comment && !c2.el) {
17898
18372
  c2.el = c1.el;
@@ -19620,7 +20094,7 @@ function isMemoSame(cached, memo) {
19620
20094
  return true;
19621
20095
  }
19622
20096
 
19623
- const version = "3.5.27";
20097
+ const version = "3.5.29";
19624
20098
  const warn = true ? warn$1 : 0;
19625
20099
  const ErrorTypeStrings = ErrorTypeStrings$1 ;
19626
20100
  const devtools = true ? devtools$1 : 0;
@@ -19831,7 +20305,7 @@ __webpack_require__.r(__webpack_exports__);
19831
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");
19832
20306
  /* harmony import */ var _vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
19833
20307
  /**
19834
- * @vue/runtime-dom v3.5.27
20308
+ * @vue/runtime-dom v3.5.29
19835
20309
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
19836
20310
  * @license MIT
19837
20311
  **/
@@ -21091,6 +21565,12 @@ class VueElement extends BaseClass {
21091
21565
  this._update();
21092
21566
  }
21093
21567
  }
21568
+ /**
21569
+ * @internal
21570
+ */
21571
+ _hasShadowRoot() {
21572
+ return this._def.shadowRoot !== false;
21573
+ }
21094
21574
  /**
21095
21575
  * @internal
21096
21576
  */
@@ -21225,10 +21705,7 @@ const TransitionGroupImpl = /* @__PURE__ */ decorate({
21225
21705
  instance
21226
21706
  )
21227
21707
  );
21228
- positionMap.set(child, {
21229
- left: child.el.offsetLeft,
21230
- top: child.el.offsetTop
21231
- });
21708
+ positionMap.set(child, getPosition(child.el));
21232
21709
  }
21233
21710
  }
21234
21711
  }
@@ -21259,10 +21736,7 @@ function callPendingCbs(c) {
21259
21736
  }
21260
21737
  }
21261
21738
  function recordPosition(c) {
21262
- newPositionMap.set(c, {
21263
- left: c.el.offsetLeft,
21264
- top: c.el.offsetTop
21265
- });
21739
+ newPositionMap.set(c, getPosition(c.el));
21266
21740
  }
21267
21741
  function applyTranslation(c) {
21268
21742
  const oldPos = positionMap.get(c);
@@ -21270,12 +21744,29 @@ function applyTranslation(c) {
21270
21744
  const dx = oldPos.left - newPos.left;
21271
21745
  const dy = oldPos.top - newPos.top;
21272
21746
  if (dx || dy) {
21273
- const s = c.el.style;
21274
- s.transform = s.webkitTransform = `translate(${dx}px,${dy}px)`;
21747
+ const el = c.el;
21748
+ const s = el.style;
21749
+ const rect = el.getBoundingClientRect();
21750
+ let scaleX = 1;
21751
+ let scaleY = 1;
21752
+ if (el.offsetWidth) scaleX = rect.width / el.offsetWidth;
21753
+ if (el.offsetHeight) scaleY = rect.height / el.offsetHeight;
21754
+ if (!Number.isFinite(scaleX) || scaleX === 0) scaleX = 1;
21755
+ if (!Number.isFinite(scaleY) || scaleY === 0) scaleY = 1;
21756
+ if (Math.abs(scaleX - 1) < 0.01) scaleX = 1;
21757
+ if (Math.abs(scaleY - 1) < 0.01) scaleY = 1;
21758
+ s.transform = s.webkitTransform = `translate(${dx / scaleX}px,${dy / scaleY}px)`;
21275
21759
  s.transitionDuration = "0s";
21276
21760
  return c;
21277
21761
  }
21278
21762
  }
21763
+ function getPosition(el) {
21764
+ const rect = el.getBoundingClientRect();
21765
+ return {
21766
+ left: rect.left,
21767
+ top: rect.top
21768
+ };
21769
+ }
21279
21770
  function hasCSSTransform(el, root, moveClass) {
21280
21771
  const clone = el.cloneNode();
21281
21772
  const _vtc = el[vtcKey];
@@ -21586,6 +22077,7 @@ const modifierGuards = {
21586
22077
  exact: (e, modifiers) => systemModifiers.some((m) => e[`${m}Key`] && !modifiers.includes(m))
21587
22078
  };
21588
22079
  const withModifiers = (fn, modifiers) => {
22080
+ if (!fn) return fn;
21589
22081
  const cache = fn._withMods || (fn._withMods = {});
21590
22082
  const cacheKey = modifiers.join(".");
21591
22083
  return cache[cacheKey] || (cache[cacheKey] = ((event, ...args) => {
@@ -21837,7 +22329,7 @@ __webpack_require__.r(__webpack_exports__);
21837
22329
  /* harmony export */ toTypeString: () => (/* binding */ toTypeString)
21838
22330
  /* harmony export */ });
21839
22331
  /**
21840
- * @vue/shared v3.5.27
22332
+ * @vue/shared v3.5.29
21841
22333
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
21842
22334
  * @license MIT
21843
22335
  **/
@@ -23084,7 +23576,7 @@ module.exports = {
23084
23576
  "use strict";
23085
23577
 
23086
23578
  Object.defineProperty(exports, "__esModule", ({ value: true }));
23087
- 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;
23088
23580
  exports.parseToElementsToArray = parseToElementsToArray;
23089
23581
  exports.pluckBSONSerializeOptions = pluckBSONSerializeOptions;
23090
23582
  exports.resolveBSONOptions = resolveBSONOptions;
@@ -23109,6 +23601,7 @@ Object.defineProperty(exports, "Int32", ({ enumerable: true, get: function () {
23109
23601
  Object.defineProperty(exports, "Long", ({ enumerable: true, get: function () { return bson_2.Long; } }));
23110
23602
  Object.defineProperty(exports, "MaxKey", ({ enumerable: true, get: function () { return bson_2.MaxKey; } }));
23111
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; } }));
23112
23605
  Object.defineProperty(exports, "ObjectId", ({ enumerable: true, get: function () { return bson_2.ObjectId; } }));
23113
23606
  Object.defineProperty(exports, "serialize", ({ enumerable: true, get: function () { return bson_2.serialize; } }));
23114
23607
  Object.defineProperty(exports, "Timestamp", ({ enumerable: true, get: function () { return bson_2.Timestamp; } }));
@@ -23774,7 +24267,7 @@ __webpack_require__.r(__webpack_exports__);
23774
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");
23775
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");
23776
24269
  /**
23777
- * vue v3.5.27
24270
+ * vue v3.5.29
23778
24271
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
23779
24272
  * @license MIT
23780
24273
  **/
@@ -24928,7 +25421,7 @@ module.exports = "<div class=\"relative flex items-start\" :class=\"{'justify-en
24928
25421
  (module) {
24929
25422
 
24930
25423
  "use strict";
24931
- 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";
24932
25425
 
24933
25426
  /***/ },
24934
25427
 
@@ -25214,7 +25707,7 @@ module.exports = ".document .document-menu {\n display: flex;\n position: stic
25214
25707
  (module) {
25215
25708
 
25216
25709
  "use strict";
25217
- 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";
25218
25711
 
25219
25712
  /***/ },
25220
25713
 
@@ -25522,7 +26015,7 @@ module.exports = ".models {\n position: relative;\n display: flex;\n flex-dir
25522
26015
  (module) {
25523
26016
 
25524
26017
  "use strict";
25525
- 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";
25526
26019
 
25527
26020
  /***/ },
25528
26021
 
@@ -25559,6 +26052,28 @@ module.exports = "<div class=\"w-full h-full flex items-center justify-center\">
25559
26052
 
25560
26053
  /***/ },
25561
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
+
25562
26077
  /***/ "./frontend/src/tasks/task-details/task-details.html"
25563
26078
  /*!***********************************************************!*\
25564
26079
  !*** ./frontend/src/tasks/task-details/task-details.html ***!
@@ -25566,7 +26081,7 @@ module.exports = "<div class=\"w-full h-full flex items-center justify-center\">
25566
26081
  (module) {
25567
26082
 
25568
26083
  "use strict";
25569
- 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";
25570
26085
 
25571
26086
  /***/ },
25572
26087
 
@@ -25588,7 +26103,7 @@ module.exports = "";
25588
26103
  (module) {
25589
26104
 
25590
26105
  "use strict";
25591
- 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";
25592
26107
 
25593
26108
  /***/ },
25594
26109
 
@@ -36902,7 +37417,7 @@ var src_default = VueToastificationPlugin;
36902
37417
  (module) {
36903
37418
 
36904
37419
  "use strict";
36905
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.2.11","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"}}');
36906
37421
 
36907
37422
  /***/ }
36908
37423
 
@@ -36918,12 +37433,6 @@ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version"
36918
37433
  /******/ if (cachedModule !== undefined) {
36919
37434
  /******/ return cachedModule.exports;
36920
37435
  /******/ }
36921
- /******/ // Check if module exists (development only)
36922
- /******/ if (__webpack_modules__[moduleId] === undefined) {
36923
- /******/ var e = new Error("Cannot find module '" + moduleId + "'");
36924
- /******/ e.code = 'MODULE_NOT_FOUND';
36925
- /******/ throw e;
36926
- /******/ }
36927
37436
  /******/ // Create a new module (and put it into the cache)
36928
37437
  /******/ var module = __webpack_module_cache__[moduleId] = {
36929
37438
  /******/ // no module.id needed
@@ -36932,6 +37441,12 @@ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version"
36932
37441
  /******/ };
36933
37442
  /******/
36934
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
+ /******/ }
36935
37450
  /******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
36936
37451
  /******/
36937
37452
  /******/ // Return the exports of the module