web-mojo 2.1.824 → 2.1.867

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 (64) hide show
  1. package/dist/admin.cjs.js +1 -1
  2. package/dist/admin.cjs.js.map +1 -1
  3. package/dist/admin.es.js +317 -39
  4. package/dist/admin.es.js.map +1 -1
  5. package/dist/auth.cjs.js +1 -1
  6. package/dist/auth.cjs.js.map +1 -1
  7. package/dist/auth.es.js +3 -3
  8. package/dist/auth.es.js.map +1 -1
  9. package/dist/charts.cjs.js +1 -1
  10. package/dist/charts.es.js +2 -2
  11. package/dist/chunks/{ChatView-DsdSe1A5.js → ChatView-8wuIN1zH.js} +327 -53
  12. package/dist/chunks/ChatView-8wuIN1zH.js.map +1 -0
  13. package/dist/chunks/ChatView-BtoAS3jU.js +2 -0
  14. package/dist/chunks/ChatView-BtoAS3jU.js.map +1 -0
  15. package/dist/chunks/{ContextMenu-C7wfFMyp.js → ContextMenu-Bem9gIL6.js} +2 -2
  16. package/dist/chunks/{ContextMenu-C7wfFMyp.js.map → ContextMenu-Bem9gIL6.js.map} +1 -1
  17. package/dist/chunks/{ContextMenu-6zcMDDiv.js → ContextMenu-DPxZgf69.js} +3 -2
  18. package/dist/chunks/{ContextMenu-6zcMDDiv.js.map → ContextMenu-DPxZgf69.js.map} +1 -1
  19. package/dist/chunks/{DataView-BDxfTYf0.js → DataView-C9O8uelM.js} +2 -2
  20. package/dist/chunks/{DataView-BDxfTYf0.js.map → DataView-C9O8uelM.js.map} +1 -1
  21. package/dist/chunks/{DataView-BJCAbOpx.js → DataView-DhbOr4Wm.js} +2 -2
  22. package/dist/chunks/{DataView-BJCAbOpx.js.map → DataView-DhbOr4Wm.js.map} +1 -1
  23. package/dist/chunks/{Dialog-CCnOsBBK.js → Dialog-CQA_WzmY.js} +5 -5
  24. package/dist/chunks/{Dialog-CCnOsBBK.js.map → Dialog-CQA_WzmY.js.map} +1 -1
  25. package/dist/chunks/{Dialog-B8_iNgDO.js → Dialog-gO9ln-ej.js} +2 -2
  26. package/dist/chunks/{Dialog-B8_iNgDO.js.map → Dialog-gO9ln-ej.js.map} +1 -1
  27. package/dist/chunks/{FormView-CHyzkf7A.js → FormView-DbPm-jDU.js} +4 -4
  28. package/dist/chunks/FormView-DbPm-jDU.js.map +1 -0
  29. package/dist/chunks/FormView-DkxYCmes.js +3 -0
  30. package/dist/chunks/FormView-DkxYCmes.js.map +1 -0
  31. package/dist/chunks/{MetricsMiniChartWidget-Co_cyqd4.js → MetricsMiniChartWidget-C41dYV1K.js} +2 -2
  32. package/dist/chunks/{MetricsMiniChartWidget-Co_cyqd4.js.map → MetricsMiniChartWidget-C41dYV1K.js.map} +1 -1
  33. package/dist/chunks/{MetricsMiniChartWidget-DkroX-HM.js → MetricsMiniChartWidget-Dz2d0GG2.js} +3 -3
  34. package/dist/chunks/{MetricsMiniChartWidget-DkroX-HM.js.map → MetricsMiniChartWidget-Dz2d0GG2.js.map} +1 -1
  35. package/dist/chunks/{PDFViewer-DLYyetM9.js → PDFViewer-BJT0-zQL.js} +3 -3
  36. package/dist/chunks/{PDFViewer-DLYyetM9.js.map → PDFViewer-BJT0-zQL.js.map} +1 -1
  37. package/dist/chunks/{PDFViewer-CFbFrBdS.js → PDFViewer-DvTxpaXI.js} +2 -2
  38. package/dist/chunks/{PDFViewer-CFbFrBdS.js.map → PDFViewer-DvTxpaXI.js.map} +1 -1
  39. package/dist/chunks/{Page-kuadj0PG.js → Page-CeYPFh_j.js} +2 -2
  40. package/dist/chunks/{Page-kuadj0PG.js.map → Page-CeYPFh_j.js.map} +1 -1
  41. package/dist/chunks/{Page-C1nY4u_3.js → Page-xjtBxzqE.js} +2 -2
  42. package/dist/chunks/{Page-C1nY4u_3.js.map → Page-xjtBxzqE.js.map} +1 -1
  43. package/dist/chunks/{TopNav-B40XDQjS.js → TopNav-CcS_qMvn.js} +2 -2
  44. package/dist/chunks/{TopNav-B40XDQjS.js.map → TopNav-CcS_qMvn.js.map} +1 -1
  45. package/dist/chunks/{TopNav-CkJNTimo.js → TopNav-baMWNGG2.js} +5 -5
  46. package/dist/chunks/{TopNav-CkJNTimo.js.map → TopNav-baMWNGG2.js.map} +1 -1
  47. package/dist/chunks/{WebApp-DT6XLijH.js → WebApp-DLAySO90.js} +13 -13
  48. package/dist/chunks/{WebApp-DT6XLijH.js.map → WebApp-DLAySO90.js.map} +1 -1
  49. package/dist/chunks/{WebApp-D0vBp8ks.js → WebApp-DkJV3BmZ.js} +2 -2
  50. package/dist/chunks/{WebApp-D0vBp8ks.js.map → WebApp-DkJV3BmZ.js.map} +1 -1
  51. package/dist/docit.cjs.js +1 -1
  52. package/dist/docit.es.js +5 -5
  53. package/dist/index.cjs.js +1 -1
  54. package/dist/index.cjs.js.map +1 -1
  55. package/dist/index.es.js +58 -52
  56. package/dist/lightbox.cjs.js +1 -1
  57. package/dist/lightbox.es.js +4 -4
  58. package/package.json +1 -1
  59. package/dist/chunks/ChatView-Dm-1ojKc.js +0 -2
  60. package/dist/chunks/ChatView-Dm-1ojKc.js.map +0 -1
  61. package/dist/chunks/ChatView-DsdSe1A5.js.map +0 -1
  62. package/dist/chunks/FormView-CHyzkf7A.js.map +0 -1
  63. package/dist/chunks/FormView-NYVaTwDE.js +0 -3
  64. package/dist/chunks/FormView-NYVaTwDE.js.map +0 -1
