chg: [barcode] add and extract barcode object

This commit is contained in:
terrtia 2024-11-05 14:10:30 +01:00
parent ee576ca06f
commit 79a187b039
No known key found for this signature in database
GPG key ID: 1E1B1F50D84613D0
18 changed files with 1161 additions and 48 deletions

View file

@ -280,7 +280,7 @@ function launching_scripts {
sleep 0.1
screen -S "Script_AIL" -X screen -t "OcrExtractor" bash -c "cd ${AIL_BIN}/modules; ${ENV_PY} ./OcrExtractor.py; read x"
sleep 0.1
screen -S "Script_AIL" -X screen -t "QrCodeReader" bash -c "cd ${AIL_BIN}/modules; ${ENV_PY} ./QrCodeReader.py; read x"
screen -S "Script_AIL" -X screen -t "CodeReader" bash -c "cd ${AIL_BIN}/modules; ${ENV_PY} ./CodeReader.py; read x"
sleep 0.1
# TITLES

View file

@ -16,16 +16,17 @@ r_serv_db = config_loader.get_db_conn("Kvrocks_DB")
r_object = config_loader.get_db_conn("Kvrocks_Objects")
config_loader = None
AIL_OBJECTS = sorted({'chat', 'chat-subchannel', 'chat-thread', 'cookie-name', 'cve', 'cryptocurrency', 'decoded',
'domain', 'dom-hash', 'etag', 'favicon', 'file-name', 'hhhash','item', 'image', 'message', 'ocr',
'pgp', 'qrcode', 'screenshot', 'title', 'user-account', 'username'})
AIL_OBJECTS = sorted({'barcode', 'chat', 'chat-subchannel', 'chat-thread', 'cookie-name', 'cve', 'cryptocurrency',
'decoded', 'domain', 'dom-hash', 'etag', 'favicon', 'file-name', 'hhhash','item', 'image',
'message', 'ocr', 'pgp', 'qrcode', 'screenshot', 'title', 'user-account', 'username'})
AIL_OBJECTS_WITH_SUBTYPES = {'chat', 'chat-subchannel', 'cryptocurrency', 'pgp', 'username', 'user-account'}
# TODO by object TYPE ????
AIL_OBJECTS_CORRELATIONS_DEFAULT = sorted({'chat', 'chat-subchannel', 'chat-thread', 'cve', 'cryptocurrency', 'decoded',
'domain', 'dom-hash', 'favicon', 'file-name', 'item', 'image', 'message',
'ocr', 'pgp', 'qrcode', 'screenshot', 'title', 'user-account', 'username'})
AIL_OBJECTS_CORRELATIONS_DEFAULT = sorted({'barcode', 'chat', 'chat-subchannel', 'chat-thread', 'cve', 'cryptocurrency',
'decoded', 'domain', 'dom-hash', 'favicon', 'file-name', 'item', 'image',
'message', 'ocr', 'pgp', 'qrcode', 'screenshot', 'title', 'user-account',
'username'})
def get_ail_uuid():
ail_uuid = r_serv_db.get('ail:uuid')
@ -86,10 +87,10 @@ def get_default_correlation_objects():
return AIL_OBJECTS_CORRELATIONS_DEFAULT
def get_obj_queued():
return ['item', 'image', 'message', 'ocr', 'qrcode'] # screenshot ???
return ['barcode', 'item', 'image', 'message', 'ocr', 'qrcode'] # screenshot ???
def get_objects_tracked():
return ['decoded', 'item', 'pgp', 'message', 'ocr', 'qrcode', 'title']
return ['barcode', 'decoded', 'item', 'pgp', 'message', 'ocr', 'qrcode', 'title']
def get_objects_retro_hunted():
return ['decoded', 'item', 'message']

View file

@ -23,6 +23,7 @@ from lib.objects import Chats
from lib.objects import ChatSubChannels
from lib.objects import ChatThreads
from lib.objects import Messages
from lib.objects.BarCodes import Barcode
from lib.objects.QrCodes import Qrcode
from lib.objects import UsersAccount
from lib.objects import Usernames
@ -429,6 +430,12 @@ def get_chat_object_messages_meta(c_messages):
temp_chats[meta['forwarded_from']] = chat.get_meta({'icon'})
else:
meta['forwarded_from'] = temp_chats[meta['forwarded_from']]
if meta['barcodes']:
barcodes = []
for q in meta['barcodes']:
obj = Barcode(q)
barcodes.append({'id': obj.id, 'content': obj.get_content(), 'tags': obj.get_tags()})
meta['qrcodes'] = barcodes
if meta['qrcodes']:
qrcodes = []
for q in meta['qrcodes']:
@ -746,6 +753,14 @@ def api_get_chat_service_instance(chat_instance_uuid):
# return chat_instance.get_meta({'chats'}), 200
return chat_instance.get_meta({'chats_with_messages'}), 200
def api_get_chats_selector():
selector = []
for instance_uuid in get_chat_service_instances():
for chat_id in ChatServiceInstance(instance_uuid).get_chats():
chat = Chats.Chat(chat_id, instance_uuid)
selector.append({'id': chat.get_global_id(), 'name': f'{chat.get_chat_instance()}: {chat.get_label()}'})
return selector
def api_get_chat(chat_id, chat_instance_uuid, translation_target=None, nb=-1, page=-1, messages=True):
chat = Chats.Chat(chat_id, chat_instance_uuid)
if not chat.exists():
@ -826,10 +841,15 @@ def api_get_message(message_id, translation_target=None):
message = Messages.Message(message_id)
if not message.exists():
return {"status": "error", "reason": "Unknown uuid"}, 404
meta = message.get_meta({'chat', 'content', 'files-names', 'forwarded_from', 'icon', 'images', 'language', 'link', 'parent', 'parent_meta', 'qrcodes', 'reactions', 'thread', 'translation', 'user-account'}, translation_target=translation_target)
meta = message.get_meta({'barcodes', 'chat', 'content', 'files-names', 'forwarded_from', 'icon', 'images', 'language', 'link', 'parent', 'parent_meta', 'qrcodes', 'reactions', 'thread', 'translation', 'user-account'}, translation_target=translation_target)
if 'forwarded_from' in meta:
chat = get_obj_chat_from_global_id(meta['forwarded_from'])
meta['forwarded_from'] = chat.get_meta({'icon'})
barcodes = []
for q in meta['barcodes']:
obj = Barcode(q)
barcodes.append({'id': obj.id, 'content': obj.get_content(), 'tags': obj.get_tags()})
meta['barcodes'] = barcodes
qrcodes = []
for q in meta['qrcodes']:
qr = Qrcode(q)

View file

@ -41,26 +41,27 @@ config_loader = None
##################################
CORRELATION_TYPES_BY_OBJ = {
"chat": ["chat-subchannel", "chat-thread", "image", "message", "ocr", "user-account"], # message or direct correlation like cve, bitcoin, ... ???
"barcode": ["chat", "cve", "cryptocurrency", "decoded", "domain", "image", "message", "screenshot"],
"chat": ["barcode", "chat-subchannel", "chat-thread", "image", "message", "ocr", "user-account"], # message or direct correlation like cve, bitcoin, ... ???
"chat-subchannel": ["chat", "chat-thread", "image", "message", "ocr", "user-account"],
"chat-thread": ["chat", "chat-subchannel", "image", "message", "ocr", "user-account"],
"cookie-name": ["domain"],
"cryptocurrency": ["domain", "item", "message", "ocr", "qrcode"],
"cve": ["domain", "item", "message", "ocr", "qrcode"],
"decoded": ["domain", "item", "message", "ocr", "qrcode"],
"domain": ["cve", "cookie-name", "cryptocurrency", "dom-hash", "decoded", "etag", "favicon", "hhhash", "item", "pgp", "title", "screenshot", "username"],
"cryptocurrency": ["barcode", "domain", "item", "message", "ocr", "qrcode"],
"cve": ["barcode", "domain", "item", "message", "ocr", "qrcode"],
"decoded": ["barcode", "domain", "item", "message", "ocr", "qrcode"],
"domain": ["barcode", "cve", "cookie-name", "cryptocurrency", "dom-hash", "decoded", "etag", "favicon", "hhhash", "item", "pgp", "title", "screenshot", "username"],
"dom-hash": ["domain", "item"],
"etag": ["domain"],
"favicon": ["domain", "item"], # TODO Decoded
"file-name": ["chat", "message"],
"hhhash": ["domain"],
"image": ["chat", "chat-subchannel", "chat-thread", "message", "ocr", "qrcode", "user-account"], # TODO subchannel + threads ????
"image": ["barcode", "chat", "chat-subchannel", "chat-thread", "message", "ocr", "qrcode", "user-account"], # TODO subchannel + threads ????
"item": ["cve", "cryptocurrency", "decoded", "domain", "dom-hash", "favicon", "pgp", "screenshot", "title", "username"], # chat ???
"message": ["chat", "chat-subchannel", "chat-thread", "cve", "cryptocurrency", "decoded", "file-name", "image", "ocr", "pgp", "user-account"],
"message": ["barcode", "chat", "chat-subchannel", "chat-thread", "cve", "cryptocurrency", "decoded", "file-name", "image", "ocr", "pgp", "user-account"],
"ocr": ["chat", "chat-subchannel", "chat-thread", "cve", "cryptocurrency", "decoded", "image", "message", "pgp", "user-account"],
"pgp": ["domain", "item", "message", "ocr"],
"qrcode": ["chat", "cve", "cryptocurrency", "decoded", "domain", "image", "message", "screenshot"], # "chat-subchannel", "chat-thread" ?????
"screenshot": ["domain", "item", "qrcode"],
"screenshot": ["barcode", "domain", "item", "qrcode"],
"title": ["domain", "item"],
"user-account": ["chat", "chat-subchannel", "chat-thread", "image", "message", "ocr", "username"],
"username": ["domain", "item", "message", "user-account"],

163
bin/lib/objects/BarCodes.py Executable file
View file

@ -0,0 +1,163 @@
#!/usr/bin/env python3
# -*-coding:UTF-8 -*
import os
import sys
from hashlib import sha256
from pymisp import MISPObject
sys.path.append(os.environ['AIL_BIN'])
##################################
# Import Project packages
##################################
from lib.objects.abstract_daterange_object import AbstractDaterangeObject, AbstractDaterangeObjects
from lib.ConfigLoader import ConfigLoader
from packages import Date
# from lib.data_retention_engine import update_obj_date, get_obj_date_first
from flask import url_for
config_loader = ConfigLoader()
r_object = config_loader.get_db_conn("Kvrocks_Objects")
r_cache = config_loader.get_redis_conn("Redis_Cache")
baseurl = config_loader.get_config_str("Notifications", "ail_domain")
IMAGE_FOLDER = config_loader.get_files_directory('images')
config_loader = None
# SET x1,y1:x2,y2:x3,y3:x4,y4:extracted_text
class Barcode(AbstractDaterangeObject):
"""
AIL Message Object. (strings)
"""
def __init__(self, id):
super(Barcode, self).__init__('barcode', id)
def get_content(self, r_type='str'):
"""
Returns content
"""
global_id = self.get_global_id()
content = r_cache.get(f'content:{global_id}')
if not content:
content = self._get_field('content')
# Set Cache
if content:
global_id = self.get_global_id()
r_cache.set(f'content:{global_id}', content)
r_cache.expire(f'content:{global_id}', 300)
if r_type == 'str':
return content
elif r_type == 'bytes':
if content:
return content.encode()
def get_date(self): # TODO
return Date.get_today_date_str()
def get_nb_seen(self):
return self.get_nb_correlation('image') + self.get_nb_correlation('screenshot')
def get_source(self): # TODO
"""
Returns source/feeder name
"""
return 'barcode'
def get_basename(self): # TODO
return 'barcode'
def get_link(self, flask_context=False):
if flask_context:
url = url_for('correlation.show_correlation', type=self.type, id=self.id)
else:
url = f'{baseurl}/correlation/show?type={self.type}&id={self.id}'
return url
def get_svg_icon(self): # TODO
return {'style': 'fas', 'icon': '\uf02a', 'color': 'yellow', 'radius': 5}
def get_misp_object(self): # TODO
pass
# obj = MISPObject('instant-message', standalone=True)
# obj_date = self.get_date()
# if obj_date:
# obj.first_seen = obj_date
# else:
# self.logger.warning(
# f'Export error, None seen {self.type}:{self.subtype}:{self.id}, first={obj_date}')
#
# # obj_attrs = [obj.add_attribute('first-seen', value=obj_date),
# # obj.add_attribute('raw-data', value=self.id, data=self.get_raw_content()),
# # obj.add_attribute('sensor', value=get_ail_uuid())]
# obj_attrs = []
# for obj_attr in obj_attrs:
# for tag in self.get_tags():
# obj_attr.add_tag(tag)
# return obj
# options: set of optional meta fields
def get_meta(self, options=None):
"""
:type options: set
"""
if options is None:
options = set()
meta = self._get_meta(options=options)
meta['tags'] = self.get_tags()
meta['content'] = self.get_content()
# optional meta fields
if 'investigations' in options:
meta['investigations'] = self.get_investigations()
if 'link' in options:
meta['link'] = self.get_link(flask_context=True)
if 'icon' in options:
meta['svg_icon'] = self.get_svg_icon()
return meta
def create(self, content, im_obj, tags=[]):
self._set_field('content', content)
if im_obj.type == 'screenshot':
for date in im_obj.get_dates():
self._add(date, None)
else:
self._copy_from(im_obj.type, im_obj.get_id())
for tag in tags:
self.add_tag(tag)
return self.id
# # WARNING: UNCLEAN DELETE /!\ TEST ONLY /!\
def delete(self):
r_object.delete(f'barcode:{self.id}')
def create(content, im_obj, tags=[]):
if content:
obj_id = sha256(content.encode()).hexdigest()
obj = Barcode(obj_id)
if not obj.exists():
obj.create(content, im_obj, tags=tags)
return obj
class Barcodes(AbstractDaterangeObjects):
"""
Barcodes Objects
"""
def __init__(self):
super().__init__('barcode', Barcode)
def sanitize_id_to_search(self, name_to_search):
return name_to_search # TODO
#### API ####
def api_get_barcode(obj_id):
obj = Barcode(obj_id)
if not obj.exists():
return {"status": "error", "reason": "Unknown barcode"}, 404
meta = obj.get_meta({'content', 'icon', 'link'})
return meta, 200

View file

@ -151,6 +151,12 @@ class Message(AbstractObject):
images.append({'id': obj_id, 'ocr': self._get_image_ocr(obj_id)})
return images
def get_barcodes(self):
barcodes = []
for c in self.get_correlation('barcode').get('barcode', []):
barcodes.append(c[1:])
return barcodes
def get_qrcodes(self):
qrcodes = []
for c in self.get_correlation('qrcode').get('qrcode', []):
@ -306,6 +312,8 @@ class Message(AbstractObject):
meta['thread'] = thread
if 'images' in options:
meta['images'] = self.get_images()
if 'barcodes' in options:
meta['barcodes'] = self.get_barcodes()
if 'qrcodes' in options:
meta['qrcodes'] = self.get_qrcodes()
if 'files-names' in options:
@ -336,10 +344,14 @@ class Message(AbstractObject):
## Language ##
def get_objs_container(self):
def get_root_obj(self):
return self.get_objs_container(root=True)
def get_objs_container(self, root=False):
objs_containers = set()
# chat
objs_containers.add(self.get_chat())
if not root:
subchannel = self.get_subchannel()
if subchannel:
objs_containers.add(subchannel)

View file

@ -19,6 +19,7 @@ from lib import Tag
from lib import chats_viewer
from lib.objects import BarCodes
from lib.objects import Chats
from lib.objects import ChatSubChannels
from lib.objects import ChatThreads
@ -102,6 +103,8 @@ def get_object(obj_type, subtype, obj_id):
return Messages.Message(obj_id)
elif obj_type == 'ocr':
return Ocrs.Ocr(obj_id)
elif obj_type == 'barcode':
return BarCodes.Barcode(obj_id)
elif obj_type == 'qrcode':
return QrCodes.Qrcode(obj_id)
elif obj_type == 'screenshot':

View file

@ -22,16 +22,17 @@ sys.path.append(os.environ['AIL_BIN'])
##################################
from modules.abstract_module import AbstractModule
from lib.ConfigLoader import ConfigLoader
from lib.objects import BarCodes
from lib.objects import QrCodes
class QrCodeReader(AbstractModule):
class CodeReader(AbstractModule):
"""
QrCodeReader for AIL framework
"""
def __init__(self):
super(QrCodeReader, self).__init__()
super(CodeReader, self).__init__()
# Waiting time in seconds between to message processed
self.pending_seconds = 1
@ -39,6 +40,8 @@ class QrCodeReader(AbstractModule):
config_loader = ConfigLoader()
self.r_cache = config_loader.get_redis_conn("Redis_Cache")
self.barcode_type = {'CODABAR', 'CODE39', 'CODE93', 'CODE128', 'EAN8', 'EAN13', 'I25'} # 2 - 5
# Send module state to logs
self.logger.info(f'Module {self.module_name} initialized')
@ -48,26 +51,35 @@ class QrCodeReader(AbstractModule):
def add_to_cache(self):
self.r_cache.setex(f'qrcode:no:{self.obj.type}:{self.obj.id}', 86400, 0)
def extract_qrcode(self, path):
def extract_codes(self, path):
barcodes = []
qrcodes = []
qr_codes = False
contents = []
try:
image = cv2.cvtColor(cv2.imread(path), cv2.COLOR_BGR2RGB)
except cv2.error:
self.logger.warning(f'Invalid image: {self.obj.get_global_id()}')
return False, []
return [], []
try:
decodeds = decode(image)
for decoded in decodeds:
# print(decoded)
if decoded.type == 'QRCODE':
qr_codes = True
if decoded.data:
contents.append(decoded.data.decode())
qrcodes.append(decoded.data.decode())
elif decoded.type in self.barcode_type:
if decoded.data:
rect = decoded.rect
if rect.width and rect.height and decoded.quality > 1:
barcodes.append(decoded.data.decode())
elif decoded.type:
self.logger.error(f'Unsupported pyzbar code type {decoded.type}: {self.obj.get_global_id()}')
except ValueError as e:
self.logger.error(f'{e}: {self.obj.get_global_id()}')
if not contents:
if not qrcodes:
detector = cv2.QRCodeDetector()
try:
qr, decodeds, qarray, _ = detector.detectAndDecodeMulti(image)
@ -75,28 +87,31 @@ class QrCodeReader(AbstractModule):
qr_codes = True
for d in decodeds:
if d:
contents.append(d)
qrcodes.append(d)
except cv2.error as e:
self.logger.error(f'{e}: {self.obj.get_global_id()}')
try:
data_qr, box, qrcode_image = detector.detectAndDecode(image)
if data_qr:
contents.append(data_qr)
qrcodes.append(data_qr)
qr_codes = True
except cv2.error as e:
self.logger.error(f'{e}: {self.obj.get_global_id()}')
if qr_codes and not contents:
if qr_codes and not qrcodes:
# # # # 0.5s per image
try:
qreader = QReader()
decoded_text = qreader.detect_and_decode(image=image)
for d in decoded_text:
contents.append(d)
qrcodes.append(d)
qr_codes = True
except ValueError as e:
self.logger.error(f'{e}: {self.obj.get_global_id()}')
return qr_codes, contents
if not qr_codes:
self.logger.warning(f'Can notextract qr code: {self.obj.get_global_id()}')
return barcodes, qrcodes
def compute(self, message):
obj = self.get_obj()
@ -111,13 +126,12 @@ class QrCodeReader(AbstractModule):
# image - screenshot
path = self.obj.get_filepath()
is_qrcode, contents = self.extract_qrcode(path)
if not contents:
# print('no qr code detected')
barcodes, qrcodes = self.extract_codes(path)
if not barcodes and not qrcodes:
self.add_to_cache()
return None
for content in contents:
for content in qrcodes:
if not content:
continue
print(content)
@ -134,11 +148,32 @@ class QrCodeReader(AbstractModule):
# TODO only if new ???
self.add_message_to_queue(obj=qr_code, queue='Item')
if is_qrcode or contents:
for content in barcodes:
if not content:
continue
print(content)
barcode = BarCodes.create(content, self.obj) # copy screenshot + image daterange
if not barcode:
print('Error Empty content', self.obj.get_global_id())
barcode.add(barcode.get_date(), self.obj)
for obj_type in ['chat', 'domain', 'message']: # TODO ITEM ???
for c_id in self.obj.get_correlation(obj_type).get(obj_type, []):
o_subtype, o_id = c_id.split(':', 1)
barcode.add_correlation(obj_type, o_subtype, o_id)
self.add_message_to_queue(obj=barcode, queue='Item')
if qrcodes:
tag = 'infoleak:automatic-detection="qrcode"'
self.add_message_to_queue(obj=self.obj, message=tag, queue='Tags')
if barcodes:
tag = 'infoleak:automatic-detection="barcode"'
self.add_message_to_queue(obj=self.obj, message=tag, queue='Tags')
if __name__ == '__main__':
module = QrCodeReader()
module = CodeReader()
module.run()

View file

@ -168,7 +168,7 @@ publish = Item
######## IMAGES ######## images + screenshots
[QrCodeReader]
[CodeReader]
subscribe = Images
publish = Item,Tags

View file

@ -30,16 +30,15 @@ from lib.objects import ail_objects
# from modules.Telegram import Telegram
from modules.CEDetector import CEDetector
from modules.CodeReader import CodeReader
from modules.Languages import Languages
from modules.OcrExtractor import OcrExtractor
from modules.QrCodeReader import QrCodeReader
MODULES = {
'CEDetector': CEDetector,
'CodeReader': CodeReader,
'Languages': Languages,
'OcrExtractor': OcrExtractor,
'QrCodeReader': QrCodeReader
'OcrExtractor': OcrExtractor
}
def reprocess_message_objects(object_type, module_name=None):

View file

@ -58,6 +58,7 @@ from blueprints.objects_dom_hash import objects_dom_hash
from blueprints.chats_explorer import chats_explorer
from blueprints.objects_image import objects_image
from blueprints.objects_ocr import objects_ocr
from blueprints.objects_barcode import objects_barcode
from blueprints.objects_qrcode import objects_qrcode
from blueprints.objects_favicon import objects_favicon
from blueprints.api_rest import api_rest
@ -143,6 +144,7 @@ app.register_blueprint(objects_dom_hash, url_prefix=baseUrl)
app.register_blueprint(chats_explorer, url_prefix=baseUrl)
app.register_blueprint(objects_image, url_prefix=baseUrl)
app.register_blueprint(objects_ocr, url_prefix=baseUrl)
app.register_blueprint(objects_barcode, url_prefix=baseUrl)
app.register_blueprint(objects_qrcode, url_prefix=baseUrl)
app.register_blueprint(objects_favicon, url_prefix=baseUrl)
app.register_blueprint(api_rest, url_prefix=baseUrl)

View file

@ -0,0 +1,99 @@
#!/usr/bin/env python3
# -*-coding:UTF-8 -*
'''
Blueprint Flask: crawler splash endpoints: dashboard, onion crawler ...
'''
import json
import os
import sys
from io import BytesIO
from flask import Flask, render_template, jsonify, request, Blueprint, redirect, url_for, Response, abort, send_file, send_from_directory
from flask_login import login_required
# Import Role_Manager
from Role_Manager import login_admin, login_read_only, no_cache
sys.path.append(os.environ['AIL_BIN'])
##################################
# Import Project packages
##################################
from lib import Language
from lib import Tag
from lib.objects import BarCodes
from packages import Date
# ============ BLUEPRINT ============
objects_barcode = Blueprint('objects_barcode', __name__, template_folder=os.path.join(os.environ['AIL_FLASK'], 'templates/objects/barcode'))
# ============ VARIABLES ============
bootstrap_label = ['primary', 'success', 'danger', 'warning', 'info']
def create_json_response(data, status_code):
return Response(json.dumps(data, indent=2, sort_keys=True), mimetype='application/json'), status_code
# ============ FUNCTIONS ============
@objects_barcode.route("/objects/barcodes", methods=['GET'])
@login_required
@login_read_only
def objects_barcodes():
date_from = request.args.get('date_from')
date_to = request.args.get('date_to')
show_objects = request.args.get('show_objects')
date = Date.sanitise_date_range(date_from, date_to)
date_from = date['date_from']
date_to = date['date_to']
if show_objects:
dict_objects = BarCodes.Barcodes().api_get_meta_by_daterange(date_from, date_to)
else:
dict_objects = {}
return render_template("BarcodeDaterange.html", date_from=date_from, date_to=date_to,
dict_objects=dict_objects, show_objects=show_objects)
@objects_barcode.route("/objects/barcodes/post", methods=['POST'])
@login_required
@login_read_only
def objects_barcodes_post():
date_from = request.form.get('date_from')
date_to = request.form.get('date_to')
show_objects = request.form.get('show_objects')
return redirect(url_for('objects_barcode.objects_barcodes', date_from=date_from, date_to=date_to, show_objects=show_objects))
@objects_barcode.route("/objects/barcodes/range/json", methods=['GET'])
@login_required
@login_read_only
def objects_barcodes_range_json():
date_from = request.args.get('date_from')
date_to = request.args.get('date_to')
date = Date.sanitise_date_range(date_from, date_to)
date_from = date['date_from']
date_to = date['date_to']
return jsonify(BarCodes.Barcodes().api_get_chart_nb_by_daterange(date_from, date_to))
@objects_barcode.route("/objects/barcode", methods=['GET'])
@login_required
@login_read_only
def object_barcode():
obj_id = request.args.get('id')
meta = BarCodes.api_get_barcode(obj_id)
if meta[1] != 200:
return create_json_response(meta[0], meta[1])
else:
meta = meta[0]
return render_template("ShowBarcode.html", meta=meta,
bootstrap_label=bootstrap_label,
ail_tags=Tag.get_modal_add_tags(meta['id'], meta['type'], meta['subtype']))
# ============= ROUTES ==============

View file

@ -97,6 +97,21 @@
<a class="btn btn-info" target="_blank" href="{{ url_for('objects_ocr.object_ocr', id=message_image['id'])}}"><i class="fas fa-expand"></i> OCR</a>
</span>
{% endif %}
{% if message['barcodes'] %}
{% for barcode in message['barcodes'] %}
<span>
<a class="btn btn-info" target="_blank" href="{{ url_for('correlation.show_correlation', type='barcode', id=barcode['id'])}}" style="max-width: 49%">
<i class="fas fa-barcode fa-lg"></i> {{ barcode['content'] }}
<div>
{% for tag in barcode['tags'] %}
<span class="badge badge-{{ bootstrap_label[loop.index0 % 5] }}">{{ tag }}</span>
{% endfor %}
</div>
</a>
</span>
{% endfor %}
{% endif %}
{% if message['qrcodes'] %}
{% for qrcode in message['qrcodes'] %}
<span>

View file

@ -134,6 +134,8 @@
{% include 'chats_explorer/card_image.html' %}
{% elif dict_object["object_type"] == "ocr" %}
{% include 'objects/ocr/card_ocr.html' %}
{% elif dict_object["object_type"] == "barcode" %}
{% include 'objects/barcode/card_barcode.html' %}
{% elif dict_object["object_type"] == "qrcode" %}
{% include 'objects/qrcode/card_qrcode.html' %}
{% elif dict_object["object_type"] == "item" %}

View file

@ -0,0 +1,604 @@
<!DOCTYPE html>
<html>
<head>
<title>Barcodes - AIL</title>
<link rel="icon" href="{{ url_for('static', filename='image/ail-icon.png') }}">
<!-- Core CSS -->
<link href="{{ url_for('static', filename='css/bootstrap4.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/font-awesome.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/dataTables.bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/daterangepicker.min.css') }}" rel="stylesheet">
<!-- JS -->
<script src="{{ url_for('static', filename='js/jquery.js')}}"></script>
<script src="{{ url_for('static', filename='js/popper.min.js')}}"></script>
<script src="{{ url_for('static', filename='js/bootstrap4.min.js')}}"></script>
<script src="{{ url_for('static', filename='js/jquery.dataTables.min.js')}}"></script>
<script src="{{ url_for('static', filename='js/dataTables.bootstrap.min.js')}}"></script>
<script src="{{ url_for('static', filename='js/moment.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/jquery.daterangepicker.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/d3.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/d3/sparklines.js')}}"></script>
<style>
.input-group .form-control {
position: unset;
}
.line {
fill: none;
stroke: #000;
stroke-width: 2.0px;
}
.bar {
fill: steelblue;
}
.bar:hover{
fill: brown;
cursor: pointer;
}
.bar_stack:hover{
cursor: pointer;
}
.pie_path:hover{
cursor: pointer;
}
.svgText {
pointer-events: none;
}
div.tooltip {
position: absolute;
text-align: center;
padding: 2px;
font: 12px sans-serif;
background: #ebf4fb;
border: 2px solid #b7ddf2;
border-radius: 8px;
pointer-events: none;
color: #000000;
}
</style>
</head>
<body>
{% include 'nav_bar.html' %}
<div class="container-fluid">
<div class="row">
{% include 'sidebars/sidebar_objects.html' %}
<div class="col-12 col-lg-10" id="core_content">
<div class="row">
<div class="col-xl-10">
<div class="mt-1" id="barchart_type"></div>
{# {% include 'image/block_images_search.html' %}#}
</div>
<div class="col-xl-2">
<div class="card mb-3 mt-2" style="background-color:#d9edf7;">
<div class="card-body text-center py-2">
<h6 class="card-title" style="color:#286090;">Select a date range :</h6>
<form action="{{ url_for('objects_barcode.objects_barcodes_post') }}" id="hash_selector_form" method='post'>
<div class="input-group" id="date-range-from">
<div class="input-group-prepend"><span class="input-group-text"><i class="far fa-calendar-alt" aria-hidden="true"></i></span></div>
<input class="form-control" id="date-range-from-input" placeholder="yyyy-mm-dd" value="{{ date_from }}" name="date_from" autocomplete="off">
</div>
<div class="input-group" id="date-range-to">
<div class="input-group-prepend"><span class="input-group-text"><i class="far fa-calendar-alt" aria-hidden="true"></i></span></div>
<input class="form-control" id="date-range-to-input" placeholder="yyyy-mm-dd" value="{{ date_to }}" name="date_to" autocomplete="off">
</div>
<div class="form-check my-1">
<input class="form-check-input" type="checkbox" id="checkbox-input-show" name="show_objects" value="True" {% if show_objects %}checked{% endif %}>
<label class="form-check-label" for="checkbox-input-show">
<span style="color:#286090; font-size: 14px;">
Show Barcodes <i class="fas fa-key"></i>
</span>
</label>
</div>
<button class="btn btn-primary" style="text-align:center;">
<i class="fas fa-copy"></i> Search
</button>
</form>
</div>
</div>
<div id="pie_chart_encoded">
</div>
<div id="pie_chart_top5_types">
</div>
</div>
</div>
{% if dict_objects %}
{% if date_from|string == date_to|string %}
<h3> {{ date_from }} Barcodes Name: </h3>
{% else %}
<h3> {{ date_from }} to {{ date_to }} Barcodes Name: </h3>
{% endif %}
<table id="tableb64" class="table table-striped table-bordered">
<thead class="bg-dark text-white">
<tr>
<th></th>
<th>First Seen</th>
<th>Last Seen</th>
<th>Total</th>
<th>Last days</th>
</tr>
</thead>
<tbody style="font-size: 15px;">
{% for obj_id in dict_objects %}
<tr>
<td style="word-break: break-all;">
<a target="_blank" href="{{ url_for('correlation.show_correlation') }}?type=barcode&id={{ obj_id }}">{{ dict_objects[obj_id]['content'] }}</a>
</td>
<td>{{ dict_objects[obj_id]['first_seen'] }}</td>
<td>{{ dict_objects[obj_id]['last_seen'] }}</td>
<td>{{ dict_objects[obj_id]['nb_seen'] }}</td>
<td id="sparklines_{{ obj_id }}" style="text-align:center;"></td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
{% if show_objects %}
{% if date_from|string == date_to|string %}
<h3> {{ date_from }}, No Barcode</h3>
{% else %}
<h3> {{ date_from }} to {{ date_to }}, No Barcode</h3>
{% endif %}
{% endif %}
{% endif %}
</div>
</div>
</div>
<script>
var chart = {};
$(document).ready(function(){
$("#page-Decoded").addClass("active");
$("#nav_barcode").addClass("active");
$('#date-range-from').dateRangePicker({
separator : ' to ',
getValue: function()
{
if ($('#date-range-from-input').val() && $('#date-range-to').val() )
return $('#date-range-from-input').val() + ' to ' + $('#date-range-to').val();
else
return '';
},
setValue: function(s,s1,s2)
{
$('#date-range-from-input').val(s1);
$('#date-range-to-input').val(s2);
},
});
$('#date-range-to').dateRangePicker({
separator : ' to ',
getValue: function()
{
if ($('#date-range-from-input').val() && $('#date-range-to').val() )
return $('#date-range-from-input').val() + ' to ' + $('#date-range-to').val();
else
return '';
},
setValue: function(s,s1,s2)
{
$('#date-range-from-input').val(s1);
$('#date-range-to-input').val(s2);
},
});
$('#date-range-from').data('dateRangePicker').setDateRange('{{date_from}}','{{date_to}}');
$('#date-range-to').data('dateRangePicker').setDateRange('{{date_from}}','{{date_to}}');
$('#tableb64').DataTable({
"aLengthMenu": [[5, 10, 15, -1], [5, 10, 15, "All"]],
"iDisplayLength": 10,
"order": [[ 3, "desc" ]]
});
chart.stackBarChart = barchart_type_stack("{{ url_for('objects_barcode.objects_barcodes_range_json') }}?date_from={{date_from}}&date_to={{date_to}}", 'id');
chart.onResize();
$(window).on("resize", function() {
chart.onResize();
});
});
function toggle_sidebar(){
if($('#nav_menu').is(':visible')){
$('#nav_menu').hide();
$('#side_menu').removeClass('border-right')
$('#side_menu').removeClass('col-lg-2')
$('#core_content').removeClass('col-lg-10')
}else{
$('#nav_menu').show();
$('#side_menu').addClass('border-right')
$('#side_menu').addClass('col-lg-2')
$('#core_content').addClass('col-lg-10')
}
}
</script>
<script>
{% for obj_id in dict_objects %}
sparkline("sparklines_{{ obj_id }}", {{ dict_objects[obj_id]['sparkline'] }}, {});
{% endfor %}
</script>
<script>
var margin = {top: 20, right: 100, bottom: 55, left: 45},
width = 1000 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1);
var y = d3.scaleLinear().rangeRound([height, 0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var color = d3.scaleOrdinal(d3.schemeSet3);
var svg = d3.select("#barchart_type").append("svg")
.attr("id", "thesvg")
.attr("viewBox", "0 0 1000 500")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
function barchart_type_stack(url, id) {
d3.json(url)
.then(function(data){
var labelVar = 'date'; //A
var varNames = d3.keys(data[0])
.filter(function (key) { return key !== labelVar;}); //B
data.forEach(function (d) { //D
var y0 = 0;
d.mapping = varNames.map(function (name) {
return {
name: name,
label: d[labelVar],
y0: y0,
y1: y0 += +d[name]
};
});
d.total = d.mapping[d.mapping.length - 1].y1;
});
x.domain(data.map(function (d) { return (d.date); })); //E
y.domain([0, d3.max(data, function (d) { return d.total; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("class", "bar")
{% if date_from|string == date_to|string and type is none %}
.on("click", function (d) { window.location.href = "{{ url_for('objects_barcode.objects_barcodes') }}?date_from={{date_from}}&date_to={{date_to}}&type_id="+d })
.attr("transform", "rotate(-18)" )
{% elif date_from|string == date_to|string and type is not none %}
.on("click", function (d) { window.location.href = "{{ url_for('objects_barcode.objects_barcodes') }}?date_from="+d+'&date_to='+d })
.attr("transform", "rotate(-18)" )
{% else %}
.on("click", function (d) { window.location.href = "{{ url_for('objects_barcode.objects_barcodes') }}?date_from="+d+'&date_to='+d })
.attr("transform", "rotate(-40)" )
{% endif %}
.style("text-anchor", "end");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end");
var selection = svg.selectAll(".series")
.data(data)
.enter().append("g")
.attr("class", "series")
.attr("transform", function (d) { return "translate(" + x((d.date)) + ",0)"; });
selection.selectAll("rect")
.data(function (d) { return d.mapping; })
.enter().append("rect")
.attr("class", "bar_stack")
.attr("width", x.bandwidth())
.attr("y", function (d) { return y(d.y1); })
.attr("height", function (d) { return y(d.y0) - y(d.y1); })
.style("fill", function (d) { return color(d.name); })
.style("stroke", "grey")
.on("mouseover", function (d) { showPopover.call(this, d); })
.on("mouseout", function (d) { removePopovers(); })
{% if date_from|string == date_to|string and type is none %}
.on("click", function(d){ window.location.href = "{{ url_for('objects_barcode.objects_barcodes') }}" +'?date_from={{date_from}}&date_to={{date_to}}&type_id='+d.label+'&encoding='+d.name; });
{% elif date_from|string == date_to|string and type is not none %}
.on("click", function(d){ window.location.href = "{{ url_for('objects_barcode.objects_barcodes') }}" +'?type_id={{type_id}}&date_from='+d.label+'&date_to='+d.label+'&encoding='+d.name; });
{% else %}
.on("click", function(d){ window.location.href = "{{ url_for('objects_barcode.objects_barcodes') }}" +'?type_id='+ d.name +'&date_from='+d.label+'&date_to='+d.label; });
{% endif %}
data.forEach(function(d) {
if(d.total !== 0){
svg.append("text")
.attr("class", "bar")
.attr("dy", "-.35em")
.attr('x', x(d.date) + x.bandwidth()/2)
.attr('y', y(d.total))
{% if date_from|string == date_to|string and type is none %}
.on("click", function () {window.location.href = "{{ url_for('objects_barcode.objects_barcodes') }}"+'?date_from={{date_from}}&date_to={{date_to}}&type_id='+d.date })
{% elif date_from|string == date_to|string and type is not none %}
.on("click", function () {window.location.href = "{{ url_for('objects_barcode.objects_barcodes') }}?type_id={{type_id}}&date_from="+d.date+'&date_to='+d.date })
{% else %}
.on("click", function () {window.location.href = "{{ url_for('objects_barcode.objects_barcodes') }}"+'?date_from='+d.date+'&date_to='+d.date })
{% endif %}
.style("text-anchor", "middle")
.text(d.total);
}
});
drawLegend(varNames);
});
}
function drawLegend (varNames) {
var legend = svg.selectAll(".legend")
.data(varNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function (d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", 943)
.attr("width", 10)
.attr("height", 10)
.style("fill", color)
.style("stroke", "grey");
legend.append("text")
.attr("class", "svgText")
.attr("x", 941)
.attr("y", 6)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function (d) { return d; });
}
function removePopovers () {
$('.popover').each(function() {
$(this).remove();
});
}
function showPopover (d) {
$(this).popover({
title: "<b><span id='tooltip-id-name-bar'></span></b>",
placement: 'top',
container: 'body',
trigger: 'manual',
html : true,
content: function() {
return "<span id='tooltip-id-label'></span>" +
"<br/>num: <span id='tooltip-id-value-bar'></span>"; }
});
$(this).popover('show');
$("#tooltip-id-name-bar").text(d.name);
$("#tooltip-id-label").text(d.label);
$("#tooltip-id-value-bar").text(d3.format(",")(d.value ? d.value: d.y1 - d.y0));
}
chart.onResize = function () {
var aspect = 1000 / 500, chart = $("#thesvg");
var targetWidth = chart.parent().width();
chart.attr("width", targetWidth);
chart.attr("height", targetWidth / aspect);
}
window.chart = chart;
</script>
<script>
function draw_pie_chart(id, url_json, pie_on_click_url) {
var width_pie = 200;
var height_pie = 200;
var padding_pie = 10;
var opacity_pie = .8;
var radius_pie = Math.min(width_pie - padding_pie, height_pie - padding_pie) / 2;
//var color_pie = d3.scaleOrdinal(d3.schemeCategory10);
var color_pie = d3.scaleOrdinal(d3.schemeSet3);
var div_pie = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var svg_pie = d3.select("#"+id)
.append('svg')
.attr("width", '100%')
.attr("height", '100%')
.attr('viewBox','0 0 '+Math.min(width_pie,height_pie) +' '+Math.min(width_pie,height_pie) )
.attr('preserveAspectRatio','xMinYMin')
var g_pie = svg_pie.append('g')
.attr('transform', 'translate(' + (width_pie/2) + ',' + (height_pie/2) + ')');
var arc_pie = d3.arc()
.innerRadius(0)
.outerRadius(radius_pie);
d3.json(url_json)
.then(function(data){
var pie_pie = d3.pie()
.value(function(d) { return d.value; })
.sort(null);
var path_pie = g_pie.selectAll('path')
.data(pie_pie(data))
.enter()
.append("g")
.append('path')
.attr('d', arc_pie)
.attr('fill', (d,i) => color_pie(i))
.attr('class', 'pie_path')
.on("mouseover", mouseovered_pie)
.on("mouseout", mouseouted_pie)
.on("click", function (d) {window.location.href = pie_on_click_url+d.data.name })
.style('opacity', opacity_pie)
.style('stroke', 'white');
});
function mouseovered_pie(d) {
//remove old content
$("#tooltip-id-name").remove();
$("#tooltip-id-value").remove();
// tooltip
var content;
content = "<b><span id='tooltip-id-name'></span></b><br/>"+
"<br/>"+
"<i>Decoded</i>: <span id='tooltip-id-value'></span><br/>"
div_pie.transition()
.duration(200)
.style("opacity", .9);
div_pie.html(content)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
$("#tooltip-id-name").text(d.data.name);
$("#tooltip-id-value").text(d.data.value);
}
function mouseouted_pie() {
div_pie.transition()
.duration(500)
.style("opacity", 0);
}
}
</script>
<script>
function barchart_type(url, id) {
var margin = {top: 20, right: 20, bottom: 70, left: 40};
var width = 960 - margin.left - margin.right;
var height = 500 - margin.top - margin.bottom;
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1);
var y = d3.scaleLinear().rangeRound([height, 0]);
var xAxis = d3.axisBottom(x)
//.tickFormat(d3.time.format("%Y-%m"));
var yAxis = d3.axisLeft(y)
.ticks(10);
/*var svg = d3.select(id).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("id", "thesvg")
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");*/
d3.json(url)
.then(function(data){
data.forEach(function(d) {
d.value = +d.value;
});
x.domain(data.map(function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
var label = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
{% if daily_type_chart %}
.attr("transform", "rotate(-20)" );
{% else %}
.attr("transform", "rotate(-70)" )
.attr("class", "bar")
{% endif %}
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Value ($)");
var bar = svg.selectAll("bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
//.style("fill", "steelblue")
.attr("x", function(d) { return x(d.date); })
.attr("width", x.bandwidth())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
data.forEach(function(d) {
if(d.value != 0){
svg.append("text")
.attr("class", "bar")
.attr("dy", "-.35em")
//.text(function(d) { return d.value; });
.text(d.value)
.style("text-anchor", "middle")
.attr('x', x(d.date) + x.bandwidth()/2)
.attr('y', y(d.value));
}
});
});
}
</script>
</body>
</html>

View file

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html>
<head>
<title>Barcode - AIL</title>
<link rel="icon" href="{{ url_for('static', filename='image/ail-icon.png') }}">
<!-- Core CSS -->
<link href="{{ url_for('static', filename='css/bootstrap4.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/font-awesome.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/dataTables.bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/tags.css') }}" rel="stylesheet" type="text/css" />
<link href="{{ url_for('static', filename='css/ail-project.css') }}" rel="stylesheet">
<!-- JS -->
<script src="{{ url_for('static', filename='js/jquery.js')}}"></script>
<script src="{{ url_for('static', filename='js/popper.min.js')}}"></script>
<script src="{{ url_for('static', filename='js/bootstrap4.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/jquery.dataTables.min.js')}}"></script>
<script src="{{ url_for('static', filename='js/dataTables.bootstrap.min.js')}}"></script>
<script src="{{ url_for('static', filename='js/tags.js') }}"></script>
</head>
<body>
{% include 'nav_bar.html' %}
<div class="container-fluid">
<div class="row">
{% include 'sidebars/sidebar_objects.html' %}
<div class="col-12 col-lg-10" id="core_content">
{% with meta=meta, is_correlation=False %}
{% include 'objects/barcode/card_barcode.html' %}
{% endwith %}
</div>
</div>
</div>
<script>
var chart = {};
$(document).ready(function(){
$("#page-Decoded").addClass("active");
$("#nav_chat").addClass("active");
});
function toggle_sidebar(){
if($('#nav_menu').is(':visible')){
$('#nav_menu').hide();
$('#side_menu').removeClass('border-right')
$('#side_menu').removeClass('col-lg-2')
$('#core_content').removeClass('col-lg-10')
}else{
$('#nav_menu').show();
$('#side_menu').addClass('border-right')
$('#side_menu').addClass('col-lg-2')
$('#core_content').addClass('col-lg-10')
}
}
</script>
</body>
</html>

View file

@ -0,0 +1,83 @@
<link href="{{ url_for('static', filename='css/tags.css') }}" rel="stylesheet" type="text/css" />
<script src="{{ url_for('static', filename='js/tags.js') }}"></script>
{% with modal_add_tags=ail_tags %}
{% include 'modals/add_tags.html' %}
{% endwith %}
{% include 'modals/edit_tag.html' %}
<style>
.object_image {
filter: blur(5px);
}
</style>
<div class="card my-1">
<div class="card-header">
<small>{{ meta["id"] }} :</small>
<div class="py-1 my-1 bg-white">{{ meta["content"] }}</div>
<ul class="list-group mb-2">
<li class="list-group-item py-0">
<table class="table">
<tbody style="font-size: 15px;">
<tr>
<td>
<svg height="26" width="26">
<g class="nodes">
<circle cx="13" cy="13" r="13" fill="orange"></circle>
<text x="13" y="13" text-anchor="middle" dominant-baseline="central" class="{{ meta["svg_icon"]["style"] }}" font-size="16px">{{ meta["svg_icon"]["icon"] }}</text>
</g>
</svg>
{{ meta['type'] }}
</td>
<td><b>First Seen:</b> {% if meta['first_seen'] %}{{ meta['first_seen'][0:4] }}-{{ meta['first_seen'][4:6] }}-{{ meta['first_seen'][6:8] }}{% endif %}</td>
<td><b>Last Seen:</b> {% if meta['last_seen'] %}{{ meta['last_seen'][0:4] }}-{{ meta['last_seen'][4:6] }}-{{ meta['last_seen'][6:8] }}{% endif %}</td>
</tr>
</tbody>
</table>
</li>
<li class="list-group-item py-0">
</li>
<li class="list-group-item py-0">
<div class="my-2">
Tags:
{% for tag in meta['tags'] %}
<button class="btn btn-{{ bootstrap_label[loop.index0 % 5] }}"
data-toggle="modal" data-target="#edit_tags_modal"
data-tagid="{{ tag }}" data-objtype="{{ meta['type'] }}" data-objsubtype="" data-objid="{{ meta["id"] }}">
{{ tag }}
</button>
{% endfor %}
<button type="button" class="btn btn-light" data-toggle="modal" data-target="#add_tags_modal">
<i class="far fa-plus-square"></i>
</button>
</div>
</li>
</ul>
{% with obj_type='barcode', obj_id=meta['id'], obj_subtype='' %}
{% include 'modals/investigations_register_obj.html' %}
{% endwith %}
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#investigations_register_obj_modal">
<i class="fas fa-microscope"></i> Investigations
</button>
<span class="mb-2 float-right">
{% if is_correlation %}
<a href="{{ url_for('objects_barcode.object_barcode')}}?id={{ meta['id'] }}">
<button class="btn btn-info"><i class="fas fa-expand"></i> Show Object</button>
</a>
{% else %}
<a href="{{ url_for('correlation.show_correlation')}}?type={{ meta['type'] }}&id={{ meta['id'] }}">
<button class="btn btn-info"><i class="far fa-eye"></i> Correlations &nbsp;
</button>
</a>
{% endif %}
</span>
</div>
</div>

View file

@ -94,6 +94,12 @@
<span>OCR</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{url_for('objects_barcode.objects_barcodes')}}" id="nav_barcode">
<i class="fas fa-barcode"></i>
<span>Barcode</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{url_for('objects_qrcode.objects_qrcodes')}}" id="nav_qrcode">
<i class="fas fa-qrcode"></i>