chg: [trackers + retro_hunts] add org level to retro_hunt + update acl to support org + refactor trackers org

otp
terrtia 2024-08-28 16:47:44 +02:00
parent b466d4766a
commit 5c903f9f88
No known key found for this signature in database
GPG Key ID: 1E1B1F50D84613D0
7 changed files with 312 additions and 77 deletions

View File

@ -26,6 +26,7 @@ sys.path.append(os.environ['AIL_BIN'])
from packages import Date
from lib.ail_core import get_objects_tracked, get_object_all_subtypes, get_objects_retro_hunted
from lib import ail_logger
from lib import ail_orgs
from lib import ConfigLoader
from lib import item_basic
from lib import Tag
@ -177,12 +178,45 @@ class Tracker:
def get_description(self):
return self._get_field('description')
## LEVEL ##
def get_level(self):
level = self._get_field('level')
level = int(self._get_field('level'))
if not level:
level = 0
return int(level)
def set_level(self, level, org_uuid):
tracker_type = self.get_type()
if level == 0: # user only
user_id = self.get_user()
r_tracker.sadd(f'user:tracker:{user_id}', self.uuid)
r_tracker.sadd(f'user:tracker:{user_id}:{tracker_type}', self.uuid)
elif level == 1: # global
r_tracker.sadd('global:tracker', self.uuid)
r_tracker.sadd(f'global:tracker:{tracker_type}', self.uuid)
elif level == 2: # org only
r_tracker.sadd(f'org:tracker:{org_uuid}', self.uuid)
r_tracker.sadd(f'org:tracker:{org_uuid}:{tracker_type}', self.uuid)
self.add_to_org(org_uuid)
self._set_field('level', level)
def reset_level(self, old_level, new_level, new_org_uuid):
if old_level == 0:
user_id = self.get_user()
r_tracker.srem(f'user:tracker:{user_id}', self.uuid)
r_tracker.srem(f'user:tracker:{user_id}:{self.get_type()}', self.uuid)
elif old_level == 1:
r_tracker.srem('global:tracker', self.uuid)
r_tracker.srem(f'global:tracker:{self.get_type()}', self.uuid)
# Org
elif old_level == 2:
old_org = self.get_org()
r_tracker.srem(f'org:tracker:{old_org}', self.uuid)
r_tracker.srem(f'org:tracker:{old_org}:{self.get_type()}', self.uuid)
ail_orgs.remove_obj_to_org(old_org, 'tracker', self.uuid)
self.set_level(new_level, new_org_uuid)
def is_level_user(self):
return self.get_level() == 0
@ -192,21 +226,19 @@ class Tracker:
def is_level_global(self):
return self.get_level() == 1
def _set_level(self, level, tracker_type=None, org=None, user=None):
if not tracker_type:
tracker_type = self.get_type()
if level == 0: # user only
if not user:
user = self.get_user()
r_tracker.sadd(f'user:tracker:{user}', self.uuid)
r_tracker.sadd(f'user:tracker:{user}:{tracker_type}', self.uuid)
elif level == 1: # global
r_tracker.sadd('global:tracker', self.uuid)
r_tracker.sadd(f'global:tracker:{tracker_type}', self.uuid)
elif level == 2: # org only
r_tracker.sadd(f'org:tracker:{org}', self.uuid)
r_tracker.sadd(f'org:tracker:{org}:{tracker_type}', self.uuid)
self._set_field('level', level)
## ORG ##
def get_creator_org(self):
return self._get_field('creator_org')
def get_org(self):
return self._get_field('org')
def add_to_org(self, org_uuid):
self._set_field('org', org_uuid)
ail_orgs.add_obj_to_org(org_uuid, 'tracker', self.uuid)
## -ORG- ##
def get_filters(self):
filters = self._get_field('filters')
@ -258,9 +290,6 @@ class Tracker:
def _del_mails(self):
r_tracker.delete(f'tracker:mail:{self.uuid}')
def get_org(self):
return self._get_field('org')
def get_user(self):
return self._get_field('user_id')
@ -424,7 +453,7 @@ class Tracker:
self._set_field('tracked', to_track)
self._set_field('type', tracker_type)
self._set_field('date', datetime.date.today().strftime("%Y%m%d"))
self._set_field('org', org)
self._set_field('creator_org', org)
self._set_field('user_id', user_id)
if description:
self._set_field('description', escape(description))
@ -441,7 +470,7 @@ class Tracker:
# TRACKER LEVEL
self._set_level(level, tracker_type=tracker_type, org=org, user=user_id)
self.set_level(level, org)
# create tracker tags list
if tags:
@ -493,16 +522,7 @@ class Tracker:
if tracker_type != old_type:
# LEVEL
if old_level == 0:
r_tracker.srem(f'user:tracker:{user_id}:{old_type}', self.uuid)
r_tracker.srem(f'user:tracker:{user_id}', self.uuid)
elif old_level == 1:
r_tracker.srem(f'global:tracker:{old_type}', self.uuid)
r_tracker.srem(f'global:tracker', self.uuid)
elif old_level == 2:
r_tracker.srem(f'org:tracker:{self.get_org()}:{old_type}', self.uuid)
r_tracker.srem(f'org:tracker:{self.get_org()}', self.uuid)
self._set_level(level, tracker_type=tracker_type, org=org, user=user_id)
self.reset_level(old_level, level, org)
# Delete OLD YARA Rule File
if old_type == 'yara':
if not is_default_yara_rule(old_to_track):
@ -524,17 +544,9 @@ class Tracker:
r_tracker.sadd(f'trackers:all:{tracker_type}', self.uuid)
# Same Type
elif level != old_level:
if old_level == 0:
r_tracker.srem(f'user:tracker:{user_id}', self.uuid)
r_tracker.srem(f'user:tracker:{user_id}:{tracker_type}', self.uuid)
elif old_level == 1:
r_tracker.srem('global:tracker', self.uuid)
r_tracker.srem(f'global:tracker:{tracker_type}', self.uuid)
elif old_level == 2:
r_tracker.srem(f'org:tracker:{self.get_org()}', self.uuid)
r_tracker.srem(f'org:tracker:{self.get_org()}:{tracker_type}', self.uuid)
self._set_level(level, tracker_type=tracker_type, org=org, user=user_id)
# LEVEL
self.reset_level(old_level, level, org)
# To Track Edited
if to_track != old_to_track:
@ -581,11 +593,6 @@ class Tracker:
tracker_type = self.get_type()
tracked = self.get_tracked()
r_tracker.srem(f'all:tracker:{tracker_type}', tracked)
# tracker - uuid map
r_tracker.srem(f'all:tracker_uuid:{tracker_type}:{tracked}', self.uuid)
r_tracker.srem('trackers:all', self.uuid)
r_tracker.srem(f'trackers:all:{tracker_type}', self.uuid)
if tracker_type == 'typosquatting':
r_tracker.delete(f'tracker:typosquatting:{tracked}')
@ -613,11 +620,17 @@ class Tracker:
elif level == 1: # global
r_tracker.srem('global:tracker', self.uuid)
r_tracker.srem(f'global:tracker:{tracker_type}', self.uuid)
elif level == 2: # TODO ORG check delete permission
elif level == 2:
org = self.get_org()
r_tracker.srem(f'org:tracker:{org}', self.uuid)
r_tracker.srem(f'org:tracker:{org}:{tracker_type}', self.uuid)
r_tracker.srem(f'all:tracker:{tracker_type}', tracked)
# tracker - uuid map
r_tracker.srem(f'all:tracker_uuid:{tracker_type}:{tracked}', self.uuid)
r_tracker.srem('trackers:all', self.uuid)
r_tracker.srem(f'trackers:all:{tracker_type}', self.uuid)
ail_orgs.remove_obj_to_org(self.get_org(), 'tracker', self.uuid)
# meta
r_tracker.delete(f'tracker:{self.uuid}')
trigger_trackers_refresh(tracker_type)
@ -1436,6 +1449,48 @@ class RetroHunt:
def _set_field(self, field, value):
return r_tracker.hset(f'retro_hunt:{self.uuid}', field, value)
## LEVEL ##
def get_level(self):
level = int(self._get_field('level'))
if not level:
level = 0
return int(level)
def set_level(self, level, org_uuid):
if level == 1: # global
r_tracker.sadd('retro_hunts', self.uuid)
elif level == 2: # org only
self.add_to_org(org_uuid)
self._set_field('level', level)
def delete_level(self, level=None):
if not level:
level = self.get_level()
if level == 1:
r_tracker.srem('retro_hunts', self.uuid)
# Org
elif level == 2:
ail_orgs.remove_obj_to_org(self.get_org(), 'retro_hunt', self.uuid)
def reset_level(self, old_level, new_level, new_org_uuid):
self.delete_level(old_level)
self.set_level(new_level, new_org_uuid)
## ORG ##
def get_creator_org(self):
return self._get_field('creator_org')
def get_org(self):
return self._get_field('org')
def add_to_org(self, org_uuid):
self._set_field('org', org_uuid)
ail_orgs.add_obj_to_org(org_uuid, 'retro_hunt', self.uuid)
## -ORG- ##
def get_creator(self):
return self._get_field('creator')
@ -1515,6 +1570,8 @@ class RetroHunt:
meta['date'] = self.get_date()
if 'description' in options:
meta['description'] = self.get_description()
if 'level' in options:
meta['level'] = self.get_level()
if 'mails' in options:
meta['mails'] = self.get_mails()
if 'nb_match' in options:
@ -1624,7 +1681,7 @@ class RetroHunt:
r_tracker.srem(f'obj:retro_hunts:{obj_type}:{subtype}:{obj_id}', self.uuid)
self._decr_nb_match()
def create(self, name, rule, creator, description=None, filters=[], mails=[], tags=[], timeout=30, state='pending'):
def create(self, org_uuid, user_id, level, name, rule, description=None, filters=[], mails=[], tags=[], timeout=30, state='pending'):
if self.exists():
raise Exception('Error: Retro Hunt Task already exists')
@ -1634,7 +1691,8 @@ class RetroHunt:
self._set_field('date', datetime.date.today().strftime("%Y%m%d"))
self._set_field('name', escape(name))
self._set_field('creator', creator)
self._set_field('creator_org', org_uuid)
self._set_field('creator', user_id)
if description:
self._set_field('description', description)
if timeout:
@ -1649,6 +1707,7 @@ class RetroHunt:
if filters:
self.set_filters(filters)
self.set_level(level, org_uuid)
r_tracker.sadd('retro_hunts:all', self.uuid)
# add to pending tasks
@ -1667,6 +1726,8 @@ class RetroHunt:
if filepath:
os.remove(filepath)
self.delete_level()
r_tracker.srem('retro_hunts:pending', self.uuid)
r_tracker.delete(f'retro_hunts:{self.uuid}')
r_tracker.delete(f'retro_hunt:tags:{self.uuid}')
@ -1683,13 +1744,13 @@ class RetroHunt:
self.clear_cache()
return self.uuid
def create_retro_hunt(name, rule_type, rule, creator, description=None, filters=[], mails=[], tags=[], timeout=30, state='pending', task_uuid=None):
def create_retro_hunt(user_org, user_id, level, name, rule_type, rule, description=None, filters=[], mails=[], tags=[], timeout=30, state='pending', task_uuid=None):
if not task_uuid:
task_uuid = str(uuid.uuid4())
retro_hunt = RetroHunt(task_uuid)
# rule_type: yara_default - yara custom
rule = save_yara_rule(rule_type, rule, tracker_uuid=retro_hunt.uuid)
retro_hunt.create(name, rule, creator, description=description, mails=mails, tags=tags,
retro_hunt.create(user_org, user_id , level, name, rule, description=description, mails=mails, tags=tags,
timeout=timeout, filters=filters, state=state)
return retro_hunt.uuid
@ -1713,6 +1774,12 @@ def create_retro_hunt(name, rule_type, rule, creator, description=None, filters=
def get_all_retro_hunt_tasks():
return r_tracker.smembers('retro_hunts:all')
def get_retro_hunts_global():
return r_tracker.smembers('retro_hunts')
def get_retro_hunts_org(org_uuid):
return ail_orgs.get_org_objs_by_type(org_uuid, 'retro_hunt')
def get_retro_hunt_pending_tasks():
return r_tracker.smembers('retro_hunts:pending')
@ -1736,9 +1803,9 @@ def get_retro_hunt_task_to_start():
## Metadata ##
def get_retro_hunt_metas():
def get_retro_hunt_metas(trackers_uuid):
tasks = []
for task_uuid in get_all_retro_hunt_tasks():
for task_uuid in trackers_uuid:
retro_hunt = RetroHunt(task_uuid)
tasks.append(retro_hunt.get_meta(options={'date', 'progress', 'nb_match', 'tags'}))
return tasks
@ -1756,7 +1823,26 @@ def delete_obj_retro_hunts(obj_type, subtype, obj_id):
retro_hunt = RetroHunt(retro_uuid)
retro_hunt.remove(obj_type, subtype, obj_id)
## API ##
#### ACL ####
def check_retro_hunt_access_acl(retro_hunt, user_org, is_admin=False):
if is_admin:
return True
level = retro_hunt.get_level()
if level == 1:
return True
elif level == 2:
return ail_orgs.check_access_acl(retro_hunt, user_org, is_admin=is_admin)
else:
return False
def api_check_retro_hunt_access_acl(retro_hunt, user_org, is_admin=False):
if not check_retro_hunt_access_acl(retro_hunt, user_org, is_admin=is_admin):
return {"status": "error", "reason": "Access Denied"}, 403
#### API ####
def api_check_retro_hunt_task_uuid(task_uuid):
if not is_valid_uuid_v4(task_uuid):
return {"status": "error", "reason": "Invalid uuid"}, 400
@ -1765,22 +1851,28 @@ def api_check_retro_hunt_task_uuid(task_uuid):
return {"status": "error", "reason": "Unknown uuid"}, 404
return None
def api_pause_retro_hunt_task(task_uuid):
def api_pause_retro_hunt_task(user_org, is_admin, task_uuid):
res = api_check_retro_hunt_task_uuid(task_uuid)
if res:
return res
retro_hunt = RetroHunt(task_uuid)
res = api_check_retro_hunt_access_acl(retro_hunt, user_org, is_admin=is_admin)
if res:
return res
task_state = retro_hunt.get_state()
if task_state not in ['pending', 'running']:
return {"status": "error", "reason": f"Task {task_uuid} not paused, current state: {task_state}"}, 400
retro_hunt.pause()
return task_uuid, 200
def api_resume_retro_hunt_task(task_uuid):
def api_resume_retro_hunt_task(user_org, is_admin, task_uuid):
res = api_check_retro_hunt_task_uuid(task_uuid)
if res:
return res
retro_hunt = RetroHunt(task_uuid)
res = api_check_retro_hunt_access_acl(retro_hunt, user_org, is_admin=is_admin)
if res:
return res
if not retro_hunt.is_paused():
return {"status": "error",
"reason": f"Task {task_uuid} not paused, current state: {retro_hunt.get_state()}"}, 400
@ -1798,7 +1890,7 @@ def api_validate_rule_to_add(rule, rule_type):
return {"status": "error", "reason": "Incorrect type"}, 400
return {"status": "success", "rule": rule, "type": rule_type}, 200
def api_create_retro_hunt_task(dict_input, creator):
def api_create_retro_hunt_task(dict_input, user_org, user_id):
# # TODO: API: check mandatory arg
# # TODO: TIMEOUT
@ -1810,6 +1902,15 @@ def api_create_retro_hunt_task(dict_input, creator):
if not task_type:
return {"status": "error", "reason": "type not provided"}, 400
# Level
level = dict_input.get('level', 1)
try:
level = int(level)
except TypeError:
level = 1
if level not in range(1, 3):
level = 1
# # TODO: limit
name = dict_input.get('name', '')
name = escape(name)
@ -1867,15 +1968,18 @@ def api_create_retro_hunt_task(dict_input, creator):
if res:
return res
task_uuid = create_retro_hunt(name, task_type, rule, creator, description=description,
task_uuid = create_retro_hunt(user_org, user_id, level, name, task_type, rule, description=description,
mails=mails, tags=tags, timeout=30, filters=filters)
return {'name': name, 'rule': rule, 'type': task_type, 'uuid': task_uuid}, 200
def api_delete_retro_hunt_task(task_uuid):
def api_delete_retro_hunt_task(user_org, is_admin, task_uuid):
res = api_check_retro_hunt_task_uuid(task_uuid)
if res:
return res
retro_hunt = RetroHunt(task_uuid)
res = api_check_retro_hunt_access_acl(retro_hunt, user_org, is_admin=is_admin)
if res:
return res
if retro_hunt.is_running() and retro_hunt.get_state() not in ['completed', 'paused']:
return {"status": "error", "reason": "You can't delete a running task"}, 400
else:

View File

@ -27,6 +27,29 @@ r_data = config_loader.get_db_conn("Kvrocks_DB") # TODO MOVE DEFAULT DB
config_loader = None
# #### PART OF ORGANISATION ####
# from abc import ABC, abstractmethod
#
# class AbstractObject(ABC):
#
# @abstractmethod
# def get_org(self):
# pass
#
#
# ## LEVEL ##
#
# @abstractmethod
# def get_level(self):
# pass
#
# @abstractmethod
# def set_level(self):
# pass
#
# @abstractmethod
# def reset_level(self):
# pass
#### ORGANISATIONS ####

View File

@ -15,6 +15,7 @@ from lib import ail_users
from lib import Investigations
from lib.ConfigLoader import ConfigLoader
from lib import chats_viewer
from lib import Tracker
class Updater(AIL_Updater):
"""default Updater."""
@ -45,6 +46,13 @@ if __name__ == '__main__':
inv = Investigations.Investigation(inv_uuid)
inv.set_level(1, None)
# TODO Trackers
print('Updating Retro Hunts ...')
for retro_hunt_uuid in Tracker.get_all_retro_hunt_tasks(): # TODO Creator ORG
retro = Tracker.RetroHunt(retro_hunt_uuid)
retro.set_level(1, None)
chats_viewer.fix_chats_with_messages()
updater = Updater('v5.7')

View File

@ -468,8 +468,10 @@ def tracker_objects():
@login_required
@login_read_only
def retro_hunt_all_tasks():
retro_hunts = Tracker.get_retro_hunt_metas()
return render_template("retro_hunt_tasks.html", retro_hunts=retro_hunts, bootstrap_label=bootstrap_label)
user_org = current_user.get_org()
retro_hunts_global = Tracker.get_retro_hunt_metas(Tracker.get_retro_hunts_global())
retro_hunts_org = Tracker.get_retro_hunt_metas(Tracker.get_retro_hunts_org(user_org))
return render_template("retro_hunt_tasks.html", retro_hunts_global=retro_hunts_global, retro_hunts_org=retro_hunts_org, bootstrap_label=bootstrap_label)
@hunters.route('/retro_hunt/task/show', methods=['GET'])
@login_required
@ -478,19 +480,19 @@ def retro_hunt_show_task():
task_uuid = request.args.get('uuid', None)
objs = request.args.get('objs', False)
date_from_item = request.args.get('date_from')
date_to_item = request.args.get('date_to')
if date_from_item:
date_from_item = date_from_item.replace('-', '')
if date_to_item:
date_to_item = date_to_item.replace('-', '')
# date_from_item = request.args.get('date_from')
# date_to_item = request.args.get('date_to')
# if date_from_item:
# date_from_item = date_from_item.replace('-', '')
# if date_to_item:
# date_to_item = date_to_item.replace('-', '')
res = Tracker.api_check_retro_hunt_task_uuid(task_uuid)
if res:
return create_json_response(res[0], res[1])
retro_hunt = Tracker.RetroHunt(task_uuid)
dict_task = retro_hunt.get_meta(options={'creator', 'date', 'description', 'progress', 'filters', 'nb_objs', 'tags'})
dict_task = retro_hunt.get_meta(options={'creator', 'date', 'description', 'level', 'progress', 'filters', 'nb_objs', 'tags'})
rule_content = Tracker.get_yara_rule_content(dict_task['rule'])
dict_task['filters'] = json.dumps(dict_task['filters'], indent=4)
@ -509,6 +511,7 @@ def retro_hunt_show_task():
@login_analyst
def retro_hunt_add_task():
if request.method == 'POST':
level = request.form.get("level", 1)
name = request.form.get("name", '')
description = request.form.get("description", '')
timeout = request.form.get("timeout", 30)
@ -586,14 +589,15 @@ def retro_hunt_add_task():
rule = yara_default_rule
rule_type='yara_default'
user_org = current_user.get_org()
user_id = current_user.get_user_id()
input_dict = {"name": name, "description": description, "creator": user_id,
input_dict = {"level": level, "name": name, "description": description, "creator": user_id,
"rule": rule, "type": rule_type,
"tags": tags, "filters": filters, "timeout": timeout, # "mails": mails
}
res = Tracker.api_create_retro_hunt_task(input_dict, user_id)
res = Tracker.api_create_retro_hunt_task(input_dict, user_org, user_id)
if res[1] == 200:
return redirect(url_for('hunters.retro_hunt_all_tasks'))
else:
@ -609,8 +613,10 @@ def retro_hunt_add_task():
@login_required
@login_analyst
def retro_hunt_pause_task():
user_org = current_user.get_org()
is_admin = current_user.is_admin()
task_uuid = request.args.get('uuid', None)
res = Tracker.api_pause_retro_hunt_task(task_uuid)
res = Tracker.api_pause_retro_hunt_task(user_org, is_admin, task_uuid)
if res[1] != 200:
return create_json_response(res[0], res[1])
return redirect(url_for('hunters.retro_hunt_all_tasks'))
@ -619,8 +625,10 @@ def retro_hunt_pause_task():
@login_required
@login_analyst
def retro_hunt_resume_task():
user_org = current_user.get_org()
is_admin = current_user.is_admin()
task_uuid = request.args.get('uuid', None)
res = Tracker.api_resume_retro_hunt_task(task_uuid)
res = Tracker.api_resume_retro_hunt_task(user_org, is_admin, task_uuid)
if res[1] != 200:
return create_json_response(res[0], res[1])
return redirect(url_for('hunters.retro_hunt_all_tasks'))
@ -629,8 +637,10 @@ def retro_hunt_resume_task():
@login_required
@login_analyst
def retro_hunt_delete_task():
user_org = current_user.get_org()
is_admin = current_user.is_admin()
task_uuid = request.args.get('uuid', None)
res = Tracker.api_delete_retro_hunt_task(task_uuid)
res = Tracker.api_delete_retro_hunt_task(user_org, is_admin, task_uuid)
if res[1] != 200:
return create_json_response(res[0], res[1])
return redirect(url_for('hunters.retro_hunt_all_tasks'))

View File

@ -175,6 +175,12 @@
</div>
<div class="col-12 col-xl-3">
<label class="mt-3" for="level_selector">View Level</label>
<select class="custom-select" id="level_selector" name="level">
<option value="1" selected><i class="fas fa-users"></i> Global</option>
<option value="2"><i class="fas fa-landmark"></i> My Organisation</option>
</select>
</div>
</div>

View File

@ -35,7 +35,9 @@
Create New Retro Hunt
</a>
<table id="table_user_trackers" class="table table-striped border-primary">
<h5>My Organisation:</h5>
<table id="table_retro_hunts_org" class="table table-striped border-primary">
<thead class="bg-dark text-white">
<tr>
<th>Name</th>
@ -46,7 +48,74 @@
</tr>
</thead>
<tbody style="font-size: 15px;">
{% for dict_task in retro_hunts %}
{% for dict_task in retro_hunts_org %}
<tr class="border-color: blue;">
<td>
<a href="{{ url_for('hunters.retro_hunt_show_task') }}?uuid={{ dict_task['uuid'] }}">
<span>
{{ dict_task['name']}}
</span>
</a>
<div>
{% for tag in dict_task['tags'] %}
<a href="{{ url_for('tags_ui.get_obj_by_tags') }}?object_type=item&ltags={{ tag }}">
<span class="badge badge-{{ bootstrap_label[loop.index0 % 5] }} pull-left">{{ tag }}</span>
</a>
{% endfor %}
</div>
</td>
<td>{{ dict_task['date'][0:4]}}/{{ dict_task['date'][4:6]}}/{{ dict_task['date'][6:8]}}</td>
<td>
<b><h3 class="font-weight-bold text-primary">{{dict_task['nb_match']}}</h3></b>
</td>
<td>
{%if dict_task['state']=='paused'%}
<a href="{{ url_for('hunters.retro_hunt_resume_task') }}?uuid={{dict_task['uuid']}}" class="mx-1">
<button class='btn btn-info'><i class="fas fa-play"></i></button>
</a>
{%endif%}
{%if dict_task['state']=='running' or dict_task['state']=='pending'%}
<!-- <a href="{{ url_for('hunters.retro_hunt_pause_task') }}?uuid={{dict_task['uuid']}}" class="mx-1">
<button class='btn btn-secondary'><i class="fas fa-stop"></i></button>
</a> -->
<a href="{{ url_for('hunters.retro_hunt_pause_task') }}?uuid={{dict_task['uuid']}}" class="mx-1">
<button class='btn btn-info'><i class="fas fa-pause"></i></button>
</a>
{%endif%}
</td>
<td class="text-center">
<span class="justify-content-end">
{%if dict_task['state']=='pending'%}
<span class="text-secondary"><i class="fas fa-ellipsis-h fa-3x"></i>pending</span>
{%elif dict_task['state']=='completed'%}
<span class="text-success"><i class="fas fa-check-square fa-3x"></i>&nbsp;completed</span>
{%elif dict_task['state']=='paused'%}
<span class="text-secondary"><i class="fas fa-pause fa-3x"></i>&nbsp;paused [{{ dict_task['progress']}}%]</span>
{%elif dict_task['state']=='running'%}
<span class="text-secondary"><i class="fas fa-sync-alt fa-3x fa-spin"></i>running [{{ dict_task['progress']}}%]</span>
{%endif%}
<span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<h5>Global:</h5>
<table id="table_retro_hunts_global" class="table table-striped border-primary">
<thead class="bg-dark text-white">
<tr>
<th>Name</th>
<th>Date</th>
<th>Nb Matches</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody style="font-size: 15px;">
{% for dict_task in retro_hunts_global %}
<tr class="border-color: blue;">
<td>
<a href="{{ url_for('hunters.retro_hunt_show_task') }}?uuid={{ dict_task['uuid'] }}">
@ -112,7 +181,12 @@ $(document).ready(function(){
$('#nav_title_retro_hunt').removeClass("text-muted");
$("#nav_retro_hunts").addClass("active");
$('#table_user_trackers').DataTable({
$('#table_retro_hunts_org').DataTable({
"aLengthMenu": [[5, 10, 15, -1], [5, 10, 15, "All"]],
"iDisplayLength": 10,
"order": [[ 1, "desc" ],[ 4, "desc" ]]
});
$('#table_retro_hunts_global').DataTable({
"aLengthMenu": [[5, 10, 15, -1], [5, 10, 15, "All"]],
"iDisplayLength": 10,
"order": [[ 1, "desc" ],[ 4, "desc" ]]

View File

@ -105,6 +105,16 @@
<td class="text-right"><b>Description</b></td>
<td>{{dict_task['description']}}</td>
</tr>
<tr>
<td class="text-right"><b>Level</b></td>
<td>
{% if dict_task['level'] == 1 %}
Global
{% elif dict_task['level'] == 2 %}
My Organisation
{% endif %}
</td>
</tr>
<tr>
<td class="text-right"><b>Tags</b></td>
<td>