mirror of
https://github.com/ail-project/ail-framework.git
synced 2024-11-22 22:27:17 +00:00
chg: [chats] add chats participants + user-accounts basic template
This commit is contained in:
parent
bef4e69a68
commit
38ce17bc8a
17 changed files with 558 additions and 77 deletions
|
@ -11,7 +11,7 @@ import datetime
|
|||
import os
|
||||
import sys
|
||||
|
||||
from abc import abstractmethod, ABC
|
||||
from abc import ABC
|
||||
|
||||
sys.path.append(os.environ['AIL_BIN'])
|
||||
##################################
|
||||
|
@ -144,6 +144,8 @@ class AbstractChatFeeder(DefaultFeeder, ABC):
|
|||
def process_chat(self, new_objs, obj, date, timestamp, reply_id=None):
|
||||
meta = self.json_data['meta']['chat'] # todo replace me by function
|
||||
chat = Chat(self.get_chat_id(), self.get_chat_instance_uuid())
|
||||
subchannel = None
|
||||
thread = None
|
||||
|
||||
# date stat + correlation
|
||||
chat.add(date, obj)
|
||||
|
@ -168,24 +170,26 @@ class AbstractChatFeeder(DefaultFeeder, ABC):
|
|||
chat.update_username_timeline(username.get_global_id(), timestamp)
|
||||
|
||||
if meta.get('subchannel'):
|
||||
subchannel = self.process_subchannel(obj, date, timestamp, reply_id=reply_id)
|
||||
subchannel, thread = self.process_subchannel(obj, date, timestamp, reply_id=reply_id)
|
||||
chat.add_children(obj_global_id=subchannel.get_global_id())
|
||||
else:
|
||||
if obj.type == 'message':
|
||||
if self.get_thread_id():
|
||||
self.process_thread(obj, chat, date, timestamp, reply_id=reply_id)
|
||||
thread = self.process_thread(obj, chat, date, timestamp, reply_id=reply_id)
|
||||
else:
|
||||
chat.add_message(obj.get_global_id(), self.get_message_id(), timestamp, reply_id=reply_id)
|
||||
|
||||
|
||||
# if meta.get('subchannels'): # TODO Update icon + names
|
||||
|
||||
return chat
|
||||
|
||||
chats_obj = [chat]
|
||||
if subchannel:
|
||||
chats_obj.append(subchannel)
|
||||
if thread:
|
||||
chats_obj.append(thread)
|
||||
return chats_obj
|
||||
|
||||
def process_subchannel(self, obj, date, timestamp, reply_id=None): # TODO CREATE DATE
|
||||
meta = self.json_data['meta']['chat']['subchannel']
|
||||
subchannel = ChatSubChannels.ChatSubChannel(f'{self.get_chat_id()}/{meta["id"]}', self.get_chat_instance_uuid())
|
||||
thread = None
|
||||
|
||||
# TODO correlation with obj = message/image
|
||||
subchannel.add(date)
|
||||
|
@ -202,10 +206,10 @@ class AbstractChatFeeder(DefaultFeeder, ABC):
|
|||
|
||||
if obj.type == 'message':
|
||||
if self.get_thread_id():
|
||||
self.process_thread(obj, subchannel, date, timestamp, reply_id=reply_id)
|
||||
thread = self.process_thread(obj, subchannel, date, timestamp, reply_id=reply_id)
|
||||
else:
|
||||
subchannel.add_message(obj.get_global_id(), self.get_message_id(), timestamp, reply_id=reply_id)
|
||||
return subchannel
|
||||
return subchannel, thread
|
||||
|
||||
def process_thread(self, obj, obj_chat, date, timestamp, reply_id=None):
|
||||
meta = self.json_data['meta']['thread']
|
||||
|
@ -231,7 +235,6 @@ class AbstractChatFeeder(DefaultFeeder, ABC):
|
|||
# else:
|
||||
# # ADD NEW MESSAGE REF (used by discord)
|
||||
|
||||
|
||||
def process_sender(self, new_objs, obj, date, timestamp):
|
||||
meta = self.json_data['meta']['sender']
|
||||
user_account = UsersAccount.UserAccount(meta['id'], self.get_chat_instance_uuid())
|
||||
|
@ -267,12 +270,6 @@ class AbstractChatFeeder(DefaultFeeder, ABC):
|
|||
|
||||
return user_account
|
||||
|
||||
# Create abstract class: -> new API endpoint ??? => force field, check if already imported ?
|
||||
# 1) Create/Get MessageInstance - # TODO uuidv5 + text like discord and telegram for default
|
||||
# 2) Create/Get CHAT ID - Done
|
||||
# 3) Create/Get Channel IF is in channel
|
||||
# 4) Create/Get Thread IF is in thread
|
||||
# 5) Create/Update Username and User-account - Done
|
||||
def process_meta(self): # TODO CHECK MANDATORY FIELDS
|
||||
"""
|
||||
Process JSON meta filed.
|
||||
|
@ -316,7 +313,7 @@ class AbstractChatFeeder(DefaultFeeder, ABC):
|
|||
message_id = self.get_message_id()
|
||||
message_id = Messages.create_obj_id(self.get_chat_instance_uuid(), chat_id, message_id, timestamp, channel_id=channel_id, thread_id=thread_id)
|
||||
message = Messages.Message(message_id)
|
||||
# create empty message if message don't exists
|
||||
# create empty message if message don't exist
|
||||
if not message.exists():
|
||||
message.create('')
|
||||
objs.add(message)
|
||||
|
@ -336,46 +333,26 @@ class AbstractChatFeeder(DefaultFeeder, ABC):
|
|||
|
||||
for obj in objs: # TODO PERF avoid parsing metas multiple times
|
||||
|
||||
# TODO get created subchannel + thread
|
||||
# => create correlation user-account with object
|
||||
|
||||
# CHAT
|
||||
chat = self.process_chat(new_objs, obj, date, timestamp, reply_id=reply_id)
|
||||
chat_objs = self.process_chat(new_objs, obj, date, timestamp, reply_id=reply_id)
|
||||
|
||||
# SENDER # TODO HANDLE NULL SENDER
|
||||
user_account = self.process_sender(new_objs, obj, date, timestamp)
|
||||
|
||||
# UserAccount---Chat
|
||||
user_account.add_correlation(chat.type, chat.get_subtype(r_str=True), chat.id)
|
||||
# UserAccount---ChatObjects
|
||||
for obj_chat in chat_objs:
|
||||
user_account.add_correlation(obj_chat.type, obj_chat.get_subtype(r_str=True), obj_chat.id)
|
||||
|
||||
# if chat: # TODO Chat---Username correlation ???
|
||||
# # Chat---Username => need to handle members and participants
|
||||
# chat.add_correlation(username.type, username.get_subtype(r_str=True), username.id)
|
||||
|
||||
|
||||
# TODO Sender image -> correlation
|
||||
# image
|
||||
# -> subchannel ?
|
||||
# -> thread id ?
|
||||
|
||||
return new_objs | objs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ def get_all_objects():
|
|||
return AIL_OBJECTS
|
||||
|
||||
def get_objects_with_subtypes():
|
||||
return ['chat', 'cryptocurrency', 'pgp', 'username']
|
||||
return ['chat', 'cryptocurrency', 'pgp', 'username', 'user-account']
|
||||
|
||||
def get_object_all_subtypes(obj_type): # TODO Dynamic subtype
|
||||
if obj_type == 'chat':
|
||||
|
@ -53,6 +53,8 @@ def get_object_all_subtypes(obj_type): # TODO Dynamic subtype
|
|||
return ['key', 'mail', 'name']
|
||||
if obj_type == 'username':
|
||||
return ['telegram', 'twitter', 'jabber']
|
||||
if obj_type == 'user-account':
|
||||
return r_object.smembers(f'all_chat:subtypes')
|
||||
return []
|
||||
|
||||
def get_objects_tracked():
|
||||
|
@ -75,10 +77,28 @@ def get_all_objects_with_subtypes_tuple():
|
|||
def unpack_obj_global_id(global_id, r_type='tuple'):
|
||||
if r_type == 'dict':
|
||||
obj = global_id.split(':', 2)
|
||||
return {'type': obj[0], 'subtype': obj[1], 'id': obj['2']}
|
||||
return {'type': obj[0], 'subtype': obj[1], 'id': obj[2]}
|
||||
else: # tuple(type, subtype, id)
|
||||
return global_id.split(':', 2)
|
||||
|
||||
def unpack_objs_global_id(objs_global_id, r_type='tuple'):
|
||||
objs = []
|
||||
for global_id in objs_global_id:
|
||||
objs.append(unpack_obj_global_id(global_id, r_type=r_type))
|
||||
return objs
|
||||
|
||||
def unpack_correl_obj__id(obj_type, global_id, r_type='tuple'):
|
||||
obj = global_id.split(':', 1)
|
||||
if r_type == 'dict':
|
||||
return {'type': obj_type, 'subtype': obj[0], 'id': obj[1]}
|
||||
else: # tuple(type, subtype, id)
|
||||
return obj_type, obj[0], obj[1]
|
||||
|
||||
def unpack_correl_objs_id(obj_type, correl_objs_id, r_type='tuple'):
|
||||
objs = []
|
||||
for correl_obj_id in correl_objs_id:
|
||||
objs.append(unpack_correl_obj__id(obj_type, correl_obj_id, r_type=r_type))
|
||||
return objs
|
||||
|
||||
##-- AIL OBJECTS --##
|
||||
|
||||
|
|
|
@ -278,6 +278,27 @@ def create_chat_service_instance(protocol, network=None, address=None):
|
|||
|
||||
#######################################################################################
|
||||
|
||||
def get_obj_chat(chat_type, chat_subtype, chat_id):
|
||||
print(chat_type, chat_subtype, chat_id)
|
||||
if chat_type == 'chat':
|
||||
return Chats.Chat(chat_id, chat_subtype)
|
||||
elif chat_type == 'chat-subchannel':
|
||||
return ChatSubChannels.ChatSubChannel(chat_id, chat_subtype)
|
||||
elif chat_type == 'chat-thread':
|
||||
return ChatThreads.ChatThread(chat_id, chat_subtype)
|
||||
|
||||
def get_obj_chat_meta(obj_chat, new_options=set()):
|
||||
options = {}
|
||||
if obj_chat.type == 'chat':
|
||||
options = {'created_at', 'icon', 'info', 'subchannels', 'threads', 'username'}
|
||||
elif obj_chat.type == 'chat-subchannel':
|
||||
options = {'chat', 'created_at', 'icon', 'nb_messages', 'threads'}
|
||||
elif obj_chat.type == 'chat-thread':
|
||||
options = {'chat', 'nb_messages'}
|
||||
for option in new_options:
|
||||
options.add(option)
|
||||
return obj_chat.get_meta(options=options)
|
||||
|
||||
def get_subchannels_meta_from_global_id(subchannels):
|
||||
meta = []
|
||||
for sub in subchannels:
|
||||
|
@ -302,6 +323,8 @@ def get_username_meta_from_global_id(username_global_id):
|
|||
username = Usernames.Username(username_id, instance_uuid)
|
||||
return username.get_meta()
|
||||
|
||||
#### API ####
|
||||
|
||||
def api_get_chat_service_instance(chat_instance_uuid):
|
||||
chat_instance = ChatServiceInstance(chat_instance_uuid)
|
||||
if not chat_instance.exists():
|
||||
|
@ -312,7 +335,7 @@ def api_get_chat(chat_id, chat_instance_uuid, translation_target=None):
|
|||
chat = Chats.Chat(chat_id, chat_instance_uuid)
|
||||
if not chat.exists():
|
||||
return {"status": "error", "reason": "Unknown chat"}, 404
|
||||
meta = chat.get_meta({'created_at', 'icon', 'info', 'subchannels', 'threads', 'username'})
|
||||
meta = chat.get_meta({'created_at', 'icon', 'info', 'nb_participants', 'subchannels', 'threads', 'username'})
|
||||
if meta['username']:
|
||||
meta['username'] = get_username_meta_from_global_id(meta['username'])
|
||||
if meta['subchannels']:
|
||||
|
@ -329,11 +352,26 @@ def api_get_nb_message_by_week(chat_id, chat_instance_uuid):
|
|||
# week = chat.get_nb_message_by_week('20231109')
|
||||
return week, 200
|
||||
|
||||
def api_get_chat_participants(chat_type, chat_subtype, chat_id):
|
||||
if chat_type not in ['chat', 'chat-subchannel', 'chat-thread']:
|
||||
return {"status": "error", "reason": "Unknown chat type"}, 400
|
||||
chat_obj = get_obj_chat(chat_type, chat_subtype, chat_id)
|
||||
if not chat_obj.exists():
|
||||
return {"status": "error", "reason": "Unknown chat"}, 404
|
||||
else:
|
||||
meta = get_obj_chat_meta(chat_obj, new_options={'participants'})
|
||||
chat_participants = []
|
||||
for participant in meta['participants']:
|
||||
user_account = UsersAccount.UserAccount(participant['id'], participant['subtype'])
|
||||
chat_participants.append(user_account.get_meta({'icon', 'info', 'username'}))
|
||||
meta['participants'] = chat_participants
|
||||
return meta, 200
|
||||
|
||||
def api_get_subchannel(chat_id, chat_instance_uuid, translation_target=None):
|
||||
subchannel = ChatSubChannels.ChatSubChannel(chat_id, chat_instance_uuid)
|
||||
if not subchannel.exists():
|
||||
return {"status": "error", "reason": "Unknown subchannel"}, 404
|
||||
meta = subchannel.get_meta({'chat', 'created_at', 'icon', 'nb_messages', 'threads'})
|
||||
meta = subchannel.get_meta({'chat', 'created_at', 'icon', 'nb_messages', 'nb_participants', 'threads'})
|
||||
if meta['chat']:
|
||||
meta['chat'] = get_chat_meta_from_global_id(meta['chat'])
|
||||
if meta.get('threads'):
|
||||
|
@ -347,7 +385,7 @@ def api_get_thread(thread_id, thread_instance_uuid, translation_target=None):
|
|||
thread = ChatThreads.ChatThread(thread_id, thread_instance_uuid)
|
||||
if not thread.exists():
|
||||
return {"status": "error", "reason": "Unknown thread"}, 404
|
||||
meta = thread.get_meta({'chat', 'nb_messages'})
|
||||
meta = thread.get_meta({'chat', 'nb_messages', 'nb_participants'})
|
||||
# if meta['chat']:
|
||||
# meta['chat'] = get_chat_meta_from_global_id(meta['chat'])
|
||||
meta['messages'], meta['tags_messages'] = thread.get_messages(translation_target=translation_target)
|
||||
|
@ -367,8 +405,7 @@ def api_get_user_account(user_id, instance_uuid):
|
|||
user_account = UsersAccount.UserAccount(user_id, instance_uuid)
|
||||
if not user_account.exists():
|
||||
return {"status": "error", "reason": "Unknown user-account"}, 404
|
||||
meta = user_account.get_meta({'icon', 'username'})
|
||||
print(meta)
|
||||
meta = user_account.get_meta({'chats', 'icon', 'info', 'subchannels', 'threads', 'username', 'username_meta'})
|
||||
return meta, 200
|
||||
|
||||
# # # # # # # # # # LATER
|
||||
|
|
|
@ -70,7 +70,7 @@ class ChatSubChannel(AbstractChatObject):
|
|||
# else:
|
||||
# style = 'fas'
|
||||
# icon = '\uf007'
|
||||
style = 'fas'
|
||||
style = 'far'
|
||||
icon = '\uf086'
|
||||
return {'style': style, 'icon': icon, 'color': '#4dffff', 'radius': 5}
|
||||
|
||||
|
@ -90,7 +90,10 @@ class ChatSubChannel(AbstractChatObject):
|
|||
meta['created_at'] = self.get_created_at(date=True)
|
||||
if 'threads' in options:
|
||||
meta['threads'] = self.get_threads()
|
||||
print(meta['threads'])
|
||||
if 'participants' in options:
|
||||
meta['participants'] = self.get_participants()
|
||||
if 'nb_participants' in options:
|
||||
meta['nb_participants'] = self.get_nb_participants()
|
||||
return meta
|
||||
|
||||
def get_misp_object(self):
|
||||
|
|
|
@ -77,6 +77,10 @@ class ChatThread(AbstractChatObject):
|
|||
meta['name'] = self.get_name()
|
||||
if 'nb_messages':
|
||||
meta['nb_messages'] = self.get_nb_messages()
|
||||
if 'participants':
|
||||
meta['participants'] = self.get_participants()
|
||||
if 'nb_participants':
|
||||
meta['nb_participants'] = self.get_nb_participants()
|
||||
# created_at ???
|
||||
return meta
|
||||
|
||||
|
|
|
@ -74,10 +74,14 @@ class Chat(AbstractChatObject):
|
|||
meta = self._get_meta(options=options)
|
||||
meta['name'] = self.get_name()
|
||||
meta['tags'] = self.get_tags(r_list=True)
|
||||
if 'icon':
|
||||
if 'icon' in options:
|
||||
meta['icon'] = self.get_icon()
|
||||
if 'info':
|
||||
if 'info' in options:
|
||||
meta['info'] = self.get_info()
|
||||
if 'participants' in options:
|
||||
meta['participants'] = self.get_participants()
|
||||
if 'nb_participants' in options:
|
||||
meta['nb_participants'] = self.get_nb_participants()
|
||||
if 'username' in options:
|
||||
meta['username'] = self.get_username()
|
||||
if 'subchannels' in options:
|
||||
|
|
|
@ -54,16 +54,7 @@ class UserAccount(AbstractSubtypeObject):
|
|||
return url
|
||||
|
||||
def get_svg_icon(self): # TODO change icon/color
|
||||
if self.subtype == 'telegram':
|
||||
style = 'fab'
|
||||
icon = '\uf2c6'
|
||||
elif self.subtype == 'twitter':
|
||||
style = 'fab'
|
||||
icon = '\uf099'
|
||||
else:
|
||||
style = 'fas'
|
||||
icon = '\uf007'
|
||||
return {'style': style, 'icon': icon, 'color': '#4dffff', 'radius': 5}
|
||||
return {'style': 'fas', 'icon': '\uf2bd', 'color': '#4dffff', 'radius': 5}
|
||||
|
||||
def get_first_name(self):
|
||||
return self._get_field('firstname')
|
||||
|
@ -97,6 +88,25 @@ class UserAccount(AbstractSubtypeObject):
|
|||
def set_info(self, info):
|
||||
return self._set_field('info', info)
|
||||
|
||||
# TODO MESSAGES:
|
||||
# 1) ALL MESSAGES + NB
|
||||
# 2) ALL MESSAGES TIMESTAMP
|
||||
# 3) ALL MESSAGES TIMESTAMP By: - chats
|
||||
# - subchannel
|
||||
# - thread
|
||||
|
||||
def get_chats(self):
|
||||
chats = self.get_correlation('chat')['chat']
|
||||
return chats
|
||||
|
||||
def get_chat_subchannels(self):
|
||||
chats = self.get_correlation('chat-subchannel')['chat-subchannel']
|
||||
return chats
|
||||
|
||||
def get_chat_threads(self):
|
||||
chats = self.get_correlation('chat-thread')['chat-thread']
|
||||
return chats
|
||||
|
||||
def _get_timeline_username(self):
|
||||
return Timeline(self.get_global_id(), 'username')
|
||||
|
||||
|
@ -109,20 +119,31 @@ class UserAccount(AbstractSubtypeObject):
|
|||
def update_username_timeline(self, username_global_id, timestamp):
|
||||
self._get_timeline_username().add_timestamp(timestamp, username_global_id)
|
||||
|
||||
def get_meta(self, options=set()):
|
||||
def get_meta(self, options=set()): # TODO Username timeline
|
||||
meta = self._get_meta(options=options)
|
||||
meta['id'] = self.id
|
||||
meta['subtype'] = self.subtype
|
||||
meta['tags'] = self.get_tags(r_list=True) # TODO add in options ????
|
||||
if 'username' in options:
|
||||
meta['username'] = self.get_username()
|
||||
if meta['username'] and 'username_meta' in options:
|
||||
if meta['username']:
|
||||
_, username_account_subtype, username_account_id = meta['username'].split(':', 3)
|
||||
meta['username'] = Usernames.Username(username_account_id, username_account_subtype).get_meta()
|
||||
if 'username_meta' in options:
|
||||
meta['username'] = Usernames.Username(username_account_id, username_account_subtype).get_meta()
|
||||
else:
|
||||
meta['username'] = {'type': 'username', 'subtype': username_account_subtype, 'id': username_account_id}
|
||||
if 'usernames' in options:
|
||||
meta['usernames'] = self.get_usernames()
|
||||
if 'icon' in options:
|
||||
meta['icon'] = self.get_icon()
|
||||
if 'info' in options:
|
||||
meta['info'] = self.get_info()
|
||||
if 'chats' in options:
|
||||
meta['chats'] = self.get_chats()
|
||||
if 'subchannels' in options:
|
||||
meta['subchannels'] = self.get_chat_subchannels()
|
||||
if 'threads' in options:
|
||||
meta['threads'] = self.get_chat_threads()
|
||||
return meta
|
||||
|
||||
def get_misp_object(self):
|
||||
|
|
|
@ -19,7 +19,7 @@ sys.path.append(os.environ['AIL_BIN'])
|
|||
# Import Project packages
|
||||
##################################
|
||||
from lib.objects.abstract_subtype_object import AbstractSubtypeObject
|
||||
from lib.ail_core import get_object_all_subtypes, zscan_iter ################
|
||||
from lib.ail_core import unpack_correl_objs_id, zscan_iter ################
|
||||
from lib.ConfigLoader import ConfigLoader
|
||||
from lib.objects import Messages
|
||||
from packages import Date
|
||||
|
@ -244,8 +244,11 @@ class AbstractChatObject(AbstractSubtypeObject, ABC):
|
|||
|
||||
# def get_deleted_messages(self, message_id):
|
||||
|
||||
def get_participants(self):
|
||||
return unpack_correl_objs_id('user-account', self.get_correlation('user-account')['user-account'], r_type='dict')
|
||||
|
||||
# get_messages_meta ????
|
||||
def get_nb_participants(self):
|
||||
return self.get_nb_correlation('user-account')
|
||||
|
||||
# TODO move me to abstract subtype
|
||||
class AbstractChatObjects(ABC):
|
||||
|
|
|
@ -132,6 +132,20 @@ def objects_thread_messages():
|
|||
languages = Language.get_translation_languages()
|
||||
return render_template('ThreadMessages.html', meta=meta, bootstrap_label=bootstrap_label, translation_languages=languages, translation_target=target)
|
||||
|
||||
@chats_explorer.route("/chats/explorer/participants", methods=['GET'])
|
||||
@login_required
|
||||
@login_read_only
|
||||
def chats_explorer_chat_participants():
|
||||
chat_type = request.args.get('type')
|
||||
chat_id = request.args.get('id')
|
||||
chat_subtype = request.args.get('subtype')
|
||||
meta = chats_viewer.api_get_chat_participants(chat_type, chat_subtype,chat_id)
|
||||
if meta[1] != 200:
|
||||
return create_json_response(meta[0], meta[1])
|
||||
else:
|
||||
meta = meta[0]
|
||||
return render_template('chat_participants.html', meta=meta, bootstrap_label=bootstrap_label)
|
||||
|
||||
@chats_explorer.route("/objects/message", methods=['GET'])
|
||||
@login_required
|
||||
@login_read_only
|
||||
|
@ -145,3 +159,16 @@ def objects_message():
|
|||
languages = Language.get_translation_languages()
|
||||
return render_template('ChatMessage.html', meta=message, bootstrap_label=bootstrap_label,
|
||||
modal_add_tags=Tag.get_modal_add_tags(message['id'], object_type='message'))
|
||||
|
||||
@chats_explorer.route("/objects/user-account", methods=['GET'])
|
||||
@login_required
|
||||
@login_read_only
|
||||
def objects_user_account():
|
||||
instance_uuid = request.args.get('subtype')
|
||||
user_id = request.args.get('id')
|
||||
user_account = chats_viewer.api_get_user_account(user_id, instance_uuid)
|
||||
if user_account[1] != 200:
|
||||
return create_json_response(user_account[0], user_account[1])
|
||||
else:
|
||||
user_account = user_account[0]
|
||||
return render_template('user_account.html', meta=user_account, bootstrap_label=bootstrap_label)
|
|
@ -43,7 +43,8 @@ def subtypes_objects_dashboard(obj_type, f_request):
|
|||
date_to = f_request.form.get('to')
|
||||
subtype = f_request.form.get('subtype')
|
||||
show_objects = bool(f_request.form.get('show_objects'))
|
||||
endpoint_dashboard = url_for(f'objects_subtypes.objects_dashboard_{obj_type}')
|
||||
t_obj_type = obj_type.replace('-', '_')
|
||||
endpoint_dashboard = url_for(f'objects_subtypes.objects_dashboard_{t_obj_type}')
|
||||
endpoint_dashboard = f'{endpoint_dashboard}?from={date_from}&to={date_to}'
|
||||
if subtype:
|
||||
if subtype == 'All types':
|
||||
|
@ -81,7 +82,8 @@ def subtypes_objects_dashboard(obj_type, f_request):
|
|||
for obj_t, obj_subtype, obj_id in subtypes_objs:
|
||||
objs.append(ail_objects.get_object_meta(obj_t, obj_subtype, obj_id, options={'sparkline'}, flask_context=True))
|
||||
|
||||
endpoint_dashboard = f'objects_subtypes.objects_dashboard_{obj_type}'
|
||||
t_obj_type = obj_type.replace('-', '_')
|
||||
endpoint_dashboard = f'objects_subtypes.objects_dashboard_{t_obj_type}'
|
||||
return render_template('subtypes_objs_dashboard.html', date_from=date_from, date_to=date_to,
|
||||
daily_type_chart = daily_type_chart, show_objects=show_objects,
|
||||
obj_type=obj_type, subtype=subtype, objs=objs,
|
||||
|
@ -115,6 +117,12 @@ def objects_dashboard_pgp():
|
|||
def objects_dashboard_username():
|
||||
return subtypes_objects_dashboard('username', request)
|
||||
|
||||
@objects_subtypes.route("/objects/user-accounts", methods=['GET'])
|
||||
@login_required
|
||||
@login_read_only
|
||||
def objects_dashboard_user_account():
|
||||
return subtypes_objects_dashboard('user-account', request)
|
||||
|
||||
# TODO REDIRECT
|
||||
@objects_subtypes.route("/objects/subtypes/post", methods=['POST'])
|
||||
@login_required
|
||||
|
|
|
@ -68,6 +68,7 @@
|
|||
<th>Last seen</th>
|
||||
<th>Username</th>
|
||||
<th>Nb Messages</th>
|
||||
<th>Participants</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -94,6 +95,9 @@
|
|||
{% endif %}
|
||||
</td>
|
||||
<td>{{ subchannel['nb_messages'] }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('chats_explorer.chats_explorer_chat_participants')}}?type=chat-subchannel&subtype={{ subchannel['subtype'] }}&id={{ subchannel['id'] }}"><i class="far fa-user-circle"></i> {{ subchannel['nb_participants']}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
<th>First seen</th>
|
||||
<th>Last seen</th>
|
||||
<th>Nb Messages</th>
|
||||
<th>Participants</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -74,6 +75,7 @@
|
|||
<td>
|
||||
{{ meta['name'] }}
|
||||
</td>
|
||||
<td></td>
|
||||
<td>
|
||||
{% if meta['first_seen'] %}
|
||||
{{ meta['first_seen'][0:4] }}-{{ meta['first_seen'][4:6] }}-{{ meta['first_seen'][6:8] }}
|
||||
|
@ -85,6 +87,9 @@
|
|||
{% endif %}
|
||||
</td>
|
||||
<td>{{ meta['nb_messages'] }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('chats_explorer.chats_explorer_chat_participants')}}?type=chat-thread&subtype={{ meta['subtype'] }}&id={{ meta['id'] }}"><i class="far fa-user-circle"></i> {{ meta['nb_participants']}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -26,8 +26,10 @@
|
|||
|
||||
<div class="chat-message-left pb-1">
|
||||
<div>
|
||||
<img src="{% if message['user-account']['icon'] %}{{ url_for('objects_image.image', filename=message['user-account']['icon'])}}{% else %}{{ url_for('static', filename='image/ail-icon.png') }}{% endif %}"
|
||||
class="rounded-circle mr-1" alt="{{ message['user-account']['id'] }}" width="40" height="40">
|
||||
<a href="{{ url_for('chats_explorer.objects_user_account')}}?subtype={{ message['user-account']['subtype'] }}&id={{ message['user-account']['id'] }}">
|
||||
<img src="{% if message['user-account']['icon'] %}{{ url_for('objects_image.image', filename=message['user-account']['icon'])}}{% else %}{{ url_for('static', filename='image/ail-icon.png') }}{% endif %}"
|
||||
class="rounded-circle mr-1" alt="{{ message['user-account']['id'] }}" width="40" height="40">
|
||||
</a>
|
||||
<div class="text-muted small text-nowrap mt-2">{{ message['hour'] }}</div>
|
||||
</div>
|
||||
<div class="flex-shrink-1 bg-light rounded py-2 px-3 ml-4 pb-4" style="overflow-x: auto">
|
||||
|
|
174
var/www/templates/chats_explorer/chat_participants.html
Normal file
174
var/www/templates/chats_explorer/chat_participants.html
Normal file
|
@ -0,0 +1,174 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Chats Protocols - 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">
|
||||
|
||||
<!-- 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/d3.min.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/d3/heatmap_week_hour.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">
|
||||
|
||||
{# <div class="card my-3">#} TODO CHAT abstract metadata
|
||||
{##}
|
||||
{# <div class="card-header" style="background-color:#d9edf7;font-size: 15px">#}
|
||||
{# <h4 class="text-secondary">{% if chat['username'] %}{{ chat["username"]["id"] }} {% else %} {{ chat['name'] }}{% endif %} :</h4>#}
|
||||
{# {% if chat['icon'] %}#}
|
||||
{# <div><img src="{{ url_for('objects_image.image', filename=chat['icon'])}}" class="mb-2" alt="{{ chat['id'] }}" width="200" height="200"></div>#}
|
||||
{# {% endif %}#}
|
||||
{# <ul class="list-group mb-2">#}
|
||||
{# <li class="list-group-item py-0">#}
|
||||
{# <table class="table">#}
|
||||
{# <thead class="">#}
|
||||
{# <tr>#}
|
||||
{# <th>Name</th>#}
|
||||
{# <th>ID</th>#}
|
||||
{# <th>Created at</th>#}
|
||||
{# <th>First Seen</th>#}
|
||||
{# <th>Last Seen</th>#}
|
||||
{# <th>NB Sub-Channels</th>#}
|
||||
{# <th>Participants</th>#}
|
||||
{# </tr>#}
|
||||
{# </thead>#}
|
||||
{# <tbody style="font-size: 15px;">#}
|
||||
{# <tr>#}
|
||||
{# <td>{{ chat['name'] }}</td>#}
|
||||
{# <td>{{ chat['id'] }}</td>#}
|
||||
{# <td>{{ chat['created_at'] }}</td>#}
|
||||
{# <td>#}
|
||||
{# {% if chat['first_seen'] %}#}
|
||||
{# {{ chat['first_seen'][0:4] }}-{{ chat['first_seen'][4:6] }}-{{ chat['first_seen'][6:8] }}#}
|
||||
{# {% endif %}#}
|
||||
{# </td>#}
|
||||
{# <td>#}
|
||||
{# {% if chat['last_seen'] %}#}
|
||||
{# {{ chat['last_seen'][0:4] }}-{{ chat['last_seen'][4:6] }}-{{ chat['last_seen'][6:8] }}#}
|
||||
{# {% endif %}#}
|
||||
{# </td>#}
|
||||
{# <td>{{ chat['nb_subchannels'] }}</td>#}
|
||||
{# <td>#}
|
||||
{# <a href="{{ url_for('chats_explorer.objects_thread_messages')}}?uuid={{ chat['subtype'] }}&id={{ chat['id'] }}"><i class="far fa-user-circle"></i> {{ chat['participants'] | length}}</a>#}
|
||||
{##}
|
||||
{# </td>#}
|
||||
{# </tr>#}
|
||||
{# </tbody>#}
|
||||
{# </table>#}
|
||||
{# {% if chat['info'] %}#}
|
||||
{# <li class="list-group-item py-0">#}
|
||||
{# <pre class="my-0">{{ chat['info'] }}</pre>#}
|
||||
{# </li>#}
|
||||
{# {% endif %}#}
|
||||
{# </li>#}
|
||||
{# </ul>#}
|
||||
{##}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
|
||||
|
||||
<h4>Participants:</h4>
|
||||
<table id="tableparticipantss" class="table">
|
||||
<thead class="bg-dark text-white">
|
||||
<tr>
|
||||
<th>Icon</th>
|
||||
<th>Username</th>
|
||||
<th>ID</th>
|
||||
<th>info</th>
|
||||
<th>First Seen</th>
|
||||
<th>Last Seen</th>
|
||||
<th>NB Messages</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody style="font-size: 15px;">
|
||||
{% for user_meta in meta["participants"] %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ url_for('chats_explorer.objects_user_account')}}?subtype={{ user_meta['subtype'] }}&id={{ user_meta['id'] }}">
|
||||
<img src="{% if user_meta['icon'] %}{{ url_for('objects_image.image', filename=user_meta['icon'])}}{% else %}{{ url_for('static', filename='image/ail-icon.png') }}{% endif %}"
|
||||
class="rounded-circle mr-1" alt="{{ user_meta['id'] }}" width="40" height="40">
|
||||
<a>
|
||||
</td>
|
||||
<td>
|
||||
{% if user_meta['username'] %}
|
||||
<b>{{ user_meta['username']['id'] }}</b>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><a href="{{ url_for('chats_explorer.objects_user_account') }}?subtype={{ user_meta['subtype'] }}&id={{ user_meta['id'] }}">{{ user_meta['id'] }}</a></td>
|
||||
<td>
|
||||
{% if user_meta['info'] %}
|
||||
{{ user_meta['info'] }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if user_meta['first_seen'] %}
|
||||
{{ user_meta['first_seen'][0:4] }}-{{ user_meta['first_seen'][4:6] }}-{{ user_meta['first_seen'][6:8] }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if user_meta['last_seen'] %}
|
||||
{{ user_meta['last_seen'][0:4] }}-{{ user_meta['last_seen'][4:6] }}-{{ user_meta['last_seen'][6:8] }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ user_meta['nb_messages'] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$("#page-Decoded").addClass("active");
|
||||
$("#nav_chat").addClass("active");
|
||||
|
||||
$('#tableparticipantss').DataTable({
|
||||
"aLengthMenu": [[5, 10, 15, -1], [5, 10, 15, "All"]],
|
||||
"iDisplayLength": 10,
|
||||
"order": [[ 5, "desc" ]]
|
||||
});
|
||||
});
|
||||
|
||||
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>
|
|
@ -66,18 +66,17 @@
|
|||
<table class="table">
|
||||
<thead class="">
|
||||
<tr>
|
||||
<th>Icon</th>
|
||||
<th>Name</th>
|
||||
<th>ID</th>
|
||||
<th>Created at</th>
|
||||
<th>First Seen</th>
|
||||
<th>Last Seen</th>
|
||||
<th>NB Sub-Channels</th>
|
||||
<th>Participants</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody style="font-size: 15px;">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>{{ chat['name'] }}</td>
|
||||
<td>{{ chat['id'] }}</td>
|
||||
<td>{{ chat['created_at'] }}</td>
|
||||
|
@ -92,6 +91,9 @@
|
|||
{% endif %}
|
||||
</td>
|
||||
<td>{{ chat['nb_subchannels'] }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('chats_explorer.chats_explorer_chat_participants')}}?type=chat&subtype={{ chat['subtype'] }}&id={{ chat['id'] }}"><i class="far fa-user-circle"></i> {{ chat['nb_participants']}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
184
var/www/templates/chats_explorer/user_account.html
Normal file
184
var/www/templates/chats_explorer/user_account.html
Normal file
|
@ -0,0 +1,184 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>User Account - 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">
|
||||
|
||||
<!-- 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/d3.min.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/d3/heatmap_week_hour.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">
|
||||
|
||||
<div class="card my-3">
|
||||
|
||||
<div class="card-header" style="background-color:#d9edf7;font-size: 15px">
|
||||
<h4 class="text-secondary">{% if meta['username'] %}{{ meta["username"]["id"] }} {% else %} {{ meta['id'] }}{% endif %} </h4>
|
||||
{% if meta['icon'] %}
|
||||
<div><img src="{{ url_for('objects_image.image', filename=meta['icon'])}}" class="mb-2" alt="{{ meta['id'] }}" width="250" height="250"></div>
|
||||
{% endif %}
|
||||
<ul class="list-group mb-2">
|
||||
<li class="list-group-item py-0">
|
||||
<table class="table">
|
||||
<thead class="">
|
||||
<tr>
|
||||
<th>username</th>
|
||||
<th>ID</th>
|
||||
<th>Created at</th>
|
||||
<th>First Seen</th>
|
||||
<th>Last Seen</th>
|
||||
<th>NB Chats</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody style="font-size: 15px;">
|
||||
<tr>
|
||||
<td>{{ meta['username']['id'] }}</td>
|
||||
<td>{{ meta['id'] }}</td>
|
||||
<td>{{ meta['created_at'] }}</td>
|
||||
<td>
|
||||
{% if meta['first_seen'] %}
|
||||
{{ meta['first_seen'][0:4] }}-{{ meta['first_seen'][4:6] }}-{{ meta['first_seen'][6:8] }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if meta['last_seen'] %}
|
||||
{{ meta['last_seen'][0:4] }}-{{ meta['last_seen'][4:6] }}-{{ meta['last_seen'][6:8] }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ meta['chats'] | length }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% if meta['info'] %}
|
||||
<li class="list-group-item py-0">
|
||||
<pre class="my-0">{{ meta['info'] }}</pre>
|
||||
</li>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="d-flex flex-row-reverse bd-highlight">
|
||||
<div>
|
||||
<a href="{{ url_for('correlation.show_correlation')}}?type={{ meta['type'] }}&subtype={{ meta['subtype'] }}&id={{ meta['id'] }}" target="_blank">
|
||||
<button class="btn btn-lg btn-info"><i class="fas fa-project-diagram"></i> Correlations Graph</button>
|
||||
</a>
|
||||
</div>
|
||||
{# <div>#}
|
||||
{# {% with obj_type=meta['type'], obj_id=meta['id'], obj_subtype=''%}#}
|
||||
{# {% include 'modals/investigations_register_obj.html' %}#}
|
||||
{# {% endwith %}#}
|
||||
{# <div class="mr-2">#}
|
||||
{# <button type="button" class="btn btn-lg btn-primary" data-toggle="modal" data-target="#investigations_register_obj_modal">#}
|
||||
{# <i class="fas fa-microscope"></i> Investigations#}
|
||||
{# </button>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{# {% if meta['subchannels'] %}#}
|
||||
{# <h4>Sub-Channels:</h4>#}
|
||||
{# <table id="tablesubchannels" class="table">#}
|
||||
{# <thead class="bg-dark text-white">#}
|
||||
{# <tr>#}
|
||||
{# <th></th>#}
|
||||
{# <th></th>#}
|
||||
{# <th></th>#}
|
||||
{# <th></th>#}
|
||||
{# </tr>#}
|
||||
{# </thead>#}
|
||||
{# <tbody style="font-size: 15px;">#}
|
||||
{# {% for meta in meta["subchannels"] %}#}
|
||||
{# <tr>#}
|
||||
{# <td>#}
|
||||
{# <img src="{{ url_for('static', filename='image/ail-icon.png') }}" class="rounded-circle mr-1" alt="{{ meta['id'] }}" width="40" height="40">#}
|
||||
{# </td>#}
|
||||
{# <td><b>{{ meta['name'] }}</b></td>#}
|
||||
{# <td><a href="{{ url_for('metas_explorer.objects_subchannel_messages') }}?uuid={{ meta['subtype'] }}&id={{ meta['id'] }}">{{ meta['id'] }}</a></td>#}
|
||||
{# <td>{{ meta['created_at'] }}</td>#}
|
||||
{# <td>#}
|
||||
{# {% if meta['first_seen'] %}#}
|
||||
{# {{ meta['first_seen'][0:4] }}-{{ meta['first_seen'][4:6] }}-{{ meta['first_seen'][6:8] }}#}
|
||||
{# {% endif %}#}
|
||||
{# </td>#}
|
||||
{# <td>#}
|
||||
{# {% if meta['last_seen'] %}#}
|
||||
{# {{ meta['last_seen'][0:4] }}-{{ meta['last_seen'][4:6] }}-{{ meta['last_seen'][6:8] }}#}
|
||||
{# {% endif %}#}
|
||||
{# </td>#}
|
||||
{# <td>{{ meta['nb_messages'] }}</td>#}
|
||||
{# </tr>#}
|
||||
{# {% endfor %}#}
|
||||
{# </tbody>#}
|
||||
{# </table>#}
|
||||
{##}
|
||||
{# {% endif %}#}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$("#page-Decoded").addClass("active");
|
||||
$("#nav_chat").addClass("active");
|
||||
|
||||
{# {% if meta['subchannels'] %}#}
|
||||
{# $('#tablesubchannels').DataTable({#}
|
||||
{# "aLengthMenu": [[5, 10, 15, -1], [5, 10, 15, "All"]],#}
|
||||
{# "iDisplayLength": 10,#}
|
||||
{# "order": [[ 5, "desc" ]]#}
|
||||
{# });#}
|
||||
{# {% endif %}#}
|
||||
});
|
||||
|
||||
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>
|
|
@ -106,6 +106,12 @@
|
|||
<span>Username</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{url_for('objects_subtypes.objects_dashboard_user_account')}}" id="nav_dashboard_user_account">
|
||||
<i class="fas fa-user-circle"></i>
|
||||
<span>User-Acoount</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue