mirror of https://github.com/CIRCL/AIL-framework
				
				
				
			chg: [dashboard] add objects tooltip
							parent
							
								
									e0ae20968d
								
							
						
					
					
						commit
						3d14bac434
					
				|  | @ -709,7 +709,15 @@ def add_object_tag(tag, obj_type, obj_id, subtype=''): | |||
|         else: | ||||
|             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) | ||||
|         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() | ||||
| def confirm_tag(tag, obj): | ||||
|  |  | |||
|  | @ -15,6 +15,8 @@ sys.path.append(os.environ['AIL_BIN']) | |||
| ################################## | ||||
| from lib.ConfigLoader import ConfigLoader | ||||
| from lib.objects import ail_objects | ||||
| from lib import Tag | ||||
| from lib import Tracker | ||||
| 
 | ||||
| 
 | ||||
| # Config | ||||
|  | @ -93,5 +95,20 @@ def get_nb_objs_dashboard(): | |||
|     date = datetime.date.today().strftime("%Y%m%d") | ||||
|     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 | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -258,6 +258,12 @@ def add_obj_tags(obj_type, subtype, id, tags): | |||
| 
 | ||||
| #### 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): | ||||
|     obj = get_object(obj_type, subtype, id) | ||||
|  |  | |||
|  | @ -302,7 +302,8 @@ sock = Sock(app) | |||
| @login_required | ||||
| @sock.route('/ws/dashboard') | ||||
| 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 | ||||
|     try: | ||||
|         while True: | ||||
|  | @ -313,7 +314,9 @@ def ws_dashboard(ws): | |||
|             if int(time.time()) >= next_feeders: | ||||
|                 feeders = ail_stats.get_feeders_dashboard() | ||||
|                 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 | ||||
|             time.sleep(1) | ||||
|     except Exception as e:  # ConnectionClosed ? | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ import sys | |||
| import json | ||||
| 
 | ||||
| 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 | ||||
| from Role_Manager import login_admin, login_read_only | ||||
|  | @ -37,10 +37,15 @@ bootstrap_label = ['primary', 'success', 'danger', 'warning', 'info'] | |||
| @login_required | ||||
| @login_read_only | ||||
| def objects(): | ||||
|     user_org = current_user.get_org() | ||||
|     user_id = current_user.get_user_id() | ||||
|     nb_objects = ail_stats.get_nb_objs_dashboard() | ||||
|     print(nb_objects) | ||||
|     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) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,3 +2,124 @@ | |||
| function sanitize_text(str_to_sanitize) { | ||||
|     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)"; | ||||
|     } | ||||
| } | ||||
|  | @ -11,6 +11,8 @@ | |||
| 
 | ||||
|   <!-- JS --> | ||||
| 	<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/bootstrap4.min.js')}}"></script> | ||||
| 
 | ||||
|  | @ -24,6 +26,10 @@ | |||
| 
 | ||||
| 
 | ||||
|     <style> | ||||
|         .popover { | ||||
|       max-width: none; | ||||
|       width: 500px; | ||||
|     } | ||||
|         .icon-button { | ||||
|       position: relative; | ||||
|       display: inline-block; | ||||
|  | @ -34,7 +40,6 @@ | |||
|       cursor: pointer; | ||||
|       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; | ||||
| } | ||||
|     } | ||||
| 
 | ||||
|     .icon-wrapper { | ||||
|  | @ -83,6 +88,83 @@ | |||
| 
 | ||||
| 		</div> | ||||
| 
 | ||||
|         | ||||
| 
 | ||||
|         <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> | ||||
|  | @ -102,6 +184,9 @@ | |||
| </script> | ||||
| 
 | ||||
| <script> | ||||
|     var tags_colors = ['primary', 'success', 'danger', 'warning', 'info']; | ||||
|     var url_obj_description = "{{ url_for('correlation.get_description') }}?object_id="; | ||||
| 
 | ||||
|     // Init Dashboard | ||||
|     var feederChart = echarts.init(document.getElementById('feeders_dashboard')); | ||||
|     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 | ||||
| var socket = new WebSocket("{{ url_for('ws_dashboard') }}"); | ||||
| socket.wsocket = function(event) { | ||||
|     console.log("WebSocket connection opened:", event); | ||||
| }; | ||||
| socket.onmessage = function(event) { | ||||
|     $("[data-toggle='popover']").popover('destroy'); | ||||
|     let data = JSON.parse(event.data); | ||||
|     updateFeederChart(data['feeders']); | ||||
|     updateNbObjects(data['objs']); | ||||
|     update_tracked_objs_dashboard(data['trackers']); | ||||
|     update_tags_obj_dashboard(data['tags']); | ||||
| }; | ||||
| socket.onerror = function(error) { | ||||
|     console.error('WebSocket error:', error); | ||||
| }; | ||||
| socket.onclose = function(event) { | ||||
|     console.log('WebSocket connection closed:', event); | ||||
|     // Optionally implement reconnection logic here | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 terrtia
						terrtia