chg: [dashboard] add objects tooltip

This commit is contained in:
terrtia 2024-11-27 14:17:22 +01:00
parent e0ae20968d
commit 3d14bac434
No known key found for this signature in database
GPG key ID: 1E1B1F50D84613D0
7 changed files with 320 additions and 7 deletions

View file

@ -709,7 +709,15 @@ def add_object_tag(tag, obj_type, obj_id, subtype=''):
else: else:
r_tags.sadd(f'{obj_type}:{subtype}:{tag}', obj_id) r_tags.sadd(f'{obj_type}:{subtype}:{tag}', obj_id)
# STATS
r_tags.hincrby(f'daily_tags:{datetime.date.today().strftime("%Y%m%d")}', tag, 1) r_tags.hincrby(f'daily_tags:{datetime.date.today().strftime("%Y%m%d")}', tag, 1)
mess = f'{int(time.time())}:{obj_type}:{subtype}:{obj_id}'
r_tags.lpush('dashboard:tags', mess)
r_tags.ltrim('dashboard:tags', 0, 19)
def get_tags_dashboard():
return r_tags.lrange('dashboard:tags', 0, -1)
# obj -> Object() # obj -> Object()
def confirm_tag(tag, obj): def confirm_tag(tag, obj):

View file

@ -15,6 +15,8 @@ sys.path.append(os.environ['AIL_BIN'])
################################## ##################################
from lib.ConfigLoader import ConfigLoader from lib.ConfigLoader import ConfigLoader
from lib.objects import ail_objects from lib.objects import ail_objects
from lib import Tag
from lib import Tracker
# Config # Config
@ -93,5 +95,20 @@ def get_nb_objs_dashboard():
date = datetime.date.today().strftime("%Y%m%d") date = datetime.date.today().strftime("%Y%m%d")
return ail_objects.get_nb_objects_dashboard(date) return ail_objects.get_nb_objects_dashboard(date)
def get_tagged_objs_dashboard():
tagged_objs = []
for tagged_obj in Tag.get_tags_dashboard():
timestamp, obj_gid = tagged_obj.split(':', 1)
timestamp = datetime.datetime.utcfromtimestamp(int(timestamp)).strftime('%H:%M:%S')
obj_meta = ail_objects.get_obj_basic_meta(ail_objects.get_obj_from_global_id(obj_gid), flask_context=True)
obj_meta['date_tag'] = timestamp
tagged_objs.append(obj_meta)
return tagged_objs
def get_tracked_objs_dashboard(user_org, user_id):
trackers = Tracker.get_trackers_dashboard(user_org, user_id)
for t in trackers:
t['obj'] = ail_objects.get_obj_basic_meta(ail_objects.get_obj_from_global_id(t['obj']))
return trackers

View file

@ -258,6 +258,12 @@ def add_obj_tags(obj_type, subtype, id, tags):
#### OBJ META #### #### OBJ META ####
def get_obj_basic_meta(obj, flask_context=False):
meta = obj.get_default_meta(tags=True)
meta['icon'] = obj.get_svg_icon()
meta['link'] = obj.get_link(flask_context=flask_context)
meta['gid'] = obj.get_global_id()
return meta
def get_object_meta(obj_type, subtype, id, options=set(), flask_context=False): def get_object_meta(obj_type, subtype, id, options=set(), flask_context=False):
obj = get_object(obj_type, subtype, id) obj = get_object(obj_type, subtype, id)

View file

@ -302,7 +302,8 @@ sock = Sock(app)
@login_required @login_required
@sock.route('/ws/dashboard') @sock.route('/ws/dashboard')
def ws_dashboard(ws): def ws_dashboard(ws):
# TODO wait %30 user_org = current_user.get_org()
user_id = current_user.get_user_id()
next_feeders = ail_stats.get_next_feeder_timestamp(int(time.time())) + 1 next_feeders = ail_stats.get_next_feeder_timestamp(int(time.time())) + 1
try: try:
while True: while True:
@ -313,7 +314,9 @@ def ws_dashboard(ws):
if int(time.time()) >= next_feeders: if int(time.time()) >= next_feeders:
feeders = ail_stats.get_feeders_dashboard() feeders = ail_stats.get_feeders_dashboard()
objs = ail_stats.get_nb_objs_today() objs = ail_stats.get_nb_objs_today()
ws.send(json.dumps({'feeders': feeders, 'objs': objs})) tags = ail_stats.get_tagged_objs_dashboard()
trackers = ail_stats.get_tracked_objs_dashboard(user_org, user_id)
ws.send(json.dumps({'feeders': feeders, 'objs': objs, 'tags': tags, 'trackers': trackers}))
next_feeders = next_feeders + 30 next_feeders = next_feeders + 30
time.sleep(1) time.sleep(1)
except Exception as e: # ConnectionClosed ? except Exception as e: # ConnectionClosed ?

View file

@ -10,7 +10,7 @@ import sys
import json import json
from flask import Flask, render_template, jsonify, request, Blueprint, redirect, url_for, Response, abort, send_file, stream_with_context from flask import Flask, render_template, jsonify, request, Blueprint, redirect, url_for, Response, abort, send_file, stream_with_context
from flask_login import login_required from flask_login import login_required, current_user
# Import Role_Manager # Import Role_Manager
from Role_Manager import login_admin, login_read_only from Role_Manager import login_admin, login_read_only
@ -37,10 +37,15 @@ bootstrap_label = ['primary', 'success', 'danger', 'warning', 'info']
@login_required @login_required
@login_read_only @login_read_only
def objects(): def objects():
user_org = current_user.get_org()
user_id = current_user.get_user_id()
nb_objects = ail_stats.get_nb_objs_dashboard() nb_objects = ail_stats.get_nb_objs_dashboard()
print(nb_objects)
feeders_dashboard = ail_stats.get_feeders_dashboard_full() feeders_dashboard = ail_stats.get_feeders_dashboard_full()
return render_template("objs_dashboard.html", feeders_dashboard=feeders_dashboard, nb_objects=nb_objects) trackers = ail_stats.get_tracked_objs_dashboard(user_org, user_id)
tagged_objs = ail_stats.get_tagged_objs_dashboard()
return render_template("objs_dashboard.html", feeders_dashboard=feeders_dashboard,
nb_objects=nb_objects, trackers=trackers, tagged_objs=tagged_objs,
bootstrap_label=bootstrap_label)

View file

