web-mojo 2.1.827 → 2.1.917
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.
- package/dist/admin.cjs.js +1 -1
- package/dist/admin.cjs.js.map +1 -1
- package/dist/admin.es.js +604 -62
- package/dist/admin.es.js.map +1 -1
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.cjs.js.map +1 -1
- package/dist/auth.es.js +3 -3
- package/dist/auth.es.js.map +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +2 -2
- package/dist/chunks/{ChatView-DJK49g8O.js → ChatView-D9hJ2u6c.js} +372 -53
- package/dist/chunks/ChatView-D9hJ2u6c.js.map +1 -0
- package/dist/chunks/ChatView-u-UdWa-P.js +2 -0
- package/dist/chunks/ChatView-u-UdWa-P.js.map +1 -0
- package/dist/chunks/{ContextMenu-CRwv6-gc.js → ContextMenu-BlmrYFlZ.js} +3 -2
- package/dist/chunks/{ContextMenu-CRwv6-gc.js.map → ContextMenu-BlmrYFlZ.js.map} +1 -1
- package/dist/chunks/{ContextMenu-K8sVV2Sf.js → ContextMenu-CfXiUGrB.js} +2 -2
- package/dist/chunks/{ContextMenu-K8sVV2Sf.js.map → ContextMenu-CfXiUGrB.js.map} +1 -1
- package/dist/chunks/{DataView-3iqbZ_4y.js → DataView-BDUCD0q0.js} +11 -21
- package/dist/chunks/DataView-BDUCD0q0.js.map +1 -0
- package/dist/chunks/DataView-CorHtzkO.js +2 -0
- package/dist/chunks/DataView-CorHtzkO.js.map +1 -0
- package/dist/chunks/{Dialog-Bup1WdQn.js → Dialog-D9qIh5Jb.js} +5 -5
- package/dist/chunks/{Dialog-Bup1WdQn.js.map → Dialog-D9qIh5Jb.js.map} +1 -1
- package/dist/chunks/{Dialog-CEEzQOVl.js → Dialog-ifYDY_7S.js} +2 -2
- package/dist/chunks/{Dialog-CEEzQOVl.js.map → Dialog-ifYDY_7S.js.map} +1 -1
- package/dist/chunks/{FormView-C4Po3Q3z.js → FormView-Bq74K6dj.js} +4 -4
- package/dist/chunks/FormView-Bq74K6dj.js.map +1 -0
- package/dist/chunks/FormView-Dtzh5qLB.js +3 -0
- package/dist/chunks/FormView-Dtzh5qLB.js.map +1 -0
- package/dist/chunks/{MetricsMiniChartWidget-CxTnMgp6.js → MetricsMiniChartWidget-BHLlrFJf.js} +2 -2
- package/dist/chunks/{MetricsMiniChartWidget-CxTnMgp6.js.map → MetricsMiniChartWidget-BHLlrFJf.js.map} +1 -1
- package/dist/chunks/{MetricsMiniChartWidget-CK6K9Kh6.js → MetricsMiniChartWidget-CoapKHw0.js} +3 -3
- package/dist/chunks/{MetricsMiniChartWidget-CK6K9Kh6.js.map → MetricsMiniChartWidget-CoapKHw0.js.map} +1 -1
- package/dist/chunks/{PDFViewer-DgXB4rR9.js → PDFViewer-BXcYgXKx.js} +3 -3
- package/dist/chunks/{PDFViewer-DgXB4rR9.js.map → PDFViewer-BXcYgXKx.js.map} +1 -1
- package/dist/chunks/{PDFViewer-BvBw9BNk.js → PDFViewer-DxoRBrX9.js} +2 -2
- package/dist/chunks/{PDFViewer-BvBw9BNk.js.map → PDFViewer-DxoRBrX9.js.map} +1 -1
- package/dist/chunks/{Page-DaptCfWp.js → Page-CFzSUQLz.js} +2 -2
- package/dist/chunks/{Page-DaptCfWp.js.map → Page-CFzSUQLz.js.map} +1 -1
- package/dist/chunks/{Page-BJxsE5Os.js → Page-Cw4qW3aV.js} +2 -2
- package/dist/chunks/{Page-BJxsE5Os.js.map → Page-Cw4qW3aV.js.map} +1 -1
- package/dist/chunks/{TopNav-BjJojcF7.js → TopNav-DaX7k8M8.js} +5 -5
- package/dist/chunks/{TopNav-BjJojcF7.js.map → TopNav-DaX7k8M8.js.map} +1 -1
- package/dist/chunks/{TopNav-BkpUjJB2.js → TopNav-DgPBfS1e.js} +2 -2
- package/dist/chunks/{TopNav-BkpUjJB2.js.map → TopNav-DgPBfS1e.js.map} +1 -1
- package/dist/chunks/{WebApp-9L0mkLAO.js → WebApp-DHNZIzfN.js} +2 -2
- package/dist/chunks/{WebApp-9L0mkLAO.js.map → WebApp-DHNZIzfN.js.map} +1 -1
- package/dist/chunks/{WebApp-Cd21W4Hv.js → WebApp-MzDCFzar.js} +13 -13
- package/dist/chunks/{WebApp-Cd21W4Hv.js.map → WebApp-MzDCFzar.js.map} +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.es.js +5 -5
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +58 -52
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.es.js +4 -4
- package/package.json +1 -1
- package/dist/chunks/ChatView-CM0ATZID.js +0 -2
- package/dist/chunks/ChatView-CM0ATZID.js.map +0 -1
- package/dist/chunks/ChatView-DJK49g8O.js.map +0 -1
- package/dist/chunks/DataView-3iqbZ_4y.js.map +0 -1
- package/dist/chunks/DataView-DoKRJHii.js +0 -2
- package/dist/chunks/DataView-DoKRJHii.js.map +0 -1
- package/dist/chunks/FormView-BEIw_LH9.js +0 -3
- package/dist/chunks/FormView-BEIw_LH9.js.map +0 -1
- package/dist/chunks/FormView-C4Po3Q3z.js.map +0 -1
package/dist/admin.es.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { P as Page } from "./chunks/Page-
|
|
2
|
-
import { V as View, h as MOJOUtils } from "./chunks/WebApp-
|
|
3
|
-
import { B, b, a, c, e, f, W } from "./chunks/WebApp-
|
|
4
|
-
import Dialog$1 from "./chunks/Dialog-
|
|
5
|
-
import { M as MetricsChart, c as MetricsMiniChartWidget, P as PieChart } from "./chunks/MetricsMiniChartWidget-
|
|
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,
|
|
7
|
-
import DataView from "./chunks/DataView-
|
|
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-
|
|
9
|
-
import { L as LightboxGallery, P as PDFViewer } from "./chunks/PDFViewer-
|
|
10
|
-
import { a as applyFileDropMixin, F as FormView } from "./chunks/FormView-
|
|
1
|
+
import { P as Page } from "./chunks/Page-CFzSUQLz.js";
|
|
2
|
+
import { V as View, h as MOJOUtils } from "./chunks/WebApp-MzDCFzar.js";
|
|
3
|
+
import { B, b, a, c, e, f, W } from "./chunks/WebApp-MzDCFzar.js";
|
|
4
|
+
import Dialog$1 from "./chunks/Dialog-D9qIh5Jb.js";
|
|
5
|
+
import { M as MetricsChart, c as MetricsMiniChartWidget, P as PieChart } from "./chunks/MetricsMiniChartWidget-CoapKHw0.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-D9hJ2u6c.js";
|
|
7
|
+
import DataView from "./chunks/DataView-BDUCD0q0.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-BlmrYFlZ.js";
|
|
9
|
+
import { L as LightboxGallery, P as PDFViewer } from "./chunks/PDFViewer-BXcYgXKx.js";
|
|
10
|
+
import { a as applyFileDropMixin, F as FormView } from "./chunks/FormView-Bq74K6dj.js";
|
|
11
11
|
class AdminHeaderView extends View {
|
|
12
12
|
constructor(options = {}) {
|
|
13
13
|
super({
|
|
@@ -831,6 +831,129 @@ class EmailTemplateTablePage extends TablePage {
|
|
|
831
831
|
});
|
|
832
832
|
}
|
|
833
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"> </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
|
+
}
|
|
834
957
|
class EventView extends View {
|
|
835
958
|
constructor(options = {}) {
|
|
836
959
|
super({
|
|
@@ -886,20 +1009,20 @@ class EventView extends View {
|
|
|
886
1009
|
{ name: "details", label: "Details", columns: 12 }
|
|
887
1010
|
]
|
|
888
1011
|
});
|
|
889
|
-
this.metadataView = new View({
|
|
890
|
-
model: this.model,
|
|
891
|
-
template: `<pre class="bg-light p-3 border rounded"><code>{{{model.metadata|json}}}</code></pre>`
|
|
892
|
-
});
|
|
893
1012
|
const tabs = { "Overview": this.overviewView };
|
|
894
|
-
|
|
895
|
-
|
|
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;
|
|
896
1019
|
}
|
|
897
|
-
if (
|
|
898
|
-
this.
|
|
1020
|
+
if (Object.keys(metadata).length > 0) {
|
|
1021
|
+
this.metadataView = new View({
|
|
899
1022
|
model: this.model,
|
|
900
|
-
template: `<pre class="bg-
|
|
1023
|
+
template: `<pre class="bg-light p-3 border rounded"><code>{{{model.metadata|json}}}</code></pre>`
|
|
901
1024
|
});
|
|
902
|
-
tabs["
|
|
1025
|
+
tabs["Metadata"] = this.metadataView;
|
|
903
1026
|
}
|
|
904
1027
|
this.tabView = new TabView({
|
|
905
1028
|
containerId: "event-tabs",
|
|
@@ -1467,7 +1590,7 @@ class FileTablePage extends TablePage {
|
|
|
1467
1590
|
*/
|
|
1468
1591
|
async onActionAdd(event, element) {
|
|
1469
1592
|
event.preventDefault();
|
|
1470
|
-
const Dialog2 = (await import("./chunks/Dialog-
|
|
1593
|
+
const Dialog2 = (await import("./chunks/Dialog-D9qIh5Jb.js")).default;
|
|
1471
1594
|
const formData = await Dialog2.showForm({
|
|
1472
1595
|
title: "Upload File",
|
|
1473
1596
|
size: "md",
|
|
@@ -1563,6 +1686,185 @@ class FileTablePage extends TablePage {
|
|
|
1563
1686
|
}
|
|
1564
1687
|
}
|
|
1565
1688
|
applyFileDropMixin(FileTablePage);
|
|
1689
|
+
class MapView extends View {
|
|
1690
|
+
constructor(options = {}) {
|
|
1691
|
+
super({
|
|
1692
|
+
className: "map-view",
|
|
1693
|
+
...options
|
|
1694
|
+
});
|
|
1695
|
+
this.markers = options.markers || [];
|
|
1696
|
+
this.center = options.center || null;
|
|
1697
|
+
this.zoom = options.zoom || 13;
|
|
1698
|
+
this.height = options.height || 400;
|
|
1699
|
+
this.showZoomControl = options.showZoomControl !== false;
|
|
1700
|
+
this.tileLayer = options.tileLayer || "osm";
|
|
1701
|
+
this.map = null;
|
|
1702
|
+
this.leafletMarkers = [];
|
|
1703
|
+
this.template = `
|
|
1704
|
+
<div class="map-container">
|
|
1705
|
+
<div id="map-{{id}}" style="height: {{height}}px; width: 100%; border-radius: 0.375rem; border: 1px solid #dee2e6;"></div>
|
|
1706
|
+
</div>
|
|
1707
|
+
`;
|
|
1708
|
+
}
|
|
1709
|
+
async onAfterRender() {
|
|
1710
|
+
await this.loadLeaflet();
|
|
1711
|
+
await this.initializeMap();
|
|
1712
|
+
}
|
|
1713
|
+
async loadLeaflet() {
|
|
1714
|
+
if (window.L) return;
|
|
1715
|
+
const cssLoaded = new Promise((resolve) => {
|
|
1716
|
+
const link = document.createElement("link");
|
|
1717
|
+
link.rel = "stylesheet";
|
|
1718
|
+
link.href = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css";
|
|
1719
|
+
link.onload = resolve;
|
|
1720
|
+
link.onerror = resolve;
|
|
1721
|
+
document.head.appendChild(link);
|
|
1722
|
+
});
|
|
1723
|
+
const jsLoaded = new Promise((resolve, reject) => {
|
|
1724
|
+
const script = document.createElement("script");
|
|
1725
|
+
script.src = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js";
|
|
1726
|
+
script.onload = resolve;
|
|
1727
|
+
script.onerror = reject;
|
|
1728
|
+
document.head.appendChild(script);
|
|
1729
|
+
});
|
|
1730
|
+
await Promise.all([cssLoaded, jsLoaded]);
|
|
1731
|
+
}
|
|
1732
|
+
getTileLayerUrl() {
|
|
1733
|
+
const tileLayers = {
|
|
1734
|
+
// Standard street maps
|
|
1735
|
+
osm: {
|
|
1736
|
+
url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
|
1737
|
+
attribution: "© OpenStreetMap contributors",
|
|
1738
|
+
maxZoom: 19
|
|
1739
|
+
},
|
|
1740
|
+
// Satellite imagery
|
|
1741
|
+
satellite: {
|
|
1742
|
+
url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
|
|
1743
|
+
attribution: "© Esri",
|
|
1744
|
+
maxZoom: 19
|
|
1745
|
+
},
|
|
1746
|
+
// Terrain and topographic
|
|
1747
|
+
terrain: {
|
|
1748
|
+
url: "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png",
|
|
1749
|
+
attribution: "© OpenTopoMap contributors",
|
|
1750
|
+
maxZoom: 17
|
|
1751
|
+
},
|
|
1752
|
+
// Dark mode styles
|
|
1753
|
+
dark: {
|
|
1754
|
+
url: "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png",
|
|
1755
|
+
attribution: "© OpenStreetMap contributors © CARTO",
|
|
1756
|
+
maxZoom: 20
|
|
1757
|
+
},
|
|
1758
|
+
// Light/minimal styles
|
|
1759
|
+
light: {
|
|
1760
|
+
url: "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
|
|
1761
|
+
attribution: "© OpenStreetMap contributors © CARTO",
|
|
1762
|
+
maxZoom: 20
|
|
1763
|
+
},
|
|
1764
|
+
// Watercolor artistic style
|
|
1765
|
+
watercolor: {
|
|
1766
|
+
url: "https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg",
|
|
1767
|
+
attribution: "© Stadia Maps © Stamen Design © OpenStreetMap contributors",
|
|
1768
|
+
maxZoom: 16
|
|
1769
|
+
},
|
|
1770
|
+
// Black and white
|
|
1771
|
+
bw: {
|
|
1772
|
+
url: "https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png",
|
|
1773
|
+
attribution: "© OpenStreetMap contributors © CARTO",
|
|
1774
|
+
maxZoom: 20
|
|
1775
|
+
},
|
|
1776
|
+
// Streets with labels
|
|
1777
|
+
streets: {
|
|
1778
|
+
url: "https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png",
|
|
1779
|
+
attribution: "© OpenStreetMap contributors © CARTO",
|
|
1780
|
+
maxZoom: 20
|
|
1781
|
+
}
|
|
1782
|
+
};
|
|
1783
|
+
return tileLayers[this.tileLayer] || tileLayers.osm;
|
|
1784
|
+
}
|
|
1785
|
+
async initializeMap() {
|
|
1786
|
+
const mapElement = this.element.querySelector(`#map-${this.id}`);
|
|
1787
|
+
if (!mapElement || !window.L) return;
|
|
1788
|
+
let mapCenter = this.center;
|
|
1789
|
+
if (!mapCenter && this.markers.length > 0) {
|
|
1790
|
+
mapCenter = [this.markers[0].lat, this.markers[0].lng];
|
|
1791
|
+
}
|
|
1792
|
+
if (!mapCenter) {
|
|
1793
|
+
mapCenter = [0, 0];
|
|
1794
|
+
}
|
|
1795
|
+
this.map = window.L.map(mapElement, {
|
|
1796
|
+
center: mapCenter,
|
|
1797
|
+
zoom: this.zoom,
|
|
1798
|
+
zoomControl: this.showZoomControl
|
|
1799
|
+
});
|
|
1800
|
+
const tileConfig = this.getTileLayerUrl();
|
|
1801
|
+
window.L.tileLayer(tileConfig.url, {
|
|
1802
|
+
attribution: tileConfig.attribution,
|
|
1803
|
+
maxZoom: tileConfig.maxZoom
|
|
1804
|
+
}).addTo(this.map);
|
|
1805
|
+
this.addMarkers(this.markers);
|
|
1806
|
+
if (this.markers.length > 1) {
|
|
1807
|
+
this.fitBounds();
|
|
1808
|
+
}
|
|
1809
|
+
setTimeout(() => {
|
|
1810
|
+
if (this.map) {
|
|
1811
|
+
this.map.invalidateSize();
|
|
1812
|
+
}
|
|
1813
|
+
}, 300);
|
|
1814
|
+
}
|
|
1815
|
+
addMarkers(markers) {
|
|
1816
|
+
if (!this.map || !Array.isArray(markers)) return;
|
|
1817
|
+
markers.forEach((markerData) => {
|
|
1818
|
+
const { lat, lng, popup, icon } = markerData;
|
|
1819
|
+
if (!lat || !lng) return;
|
|
1820
|
+
const markerOptions = {};
|
|
1821
|
+
if (icon) {
|
|
1822
|
+
markerOptions.icon = window.L.icon(icon);
|
|
1823
|
+
}
|
|
1824
|
+
const marker = window.L.marker([lat, lng], markerOptions).addTo(this.map);
|
|
1825
|
+
if (popup) {
|
|
1826
|
+
marker.bindPopup(popup);
|
|
1827
|
+
}
|
|
1828
|
+
this.leafletMarkers.push(marker);
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
fitBounds() {
|
|
1832
|
+
if (!this.map || this.leafletMarkers.length === 0) return;
|
|
1833
|
+
const group = new window.L.featureGroup(this.leafletMarkers);
|
|
1834
|
+
this.map.fitBounds(group.getBounds().pad(0.1));
|
|
1835
|
+
}
|
|
1836
|
+
updateMarkers(newMarkers) {
|
|
1837
|
+
this.clearMarkers();
|
|
1838
|
+
this.markers = newMarkers;
|
|
1839
|
+
this.addMarkers(newMarkers);
|
|
1840
|
+
if (newMarkers.length > 1) {
|
|
1841
|
+
this.fitBounds();
|
|
1842
|
+
} else if (newMarkers.length === 1) {
|
|
1843
|
+
this.map.setView([newMarkers[0].lat, newMarkers[0].lng], this.zoom);
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
clearMarkers() {
|
|
1847
|
+
this.leafletMarkers.forEach((marker) => {
|
|
1848
|
+
this.map.removeLayer(marker);
|
|
1849
|
+
});
|
|
1850
|
+
this.leafletMarkers = [];
|
|
1851
|
+
}
|
|
1852
|
+
setView(lat, lng, zoom = null) {
|
|
1853
|
+
if (!this.map) return;
|
|
1854
|
+
this.map.setView([lat, lng], zoom || this.zoom);
|
|
1855
|
+
}
|
|
1856
|
+
setZoom(zoom) {
|
|
1857
|
+
if (!this.map) return;
|
|
1858
|
+
this.map.setZoom(zoom);
|
|
1859
|
+
}
|
|
1860
|
+
async onBeforeDestroy() {
|
|
1861
|
+
if (this.map) {
|
|
1862
|
+
this.map.remove();
|
|
1863
|
+
this.map = null;
|
|
1864
|
+
}
|
|
1865
|
+
await super.onBeforeDestroy();
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1566
1868
|
class GeoIPView extends View {
|
|
1567
1869
|
constructor(options = {}) {
|
|
1568
1870
|
super({
|
|
@@ -1597,36 +1899,99 @@ class GeoIPView extends View {
|
|
|
1597
1899
|
</div>
|
|
1598
1900
|
</div>
|
|
1599
1901
|
|
|
1600
|
-
<!--
|
|
1601
|
-
<div data-container="geoip-
|
|
1902
|
+
<!-- Tabs -->
|
|
1903
|
+
<div data-container="geoip-tabs"></div>
|
|
1602
1904
|
</div>
|
|
1603
1905
|
`;
|
|
1604
1906
|
}
|
|
1605
1907
|
async onInit() {
|
|
1606
|
-
this.
|
|
1607
|
-
containerId: "geoip-data-view",
|
|
1908
|
+
this.detailsView = new DataView({
|
|
1608
1909
|
model: this.model,
|
|
1609
|
-
className: "p-3
|
|
1910
|
+
className: "p-3",
|
|
1610
1911
|
showEmptyValues: true,
|
|
1611
1912
|
emptyValueText: "—",
|
|
1612
1913
|
columns: 2,
|
|
1613
1914
|
fields: [
|
|
1614
|
-
{ name: "
|
|
1615
|
-
{ name: "subnet", label: "Subnet" },
|
|
1616
|
-
{ name: "
|
|
1617
|
-
{ name: "
|
|
1618
|
-
{ name: "
|
|
1619
|
-
{ name: "
|
|
1620
|
-
{ name: "
|
|
1621
|
-
{ name: "
|
|
1622
|
-
{ name: "
|
|
1623
|
-
{ name: "
|
|
1624
|
-
{ name: "modified", label: "Last Modified", format: "datetime" },
|
|
1625
|
-
{ name: "expires_at", label: "Expires", format: "datetime" }
|
|
1915
|
+
{ name: "ip_address", label: "IP Address", cols: 4 },
|
|
1916
|
+
{ name: "subnet", label: "Subnet", cols: 4 },
|
|
1917
|
+
{ name: "country_name", label: "Country", cols: 4 },
|
|
1918
|
+
{ name: "country_code", label: "Country Code", cols: 4 },
|
|
1919
|
+
{ name: "region", label: "Region", cols: 4 },
|
|
1920
|
+
{ name: "city", label: "City", cols: 4 },
|
|
1921
|
+
{ name: "postal_code", label: "Postal Code", cols: 4 },
|
|
1922
|
+
{ name: "timezone", label: "Timezone", cols: 4 },
|
|
1923
|
+
{ name: "latitude", label: "Latitude", cols: 4 },
|
|
1924
|
+
{ name: "longitude", label: "Longitude", cols: 4 }
|
|
1626
1925
|
]
|
|
1627
1926
|
});
|
|
1628
|
-
this.
|
|
1927
|
+
this.securityView = new DataView({
|
|
1928
|
+
model: this.model,
|
|
1929
|
+
className: "p-3",
|
|
1930
|
+
showEmptyValues: true,
|
|
1931
|
+
emptyValueText: "—",
|
|
1932
|
+
columns: 2,
|
|
1933
|
+
fields: [
|
|
1934
|
+
{ name: "threat_level", label: "Threat Level", cols: 4 },
|
|
1935
|
+
{ name: "is_tor", label: "TOR Exit Node", cols: 4 },
|
|
1936
|
+
{ name: "is_vpn", label: "VPN", formatter: "yesnoicon", cols: 4 },
|
|
1937
|
+
{ name: "is_proxy", label: "Proxy", formatter: "yesnoicon", cols: 4 },
|
|
1938
|
+
{ name: "is_cloud", label: "Cloud Provider", formatter: "yesnoicon", cols: 4 },
|
|
1939
|
+
{ name: "is_datacenter", label: "Datacenter", formatter: "yesnoicon", cols: 4 },
|
|
1940
|
+
{ name: "asn", label: "ASN", cols: 4 },
|
|
1941
|
+
{ name: "asn_org", label: "ASN Organization", cols: 4 },
|
|
1942
|
+
{ name: "isp", label: "ISP", cols: 4 },
|
|
1943
|
+
{ name: "connection_type", label: "Connection Type", cols: 6 }
|
|
1944
|
+
]
|
|
1945
|
+
});
|
|
1946
|
+
this.metadataView = new DataView({
|
|
1947
|
+
model: this.model,
|
|
1948
|
+
className: "p-3",
|
|
1949
|
+
showEmptyValues: true,
|
|
1950
|
+
emptyValueText: "—",
|
|
1951
|
+
columns: 2,
|
|
1952
|
+
fields: [
|
|
1953
|
+
{ name: "id", label: "Record ID", cols: 6 },
|
|
1954
|
+
{ name: "provider", label: "Data Provider", formatter: "capitalize", cols: 6 },
|
|
1955
|
+
{ name: "created", label: "Created", formatter: "datetime", cols: 6 },
|
|
1956
|
+
{ name: "modified", label: "Last Modified", formatter: "datetime", cols: 6 },
|
|
1957
|
+
{ name: "expires_at", label: "Expires", formatter: "datetime", cols: 12 }
|
|
1958
|
+
]
|
|
1959
|
+
});
|
|
1960
|
+
const tabs = {
|
|
1961
|
+
"Location": this.detailsView,
|
|
1962
|
+
"Security": this.securityView,
|
|
1963
|
+
"Metadata": this.metadataView
|
|
1964
|
+
};
|
|
1965
|
+
if (this.hasCoordinates) {
|
|
1966
|
+
const lat = this.model.get("latitude");
|
|
1967
|
+
const lng = this.model.get("longitude");
|
|
1968
|
+
const city = this.model.get("city") || "Unknown";
|
|
1969
|
+
const region = this.model.get("region") || "";
|
|
1970
|
+
const country = this.model.get("country_name") || "";
|
|
1971
|
+
const locationStr = [city, region, country].filter(Boolean).join(", ");
|
|
1972
|
+
this.mapView = new MapView({
|
|
1973
|
+
markers: [{
|
|
1974
|
+
lat,
|
|
1975
|
+
lng,
|
|
1976
|
+
popup: `<strong>${this.model.get("ip_address")}</strong><br>${locationStr}`
|
|
1977
|
+
}],
|
|
1978
|
+
tileLayer: "light",
|
|
1979
|
+
zoom: 4,
|
|
1980
|
+
height: 450
|
|
1981
|
+
});
|
|
1982
|
+
tabs["Map"] = this.mapView;
|
|
1983
|
+
}
|
|
1984
|
+
this.tabView = new TabView({
|
|
1985
|
+
containerId: "geoip-tabs",
|
|
1986
|
+
tabs,
|
|
1987
|
+
activeTab: this.hasCoordinates ? "Map" : "Location"
|
|
1988
|
+
});
|
|
1989
|
+
this.addChild(this.tabView);
|
|
1629
1990
|
const menuItems = [
|
|
1991
|
+
{ label: "Edit Location", action: "edit-location", icon: "bi-geo-alt" },
|
|
1992
|
+
{ label: "Edit Security", action: "edit-security", icon: "bi-shield-lock" },
|
|
1993
|
+
{ label: "Edit Network", action: "edit-network", icon: "bi-diagram-3" },
|
|
1994
|
+
{ type: "divider" },
|
|
1630
1995
|
{ label: "Refresh Geolocation", action: "refresh-geoip", icon: "bi-arrow-clockwise" }
|
|
1631
1996
|
];
|
|
1632
1997
|
if (this.hasCoordinates) {
|
|
@@ -1651,8 +2016,41 @@ class GeoIPView extends View {
|
|
|
1651
2016
|
});
|
|
1652
2017
|
this.addChild(geoIPMenu);
|
|
1653
2018
|
}
|
|
2019
|
+
async onActionEditLocation() {
|
|
2020
|
+
const resp = await Dialog$1.showModelForm({
|
|
2021
|
+
title: `Edit Location - ${this.model.get("ip_address")}`,
|
|
2022
|
+
model: this.model,
|
|
2023
|
+
formConfig: GeoLocatedIP.EDIT_LOCATION_FORM
|
|
2024
|
+
});
|
|
2025
|
+
if (resp) {
|
|
2026
|
+
await this.render();
|
|
2027
|
+
this.getApp()?.toast?.success("Location updated successfully");
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
async onActionEditSecurity() {
|
|
2031
|
+
const resp = await Dialog$1.showModelForm({
|
|
2032
|
+
title: `Edit Security - ${this.model.get("ip_address")}`,
|
|
2033
|
+
model: this.model,
|
|
2034
|
+
formConfig: GeoLocatedIP.EDIT_SECURITY_FORM
|
|
2035
|
+
});
|
|
2036
|
+
if (resp) {
|
|
2037
|
+
await this.render();
|
|
2038
|
+
this.getApp()?.toast?.success("Security settings updated successfully");
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
async onActionEditNetwork() {
|
|
2042
|
+
const resp = await Dialog$1.showModelForm({
|
|
2043
|
+
title: `Edit Network - ${this.model.get("ip_address")}`,
|
|
2044
|
+
model: this.model,
|
|
2045
|
+
formConfig: GeoLocatedIP.EDIT_NETWORK_FORM
|
|
2046
|
+
});
|
|
2047
|
+
if (resp) {
|
|
2048
|
+
await this.render();
|
|
2049
|
+
this.getApp()?.toast?.success("Network information updated successfully");
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
1654
2052
|
async onActionRefreshGeoip() {
|
|
1655
|
-
|
|
2053
|
+
await this.model.save({ refresh: true });
|
|
1656
2054
|
this.getApp()?.toast?.info("Refresh request sent for " + this.model.get("ip_address"));
|
|
1657
2055
|
}
|
|
1658
2056
|
async onActionViewOnMap() {
|
|
@@ -1703,20 +2101,19 @@ class GeoLocatedIPTablePage extends TablePage {
|
|
|
1703
2101
|
pageName: "GeoIP Cache",
|
|
1704
2102
|
router: "admin/system/geoip",
|
|
1705
2103
|
Collection: GeoLocatedIPList,
|
|
1706
|
-
|
|
2104
|
+
itemView: GeoIPView,
|
|
1707
2105
|
viewDialogOptions: {
|
|
1708
2106
|
header: false,
|
|
1709
|
-
size: "
|
|
2107
|
+
size: "xl"
|
|
1710
2108
|
},
|
|
1711
2109
|
// Column definitions
|
|
1712
2110
|
columns: [
|
|
1713
|
-
{ key: "id", label: "ID", width: "70px", sortable: true, class: "text-muted" },
|
|
1714
2111
|
{ key: "ip_address", label: "IP Address", sortable: true },
|
|
1715
2112
|
{ key: "city", label: "City", sortable: true, formatter: "default('—')" },
|
|
1716
2113
|
{ key: "region", label: "Region", sortable: true, formatter: "default('—')" },
|
|
1717
2114
|
{ key: "country_name", label: "Country", sortable: true, formatter: "default('—')" },
|
|
1718
|
-
{ key: "
|
|
1719
|
-
{ key: "
|
|
2115
|
+
{ key: "isp", label: "ISP", sortable: true, formatter: "default('—')" },
|
|
2116
|
+
{ key: "threat_level", label: "Threat", formatter: "default('—')" }
|
|
1720
2117
|
],
|
|
1721
2118
|
// Table features
|
|
1722
2119
|
selectable: true,
|
|
@@ -1724,6 +2121,9 @@ class GeoLocatedIPTablePage extends TablePage {
|
|
|
1724
2121
|
sortable: true,
|
|
1725
2122
|
filterable: true,
|
|
1726
2123
|
paginated: true,
|
|
2124
|
+
// Actions
|
|
2125
|
+
// actions: ['view', 'edit', 'delete'],
|
|
2126
|
+
clickAction: "view",
|
|
1727
2127
|
// Toolbar
|
|
1728
2128
|
showRefresh: true,
|
|
1729
2129
|
showAdd: false,
|
|
@@ -2365,13 +2765,39 @@ class IncidentView extends View {
|
|
|
2365
2765
|
{ name: "details", label: "Details", columns: 12, format: "pre" }
|
|
2366
2766
|
]
|
|
2367
2767
|
});
|
|
2768
|
+
const eventsCollection = new IncidentEventList({
|
|
2769
|
+
params: { incident: this.model.get("id") }
|
|
2770
|
+
});
|
|
2771
|
+
this.eventsView = new TableView({
|
|
2772
|
+
collection: eventsCollection,
|
|
2773
|
+
hideActivePillNames: ["incident"],
|
|
2774
|
+
columns: [
|
|
2775
|
+
{ key: "id", label: "ID", width: "70px", sortable: true },
|
|
2776
|
+
{ key: "created", label: "Date", formatter: "datetime", sortable: true, width: "180px" },
|
|
2777
|
+
{ key: "category", label: "Category", formatter: "badge", sortable: true },
|
|
2778
|
+
{ key: "title", label: "Title", sortable: true },
|
|
2779
|
+
{ key: "level", label: "Level", sortable: true, width: "80px" }
|
|
2780
|
+
],
|
|
2781
|
+
showAdd: false,
|
|
2782
|
+
actions: ["view"],
|
|
2783
|
+
paginated: true,
|
|
2784
|
+
size: 10
|
|
2785
|
+
});
|
|
2368
2786
|
const adapter = new IncidentHistoryAdapter(this.model.get("id"));
|
|
2369
2787
|
this.historyView = new ChatView({ adapter });
|
|
2370
2788
|
const tabs = {
|
|
2371
2789
|
"Overview": this.overviewView,
|
|
2790
|
+
"Events": this.eventsView,
|
|
2372
2791
|
"History & Comments": this.historyView
|
|
2373
2792
|
};
|
|
2374
|
-
|
|
2793
|
+
const metadata = this.model.get("metadata") || {};
|
|
2794
|
+
if (metadata.stack_trace) {
|
|
2795
|
+
this.stackTraceView = new StackTraceView({
|
|
2796
|
+
stackTrace: metadata.stack_trace
|
|
2797
|
+
});
|
|
2798
|
+
tabs["Stack Trace"] = this.stackTraceView;
|
|
2799
|
+
}
|
|
2800
|
+
if (Object.keys(metadata).length > 0) {
|
|
2375
2801
|
this.metadataView = new View({
|
|
2376
2802
|
model: this.model,
|
|
2377
2803
|
template: `<pre class="bg-light p-3 border rounded"><code>{{{model.metadata|json}}}</code></pre>`
|
|
@@ -2507,7 +2933,7 @@ class IncidentTablePage extends TablePage {
|
|
|
2507
2933
|
{ label: "Resolve", icon: "bi bi-check-circle", action: "resolve" },
|
|
2508
2934
|
{ label: "Pause", icon: "bi bi-pause-circle", action: "pause" },
|
|
2509
2935
|
{ label: "Ignore", icon: "bi bi-x-circle", action: "ignore" },
|
|
2510
|
-
{ label: "
|
|
2936
|
+
{ label: "Merge", icon: "bi bi-merge", action: "merge" }
|
|
2511
2937
|
],
|
|
2512
2938
|
// Table display options
|
|
2513
2939
|
tableOptions: {
|
|
@@ -2554,6 +2980,29 @@ class IncidentTablePage extends TablePage {
|
|
|
2554
2980
|
await Promise.all(selected.map((item) => item.model.save({ status: "ignored" })));
|
|
2555
2981
|
this.tableView.collection.fetch();
|
|
2556
2982
|
}
|
|
2983
|
+
async onActionBatchMerge(_event, _element) {
|
|
2984
|
+
const selected = this.tableView.getSelectedItems();
|
|
2985
|
+
if (!selected.length) return;
|
|
2986
|
+
const app = this.getApp();
|
|
2987
|
+
const result = await app.showForm({
|
|
2988
|
+
title: `Merge ${selected.length} incidents`,
|
|
2989
|
+
fields: [
|
|
2990
|
+
{
|
|
2991
|
+
name: "merge",
|
|
2992
|
+
type: "select",
|
|
2993
|
+
label: "Select Parent Incident",
|
|
2994
|
+
options: selected.map((item) => ({ value: item.model.id, label: item.model.id })),
|
|
2995
|
+
required: true
|
|
2996
|
+
}
|
|
2997
|
+
]
|
|
2998
|
+
});
|
|
2999
|
+
if (!result) return;
|
|
3000
|
+
const parentModel = selected.find((item) => item.model.id == result.merge)?.model;
|
|
3001
|
+
if (!parentModel) return;
|
|
3002
|
+
const mergeIds = selected.map((item) => item.model.id).filter((id) => id != result.merge);
|
|
3003
|
+
await parentModel.save({ merge: mergeIds });
|
|
3004
|
+
this.tableView.collection.fetch();
|
|
3005
|
+
}
|
|
2557
3006
|
}
|
|
2558
3007
|
class JobStatsView extends View {
|
|
2559
3008
|
constructor(options = {}) {
|
|
@@ -5013,20 +5462,36 @@ class RuleSetView extends View {
|
|
|
5013
5462
|
`;
|
|
5014
5463
|
}
|
|
5015
5464
|
async onInit() {
|
|
5465
|
+
const matchByValue = this.model.get("match_by");
|
|
5466
|
+
const matchByOption = MatchByOptions.find((opt) => opt.value === matchByValue);
|
|
5467
|
+
const matchByLabel = matchByOption ? matchByOption.label : String(matchByValue);
|
|
5468
|
+
const bundleByValue = this.model.get("bundle_by");
|
|
5469
|
+
const bundleByOption = BundleByOptions.find((opt) => opt.value === bundleByValue);
|
|
5470
|
+
const bundleByLabel = bundleByOption ? bundleByOption.label : String(bundleByValue);
|
|
5016
5471
|
this.configView = new DataView({
|
|
5017
5472
|
model: this.model,
|
|
5018
5473
|
className: "p-3",
|
|
5019
5474
|
columns: 2,
|
|
5020
5475
|
fields: [
|
|
5021
|
-
{ name: "id", label: "RuleSet ID" },
|
|
5022
|
-
{ name: "
|
|
5023
|
-
{ name: "
|
|
5024
|
-
{ name: "
|
|
5025
|
-
{ name: "
|
|
5026
|
-
{
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5476
|
+
{ name: "id", label: "RuleSet ID", cols: 6 },
|
|
5477
|
+
{ name: "priority", label: "Priority", cols: 6 },
|
|
5478
|
+
{ name: "name", label: "Name", cols: 12 },
|
|
5479
|
+
{ name: "category", label: "Category", formatter: "badge", cols: 6 },
|
|
5480
|
+
{ name: "is_active", label: "Status", formatter: "boolean", cols: 6 },
|
|
5481
|
+
{
|
|
5482
|
+
name: "match_by",
|
|
5483
|
+
label: "Match Logic",
|
|
5484
|
+
template: matchByLabel,
|
|
5485
|
+
cols: 12
|
|
5486
|
+
},
|
|
5487
|
+
{
|
|
5488
|
+
name: "bundle_by",
|
|
5489
|
+
label: "Bundle By",
|
|
5490
|
+
template: bundleByLabel,
|
|
5491
|
+
cols: 12
|
|
5492
|
+
},
|
|
5493
|
+
{ name: "bundle_minutes", label: "Bundle Minutes", cols: 6 },
|
|
5494
|
+
{ name: "handler", label: "Handler", cols: 12 }
|
|
5030
5495
|
]
|
|
5031
5496
|
});
|
|
5032
5497
|
const rulesCollection = new RuleList({
|
|
@@ -5038,12 +5503,23 @@ class RuleSetView extends View {
|
|
|
5038
5503
|
{ key: "id", label: "ID", width: "70px" },
|
|
5039
5504
|
{ key: "name", label: "Name" },
|
|
5040
5505
|
{ key: "field_name", label: "Field" },
|
|
5041
|
-
{ key: "comparator", label: "Comparator" },
|
|
5506
|
+
{ key: "comparator", label: "Comparator", width: "120px" },
|
|
5042
5507
|
{ key: "value", label: "Value" },
|
|
5043
|
-
{ key: "value_type", label: "Type" }
|
|
5508
|
+
{ key: "value_type", label: "Type", width: "100px" }
|
|
5044
5509
|
],
|
|
5045
5510
|
showAdd: true,
|
|
5046
|
-
|
|
5511
|
+
clickAction: "edit",
|
|
5512
|
+
actions: ["edit", "delete"],
|
|
5513
|
+
contextMenu: [
|
|
5514
|
+
{ label: "Edit Rule", action: "edit", icon: "bi-pencil" },
|
|
5515
|
+
{ label: "Duplicate Rule", action: "duplicate", icon: "bi-files" },
|
|
5516
|
+
{ divider: true },
|
|
5517
|
+
{ label: "Delete Rule", action: "delete", icon: "bi-trash", danger: true }
|
|
5518
|
+
],
|
|
5519
|
+
// Pass the parent ID so new rules get associated with this ruleset
|
|
5520
|
+
defaultData: {
|
|
5521
|
+
parent: this.model.get("id")
|
|
5522
|
+
}
|
|
5047
5523
|
});
|
|
5048
5524
|
this.tabView = new TabView({
|
|
5049
5525
|
containerId: "ruleset-tabs",
|
|
@@ -5069,6 +5545,73 @@ class RuleSetView extends View {
|
|
|
5069
5545
|
});
|
|
5070
5546
|
this.addChild(contextMenu);
|
|
5071
5547
|
}
|
|
5548
|
+
/**
|
|
5549
|
+
* Action handler: Edit RuleSet
|
|
5550
|
+
*/
|
|
5551
|
+
async onActionEditRuleset() {
|
|
5552
|
+
const resp = await Dialog$1.showModelForm({
|
|
5553
|
+
title: `Edit RuleSet - ${this.model.get("name")}`,
|
|
5554
|
+
model: this.model,
|
|
5555
|
+
formConfig: RuleSet.EDIT_FORM
|
|
5556
|
+
});
|
|
5557
|
+
if (resp) {
|
|
5558
|
+
await this.render();
|
|
5559
|
+
}
|
|
5560
|
+
}
|
|
5561
|
+
/**
|
|
5562
|
+
* Action handler: Disable/Enable RuleSet
|
|
5563
|
+
*/
|
|
5564
|
+
async onActionDisableRuleset() {
|
|
5565
|
+
const isActive = this.model.get("is_active");
|
|
5566
|
+
const newStatus = !isActive;
|
|
5567
|
+
try {
|
|
5568
|
+
this.model.set("is_active", newStatus);
|
|
5569
|
+
await this.model.save();
|
|
5570
|
+
await this.render();
|
|
5571
|
+
Dialog$1.showToast({
|
|
5572
|
+
message: `RuleSet ${newStatus ? "enabled" : "disabled"} successfully`,
|
|
5573
|
+
type: "success"
|
|
5574
|
+
});
|
|
5575
|
+
} catch (error) {
|
|
5576
|
+
Dialog$1.showToast({
|
|
5577
|
+
message: `Failed to update RuleSet: ${error.message}`,
|
|
5578
|
+
type: "error"
|
|
5579
|
+
});
|
|
5580
|
+
}
|
|
5581
|
+
}
|
|
5582
|
+
/**
|
|
5583
|
+
* Action handler: Delete RuleSet
|
|
5584
|
+
*/
|
|
5585
|
+
async onActionDeleteRuleset() {
|
|
5586
|
+
const confirmed = await Dialog$1.confirm({
|
|
5587
|
+
title: "Delete RuleSet",
|
|
5588
|
+
message: `Are you sure you want to delete the ruleset "${this.model.get("name")}"? This action cannot be undone.`,
|
|
5589
|
+
confirmText: "Delete",
|
|
5590
|
+
confirmClass: "btn-danger"
|
|
5591
|
+
});
|
|
5592
|
+
if (confirmed) {
|
|
5593
|
+
try {
|
|
5594
|
+
await this.model.destroy();
|
|
5595
|
+
Dialog$1.showToast({
|
|
5596
|
+
message: "RuleSet deleted successfully",
|
|
5597
|
+
type: "success"
|
|
5598
|
+
});
|
|
5599
|
+
const dialog = this.element?.closest(".modal");
|
|
5600
|
+
if (dialog) {
|
|
5601
|
+
const bsModal = bootstrap.Modal.getInstance(dialog);
|
|
5602
|
+
if (bsModal) {
|
|
5603
|
+
bsModal.hide();
|
|
5604
|
+
}
|
|
5605
|
+
}
|
|
5606
|
+
this.emit("ruleset:deleted", { model: this.model });
|
|
5607
|
+
} catch (error) {
|
|
5608
|
+
Dialog$1.showToast({
|
|
5609
|
+
message: `Failed to delete RuleSet: ${error.message}`,
|
|
5610
|
+
type: "error"
|
|
5611
|
+
});
|
|
5612
|
+
}
|
|
5613
|
+
}
|
|
5614
|
+
}
|
|
5072
5615
|
}
|
|
5073
5616
|
RuleSetView.VIEW_CLASS = RuleSetView;
|
|
5074
5617
|
class RuleSetTablePage extends TablePage {
|
|
@@ -5079,7 +5622,7 @@ class RuleSetTablePage extends TablePage {
|
|
|
5079
5622
|
pageName: "Rule Engine",
|
|
5080
5623
|
router: "admin/rulesets",
|
|
5081
5624
|
Collection: RuleSetList,
|
|
5082
|
-
|
|
5625
|
+
itemView: RuleSetView,
|
|
5083
5626
|
viewDialogOptions: {
|
|
5084
5627
|
header: false,
|
|
5085
5628
|
size: "xl"
|
|
@@ -5089,8 +5632,7 @@ class RuleSetTablePage extends TablePage {
|
|
|
5089
5632
|
{ key: "name", label: "Name", sortable: true },
|
|
5090
5633
|
{ key: "category", label: "Category", sortable: true, formatter: "badge" },
|
|
5091
5634
|
{ key: "priority", label: "Priority", sortable: true },
|
|
5092
|
-
{ key: "match_by", label: "Match Logic", formatter: (v) => v === 0 ? "ALL" : "ANY" }
|
|
5093
|
-
{ key: "is_active", label: "Status", formatter: "boolean|badge('Active:success,Inactive:secondary')" }
|
|
5635
|
+
{ key: "match_by", label: "Match Logic", formatter: (v) => v === 0 ? "ALL" : "ANY" }
|
|
5094
5636
|
],
|
|
5095
5637
|
selectable: true,
|
|
5096
5638
|
searchable: true,
|