@mongoosejs/studio 0.3.0 → 0.3.2

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.
Files changed (32) hide show
  1. package/DEVGUIDE.md +8 -0
  2. package/backend/actions/ChatThread/createChatMessage.js +2 -0
  3. package/backend/actions/ChatThread/streamChatMessage.js +2 -0
  4. package/backend/actions/Dashboard/getDashboard.js +15 -11
  5. package/backend/actions/Dashboard/updateDashboard.js +2 -2
  6. package/backend/actions/Task/getTaskOverview.js +102 -0
  7. package/backend/actions/Task/getTasks.js +85 -45
  8. package/backend/actions/Task/index.js +1 -0
  9. package/frontend/public/app.js +318 -148
  10. package/frontend/public/tw.css +82 -0
  11. package/frontend/src/_util/dateRange.js +82 -0
  12. package/frontend/src/ace-editor/ace-editor.js +6 -0
  13. package/frontend/src/api.js +6 -0
  14. package/frontend/src/chat/chat-message-script/chat-message-script.html +11 -16
  15. package/frontend/src/chat/chat-message-script/chat-message-script.js +0 -6
  16. package/frontend/src/chat/chat.js +2 -0
  17. package/frontend/src/dashboard/dashboard.js +13 -2
  18. package/frontend/src/dashboard/edit-dashboard/edit-dashboard.js +4 -3
  19. package/frontend/src/dashboard-result/dashboard-result.js +3 -0
  20. package/frontend/src/dashboard-result/dashboard-table/dashboard-table.html +34 -0
  21. package/frontend/src/dashboard-result/dashboard-table/dashboard-table.js +37 -0
  22. package/frontend/src/models/models.js +9 -3
  23. package/frontend/src/navbar/navbar.js +3 -2
  24. package/frontend/src/task-by-name/task-by-name.html +77 -7
  25. package/frontend/src/task-by-name/task-by-name.js +84 -9
  26. package/frontend/src/tasks/task-details/task-details.html +8 -8
  27. package/frontend/src/tasks/task-details/task-details.js +2 -1
  28. package/frontend/src/tasks/tasks.js +25 -118
  29. package/local.js +38 -0
  30. package/package.json +4 -1
  31. package/seed/connect.js +23 -0
  32. package/seed/index.js +101 -0
@@ -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;
@@ -1723,6 +1822,8 @@ module.exports = {
1723
1822
  }
1724
1823
  },
1725
1824
  async mounted() {
1825
+ window.pageState = this;
1826
+
1726
1827
  this.chatThreadId = this.threadId;
1727
1828
  const { chatThreads } = await api.ChatThread.listChatThreads();
1728
1829
  this.chatThreads = chatThreads;
@@ -2387,12 +2488,63 @@ module.exports = app => app.component('dashboard-result', {
2387
2488
  if (value.$grid) {
2388
2489
  return 'dashboard-grid';
2389
2490
  }
2491
+ if (value.$table) {
2492
+ return 'dashboard-table';
2493
+ }
2390
2494
  return 'dashboard-object';
2391
2495
  }
2392
2496
  }
2393
2497
  });
2394
2498
 
2395
2499
 
2500
+ /***/ },
2501
+
2502
+ /***/ "./frontend/src/dashboard-result/dashboard-table/dashboard-table.js"
2503
+ /*!**************************************************************************!*\
2504
+ !*** ./frontend/src/dashboard-result/dashboard-table/dashboard-table.js ***!
2505
+ \**************************************************************************/
2506
+ (module, __unused_webpack_exports, __webpack_require__) {
2507
+
2508
+ "use strict";
2509
+
2510
+
2511
+ const template = __webpack_require__(/*! ./dashboard-table.html */ "./frontend/src/dashboard-result/dashboard-table/dashboard-table.html");
2512
+
2513
+ module.exports = app => app.component('dashboard-table', {
2514
+ template,
2515
+ props: ['value'],
2516
+ computed: {
2517
+ columns() {
2518
+ return Array.isArray(this.value?.$table?.columns) ? this.value.$table.columns : [];
2519
+ },
2520
+ rows() {
2521
+ return Array.isArray(this.value?.$table?.rows) ? this.value.$table.rows : [];
2522
+ },
2523
+ hasColumns() {
2524
+ return this.columns.length > 0;
2525
+ },
2526
+ hasRows() {
2527
+ return this.rows.length > 0;
2528
+ }
2529
+ },
2530
+ methods: {
2531
+ displayValue(cell) {
2532
+ if (cell == null) {
2533
+ return '';
2534
+ }
2535
+ if (typeof cell === 'object') {
2536
+ try {
2537
+ return JSON.stringify(cell);
2538
+ } catch (err) {
2539
+ return String(cell);
2540
+ }
2541
+ }
2542
+ return String(cell);
2543
+ }
2544
+ }
2545
+ });
2546
+
2547
+
2396
2548
  /***/ },