@ -2,3 +2,124 @@
function sanitize_text(str_to_sanitize) { function sanitize_text(str_to_sanitize) {
return $("<span\>").text(str_to_sanitize).html() return $("<span\>").text(str_to_sanitize).html()
} }
// REQUIRE var url_obj_description
function show_obj_tooltip(container, obj_gid) {
container = $(container);
if (container.data('bs.popover')) {
container.popover('show');
} else {
let pop_header = "<div class=\"card text-white\"><div class=\"card-header bg-dark pb-0\">" + sanitize_text(obj_gid) + "/div>";
let spinner = "<div class=\"card-body bg-dark pt-0\"><div class=\"spinner-border text-warning\" role=\"status\"></div> Loading...</div>";
container.popover({
title: pop_header,
content: spinner,
html: true,
container: container,
})
container.popover('show');
let popoverInstance = container.data('bs.popover');
$.getJSON(url_obj_description + obj_gid, function (data) {
let desc = "<div class=\"card-body bg-dark text-white pb-1 pt-2\"><dl class=\"row py-0 my-0\">"
Object.keys(data).forEach(function(key) {
if (key=="status") {
desc = desc + "<dt class=\"col-sm-3 px-0\">status</dt><dd class=\"col-sm-9 px-0\"><div class=\"badge badge-pill badge-light flex-row-reverse\" style=\"color:"
if (data["status"]) {
desc = desc + "Green"
} else {
desc = desc + "Red"
}
desc = desc + ";\"><i class=\"fas "
if (data["status"]) {
desc = desc + "fa-check-circle\"></i>UP"
} else {
desc = desc + "fa-times-circle\"></i>DOWN"
}
desc = desc + "</div></dd>"
} else if (key!=="tags" && key!=="id" && key!=="img" && key!=="svg_icon" && key!=="icon" && key!=="link" && key!=="type") {
if (data[key]) {
if ((key==="first_seen" || key==="last_seen") && data[key].length===8) {
let date = sanitize_text(data[key])
desc = desc + "<dt class=\"col-sm-3 px-0\">" + sanitize_text(key) + "</dt><dd class=\"col-sm-9 px-0 mb-1\">" + date.slice(0, 4) + "-" + date.slice(4, 6) + "-" + date.slice(6, 8) + "</dd>"
} else {
desc = desc + "<dt class=\"col-sm-3 px-0\">" + sanitize_text(key) + "</dt><dd class=\"col-sm-9 px-0 mb-1\">" + sanitize_text(data[key]) + "</dd>"
}
}
}
});
desc = desc + "</dl>"
if (data["tags"]) {
data["tags"].forEach(function(tag) {
desc = desc + "<span class=\"badge badge-warning\">"+ sanitize_text(tag) +"</span>";
});
}
/*if (data["img"]) {
if (data["tags_safe"]) {
if (data["type"] === "screenshot") {
desc = desc + "<img src={{ url_for('objects_item.screenshot', filename="") }}"
} else if (data["type"] === "favicon") {
desc = desc + "<img src={{ url_for('objects_favicon.favicon', filename="") }}"
} else {
desc = desc + "<img src={{ url_for('objects_image.image', filename="") }}"
}
desc = desc + data["img"] +" class=\"img-thumbnail blured\" id=\"tooltip_screenshot_correlation\" style=\"\"/>";
} else {
desc = desc + "<span class=\"my-2 fa-stack fa-4x\"><i class=\"fas fa-stack-1x fa-image\"></i><i class=\"fas fa-stack-2x fa-ban\" style=\"color:Red\"></i></span>";
}
}*/
desc = desc + "</div></div>"
//div.html(desc)
// .style("left", (d3_pageX) + "px")
// .style("top", (d3_pageY - 28) + "px");
//d.popover = desc
if (data["img"]) {
blur_tooltip();
}
popoverInstance.config.content = desc;
popoverInstance.setContent();
popoverInstance.update();
//let popoverid = container.attr('aria-describedby');
//$('#' + popoverid).find('.popover-header').html(newTitle);
//$('#' + popoverid).find('.popover-body').html('newContesssnt');
}).fail(function(error) {
let desc = "<div class=\"card-body bg-dark text-white pt-0\"><i class=\"fas fa-3x fa-times text-danger\"></i>"+ error.statusText +"</div>"
popoverInstance.config.content = desc;
popoverInstance.setContent();
popoverInstance.update();
});
container.popover('hide');
container.popover('show');
}
}
function hide_obj_tooltip(container) {
container = $(container);
container.popover('hide')
}
function blur_tooltip(){
var image = $('#tooltip_screenshot_correlation')[0];
if (image) {
let blurValue = $('#blur-slider-correlation').val();
blurValue = 15 - blurValue;
image.style.filter = "blur(" + blurValue + "px)";
}
}

View file

