@mongoosejs/studio 0.3.0 → 0.3.1

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.
@@ -29,6 +29,99 @@ module.exports = {
29
29
  };
30
30
 
31
31
 
32
+ /***/ },
33
+
34
+ /***/ "./frontend/src/_util/dateRange.js"
35
+ /*!*****************************************!*\
36
+ !*** ./frontend/src/_util/dateRange.js ***!
37
+ \*****************************************/
38
+ (module) {
39
+
40
+ "use strict";
41
+
42
+
43
+ const DATE_FILTERS = [
44
+ { value: 'last_hour', label: 'Last Hour' },
45
+ { value: 'today', label: 'Today' },
46
+ { value: 'yesterday', label: 'Yesterday' },
47
+ { value: 'thisWeek', label: 'This Week' },
48
+ { value: 'lastWeek', label: 'Last Week' },
49
+ { value: 'thisMonth', label: 'This Month' },
50
+ { value: 'lastMonth', label: 'Last Month' }
51
+ ];
52
+
53
+ const DATE_FILTER_VALUES = DATE_FILTERS.map(f => f.value);
54
+
55
+ /**
56
+ * Returns { start, end } Date objects for a given range key (e.g. 'last_hour', 'today').
57
+ * Month ranges use UTC boundaries.
58
+ * @param {string} selectedRange - One of DATE_FILTER_VALUES
59
+ * @returns {{ start: Date, end: Date }}
60
+ */
61
+ function getDateRangeForRange(selectedRange) {
62
+ const now = new Date();
63
+ let start, end;
64
+ switch (selectedRange) {
65
+ case 'last_hour':
66
+ start = new Date();
67
+ start.setHours(start.getHours() - 1);
68
+ end = new Date();
69
+ break;
70
+ case 'today':
71
+ start = new Date();
72
+ start.setHours(0, 0, 0, 0);
73
+ end = new Date();
74
+ end.setHours(23, 59, 59, 999);
75
+ break;
76
+ case 'yesterday':
77
+ start = new Date(now);
78
+ start.setDate(start.getDate() - 1);
79
+ start.setHours(0, 0, 0, 0);
80
+ end = new Date(start);
81
+ end.setHours(23, 59, 59, 999);
82
+ break;
83
+ case 'thisWeek':
84
+ start = new Date(now.getTime() - (7 * 86400000));
85
+ start.setHours(0, 0, 0, 0);
86
+ end = new Date();
87
+ end.setHours(23, 59, 59, 999);
88
+ break;
89
+ case 'lastWeek':
90
+ start = new Date(now.getTime() - (14 * 86400000));
91
+ start.setHours(0, 0, 0, 0);
92
+ end = new Date(now.getTime() - (7 * 86400000));
93
+ end.setHours(23, 59, 59, 999);
94
+ break;
95
+ case 'thisMonth': {
96
+ const y = now.getUTCFullYear();
97
+ const m = now.getUTCMonth();
98
+ start = new Date(Date.UTC(y, m, 1, 0, 0, 0, 0));
99
+ end = new Date(Date.UTC(y, m + 1, 0, 23, 59, 59, 999));
100
+ break;
101
+ }
102
+ case 'lastMonth': {
103
+ const y = now.getUTCFullYear();
104
+ const m = now.getUTCMonth();
105
+ start = new Date(Date.UTC(y, m - 1, 1, 0, 0, 0, 0));
106
+ end = new Date(Date.UTC(y, m, 0, 23, 59, 59, 999));
107
+ break;
108
+ }
109
+ default:
110
+ start = new Date();
111
+ start.setHours(start.getHours() - 1);
112
+ end = new Date();
113
+ break;
114
+ }
115
+ return { start, end };
116
+ }
117
+
118
+ module.exports = {
119
+ DATE_FILTERS,
120
+ DATE_FILTER_VALUES,
121
+ getDateRangeForRange
122
+ };
123
+
124
+
32
125
  /***/ },
33
126
 
34
127
  /***/ "./frontend/src/_util/deepEqual.js"
@@ -424,6 +517,12 @@ module.exports = app => app.component('ace-editor', {
424
517
  }
425
518
  },