2397
2549
 
2398
2550
  /***/ "./frontend/src/dashboard-result/dashboard-text/dashboard-text.js"
@@ -2451,6 +2603,13 @@ module.exports = {
2451
2603
  this.showEditor = !this.showEditor;
2452
2604
  },
2453
2605
  async updateCode(update) {
2606
+ if (!update?.doc) {
2607
+ const message = update?.error?.message || 'Dashboard update failed';
2608
+ console.error(update?.error || new Error(message));
2609
+ this.$toast.error(message);
2610
+ return;
2611
+ }
2612
+
2454
2613
  this.code = update.doc.code;
2455
2614
  this.title = update.doc.title;
2456
2615
  this.description = update.doc.description;
@@ -2475,7 +2634,9 @@ module.exports = {
2475
2634
  this.dashboardResults.unshift({ error: { message: error.message || 'Evaluation failed' }, finishedEvaluatingAt: new Date() });
2476
2635
  }
2477
2636
  } catch (err) {
2478
- this.$toast.error(err?.response?.data?.message || err?.message || 'Dashboard evaluation failed');
2637
+ const message = err?.response?.data?.message || err?.message || 'Dashboard evaluation failed';
2638
+ console.error(err || new Error(message));
2639
+ this.$toast.error(message);
2479
2640
  } finally {
2480
2641
  this.status = 'loaded';
2481
2642
  }
@@ -2571,7 +2732,9 @@ module.exports = {
2571
2732
  return this.dashboardResults.length > 0 ? this.dashboardResults[0] : null;
2572
2733
  }
2573
2734
  },
