new+chg: [livelog] Added basic filtering capabilities and fullscreen mode

Also, Improved table, reconnection mechanism and UI
pull/82/head
mokaddem 2019-02-22 10:41:54 +01:00
parent 1e91b9b59c
commit 97a9465915
6 changed files with 789 additions and 129 deletions

View File

@ -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

View File

@ -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()

View File

@ -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 "";
}

View File

@ -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">&times;</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
View File

@ -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

View File

@ -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)
###############