mirror of https://github.com/MISP/misp-dashboard
new+chg: [livelog] Added basic filtering capabilities and fullscreen mode
Also, Improved table, reconnection mechanism and UIpull/82/head
parent
1e91b9b59c
commit
97a9465915
|
@ -43,7 +43,7 @@ class Live_helper:
|
|||
jentry = json.loads(entry.decode('utf8'))
|
||||
to_ret.append(jentry)
|
||||
return to_ret
|
||||
|
||||
|
||||
|
||||
def add_to_stream_log_cache(self, cacheKey, item):
|
||||
rKey = self.prefix_redis_key+cacheKey
|
||||
|
|
98
server.py
98
server.py
|
@ -68,10 +68,10 @@ class LogItem():
|
|||
FIELDNAME_ORDER_HEADER.append(item)
|
||||
FIELDNAME_ORDER.append(item)
|
||||
|
||||
def __init__(self, feed):
|
||||
def __init__(self, feed, filters={}):
|
||||
self.filters = filters
|
||||
self.feed = feed
|
||||
self.fields = []
|
||||
for f in feed:
|
||||
self.fields.append(f)
|
||||
|
||||
def get_head_row(self):
|
||||
to_ret = []
|
||||
|
@ -80,38 +80,70 @@ class LogItem():
|
|||
return to_ret
|
||||
|
||||
def get_row(self):
|
||||
if not self.pass_filter():
|
||||
return False
|
||||
|
||||
to_ret = {}
|
||||
#Number to keep them sorted (jsonify sort keys)
|
||||
for item in range(len(LogItem.FIELDNAME_ORDER)):
|
||||
try:
|
||||
to_ret[item] = self.fields[item]
|
||||
except IndexError: # not enough field in rcv item
|
||||
to_ret[item] = ''
|
||||
for i, field in enumerate(json.loads(cfg.get('Dashboard', 'fieldname_order'))):
|
||||
if type(field) is list:
|
||||
to_join = []
|
||||
for subField in field:
|
||||
to_join.append(str(util.getFields(self.feed, subField)))
|
||||
to_add = cfg.get('Dashboard', 'char_separator').join(to_join)
|
||||
else:
|
||||
to_add = util.getFields(self.feed, field)
|
||||
to_ret[i] = to_add if to_add is not None else ''
|
||||
|
||||
return to_ret
|
||||
|
||||
|
||||
def pass_filter(self):
|
||||
for filter, filterValue in self.filters.items():
|
||||
jsonValue = util.getFields(self.feed, filter)
|
||||
if jsonValue is None or jsonValue != filterValue:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class EventMessage():
|
||||
# Suppose the event message is a json with the format {name: 'feedName', log:'logData'}
|
||||
def __init__(self, msg):
|
||||
msg = msg.decode('utf8')
|
||||
try:
|
||||
jsonMsg = json.loads(msg)
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(e)
|
||||
jsonMsg = { 'name': "undefined" ,'log': json.loads(msg) }
|
||||
def __init__(self, msg, filters):
|
||||
if not isinstance(msg, dict):
|
||||
msg = msg.decode('utf8')
|
||||
try:
|
||||
jsonMsg = json.loads(msg)
|
||||
jsonMsg['log'] = json.loads(jsonMsg['log'])
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(e)
|
||||
jsonMsg = { 'name': "undefined" ,'log': json.loads(msg) }
|
||||
else:
|
||||
jsonMsg = msg
|
||||
|
||||
self.name = jsonMsg['name']
|
||||
self.zmqName = jsonMsg['zmqName']
|
||||
self.feed = json.loads(jsonMsg['log'])
|
||||
self.feed = LogItem(self.feed).get_row()
|
||||
if self.name == 'Attribute':
|
||||
self.feed = jsonMsg['log']
|
||||
self.feed = LogItem(self.feed, filters).get_row()
|
||||
else:
|
||||
self.feed = jsonMsg['log']
|
||||
|
||||
def to_json_ev(self):
|
||||
to_ret = { 'log': self.feed, 'name': self.name, 'zmqName': self.zmqName }
|
||||
return 'data: {}\n\n'.format(json.dumps(to_ret))
|
||||
if self.feed is not False:
|
||||
to_ret = { 'log': self.feed, 'name': self.name, 'zmqName': self.zmqName }
|
||||
return 'data: {}\n\n'.format(json.dumps(to_ret))
|
||||
else:
|
||||
return ''
|
||||
|
||||
def to_json(self):
|
||||
to_ret = { 'log': self.feed, 'name': self.name, 'zmqName': self.zmqName }
|
||||
return json.dumps(to_ret)
|
||||
if self.feed is not False:
|
||||
to_ret = { 'log': self.feed, 'name': self.name, 'zmqName': self.zmqName }
|
||||
return json.dumps(to_ret)
|
||||
else:
|
||||
return ''
|
||||
|
||||
def to_dict(self):
|
||||
return {'log': self.feed, 'name': self.name, 'zmqName': self.zmqName}
|
||||
|
||||
|
||||
###########
|
||||
## ROUTE ##
|
||||
|
@ -226,9 +258,18 @@ def logs():
|
|||
if request.accept_mimetypes.accept_json or request.method == 'POST':
|
||||
key = 'Attribute'
|
||||
j = live_helper.get_stream_log_cache(key)
|
||||
return jsonify(j)
|
||||
to_ret = []
|
||||
for item in j:
|
||||
filters = request.cookies.get('filters', '{}')
|
||||
filters = json.loads(filters)
|
||||
ev = EventMessage(item, filters)
|
||||
if ev is not None:
|
||||
dico = ev.to_dict()
|
||||
if dico['log'] != False:
|
||||
to_ret.append(dico)
|
||||
return jsonify(to_ret)
|
||||
else:
|
||||
return Response(event_stream_log(), mimetype="text/event-stream")
|
||||
return Response(stream_with_context(event_stream_log()), mimetype="text/event-stream")
|
||||
|
||||
@app.route("/_maps")
|
||||
def maps():
|
||||
|
@ -248,9 +289,14 @@ def event_stream_log():
|
|||
subscriber_log.subscribe(live_helper.CHANNEL)
|
||||
try:
|
||||
for msg in subscriber_log.listen():
|
||||
filters = request.cookies.get('filters', '{}')
|
||||
filters = json.loads(filters)
|
||||
content = msg['data']
|
||||
ev = EventMessage(content)
|
||||
yield ev.to_json_ev()
|
||||
ev = EventMessage(content, filters)
|
||||
if ev is not None:
|
||||
yield ev.to_json_ev()
|
||||
else:
|
||||
pass
|
||||
except GeneratorExit:
|
||||
subscriber_log.unsubscribe()
|
||||
|
||||
|
|
|
@ -170,52 +170,67 @@ var curNumLog = 0;
|
|||
var curMaxDataNumLog = 0;
|
||||
var source_log;
|
||||
|
||||
function connect_source_log() {
|
||||
source_log = new EventSource(urlForLogs);
|
||||
|
||||
source_log.onopen = function(){
|
||||
//console.log('connection is opened. '+source_log.readyState);
|
||||
};
|
||||
|
||||
source_log.onerror = function(){
|
||||
console.log('error: '+source_log.readyState);
|
||||
setTimeout(function() { connect_source_log(); }, 5000);
|
||||
};
|
||||
|
||||
source_log.onmessage = function(event) {
|
||||
var json = jQuery.parseJSON( event.data );
|
||||
updateLogTable(json.name, json.log, json.zmqName);
|
||||
};
|
||||
}
|
||||
// function connect_source_log() {
|
||||
// source_log = new EventSource(urlForLogs);
|
||||
//
|
||||
// source_log.onopen = function(){
|
||||
// //console.log('connection is opened. '+source_log.readyState);
|
||||
// };
|
||||
//
|
||||
// source_log.onerror = function(){
|
||||
// console.log('error: '+source_log.readyState);
|
||||
// setTimeout(function() { connect_source_log(); }, 5000);
|
||||
// };
|
||||
//
|
||||
// source_log.onmessage = function(event) {
|
||||
// var json = jQuery.parseJSON( event.data );
|
||||
// updateLogTable(json.name, json.log, json.zmqName);
|
||||
// };
|
||||
// }
|
||||
|
||||
var livelog;
|
||||
$(document).ready(function () {
|
||||
createHead(function() {
|
||||
if (!!window.EventSource) {
|
||||
$.getJSON( urlForLogs, function( data ) {
|
||||
data.forEach(function(item) {
|
||||
updateLogTable(item.name, item.log, item.zmqName);
|
||||
});
|
||||
connect_source_log();
|
||||
});
|
||||
} else {
|
||||
console.log("No event source_log");
|
||||
}
|
||||
// createHead(function() {
|
||||
// if (!!window.EventSource) {
|
||||
// $.getJSON( urlForLogs, function( data ) {
|
||||
// data.forEach(function(item) {
|
||||
// updateLogTable(item.name, item.log, item.zmqName);
|
||||
// });
|
||||
// connect_source_log();
|
||||
// });
|
||||
// } else {
|
||||
// console.log("No event source_log");
|
||||
// }
|
||||
//
|
||||
// });
|
||||
|
||||
$.getJSON(urlForHead, function(head) {
|
||||
livelog = new $.livelog($("#divLogTable"), {
|
||||
pollingFrequency: 5000,
|
||||
tableHeader: head,
|
||||
tableMaxEntries: 50,
|
||||
animate: false,
|
||||
preDataURL: urlForLogs,
|
||||
endpoint: urlForLogs
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
// LOG TABLE
|
||||
function updateLogTable(name, log, zmqName) {
|
||||
function updateLogTable(name, log, zmqName, ignoreLed) {
|
||||
if (log.length == 0)
|
||||
return;
|
||||
|
||||
// update keepAlives
|
||||
ledmanager.updateKeepAlive(zmqName);
|
||||
if (ignoreLed !== true) {
|
||||
ledmanager.updateKeepAlive(zmqName);
|
||||
}
|
||||
|
||||
// Create new row
|
||||
tableBody = document.getElementById('table_log_body');
|
||||
// tableBody = document.getElementById('table_log_body');
|
||||
|
||||
// only add row for attribute
|
||||
if (name == "Attribute" ) {
|
||||
|
@ -224,12 +239,12 @@ function updateLogTable(name, log, zmqName) {
|
|||
sources.incCountOnSource(categName);
|
||||
sources.incCountOnSource('global');
|
||||
updateChartDirect();
|
||||
createRow(tableBody, log);
|
||||
// createRow(tableBody, log);
|
||||
|
||||
// Remove old row
|
||||
while ($("#table_log").height() >= $("#panelLogTable").height()-26){ //26 for margin
|
||||
tableBody.deleteRow(0);
|
||||
}
|
||||
// while ($("#table_log").height() >= $("#panelLogTable").height()-26){ //26 for margin
|
||||
// tableBody.deleteRow(0);
|
||||
// }
|
||||
|
||||
} else if (name == "Keepalive") {
|
||||
// do nothing
|
||||
|
@ -264,23 +279,6 @@ function getTextColour(rgb) {
|
|||
}
|
||||
}
|
||||
|
||||
function addObjectToLog(name, obj, td) {
|
||||
if(name == "Tag") {
|
||||
var a = document.createElement('A');
|
||||
a.classList.add('tagElem');
|
||||
a.style.backgroundColor = obj.colour;
|
||||
a.style.color = getTextColour(obj.colour.substring(1,6));
|
||||
a.innerHTML = obj.name;
|
||||
td.appendChild(a);
|
||||
td.appendChild(document.createElement('br'));
|
||||
} else if (name == "mispObject") {
|
||||
td.appendChild(document.createTextNode('mispObj'));
|
||||
} else {
|
||||
td.appendChild(document.createTextNode('nop'));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function createRow(tableBody, log) {
|
||||
var tr = document.createElement('TR');
|
||||
|
||||
|
@ -338,3 +336,554 @@ function createHead(callback) {
|
|||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* LIVE LOG */
|
||||
(function(factory) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(['jquery'], factory);
|
||||
} else if (window.jQuery && !window.jQuery.fn.Livelog) {
|
||||
factory(window.jQuery);
|
||||
}
|
||||
}
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
// Livelog object
|
||||
var Livelog = function(container, options) {
|
||||
this._default_options = {
|
||||
pollingFrequency: 5000,
|
||||
tableHeader: undefined,
|
||||
tableMaxEntries: undefined,
|
||||
animate: true
|
||||
}
|
||||
|
||||
options.container = container;
|
||||
|
||||
this.validateOptions(options);
|
||||
this._options = $.extend({}, this._default_options, options);
|
||||
|
||||
// create table and draw header
|
||||
this.origTableOptions = {
|
||||
dom: "<'row'<'col-sm-12'<'dt-toolbar-led'>>>"
|
||||
+ "<'row'<'col-sm-12'tr>>",
|
||||
searching: false,
|
||||
paging: false,
|
||||
"order": [[ 0, "desc" ]],
|
||||
responsive: true,
|
||||
columnDefs: [
|
||||
{ targets: 0, orderable: false },
|
||||
{ targets: '_all', searchable: false, orderable: false,
|
||||
render: function ( data, type, row ) {
|
||||
// return data +' ('+ row[3]+')';
|
||||
var $toRet;
|
||||
if (typeof data === 'object') {
|
||||
$toRet = $('<span></span>');
|
||||
data.data.forEach(function(cur, i) {
|
||||
switch (data.name) {
|
||||
case 'Tag':
|
||||
var $tag = $('<a></a>');
|
||||
$tag.addClass('tagElem');
|
||||
$tag.css({
|
||||
backgroundColor: cur.colour,
|
||||
color: getTextColour(cur.colour.substring(1,6))
|
||||
});
|
||||
$tag.text(cur.name)
|
||||
$toRet.append($tag);
|
||||
break;
|
||||
case 'mispObject':
|
||||
$toRet.append('MISP Object not supported yet')
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
$toRet = $toRet[0].outerHTML;
|
||||
} else if (data === undefined) {
|
||||
$toRet = '';
|
||||
} else {
|
||||
var textToAddArray = data.split(char_separator);
|
||||
$toRet = '';
|
||||
textToAddArray.forEach(function(e, i) {
|
||||
if (i > 0) {
|
||||
$toRet += '<br>' + e;
|
||||
} else {
|
||||
$toRet += e;
|
||||
}
|
||||
});
|
||||
}
|
||||
return $toRet;
|
||||
},
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
this.DOMTable = $('<table class="table table-striped table-bordered" style="width:100%"></table>');
|
||||
this._options.container.append(this.DOMTable);
|
||||
this.origTableOptions.columns = [];
|
||||
var that = this;
|
||||
this._options.tableHeader.forEach(function(field) {
|
||||
var th = $('<th>'+field+'</th>');
|
||||
that.origTableOptions.columns.push({ title: field });
|
||||
});
|
||||
this.dt = this.DOMTable.DataTable(this.origTableOptions);
|
||||
|
||||
this.fetch_predata();
|
||||
|
||||
// add status led
|
||||
this._ev_timer = null;
|
||||
this._ev_retry_frequency = this._options.pollingFrequency; // sec
|
||||
this._cur_ev_retry_count = 0;
|
||||
this._ev_retry_count_thres = 3;
|
||||
var led_container = $('<div class="led-container" style="margin-left: 10px;"></div>');
|
||||
var led = $('<div class="led-small led_red"></div>');
|
||||
this.statusLed = led;
|
||||
led_container.append(led);
|
||||
var header = this._options.container.parent().parent().find('.panel-heading');
|
||||
|
||||
if (header.length > 0) { // add in panel header
|
||||
header.append(led_container);
|
||||
} else { // add over the map
|
||||
// this._options.container.append(led_container);
|
||||
led.css('display', 'inline-block');
|
||||
led_container.append($('<span>Status</span>')).css('float', 'left');
|
||||
$('.dt-toolbar-led').append(led_container)
|
||||
}
|
||||
this.data_source = undefined;
|
||||
|
||||
this.connect_to_data_source();
|
||||
|
||||
};
|
||||
|
||||
Livelog.prototype = {
|
||||
constructor: Livelog,
|
||||
|
||||
validateOptions: function(options) {
|
||||
var o = options;
|
||||
|
||||
if (o.endpoint === undefined || typeof o.endpoint != 'string') {
|
||||
throw "Livelog must have a valid endpoint";
|
||||
}
|
||||
|
||||
if (o.container === undefined) {
|
||||
throw "Livelog must have a container";
|
||||
} else {
|
||||
o.container = o.container instanceof jQuery ? o.container : $('#'+o.container);
|
||||
}
|
||||
|
||||
// pre-data is either the data to be shown or an URL from which the data should be taken from
|
||||
if (Array.isArray(o.preData)){
|
||||
o.preDataURL = null;
|
||||
o.preData = o.preData;
|
||||
} else if (o.preData !== undefined) { // should fetch
|
||||
o.preDataURL = o.preData;
|
||||
o.preData = [];
|
||||
}
|
||||
|
||||
if (o.tableHeader === undefined || !Array.isArray(o.tableHeader)) {
|
||||
throw "Livelog must have a valid header";
|
||||
}
|
||||
|
||||
if (o.tableMaxEntries !== undefined) {
|
||||
o.tableMaxEntries = parseInt(o.tableMaxEntries);
|
||||
}
|
||||
},
|
||||
|
||||
fetch_predata: function() {
|
||||
var that = this;
|
||||
if (this._options.preDataURL !== null) {
|
||||
$.when(
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url: this._options.preDataURL,
|
||||
data: this._options.additionalOptions,
|
||||
success: function(data) {
|
||||
that._options.preData = data;
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
console.log(textStatus);
|
||||
that._options.preData = [];
|
||||
}
|
||||
})
|
||||
).then(
|
||||
function() { // success
|
||||
// add data to the widget
|
||||
that._options.preData.forEach(function(j) {
|
||||
var name = j.name,
|
||||
zmqName = j.zmqName,
|
||||
entry = j.log;
|
||||
updateLogTable(name, entry, zmqName, true);
|
||||
switch (name) {
|
||||
case 'Attribute':
|
||||
that.add_entry(entry);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}, function() { // fail
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
connect_to_data_source: function() {
|
||||
var that = this;
|
||||
if (!this.data_source) {
|
||||
// var url_param = $.param( this.additionalOptions );
|
||||
this.data_source = new EventSource(this._options.endpoint);
|
||||
this.data_source.onmessage = function(event) {
|
||||
var json = jQuery.parseJSON( event.data );
|
||||
var name = json.name,
|
||||
zmqName = json.zmqName,
|
||||
entry = json.log;
|
||||
updateLogTable(name, entry, zmqName);
|
||||
switch (name) {
|
||||
case 'Attribute':
|
||||
that.add_entry(entry);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
this.data_source.onopen = function(){
|
||||
that._cur_ev_retry_count = 0;
|
||||
that.update_connection_state('connected');
|
||||
};
|
||||
this.data_source.onerror = function(){
|
||||
if (that.data_source.readyState == 0) { // reconnecting
|
||||
that.update_connection_state('connecting');
|
||||
} else if (that.data_source.readyState == 2) { // closed, reconnect with new object
|
||||
that.reconnection_logique();
|
||||
} else {
|
||||
that.update_connection_state('not connected');
|
||||
that.reconnection_logique();
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
reconnection_logique: function () {
|
||||
var that = this;
|
||||
if (that.data_source) {
|
||||
that.data_source.close();
|
||||
that.data_source = null;
|
||||
}
|
||||
if (that._ev_timer) {
|
||||
clearTimeout(that._ev_timer);
|
||||
}
|
||||
if(that._cur_ev_retry_count >= that._ev_retry_count_thres) {
|
||||
that.update_connection_state('not connected');
|
||||
} else {
|
||||
that._cur_ev_retry_count++;
|
||||
that.update_connection_state('connecting');
|
||||
}
|
||||
that._ev_timer = setTimeout(function () { that.connect_to_data_source(); }, that._ev_retry_frequency*1000);
|
||||
},
|
||||
|
||||
reconnect: function() {
|
||||
if (this.data_source) {
|
||||
this.data_source.close();
|
||||
this.data_source = null;
|
||||
this._cur_ev_retry_count = 0;
|
||||
this.update_connection_state('reconnecting');
|
||||
this.connect_to_data_source();
|
||||
}
|
||||
},
|
||||
|
||||
update_connection_state: function(connectionState) {
|
||||
this.connectionState = connectionState;
|
||||
this.updateDOMState(this.statusLed, connectionState);
|
||||
},
|
||||
|
||||
updateDOMState: function(led, state) {
|
||||
switch (state) {
|
||||
case 'connected':
|
||||
led.removeClass("led_red");
|
||||
led.removeClass("led_orange");
|
||||
led.addClass("led_green");
|
||||
break;
|
||||
case 'not connected':
|
||||
led.removeClass("led_green");
|
||||
led.removeClass("led_orange");
|
||||
led.addClass("led_red");
|
||||
break;
|
||||
case 'connecting':
|
||||
led.removeClass("led_green");
|
||||
led.removeClass("led_red");
|
||||
led.addClass("led_orange");
|
||||
break;
|
||||
default:
|
||||
led.removeClass("led_green");
|
||||
led.removeClass("led_orange");
|
||||
led.addClass("led_red");
|
||||
}
|
||||
},
|
||||
|
||||
add_entry: function(entry) {
|
||||
var rowNode = this.dt.row.add(entry).draw().node();
|
||||
if (this.animate) {
|
||||
$( rowNode )
|
||||
.css( 'background-color', '#5cb85c' )
|
||||
.animate( { 'background-color': '', duration: 600 } );
|
||||
}
|
||||
// this.dt.row.add(entry).draw( false );
|
||||
// remove entries
|
||||
var numRows = this.dt.rows().count();
|
||||
var rowsToRemove = numRows - this._options.tableMaxEntries;
|
||||
if (rowsToRemove > 0 && this._options.tableMaxEntries != -1) {
|
||||
//get row indexes as an array
|
||||
var arraySlice = this.dt.rows().indexes().toArray();
|
||||
//get row indexes to remove starting at row 0
|
||||
arraySlice = arraySlice.slice(-rowsToRemove);
|
||||
//remove the rows and redraw the table
|
||||
var rows = this.dt.rows(arraySlice).remove().draw();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$.livelog = Livelog;
|
||||
$.fn.livelog = function(option) {
|
||||
var pickerArgs = arguments;
|
||||
|
||||
return this.each(function() {
|
||||
var $this = $(this),
|
||||
inst = $this.data('livelog'),
|
||||
options = ((typeof option === 'object') ? option : {});
|
||||
if ((!inst) && (typeof option !== 'string')) {
|
||||
$this.data('livelog', new Livelog(this, options));
|
||||
} else {
|
||||
if (typeof option === 'string') {
|
||||
inst[option].apply(inst, Array.prototype.slice.call(pickerArgs, 1));
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$.fn.livelog.constructor = Livelog;
|
||||
|
||||
}));
|
||||
|
||||
// ### ///
|
||||
// ### ///
|
||||
// ### ///
|
||||
// ### ///
|
||||
// ### ///
|
||||
// ### ///
|
||||
|
||||
function recursiveInject(result, rules, isNot) {
|
||||
if (rules.rules === undefined) { // add to result
|
||||
var field = rules.field;
|
||||
var value = rules.value;
|
||||
var operator_notequal = rules.operator === 'not_equal' ? true : false;
|
||||
var negate = isNot ^ operator_notequal;
|
||||
value = negate ? '!' + value : value;
|
||||
if (result.hasOwnProperty(field)) {
|
||||
if (Array.isArray(result[field])) {
|
||||
result[field].push(value);
|
||||
} else {
|
||||
result[field] = [result[field], value];
|
||||
}
|
||||
} else {
|
||||
result[field] = value;
|
||||
}
|
||||
}
|
||||
else if (Array.isArray(rules.rules)) {
|
||||
rules.rules.forEach(function(subrules) {
|
||||
recursiveInject(result, subrules, isNot ^ rules.not) ;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function cleanRules(rules) {
|
||||
var res = {};
|
||||
recursiveInject(res, rules);
|
||||
// clean up invalid and unset
|
||||
Object.keys(res).forEach(function(k) {
|
||||
var v = res[k];
|
||||
if (v === undefined || v === '') {
|
||||
delete res[k];
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
var qbOptions = {
|
||||
plugins: {
|
||||
'filter-description' : {
|
||||
mode: 'inline'
|
||||
},
|
||||
'unique-filter': null,
|
||||
'bt-tooltip-errors': null,
|
||||
},
|
||||
allow_empty: true,
|
||||
// lang: {
|
||||
// operators: {
|
||||
// equal: 'show',
|
||||
// in: 'show'
|
||||
// }
|
||||
// },
|
||||
filters: [],
|
||||
rules: {
|
||||
condition: 'AND',
|
||||
not: false,
|
||||
rules: [],
|
||||
flags: {
|
||||
no_add_group: true,
|
||||
condition_readonly: true,
|
||||
}
|
||||
},
|
||||
icons: {
|
||||
add_group: 'fa fa-plus-square',
|
||||
add_rule: 'fa fa-plus-circle',
|
||||
remove_group: 'fa fa-minus-square',
|
||||
remove_rule: 'fa fa-minus-circle',
|
||||
error: 'fa fa-exclamation-triangle'
|
||||
}
|
||||
};
|
||||
|
||||
// add filters and rules
|
||||
[
|
||||
'Attribute.category',
|
||||
'Attribute.comment',
|
||||
'Attribute.deleted',
|
||||
'Attribute.disable_correlation',
|
||||
'Attribute.distribution',
|
||||
'Attribute.event_id',
|
||||
'Attribute.id',
|
||||
'Attribute.object_id',
|
||||
'Attribute.object_relation',
|
||||
'Attribute.sharing_group_id',
|
||||
'Attribute.Tag.name',
|
||||
'Attribute.timestamp',
|
||||
'Attribute.to_ids',
|
||||
'Attribute.type',
|
||||
'Attribute.uuid',
|
||||
'Attribute.value',
|
||||
'Event.Org',
|
||||
'Event.Orgc',
|
||||
'Event.analysis',
|
||||
'Event.attribute_count',
|
||||
'Event.date',
|
||||
'Event.disable_correlation',
|
||||
'Event.distribution',
|
||||
'Event.event_creator_email',
|
||||
'Event.extends_uuid',
|
||||
'Event.id',
|
||||
'Event.info',
|
||||
'Event.locked',
|
||||
'Event.org_id',
|
||||
'Event.orgc_id',
|
||||
'Event.proposal_email_lock',
|
||||
'Event.publish_timestamp',
|
||||
'Event.published',
|
||||
'Event.sharing_group_id',
|
||||
'Event.threat_level_id',
|
||||
'Event.Tag.name',
|
||||
'Event.timestamp',
|
||||
'Event.uuid',
|
||||
'Org.id',
|
||||
'Org.name',
|
||||
'Org.uuid',
|
||||
'Orgc.id',
|
||||
'Orgc.name',
|
||||
'Orgc.uuid'
|
||||
].forEach(function(field) {
|
||||
var tempFilter = {
|
||||
"input": "text",
|
||||
"type": "string",
|
||||
"operators": [
|
||||
"equal",
|
||||
"not_equal"
|
||||
],
|
||||
"unique": true,
|
||||
"id": field,
|
||||
"label": field,
|
||||
"description": "Perfom strict equality on " + field,
|
||||
"validation": {
|
||||
"allow_empty_value": true
|
||||
}
|
||||
};
|
||||
qbOptions.filters.push(tempFilter);
|
||||
});
|
||||
|
||||
var filterCookie = getCookie('filters');
|
||||
var filters = JSON.parse(filterCookie !== undefined && filterCookie !== '' ? filterCookie : "{}");
|
||||
var activeFilters = Object.keys(filters)
|
||||
var tempRule = [];
|
||||
activeFilters.forEach(function(field) {
|
||||
var v = filters[field];
|
||||
var tmp = {
|
||||
field: field,
|
||||
id: field,
|
||||
value: v
|
||||
};
|
||||
tempRule.push(tmp);
|
||||
});
|
||||
qbOptions.rules.rules = tempRule;
|
||||
updateFilterButton(activeFilters);
|
||||
|
||||
var $ev = $('#filteringQB');
|
||||
var querybuilderTool = $ev.queryBuilder(qbOptions);
|
||||
querybuilderTool = querybuilderTool[0].queryBuilder;
|
||||
|
||||
$('#saveFilters').click(function() {
|
||||
var rules = querybuilderTool.getRules({ skip_empty: true, allow_invalid: true });
|
||||
var result = {};
|
||||
recursiveInject(result, rules, false);
|
||||
updateFilterButton(Object.keys(result));
|
||||
var jres = JSON.stringify(result, null);
|
||||
document.cookie = 'filters=' + jres;
|
||||
$('#modalFilters').modal('hide');
|
||||
livelog.dt
|
||||
.clear()
|
||||
.draw();
|
||||
livelog.fetch_predata();
|
||||
livelog.reconnect();
|
||||
})
|
||||
|
||||
$('#log-fullscreen').click(function() {
|
||||
var $this = $(this);
|
||||
var $panel = $('#panelLogTable');
|
||||
var isfullscreen = $this.data('isfullscreen');
|
||||
if (isfullscreen === undefined || !isfullscreen) {
|
||||
$panel.detach().prependTo('#page-wrapper')
|
||||
$panel.addClass('liveLogFullScreen');
|
||||
$this.data('isfullscreen', true);
|
||||
} else {
|
||||
$panel.detach().appendTo('#rightCol')
|
||||
$panel.removeClass('liveLogFullScreen');
|
||||
$this.data('isfullscreen', false);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function updateFilterButton(activeFilters) {
|
||||
if (activeFilters.length > 0) {
|
||||
$('#log-filter').removeClass('btn-default');
|
||||
$('#log-filter').addClass('btn-success');
|
||||
} else {
|
||||
$('#log-filter').removeClass('btn-success');
|
||||
$('#log-filter').addClass('btn-default');
|
||||
}
|
||||
}
|
||||
|
||||
function getCookie(cname) {
|
||||
var name = cname + "=";
|
||||
var decodedCookie = decodeURIComponent(document.cookie);
|
||||
var ca = decodedCookie.split(';');
|
||||
for(var i = 0; i <ca.length; i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0) == ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
|
|
@ -24,9 +24,28 @@
|
|||
<script src="{{ url_for('static', filename='js/jquery.flot.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/jquery.flot.pie.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/jquery.flot.resize.js') }}"></script>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/jquery-ui.min.js') }}"></script>
|
||||
<link href="{{ url_for('static', filename='css/jquery-ui.css') }}" type="text/css" rel="stylesheet">
|
||||
|
||||
<!-- Bootstrap Core JavaScript -->
|
||||
<script src="{{ url_for('static', filename='js/bootstrap.js') }}"></script>
|
||||
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/jquery.dataTables.min.css') }}">
|
||||
<script src="{{ url_for('static', filename='js/jquery.dataTables.min.js') }}"></script>
|
||||
|
||||
<link href="{{ url_for('static', filename='css/font-awesome.min.css') }}" type="text/css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/jquery-jvectormap-2.0.3.css') }}" type="text/css" media="screen"/>
|
||||
<script src="{{ url_for('static', filename='js/jquery-jvectormap-2.0.3.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/jquery-jvectormap-world-mill.js') }}"></script>
|
||||
|
||||
|
||||
<script src="{{ url_for('static', filename='js/doT.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/extendext.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/moment-with-locales.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/query-builder.js') }}"></script>
|
||||
<link href="{{ url_for('static', filename='css/query-builder.default.css') }}" type="text/css" rel="stylesheet">
|
||||
|
||||
</head>
|
||||
|
||||
<style>
|
||||
|
@ -42,8 +61,9 @@
|
|||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 14px;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-radius: 3px;
|
||||
box-shadow: 3px 3px 3px #888888;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
table {
|
||||
|
@ -123,6 +143,56 @@ small {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.led_green {
|
||||
background-color: #ABFF00;
|
||||
box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #304701 0 -1px 6px, #89FF00 0 0px 6px;
|
||||
}
|
||||
|
||||
.led_red {
|
||||
background-color: #F82222;
|
||||
box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #304701 0 -1px 6px, #FF0303 0 0px 6px;
|
||||
}
|
||||
|
||||
.led_orange {
|
||||
background-color: #FFB400;
|
||||
box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #304701 0 -1px 6px, #FF9000 0 0px 6px;
|
||||
}
|
||||
|
||||
.led-small {
|
||||
margin: auto auto;
|
||||
margin-top: 6px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.led-container {
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.led-container > span {
|
||||
margin: auto 5px;
|
||||
}
|
||||
|
||||
div.dataTables_scrollHead table.dataTable {
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
.dataTables_scrollBody thead tr {
|
||||
visibility: collapse !important;
|
||||
}
|
||||
|
||||
.liveLogFullScreen {
|
||||
position: absolute !important;
|
||||
top: 66px !important;
|
||||
left: 15px !important;
|
||||
right: 10px !important;
|
||||
z-index: 1001 !important;
|
||||
bottom: 5px !important;
|
||||
height: unset !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<body>
|
||||
|
@ -198,7 +268,7 @@ small {
|
|||
</div>
|
||||
<!-- /.col-lg-6 -->
|
||||
<!-- /.col-lg-6 -->
|
||||
<div class="col-lg-{{ size_dashboard_width[1] }}">
|
||||
<div id="rightCol" class="col-lg-{{ size_dashboard_width[1] }}">
|
||||
|
||||
<div class="panel panel-default" style="margin-top: 15px; height: {{ pannelSize[2] }}vh;">
|
||||
<div id="panelbody" class="panel-body" style="height: 100%;">
|
||||
|
@ -212,23 +282,12 @@ small {
|
|||
<div id="panelLogTable" class="panel panel-default" style="height: {{ pannelSize[3] }}vh;">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-tasks fa-fw"></i> Logs
|
||||
<div class="pull-right">
|
||||
<input id="checkbox_log_info" type="checkbox" value="info"> INFO
|
||||
<input id="checkbox_log_warning" type="checkbox" value="warning" checked="true"> WARNING
|
||||
<input id="checkbox_log_critical" type="checkbox" value="critical" checked="true"> CRITICAL
|
||||
<div style="display: inline-block; float: right;">
|
||||
<button id="log-filter" data-toggle="modal" data-target="#modalFilters" class="btn btn-xs btn-default" ><i class="fa fa-filter"></i></button>
|
||||
<button id="log-fullscreen" class="btn btn-xs btn-default"><i class="fa fa-expand"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="divLogTable" class="panel-body" style="height: 98%; padding: 0px;">
|
||||
<div class="row" style="height: 100%;">
|
||||
<div class="col-lg-12" style="height: 100%;">
|
||||
<table class="table table-bordered table-hover table-striped" id="table_log">
|
||||
<thead id="table_log_head">
|
||||
</thead>
|
||||
<tbody id="table_log_body">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div id="divLogTable" class="panel-body" style="height: calc(100% - 46px); padding: 0px; overflow: hidden">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -254,6 +313,25 @@ small {
|
|||
</div>
|
||||
<!-- /#wrapper -->
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="modalFilters" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="myModalLabel">Log filtering rules</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="filteringQB"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
<button id="saveFilters" type="button" class="btn btn-primary">Save filters</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Index -->
|
||||
<script>
|
||||
/* URL */
|
||||
|
@ -299,11 +377,6 @@ small {
|
|||
<script src="{{ url_for('static', filename='js/index/index_map.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/index/index_pie.js') }}"></script>
|
||||
|
||||
|
||||
<link href="{{ url_for('static', filename='css/font-awesome.min.css') }}" rel="text/css">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/jquery-jvectormap-2.0.3.css') }}" type="text/css" media="screen"/>
|
||||
<script src="{{ url_for('static', filename='js/jquery-jvectormap-2.0.3.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/jquery-jvectormap-world-mill.js') }}"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
</script>
|
||||
|
|
19
util.py
19
util.py
|
@ -102,3 +102,22 @@ def sortByTrendingScore(toSort, topNum=5):
|
|||
topArray.append(dailyCombi)
|
||||
|
||||
return topArray
|
||||
|
||||
|
||||
def getFields(obj, fields):
|
||||
jsonWalker = fields.split('.')
|
||||
itemToExplore = obj
|
||||
lastName = ""
|
||||
try:
|
||||
for i in jsonWalker:
|
||||
itemToExplore = itemToExplore[i]
|
||||
lastName = i
|
||||
if type(itemToExplore) is list:
|
||||
return {'name': lastName, 'data': itemToExplore}
|
||||
else:
|
||||
if i == 'timestamp':
|
||||
itemToExplore = datetime.datetime.utcfromtimestamp(
|
||||
int(itemToExplore)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
return itemToExplore
|
||||
except KeyError as e:
|
||||
return None
|
||||
|
|
|
@ -53,23 +53,6 @@ users_helper = users_helper.Users_helper(serv_redis_db, cfg)
|
|||
trendings_helper = trendings_helper.Trendings_helper(serv_redis_db, cfg)
|
||||
|
||||
|
||||
def getFields(obj, fields):
|
||||
jsonWalker = fields.split('.')
|
||||
itemToExplore = obj
|
||||
lastName = ""
|
||||
try:
|
||||
for i in jsonWalker:
|
||||
itemToExplore = itemToExplore[i]
|
||||
lastName = i
|
||||
if type(itemToExplore) is list:
|
||||
return { 'name': lastName , 'data': itemToExplore }
|
||||
else:
|
||||
if i == 'timestamp':
|
||||
itemToExplore = datetime.datetime.utcfromtimestamp(int(itemToExplore)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
return itemToExplore
|
||||
except KeyError as e:
|
||||
return ""
|
||||
|
||||
##############
|
||||
## HANDLERS ##
|
||||
##############
|
||||
|
@ -214,16 +197,6 @@ def handler_attribute(zmq_name, jsonobj, hasAlreadyBeenContributed=False):
|
|||
pass
|
||||
trendings_helper.addTrendingTags(tags, timestamp)
|
||||
|
||||
to_push = []
|
||||
for field in json.loads(cfg.get('Dashboard', 'fieldname_order')):
|
||||
if type(field) is list:
|
||||
to_join = []
|
||||
for subField in field:
|
||||
to_join.append(str(getFields(jsonobj, subField)))
|
||||
to_add = cfg.get('Dashboard', 'char_separator').join(to_join)
|
||||
else:
|
||||
to_add = getFields(jsonobj, field)
|
||||
to_push.append(to_add)
|
||||
|
||||
#try to get coord from ip
|
||||
if jsonattr['category'] == "Network activity":
|
||||
|
@ -242,7 +215,7 @@ def handler_attribute(zmq_name, jsonobj, hasAlreadyBeenContributed=False):
|
|||
action,
|
||||
isLabeled=eventLabeled)
|
||||
# Push to log
|
||||
live_helper.publish_log(zmq_name, 'Attribute', to_push)
|
||||
live_helper.publish_log(zmq_name, 'Attribute', jsonobj)
|
||||
|
||||
|
||||
###############
|
||||
|
|
Loading…
Reference in New Issue