2574
- mounted: async function() {
2735
+ mounted: async function () {
2736
+ window.pageState = this;
2737
+
2575
2738
  document.addEventListener('click', this.handleDocumentClick);
2576
2739
  this.showEditor = this.$route.query.edit;
2577
2740
  await this.loadInitial();
@@ -2599,7 +2762,7 @@ const template = __webpack_require__(/*! ./edit-dashboard.html */ "./frontend/sr
2599
2762
  module.exports = app => app.component('edit-dashboard', {
2600
2763
  template: template,
2601
2764
  props: ['dashboardId', 'code', 'currentDescription', 'currentTitle'],
2602
- emits: ['close'],
2765
+ emits: ['close', 'update'],
2603
2766
  data: function() {
2604
2767
  return {
2605
2768
  status: 'loaded',
@@ -2620,7 +2783,7 @@ module.exports = app => app.component('edit-dashboard', {
2620
2783
  async updateCode() {
2621
2784
  this.status = 'loading';
2622
2785
  try {
2623
- const codeToSave = this.$refs.codeEditor ? this.$refs.codeEditor.getValue() : this.editCode;
2786
+ const codeToSave = this.$refs.codeEditor?.getValue ? this.$refs.codeEditor.getValue() : this.editCode;
2624
2787
  const { doc } = await api.Dashboard.updateDashboard({
2625
2788
  dashboardId: this.dashboardId,
2626
2789
  code: codeToSave,
@@ -2636,7 +2799,8 @@ module.exports = app => app.component('edit-dashboard', {
2636
2799
  this.$toast.success('Dashboard updated!');
2637
2800
  this.closeEditor();
2638
2801
  } catch (err) {
2639
- this.$emit('update', { error: { message: err.message } });
2802
+ const message = err?.response?.data?.message || err?.message || 'Dashboard update failed';
2803
+ this.$emit('update', { error: { message } });
2640
2804
  } finally {
2641
2805
  this.status = 'loaded';
2642
2806
  }
@@ -6946,6 +7110,7 @@ module.exports = app => app.component('models', {
6946
7110
  }),
6947
7111
  created() {
6948
7112
  this.currentModel = this.model;
7113
+ this.setSearchTextFromRoute();
6949
7114
  this.loadOutputPreference();
6950
7115
  this.loadSelectedGeoField();
6951
7116
  this.loadRecentlyViewedModels();
@@ -6959,6 +7124,7 @@ module.exports = app => app.component('models', {
6959
7124
  this.destroyMap();
6960
7125
  },
6961
7126
  async mounted() {
7127
+ window.pageState = this;
6962
7128
  this.onScroll = () => this.checkIfScrolledToBottom();
6963
7129
  document.addEventListener('scroll', this.onScroll, true);
6964
7130
  this.onPopState = () => this.initSearchFromUrl();
@@ -6982,6 +7148,7 @@ module.exports = app => app.component('models', {
6982
7148
  }
6983
7149
  };
6984
7150
  document.addEventListener('keydown', this.onCtrlP, true);
7151
+ this.query = Object.assign({}, this.$route.query);
6985
7152
  const { models, readyState } = await api.Model.listModels();
6986
7153
  this.models = models;
6987
7154
  await this.loadModelCounts();
@@ -7364,14 +7531,17 @@ module.exports = app => app.component('models', {
7364
7531
 
7365
7532
  return params;
7366
7533
  },
7367
- async initSearchFromUrl() {
7368
- this.status = 'loading';
7369
- this.query = Object.assign({}, this.$route.query); // important that this is here before the if statements
7534
+ setSearchTextFromRoute() {
7370
7535
  if (this.$route.query?.search) {
7371
7536
  this.searchText = this.$route.query.search;
7372
7537
  } else {
7373
7538
  this.searchText = '';
7374
7539
  }
7540
+ },
7541
+ async initSearchFromUrl() {
7542
+ this.status = 'loading';
7543
+ this.query = Object.assign({}, this.$route.query); // important that this is here before the if statements
7544
+ this.setSearchTextFromRoute();
7375
7545
  if (this.$route.query?.sort) {
7376
7546
  const sort = eval(`(${this.$route.query.sort})`);
7377
7547
  const path = Object.keys(sort)[0];
@@ -8129,7 +8299,8 @@ module.exports = app => app.component('navbar', {
8129
8299
  showFlyout: false,
8130
8300
  darkMode: typeof localStorage !== 'undefined' && localStorage.getItem('studio-theme') === 'dark'
8131
8301
  }),
8132
- mounted: function() {
8302
+ mounted: function () {
8303
+ window.navbar = this;
8133
8304
  const mobileMenuMask = document.querySelector('#mobile-menu-mask');
8134
8305
  const mobileMenu = document.querySelector('#mobile-menu');
8135
8306
  const openBtn = document.querySelector('#open-mobile-menu');
@@ -8183,7 +8354,7 @@ module.exports = app => app.component('navbar', {
8183
8354
  } else {
8184
8355
  return 'https://www.npmjs.com/package/@mongoosejs/task';
8185
8356
  }
8186
-
8357
+
8187
8358
  }
8188
8359
  },
8189
8360
  methods: {
@@ -8413,6 +8584,7 @@ module.exports = app => app.component('splash', {
8413
8584
  // Page: all tasks with a given name. Reuses task-details to render the list (many tasks).
8414
8585
  const template = __webpack_require__(/*! ./task-by-name.html */ "./frontend/src/task-by-name/task-by-name.html");
8415
8586
  const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
8587
+ const { DATE_FILTERS, DATE_FILTER_VALUES, getDateRangeForRange } = __webpack_require__(/*! ../_util/dateRange */ "./frontend/src/_util/dateRange.js");
8416
8588
 
8417
8589
  function buildTaskGroup(name, tasks) {
8418
8590
  const statusCounts = { pending: 0, succeeded: 0, failed: 0, cancelled: 0, in_progress: 0, unknown: 0 };
@@ -8436,44 +8608,118 @@ function buildTaskGroup(name, tasks) {
8436
8608
  };
8437
8609
  }
8438
8610
 
8611
+ const PAGE_SIZE_OPTIONS = [25, 50, 100, 200];
8612
+
8439
8613
  module.exports = app => app.component('task-by-name', {
8440
8614
  template,
8441
8615
  data: () => ({
8442
8616
  status: 'init',
8443
8617
  taskGroup: null,
8444
- errorMessage: ''
8618
+ errorMessage: '',
8619
+ selectedRange: 'last_hour',
8620
+ start: null,
8621
+ end: null,
8622
+ dateFilters: DATE_FILTERS,
8623
+ page: 1,
8624
+ pageSize: 50,
8625
+ numDocs: 0,
8626
+ pageSizeOptions: PAGE_SIZE_OPTIONS,
8627
+ _loadId: 0,
8628
+ _lastQueryFilters: null
8445
8629
  }),
8446
8630
  computed: {
8447
8631
  taskName() {
8448
8632
  return this.$route.params.name || '';
8449
8633
  }
8450
8634
  },
8635
+ created() {
8636
+ const fromQuery = this.$route.query.dateRange;
8637
+ this.selectedRange = (fromQuery && DATE_FILTER_VALUES.includes(fromQuery)) ? fromQuery : 'last_hour';
8638
+ if (!this.$route.query.dateRange) {
8639
+ this.$router.replace({
8640
+ path: this.$route.path,
8641
+ query: { ...this.$route.query, dateRange: this.selectedRange }
8642
+ });
8643
+ }
8644
+ },
8451
8645
  watch: {
8452
8646
  taskName: {
8453
8647
  immediate: true,
8454
8648
  handler() {
8649
+ this.page = 1;
8455
8650
  this.loadTasks();
8456
8651
  }
8652
+ },
8653
+ '$route.query': {
8654
+ handler(query) {
8655
+ const dateRange = query.dateRange;
8656
+ if (dateRange && DATE_FILTER_VALUES.includes(dateRange) && this.selectedRange !== dateRange) {
8657
+ this.selectedRange = dateRange;
8658
+ }
8659
+ const effectiveDateRange = (dateRange && DATE_FILTER_VALUES.includes(dateRange)) ? dateRange : (this.selectedRange || 'last_hour');
8660
+ const effectiveStatus = query.status ?? '';
8661
+ const key = `${effectiveDateRange}|${effectiveStatus}`;
8662
+ if (this._lastQueryFilters === key) return;
8663
+ this.page = 1;
8664
+ this.loadTasks();
8665
+ },
8666
+ deep: true
8457
8667
  }
8458
8668
  },
8459
8669
  methods: {
8670
+ updateDateRange() {
8671
+ this.page = 1;
8672
+ this.$router.replace({
8673
+ path: this.$route.path,
8674
+ query: { ...this.$route.query, dateRange: this.selectedRange }
8675
+ });
8676
+ },
8677
+ goToPage(page) {
8678
+ const maxPage = Math.max(1, Math.ceil(this.numDocs / this.pageSize));
8679
+ const next = Math.max(1, Math.min(page, maxPage));
8680
+ if (next === this.page) return;
8681
+ this.page = next;
8682
+ this.loadTasks();
8683
+ },
8684
+ onPageSizeChange() {
8685
+ this.page = 1;
8686
+ this.loadTasks();
8687
+ },
8460
8688
  async loadTasks() {
8461
8689
  if (!this.taskName) return;
8690
+ const loadId = ++this._loadId;
8462
8691
  this.status = 'init';
8463
8692
  this.taskGroup = null;
8464
8693
  this.errorMessage = '';
8465
- const start = new Date();
8466
- start.setHours(start.getHours() - 1);
8467
- const end = new Date();
8694
+ const dateRangeFromQuery = this.$route.query.dateRange;
8695
+ const dateRange = (dateRangeFromQuery && DATE_FILTER_VALUES.includes(dateRangeFromQuery))
8696
+ ? dateRangeFromQuery
8697
+ : (this.selectedRange || 'last_hour');
8698
+ this.selectedRange = dateRange;
8699
+ const { start, end } = getDateRangeForRange(dateRange);
8700
+ this.start = start;
8701
+ this.end = end;
8702
+ const skip = (this.page - 1) * this.pageSize;
8703
+ const params = {
8704
+ name: this.taskName,
8705
+ start: start instanceof Date ? start.toISOString() : start,
8706
+ end: end instanceof Date ? end.toISOString() : end,
8707
+ skip,
8708
+ limit: this.pageSize
8709
+ };
8710
+ const statusFromQuery = this.$route.query.status;
8711
+ if (statusFromQuery) params.status = statusFromQuery;
8712
+ this._lastQueryFilters = `${dateRange}|${statusFromQuery ?? ''}`;
8468
8713
  try {
8469
- const { tasks } = await api.Task.getTasks({
8470
- name: this.taskName,
8471
- start,
8472
- end
8473
- });
8714
+ const { tasks, numDocs, statusCounts } = await api.Task.getTasks(params);
8715
+ if (loadId !== this._loadId) return;
8716
+ this.numDocs = numDocs ?? tasks.length;
8474
8717
  this.taskGroup = buildTaskGroup(this.taskName, tasks);
8718
+ this.taskGroup.totalCount = this.numDocs;
8719
+ if (statusCounts) this.taskGroup.statusCounts = statusCounts;
8475
8720
  this.status = 'loaded';
8476
8721
  } catch (err) {
8722
+ if (loadId !== this._loadId) return;
8477
8723
  this.status = 'error';
8478
8724
  this.errorMessage = err?.response?.data?.message || err.message || 'Failed to load tasks';
8479
8725
  }
@@ -8636,7 +8882,8 @@ const PIE_HOVER = ['#ca8a04', '#16a34a', '#dc2626', '#4b5563'];
8636
8882
  module.exports = app => app.component('task-details', {
8637
8883
  props: {
8638
8884
  taskGroup: { type: Object, required: true },
8639
- backTo: { type: Object, default: null }
8885
+ backTo: { type: Object, default: null },
8886
+ showBackButton: { type: Boolean, default: true }
8640
8887
  },
8641
8888
  data: () => ({
8642
8889
  currentFilter: null,
@@ -8977,25 +9224,17 @@ module.exports = app => app.component('task-details', {
8977
9224
 
8978
9225
  const template = __webpack_require__(/*! ./tasks.html */ "./frontend/src/tasks/tasks.html");
8979
9226
  const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
9227
+ const { DATE_FILTERS, getDateRangeForRange } = __webpack_require__(/*! ../_util/dateRange */ "./frontend/src/_util/dateRange.js");
8980
9228
 
8981
9229
  module.exports = app => app.component('tasks', {
8982
9230
  data: () => ({
8983
9231
  status: 'init',
8984
- tasks: [],
8985
- groupedTasks: {},
9232
+ statusCounts: { pending: 0, succeeded: 0, failed: 0, cancelled: 0 },
9233
+ tasksByName: [],
8986
9234
  selectedRange: 'last_hour',
8987
9235
  start: null,
8988
9236
  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
- ],
9237
+ dateFilters: DATE_FILTERS,
8999
9238
  selectedStatus: 'all',
9000
9239
  statusFilters: [
9001
9240
  { label: 'All', value: 'all' },
@@ -9025,26 +9264,31 @@ module.exports = app => app.component('tasks', {
9025
9264
  params.status = this.selectedStatus;
9026
9265
  }
9027
9266
 
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;
9267
+ if (this.start != null && this.end != null) {
9268
+ params.start = this.start instanceof Date ? this.start.toISOString() : this.start;
9269
+ params.end = this.end instanceof Date ? this.end.toISOString() : this.end;
9270
+ } else if (this.start != null) {
9271
+ params.start = this.start instanceof Date ? this.start.toISOString() : this.start;
9033
9272
  }
9034
9273
 
9035
9274
  if (this.searchQuery.trim()) {
9036
9275
  params.name = this.searchQuery.trim();
9037
9276
  }
9038
9277
 
9039
- const { tasks, groupedTasks } = await api.Task.getTasks(params);
9040
- this.tasks = tasks;
9041
- this.groupedTasks = groupedTasks;
9278
+ const { statusCounts, tasksByName } = await api.Task.getTaskOverview(params);
9279
+ this.statusCounts = statusCounts || this.statusCounts;
9280
+ this.tasksByName = tasksByName || [];
9042
9281
  },
9043
9282
  openTaskGroupDetails(group) {
9044
- this.$router.push({ path: `/tasks/${encodeURIComponent(group.name || '')}` });
9283
+ const query = { dateRange: this.selectedRange || 'last_hour' };
9284
+ if (this.selectedStatus && this.selectedStatus !== 'all') query.status = this.selectedStatus;
9285
+ this.$router.push({ path: `/tasks/${encodeURIComponent(group.name || '')}`, query });
9045
9286
  },
9046
9287
  openTaskGroupDetailsWithFilter(group, status) {
9047
- this.$router.push({ path: `/tasks/${encodeURIComponent(group.name || '')}`, query: status ? { status } : {} });
9288
+ const query = { dateRange: this.selectedRange || 'last_hour' };
9289
+ if (status) query.status = status;
9290
+ else if (this.selectedStatus && this.selectedStatus !== 'all') query.status = this.selectedStatus;
9291
+ this.$router.push({ path: `/tasks/${encodeURIComponent(group.name || '')}`, query });
9048
9292
  },
9049
9293
  async onTaskCreated() {
9050
9294
  // Refresh the task data when a new task is created
@@ -9188,114 +9432,24 @@ module.exports = app => app.component('tasks', {
9188
9432
  }, 300);
9189
9433
  },
9190
9434
  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
-
9435
+ const { start, end } = getDateRangeForRange(this.selectedRange);
9239
9436
  this.start = start;
9240
9437
  this.end = end;
9241
-
9242
9438
  await this.getTasks();
9243
9439
  }
9244
9440
  },
9245
9441
  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
- });
9442
+ pendingCount() {
9443
+ return this.statusCounts.pending || 0;
9287
9444
  },
9288
9445
  succeededCount() {
9289
- return this.groupedTasks.succeeded ? this.groupedTasks.succeeded.length : 0;
9446
+ return this.statusCounts.succeeded || 0;
9290
9447
  },
9291
9448
  failedCount() {
9292
- return this.groupedTasks.failed ? this.groupedTasks.failed.length : 0;
9449
+ return this.statusCounts.failed || 0;
9293
9450
  },
9294
9451
  cancelledCount() {
9295
- return this.groupedTasks.cancelled ? this.groupedTasks.cancelled.length : 0;
9296
- },
9297
- pendingCount() {
9298
- return this.groupedTasks.pending ? this.groupedTasks.pending.length : 0;
9452
+ return this.statusCounts.cancelled || 0;
9299
9453
  }
9300
9454
  },
9301
9455
  mounted: async function() {
@@ -9527,6 +9681,8 @@ var map = {
9527
9681
  "./": "./frontend/src/index.js",
9528
9682
  "./_util/baseComponent": "./frontend/src/_util/baseComponent.js",
9529
9683
  "./_util/baseComponent.js": "./frontend/src/_util/baseComponent.js",
9684
+ "./_util/dateRange": "./frontend/src/_util/dateRange.js",
9685
+ "./_util/dateRange.js": "./frontend/src/_util/dateRange.js",
9530
9686
  "./_util/deepEqual": "./frontend/src/_util/deepEqual.js",
9531
9687
  "./_util/deepEqual.js": "./frontend/src/_util/deepEqual.js",
9532
9688
  "./_util/document-search-autocomplete": "./frontend/src/_util/document-search-autocomplete.js",
@@ -9586,6 +9742,9 @@ var map = {
9586
9742
  "./dashboard-result/dashboard-result": "./frontend/src/dashboard-result/dashboard-result.js",
9587
9743
  "./dashboard-result/dashboard-result.html": "./frontend/src/dashboard-result/dashboard-result.html",
9588
9744
  "./dashboard-result/dashboard-result.js": "./frontend/src/dashboard-result/dashboard-result.js",
9745
+ "./dashboard-result/dashboard-table/dashboard-table": "./frontend/src/dashboard-result/dashboard-table/dashboard-table.js",
9746
+ "./dashboard-result/dashboard-table/dashboard-table.html": "./frontend/src/dashboard-result/dashboard-table/dashboard-table.html",
9747
+ "./dashboard-result/dashboard-table/dashboard-table.js": "./frontend/src/dashboard-result/dashboard-table/dashboard-table.js",
9589
9748
  "./dashboard-result/dashboard-text/dashboard-text": "./frontend/src/dashboard-result/dashboard-text/dashboard-text.js",
9590
9749
  "./dashboard-result/dashboard-text/dashboard-text.html": "./frontend/src/dashboard-result/dashboard-text/dashboard-text.html",
9591
9750
  "./dashboard-result/dashboard-text/dashboard-text.js": "./frontend/src/dashboard-result/dashboard-text/dashboard-text.js",
@@ -49468,7 +49627,7 @@ module.exports = "<button v-bind=\"attrsToBind\" :disabled=\"isDisabled\" @click
49468
49627
  (module) {
49469
49628
 
49470
49629
  "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";
49630
+ 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
49631
 
49473
49632
  /***/ },
49474
49633
 
@@ -49626,6 +49785,17 @@ module.exports = "<div>\n <div v-if=\"Array.isArray(result)\">\n <div v-for=
49626
49785
 
49627
49786
  /***/ },
49628
49787
 
49788
+ /***/ "./frontend/src/dashboard-result/dashboard-table/dashboard-table.html"
49789
+ /*!****************************************************************************!*\
49790
+ !*** ./frontend/src/dashboard-result/dashboard-table/dashboard-table.html ***!
49791
+ \****************************************************************************/
49792
+ (module) {
49793
+
49794
+ "use strict";
49795
+ 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";
49796
+
49797
+ /***/ },
49798
+
49629
49799
  /***/ "./frontend/src/dashboard-result/dashboard-text/dashboard-text.html"
49630
49800
  /*!**************************************************************************!*\
49631
49801
  !*** ./frontend/src/dashboard-result/dashboard-text/dashboard-text.html ***!
@@ -50139,7 +50309,7 @@ module.exports = "<div class=\"w-full h-full flex items-center justify-center\">
50139
50309
  (module) {
50140
50310
 
50141
50311
  "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";
50312
+ 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
50313
 
50144
50314
  /***/ },
50145
50315
 
@@ -50161,7 +50331,7 @@ module.exports = "<div class=\"p-4 space-y-6\">\n <div v-if=\"status === 'init'
50161
50331
  (module) {
50162
50332
 
50163
50333
  "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";
50334
+ 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
50335
 
50166
50336
  /***/ },
50167
50337
 
@@ -61497,7 +61667,7 @@ var src_default = VueToastificationPlugin;
61497
61667
  (module) {
61498
61668
 
61499
61669
  "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"}}');
61670
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.3.2","description":"A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ace-builds":"^1.43.6","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","regexp.escape":"^2.0.1","tailwindcss":"3.4.0","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x","xss":"^1.0.15"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"optionalPeerDependencies":{"@mongoosejs/task":"0.5.x || 0.6.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongodb-memory-server":"^11.0.1","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","seed":"node seed/index.js","start":"node ./local.js","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js","test:frontend":"mocha test/frontend/*.test.js"}}');
61501
61671
 
61502
61672
  /***/ }
61503
61673