Skip to content

Commit 63d694a

Browse files
committed
Merge branch 'release/3.3.0-RC6'
2 parents c91802b + 55adc90 commit 63d694a

File tree

18 files changed

+283
-14
lines changed

18 files changed

+283
-14
lines changed

CHANGELOG.md

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
# Change Log
22

3-
## [3.3.0-RC5](https://github.com/TheHive-Project/TheHive/tree/HEAD) (2019-02-24)
3+
## [3.3.0-RC6](https://github.com/TheHive-Project/TheHive/tree/3.3.0-RC6) (2019-02-07)
4+
5+
[Full Changelog](https://github.com/TheHive-Project/TheHive/compare/3.3.0-RC5...3.3.0-RC6)
6+
7+
**Implemented enhancements:**
8+
9+
- Add Tags to an Alert with Responder [\#912](https://github.com/TheHive-Project/TheHive/issues/912)
10+
- Dashboards - Add text widget [\#908](https://github.com/TheHive-Project/TheHive/issues/908)
11+
- Empty case still available when disabled [\#901](https://github.com/TheHive-Project/TheHive/issues/901)
12+
- Support for filtering Tags by prefix \(using asterisk, % or something\) in search dialog [\#666](https://github.com/TheHive-Project/TheHive/issues/666)
13+
14+
**Closed issues:**
15+
16+
- Dynamic \(auto-refresh\) of cases is break in 3.3.0-RC5 [\#907](https://github.com/TheHive-Project/TheHive/issues/907)
17+
- Hostname Artifact [\#900](https://github.com/TheHive-Project/TheHive/issues/900)
18+
- DOS issue: Firefox crashing TheHive [\#899](https://github.com/TheHive-Project/TheHive/issues/899)
19+
20+
## [3.3.0-RC5](https://github.com/TheHive-Project/TheHive/tree/3.3.0-RC5) (2019-02-23)
421
[Full Changelog](https://github.com/TheHive-Project/TheHive/compare/3.3.0-RC4...3.3.0-RC5)
522

623
**Implemented enhancements:**

project/Dependencies.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ object Dependencies {
2020

2121
val reflections = "org.reflections" % "reflections" % "0.9.11"
2222
val zip4j = "net.lingala.zip4j" % "zip4j" % "1.3.2"
23-
val elastic4play = "org.thehive-project" %% "elastic4play" % "1.8.0-1"
23+
val elastic4play = "org.thehive-project" %% "elastic4play" % "1.9.0"
2424
val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % "2.5.19"
2525
val akkaClusterTools = "com.typesafe.akka" %% "akka-cluster-tools" % "2.5.19"
2626
}

thehive-cortex/app/connectors/cortex/services/ActionOperation.scala

+16
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ case class AddLogToTask(content: String, owner: Option[String], status: ActionOp
6161
override def updateStatus(newStatus: Type, newMessage: String): ActionOperation = copy(status = newStatus, message = newMessage)
6262
}
6363

64+
case class AddTagToAlert(tag: String, status: ActionOperationStatus.Type = ActionOperationStatus.Waiting, message: String = "") extends ActionOperation {
65+
override def updateStatus(newStatus: ActionOperationStatus.Type, newMessage: String): AddTagToAlert = copy(status = newStatus, message = newMessage)
66+
}
67+
6468
object ActionOperation {
6569
val addTagToCaseWrites = Json.writes[AddTagToCase]
6670
val addTagToArtifactWrites = Json.writes[AddTagToArtifact]
@@ -69,6 +73,7 @@ object ActionOperation {
6973
val closeTaskWrites = Json.writes[CloseTask]
7074
val markAlertAsReadWrites = Json.writes[MarkAlertAsRead]
7175
val addLogToTaskWrites = Json.writes[AddLogToTask]
76+
val addTagToAlertWrites = Json.writes[AddTagToAlert]
7277
implicit val actionOperationReads: Reads[ActionOperation] = Reads[ActionOperation](json
7378
(json \ "type").asOpt[String].fold[JsResult[ActionOperation]](JsError("type is missing in action operation")) {
7479
case "AddTagToCase" (json \ "tag").validate[String].map(tag AddTagToCase(tag))
@@ -85,6 +90,7 @@ object ActionOperation {
8590
content (json \ "content").validate[String]
8691
owner (json \ "owner").validateOpt[String]
8792
} yield AddLogToTask(content, owner)
93+
case "AddTagToAlert" => (json \ "tag").validate[String].map(tag AddTagToAlert(tag))
8894
case other JsError(s"Unknown operation $other")
8995
})
9096
implicit val actionOperationWrites: Writes[ActionOperation] = Writes[ActionOperation] {
@@ -95,6 +101,7 @@ object ActionOperation {
95101
case a: CloseTask closeTaskWrites.writes(a)
96102
case a: MarkAlertAsRead markAlertAsReadWrites.writes(a)
97103
case a: AddLogToTask addLogToTaskWrites.writes(a)
104+
case a: AddTagToAlert addTagToAlertWrites.writes(a)
98105
case a Json.obj("unsupported operation" a.toString)
99106
}
100107
}
@@ -198,6 +205,15 @@ class ActionOperationSrv @Inject() (
198205
task findTaskEntity(entity)
199206
_ logSrv.create(task, Fields.empty.set("message", content).set("owner", owner.map(JsString)))
200207
} yield operation.updateStatus(ActionOperationStatus.Success, "")
208+
case AddTagToAlert(tag, _, _) =>
209+
entity match {
210+
case initialAlert: Alert
211+
for {
212+
alert alertSrv.get(initialAlert.id)
213+
_ alertSrv.update(alert.id, Fields.empty.set("tags", Json.toJson((alert.tags() :+ tag).distinct)), ModifyConfig(retryOnConflict = 0, version = Some(alert.version)))
214+
} yield operation.updateStatus(ActionOperationStatus.Success, "")
215+
case _ Future.failed(BadRequestError("Alert not found"))
216+
}
201217
case o Future.successful(operation.updateStatus(ActionOperationStatus.Failure, s"Operation $o not supported"))
202218
}
203219
}

ui/app/index.html

+1
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@
186186
<script src="scripts/directives/dashboard/item.js"></script>
187187
<script src="scripts/directives/dashboard/line.js"></script>
188188
<script src="scripts/directives/dashboard/multiline.js"></script>
189+
<script src="scripts/directives/dashboard/text.js"></script>
189190
<script src="scripts/directives/dateTimePicker.js"></script>
190191
<script src="scripts/directives/dt-picker.js"></script>
191192
<script src="scripts/directives/entityLink.js"></script>

ui/app/scripts/controllers/dashboard/DashboardViewCtrl.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@
5757
this.canEditDashboard = function() {
5858
return (this.createdBy === this.currentUser.id) ||
5959
(this.dashboardStatus = 'Shared' && AuthenticationSrv.isAdmin(this.currentUser));
60-
}
60+
};
6161

6262
this.options = {
6363
dashboardAllowedTypes: ['container'],
64-
containerAllowedTypes: ['bar', 'line', 'donut', 'counter', 'multiline'],
64+
containerAllowedTypes: ['bar', 'line', 'donut', 'counter', 'text', 'multiline'],
6565
maxColumns: 3,
6666
cls: DashboardSrv.typeClasses,
6767
labels: {
@@ -70,6 +70,7 @@
7070
donut: 'Donut',
7171
line: 'Line',
7272
counter: 'Counter',
73+
text: 'Text',
7374
multiline: 'Multi Lines'
7475
},
7576
editLayout: !_.find(this.definition.items, function(row) {
@@ -132,9 +133,9 @@
132133
}, 0);
133134
});
134135

135-
}
136+
};
136137

137-
this.itemInserted = function(item, rows, rowIndex, index) {
138+
this.itemInserted = function(item, rows/*, rowIndex, index*/) {
138139
if(!item.id){
139140
item.id = UtilsSrv.guid();
140141
}

ui/app/scripts/directives/dashboard/multiline.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
}
3434

3535
return s;
36-
}
36+
};
3737

3838
scope.buildSerie = function(serie, q, index) {
3939
return {
@@ -166,7 +166,7 @@
166166
};
167167

168168
scope.chart = chart;
169-
}, function(err) {
169+
}, function(/*err*/) {
170170
scope.error = true;
171171
NotificationSrv.log('Failed to fetch data, please edit the widget definition', 'error');
172172
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
(function() {
2+
'use strict';
3+
angular.module('theHiveDirectives').directive('dashboardText', function($q, $http, $state, DashboardSrv, GlobalSearchSrv, NotificationSrv) {
4+
return {
5+
restrict: 'E',
6+
scope: {
7+
filter: '=?',
8+
options: '=',
9+
entity: '=',
10+
autoload: '=',
11+
mode: '=',
12+
refreshOn: '@',
13+
resizeOn: '@',
14+
metadata: '='
15+
},
16+
templateUrl: 'views/directives/dashboard/text/view.html',
17+
link: function(scope, elem) {
18+
19+
scope.error = false;
20+
scope.data = null;
21+
scope.globalQuery = null;
22+
23+
scope.load = function() {
24+
if(!scope.options.series || scope.options.series.length === 0) {
25+
scope.error = true;
26+
return;
27+
}
28+
29+
var query = DashboardSrv.buildChartQuery(scope.filter, scope.options.query);
30+
scope.globalQuery = query;
31+
32+
var stats = {
33+
stats: _.map(scope.options.series || [], function(serie, index) {
34+
var s = {
35+
_agg: serie.agg,
36+
_name: serie.name || 'agg_' + (index + 1),
37+
_query: serie.query || {}
38+
};
39+
40+
if(serie.agg !== 'count') {
41+
s._field = serie.field;
42+
}
43+
44+
return {
45+
model: serie.entity,
46+
query: query,
47+
stats: [s]
48+
};
49+
})
50+
};
51+
52+
var statsPromise = $http.post('./api/_stats', stats);
53+
54+
statsPromise.then(function(response) {
55+
scope.error = false;
56+
scope.data = response.data;
57+
58+
var template = scope.options.template;
59+
Object.keys(scope.data).forEach(function(key){
60+
var regex = new RegExp('{{' + key + '}}', 'gi');
61+
62+
template = template.replace(regex, scope.data[key]);
63+
});
64+
65+
scope.content = template;
66+
67+
}, function(/*err*/) {
68+
scope.error = true;
69+
70+
NotificationSrv.log('Failed to fetch data, please edit the widget definition', 'error');
71+
});
72+
};
73+
74+
scope.copyHTML = function() {
75+
var html = elem[0].querySelector('.widget-content').innerHTML;
76+
function listener(e) {
77+
e.clipboardData.setData('text/html', html);
78+
e.clipboardData.setData('text/plain', html);
79+
e.preventDefault();
80+
}
81+
document.addEventListener('copy', listener);
82+
document.execCommand('copy');
83+
document.removeEventListener('copy', listener);
84+
}
85+
86+
if (scope.autoload === true) {
87+
scope.load();
88+
}
89+
90+
if (!_.isEmpty(scope.refreshOn)) {
91+
scope.$on(scope.refreshOn, function(event, filter) {
92+
scope.filter = filter;
93+
scope.load();
94+
});
95+
}
96+
}
97+
};
98+
});
99+
})();

ui/app/scripts/services/DashboardSrv.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@
7474
donut: 'fa-pie-chart',
7575
line: 'fa-line-chart',
7676
multiline: 'fa-area-chart',
77-
counter: 'fa-calculator'
77+
counter: 'fa-calculator',
78+
text: 'fa-file'
7879
};
7980

8081
this.sortOptions = [{
@@ -113,6 +114,14 @@
113114
entity: null
114115
}
115116
},
117+
{
118+
type: 'text',
119+
options: {
120+
title: null,
121+
template: null,
122+
entity: null
123+
}
124+
},
116125
{
117126
type: 'donut',
118127
options: {
@@ -234,6 +243,7 @@
234243
this.hasMinimalConfiguration = function(component) {
235244
switch (component.type) {
236245
case 'multiline':
246+
case 'text':
237247
return component.options.series.length === _.without(_.pluck(component.options.series, 'entity'), undefined).length;
238248
default:
239249
return !!component.options.entity;

ui/app/views/directives/dashboard/item.html

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<div class="box box-primary">
22
<div class="box-header with-border">
33
<h3 class="box-title">
4-
<i class="mr-xxs fa" ng-class="typeClasses[component.type]"></i>{{component.options.title || 'No title'}} {{mode}}
4+
<i class="mr-xxs fa" ng-class="typeClasses[component.type]"></i>{{component.options.title || 'No title'}}
55
</h3>
66

77
<div class="box-tools pull-right">
@@ -56,6 +56,14 @@ <h3 class="box-title">
5656
resize-on="{{resizeOn}}"
5757
mode="mode"></dashboard-multiline>
5858

59+
<dashboard-text ng-switch-when="text"
60+
entity="metadata[component.options.entity]"
61+
options="component.options"
62+
filter="filter"
63+
autoload="autoload"
64+
refresh-on="{{refreshOn}}"
65+
mode="mode"></dashboard-text>
66+
5967
<dashboard-counter ng-switch-when="counter"
6068
entity="metadata[component.options.entity]"
6169
options="component.options"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<div class="form-group">
2+
<label>Title</label>
3+
<input type="text" class="form-control" placeholder="Ex: Summary" ng-model="component.options.title">
4+
</div>
5+
<div class="row">
6+
<div class="col-sm-12">
7+
<div class="form-group">
8+
<label>Template</label>
9+
<textarea class="content-box" name="template"
10+
placeholder="Close summary" markdown-editor="{
11+
'fullscreen': {enable: false},
12+
'iconlibrary': 'fa',
13+
addExtraButtons: true,
14+
resize: 'vertical'}" rows="6" ng-model="component.options.template" required></textarea>
15+
</div>
16+
</div>
17+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<uib-tabset class="nav-tabs-custom" active="layout.activeTab">
2+
<uib-tab index="0">
3+
<uib-tab-heading>
4+
<i class="fa fa-bars"></i> Basic
5+
</uib-tab-heading>
6+
<ng-include src="'views/directives/dashboard/text/basic.html'"></ng-include>
7+
</uib-tab>
8+
<uib-tab index="1">
9+
<uib-tab-heading>
10+
<i class="fa fa-sort"></i> Series
11+
</uib-tab-heading>
12+
<ng-include src="'views/directives/dashboard/text/series.html'"></ng-include>
13+
</uib-tab>
14+
</uib-tabset>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<div ng-if="serie.filters.length > 0">
2+
<strong>Serie's filter</strong>
3+
</div>
4+
<div class="row mb-xxxs" ng-repeat="filter in serie.filters track by $index">
5+
<div class="col-sm-4">
6+
<div class="input-group">
7+
<span class="input-group-btn">
8+
<button class="btn btn-default" type="button" ng-click="removeSerieFilter(serie, $index)">
9+
<i class="fa fa-times text-danger"></i>
10+
</button>
11+
</span>
12+
<select class="form-control" ng-model="filter.field"
13+
ng-options="item.name as item.name for (key, item) in metadata[serie.entity].attributes"
14+
ng-change="setFilterField(filter, serie.entity)"></select>
15+
</div>
16+
</div>
17+
<div class="col-sm-8">
18+
<filter-editor metadata="metadata" filter="filter" entity="serie.entity"></filter-editor>
19+
</div>
20+
</div>
21+
<div class="mt-xxs">
22+
<a href ng-click="addSerieFilter(serie)">
23+
<i class="fa fa-plus"></i> Add a filter
24+
</a>
25+
</div>

0 commit comments

Comments
 (0)