package/dist/admin.es.js CHANGED
@@ -1,13 +1,13 @@
1
- import { P as Page } from "./chunks/Page-kuadj0PG.js";
2
- import { V as View, h as MOJOUtils } from "./chunks/WebApp-DT6XLijH.js";
3
- import { B, b, a, c, e, f, W } from "./chunks/WebApp-DT6XLijH.js";
4
- import Dialog$1 from "./chunks/Dialog-CCnOsBBK.js";
5
- import { M as MetricsChart, c as MetricsMiniChartWidget, P as PieChart } from "./chunks/MetricsMiniChartWidget-DkroX-HM.js";
6
- import { b as TablePage, k as EmailDomainForms, j as EmailDomainList, E as EmailDomain, n as MailboxForms, m as MailboxList, l as Mailbox, r as EmailTemplate, d as TabView, t as EmailTemplateForms, s as EmailTemplateList, I as IncidentEvent, B as IncidentEventForms, A as IncidentEventList, w as FileManagerForms, v as FileManagerList, x as File, T as TableView, z as FileForms, y as FileList, ap as GeoLocatedIP, aq as GeoLocatedIPList, aa as MemberList, a9 as LogList, as as TicketList, G as IncidentList, Y as IncidentStats, R as IncidentHistoryList, Q as IncidentHistory, D as Incident, C as ChatView, H as IncidentForms, Z as Job, a3 as JobEventList, a1 as JobLogList, $ as JobForms, _ as JobList, a6 as JobRunnerList, a4 as JobsEngineStats, a7 as JobRunnerForms, a5 as JobRunner, a8 as Log, M as Member, ab as MemberForms, ac as MetricsPermission, ae as MetricsForms, ad as MetricsPermissionList, an as PushConfigForms, ak as PushConfigList, am as PushDeliveryList, ag as PushDeviceList, ao as PushTemplateForms, ai as PushTemplateList, U as RuleSet, X as RuleList, V as RuleSetList, i as S3BucketForms, h as S3BucketList, o as SentMessage, p as SentMessageList, au as TicketNoteList, at as TicketNote, ar as Ticket, av as TicketForms, aw as TicketCategories } from "./chunks/ChatView-DsdSe1A5.js";
7
- import DataView from "./chunks/DataView-BJCAbOpx.js";
8
- import { b as ContextMenu, C as Collection, a as Group, G as GroupList, c as GroupForms, g as UserDevice, j as UserDeviceLocationList, h as UserDeviceList, U as User, f as UserDataView, e as UserForms, d as UserList } from "./chunks/ContextMenu-6zcMDDiv.js";
9
- import { L as LightboxGallery, P as PDFViewer } from "./chunks/PDFViewer-DLYyetM9.js";
10
- import { a as applyFileDropMixin, F as FormView } from "./chunks/FormView-CHyzkf7A.js";
1
+ import { P as Page } from "./chunks/Page-CeYPFh_j.js";
2
+ import { V as View, h as MOJOUtils } from "./chunks/WebApp-DLAySO90.js";
3
+ import { B, b, a, c, e, f, W } from "./chunks/WebApp-DLAySO90.js";
4
+ import Dialog$1 from "./chunks/Dialog-CQA_WzmY.js";
5
+ import { M as MetricsChart, c as MetricsMiniChartWidget, P as PieChart } from "./chunks/MetricsMiniChartWidget-Dz2d0GG2.js";
6
+ import { b as TablePage, k as EmailDomainForms, j as EmailDomainList, E as EmailDomain, n as MailboxForms, m as MailboxList, l as Mailbox, r as EmailTemplate, d as TabView, t as EmailTemplateForms, s as EmailTemplateList, I as IncidentEvent, B as IncidentEventForms, A as IncidentEventList, w as FileManagerForms, v as FileManagerList, x as File, T as TableView, z as FileForms, y as FileList, av as GeoLocatedIP, aw as GeoLocatedIPList, ag as MemberList, af as LogList, ay as TicketList, G as IncidentList, _ as IncidentStats, R as IncidentHistoryList, Q as IncidentHistory, D as Incident, C as ChatView, H as IncidentForms, a3 as Job, a9 as JobEventList, a7 as JobLogList, a5 as JobForms, a4 as JobList, ac as JobRunnerList, aa as JobsEngineStats, ad as JobRunnerForms, ab as JobRunner, ae as Log, M as Member, ah as MemberForms, ai as MetricsPermission, ak as MetricsForms, aj as MetricsPermissionList, at as PushConfigForms, aq as PushConfigList, as as PushDeliveryList, am as PushDeviceList, au as PushTemplateForms, ao as PushTemplateList, U as RuleSet, a0 as MatchByOptions, $ as BundleByOptions, Y as RuleList, V as RuleSetList, i as S3BucketForms, h as S3BucketList, o as SentMessage, p as SentMessageList, aA as TicketNoteList, az as TicketNote, ax as Ticket, aB as TicketForms, aC as TicketCategories } from "./chunks/ChatView-8wuIN1zH.js";
7
+ import DataView from "./chunks/DataView-DhbOr4Wm.js";
8
+ import { b as ContextMenu, C as Collection, a as Group, G as GroupList, c as GroupForms, g as UserDevice, j as UserDeviceLocationList, h as UserDeviceList, U as User, f as UserDataView, e as UserForms, d as UserList } from "./chunks/ContextMenu-DPxZgf69.js";
9
+ import { L as LightboxGallery, P as PDFViewer } from "./chunks/PDFViewer-BJT0-zQL.js";
10
+ import { a as applyFileDropMixin, F as FormView } from "./chunks/FormView-DbPm-jDU.js";
11
11
  class AdminHeaderView extends View {
12
12
  constructor(options = {}) {
13
13
  super({
@@ -798,6 +798,10 @@ class EmailTemplateTablePage extends TablePage {
798
798
  size: "xl",
799
799
  scrollable: true
800
800
  },
801
+ formDialogConfig: {
802
+ // Dialog options for forms
803
+ size: "xl"
804
+ },
801
805
  // Table columns
802
806
  columns: [
803
807
  { key: "id", label: "ID", width: "70px", sortable: true, class: "text-muted" },
@@ -827,6 +831,129 @@ class EmailTemplateTablePage extends TablePage {
827
831
  });
828
832
  }
829
833
  }
834
+ class StackTraceView extends View {
835
+ constructor(options = {}) {
836
+ super({
837
+ className: "stack-trace-view",
838
+ ...options
839
+ });
840
+ this.stackTrace = options.stackTrace || "";
841
+ this.template = `
842
+ <div class="stack-trace-container p-3">
843
+ <style>
844
+ .stack-trace-line {
845
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
846
+ font-size: 13px;
847
+ line-height: 1.6;
848
+ padding: 4px 8px;
849
+ margin: 0;
850
+ border-left: 3px solid transparent;
851
+ }
852
+ .stack-trace-line:hover {
853
+ background-color: rgba(0, 0, 0, 0.05);
854
+ }
855
+ .stack-trace-error {
856
+ color: #dc3545;
857
+ font-weight: 600;
858
+ border-left-color: #dc3545;
859
+ background-color: rgba(220, 53, 69, 0.05);
860
+ }
861
+ .stack-trace-file {
862
+ color: #0d6efd;
863
+ font-weight: 500;
864
+ border-left-color: #0d6efd;
865
+ }
866
+ .stack-trace-function {
867
+ color: #6610f2;
868
+ font-weight: 500;
869
+ }
870
+ .stack-trace-location {
871
+ color: #6c757d;
872
+ font-size: 12px;
873
+ }
874
+ .stack-trace-line-number {
875
+ color: #fd7e14;
876
+ font-weight: 600;
877
+ }
878
+ .stack-trace-context {
879
+ color: #495057;
880
+ background-color: rgba(0, 0, 0, 0.02);
881
+ }
882
+ .stack-trace-container {
883
+ background-color: #f8f9fa;
884
+ border: 1px solid #dee2e6;
885
+ border-radius: 0.375rem;
886
+ max-height: 600px;
887
+ overflow-y: auto;
888
+ }
889
+ </style>
890
+ <div class="stack-trace-content">
891
+ {{{formattedStackTrace}}}
892
+ </div>
893
+ </div>
894
+ `;
895
+ }
896
+ async onBeforeRender() {
897
+ this.formattedStackTrace = this.formatStackTrace(this.stackTrace);
898
+ }
899
+ formatStackTrace(stackTrace) {
900
+ if (!stackTrace) {
901
+ return '<div class="text-muted p-3">No stack trace available</div>';
902
+ }
903
+ const traceStr = typeof stackTrace === "string" ? stackTrace : JSON.stringify(stackTrace, null, 2);
904
+ const lines = traceStr.split("\n");
905
+ let html = "";
906
+ lines.forEach((line, index) => {
907
+ if (!line.trim()) {
908
+ html += '<div class="stack-trace-line">&nbsp;</div>';
909
+ return;
910
+ }
911
+ if (index === 0 && (line.includes("Error:") || line.includes("Exception:"))) {
912
+ html += `<div class="stack-trace-line stack-trace-error">${this.escapeHtml(line)}</div>`;
913
+ return;
914
+ }
915
+ const filePattern = /(.+?)\s*\(([^:]+):(\d+):(\d+)\)/;
916
+ const simpleFilePattern = /^\s*at\s+([^:]+):(\d+):(\d+)/;
917
+ let match = line.match(filePattern);
918
+ if (match) {
919
+ const [, funcName, filePath, lineNum, colNum] = match;
920
+ html += `<div class="stack-trace-line stack-trace-file">
921
+ <span class="stack-trace-function">${this.escapeHtml(funcName.trim())}</span>
922
+ <span class="stack-trace-location"> (${this.escapeHtml(filePath)}:<span class="stack-trace-line-number">${lineNum}</span>:${colNum})</span>
923
+ </div>`;
924
+ return;
925
+ }
926
+ match = line.match(simpleFilePattern);
927
+ if (match) {
928
+ const [, filePath, lineNum, colNum] = match;
929
+ html += `<div class="stack-trace-line stack-trace-file">
930
+ <span class="stack-trace-location">at ${this.escapeHtml(filePath)}:<span class="stack-trace-line-number">${lineNum}</span>:${colNum}</span>
931
+ </div>`;
932
+ return;
933
+ }
934
+ const pythonPattern = /File\s+"([^"]+)",\s+line\s+(\d+),\s+in\s+(.+)/;
935
+ match = line.match(pythonPattern);
936
+ if (match) {
937
+ const [, filePath, lineNum, funcName] = match;
938
+ html += `<div class="stack-trace-line stack-trace-file">
939
+ <span class="stack-trace-location">File "${this.escapeHtml(filePath)}", line <span class="stack-trace-line-number">${lineNum}</span>, in </span>
940
+ <span class="stack-trace-function">${this.escapeHtml(funcName)}</span>
941
+ </div>`;
942
+ return;
943
+ }
944
+ if (line.trim().startsWith("at ")) {
945
+ html += `<div class="stack-trace-line stack-trace-file">${this.escapeHtml(line)}</div>`;
946
+ return;
947
+ }
948
+ html += `<div class="stack-trace-line stack-trace-context">${this.escapeHtml(line)}</div>`;
949
+ });
950
+ return html;
951
+ }
952
+ updateStackTrace(newStackTrace) {
953
+ this.stackTrace = newStackTrace;
954
+ this.render();
955
+ }
956
+ }
830
957
  class EventView extends View {
831
958
  constructor(options = {}) {
832
959
  super({
@@ -882,20 +1009,20 @@ class EventView extends View {
882
1009
  { name: "details", label: "Details", columns: 12 }
883
1010
  ]
884
1011
  });
885
- this.metadataView = new View({
886
- model: this.model,
887
- template: `<pre class="bg-light p-3 border rounded"><code>{{{model.metadata|json}}}</code></pre>`
888
- });
889
1012
  const tabs = { "Overview": this.overviewView };
890
- if (this.model.get("metadata") && Object.keys(this.model.get("metadata")).length > 0) {
891
- tabs["Metadata"] = this.metadataView;
1013
+ const metadata = this.model.get("metadata") || {};
1014
+ if (metadata.stack_trace) {
1015
+ this.stackTraceView = new StackTraceView({
1016
+ stackTrace: metadata.stack_trace
1017
+ });
1018
+ tabs["Stack Trace"] = this.stackTraceView;
892
1019
  }
893
- if (this.model.get("metadata.stack_trace")) {
894
- this.stackTraceView = new View({
1020
+ if (Object.keys(metadata).length > 0) {
1021
+ this.metadataView = new View({
895
1022
  model: this.model,
896
- template: `<pre class="bg-dark text-white p-3 border rounded">{{{model.metadata.stack_trace}}}</pre>`
1023
+ template: `<pre class="bg-light p-3 border rounded"><code>{{{model.metadata|json}}}</code></pre>`
897
1024
  });
898
- tabs["Stack Trace"] = this.stackTraceView;
1025
+ tabs["Metadata"] = this.metadataView;
899
1026
  }
900
1027
  this.tabView = new TabView({
901
1028
  containerId: "event-tabs",
@@ -1463,7 +1590,7 @@ class FileTablePage extends TablePage {
1463
1590
  */
1464
1591
  async onActionAdd(event, element) {
1465
1592
  event.preventDefault();
1466
- const Dialog2 = (await import("./chunks/Dialog-CCnOsBBK.js")).default;
1593
+ const Dialog2 = (await import("./chunks/Dialog-CQA_WzmY.js")).default;
1467
1594
  const formData = await Dialog2.showForm({
1468
1595
  title: "Upload File",
1469
1596
  size: "md",
@@ -1916,6 +2043,10 @@ class GroupTablePage extends TablePage {
1916
2043
  viewDialogOptions: {
1917
2044
  header: false
1918
2045
  },
2046
+ defaultQuery: {
2047
+ sort: "-id",
2048
+ is_active: 1
2049
+ },
1919
2050
  // Column definitions
1920
2051
  columns: [
1921
2052
  {
@@ -1925,6 +2056,11 @@ class GroupTablePage extends TablePage {
1925
2056
  sortable: true,
1926
2057
  class: "text-muted"
1927
2058
  },
2059
+ {
2060
+ key: "is_active|yesnoicon",
2061
+ label: "Enabled",
2062
+ visibility: "lg"
2063
+ },
1928
2064
  {
1929
2065
  label: "Avatar",
1930
2066
  key: 'avatar|avatar("sm", "rounded")',
@@ -2352,13 +2488,39 @@ class IncidentView extends View {
2352
2488
  { name: "details", label: "Details", columns: 12, format: "pre" }
2353
2489
  ]
2354
2490
  });
2491
+ const eventsCollection = new IncidentEventList({
2492
+ params: { incident: this.model.get("id") }
2493
+ });
2494
+ this.eventsView = new TableView({
2495
+ collection: eventsCollection,
2496
+ hideActivePillNames: ["incident"],
2497
+ columns: [
2498
+ { key: "id", label: "ID", width: "70px", sortable: true },
2499
+ { key: "created", label: "Date", formatter: "datetime", sortable: true, width: "180px" },
2500
+ { key: "category", label: "Category", formatter: "badge", sortable: true },
2501
+ { key: "title", label: "Title", sortable: true },
2502
+ { key: "level", label: "Level", sortable: true, width: "80px" }
2503
+ ],
2504
+ showAdd: false,
2505
+ actions: ["view"],
2506
+ paginated: true,
2507
+ size: 10
2508
+ });
2355
2509
  const adapter = new IncidentHistoryAdapter(this.model.get("id"));
2356
2510
  this.historyView = new ChatView({ adapter });
2357
2511
  const tabs = {
2358
2512
  "Overview": this.overviewView,
2513
+ "Events": this.eventsView,
2359
2514
  "History & Comments": this.historyView
2360
2515
  };
2361
- if (this.model.get("metadata") && Object.keys(this.model.get("metadata")).length > 0) {
2516
+ const metadata = this.model.get("metadata") || {};
2517
+ if (metadata.stack_trace) {
2518
+ this.stackTraceView = new StackTraceView({
2519
+ stackTrace: metadata.stack_trace
2520
+ });
2521
+ tabs["Stack Trace"] = this.stackTraceView;
2522
+ }
2523
+ if (Object.keys(metadata).length > 0) {
2362
2524
  this.metadataView = new View({
2363
2525
  model: this.model,
2364
2526
  template: `<pre class="bg-light p-3 border rounded"><code>{{{model.metadata|json}}}</code></pre>`
@@ -2493,8 +2655,8 @@ class IncidentTablePage extends TablePage {
2493
2655
  { label: "Open", icon: "bi bi-folder2-open", action: "open" },
2494
2656
  { label: "Resolve", icon: "bi bi-check-circle", action: "resolve" },
2495
2657
  { label: "Pause", icon: "bi bi-pause-circle", action: "pause" },
2496
- { label: "Ignore", icon: "bi bi-x-circle", action: "ignored" },
2497
- { label: "Change Status", icon: "bi bi-arrow-repeat", action: "status" }
2658
+ { label: "Ignore", icon: "bi bi-x-circle", action: "ignore" },
2659
+ { label: "Merge", icon: "bi bi-merge", action: "merge" }
2498
2660
  ],
2499
2661
  // Table display options
2500
2662
  tableOptions: {
@@ -2541,6 +2703,29 @@ class IncidentTablePage extends TablePage {
2541
2703
  await Promise.all(selected.map((item) => item.model.save({ status: "ignored" })));
2542
2704
  this.tableView.collection.fetch();
2543
2705
  }
2706
+ async onActionBatchMerge(_event, _element) {
2707
+ const selected = this.tableView.getSelectedItems();
2708
+ if (!selected.length) return;
2709
+ const app = this.getApp();
2710
+ const result = await app.showForm({
2711
+ title: `Merge ${selected.length} incidents`,
2712
+ fields: [
2713
+ {
2714
+ name: "merge",
2715
+ type: "select",
2716
+ label: "Select Parent Incident",
2717
+ options: selected.map((item) => ({ value: item.model.id, label: item.model.id })),
2718
+ required: true
2719
+ }
2720
+ ]
2721
+ });
2722
+ if (!result) return;
2723
+ const parentModel = selected.find((item) => item.model.id == result.merge)?.model;
2724
+ if (!parentModel) return;
2725
+ const mergeIds = selected.map((item) => item.model.id).filter((id) => id != result.merge);
2726
+ await parentModel.save({ merge: mergeIds });
2727
+ this.tableView.collection.fetch();
2728
+ }
2544
2729
  }
2545
2730
  class JobStatsView extends View {
2546
2731
  constructor(options = {}) {
@@ -5000,20 +5185,36 @@ class RuleSetView extends View {
5000
5185
  `;
5001
5186
  }
5002
5187
  async onInit() {
5188
+ const matchByValue = this.model.get("match_by");
5189
+ const matchByOption = MatchByOptions.find((opt) => opt.value === matchByValue);
5190
+ const matchByLabel = matchByOption ? matchByOption.label : String(matchByValue);
5191
+ const bundleByValue = this.model.get("bundle_by");
5192
+ const bundleByOption = BundleByOptions.find((opt) => opt.value === bundleByValue);
5193
+ const bundleByLabel = bundleByOption ? bundleByOption.label : String(bundleByValue);
5003
5194
  this.configView = new DataView({
5004
5195
  model: this.model,
5005
5196
  className: "p-3",
5006
5197
  columns: 2,
5007
5198
  fields: [
5008
- { name: "id", label: "RuleSet ID" },
5009
- { name: "name", label: "Name" },
5010
- { name: "category", label: "Category" },
5011
- { name: "priority", label: "Priority" },
5012
- { name: "match_by", label: "Match Logic", format: (v) => v === 0 ? "ALL" : "ANY" },
5013
- { name: "bundle_by", label: "Bundle By" },
5014
- { name: "bundle_minutes", label: "Bundle Minutes" },
5015
- { name: "handler", label: "Handler" },
5016
- { name: "is_active", label: "Status", format: "boolean" }
5199
+ { name: "id", label: "RuleSet ID", cols: 6 },
5200
+ { name: "priority", label: "Priority", cols: 6 },
5201
+ { name: "name", label: "Name", cols: 12 },
5202
+ { name: "category", label: "Category", formatter: "badge", cols: 6 },
5203
+ { name: "is_active", label: "Status", formatter: "boolean", cols: 6 },
5204
+ {
5205
+ name: "match_by",
5206
+ label: "Match Logic",
5207
+ template: matchByLabel,
5208
+ cols: 12
5209
+ },
5210
+ {
5211
+ name: "bundle_by",
5212
+ label: "Bundle By",
5213
+ template: bundleByLabel,
5214
+ cols: 12
5215
+ },
5216
+ { name: "bundle_minutes", label: "Bundle Minutes", cols: 6 },
5217
+ { name: "handler", label: "Handler", cols: 12 }
5017
5218
  ]
5018
5219
  });
5019
5220
  const rulesCollection = new RuleList({
@@ -5025,12 +5226,23 @@ class RuleSetView extends View {
5025
5226
  { key: "id", label: "ID", width: "70px" },
5026
5227
  { key: "name", label: "Name" },
5027
5228
  { key: "field_name", label: "Field" },
5028
- { key: "comparator", label: "Comparator" },
5229
+ { key: "comparator", label: "Comparator", width: "120px" },
5029
5230
  { key: "value", label: "Value" },
5030
- { key: "value_type", label: "Type" }
5231
+ { key: "value_type", label: "Type", width: "100px" }
5031
5232
  ],
5032
5233
  showAdd: true,
5033
- actions: ["edit", "delete"]
5234
+ clickAction: "edit",
5235
+ actions: ["edit", "delete"],
5236
+ contextMenu: [
5237
+ { label: "Edit Rule", action: "edit", icon: "bi-pencil" },
5238
+ { label: "Duplicate Rule", action: "duplicate", icon: "bi-files" },
5239
+ { divider: true },
5240
+ { label: "Delete Rule", action: "delete", icon: "bi-trash", danger: true }
5241
+ ],
5242
+ // Pass the parent ID so new rules get associated with this ruleset
5243
+ defaultData: {
5244
+ parent: this.model.get("id")
5245
+ }
5034
5246
  });
5035
5247
  this.tabView = new TabView({
5036
5248
  containerId: "ruleset-tabs",
@@ -5056,6 +5268,73 @@ class RuleSetView extends View {
5056
5268
  });
5057
5269
  this.addChild(contextMenu);
5058
5270
  }
5271
+ /**
5272
+ * Action handler: Edit RuleSet
5273
+ */
5274
+ async onActionEditRuleset() {
5275
+ const resp = await Dialog$1.showModelForm({
5276
+ title: `Edit RuleSet - ${this.model.get("name")}`,
5277
+ model: this.model,
5278
+ formConfig: RuleSet.EDIT_FORM
5279
+ });
5280
+ if (resp) {
5281
+ await this.render();
5282
+ }
5283
+ }
5284
+ /**
5285
+ * Action handler: Disable/Enable RuleSet
5286
+ */
5287
+ async onActionDisableRuleset() {
5288
+ const isActive = this.model.get("is_active");
5289
+ const newStatus = !isActive;
5290
+ try {
5291
+ this.model.set("is_active", newStatus);
5292
+ await this.model.save();
5293
+ await this.render();
5294
+ Dialog$1.showToast({
5295
+ message: `RuleSet ${newStatus ? "enabled" : "disabled"} successfully`,
5296
+ type: "success"
5297
+ });
5298
+ } catch (error) {
5299
+ Dialog$1.showToast({
5300
+ message: `Failed to update RuleSet: ${error.message}`,
5301
+ type: "error"
5302
+ });
5303
+ }
5304
+ }
5305
+ /**
5306
+ * Action handler: Delete RuleSet
5307
+ */
5308
+ async onActionDeleteRuleset() {
5309
+ const confirmed = await Dialog$1.confirm({
5310
+ title: "Delete RuleSet",
5311
+ message: `Are you sure you want to delete the ruleset "${this.model.get("name")}"? This action cannot be undone.`,
5312
+ confirmText: "Delete",
5313
+ confirmClass: "btn-danger"
5314
+ });
5315
+ if (confirmed) {
5316
+ try {
5317
+ await this.model.destroy();
5318
+ Dialog$1.showToast({
5319
+ message: "RuleSet deleted successfully",
5320
+ type: "success"
5321
+ });
5322
+ const dialog = this.element?.closest(".modal");
5323
+ if (dialog) {
5324
+ const bsModal = bootstrap.Modal.getInstance(dialog);
5325
+ if (bsModal) {
5326
+ bsModal.hide();
5327
+ }
5328
+ }
5329
+ this.emit("ruleset:deleted", { model: this.model });
5330
+ } catch (error) {
5331
+ Dialog$1.showToast({
5332
+ message: `Failed to delete RuleSet: ${error.message}`,
5333
+ type: "error"
5334
+ });
5335
+ }
5336
+ }
5337
+ }
5059
5338
  }
5060
5339
  RuleSetView.VIEW_CLASS = RuleSetView;
5061
5340
  class RuleSetTablePage extends TablePage {
@@ -5066,7 +5345,7 @@ class RuleSetTablePage extends TablePage {
5066
5345
  pageName: "Rule Engine",
5067
5346
  router: "admin/rulesets",
5068
5347
  Collection: RuleSetList,
5069
- itemViewClass: RuleSetView,
5348
+ itemView: RuleSetView,
5070
5349
  viewDialogOptions: {
5071
5350
  header: false,
5072
5351
  size: "xl"
@@ -5076,8 +5355,7 @@ class RuleSetTablePage extends TablePage {
5076
5355
  { key: "name", label: "Name", sortable: true },
5077
5356
  { key: "category", label: "Category", sortable: true, formatter: "badge" },
5078
5357
  { key: "priority", label: "Priority", sortable: true },
5079
- { key: "match_by", label: "Match Logic", formatter: (v) => v === 0 ? "ALL" : "ANY" },
5080
- { key: "is_active", label: "Status", formatter: "boolean|badge('Active:success,Inactive:secondary')" }
5358
+ { key: "match_by", label: "Match Logic", formatter: (v) => v === 0 ? "ALL" : "ANY" }
5081
5359
  ],
5082
5360
  selectable: true,
5083
5361
  searchable: true,