diff --git a/html/css/app.css b/html/css/app.css index ab21f820..4b79ad05 100644 --- a/html/css/app.css +++ b/html/css/app.css @@ -673,3 +673,15 @@ code { .warning-message a:visited { color: white; } + +tbody tr:hover { + background-color: transparent !important; +} + +.theme--dark tbody tr:hover:nth-of-type(even) { + background-color: rgba(45, 45, 45, 0.25) !important; +} + +.theme--light tbody tr:hover:nth-of-type(even) { + background-color: rgba(230, 230, 230, 0.25) !important; +} \ No newline at end of file diff --git a/html/index.html b/html/index.html index 6b8b675c..9b1ba3a4 100644 --- a/html/index.html +++ b/html/index.html @@ -1141,7 +1141,6 @@

{{ detect.title }}

fa-history
{{ i18n.history }}
- @@ -1288,7 +1287,6 @@

{{ i18n.commentAddDetection }}

- @@ -1310,7 +1308,6 @@

{{ i18n.commentAddDetection }}

- @@ -1395,8 +1392,9 @@

{{ i18n.commentAddDetection }}

- {{ item[field.value] }} - + + {{ $root.tryLocalize(item[field.value]) }} + {{ item[field.value] }}
@@ -1487,7 +1485,7 @@

{{ i18n.commentAddDetection }}

- IP: + {{i18n.ip}}:
{{item.ip}} @@ -1622,158 +1620,257 @@

{{ i18n.commentAddDetection }}

- - - - + + +
+ +
diff --git a/html/js/app.js b/html/js/app.js index bd357e9f..64f01b36 100644 --- a/html/js/app.js +++ b/html/js/app.js @@ -1208,7 +1208,7 @@ $(document).ready(function() { }, dateAwareSort(items, index, isDesc) { items.sort((a, b) => { - if (index[0] === 'createTime' || index[0] === 'updateTime') { + if (index[0] === 'createTime' || index[0] === 'updateTime' || index[0] === 'createdAt' || index[0] === 'updatedAt') { if (!isDesc[0]) { return new Date(a[index]) - new Date(b[index]); } @@ -1218,10 +1218,10 @@ $(document).ready(function() { if (typeof a[index] !== 'undefined') { if (!isDesc[0]) { - return a[index].toLowerCase().localeCompare(b[index].toLowerCase()); + return (a[index]+'').toLowerCase().localeCompare((b[index]+'').toLowerCase()); } - return b[index].toLowerCase().localeCompare(a[index].toLowerCase()); + return (b[index]+'').toLowerCase().localeCompare((a[index]+'').toLowerCase()); } }); diff --git a/html/js/i18n.js b/html/js/i18n.js index 013f1a94..5d387687 100644 --- a/html/js/i18n.js +++ b/html/js/i18n.js @@ -536,6 +536,7 @@ const i18n = { invalid: 'Invalid', ioWait: 'I/O Wait', invalidCidrOrVar: 'Invalid CIDR Notation or Suricata Variable', + ip: 'IP', ipCidr: 'IP - CIDR Notation or Suricata Variable', job: 'Job', jobIncomplete: 'The job was unable to complete and will retry within a few minutes. Details are available below.', @@ -652,6 +653,7 @@ const i18n = { overrideDeleteHelp: 'Delete this rule override', overrideDocumentationHelp: 'Click for more information', overrideExpand: 'Show all override fields', + overrides: 'Overrides', overview: 'Overview', owner: 'Owner', packages: 'Packages', diff --git a/html/js/routes/detection.js b/html/js/routes/detection.js index cd1a0bc8..762653f7 100644 --- a/html/js/routes/detection.js +++ b/html/js/routes/detection.js @@ -62,7 +62,7 @@ routes.push({ path: '/detection/:id', name: 'detection', component: { overrideHeaders: { 'elastalert': [ { text: this.$root.i18n.enabled, value: 'isEnabled' }, - { text: this.$root.i18n.type, value: 'type' }, + { text: this.$root.i18n.type, value: 'type', localize: true }, { text: this.$root.i18n.track, value: 'track' }, { text: this.$root.i18n.dateCreated, value: 'createdAt', format: true }, { text: this.$root.i18n.dateModified, value: 'updatedAt', format: true }, @@ -70,7 +70,7 @@ routes.push({ path: '/detection/:id', name: 'detection', component: { 'strelka': [], // no overrides 'suricata': [ { text: this.$root.i18n.enabled, value: 'isEnabled' }, - { text: this.$root.i18n.type, value: 'type' }, + { text: this.$root.i18n.type, value: 'type', localize: true }, { text: this.$root.i18n.ipVar, value: 'ip' }, { text: this.$root.i18n.dateCreated, value: 'createdAt', format: true }, { text: this.$root.i18n.dateModified, value: 'updatedAt', format: true }, @@ -101,6 +101,38 @@ routes.push({ path: '/detection/:id', name: 'detection', component: { expanded: [], loading: false, }, + historyOverrideTableOpts: { + "elastalert": { + sortBy: 'updatedAt', + sortDesc: false, + headers: [ + { text: this.$root.i18n.actions, width: '10.0em' }, + { text: this.$root.i18n.kind, value: 'type' }, + { text: this.$root.i18n.time, value: 'updatedAt' }, + { text: this.$root.i18n.enabled, value: 'isEnabled' }, + ], + itemsPerPage: 10, + footerProps: { 'items-per-page-options': [10, 50, 250, 1000] }, + count: 500, + expanded: [], + loading: false, + }, + "suricata": { + sortBy: 'updatedAt', + sortDesc: false, + headers: [ + { text: this.$root.i18n.actions, width: '10.0em' }, + { text: this.$root.i18n.kind, value: 'type' }, + { text: this.$root.i18n.time, value: 'updatedAt' }, + { text: this.$root.i18n.enabled, value: 'isEnabled' }, + ], + itemsPerPage: 10, + footerProps: { 'items-per-page-options': [10, 50, 250, 1000] }, + count: 500, + expanded: [], + loading: false, + }, + }, extractedSummary: '', extractedReferences: [], extractedLogic: '', @@ -140,6 +172,7 @@ routes.push({ path: '/detection/:id', name: 'detection', component: { 'yara': 'strelka', }, changedKeys: {}, + changedOverrideKeys: {}, }}, created() { this.$root.initializeEditor(); @@ -504,10 +537,9 @@ routes.push({ path: '/detection/:id', name: 'detection', component: { if (response && response.data) { this.history = response.data; - //this.changedKeys[this.history[this.history.length - 1]['id']] = this.findHistoryChange(this.history); - for (var i = 0; i < this.history.length; i++) { this.$root.populateUserDetails(this.history[i], "userId", "owner"); + this.history[i].overrides = this.history[i].overrides || []; } } if (showLoadingIndicator) this.$root.stopLoading(); @@ -563,7 +595,40 @@ routes.push({ path: '/detection/:id', name: 'detection', component: { } } } + this.changedKeys[id] = retList; + + this.findOverrideHistoryChange(id); + }, + findOverrideHistoryChange(historyID) { + let index = this.history.findIndex((row) => row.id === historyID); + + if (index <= 0) return; + + let prev = this.history[index - 1]; + let parent = this.history[index]; + let releventKeys = ['isEnabled', 'customFilter', 'regex', 'value', 'track', 'ip', 'count', 'seconds']; + let overrideRetList = []; + + for (let i = 0; i < parent.overrides.length; i++) { + let retList = []; + let newOverride = parent.overrides[i]; + let oldOverride = prev.overrides.find(o => o.createdAt === newOverride.createdAt); + + if (oldOverride == null) { + return; + } + + for (let key of releventKeys) { + if (oldOverride[key] !== newOverride[key]) { + retList.push(key); + } + } + + overrideRetList.push(retList); + } + + this.changedOverrideKeys[historyID] = overrideRetList; }, getDefaultPreset(preset) { if (this.presets) { @@ -1354,6 +1419,9 @@ routes.push({ path: '/detection/:id', name: 'detection', component: { }, checkChangedKey(id, key) { return this.changedKeys[id]?.includes(key); + }, + checkOverrideChangedKey(id, index, key) { + return this.changedOverrideKeys?.[id]?.[index]?.includes(key); } } }}); diff --git a/html/js/routes/detection.test.js b/html/js/routes/detection.test.js index fe86ec9e..662f7f8c 100644 --- a/html/js/routes/detection.test.js +++ b/html/js/routes/detection.test.js @@ -659,7 +659,6 @@ test('getPresets', () => { }); test('findHistoryChange', () => { - // elastalert comp.history = [ { @@ -831,6 +830,183 @@ test('checkChangedKey', () => { expect(comp.checkChangedKey(id, 'severity')).toBe(false); }); +test('findOverrideHistoryChange', () => { + // elastalert + comp.history = [ + { + id: '1', + overrides: [ + { + type: "customFilter", + isEnabled: true, + createdAt: "2024-07-29T10:29:32.421321098-06:00", + updatedAt: "2024-07-29T10:29:32.421321098-06:00", + customFilter: "this:\n that" + }, + { + type: "customFilter", + isEnabled: false, + createdAt: "2024-07-29T11:54:34.569557439-06:00", + updatedAt: "2024-07-29T11:54:49.086205751-06:00", + customFilter: "another:\n one" + } + ], + }, + { + id: '2', + overrides: [ + { + type: "customFilter", + isEnabled: true, + createdAt: "2024-07-29T10:29:32.421321098-06:00", + updatedAt: "2024-07-29T10:29:32.421321098-06:00", + customFilter: "this:\n that" + }, + { + type: "customFilter", + isEnabled: false, + createdAt: "2024-07-29T11:54:34.569557439-06:00", + updatedAt: "2024-07-29T13:55:29.197427405-06:00", + customFilter: "another:\n two" + } + ], + } + ]; + + id = '2'; + comp.changedOverrideKeys = {}; + comp.findOverrideHistoryChange(id); + expect(Object.keys(comp.changedOverrideKeys).length).toBe(1); + + let overrideKeys = comp.changedOverrideKeys[id]; + expect(overrideKeys.length).toBe(2); + expect(overrideKeys[0].length).toBe(0); + expect(overrideKeys[1].length).toBe(1); + expect(overrideKeys[1][0]).toBe('customFilter'); + + // suricata + comp.history = [ + { + id: '1', + overrides: [ + { + type: "modify", + isEnabled: false, + createdAt: "2024-07-29T14:39:09.544042454-06:00", + updatedAt: "2024-07-29T14:39:21.947393163-06:00", + regex: "rev: 2", + value: "rev: 3" + }, + { + type: "suppress", + isEnabled: false, + createdAt: "2024-07-29T14:39:44.312946909-06:00", + updatedAt: "2024-07-29T14:57:49.637548072-06:00", + track: "by_either", + ip: "0.0.0.0/0" + }, + { + type: "threshold", + isEnabled: true, + createdAt: "2024-07-29T14:40:17.043093339-06:00", + updatedAt: "2024-07-29T14:40:17.043093339-06:00", + thresholdType: "both", + track: "by_src", + count: 10, + seconds: 60 + } + ] + }, + { + id: '2', + overrides: [ + { + type: "modify", + isEnabled: true, + createdAt: "2024-07-29T14:39:09.544042454-06:00", + updatedAt: "2024-07-29T14:39:20.947393160-06:00", + regex: "rev: 2", + value: "rev: 3" + }, + { + type: "suppress", + isEnabled: false, + createdAt: "2024-07-29T14:39:44.312946909-06:00", + updatedAt: "2024-07-29T14:57:50.637548070-06:00", + track: "by_src", + ip: "0.0.0.0/1" + }, + { + type: "threshold", + isEnabled: true, + createdAt: "2024-07-29T14:40:17.043093339-06:00", + updatedAt: "2024-07-29T14:40:17.043093339-06:00", + thresholdType: "both", + track: "by_src", + "count": 10, + "seconds": 60 + } + ], + } + ]; + + id = '2'; + comp.changedOverrideKeys = {}; + comp.findOverrideHistoryChange(id); + expect(Object.keys(comp.changedOverrideKeys).length).toBe(1); + + overrideKeys = comp.changedOverrideKeys[id]; + expect(overrideKeys.length).toBe(3); + expect(overrideKeys[0].length).toBe(1); + expect(overrideKeys[0][0]).toBe('isEnabled'); + expect(overrideKeys[1].length).toBe(2); + expect(overrideKeys[1][0]).toBe('track'); + expect(overrideKeys[1][1]).toBe('ip'); + expect(overrideKeys[2].length).toBe(0); + + id = '1'; + comp.changedOverrideKeys = {}; + comp.findOverrideHistoryChange(id); + expect(Object.keys(comp.changedOverrideKeys).length).toBe(0); + + // elastalert, nothing to diff + comp.history = [ + { + id: '1', + overrides: [], + }, + { + id: '2', + overrides: [ + { + type: "customFilter", + isEnabled: true, + createdAt: "2024-07-29T10:29:32.421321098-06:00", + updatedAt: "2024-07-29T10:29:32.421321098-06:00", + customFilter: "this:\n that" + }, + ], + } + ]; + + id = '2'; + comp.changedOverrideKeys = {}; + comp.findOverrideHistoryChange(id); + expect(Object.keys(comp.changedOverrideKeys).length).toBe(0); +}); + +test('checkOverrideChangedKey', () => { + expect(Object.keys(comp.changedKeys).length).toBe(0); + let id = "BHypPJABXppUUuo3UnEl"; + comp.changedOverrideKeys[id] = [[], ['track', 'ip']]; + expect(comp.checkOverrideChangedKey(id, 0, 'track')).toBe(false); + expect(comp.checkOverrideChangedKey(id, 1, 'track')).toBe(true); + expect(comp.checkOverrideChangedKey(id, 0, 'ip')).toBe(false); + expect(comp.checkOverrideChangedKey(id, 1, 'ip')).toBe(true); + expect(comp.checkOverrideChangedKey(id, 0, 'seconds')).toBe(false); + expect(comp.checkOverrideChangedKey(id, 1, 'seconds')).toBe(false); +}); + test('loadUrlParameters', () => { let nextTickCalled = 0; comp.$nextTick = (f) => {