@ -11,6 +11,8 @@
<!-- JS --> <!-- JS -->
<script src="{{ url_for('static', filename='js/jquery.js')}}"></script> <script src="{{ url_for('static', filename='js/jquery.js')}}"></script>
<script src="{{ url_for('static', filename='js/helper.js')}}"></script>
<script src="{{ url_for('static', filename='js/popper.min.js')}}"></script>
<script src="{{ url_for('static', filename='js/echarts.min.js')}}"></script> <script src="{{ url_for('static', filename='js/echarts.min.js')}}"></script>
<script src="{{ url_for('static', filename='js/bootstrap4.min.js')}}"></script> <script src="{{ url_for('static', filename='js/bootstrap4.min.js')}}"></script>
@ -24,6 +26,10 @@
<style> <style>
.popover {
max-width: none;
width: 500px;
}
.icon-button { .icon-button {
position: relative; position: relative;
display: inline-block; display: inline-block;
@ -34,7 +40,6 @@
cursor: pointer; cursor: pointer;
outline: inherit; outline: inherit;
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
}
} }
.icon-wrapper { .icon-wrapper {
@ -85,6 +90,83 @@
<div class="row">
<div class="col-12 col-xl-6">
<table class="table">
<thead>
<tr>
<th><i class="fa-solid fa-crosshairs"></i></th>
<th>tracker</th>
<th class="text-center"><i class="fas fa-cube"></i></th>
<th>Time</th>
</tr>
</thead>
<tbody id="tracked_objs">
{% for tracker in trackers %}
<tr>
<td>{{ tracker['type'] }}</td>
<td style="word-break: break-all;">
<a href="{{ url_for('hunters.show_tracker') }}?uuid={{ tracker['uuid'] }}">{{ tracker['tracked'] }}</a>
</td>
<td class="text-center">
<a href="{{ tracker['obj']['link'] }}" onmouseenter="show_obj_tooltip(this, '{{ tracker['obj']['gid'] }}')" onmouseleave="hide_obj_tooltip(this)">
<svg height="26" width="26" style="pointer-events:none;">
<g class="nodes">
<circle cx="13" cy="13" r="13" fill="{{ tracker['obj']['icon']['color'] }}"></circle>
<text x="13" y="13" text-anchor="middle" dominant-baseline="central" class="{{ tracker['obj']['icon']['style'] }}" font-size="16px">{{ tracker['obj']['icon']['icon'] }}</text>
</g>
</svg>
</a>
</td>
<td>{{ tracker['timestamp'] }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="col-12 col-xl-6">
<table class="table">
<thead>
<tr>
<th class="text-center"><i class="fas fa-cube"></i></th>
<th>ID</th>
<th><i class="fas fa-tag"></i> Tags</th>
<th>Time</th>
</tr>
</thead>
<tbody id="t_tags_objs">
{% for t_obj in tagged_objs %}
<tr>
<td>
<a href="{{ t_obj['link'] }}" onmouseenter="show_obj_tooltip(this, '{{ t_obj['gid'] }}')" onmouseleave="hide_obj_tooltip(this)">
<svg height="26" width="26">
<g class="nodes">
<circle cx="13" cy="13" r="13" fill="{{ t_obj['icon']['color'] }}"></circle>
<text x="13" y="13" text-anchor="middle" dominant-baseline="central" class="{{ t_obj['icon']['style'] }}" font-size="16px">{{ t_obj['icon']['icon'] }}</text>
</g>
</svg>
</a>
</td>
<td style="word-break: break-all;">
<a href="{{ t_obj['link'] }}">{{ t_obj['id'] }}</a>
</td>
<td>
{% for tag in t_obj['tags'] %}
<span class="badge badge-{{ bootstrap_label[loop.index0 % 5] }}">{{ tag }}</span>
{% endfor %}
</td>
<td>{{ t_obj['date_tag'] }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div> </div>
<script> <script>
@ -102,6 +184,9 @@
</script> </script>
<script> <script>
var tags_colors = ['primary', 'success', 'danger', 'warning', 'info'];
var url_obj_description = "{{ url_for('correlation.get_description') }}?object_id=";
// Init Dashboard // Init Dashboard
var feederChart = echarts.init(document.getElementById('feeders_dashboard')); var feederChart = echarts.init(document.getElementById('feeders_dashboard'));
window.addEventListener('resize', function() { window.addEventListener('resize', function() {
@ -201,22 +286,90 @@ function updateNbObjects(data) {
} }
} }
///////
function create_obj_svg(container, obj_gid, url, color, fa_style, icon) {
var svg_obj = '<svg height="26" width="26"> <g class="nodes"> <circle cx="13" cy="13" r="13" fill="' + color + '"></circle> <text x="13" y="13" text-anchor="middle" dominant-baseline="central" class="' + fa_style + '" font-size="16px">' + icon + '</text> </g> </svg>';
var obj_link = $('<a>');
obj_link.on('mouseenter', function () {
show_obj_tooltip(this, obj_gid);
});
obj_link.on('mouseleave', function () {
hide_obj_tooltip(this);
});
obj_link.attr('href', url);
obj_link.html(svg_obj);
container.append(obj_link);
return container
}
function create_tags_badges(container, tags) {
var tspan = '';
for (const i in tags) {
tspan = tspan + '<span class="badge badge-' + 'primary' + '">' + sanitize_text(tags[i]) + '</span>'
}
return tspan;
}
function update_tags_obj_dashboard(data) {
// delete table
var tbody = $("#t_tags_objs");
var ntd;
tbody.empty();
for (const elem in data) {
let obj = data[elem];
var row = $('<tr></tr>');
ntd = $('<td></td>');
row.append(create_obj_svg(ntd, obj['gid'], obj.link, obj['icon']['color'], obj['icon']['style'], obj['icon']['icon']))
row.append('<td style="word-break: break-all;"><a href="'+ obj['link'] + '">' + sanitize_text(obj['id']) + '</a></td>');
ntd = $('<td></td>');
row.append(create_tags_badges(ntd, obj['tags']));
row.append('<td>' + obj['date_tag'] + '</td>');
tbody.append(row);
{# {% for tag in t_obj['tags'] %}#}
{# <span class="badge badge-{{ bootstrap_label[loop.index0 % 5] }}">{{ tag }}</span>#}
{# {% endfor %}#}
{#</td>#}
}
}
function update_tracked_objs_dashboard(data) {
// delete table
var tbody = $("#tracked_objs");
var ntd;
tbody.empty();
for (const elem in data) {
let tracker = data[elem];
var row = $('<tr></tr>');
row.append('<td>' + tracker['type'] + '</td>');
row.append('<td style="word-break: break-all;"><a href="{{ url_for('hunters.show_tracker') }}?uuid=' + tracker['uuid'] + '">' + sanitize_text(tracker['tracked']) + '</a></td>');
ntd = $('<td class="text-center"></td>');
row.append(create_obj_svg(ntd, tracker['obj']['gid'], tracker['obj']['link'], tracker['obj']['icon']['color'], tracker['obj']['icon']['style'], tracker['obj']['icon']['icon']));
row.append('<td>' + tracker['timestamp'] + '</td>');
tbody.append(row);
}
}
// WebSocket // WebSocket
var socket = new WebSocket("{{ url_for('ws_dashboard') }}"); var socket = new WebSocket("{{ url_for('ws_dashboard') }}");
socket.wsocket = function(event) { socket.wsocket = function(event) {
console.log("WebSocket connection opened:", event); console.log("WebSocket connection opened:", event);
}; };
socket.onmessage = function(event) { socket.onmessage = function(event) {
$("[data-toggle='popover']").popover('destroy');
let data = JSON.parse(event.data); let data = JSON.parse(event.data);
updateFeederChart(data['feeders']); updateFeederChart(data['feeders']);
updateNbObjects(data['objs']); updateNbObjects(data['objs']);
update_tracked_objs_dashboard(data['trackers']);
update_tags_obj_dashboard(data['tags']);
}; };
socket.onerror = function(error) { socket.onerror = function(error) {
console.error('WebSocket error:', error); console.error('WebSocket error:', error);
}; };
socket.onclose = function(event) { socket.onclose = function(event) {
console.log('WebSocket connection closed:', event); console.log('WebSocket connection closed:', event);
// Optionally implement reconnection logic here
}; };