426
519
  methods: {
520
+ getValue() {
521
+ if (this.editor) {
522
+ return this.editor.getValue();
523
+ }
524
+ return this.modelValue !== '' ? this.modelValue : this.value;
525
+ },
427
526
  setValue(val) {
428
527
  if (this.editor) {
429
528
  this.editor.setValue(val ?? '', -1);
@@ -709,6 +808,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
709
808
  getTasks: function getTasks(params) {
710
809
  return client.post('', { action: 'Task.getTasks', ...params }).then(res => res.data);
711
810
  },
811
+ getTaskOverview: function getTaskOverview(params) {
812
+ return client.post('', { action: 'Task.getTaskOverview', ...params }).then(res => res.data);
813
+ },
712
814
  rescheduleTask: function rescheduleTask(params) {
713
815
  return client.post('', { action: 'Task.rescheduleTask', ...params }).then(res => res.data);
714
816
  },
@@ -1043,6 +1145,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
1043
1145
  getTasks: function getTasks(params) {
1044
1146
  return client.post('/Task/getTasks', params).then(res => res.data);
1045
1147
  },
1148
+ getTaskOverview: function getTaskOverview(params) {
1149
+ return client.post('/Task/getTaskOverview', params).then(res => res.data);
1150
+ },
1046
1151
  rescheduleTask: function rescheduleTask(params) {
1047
1152
  return client.post('/Task/rescheduleTask', params).then(res => res.data);
1048
1153
  },
@@ -1252,15 +1357,9 @@ module.exports = app => app.component('chat-message-script', {
1252
1357
  },
1253
1358
  canOverwriteDashboard() {
1254
1359
  return !!this.targetDashboardId;
1255
- },
1256
- isChartOutput() {
1257
- return !!this.message.executionResult?.output?.$chart;
1258
1360
  }
1259
1361
  },
1260
1362
  methods: {
1261
- exportChartPNG() {
1262
- this.$refs.chartOutput?.exportPNG?.();
1263
- },
1264
1363
  async executeScript() {
1265
1364
  const scriptToRun = this.isEditing ? this.editedScript : this.script;
1266
1365
  this.editedScript = scriptToRun;
@@ -2387,12 +2486,63 @@ module.exports = app => app.component('dashboard-result', {
2387
2486
  if (value.$grid) {
2388
2487
  return 'dashboard-grid';
2389
2488
  }
2489
+ if (value.$table) {
2490
+ return 'dashboard-table';
2491
+ }
2390
2492
  return 'dashboard-object';
2391
2493
  }
2392
2494
  }
2393
2495
  });
2394
2496
 
2395
2497
 
2498
+ /***/ },
2499
+
2500
+ /***/ "./frontend/src/dashboard-result/dashboard-table/dashboard-table.js"
2501
+ /*!**************************************************************************!*\
2502
+ !*** ./frontend/src/dashboard-result/dashboard-table/dashboard-table.js ***!
2503
+ \**************************************************************************/
2504
+ (module, __unused_webpack_exports, __webpack_require__) {
2505
+
2506
+ "use strict";
2507
+
2508
+
2509
+ const template = __webpack_require__(/*! ./dashboard-table.html */ "./frontend/src/dashboard-result/dashboard-table/dashboard-table.html");
2510
+
2511
+ module.exports = app => app.component('dashboard-table', {
2512
+ template,
2513
+ props: ['value'],
2514
+ computed: {
2515
+ columns() {
2516
+ return Array.isArray(this.value?.$table?.columns) ? this.value.$table.columns : [];
2517
+ },
2518
+ rows() {
2519
+ return Array.isArray(this.value?.$table?.rows) ? this.value.$table.rows : [];
2520
+ },
2521
+ hasColumns() {
2522
+ return this.columns.length > 0;
2523
+ },
2524
+ hasRows() {
2525
+ return this.rows.length > 0;
2526
+ }
2527
+ },
2528
+ methods: {
2529
+ displayValue(cell) {
2530
+ if (cell == null) {
2531
+ return '';
2532
+ }
2533
+ if (typeof cell === 'object') {
2534
+ try {
2535
+ return JSON.stringify(cell);
2536
+ } catch (err) {
2537
+ return String(cell);
2538
+ }
2539
+ }
2540
+ return String(cell);
2541
+ }
2542
+ }
2543
+ });
2544
+
2545
+
2396
2546
  /***/ },
2397
2547
 
2398
2548
  /***/ "./frontend/src/dashboard-result/dashboard-text/dashboard-text.js"
@@ -2451,6 +2601,13 @@ module.exports = {
2451
2601
  this.showEditor = !this.showEditor;
2452
2602
  },
2453
2603
  async updateCode(update) {
2604
+ if (!update?.doc) {
2605
+ const message = update?.error?.message || 'Dashboard update failed';
2606
+ console.error(update?.error || new Error(message));
2607
+ this.$toast.error(message);
2608
+ return;
2609
+ }
2610
+
2454
2611
  this.code = update.doc.code;
2455
2612
  this.title = update.doc.title;
2456
2613
  this.description = update.doc.description;
@@ -2475,7 +2632,9 @@ module.exports = {
2475
2632
  this.dashboardResults.unshift({ error: { message: error.message || 'Evaluation failed' }, finishedEvaluatingAt: new Date() });
2476
2633
  }
2477
2634
  } catch (err) {
2478
- this.$toast.error(err?.response?.data?.message || err?.message || 'Dashboard evaluation failed');
2635
+ const message = err?.response?.data?.message || err?.message || 'Dashboard evaluation failed';
2636
+ console.error(err || new Error(message));
2637
+ this.$toast.error(message);
2479
2638
  } finally {
2480
2639
  this.status = 'loaded';
2481
2640
  }
@@ -2599,7 +2758,7 @@ const template = __webpack_require__(/*! ./edit-dashboard.html */ "./frontend/sr
2599
2758
  module.exports = app => app.component('edit-dashboard', {
2600
2759
  template: template,
2601
2760
  props: ['dashboardId', 'code', 'currentDescription', 'currentTitle'],
2602
- emits: ['close'],
2761
+ emits: ['close', 'update'],
2603
2762
  data: function() {
2604
2763
  return {
2605
2764
  status: 'loaded',
@@ -2620,7 +2779,7 @@ module.exports = app => app.component('edit-dashboard', {
2620
2779
  async updateCode() {
2621
2780
  this.status = 'loading';
2622
2781
  try {
2623
- const codeToSave = this.$refs.codeEditor ? this.$refs.codeEditor.getValue() : this.editCode;
2782
+ const codeToSave = this.$refs.codeEditor?.getValue ? this.$refs.codeEditor.getValue() : this.editCode;
2624
2783
  const { doc } = await api.Dashboard.updateDashboard({
2625
2784
  dashboardId: this.dashboardId,
2626
2785
  code: codeToSave,
@@ -2636,7 +2795,8 @@ module.exports = app => app.component('edit-dashboard', {
2636
2795
  this.$toast.success('Dashboard updated!');
2637
2796
  this.closeEditor();
2638
2797
  } catch (err) {
2639
- this.$emit('update', { error: { message: err.message } });
2798
+ const message = err?.response?.data?.message || err?.message || 'Dashboard update failed';
2799
+ this.$emit('update', { error: { message } });
2640
2800
  } finally {
2641
2801
  this.status = 'loaded';
2642
2802
  }
@@ -6946,6 +7106,7 @@ module.exports = app => app.component('models', {
6946
7106
  }),
6947
7107
  created() {
6948
7108
  this.currentModel = this.model;
7109
+ this.setSearchTextFromRoute();
6949
7110
  this.loadOutputPreference();
6950
7111
  this.loadSelectedGeoField();
6951
7112
  this.loadRecentlyViewedModels();
@@ -6982,6 +7143,7 @@ module.exports = app => app.component('models', {
6982
7143
  }
6983
7144
  };
6984
7145
  document.addEventListener('keydown', this.onCtrlP, true);
7146
+ this.query = Object.assign({}, this.$route.query);
6985
7147
  const { models, readyState } = await api.Model.listModels();
6986
7148
  this.models = models;
6987
7149
  await this.loadModelCounts();
@@ -7364,14 +7526,17 @@ module.exports = app => app.component('models', {
7364
7526
 
7365
7527
  return params;
7366
7528
  },
7367
- async initSearchFromUrl() {
7368
- this.status = 'loading';
7369
- this.query = Object.assign({}, this.$route.query); // important that this is here before the if statements
7529
+ setSearchTextFromRoute() {
7370
7530
  if (this.$route.query?.search) {
7371
7531
  this.searchText = this.$route.query.search;
7372
7532
  } else {
7373
7533
  this.searchText = '';
7374
7534
  }
7535
+ },
7536
+ async initSearchFromUrl() {
7537
+ this.status = 'loading';
7538
+ this.query = Object.assign({}, this.$route.query); // important that this is here before the if statements
7539
+ this.setSearchTextFromRoute();
7375
7540
  if (this.$route.query?.sort) {
7376
7541
  const sort = eval(`(${this.$route.query.sort})`);
7377
7542
  const path = Object.keys(sort)[0];
@@ -8413,6 +8578,7 @@ module.exports = app => app.component('splash', {
8413
8578
  // Page: all tasks with a given name. Reuses task-details to render the list (many tasks).
8414
8579
  const template = __webpack_require__(/*! ./task-by-name.html */ "./frontend/src/task-by-name/task-by-name.html");
8415
8580
  const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
8581
+ const { DATE_FILTERS, DATE_FILTER_VALUES, getDateRangeForRange } = __webpack_require__(/*! ../_util/dateRange */ "./frontend/src/_util/dateRange.js");
8416
8582
 
8417
8583
  function buildTaskGroup(name, tasks) {
8418
8584
  const statusCounts = { pending: 0, succeeded: 0, failed: 0, cancelled: 0, in_progress: 0, unknown: 0 };
@@ -8436,44 +8602,118 @@ function buildTaskGroup(name, tasks) {
8436
8602
  };
8437
8603
  }
8438
8604
 
8605
+ const PAGE_SIZE_OPTIONS = [25, 50, 100, 200];
8606
+
8439
8607
  module.exports = app => app.component('task-by-name', {
8440
8608
  template,
8441
8609
  data: () => ({
8442
8610
  status: 'init',
8443
8611
  taskGroup: null,
8444
- errorMessage: ''
8612
+ errorMessage: '',
8613
+ selectedRange: 'last_hour',
8614
+ start: null,
8615
+ end: null,
8616
+ dateFilters: DATE_FILTERS,
8617
+ page: 1,
8618
+ pageSize: 50,
8619
+ numDocs: 0,
8620
+ pageSizeOptions: PAGE_SIZE_OPTIONS,
8621
+ _loadId: 0,
8622
+ _lastQueryFilters: null
8445
8623
  }),
8446
8624
  computed: {
8447
8625
  taskName() {
8448
8626
  return this.$route.params.name || '';
8449
8627
  }
8450
8628
  },
8629
+ created() {
8630
+ const fromQuery = this.$route.query.dateRange;
8631
+ this.selectedRange = (fromQuery && DATE_FILTER_VALUES.includes(fromQuery)) ? fromQuery : 'last_hour';
8632
+ if (!this.$route.query.dateRange) {
8633
+ this.$router.replace({
8634
+ path: this.$route.path,
8635
+ query: { ...this.$route.query, dateRange: this.selectedRange }
8636
+ });
8637
+ }
8638
+ },
8451
8639
  watch: {
8452
8640
  taskName: {
8453
8641
  immediate: true,
8454
8642
  handler() {
8643
+ this.page = 1;
8455
8644
  this.loadTasks();
8456
8645
  }
8646
+ },
8647
+ '$route.query': {
8648
+ handler(query) {
8649
+ const dateRange = query.dateRange;
8650
+ if (dateRange && DATE_FILTER_VALUES.includes(dateRange) && this.selectedRange !== dateRange) {
8651
+ this.selectedRange = dateRange;
8652
+ }
8653
+ const effectiveDateRange = (dateRange && DATE_FILTER_VALUES.includes(dateRange)) ? dateRange : (this.selectedRange || 'last_hour');
8654
+ const effectiveStatus = query.status ?? '';
8655
+ const key = `${effectiveDateRange}|${effectiveStatus}`;
8656
+ if (this._lastQueryFilters === key) return;
8657
+ this.page = 1;
8658
+ this.loadTasks();
8659
+ },
8660
+ deep: true
8457
8661
  }
8458
8662
  },
8459
8663
  methods: {
8664
+ updateDateRange() {
8665
+ this.page = 1;
8666
+ this.$router.replace({
8667
+ path: this.$route.path,
8668
+ query: { ...this.$route.query, dateRange: this.selectedRange }
8669
+ });
8670
+ },
8671
+ goToPage(page) {
8672
+ const maxPage = Math.max(1, Math.ceil(this.numDocs / this.pageSize));
8673
+ const next = Math.max(1, Math.min(page, maxPage));
8674
+ if (next === this.page) return;
8675
+ this.page = next;
8676
+ this.loadTasks();
8677
+ },
8678
+ onPageSizeChange() {
8679
+ this.page = 1;
8680
+ this.loadTasks();
8681
+ },
8460
8682
  async loadTasks() {
8461
8683
  if (!this.taskName) return;
8684
+ const loadId = ++this._loadId;
8462
8685
  this.status = 'init';
8463
8686
  this.taskGroup = null;
8464
8687
  this.errorMessage = '';
8465
- const start = new Date();
8466
- start.setHours(start.getHours() - 1);
8467
- const end = new Date();
8688
+ const dateRangeFromQuery = this.$route.query.dateRange;
8689
+ const dateRange = (dateRangeFromQuery && DATE_FILTER_VALUES.includes(dateRangeFromQuery))
8690
+ ? dateRangeFromQuery
8691
+ : (this.selectedRange || 'last_hour');
8692
+ this.selectedRange = dateRange;
8693
+ const { start, end } = getDateRangeForRange(dateRange);
8694
+ this.start = start;
8695
+ this.end = end;
8696
+ const skip = (this.page - 1) * this.pageSize;
8697
+ const params = {
8698
+ name: this.taskName,
8699
+ start: start instanceof Date ? start.toISOString() : start,
8700
+ end: end instanceof Date ? end.toISOString() : end,
8701
+ skip,
8702
+ limit: this.pageSize
8703
+ };
8704
+ const statusFromQuery = this.$route.query.status;
8705
+ if (statusFromQuery) params.status = statusFromQuery;
8706
+ this._lastQueryFilters = `${dateRange}|${statusFromQuery ?? ''}`;
8468
8707
  try {
8469
- const { tasks } = await api.Task.getTasks({
8470
- name: this.taskName,
8471
- start,
8472
- end
8473
- });
8708
+ const { tasks, numDocs, statusCounts } = await api.Task.getTasks(params);
8709
+ if (loadId !== this._loadId) return;
8710
+ this.numDocs = numDocs ?? tasks.length;
8474
8711
  this.taskGroup = buildTaskGroup(this.taskName, tasks);
8712
+ this.taskGroup.totalCount = this.numDocs;
8713
+ if (statusCounts) this.taskGroup.statusCounts = statusCounts;
8475
8714
  this.status = 'loaded';
8476
8715
  } catch (err) {
8716
+ if (loadId !== this._loadId) return;
8477
8717
  this.status = 'error';
8478
8718
  this.errorMessage = err?.response?.data?.message || err.message || 'Failed to load tasks';
8479
8719
  }
@@ -8636,7 +8876,8 @@ const PIE_HOVER = ['#ca8a04', '#16a34a', '#dc2626', '#4b5563'];
8636
8876
  module.exports = app => app.component('task-details', {
8637
8877
  props: {
8638
8878
  taskGroup: { type: Object, required: true },
8639
- backTo: { type: Object, default: null }
8879
+ backTo: { type: Object, default: null },
8880
+ showBackButton: { type: Boolean, default: true }
8640
8881
  },
8641
8882
  data: () => ({
8642
8883
  currentFilter: null,
@@ -8977,25 +9218,17 @@ module.exports = app => app.component('task-details', {
8977
9218
 
8978
9219
  const template = __webpack_require__(/*! ./tasks.html */ "./frontend/src/tasks/tasks.html");
8979
9220
  const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
9221
+ const { DATE_FILTERS, getDateRangeForRange } = __webpack_require__(/*! ../_util/dateRange */ "./frontend/src/_util/dateRange.js");
8980
9222
 
8981
9223
  module.exports = app => app.component('tasks', {
8982
9224
  data: () => ({
8983
9225
  status: 'init',
8984
- tasks: [],
8985
- groupedTasks: {},
9226
+ statusCounts: { pending: 0, succeeded: 0, failed: 0, cancelled: 0 },
9227
+ tasksByName: [],
8986
9228
  selectedRange: 'last_hour',
8987
9229
  start: null,
8988
9230
  end: null,
8989
- dateFilters: [
8990
- { value: 'all', label: 'All Time' },
8991
- { value: 'last_hour', label: 'Last Hour' },
8992
- { value: 'today', label: 'Today' },
8993
- { value: 'yesterday', label: 'Yesterday' },
8994
- { value: 'thisWeek', label: 'This Week' },
8995
- { value: 'lastWeek', label: 'Last Week' },
8996
- { value: 'thisMonth', label: 'This Month' },
8997
- { value: 'lastMonth', label: 'Last Month' }
8998
- ],
9231
+ dateFilters: DATE_FILTERS,
8999
9232
  selectedStatus: 'all',
9000
9233
  statusFilters: [
9001
9234
  { label: 'All', value: 'all' },
@@ -9025,26 +9258,31 @@ module.exports = app => app.component('tasks', {
9025
9258
  params.status = this.selectedStatus;
9026
9259
  }
9027
9260
 
9028
- if (this.start && this.end) {
9029
- params.start = this.start;
9030
- params.end = this.end;
9031
- } else if (this.start) {
9032
- params.start = this.start;
9261
+ if (this.start != null && this.end != null) {
9262
+ params.start = this.start instanceof Date ? this.start.toISOString() : this.start;
9263
+ params.end = this.end instanceof Date ? this.end.toISOString() : this.end;
9264
+ } else if (this.start != null) {
9265
+ params.start = this.start instanceof Date ? this.start.toISOString() : this.start;
9033
9266
  }
9034
9267
 
9035
9268
  if (this.searchQuery.trim()) {
9036
9269
  params.name = this.searchQuery.trim();
9037
9270
  }
9038
9271
 
9039
- const { tasks, groupedTasks } = await api.Task.getTasks(params);
9040
- this.tasks = tasks;
9041
- this.groupedTasks = groupedTasks;
9272
+ const { statusCounts, tasksByName } = await api.Task.getTaskOverview(params);
9273
+ this.statusCounts = statusCounts || this.statusCounts;
9274
+ this.tasksByName = tasksByName || [];
9042
9275
  },
9043
9276
  openTaskGroupDetails(group) {
9044
- this.$router.push({ path: `/tasks/${encodeURIComponent(group.name || '')}` });
9277
+ const query = { dateRange: this.selectedRange || 'last_hour' };
9278
+ if (this.selectedStatus && this.selectedStatus !== 'all') query.status = this.selectedStatus;
9279
+ this.$router.push({ path: `/tasks/${encodeURIComponent(group.name || '')}`, query });
9045
9280
  },
9046
9281
  openTaskGroupDetailsWithFilter(group, status) {
9047
- this.$router.push({ path: `/tasks/${encodeURIComponent(group.name || '')}`, query: status ? { status } : {} });
9282
+ const query = { dateRange: this.selectedRange || 'last_hour' };
9283
+ if (status) query.status = status;
9284
+ else if (this.selectedStatus && this.selectedStatus !== 'all') query.status = this.selectedStatus;
9285
+ this.$router.push({ path: `/tasks/${encodeURIComponent(group.name || '')}`, query });
9048
9286
  },
9049
9287
  async onTaskCreated() {
9050
9288
  // Refresh the task data when a new task is created
@@ -9188,114 +9426,24 @@ module.exports = app => app.component('tasks', {
9188
9426
  }, 300);
9189
9427
  },
9190
9428
  async updateDateRange() {
9191
- const now = new Date();
9192
- let start, end;
9193
-
9194
- switch (this.selectedRange) {
9195
- case 'last_hour':
9196
- start = new Date();
9197
- start.setHours(start.getHours() - 1);
9198
- end = new Date();
9199
- break;
9200
- case 'today':
9201
- start = new Date();
9202
- start.setHours(0, 0, 0, 0);
9203
- end = new Date();
9204
- end.setHours(23, 59, 59, 999);
9205
- break;
9206
- case 'yesterday':
9207
- start = new Date();
9208
- start.setDate(start.getDate() - 1);
9209
- start.setHours(0, 0, 0, 0);
9210
- end = new Date();
9211
- break;
9212
- case 'thisWeek':
9213
- start = new Date(now.getTime() - (7 * 86400000));
9214
- start.setHours(0, 0, 0, 0);
9215
- end = new Date();
9216
- end.setHours(23, 59, 59, 999);
9217
- break;
9218
- case 'lastWeek':
9219
- start = new Date(now.getTime() - (14 * 86400000));
9220
- start.setHours(0, 0, 0, 0);
9221
- end = new Date(now.getTime() - (7 * 86400000));
9222
- end.setHours(23, 59, 59, 999);
9223
- break;
9224
- case 'thisMonth':
9225
- start = new Date(now.getFullYear(), now.getMonth(), 1);
9226
- end = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
9227
- break;
9228
- case 'lastMonth':
9229
- start = new Date(now.getFullYear(), now.getMonth() - 1, 1);
9230
- end = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);
9231
- break;
9232
- case 'all':
9233
- default:
9234
- this.start = null;
9235
- this.end = null;
9236
- break;
9237
- }
9238
-
9429
+ const { start, end } = getDateRangeForRange(this.selectedRange);
9239
9430
  this.start = start;
9240
9431
  this.end = end;
9241
-
9242
9432
  await this.getTasks();
9243
9433
  }
9244
9434
  },
9245
9435
  computed: {
9246
- tasksByName() {
9247
- const groups = {};
9248
-
9249
- // Process tasks from groupedTasks to create name-based groups
9250
- Object.entries(this.groupedTasks).forEach(([status, tasks]) => {
9251
- tasks.forEach(task => {
9252
- if (!groups[task.name]) {
9253
- groups[task.name] = {
9254
- name: task.name,
9255
- tasks: [],
9256
- statusCounts: {
9257
- pending: 0,
9258
- succeeded: 0,
9259
- failed: 0,
9260
- cancelled: 0
9261
- },
9262
- totalCount: 0,
9263
- lastRun: null
9264
- };
9265
- }
9266
-
9267
- groups[task.name].tasks.push(task);
9268
- groups[task.name].totalCount++;
9269
-
9270
- // Count status using the status from groupedTasks
9271
- if (groups[task.name].statusCounts.hasOwnProperty(status)) {
9272
- groups[task.name].statusCounts[status]++;
9273
- }
9274
-
9275
- // Track last run time
9276
- const taskTime = new Date(task.scheduledAt || task.createdAt || 0);
9277
- if (!groups[task.name].lastRun || taskTime > new Date(groups[task.name].lastRun)) {
9278
- groups[task.name].lastRun = taskTime;
9279
- }
9280
- });
9281
- });
9282
-
9283
- // Convert to array and sort alphabetically by name
9284
- return Object.values(groups).sort((a, b) => {
9285
- return a.name.localeCompare(b.name);
9286
- });
9436
+ pendingCount() {
9437
+ return this.statusCounts.pending || 0;
9287
9438
  },
9288
9439
  succeededCount() {
9289
- return this.groupedTasks.succeeded ? this.groupedTasks.succeeded.length : 0;
9440
+ return this.statusCounts.succeeded || 0;
9290
9441
  },
9291
9442
  failedCount() {
9292
- return this.groupedTasks.failed ? this.groupedTasks.failed.length : 0;
9443
+ return this.statusCounts.failed || 0;
9293
9444
  },
9294
9445
  cancelledCount() {
9295
- return this.groupedTasks.cancelled ? this.groupedTasks.cancelled.length : 0;
9296
- },
9297
- pendingCount() {
9298
- return this.groupedTasks.pending ? this.groupedTasks.pending.length : 0;
9446
+ return this.statusCounts.cancelled || 0;
9299
9447
  }
9300
9448
  },
9301
9449
  mounted: async function() {
@@ -9527,6 +9675,8 @@ var map = {
9527
9675
  "./": "./frontend/src/index.js",
9528
9676
  "./_util/baseComponent": "./frontend/src/_util/baseComponent.js",
9529
9677
  "./_util/baseComponent.js": "./frontend/src/_util/baseComponent.js",
9678
+ "./_util/dateRange": "./frontend/src/_util/dateRange.js",
9679
+ "./_util/dateRange.js": "./frontend/src/_util/dateRange.js",
9530
9680
  "./_util/deepEqual": "./frontend/src/_util/deepEqual.js",
9531
9681
  "./_util/deepEqual.js": "./frontend/src/_util/deepEqual.js",
9532
9682
  "./_util/document-search-autocomplete": "./frontend/src/_util/document-search-autocomplete.js",
@@ -9586,6 +9736,9 @@ var map = {
9586
9736
  "./dashboard-result/dashboard-result": "./frontend/src/dashboard-result/dashboard-result.js",
9587
9737
  "./dashboard-result/dashboard-result.html": "./frontend/src/dashboard-result/dashboard-result.html",
9588
9738
  "./dashboard-result/dashboard-result.js": "./frontend/src/dashboard-result/dashboard-result.js",
9739
+ "./dashboard-result/dashboard-table/dashboard-table": "./frontend/src/dashboard-result/dashboard-table/dashboard-table.js",
9740
+ "./dashboard-result/dashboard-table/dashboard-table.html": "./frontend/src/dashboard-result/dashboard-table/dashboard-table.html",
9741
+ "./dashboard-result/dashboard-table/dashboard-table.js": "./frontend/src/dashboard-result/dashboard-table/dashboard-table.js",
9589
9742
  "./dashboard-result/dashboard-text/dashboard-text": "./frontend/src/dashboard-result/dashboard-text/dashboard-text.js",
9590
9743
  "./dashboard-result/dashboard-text/dashboard-text.html": "./frontend/src/dashboard-result/dashboard-text/dashboard-text.html",
9591
9744
  "./dashboard-result/dashboard-text/dashboard-text.js": "./frontend/src/dashboard-result/dashboard-text/dashboard-text.js",
@@ -49468,7 +49621,7 @@ module.exports = "<button v-bind=\"attrsToBind\" :disabled=\"isDisabled\" @click
49468
49621
  (module) {
49469
49622
 
49470
49623
  "use strict";
49471
- module.exports = "<div class=\"chat-message-script relative border rounded bg-surface my-1 text-content text-sm overflow-hidden\">\n <div class=\"flex border-b py-1 pl-1 text-xs font-medium bg-surface\">\n <button\n class=\"px-4 py-1 border-r border-edge-strong text-content-secondary font-semibold transition-colors duration-200 focus:outline-none\"\n :class=\"[\n 'rounded-l-md',\n activeTab === 'code'\n ? 'bg-gray-700 text-white shadow'\n : 'bg-muted hover:bg-muted text-gray-600'\n ]\"\n @click=\"activeTab = 'code'\">\n Code\n </button>\n <button\n class=\"px-4 py-1 text-content-secondary font-semibold transition-colors duration-200 focus:outline-none\"\n :class=\"[\n 'rounded-r-md',\n activeTab === 'output'\n ? 'bg-gray-700 text-white shadow'\n : 'bg-muted hover:bg-muted text-gray-600'\n ]\"\n @click=\"activeTab = 'output'\">\n Output\n </button>\n <div class=\"ml-auto mr-1 flex items-center\">\n <button\n v-if=\"activeTab === 'output' && isChartOutput\"\n class=\"px-2 py-1 mr-1 text-xs bg-gray-700 text-white border-none rounded cursor-pointer hover:bg-primary-hover transition-colors flex items-center\"\n @click=\"exportChartPNG\"\n title=\"Export PNG\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"height: 1.2em;\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M280-280h400v-80H280v80Zm200-120 160-160-56-56-64 62v-166h-80v166l-64-62-56 56 160 160Zm0 320q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z\"/></svg>\n </button>\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-gray-700 text-white border-none rounded cursor-pointer hover:bg-primary-hover transition-colors flex items-center\"\n @click=\"openDetailModal\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"height: 1.2em;\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5\" />\n </svg>\n </button>\n <button\n v-if=\"activeTab === 'code' && !isEditing\"\n class=\"px-2 py-1 mr-1 text-xs bg-page0 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center\"\n @click.stop=\"startEditing\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM4 13.5V16h2.5l7.086-7.086-2.828-2.828L4 13.5z\" />\n </svg>\n </button>\n <async-button\n v-if=\"!isEditing\"\n class=\"px-2 py-1 text-xs bg-primary hover:bg-primary-hover text-primary-text border-none rounded cursor-pointer transition-colors disabled:bg-gray-400\"\n @click=\"executeScript\">\n Run\n </async-button>\n <div class=\"relative ml-1\" ref=\"dropdown\">\n <button\n @click.stop=\"toggleDropdown\"\n class=\"px-1 py-1 text-xs hover:bg-gray-300 rounded flex items-center\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4z\" />\n </svg>\n </button>\n <div\n v-if=\"showDropdown\"\n class=\"absolute right-0 z-10 mt-1 w-64 origin-top-right rounded-md bg-surface py-1 shadow-lg ring-1 ring-black/5\">\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted\"\n @click=\"openCreateDashboardModal(); showDropdown = false\">\n Create Dashboard\n </button>\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted\"\n @click=\"copyOutput(); showDropdown = false\">\n Copy Output As Text\n </button>\n <button\n v-if=\"canOverwriteDashboard\"\n class=\"block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted\"\n @click=\"openOverwriteDashboardConfirmation(); showDropdown = false\">\n Overwrite Dashboard\n </button>\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted\"\n @click=\"$emit('copyMessage'); showDropdown = false\">\n Copy Full Message\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"p-0 max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto\" v-show=\"activeTab === 'code'\">\n <div v-if=\"isEditing\" class=\"flex flex-col space-y-2 chat-script-editor-wrap\">\n <div class=\"border border-edge\">\n <ace-editor\n v-model=\"editedScript\"\n mode=\"javascript\"\n :line-numbers=\"true\"\n class=\"w-full h-[45vh] min-h-[200px]\"\n />\n </div>\n <div class=\"flex justify-end gap-2 pb-2\">\n <button\n class=\"px-2 py-1 text-xs bg-gray-300 text-gray-800 border-none rounded cursor-pointer hover:bg-gray-400 transition-colors\"\n @click.stop=\"cancelEditing\">\n Cancel\n </button>\n <async-button\n class=\"px-2 py-1 text-xs bg-blue-600 text-white border-none rounded cursor-pointer hover:bg-blue-700 transition-colors disabled:bg-gray-400\"\n @click=\"executeScript\">\n Execute\n </async-button>\n </div>\n </div>\n <pre v-else class=\"whitespace-pre-wrap !my-0 bg-muted\"><code v-text=\"script\" ref=\"code\" :class=\"'language-' + language\"></code></pre>\n </div>\n\n <div class=\"p-3 whitespace-pre-wrap max-h-[50vh] overflow-y-auto bg-surface border-t max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] relative\" v-show=\"activeTab === 'output'\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" ref=\"chartOutput\" />\n <dashboard-map v-else-if=\"message.executionResult?.output?.$featureCollection\" :value=\"message.executionResult?.output\" />\n <pre v-else>{{ message.executionResult?.output || 'No output' }}</pre>\n\n <div v-if=\"message.executionResult?.logs?.length\" class=\"mt-3 pt-3 border-t border-edge\">\n <div class=\"text-xs font-semibold text-gray-600 uppercase tracking-wide\">Console</div>\n <pre class=\"mt-1 bg-muted text-content p-3 rounded whitespace-pre-wrap overflow-x-auto max-h-[280px]\">{{ message.executionResult.logs }}</pre>\n </div>\n </div>\n\n <modal ref=\"outputModal\" v-if=\"showDetailModal\" containerClass=\"!h-[90vh] !w-[90vw]\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\">&times;</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-chart v-if=\"message.executionResult?.output?.$chart\" :value=\"message.executionResult?.output\" :responsive=\"true\" />\n <dashboard-map\n v-else-if=\"message.executionResult?.output?.$featureCollection\"\n :value=\"message.executionResult?.output\"\n height=\"80vh\" />\n <pre v-else class=\"whitespace-pre-wrap\">{{ message.executionResult?.output || 'No output' }}</pre>\n </div>\n </template>\n </modal>\n <modal v-if=\"showCreateDashboardModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false\">&times;</div>\n <div>\n <div class=\"mt-4 text-content font-semibold\">Create Dashboard</div>\n <div class=\"mt-4\">\n <label class=\"block text-sm font-medium leading-6 text-content\">Title</label>\n <div class=\"mt-2\">\n <div class=\"w-full flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-teal-600\">\n <input type=\"text\" v-model=\"newDashboardTitle\" class=\"outline-none block flex-1 border-0 bg-transparent py-1.5 pl-1 text-content placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6\" placeholder=\"My Dashboard\">\n </div>\n </div>\n </div>\n <div class=\"my-4\">\n <label class=\"block text-sm font-medium leading-6 text-content\">Code</label>\n <div class=\"border border-edge\">\n <ace-editor\n v-model=\"dashboardCode\"\n mode=\"javascript\"\n :line-numbers=\"true\"\n class=\"h-[300px] w-full\"\n />\n </div>\n </div>\n <async-button\n @click=\"createDashboardFromScript\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n Submit\n </async-button>\n <div v-if=\"createError\" class=\"rounded-md bg-red-50 p-4 mt-1\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">Error</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n {{createError}}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"showOverwriteDashboardConfirmationModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showOverwriteDashboardConfirmationModal = false\">&times;</div>\n <div>\n <div class=\"mt-4 text-content font-semibold\">Overwrite Dashboard</div>\n <p class=\"mt-2 text-sm text-content-secondary\">\n This will replace the linked dashboard's code with the script below.\n </p>\n <p class=\"mt-1 text-xs text-gray-600 break-all\" v-if=\"targetDashboardId\">\n Dashboard ID: {{ targetDashboardId }}\n </p>\n <div class=\"my-4 border border-edge bg-page rounded\">\n <pre class=\"p-2 h-[300px] overflow-auto whitespace-pre-wrap text-xs\">{{ overwriteDashboardCode }}</pre>\n </div>\n <div class=\"flex items-center gap-2\">\n <async-button\n @click=\"confirmOverwriteDashboard\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n Confirm Overwrite\n </async-button>\n <button\n class=\"px-2.5 py-1.5 rounded-md text-sm font-semibold text-content-secondary bg-gray-200 hover:bg-gray-300\"\n @click=\"showOverwriteDashboardConfirmationModal = false\">\n Cancel\n </button>\n </div>\n <div v-if=\"overwriteError\" class=\"rounded-md bg-red-50 p-4 mt-3\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">Error</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n {{overwriteError}}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n</div>\n";
49624
+ module.exports = "<div class=\"chat-message-script relative border rounded bg-surface my-1 text-content text-sm overflow-hidden\">\n <div class=\"flex border-b py-1 pl-1 text-xs font-medium bg-surface\">\n <button\n class=\"px-4 py-1 border-r border-edge-strong text-content-secondary font-semibold transition-colors duration-200 focus:outline-none\"\n :class=\"[\n 'rounded-l-md',\n activeTab === 'code'\n ? 'bg-gray-700 text-white shadow'\n : 'bg-muted hover:bg-muted text-gray-600'\n ]\"\n @click=\"activeTab = 'code'\">\n Code\n </button>\n <button\n class=\"px-4 py-1 text-content-secondary font-semibold transition-colors duration-200 focus:outline-none\"\n :class=\"[\n 'rounded-r-md',\n activeTab === 'output'\n ? 'bg-gray-700 text-white shadow'\n : 'bg-muted hover:bg-muted text-gray-600'\n ]\"\n @click=\"activeTab = 'output'\">\n Output\n </button>\n <div class=\"ml-auto mr-1 flex items-center\">\n <button\n v-if=\"activeTab === 'output'\"\n class=\"px-2 py-1 mr-1 text-xs bg-gray-700 text-white border-none rounded cursor-pointer hover:bg-primary-hover transition-colors flex items-center\"\n @click=\"openDetailModal\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"height: 1.2em;\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 1v4m0 0h-4m4 0l-5-5\" />\n </svg>\n </button>\n <button\n v-if=\"activeTab === 'code' && !isEditing\"\n class=\"px-2 py-1 mr-1 text-xs bg-page0 text-white border-none rounded cursor-pointer hover:bg-gray-600 transition-colors flex items-center\"\n @click.stop=\"startEditing\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-3 w-3\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM4 13.5V16h2.5l7.086-7.086-2.828-2.828L4 13.5z\" />\n </svg>\n </button>\n <async-button\n v-if=\"!isEditing\"\n class=\"px-2 py-1 text-xs bg-primary hover:bg-primary-hover text-primary-text border-none rounded cursor-pointer transition-colors disabled:bg-gray-400\"\n @click=\"executeScript\">\n Run\n </async-button>\n <div class=\"relative ml-1\" ref=\"dropdown\">\n <button\n @click.stop=\"toggleDropdown\"\n class=\"px-1 py-1 text-xs hover:bg-gray-300 rounded flex items-center\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M10 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4z\" />\n </svg>\n </button>\n <div\n v-if=\"showDropdown\"\n class=\"absolute right-0 z-10 mt-1 w-64 origin-top-right rounded-md bg-surface py-1 shadow-lg ring-1 ring-black/5\">\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted\"\n @click=\"openCreateDashboardModal(); showDropdown = false\">\n Create Dashboard\n </button>\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted\"\n @click=\"copyOutput(); showDropdown = false\">\n Copy Output As Text\n </button>\n <button\n v-if=\"canOverwriteDashboard\"\n class=\"block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted\"\n @click=\"openOverwriteDashboardConfirmation(); showDropdown = false\">\n Overwrite Dashboard\n </button>\n <button\n class=\"block w-full text-left px-4 py-2 text-xs text-content-secondary hover:bg-muted\"\n @click=\"$emit('copyMessage'); showDropdown = false\">\n Copy Full Message\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"p-0 max-h-[50vh] max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] overflow-y-auto\" v-show=\"activeTab === 'code'\">\n <div v-if=\"isEditing\" class=\"flex flex-col space-y-2 chat-script-editor-wrap\">\n <div class=\"border border-edge\">\n <ace-editor\n v-model=\"editedScript\"\n mode=\"javascript\"\n :line-numbers=\"true\"\n class=\"w-full h-[45vh] min-h-[200px]\"\n />\n </div>\n <div class=\"flex justify-end gap-2 pb-2\">\n <button\n class=\"px-2 py-1 text-xs bg-gray-300 text-gray-800 border-none rounded cursor-pointer hover:bg-gray-400 transition-colors\"\n @click.stop=\"cancelEditing\">\n Cancel\n </button>\n <async-button\n class=\"px-2 py-1 text-xs bg-blue-600 text-white border-none rounded cursor-pointer hover:bg-blue-700 transition-colors disabled:bg-gray-400\"\n @click=\"executeScript\">\n Execute\n </async-button>\n </div>\n </div>\n <pre v-else class=\"whitespace-pre-wrap !my-0 bg-muted\"><code v-text=\"script\" ref=\"code\" :class=\"'language-' + language\"></code></pre>\n </div>\n\n <div class=\"p-3 whitespace-pre-wrap max-h-[50vh] overflow-y-auto bg-surface border-t max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] relative\" v-show=\"activeTab === 'output'\">\n <dashboard-result\n v-if=\"message.executionResult?.output != null\"\n :result=\"message.executionResult.output\">\n </dashboard-result>\n <pre v-else>No output</pre>\n\n <div v-if=\"message.executionResult?.logs?.length\" class=\"mt-3 pt-3 border-t border-edge\">\n <div class=\"text-xs font-semibold text-gray-600 uppercase tracking-wide\">Console</div>\n <pre class=\"mt-1 bg-muted text-content p-3 rounded whitespace-pre-wrap overflow-x-auto max-h-[280px]\">{{ message.executionResult.logs }}</pre>\n </div>\n </div>\n\n <modal ref=\"outputModal\" v-if=\"showDetailModal\" containerClass=\"!h-[90vh] !w-[90vw]\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showDetailModal = false;\">&times;</div>\n <div class=\"h-full overflow-auto\">\n <dashboard-result\n v-if=\"message.executionResult?.output != null\"\n :result=\"message.executionResult.output\"\n :fullscreen=\"true\">\n </dashboard-result>\n <pre v-else class=\"whitespace-pre-wrap\">No output</pre>\n </div>\n </template>\n </modal>\n <modal v-if=\"showCreateDashboardModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false\">&times;</div>\n <div>\n <div class=\"mt-4 text-content font-semibold\">Create Dashboard</div>\n <div class=\"mt-4\">\n <label class=\"block text-sm font-medium leading-6 text-content\">Title</label>\n <div class=\"mt-2\">\n <div class=\"w-full flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-teal-600\">\n <input type=\"text\" v-model=\"newDashboardTitle\" class=\"outline-none block flex-1 border-0 bg-transparent py-1.5 pl-1 text-content placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6\" placeholder=\"My Dashboard\">\n </div>\n </div>\n </div>\n <div class=\"my-4\">\n <label class=\"block text-sm font-medium leading-6 text-content\">Code</label>\n <div class=\"border border-edge\">\n <ace-editor\n v-model=\"dashboardCode\"\n mode=\"javascript\"\n :line-numbers=\"true\"\n class=\"h-[300px] w-full\"\n />\n </div>\n </div>\n <async-button\n @click=\"createDashboardFromScript\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n Submit\n </async-button>\n <div v-if=\"createError\" class=\"rounded-md bg-red-50 p-4 mt-1\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">Error</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n {{createError}}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"showOverwriteDashboardConfirmationModal\">\n <template #body>\n <div class=\"modal-exit\" @click=\"showOverwriteDashboardConfirmationModal = false\">&times;</div>\n <div>\n <div class=\"mt-4 text-content font-semibold\">Overwrite Dashboard</div>\n <p class=\"mt-2 text-sm text-content-secondary\">\n This will replace the linked dashboard's code with the script below.\n </p>\n <p class=\"mt-1 text-xs text-gray-600 break-all\" v-if=\"targetDashboardId\">\n Dashboard ID: {{ targetDashboardId }}\n </p>\n <div class=\"my-4 border border-edge bg-page rounded\">\n <pre class=\"p-2 h-[300px] overflow-auto whitespace-pre-wrap text-xs\">{{ overwriteDashboardCode }}</pre>\n </div>\n <div class=\"flex items-center gap-2\">\n <async-button\n @click=\"confirmOverwriteDashboard\"\n class=\"rounded-md bg-teal-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-teal-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n Confirm Overwrite\n </async-button>\n <button\n class=\"px-2.5 py-1.5 rounded-md text-sm font-semibold text-content-secondary bg-gray-200 hover:bg-gray-300\"\n @click=\"showOverwriteDashboardConfirmationModal = false\">\n Cancel\n </button>\n </div>\n <div v-if=\"overwriteError\" class=\"rounded-md bg-red-50 p-4 mt-3\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">Error</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n {{overwriteError}}\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n</div>\n";
49472
49625
 
49473
49626
  /***/ },
49474
49627
 
@@ -49626,6 +49779,17 @@ module.exports = "<div>\n <div v-if=\"Array.isArray(result)\">\n <div v-for=
49626
49779
 
49627
49780
  /***/ },
49628
49781
 
49782
+ /***/ "./frontend/src/dashboard-result/dashboard-table/dashboard-table.html"
49783
+ /*!****************************************************************************!*\
49784
+ !*** ./frontend/src/dashboard-result/dashboard-table/dashboard-table.html ***!
49785
+ \****************************************************************************/
49786
+ (module) {
49787
+
49788
+ "use strict";
49789
+ module.exports = "<div class=\"overflow-x-auto\">\n <table class=\"min-w-full border-separate border-spacing-0\">\n <thead v-if=\"hasColumns\" class=\"bg-slate-50\">\n <tr>\n <th\n v-for=\"(column, index) in columns\"\n :key=\"'column-' + index\"\n class=\"bg-slate-50 p-3 text-left text-sm font-semibold text-content border-b border-edge\"\n >\n {{ column }}\n </th>\n </tr>\n </thead>\n <tbody>\n <tr v-for=\"(row, rowIndex) in rows\" :key=\"'row-' + rowIndex\" class=\"bg-surface hover:bg-slate-50\">\n <td\n v-for=\"(cell, columnIndex) in row\"\n :key=\"'cell-' + rowIndex + '-' + columnIndex\"\n class=\"p-3 text-sm text-content border-b border-edge\"\n >\n {{ displayValue(cell) }}\n </td>\n </tr>\n <tr v-if=\"!hasRows\">\n <td\n :colspan=\"Math.max(columns.length, 1)\"\n class=\"p-3 text-sm text-content-tertiary border-b border-edge\"\n >\n No rows\n </td>\n </tr>\n </tbody>\n </table>\n</div>\n";
49790
+
49791
+ /***/ },
49792
+
49629
49793
  /***/ "./frontend/src/dashboard-result/dashboard-text/dashboard-text.html"
49630
49794
  /*!**************************************************************************!*\
49631
49795
  !*** ./frontend/src/dashboard-result/dashboard-text/dashboard-text.html ***!
@@ -50139,7 +50303,7 @@ module.exports = "<div class=\"w-full h-full flex items-center justify-center\">
50139
50303
  (module) {
50140
50304
 
50141
50305
  "use strict";
50142
- 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";
50306
+ module.exports = "<div class=\"p-4 space-y-6\">\n <div v-if=\"status === 'init'\">\n <img src=\"images/loader.gif\" alt=\"Loading\" />\n </div>\n <div v-else-if=\"status === 'error'\" class=\"text-red-600\">\n {{ errorMessage }}\n </div>\n <template v-else-if=\"taskGroup\">\n <div class=\"pb-24\">\n <router-link :to=\"{ name: 'tasks' }\" class=\"inline-flex items-center gap-1 text-gray-500 hover:text-gray-700 mb-4\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 19l-7-7 7-7\"></path>\n </svg>\n Back to Task Groups\n </router-link>\n <div class=\"mb-4\">\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Filter by Date:</label>\n <select v-model=\"selectedRange\" @change=\"updateDateRange\" class=\"border-gray-300 rounded-md shadow-sm w-full p-2 max-w-xs\">\n <option v-for=\"option in dateFilters\" :key=\"option.value\" :value=\"option.value\">\n {{ option.label }}\n </option>\n </select>\n </div>\n <task-details\n :task-group=\"taskGroup\"\n :back-to=\"{ name: 'tasks' }\"\n :show-back-button=\"false\"\n @task-created=\"onTaskCreated\"\n @task-cancelled=\"onTaskCancelled\"\n ></task-details>\n </div>\n <div\n v-if=\"numDocs > 0\"\n class=\"fixed bottom-0 left-0 right-0 z-10 px-4 py-4 bg-white border-t border-gray-200 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)]\"\n >\n <div class=\"flex flex-wrap items-center justify-between gap-4 max-w-6xl mx-auto\">\n <div class=\"flex items-center gap-6\">\n <p class=\"text-sm text-gray-600\">\n <span class=\"font-medium text-gray-900\">{{ Math.min((page - 1) * pageSize + 1, numDocs) }}–{{ Math.min(page * pageSize, numDocs) }}</span>\n <span class=\"mx-1\">of</span>\n <span class=\"font-medium text-gray-900\">{{ numDocs }}</span>\n <span class=\"ml-1 text-gray-500\">tasks</span>\n </p>\n <div class=\"flex items-center gap-2\">\n <label class=\"text-sm font-medium text-gray-700\">Per page</label>\n <select\n v-model.number=\"pageSize\"\n @change=\"onPageSizeChange\"\n class=\"border border-gray-300 rounded-md shadow-sm px-3 py-2 text-sm text-gray-700 bg-white hover:border-gray-400 focus:outline-none focus:ring-2 focus:ring-ultramarine-500 focus:border-ultramarine-500\"\n >\n <option v-for=\"n in pageSizeOptions\" :key=\"n\" :value=\"n\">{{ n }}</option>\n </select>\n </div>\n </div>\n <div class=\"flex items-center gap-1\">\n <button\n type=\"button\"\n :disabled=\"page <= 1\"\n @click=\"goToPage(page - 1)\"\n class=\"inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium rounded-md border transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-white bg-white text-gray-700 border-gray-300 hover:bg-gray-50 hover:border-gray-400\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 19l-7-7 7-7\"></path>\n </svg>\n Previous\n </button>\n <span class=\"px-4 py-2 text-sm text-gray-600 min-w-[7rem] text-center\">\n Page <span class=\"font-semibold text-gray-900\">{{ page }}</span> of <span class=\"font-semibold text-gray-900\">{{ Math.max(1, Math.ceil(numDocs / pageSize)) }}</span>\n </span>\n <button\n type=\"button\"\n :disabled=\"page >= Math.ceil(numDocs / pageSize)\"\n @click=\"goToPage(page + 1)\"\n class=\"inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium rounded-md border transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-white bg-white text-gray-700 border-gray-300 hover:bg-gray-50 hover:border-gray-400\"\n >\n Next\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n </button>\n </div>\n </div>\n </div>\n </template>\n</div>\n";
50143
50307
 
50144
50308
  /***/ },
50145
50309
 
@@ -50161,7 +50325,7 @@ module.exports = "<div class=\"p-4 space-y-6\">\n <div v-if=\"status === 'init'
50161
50325
  (module) {
50162
50326
 
50163
50327
  "use strict";
50164
- 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-content-tertiary hover:text-content-secondary mb-2\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 19l-7-7 7-7\"></path>\n </svg>\n {{ backLabel }}\n </button>\n <h1 class=\"text-2xl font-bold text-content-secondary\">{{ taskGroup.name }}</h1>\n <p class=\"text-content-tertiary\">Total: {{ taskGroup.totalCount }} tasks</p>\n </div>\n\n </div>\n\n <!-- Status Summary -->\n <div class=\"space-y-3\">\n <div class=\"flex items-center justify-between\">\n <span class=\"text-sm font-medium text-content-secondary\">Status</span>\n <div class=\"flex rounded-md shadow-sm\" role=\"group\">\n <button\n type=\"button\"\n @click=\"statusView = 'summary'\"\n class=\"px-3 py-1.5 text-sm font-medium rounded-l-md border transition-colors\"\n :class=\"statusView === 'summary' ? 'bg-primary text-primary-text border-primary' : 'bg-surface text-content-secondary border-edge-strong hover:bg-page'\"\n >\n Summary\n </button>\n <button\n type=\"button\"\n @click=\"statusView = 'chart'\"\n class=\"px-3 py-1.5 text-sm font-medium rounded-r-md border border-l-0 transition-colors\"\n :class=\"statusView === 'chart' ? 'bg-primary text-primary-text border-primary' : 'bg-surface text-content-secondary border-edge-strong hover:bg-page'\"\n >\n Chart\n </button>\n </div>\n </div>\n <!-- Summary view -->\n <div v-show=\"statusView === 'summary'\" class=\"grid grid-cols-2 sm:grid-cols-4 gap-4\">\n <button \n @click=\"filterByStatus('pending')\"\n class=\"bg-yellow-50 border border-yellow-200 rounded-md p-3 text-center hover:bg-yellow-100 transition-colors cursor-pointer\"\n :class=\"{ 'ring-2 ring-yellow-400': currentFilter === 'pending' }\"\n >\n <div class=\"text-xs text-yellow-600 font-medium\">Pending</div>\n <div class=\"text-lg font-bold text-yellow-700\">{{ taskGroup.statusCounts.pending || 0 }}</div>\n </button>\n <button \n @click=\"filterByStatus('succeeded')\"\n class=\"bg-green-50 border border-green-200 rounded-md p-3 text-center hover:bg-green-100 transition-colors cursor-pointer\"\n :class=\"{ 'ring-2 ring-green-400': currentFilter === 'succeeded' }\"\n >\n <div class=\"text-xs text-green-600 font-medium\">Succeeded</div>\n <div class=\"text-lg font-bold text-green-700\">{{ taskGroup.statusCounts.succeeded || 0 }}</div>\n </button>\n <button \n @click=\"filterByStatus('failed')\"\n class=\"bg-red-50 border border-red-200 rounded-md p-3 text-center hover:bg-red-100 transition-colors cursor-pointer\"\n :class=\"{ 'ring-2 ring-red-400': currentFilter === 'failed' }\"\n >\n <div class=\"text-xs text-red-600 font-medium\">Failed</div>\n <div class=\"text-lg font-bold text-red-700\">{{ taskGroup.statusCounts.failed || 0 }}</div>\n </button>\n <button \n @click=\"filterByStatus('cancelled')\"\n class=\"bg-page border border-edge rounded-md p-3 text-center hover:bg-muted transition-colors cursor-pointer\"\n :class=\"{ 'ring-2 ring-gray-400': currentFilter === 'cancelled' }\"\n >\n <div class=\"text-xs text-gray-600 font-medium\">Cancelled</div>\n <div class=\"text-lg font-bold text-content-secondary\">{{ taskGroup.statusCounts.cancelled || 0 }}</div>\n </button>\n </div>\n <!-- Chart view -->\n <div v-show=\"statusView === 'chart'\" class=\"flex flex-col items-center justify-center bg-surface border border-edge rounded-lg p-4 gap-3\" style=\"min-height: 280px;\">\n <div v-if=\"taskGroup.totalCount > 0\" class=\"w-[240px] h-[240px] shrink-0\">\n <canvas ref=\"statusPieChart\" width=\"240\" height=\"240\" class=\"block\"></canvas>\n </div>\n <p v-else class=\"text-content-tertiary text-sm py-8\">No tasks to display</p>\n <!-- Selection labels: show which segment is selected (click to filter) -->\n <div v-if=\"taskGroup.totalCount > 0\" class=\"flex flex-wrap justify-center gap-2\">\n <button\n v-for=\"status in statusOrderForDisplay\"\n :key=\"status\"\n type=\"button\"\n class=\"text-xs px-2 py-1 rounded-full font-medium transition-all cursor-pointer\"\n :class=\"currentFilter === status ? getStatusPillClass(status) : 'bg-muted text-content-tertiary hover:bg-muted'\"\n @click=\"filterByStatus(status)\"\n >\n {{ statusLabel(status) }}\n </button>\n </div>\n </div>\n </div>\n\n <!-- Task List -->\n <div class=\"bg-surface rounded-lg shadow\">\n <div class=\"px-6 py-6 border-b border-edge flex items-center justify-between bg-page\">\n <h2 class=\"text-xl font-bold text-content\">\n Individual Tasks\n <span v-if=\"currentFilter\" class=\"ml-3 text-base font-semibold text-primary\">\n (Filtered by {{ currentFilter }})\n </span>\n </h2>\n <button \n v-if=\"currentFilter\"\n @click=\"clearFilter\"\n class=\"text-sm font-semibold text-primary hover:text-primary\"\n >\n Show All\n </button>\n </div>\n <div class=\"divide-y divide-gray-200\">\n <div v-for=\"task in sortedTasks\" :key=\"task.id\" class=\"p-6\">\n <div class=\"flex items-start justify-between\">\n <div class=\"flex-1\">\n <div class=\"flex items-center gap-3 mb-2\">\n <span class=\"text-sm font-medium text-content\">Task ID: {{ task.id }}</span>\n <router-link\n v-if=\"backTo\"\n :to=\"taskDetailRoute(task)\"\n class=\"text-sm text-primary hover:text-primary font-medium\"\n >\n View details\n </router-link>\n <span\n class=\"text-xs px-2 py-1 rounded-full font-medium\"\n :class=\"getStatusColor(task.status)\"\n >\n {{ task.status }}\n </span>\n </div>\n \n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4\">\n <div>\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Scheduled At</label>\n <div class=\"text-sm text-content\">{{ formatDate(task.scheduledAt) }}</div>\n </div>\n <div v-if=\"task.startedAt\">\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Started At</label>\n <div class=\"text-sm text-content\">{{ formatDate(task.startedAt) }}</div>\n </div>\n <div v-if=\"task.completedAt\">\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Completed At</label>\n <div class=\"text-sm text-content\">{{ formatDate(task.completedAt) }}</div>\n </div>\n <div v-if=\"task.error\">\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Error</label>\n <div class=\"text-sm text-red-600\">{{ task.error }}</div>\n </div>\n </div>\n\n <!-- Task Parameters -->\n <div v-if=\"task.parameters && Object.keys(task.parameters).length > 0\">\n <label class=\"block text-sm font-medium text-content-secondary mb-2\">Parameters</label>\n <div class=\"bg-page rounded-md p-3\">\n <pre class=\"text-sm text-gray-800 whitespace-pre-wrap\">{{ JSON.stringify(task.parameters, null, 2) }}</pre>\n </div>\n </div>\n </div>\n \n <div class=\"flex flex-col gap-3 ml-6\">\n <button \n @click=\"showRescheduleConfirmation(task)\"\n class=\"flex items-center justify-center gap-2 bg-gradient-to-r from-blue-500 to-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-blue-600 hover:to-blue-700 transform hover:scale-105 transition-all duration-200 shadow-md hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none\"\n :disabled=\"task.status === 'in_progress'\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\n </svg>\n Reschedule\n </button>\n <button \n @click=\"showRunConfirmation(task)\"\n class=\"flex items-center justify-center gap-2 bg-gradient-to-r from-green-500 to-green-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-green-600 hover:to-green-700 transform hover:scale-105 transition-all duration-200 shadow-md hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none\"\n :disabled=\"task.status === 'in_progress'\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 3l14 9-14 9V3z\"></path>\n </svg>\n Run Now\n </button>\n <button \n v-if=\"task.status === 'pending'\"\n @click=\"showCancelConfirmation(task)\"\n class=\"flex items-center justify-center gap-2 bg-gradient-to-r from-red-500 to-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-red-600 hover:to-red-700 transform hover:scale-105 transition-all duration-200 shadow-md hover:shadow-lg\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\"></path>\n </svg>\n Cancel\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Reschedule Confirmation Modal -->\n <modal v-if=\"showRescheduleModal\" containerClass=\"!max-w-md\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showRescheduleModal = false;\" role=\"button\" aria-label=\"Close modal\">&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-content\">Reschedule Task</h3>\n </div>\n </div>\n <div class=\"mb-4\">\n <p class=\"text-sm text-gray-600\">\n Are you sure you want to reschedule task <strong>{{ selectedTask?.id }}</strong>?\n </p>\n <p class=\"text-sm text-content-tertiary mt-2\">\n This will reset the task's status and schedule it to run again.\n </p>\n \n <div class=\"mt-4\">\n <label for=\"newScheduledTime\" class=\"block text-sm font-medium text-content-secondary mb-2\">\n New Scheduled Time\n </label>\n <input\n id=\"newScheduledTime\"\n v-model=\"newScheduledTime\"\n type=\"datetime-local\"\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500\"\n required\n />\n </div>\n </div>\n <div class=\"flex gap-3\">\n <button \n @click=\"confirmRescheduleTask\"\n class=\"flex-1 bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 font-medium\"\n >\n Reschedule\n </button>\n <button \n @click=\"showRescheduleModal = false\"\n class=\"flex-1 bg-gray-300 text-content-secondary px-4 py-2 rounded-md hover:bg-gray-400 font-medium\"\n >\n Cancel\n </button>\n </div>\n </div>\n </template>\n </modal>\n\n <!-- Run Task Confirmation Modal -->\n <modal v-if=\"showRunModal\" containerClass=\"!max-w-md\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showRunModal = false;\" role=\"button\" aria-label=\"Close modal\">&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-content\">Run Task Now</h3>\n </div>\n </div>\n <div class=\"mb-4\">\n <p class=\"text-sm text-gray-600\">\n Are you sure you want to run task <strong>{{ selectedTask?.id }}</strong> immediately?\n </p>\n <p class=\"text-sm text-content-tertiary mt-2\">\n This will execute the task right away, bypassing its scheduled time.\n </p>\n </div>\n <div class=\"flex gap-3\">\n <button \n @click=\"confirmRunTask\"\n class=\"flex-1 bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 font-medium\"\n >\n Run Now\n </button>\n <button \n @click=\"showRunModal = false\"\n class=\"flex-1 bg-gray-300 text-content-secondary px-4 py-2 rounded-md hover:bg-gray-400 font-medium\"\n >\n Cancel\n </button>\n </div>\n </div>\n </template>\n </modal>\n\n <!-- Cancel Task Confirmation Modal -->\n <modal v-if=\"showCancelModal\" containerClass=\"!max-w-md\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showCancelModal = false;\" role=\"button\" aria-label=\"Close modal\">&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-content\">Cancel Task</h3>\n </div>\n </div>\n <div class=\"mb-4\">\n <p class=\"text-sm text-gray-600\">\n Are you sure you want to cancel task <strong>{{ selectedTask?.id }}</strong>?\n </p>\n <p class=\"text-sm text-content-tertiary mt-2\">\n This will permanently cancel the task and it cannot be undone.\n </p>\n </div>\n <div class=\"flex gap-3\">\n <button \n @click=\"confirmCancelTask\"\n class=\"flex-1 bg-red-600 text-white px-4 py-2 rounded-md hover:bg-red-700 font-medium\"\n >\n Cancel Task\n </button>\n <button \n @click=\"showCancelModal = false\"\n class=\"flex-1 bg-gray-300 text-content-secondary px-4 py-2 rounded-md hover:bg-gray-400 font-medium\"\n >\n Keep Task\n </button>\n </div>\n </div>\n </template>\n </modal>\n</div>\n\n";
50328
+ module.exports = "<div class=\"p-4 space-y-6\">\n <div class=\"flex items-center justify-between\">\n <div>\n <button v-if=\"showBackButton\" @click=\"goBack\" class=\"text-content-tertiary hover:text-content-secondary mb-2\">\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 19l-7-7 7-7\"></path>\n </svg>\n {{ backLabel }}\n </button>\n <h1 class=\"text-2xl font-bold text-content-secondary\">{{ taskGroup.name }}</h1>\n <p class=\"text-content-tertiary\">Total: {{ taskGroup.totalCount }} tasks</p>\n </div>\n\n </div>\n\n <!-- Status Summary -->\n <div class=\"space-y-3\">\n <div class=\"flex items-center justify-between\">\n <span class=\"text-sm font-medium text-content-secondary\">Status</span>\n <div class=\"flex rounded-md shadow-sm\" role=\"group\">\n <button\n type=\"button\"\n @click=\"statusView = 'summary'\"\n class=\"px-3 py-1.5 text-sm font-medium rounded-l-md border transition-colors\"\n :class=\"statusView === 'summary' ? 'bg-primary text-primary-text border-primary' : 'bg-surface text-content-secondary border-edge-strong hover:bg-page'\"\n >\n Summary\n </button>\n <button\n type=\"button\"\n @click=\"statusView = 'chart'\"\n class=\"px-3 py-1.5 text-sm font-medium rounded-r-md border border-l-0 transition-colors\"\n :class=\"statusView === 'chart' ? 'bg-primary text-primary-text border-primary' : 'bg-surface text-content-secondary border-edge-strong hover:bg-page'\"\n >\n Chart\n </button>\n </div>\n </div>\n <!-- Summary view -->\n <div v-show=\"statusView === 'summary'\" class=\"grid grid-cols-2 sm:grid-cols-4 gap-4\">\n <button \n @click=\"filterByStatus('pending')\"\n class=\"bg-yellow-50 border border-yellow-200 rounded-md p-3 text-center hover:bg-yellow-100 transition-colors cursor-pointer\"\n :class=\"{ 'ring-2 ring-yellow-400': currentFilter === 'pending' }\"\n >\n <div class=\"text-xs text-yellow-600 font-medium\">Pending</div>\n <div class=\"text-lg font-bold text-yellow-700\">{{ taskGroup.statusCounts.pending || 0 }}</div>\n </button>\n <button \n @click=\"filterByStatus('succeeded')\"\n class=\"bg-green-50 border border-green-200 rounded-md p-3 text-center hover:bg-green-100 transition-colors cursor-pointer\"\n :class=\"{ 'ring-2 ring-green-400': currentFilter === 'succeeded' }\"\n >\n <div class=\"text-xs text-green-600 font-medium\">Succeeded</div>\n <div class=\"text-lg font-bold text-green-700\">{{ taskGroup.statusCounts.succeeded || 0 }}</div>\n </button>\n <button \n @click=\"filterByStatus('failed')\"\n class=\"bg-red-50 border border-red-200 rounded-md p-3 text-center hover:bg-red-100 transition-colors cursor-pointer\"\n :class=\"{ 'ring-2 ring-red-400': currentFilter === 'failed' }\"\n >\n <div class=\"text-xs text-red-600 font-medium\">Failed</div>\n <div class=\"text-lg font-bold text-red-700\">{{ taskGroup.statusCounts.failed || 0 }}</div>\n </button>\n <button \n @click=\"filterByStatus('cancelled')\"\n class=\"bg-page border border-edge rounded-md p-3 text-center hover:bg-muted transition-colors cursor-pointer\"\n :class=\"{ 'ring-2 ring-gray-400': currentFilter === 'cancelled' }\"\n >\n <div class=\"text-xs text-gray-600 font-medium\">Cancelled</div>\n <div class=\"text-lg font-bold text-content-secondary\">{{ taskGroup.statusCounts.cancelled || 0 }}</div>\n </button>\n </div>\n <!-- Chart view -->\n <div v-show=\"statusView === 'chart'\" class=\"flex flex-col items-center justify-center bg-surface border border-edge rounded-lg p-4 gap-3\" style=\"min-height: 280px;\">\n <div v-if=\"taskGroup.totalCount > 0\" class=\"w-[240px] h-[240px] shrink-0\">\n <canvas ref=\"statusPieChart\" width=\"240\" height=\"240\" class=\"block\"></canvas>\n </div>\n <p v-else class=\"text-content-tertiary text-sm py-8\">No tasks to display</p>\n <!-- Selection labels: show which segment is selected (click to filter) -->\n <div v-if=\"taskGroup.totalCount > 0\" class=\"flex flex-wrap justify-center gap-2\">\n <button\n v-for=\"status in statusOrderForDisplay\"\n :key=\"status\"\n type=\"button\"\n class=\"text-xs px-2 py-1 rounded-full font-medium transition-all cursor-pointer\"\n :class=\"currentFilter === status ? getStatusPillClass(status) : 'bg-muted text-content-tertiary hover:bg-muted'\"\n @click=\"filterByStatus(status)\"\n >\n {{ statusLabel(status) }}\n </button>\n </div>\n </div>\n </div>\n\n <!-- Task List -->\n <div class=\"bg-surface rounded-lg shadow\">\n <div class=\"px-6 py-6 border-b border-edge flex items-center justify-between bg-page\">\n <h2 class=\"text-xl font-bold text-content\">\n Individual Tasks\n <span v-if=\"currentFilter\" class=\"ml-3 text-base font-semibold text-primary\">\n (Filtered by {{ currentFilter }})\n </span>\n </h2>\n <button \n v-if=\"currentFilter\"\n @click=\"clearFilter\"\n class=\"text-sm font-semibold text-primary hover:text-primary\"\n >\n Show All\n </button>\n </div>\n <div class=\"divide-y divide-gray-200\">\n <div v-for=\"task in sortedTasks\" :key=\"task.id\" class=\"p-6\">\n <div class=\"flex items-start justify-between\">\n <div class=\"flex-1\">\n <div class=\"flex items-center gap-3 mb-2\">\n <span class=\"text-sm font-medium text-content\">Task ID: {{ task.id }}</span>\n <router-link\n v-if=\"backTo\"\n :to=\"taskDetailRoute(task)\"\n class=\"text-sm text-primary hover:text-primary font-medium\"\n >\n View details\n </router-link>\n <span\n class=\"text-xs px-2 py-1 rounded-full font-medium\"\n :class=\"getStatusColor(task.status)\"\n >\n {{ task.status }}\n </span>\n </div>\n \n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4\">\n <div>\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Scheduled At</label>\n <div class=\"text-sm text-content\">{{ formatDate(task.scheduledAt) }}</div>\n </div>\n <div v-if=\"task.startedAt\">\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Started At</label>\n <div class=\"text-sm text-content\">{{ formatDate(task.startedAt) }}</div>\n </div>\n <div v-if=\"task.completedAt\">\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Completed At</label>\n <div class=\"text-sm text-content\">{{ formatDate(task.completedAt) }}</div>\n </div>\n <div v-if=\"task.error\">\n <label class=\"block text-sm font-medium text-content-secondary mb-1\">Error</label>\n <div class=\"text-sm text-red-600\">{{ task.error }}</div>\n </div>\n </div>\n\n <!-- Task Parameters -->\n <div v-if=\"task.parameters && Object.keys(task.parameters).length > 0\">\n <label class=\"block text-sm font-medium text-content-secondary mb-2\">Parameters</label>\n <div class=\"bg-page rounded-md p-3\">\n <pre class=\"text-sm text-gray-800 whitespace-pre-wrap\">{{ JSON.stringify(task.parameters, null, 2) }}</pre>\n </div>\n </div>\n </div>\n \n <div class=\"flex flex-col gap-3 ml-6\">\n <button \n @click=\"showRescheduleConfirmation(task)\"\n class=\"flex items-center justify-center gap-2 bg-gradient-to-r from-blue-500 to-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-blue-600 hover:to-blue-700 transform hover:scale-105 transition-all duration-200 shadow-md hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none\"\n :disabled=\"task.status === 'in_progress'\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\n </svg>\n Reschedule\n </button>\n <button \n @click=\"showRunConfirmation(task)\"\n class=\"flex items-center justify-center gap-2 bg-gradient-to-r from-green-500 to-green-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-green-600 hover:to-green-700 transform hover:scale-105 transition-all duration-200 shadow-md hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none\"\n :disabled=\"task.status === 'in_progress'\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 3l14 9-14 9V3z\"></path>\n </svg>\n Run Now\n </button>\n <button \n v-if=\"task.status === 'pending'\"\n @click=\"showCancelConfirmation(task)\"\n class=\"flex items-center justify-center gap-2 bg-gradient-to-r from-red-500 to-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-red-600 hover:to-red-700 transform hover:scale-105 transition-all duration-200 shadow-md hover:shadow-lg\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\"></path>\n </svg>\n Cancel\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Reschedule Confirmation Modal -->\n <modal v-if=\"showRescheduleModal\" containerClass=\"!max-w-md\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showRescheduleModal = false;\" role=\"button\" aria-label=\"Close modal\">&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-content\">Reschedule Task</h3>\n </div>\n </div>\n <div class=\"mb-4\">\n <p class=\"text-sm text-gray-600\">\n Are you sure you want to reschedule task <strong>{{ selectedTask?.id }}</strong>?\n </p>\n <p class=\"text-sm text-content-tertiary mt-2\">\n This will reset the task's status and schedule it to run again.\n </p>\n \n <div class=\"mt-4\">\n <label for=\"newScheduledTime\" class=\"block text-sm font-medium text-content-secondary mb-2\">\n New Scheduled Time\n </label>\n <input\n id=\"newScheduledTime\"\n v-model=\"newScheduledTime\"\n type=\"datetime-local\"\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500\"\n required\n />\n </div>\n </div>\n <div class=\"flex gap-3\">\n <button \n @click=\"confirmRescheduleTask\"\n class=\"flex-1 bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 font-medium\"\n >\n Reschedule\n </button>\n <button \n @click=\"showRescheduleModal = false\"\n class=\"flex-1 bg-gray-300 text-content-secondary px-4 py-2 rounded-md hover:bg-gray-400 font-medium\"\n >\n Cancel\n </button>\n </div>\n </div>\n </template>\n </modal>\n\n <!-- Run Task Confirmation Modal -->\n <modal v-if=\"showRunModal\" containerClass=\"!max-w-md\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showRunModal = false;\" role=\"button\" aria-label=\"Close modal\">&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-content\">Run Task Now</h3>\n </div>\n </div>\n <div class=\"mb-4\">\n <p class=\"text-sm text-gray-600\">\n Are you sure you want to run task <strong>{{ selectedTask?.id }}</strong> immediately?\n </p>\n <p class=\"text-sm text-content-tertiary mt-2\">\n This will execute the task right away, bypassing its scheduled time.\n </p>\n </div>\n <div class=\"flex gap-3\">\n <button \n @click=\"confirmRunTask\"\n class=\"flex-1 bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 font-medium\"\n >\n Run Now\n </button>\n <button \n @click=\"showRunModal = false\"\n class=\"flex-1 bg-gray-300 text-content-secondary px-4 py-2 rounded-md hover:bg-gray-400 font-medium\"\n >\n Cancel\n </button>\n </div>\n </div>\n </template>\n </modal>\n\n <!-- Cancel Task Confirmation Modal -->\n <modal v-if=\"showCancelModal\" containerClass=\"!max-w-md\">\n <template #body>\n <div class=\"absolute font-mono right-1 top-1 cursor-pointer text-xl\" @click=\"showCancelModal = false;\" role=\"button\" aria-label=\"Close modal\">&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-content\">Cancel Task</h3>\n </div>\n </div>\n <div class=\"mb-4\">\n <p class=\"text-sm text-gray-600\">\n Are you sure you want to cancel task <strong>{{ selectedTask?.id }}</strong>?\n </p>\n <p class=\"text-sm text-content-tertiary mt-2\">\n This will permanently cancel the task and it cannot be undone.\n </p>\n </div>\n <div class=\"flex gap-3\">\n <button \n @click=\"confirmCancelTask\"\n class=\"flex-1 bg-red-600 text-white px-4 py-2 rounded-md hover:bg-red-700 font-medium\"\n >\n Cancel Task\n </button>\n <button \n @click=\"showCancelModal = false\"\n class=\"flex-1 bg-gray-300 text-content-secondary px-4 py-2 rounded-md hover:bg-gray-400 font-medium\"\n >\n Keep Task\n </button>\n </div>\n </div>\n </template>\n </modal>\n</div>\n\n";
50165
50329
 
50166
50330
  /***/ },
50167
50331
 
@@ -61497,7 +61661,7 @@ var src_default = VueToastificationPlugin;
61497
61661
  (module) {
61498
61662
 
61499
61663
  "use strict";
61500
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.3.0","description":"A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ace-builds":"^1.43.6","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","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"}}');
61664
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.3.1","description":"A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ace-builds":"^1.43.6","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","regexp.escape":"^2.0.1","tailwindcss":"3.4.0","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x","xss":"^1.0.15"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"optionalPeerDependencies":{"@mongoosejs/task":"0.5.x || 0.6.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongodb-memory-server":"^11.0.1","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","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"}}');
61501
61665
 
61502
61666
  /***/ }
61503
61667