diff --git a/helpers/live_helper.py b/helpers/live_helper.py index 985bca8..7078e49 100644 --- a/helpers/live_helper.py +++ b/helpers/live_helper.py @@ -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 diff --git a/server.py b/server.py index 8a1943f..5136e0c 100755 --- a/server.py +++ b/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() diff --git a/static/js/index/index.js b/static/js/index/index.js index 7c5a752..0fa2334 100644 --- a/static/js/index/index.js +++ b/static/js/index/index.js @@ -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 = $(''); + data.data.forEach(function(cur, i) { + switch (data.name) { + case 'Tag': + var $tag = $(''); + $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 += '
' + e; + } else { + $toRet += e; + } + }); + } + return $toRet; + }, + } + ], + }; + + this.DOMTable = $('
'); + this._options.container.append(this.DOMTable); + this.origTableOptions.columns = []; + var that = this; + this._options.tableHeader.forEach(function(field) { + var th = $(''+field+''); + 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 = $('
'); + var led = $('
'); + 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($('Status')).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 + + + + + + + + + + + + + + + + + + + @@ -198,7 +268,7 @@ small { -
+
@@ -212,23 +282,12 @@ small {
Logs -
- INFO - WARNING - CRITICAL +
+ +
-
-
-
- - - - - -
-
-
+
@@ -254,6 +313,25 @@ small {
+ + + - - - - - diff --git a/util.py b/util.py index 6f6d0db..78ded1b 100644 --- a/util.py +++ b/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 diff --git a/zmq_dispatcher.py b/zmq_dispatcher.py index f7e73bb..436ec1d 100755 --- a/zmq_dispatcher.py +++ b/zmq_dispatcher.py @@ -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) ###############