Skip to content

Commit

Permalink
#1423 Add case bulk edit feature
Browse files Browse the repository at this point in the history
  • Loading branch information
nadouani committed Jul 7, 2020
1 parent 469c4c2 commit 58ee825
Show file tree
Hide file tree
Showing 9 changed files with 349 additions and 7 deletions.
1 change: 1 addition & 0 deletions frontend/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@
<script src="scripts/controllers/case/CaseTasksCtrl.js"></script>
<script src="scripts/controllers/case/CaseTasksItemCtrl.js"></script>
<script src="scripts/controllers/case/CaseTemplatesDialogCtrl.js"></script>
<script src="scripts/controllers/case/CaseUpdateCtrl.js"></script>
<script src="scripts/controllers/case/ObservableAnalyzeCtrl.js"></script>
<script src="scripts/controllers/case/ObservableCreationCtrl.js"></script>
<script src="scripts/controllers/case/ObservablesStatsCtrl.js"></script>
Expand Down
74 changes: 72 additions & 2 deletions frontend/app/scripts/controllers/case/CaseListCtrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
angular.module('theHiveControllers')
.controller('CaseListCtrl', CaseListCtrl);

function CaseListCtrl($scope, $q, $state, $window, FilteringSrv, StreamStatSrv, PaginatedQuerySrv, EntitySrv, TagSrv, UserSrv, AuthenticationSrv, CaseResolutionStatus, NotificationSrv, Severity, Tlp, CortexSrv) {
function CaseListCtrl($scope, $q, $state, $window, $uibModal, FilteringSrv, StreamStatSrv, PaginatedQuerySrv, EntitySrv, CaseSrv, UserSrv, AuthenticationSrv, CaseResolutionStatus, NotificationSrv, Severity, Tlp, CortexSrv) {
var self = this;

this.openEntity = EntitySrv.open;
Expand All @@ -13,6 +13,11 @@

this.lastQuery = null;

self.selection = [];
self.menu = {
selectAll: false
};

this.$onInit = function() {
self.filtering = new FilteringSrv('case', 'case.list', {
version: 'v1',
Expand Down Expand Up @@ -67,8 +72,48 @@
operations: [
{'_name': 'listCase'}
],
extraData: ["observableStats", "taskStats", "isOwner", "shareCount"]
extraData: ["observableStats", "taskStats", "isOwner", "shareCount"],
onUpdate: function() {
self.resetSelection();
}
});
};

self.resetSelection = function() {
if (self.menu.selectAll) {
self.selectAll();
} else {
self.selection = [];
self.menu.selectAll = false;
// self.updateMenu();
}
};

self.select = function(caze) {
if (caze.selected) {
self.selection.push(caze);
} else {
self.selection = _.reject(self.selection, function(item) {
return item._id === caze._id;
});
}
// self.updateMenu();
};

self.selectAll = function() {
var selected = self.menu.selectAll;
_.each(self.list.values, function(item) {
item.selected = selected;
});

if (selected) {
self.selection = self.list.values;
} else {
self.selection = [];
}

//self.updateMenu();

};

this.toggleStats = function () {
Expand Down Expand Up @@ -151,6 +196,31 @@
this.filtering.setSort(sort);
};

this.bulkEdit = function() {
var modal = $uibModal.open({
animation: 'true',
templateUrl: 'views/partials/case/case.update.html',
controller: 'CaseUpdateCtrl',
controllerAs: '$dialog',
size: 'lg',
resolve: {
selection: function() {
return self.selection;
}
}
});

modal.result.then(function(operations) {
console.log(operations);

$q.all(_.map(operations, function(operation) {
return CaseSrv.bulkUpdate(operation.ids, operation.patch);
})).then(function(/*responses*/) {
NotificationSrv.log('Selected cases have been updated successfully', 'success');
});
});
};

this.getCaseResponders = function(caseId, force) {
if (!force && this.caseResponders !== null) {
return;
Expand Down
175 changes: 175 additions & 0 deletions frontend/app/scripts/controllers/case/CaseUpdateCtrl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
(function() {
'use strict';
angular.module('theHiveControllers').controller('CaseUpdateCtrl',
function($scope, $uibModalInstance, TagSrv, selection) {
var self = this;

this.selection = selection;
this.state = {
all: false,
enableTlp: false,
enablePap: false,
enableSeverity: false,
enableAddTags: false,
enableRemoveTags: false
};

this.activeTlp = 'active';
this.activePap = 'active';
this.activeSeverity = true;

this.params = {
ioc: false,
tlp: 2,
pap: 2,
severity: 2,
addTagNames: '',
removeTagNames: ''
};

this.toggleAll = function() {

this.state.all = !this.state.all;

this.state.enableTlp = this.state.all;
this.state.enablePap = this.state.all;
this.state.enableSeverity = this.state.all;
this.state.enableAddTags = this.state.all;
this.state.enableRemoveTags = this.state.all;
};

this.categorizeObservables = function() {
var data = {
withTags: [],
withoutTags: []
};

_.each(this.selection, function(item) {
if(item.tags.length > 0) {
data.withTags.push(item);
} else {
data.withoutTags.push(item);
}
});

return data;
};

this.buildOperations = function(postData) {
var flags = _.pick(postData, 'pap', 'tlp', 'severity');

// Handle updates without tag changes
if(!postData.addTags && !postData.removeTags) {
return [
{
ids: _.pluck(this.selection, '_id'),
patch: flags
}
];
}

// Handle update with tag changes
var input = this.categorizeObservables();
var operations = [];
if(input.withoutTags.length > 0) {
var tags = (postData.addTags || []).filter(function(i) {
return (postData.removeTags || []).indexOf(i) === -1;
});

operations.push({
ids: _.pluck(input.withoutTags, '_id'),
patch: _.extend({}, flags ,{
tags: _.unique(tags)
})
});
}

if(input.withTags.length > 0) {
_.each(input.withTags, function(caze) {
tags = caze.tags.concat(postData.addTags || []).filter(function(i) {
return (postData.removeTags || []).indexOf(i) === -1;
});

operations.push({
ids: [caze._id],
patch: _.extend({}, flags ,{
tags: _.unique(tags)
})
});
});
}

return operations;
};

this.save = function() {

var postData = {};

if(this.state.enableTlp) {
postData.tlp = this.params.tlp;
}

if(this.state.enablePap) {
postData.pap = this.params.pap;
}

if(this.state.enableSeverity) {
postData.severity = this.params.severity;
}

if(this.state.enableAddTags) {
postData.addTags = _.pluck(this.params.addTags, 'text');
}

if(this.state.enableRemoveTags) {
postData.removeTags = _.pluck(this.params.removeTags, 'text');
}

$uibModalInstance.close(this.buildOperations(postData));
};

this.cancel = function() {
$uibModalInstance.dismiss();
};

this.getTags = function(query) {
return TagSrv.fromCases(query);
};

this.toggleTlp = function(value) {
this.params.tlp = value;
this.activeTlp = 'active';
this.state.enableTlp = true;
};

this.togglePap = function(value) {
this.params.pap = value;
this.activePap = 'active';
this.state.enablePap = true;
};

this.toggleSeverity = function(value) {
this.params.severity = value;
this.activeSeverity = true;
this.state.enableSeverity = true;
};

this.toggleAddTags = function() {
this.state.enableAddTags = true;
};

this.toggleRemoveTags = function() {
this.state.enableRemoveTags = true;
};

$scope.$watchCollection('$dialog.params.addTags', function(value) {
self.params.addTagNames = _.pluck(value, 'text').join(',');
});

$scope.$watchCollection('$dialog.params.removeTags', function(value) {
self.params.removeTagNames = _.pluck(value, 'text').join(',');
});
}
);
})();
4 changes: 4 additions & 0 deletions frontend/app/scripts/services/api/CaseSrv.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
this.merge = resource.merge;
this.query = resource.query;

this.bulkUpdate = function(ids, update) {
return $http.patch('./api/case/_bulk', _.extend({ids: ids}, update));
};

this.getShares = function(id) {
return $http.get('./api/case/' + id + '/shares');
};
Expand Down
4 changes: 2 additions & 2 deletions frontend/app/scripts/services/api/TagSrv.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@

// Get the list
QuerySrv.call('v0', operations)
.then(function(data) {
defer.resolve(_.map(data, function(tag) {
.then(function(data) {
defer.resolve(_.map(_.unique(data), function(tag) {
return {text: tag};
}));
});
Expand Down
7 changes: 6 additions & 1 deletion frontend/app/views/partials/case/case.list.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ <h3 class="box-title">List of cases ({{$vm.list.total || 0}} of {{$vm.caseStats.
<table class="table table-striped case-list">
<thead>
<tr>
<th width="20px" if-permission="manageCase">
<input type="checkbox" ng-model="$vm.menu.selectAll" ng-change="$vm.selectAll()">
</th>
<th style="width: 10px;" class="p-0"></th>
<th>Title</th>
<th style="width: 70px;"></th>
Expand All @@ -52,7 +55,9 @@ <h3 class="box-title">List of cases ({{$vm.list.total || 0}} of {{$vm.caseStats.

<tbody>
<tr ng-class="{true:'tr-warning'}[currentCase.flag]" ng-repeat="currentCase in $vm.list.values">

<td if-permission="manageCase">
<input type="checkbox" ng-model="currentCase.selected" ng-change="$vm.select(currentCase)">
</td>
<td class="p-0 bg-tlp-{{currentCase.tlp}} clickable" ng-click="$vm.addFilterValue('tlp', currentCase.tlp)"></td>
<td>
<div class="case-title wrap">
Expand Down
Loading

0 comments on commit 58ee825

Please sign in to comment.