mirror of https://github.com/MISP/misp-modules
new: [webiste] query+config
parent
b5b9d8d408
commit
2fe44f21fd
|
@ -0,0 +1,5 @@
|
|||
# PetitCrolle
|
||||
|
||||
Tool that help conducting a forensic analysis by providing a regroupement of tools in one place
|
||||
|
||||
<img title="MarkText logo" src="file:///home/dacru/Desktop/Git/PetitCrolle/doc/crolle.png" alt="Alt text" width="388" data-align="center">
|
|
@ -0,0 +1,38 @@
|
|||
from app import create_app, db
|
||||
import argparse
|
||||
from flask import render_template
|
||||
import os
|
||||
from app.utils.init_modules import create_modules_db
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-i", "--init_db", help="Initialise the db if it not exist", action="store_true")
|
||||
parser.add_argument("-r", "--recreate_db", help="Delete and initialise the db", action="store_true")
|
||||
parser.add_argument("-d", "--delete_db", help="Delete the db", action="store_true")
|
||||
parser.add_argument("-m", "--create_module", help="Create modules in db", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
os.environ.setdefault('FLASKENV', 'development')
|
||||
|
||||
app = create_app()
|
||||
|
||||
@app.errorhandler(404)
|
||||
def error_page_not_found(e):
|
||||
return render_template('404.html'), 404
|
||||
|
||||
|
||||
if args.init_db:
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
elif args.recreate_db:
|
||||
with app.app_context():
|
||||
db.drop_all()
|
||||
db.create_all()
|
||||
elif args.delete_db:
|
||||
with app.app_context():
|
||||
db.drop_all()
|
||||
elif args.create_module:
|
||||
with app.app_context():
|
||||
create_modules_db()
|
||||
else:
|
||||
app.run(host=app.config.get("FLASK_URL"), port=app.config.get("FLASK_PORT"))
|
|
@ -0,0 +1,30 @@
|
|||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_wtf import CSRFProtect
|
||||
from flask_migrate import Migrate
|
||||
|
||||
from config import config as Config
|
||||
import os
|
||||
|
||||
|
||||
db = SQLAlchemy()
|
||||
csrf = CSRFProtect()
|
||||
migrate = Migrate()
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
config_name = os.environ.get("FLASKENV")
|
||||
|
||||
app.config.from_object(Config[config_name])
|
||||
|
||||
Config[config_name].init_app(app)
|
||||
|
||||
db.init_app(app)
|
||||
csrf.init_app(app)
|
||||
migrate.init_app(app, db, render_as_batch=True)
|
||||
|
||||
from .home import home_blueprint
|
||||
app.register_blueprint(home_blueprint, url_prefix="/")
|
||||
|
||||
return app
|
||||
|
|
@ -0,0 +1,591 @@
|
|||
import json
|
||||
from flask import Blueprint, render_template, redirect, jsonify, request, flash
|
||||
from .form import CaseForm, CaseEditForm, AddOrgsCase, RecurringForm
|
||||
from flask_login import login_required, current_user
|
||||
from . import case_core as CaseModel
|
||||
from . import common_core as CommonModel
|
||||
from . import task_core as TaskModel
|
||||
from ..db_class.db import Task_Template, Case_Template
|
||||
from ..decorators import editor_required
|
||||
from ..utils.utils import form_to_dict, check_tag
|
||||
|
||||
case_blueprint = Blueprint(
|
||||
'case',
|
||||
__name__,
|
||||
template_folder='templates',
|
||||
static_folder='static'
|
||||
)
|
||||
|
||||
from .task import task_blueprint
|
||||
case_blueprint.register_blueprint(task_blueprint)
|
||||
|
||||
##########
|
||||
# Render #
|
||||
##########
|
||||
|
||||
|
||||
@case_blueprint.route("/", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def index():
|
||||
"""List all cases"""
|
||||
return render_template("case/case_index.html")
|
||||
|
||||
@case_blueprint.route("/create_case", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def create_case():
|
||||
"""Create a case"""
|
||||
form = CaseForm()
|
||||
form.template_select.choices = [(template.id, template.title) for template in Case_Template.query.all()]
|
||||
form.template_select.choices.insert(0, (0," "))
|
||||
|
||||
form.tasks_templates.choices = [(template.id, template.title) for template in Task_Template.query.all()]
|
||||
form.tasks_templates.choices.insert(0, (0," "))
|
||||
|
||||
if form.validate_on_submit():
|
||||
tag_list = request.form.getlist("tags_select")
|
||||
cluster_list = request.form.getlist("clusters_select")
|
||||
if CommonModel.check_tag(tag_list):
|
||||
if CommonModel.check_cluster(cluster_list):
|
||||
form_dict = form_to_dict(form)
|
||||
form_dict["tags"] = tag_list
|
||||
form_dict["clusters"] = cluster_list
|
||||
case = CaseModel.create_case(form_dict, current_user)
|
||||
flash("Case created", "success")
|
||||
return redirect(f"/case/{case.id}")
|
||||
return render_template("case/create_case.html", form=form)
|
||||
return render_template("case/create_case.html", form=form)
|
||||
return render_template("case/create_case.html", form=form)
|
||||
|
||||
@case_blueprint.route("/<cid>", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def view(cid):
|
||||
"""View a case"""
|
||||
case = CommonModel.get_case(cid)
|
||||
if case:
|
||||
present_in_case = CaseModel.get_present_in_case(cid, current_user)
|
||||
return render_template("case/case_view.html", case=case.to_json(), present_in_case=present_in_case)
|
||||
return render_template("404.html")
|
||||
|
||||
|
||||
@case_blueprint.route("/edit/<cid>", methods=['GET','POST'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def edit_case(cid):
|
||||
"""Edit the case"""
|
||||
if CommonModel.get_case(cid):
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
form = CaseEditForm()
|
||||
|
||||
if form.validate_on_submit():
|
||||
tag_list = request.form.getlist("tags_select")
|
||||
cluster_list = request.form.getlist("clusters_select")
|
||||
if CommonModel.check_tag(tag_list):
|
||||
if CommonModel.check_cluster(cluster_list):
|
||||
form_dict = form_to_dict(form)
|
||||
form_dict["tags"] = tag_list
|
||||
form_dict["clusters"] = cluster_list
|
||||
CaseModel.edit_case(form_dict, cid, current_user)
|
||||
flash("Case edited", "success")
|
||||
return redirect(f"/case/{cid}")
|
||||
return render_template("case/edit_case.html", form=form)
|
||||
return render_template("case/edit_case.html", form=form)
|
||||
else:
|
||||
case_modif = CommonModel.get_case(cid)
|
||||
form.description.data = case_modif.description
|
||||
form.title.data = case_modif.title
|
||||
form.deadline_date.data = case_modif.deadline
|
||||
form.deadline_time.data = case_modif.deadline
|
||||
|
||||
return render_template("case/edit_case.html", form=form)
|
||||
else:
|
||||
flash("Access denied", "error")
|
||||
else:
|
||||
return render_template("404.html")
|
||||
return redirect(f"/case/{id}")
|
||||
|
||||
|
||||
@case_blueprint.route("/<cid>/add_orgs", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def add_orgs(cid):
|
||||
"""Add orgs to the case"""
|
||||
|
||||
if CommonModel.get_case(cid):
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
form = AddOrgsCase()
|
||||
case_org = CommonModel.get_org_in_case_by_case_id(cid)
|
||||
org_list = list()
|
||||
for org in CommonModel.get_org_order_by_name():
|
||||
if case_org:
|
||||
flag = False
|
||||
for c_o in case_org:
|
||||
if c_o.org_id == org.id:
|
||||
flag = True
|
||||
if not flag:
|
||||
org_list.append((org.id, f"{org.name}"))
|
||||
else:
|
||||
org_list.append((org.id, f"{org.name}"))
|
||||
|
||||
form.org_id.choices = org_list
|
||||
form.case_id.data = cid
|
||||
|
||||
if form.validate_on_submit():
|
||||
form_dict = form_to_dict(form)
|
||||
CaseModel.add_orgs_case(form_dict, cid, current_user)
|
||||
flash("Orgs added", "success")
|
||||
return redirect(f"/case/{cid}")
|
||||
|
||||
return render_template("case/add_orgs.html", form=form)
|
||||
else:
|
||||
flash("Access denied", "error")
|
||||
else:
|
||||
return render_template("404.html")
|
||||
return redirect(f"/case/{cid}")
|
||||
|
||||
|
||||
@case_blueprint.route("/<cid>/recurring", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def recurring(cid):
|
||||
"""Recurring form"""
|
||||
|
||||
if CommonModel.get_case(cid):
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
form = RecurringForm()
|
||||
form.case_id.data = cid
|
||||
|
||||
# List orgs and users in and verify if all users of an org are currently notify
|
||||
orgs_in_case = CommonModel.get_orgs_in_case(cid)
|
||||
orgs_to_return = list()
|
||||
for org in orgs_in_case:
|
||||
loc = org.to_json()
|
||||
loc["users"] = list()
|
||||
cp_checked_user = 0
|
||||
cp_users = 0
|
||||
for user in org.users:
|
||||
cp_users += 1
|
||||
loc_user = user.to_json()
|
||||
if CommonModel.get_recu_notif_user(cid, user.id):
|
||||
loc_user["checked"] = True
|
||||
cp_checked_user += 1
|
||||
else:
|
||||
loc_user["checked"] = False
|
||||
loc["users"].append(loc_user)
|
||||
# if all users in an org are notify, then check the org checkbox
|
||||
if cp_checked_user == cp_users:
|
||||
loc["checked"] = True
|
||||
else:
|
||||
loc["checked"] = False
|
||||
orgs_to_return.append(loc)
|
||||
|
||||
if form.validate_on_submit():
|
||||
form_dict = form_to_dict(form)
|
||||
if not CaseModel.change_recurring(form_dict, cid, current_user):
|
||||
flash("Recurring empty", "error")
|
||||
return redirect(f"/case/{cid}/recurring")
|
||||
if not form_dict["remove"]:
|
||||
CaseModel.notify_user_recurring(request.form.to_dict(), cid, orgs_in_case)
|
||||
flash("Recurring set", "success")
|
||||
return redirect(f"/case/{cid}")
|
||||
|
||||
return render_template("case/case_recurring.html", form=form, orgs=orgs_to_return)
|
||||
|
||||
flash("Action not allowed", "warning")
|
||||
return redirect(f"/case/{cid}")
|
||||
|
||||
return render_template("404.html")
|
||||
|
||||
|
||||
############
|
||||
# Function #
|
||||
# Route #
|
||||
############
|
||||
|
||||
@case_blueprint.route("/get_cases_page", methods=['GET'])
|
||||
@login_required
|
||||
def get_cases():
|
||||
"""Return all cases"""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
tags = request.args.get('tags')
|
||||
taxonomies = request.args.get('taxonomies')
|
||||
or_and = request.args.get("or_and")
|
||||
|
||||
cases = CaseModel.sort_by_status(page, tags, taxonomies, or_and, completed=False)
|
||||
role = CommonModel.get_role(current_user).to_json()
|
||||
|
||||
loc = CaseModel.regroup_case_info(cases, current_user)
|
||||
return jsonify({"cases": loc["cases"], "role": role, "nb_pages": cases.pages}), 200
|
||||
|
||||
|
||||
@case_blueprint.route("/search", methods=['GET'])
|
||||
@login_required
|
||||
def search():
|
||||
"""Return cases matching search terms"""
|
||||
text_search = request.args.get("text")
|
||||
cases = CommonModel.search(text_search)
|
||||
if cases:
|
||||
return {"cases": [case.to_json() for case in cases]}, 200
|
||||
return {"message": "No case", 'toast_class': "danger-subtle"}, 404
|
||||
|
||||
|
||||
@case_blueprint.route("/<cid>/delete", methods=['GET'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def delete(cid):
|
||||
"""Delete the case"""
|
||||
|
||||
if CommonModel.get_case(cid):
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
if CaseModel.delete_case(cid, current_user):
|
||||
return {"message": "Case deleted", "toast_class": "success-subtle"}, 200
|
||||
else:
|
||||
return {"message": "Error case deleted", 'toast_class': "danger-subtle"}, 400
|
||||
return {"message": "Action not allowed", "toast_class": "warning-subtle"}, 401
|
||||
return {"message": "Case no found", 'toast_class': "danger-subtle"}, 404
|
||||
|
||||
|
||||
@case_blueprint.route("/<cid>/get_case_info", methods=['GET'])
|
||||
@login_required
|
||||
def get_case_info(cid):
|
||||
"""Return all info of the case"""
|
||||
case = CommonModel.get_case(cid)
|
||||
if case:
|
||||
tasks = TaskModel.sort_by_status_task_core(case, current_user, completed=False)
|
||||
|
||||
o_in_c = CommonModel.get_orgs_in_case(case.id)
|
||||
orgs_in_case = [o_c.to_json() for o_c in o_in_c]
|
||||
permission = CommonModel.get_role(current_user).to_json()
|
||||
present_in_case = CaseModel.get_present_in_case(cid, current_user)
|
||||
|
||||
return jsonify({"case": case.to_json(), "tasks": tasks, "orgs_in_case": orgs_in_case, "permission": permission, "present_in_case": present_in_case, "current_user": current_user.to_json()}), 200
|
||||
return {"message": "Case not found", 'toast_class': "danger-subtle"}, 404
|
||||
|
||||
|
||||
@case_blueprint.route("/<cid>/complete_case", methods=['GET'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def complete_case(cid):
|
||||
"""Complete the case"""
|
||||
if CommonModel.get_case(cid):
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
if CaseModel.complete_case(cid, current_user):
|
||||
flash("Case Completed")
|
||||
if request.args.get('revived', 1) == "true":
|
||||
return {"message": "Case Revived", "toast_class": "success-subtle"}, 200
|
||||
return {"message": "Case completed", "toast_class": "success-subtle"}, 200
|
||||
else:
|
||||
if request.args.get('revived', 1) == "true":
|
||||
return {"message": "Error case revived", 'toast_class': "danger-subtle"}, 400
|
||||
return {"message": "Error case completed", 'toast_class': "danger-subtle"}, 400
|
||||
return {"message": "Action not allowed", "toast_class": "warning-subtle"}, 401
|
||||
return {"message": "Case not found", 'toast_class': "danger-subtle"}, 404
|
||||
|
||||
|
||||
@case_blueprint.route("/<cid>/remove_org/<oid>", methods=['GET'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def remove_org_case(cid, oid):
|
||||
"""Remove an org to the case"""
|
||||
|
||||
if CommonModel.get_case(cid):
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
if CaseModel.remove_org_case(cid, oid, current_user):
|
||||
return {"message": "Org removed from case", "toast_class": "success-subtle"}, 200
|
||||
return {"message": "Error removing org from case", "toast_class": "danger-subtle"}, 400
|
||||
return {"message": "Action not allowed", "toast_class": "warning-subtle"}, 401
|
||||
return {"message": "Case not found", 'toast_class': "danger-subtle"}, 404
|
||||
|
||||
|
||||
@case_blueprint.route("/<cid>/change_status", methods=['POST'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def change_status(cid):
|
||||
"""Change the status of the case"""
|
||||
|
||||
status = request.json["status"]
|
||||
case = CommonModel.get_case(cid)
|
||||
|
||||
if CommonModel.get_case(cid):
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
CaseModel.change_status_core(status, case, current_user)
|
||||
return {"message": "Status changed", "toast_class": "success-subtle"}, 200
|
||||
return {"message": "Action not allowed", "toast_class": "warning-subtle"}, 401
|
||||
return {"message": "Case not found", 'toast_class': "danger-subtle"}, 404
|
||||
|
||||
|
||||
@case_blueprint.route("/get_status", methods=['GET'])
|
||||
@login_required
|
||||
def get_status():
|
||||
"""Get status"""
|
||||
|
||||
status = CommonModel.get_all_status()
|
||||
status_list = list()
|
||||
for s in status:
|
||||
status_list.append(s.to_json())
|
||||
return jsonify({"status": status_list}), 200
|
||||
|
||||
|
||||
@case_blueprint.route("/sort_by_ongoing", methods=['GET'])
|
||||
@login_required
|
||||
def sort_by_ongoing():
|
||||
"""Sort Case by living one"""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
tags = request.args.get('tags')
|
||||
taxonomies = request.args.get('taxonomies')
|
||||
or_and_taxo = request.args.get("or_and_taxo")
|
||||
|
||||
galaxies = request.args.get('galaxies')
|
||||
clusters = request.args.get('clusters')
|
||||
or_and_galaxies = request.args.get("or_and_galaxies")
|
||||
|
||||
cases_list = CaseModel.sort_by_status(page, taxonomies, galaxies, tags, clusters, or_and_taxo, or_and_galaxies, completed=False)
|
||||
return CaseModel.regroup_case_info(cases_list, current_user)
|
||||
|
||||
|
||||
|
||||
@case_blueprint.route("/sort_by_finished", methods=['GET'])
|
||||
@login_required
|
||||
def sort_by_finished():
|
||||
"""Sort Case by finished one"""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
tags = request.args.get('tags')
|
||||
taxonomies = request.args.get('taxonomies')
|
||||
or_and_taxo = request.args.get("or_and_taxo")
|
||||
|
||||
galaxies = request.args.get('galaxies')
|
||||
clusters = request.args.get('clusters')
|
||||
or_and_galaxies = request.args.get("or_and_galaxies")
|
||||
|
||||
cases_list = CaseModel.sort_by_status(page, taxonomies, galaxies, tags, clusters, or_and_taxo, or_and_galaxies, completed=True)
|
||||
return CaseModel.regroup_case_info(cases_list, current_user)
|
||||
|
||||
|
||||
@case_blueprint.route("/ongoing", methods=['GET'])
|
||||
@login_required
|
||||
def ongoing_sort_by_filter():
|
||||
"""Sort by filter for living case"""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
filter = request.args.get('filter')
|
||||
tags = request.args.get('tags')
|
||||
taxonomies = request.args.get('taxonomies')
|
||||
or_and_taxo = request.args.get("or_and_taxo")
|
||||
|
||||
galaxies = request.args.get('galaxies')
|
||||
clusters = request.args.get('clusters')
|
||||
or_and_galaxies = request.args.get("or_and_galaxies")
|
||||
|
||||
if filter:
|
||||
cases_list, nb_pages = CaseModel.sort_by_filter(filter, page, taxonomies, galaxies, tags, clusters, or_and_taxo, or_and_galaxies, completed=False)
|
||||
return CaseModel.regroup_case_info(cases_list, current_user, nb_pages)
|
||||
return {"message": "No filter pass"}
|
||||
|
||||
|
||||
@case_blueprint.route("/finished", methods=['GET'])
|
||||
@login_required
|
||||
def finished_sort_by_filter():
|
||||
"""Sort by filter for finished task"""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
filter = request.args.get('filter')
|
||||
tags = request.args.get('tags')
|
||||
taxonomies = request.args.get('taxonomies')
|
||||
or_and_taxo = request.args.get("or_and_taxo")
|
||||
|
||||
galaxies = request.args.get('galaxies')
|
||||
clusters = request.args.get('clusters')
|
||||
or_and_galaxies = request.args.get("or_and_galaxies")
|
||||
|
||||
if filter:
|
||||
cases_list, nb_pages = CaseModel.sort_by_filter(filter, page, taxonomies, galaxies, tags, clusters, or_and_taxo, or_and_galaxies, completed=True)
|
||||
return CaseModel.regroup_case_info(cases_list, current_user, nb_pages)
|
||||
return {"message": "No filter pass"}
|
||||
|
||||
|
||||
@case_blueprint.route("/<cid>/get_all_users", methods=['GET'])
|
||||
@login_required
|
||||
def get_all_users(cid):
|
||||
"""Get all user in case"""
|
||||
|
||||
case = CommonModel.get_case(cid)
|
||||
if case:
|
||||
users_list = list()
|
||||
orgs = CommonModel.get_all_users_core(case)
|
||||
for org in orgs:
|
||||
for user in org.users:
|
||||
if not user == current_user:
|
||||
users_list.append(user.to_json())
|
||||
return {"users_list": users_list}
|
||||
return {"message": "Case not found"}, 404
|
||||
|
||||
|
||||
@case_blueprint.route("/<cid>/get_assigned_users/<tid>", methods=['GET'])
|
||||
@login_required
|
||||
def get_assigned_users(cid, tid):
|
||||
"""Get assigned users to the task"""
|
||||
|
||||
if CommonModel.get_case(cid):
|
||||
users, _ = TaskModel.get_users_assign_task(tid, current_user)
|
||||
return users
|
||||
return {"message": "Case not found", 'toast_class': "danger-subtle"}, 404
|
||||
|
||||
|
||||
@case_blueprint.route("/<cid>/download", methods=['GET'])
|
||||
@login_required
|
||||
def download_case(cid):
|
||||
"""Download a case"""
|
||||
|
||||
case = CommonModel.get_case(cid)
|
||||
if case:
|
||||
task_list = list()
|
||||
for task in case.tasks:
|
||||
task_list.append(task.download())
|
||||
return_dict = case.download()
|
||||
return_dict["tasks"] = task_list
|
||||
return jsonify(return_dict), 200, {'Content-Disposition': f'attachment; filename=case_{case.title}.json'}
|
||||
return {"message": "Case not found", 'toast_class': "danger-subtle"}, 404
|
||||
|
||||
|
||||
|
||||
@case_blueprint.route("/<cid>/fork", methods=['POST'])
|
||||
@login_required
|
||||
def fork_case(cid):
|
||||
"""Assign current user to the task"""
|
||||
|
||||
if CommonModel.get_case(cid):
|
||||
if "case_title_fork" in request.json:
|
||||
case_title_fork = request.json["case_title_fork"]
|
||||
|
||||
new_case = CaseModel.fork_case_core(cid, case_title_fork, current_user)
|
||||
if type(new_case) == dict:
|
||||
return new_case
|
||||
return {"new_case_id": new_case.id}, 201
|
||||
return {"message": "'case_title_fork' is missing", 'toast_class': "danger-subtle"}, 400
|
||||
return {"message": "Case not found", 'toast_class': "danger-subtle"}, 404
|
||||
|
||||
|
||||
@case_blueprint.route("/get_all_case_title", methods=['GET'])
|
||||
@login_required
|
||||
def get_all_case_title():
|
||||
data_dict = dict(request.args)
|
||||
if CommonModel.get_case_by_title(data_dict["title"]):
|
||||
flag = True
|
||||
else:
|
||||
flag = False
|
||||
|
||||
return {"title_already_exist": flag}
|
||||
|
||||
|
||||
@case_blueprint.route("/<cid>/create_template", methods=['POST'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def create_template(cid):
|
||||
if CommonModel.get_case(cid):
|
||||
if "case_title_template" in request.json:
|
||||
case_title_template = request.json["case_title_template"]
|
||||
|
||||
new_template = CaseModel.create_template_from_case(cid, case_title_template)
|
||||
if type(new_template) == dict:
|
||||
return new_template
|
||||
return {"template_id": new_template.id}, 201
|
||||
return {"message": "'case_title_template' is missing", 'toast_class': "danger-subtle"}, 400
|
||||
return {"message": "Case not found", 'toast_class': "danger-subtle"}, 404
|
||||
|
||||
|
||||
@case_blueprint.route("/get_all_case_template_title", methods=['GET'])
|
||||
@login_required
|
||||
def get_all_case_template_title():
|
||||
data_dict = dict(request.args)
|
||||
if CommonModel.get_case_template_by_title(data_dict["title"]):
|
||||
flag = True
|
||||
else:
|
||||
flag = False
|
||||
|
||||
return {"title_already_exist": flag}
|
||||
|
||||
|
||||
@case_blueprint.route("/history/<cid>", methods=['GET'])
|
||||
@login_required
|
||||
def history(cid):
|
||||
case = CommonModel.get_case(cid)
|
||||
if case:
|
||||
history = CommonModel.get_history(case.uuid)
|
||||
if history:
|
||||
return {"history": history}
|
||||
return {"history": None}
|
||||
return {"message": "Case Not found", 'toast_class': "danger-subtle"}, 404
|
||||
|
||||
|
||||
@case_blueprint.route("/get_taxonomies", methods=['GET'])
|
||||
@login_required
|
||||
def get_taxonomies():
|
||||
return {"taxonomies": CommonModel.get_taxonomies()}, 200
|
||||
|
||||
@case_blueprint.route("/get_tags", methods=['GET'])
|
||||
@login_required
|
||||
def get_tags():
|
||||
data_dict = dict(request.args)
|
||||
if "taxonomies" in data_dict:
|
||||
taxos = json.loads(data_dict["taxonomies"])
|
||||
return {"tags": CommonModel.get_tags(taxos)}, 200
|
||||
return {"message": "'taxonomies' is missing", 'toast_class': "warning-subtle"}, 400
|
||||
|
||||
|
||||
@case_blueprint.route("/get_taxonomies_case/<cid>", methods=['GET'])
|
||||
@login_required
|
||||
def get_taxonomies_case(cid):
|
||||
case = CommonModel.get_case(cid)
|
||||
if case:
|
||||
tags = CommonModel.get_case_tags(case.id)
|
||||
taxonomies = []
|
||||
if tags:
|
||||
taxonomies = [tag.split(":")[0] for tag in tags]
|
||||
return {"tags": tags, "taxonomies": taxonomies}
|
||||
return {"message": "Case Not found", 'toast_class': "danger-subtle"}, 404
|
||||
|
||||
|
||||
@case_blueprint.route("/get_galaxies", methods=['GET'])
|
||||
@login_required
|
||||
def get_galaxies():
|
||||
return {"galaxies": CommonModel.get_galaxies()}, 200
|
||||
|
||||
|
||||
@case_blueprint.route("/get_clusters", methods=['GET'])
|
||||
@login_required
|
||||
def get_clusters():
|
||||
if "galaxies" in request.args:
|
||||
galaxies = request.args.get("galaxies")
|
||||
galaxies = json.loads(galaxies)
|
||||
return {"clusters": CommonModel.get_clusters_galaxy(galaxies)}, 200
|
||||
return {"message": "'galaxies' is missing", 'toast_class': "warning-subtle"}, 400
|
||||
|
||||
|
||||
@case_blueprint.route("/get_galaxies_case/<cid>", methods=['GET'])
|
||||
@login_required
|
||||
def get_galaxies_case(cid):
|
||||
case = CommonModel.get_case(cid)
|
||||
if case:
|
||||
clusters = CommonModel.get_case_clusters(case.id)
|
||||
galaxies = []
|
||||
if clusters:
|
||||
for cluster in clusters:
|
||||
loc_g = CommonModel.get_galaxy(cluster.galaxy_id)
|
||||
if not loc_g.name in galaxies:
|
||||
galaxies.append(loc_g.name)
|
||||
index = clusters.index(cluster)
|
||||
clusters[index] = cluster.tag
|
||||
return {"clusters": clusters, "galaxies": galaxies}
|
||||
return {"message": "Case Not found", 'toast_class': "danger-subtle"}, 404
|
||||
|
||||
|
||||
@case_blueprint.route("/get_modules", methods=['GET'])
|
||||
@login_required
|
||||
def get_modules():
|
||||
return {"modules": CaseModel.get_modules()}, 200
|
||||
# return {"message": "'galaxies' is missing", 'toast_class': "warning-subtle"}, 400
|
||||
|
||||
|
||||
@case_blueprint.route("/get_instance_module", methods=['GET'])
|
||||
@login_required
|
||||
def get_instance_module():
|
||||
if "module" in request.args:
|
||||
module = request.args.get("module")
|
||||
return {"instances": CaseModel.get_instance_module_core(module, current_user.id)}, 200
|
|
@ -0,0 +1,602 @@
|
|||
from flask import Blueprint, request
|
||||
from . import case_core as CaseModel
|
||||
from . import common_core as CommonModel
|
||||
from . import task_core as TaskModel
|
||||
from . import case_core_api as CaseModelApi
|
||||
|
||||
from flask_restx import Api, Resource
|
||||
from ..decorators import api_required, editor_required
|
||||
|
||||
api_case_blueprint = Blueprint('api_case', __name__)
|
||||
api = Api(api_case_blueprint,
|
||||
title='Flowintel-cm API',
|
||||
description='API to manage a case management instance.',
|
||||
version='0.1',
|
||||
default='GenericAPI',
|
||||
default_label='Generic Flowintel-cm API',
|
||||
doc='/doc/'
|
||||
)
|
||||
|
||||
|
||||
|
||||
@api.route('/all')
|
||||
@api.doc(description='Get all cases')
|
||||
class GetCases(Resource):
|
||||
method_decorators = [api_required]
|
||||
def get(self):
|
||||
cases = CommonModel.get_all_cases()
|
||||
return {"cases": [case.to_json() for case in cases]}, 200
|
||||
|
||||
@api.route('/not_completed')
|
||||
@api.doc(description='Get all not completed cases')
|
||||
class GetCases_not_completed(Resource):
|
||||
method_decorators = [api_required]
|
||||
def get(self):
|
||||
cases = CommonModel.get_case_by_completed(False)
|
||||
return {"cases": [case.to_json() for case in cases]}, 200
|
||||
|
||||
@api.route('/completed')
|
||||
@api.doc(description='Get all completed cases')
|
||||
class GetCases_not_completed(Resource):
|
||||
method_decorators = [api_required]
|
||||
def get(self):
|
||||
cases = CommonModel.get_case_by_completed(True)
|
||||
return {"cases": [case.to_json() for case in cases]}, 200
|
||||
|
||||
|
||||
@api.route('/<cid>')
|
||||
@api.doc(description='Get a case', params={'cid': 'id of a case'})
|
||||
class GetCase(Resource):
|
||||
method_decorators = [api_required]
|
||||
def get(self, cid):
|
||||
case = CommonModel.get_case(cid)
|
||||
if case:
|
||||
case_json = case.to_json()
|
||||
orgs = CommonModel.get_orgs_in_case(cid)
|
||||
case_json["orgs"] = list()
|
||||
for org in orgs:
|
||||
case_json["orgs"].append({"id": org.id, "uuid": org.uuid, "name": org.name})
|
||||
|
||||
return case_json, 200
|
||||
return {"message": "Case not found"}, 404
|
||||
|
||||
|
||||
@api.route('/title', methods=["POST"])
|
||||
@api.doc(description='Get a case by title')
|
||||
class GetCaseTitle(Resource):
|
||||
method_decorators = [api_required]
|
||||
@api.doc(params={"title": "Title of a case"})
|
||||
def post(self):
|
||||
if "title" in request.json:
|
||||
case = CommonModel.get_case_by_title(request.json["title"])
|
||||
if case:
|
||||
case_json = case.to_json()
|
||||
orgs = CommonModel.get_orgs_in_case(case.id)
|
||||
case_json["orgs"] = [{"id": org.id, "uuid": org.uuid, "name": org.name} for org in orgs]
|
||||
return case_json, 200
|
||||
return {"message": "Case not found"}, 404
|
||||
return {"message": "Need to pass a title"}, 404
|
||||
|
||||
|
||||
@api.route('/<cid>/complete')
|
||||
@api.doc(description='Complete a case', params={'cid': 'id of a case'})
|
||||
class CompleteCase(Resource):
|
||||
method_decorators = [editor_required, api_required]
|
||||
def get(self, cid):
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
case = CommonModel.get_case(cid)
|
||||
if case:
|
||||
if CaseModel.complete_case(cid, current_user):
|
||||
return {"message": f"Case {cid} completed"}, 200
|
||||
return {"message": f"Error case {cid} completed"}, 400
|
||||
return {"message": "Case not found"}, 404
|
||||
return {"message": "Permission denied"}, 403
|
||||
|
||||
|
||||
@api.route('/<cid>/create_template', methods=["POST"])
|
||||
@api.doc(description='Create a template form case', params={'cid': 'id of a case'})
|
||||
class CreateTemplate(Resource):
|
||||
method_decorators = [editor_required, api_required]
|
||||
@api.doc(params={"title_template": "Title for the template that will be create"})
|
||||
def post(self, cid):
|
||||
if "title_template" in request.json:
|
||||
if CommonModel.get_case(cid):
|
||||
new_template = CaseModel.create_template_from_case(cid, request.json["title_template"])
|
||||
if type(new_template) == dict:
|
||||
return new_template
|
||||
return {"template_id": new_template.id}, 201
|
||||
return {"message": "Case not found"}, 404
|
||||
return {"message": "'title_template' is missing"}, 400
|
||||
|
||||
|
||||
@api.route('/<cid>/recurring', methods=['POST'])
|
||||
@api.doc(description='Set a case recurring')
|
||||
class RecurringCase(Resource):
|
||||
method_decorators = [editor_required, api_required]
|
||||
@api.doc(params={
|
||||
"once": "Date(%Y-%m-%d)",
|
||||
"daily": "Boolean",
|
||||
"weekly": "Date(%Y-%m-%d). Start date.",
|
||||
"monthly": "Date(%Y-%m-%d). Start date.",
|
||||
"remove": "Boolean"
|
||||
})
|
||||
def post(self, cid):
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
if request.json:
|
||||
verif_dict = CaseModelApi.verif_set_recurring(request.json)
|
||||
|
||||
if "message" not in verif_dict:
|
||||
CaseModel.change_recurring(verif_dict, cid, current_user)
|
||||
return {"message": "Recurring changed"}, 200
|
||||
return verif_dict
|
||||
return {"message": "Please give data"}, 400
|
||||
return {"message": "Permission denied"}, 403
|
||||
|
||||
|
||||
@api.route('/<cid>/tasks')
|
||||
@api.doc(description='Get all tasks for a case', params={'cid': 'id of a case'})
|
||||
class GetTasks(Resource):
|
||||
method_decorators = [api_required]
|
||||
def get(self, cid):
|
||||
case = CommonModel.get_case(cid)
|
||||
if case:
|
||||
tasks = list()
|
||||
for task in case.tasks:
|
||||
tasks.append(task.to_json())
|
||||
|
||||
return tasks, 200
|
||||
return {"message": "Case not found"}, 404
|
||||
|
||||
|
||||
@api.route('/<cid>/task/<tid>')
|
||||
@api.doc(description='Get a specific task for a case', params={"cid": "id of a case", "tid": "id of a task"})
|
||||
class GetTask(Resource):
|
||||
method_decorators = [api_required]
|
||||
def get(self, cid, tid):
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
if int(cid) == task.case_id:
|
||||
loc = dict()
|
||||
loc["users_assign"], loc["is_current_user_assign"] = TaskModel.get_users_assign_task(task.id, CaseModelApi.get_user_api(request.headers["X-API-KEY"]))
|
||||
loc["task"] = task.to_json()
|
||||
return loc, 200
|
||||
else:
|
||||
return {"message": "Task not in this case"}, 404
|
||||
return {"message": "Task not found"}, 404
|
||||
|
||||
@api.route('/<cid>/delete')
|
||||
@api.doc(description='Delete a case', params={'cid': 'id of a case'})
|
||||
class DeleteCase(Resource):
|
||||
method_decorators = [editor_required, api_required]
|
||||
def get(self, cid):
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
if CaseModel.delete_case(cid, current_user):
|
||||
return {"message": "Case deleted"}, 200
|
||||
return {"message": "Error case deleted"}, 400
|
||||
return {"message": "Permission denied"}, 403
|
||||
|
||||
|
||||
@api.route('/<cid>/task/<tid>/delete')
|
||||
@api.doc(description='Delete a specific task in a case', params={'cid': 'id of a case', "tid": "id of a task"})
|
||||
class DeleteTask(Resource):
|
||||
method_decorators = [editor_required, api_required]
|
||||
def get(self, cid, tid):
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
if int(cid) == task.case_id:
|
||||
if TaskModel.delete_task(tid, current_user):
|
||||
return {"message": "Task deleted"}, 200
|
||||
else:
|
||||
return {"message": "Error task deleted"}, 400
|
||||
else:
|
||||
return {"message": "Task not in this case"}, 404
|
||||
return {"message": "Task not found"}, 404
|
||||
return {"message": "Permission denied"}, 403
|
||||
|
||||
|
||||
@api.route('/create', methods=['POST'])
|
||||
@api.doc(description='Create a case')
|
||||
class CreateCase(Resource):
|
||||
method_decorators = [editor_required, api_required]
|
||||
@api.doc(params={
|
||||
"title": "Required. Title for a case",
|
||||
"description": "Description of a case",
|
||||
"deadline_date": "Date(%Y-%m-%d)",
|
||||
"deadline_time": "Time(%H-%M)"
|
||||
})
|
||||
def post(self):
|
||||
user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
|
||||
if request.json:
|
||||
verif_dict = CaseModelApi.verif_create_case_task(request.json, True)
|
||||
|
||||
if "message" not in verif_dict:
|
||||
case = CaseModel.create_case(verif_dict, user)
|
||||
return {"message": f"Case created, id: {case.id}"}, 201
|
||||
|
||||
return verif_dict, 400
|
||||
return {"message": "Please give data"}, 400
|
||||
|
||||
|
||||
@api.route('/<cid>/create_task', methods=['POST'])
|
||||
@api.doc(description='Create a new task to a case', params={'cid': 'id of a case'})
|
||||
class CreateTask(Resource):
|
||||
method_decorators = [editor_required, api_required]
|
||||
@api.doc(params={
|
||||
"title": "Required. Title for a task",
|
||||
"description": "Description of a task",
|
||||
"url": "Link to a tool or a ressource",
|
||||
"deadline_date": "Date(%Y-%m-%d)",
|
||||
"deadline_time": "Time(%H-%M)"
|
||||
})
|
||||
def post(self, cid):
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
if request.json:
|
||||
verif_dict = CaseModelApi.verif_create_case_task(request.json, False)
|
||||
|
||||
if "message" not in verif_dict:
|
||||
task = TaskModel.create_task(verif_dict, cid, current_user)
|
||||
return {"message": f"Task created for case id: {cid}"}, 201
|
||||
return verif_dict, 400
|
||||
return {"message": "Please give data"}, 400
|
||||
return {"message": "Permission denied"}, 403
|
||||
|
||||
|
||||
@api.route('/<id>/edit', methods=['POST'])
|
||||
@api.doc(description='Edit a case', params={'id': 'id of a case'})
|
||||
class EditCase(Resource):
|
||||
method_decorators = [editor_required, api_required]
|
||||
@api.doc(params={"title": "Title for a case", "description": "Description of a case", "deadline_date": "Date(%Y-%m-%d)", "deadline_time": "Time(%H-%M)"})
|
||||
def post(self, id):
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(id, current_user) or current_user.is_admin():
|
||||
if request.json:
|
||||
verif_dict = CaseModelApi.verif_edit_case(request.json, id)
|
||||
|
||||
if "message" not in verif_dict:
|
||||
CaseModel.edit_case(verif_dict, id, current_user)
|
||||
return {"message": f"Case {id} edited"}, 200
|
||||
|
||||
return verif_dict, 400
|
||||
return {"message": "Please give data"}, 400
|
||||
return {"message": "Permission denied"}, 403
|
||||
|
||||
|
||||
@api.route('/<cid>/task/<tid>/edit', methods=['POST'])
|
||||
@api.doc(description='Edit a task in a case', params={'cid': 'id of a case', "tid": "id of a task"})
|
||||
class EditTake(Resource):
|
||||
method_decorators = [editor_required, api_required]
|
||||
@api.doc(params={"title": "Title for a case", "description": "Description of a case", "deadline_date": "Date(%Y-%m-%d)", "deadline_time": "Time(%H-%M)"})
|
||||
def post(self, cid, tid):
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
if request.json:
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
if int(cid) == task.case_id:
|
||||
verif_dict = CaseModelApi.verif_edit_task(request.json, tid)
|
||||
|
||||
if "message" not in verif_dict:
|
||||
TaskModel.edit_task_core(verif_dict, tid, current_user)
|
||||
return {"message": f"Task {tid} edited"}, 200
|
||||
|
||||
return verif_dict, 400
|
||||
else:
|
||||
return {"message": "Task not in this case"}, 404
|
||||
else:
|
||||
return {"message": "Task not found"}, 404
|
||||
return {"message": "Please give data"}, 400
|
||||
return {"message": "Permission denied"}, 403
|
||||
|
||||
|
||||
@api.route('/<cid>/task/<tid>/complete')
|
||||
@api.doc(description='Complete a task in a case', params={'cid': 'id of a case', "tid": "id of a task"})
|
||||
class CompleteTake(Resource):
|
||||
method_decorators = [editor_required, api_required]
|
||||
def get(self, cid, tid):
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
if int(cid) == task.case_id:
|
||||
if TaskModel.complete_task(tid, current_user):
|
||||
return {"message": f"Task {tid} completed"}, 200
|
||||
return {"message": f"Error task {tid} completed"}, 400
|
||||
else:
|
||||
return {"message": "Task not in this case"}, 404
|
||||
return {"message": "Task not found"}, 404
|
||||
return {"message": "Permission denied"}, 403
|
||||
|
||||
|
||||
@api.route('/<cid>/task/<tid>/get_note')
|
||||
@api.doc(description='Get note of a task in a case', params={'cid': 'id of a case', "tid": "id of a task"})
|
||||
class GetNoteTask(Resource):
|
||||
method_decorators = [api_required]
|
||||
def get(self, cid, tid):
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
if int(cid) == task.case_id:
|
||||
return {"note": task.notes}, 200
|
||||
else:
|
||||
return {"message": "Task not in this case"}, 404
|
||||
return {"message": "Task not found"}, 404
|
||||
|
||||
|
||||
@api.route('/<cid>/task/<tid>/modif_note', methods=['POST'])
|
||||
@api.doc(description='Edit note of a task in a case', params={'cid': 'id of a case', "tid": "id of a task"})
|
||||
class ModifNoteTask(Resource):
|
||||
method_decorators = [editor_required, api_required]
|
||||
@api.doc(params={"note": "note to create or modify"})
|
||||
def post(self, cid, tid):
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
if "note" in request.json:
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
if int(cid) == task.case_id:
|
||||
if TaskModel.modif_note_core(tid, current_user, request.json["note"]):
|
||||
return {"message": f"Note for task {tid} edited"}, 200
|
||||
return {"message": f"Error Note for task {tid} edited"}, 400
|
||||
else:
|
||||
return {"message": "Task not in this case"}, 404
|
||||
return {"message": "Task not found"}, 404
|
||||
return {"message": "Key 'note' not found"}, 400
|
||||
return {"message": "Permission denied"}, 403
|
||||
|
||||
|
||||
|
||||
@api.route('/<cid>/add_org', methods=['POST'])
|
||||
@api.doc(description='Add an org to the case', params={'cid': 'id of a case'})
|
||||
class AddOrgCase(Resource):
|
||||
method_decorators = [editor_required, api_required]
|
||||
@api.doc(params={"name": "Name of the organisation", "oid": "id of the organisation"})
|
||||
def post(self, cid):
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
if "name" in request.json:
|
||||
org = CommonModel.get_org_by_name(request.json["name"])
|
||||
elif "oid" in request.json:
|
||||
org = CommonModel.get_org(request.json["oid"])
|
||||
else:
|
||||
return {"message": "Required an id or a name of an Org"}, 400
|
||||
|
||||
if org:
|
||||
if not CommonModel.get_org_in_case(org.id, cid):
|
||||
if CaseModel.add_orgs_case({"org_id": [org.id]}, cid, current_user):
|
||||
return {"message": f"Org added to case {cid}"}, 200
|
||||
return {"message": f"Error Org added to case {cid}"}, 400
|
||||
return {"message": "Org already in case"}, 400
|
||||
return {"message": "Org not found"}, 404
|
||||
return {"message": "Permission denied"}, 403
|
||||
|
||||
|
||||
@api.route('/<cid>/remove_org/<oid>', methods=['GET'])
|
||||
@api.doc(description='Add an org to the case', params={'cid': 'id of a case', "oid": "id of an org"})
|
||||
class RemoveOrgCase(Resource):
|
||||
method_decorators = [editor_required, api_required]
|
||||
def get(self, cid, oid):
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
org = CommonModel.get_org(oid)
|
||||
|
||||
if org:
|
||||
if CommonModel.get_org_in_case(org.id, cid):
|
||||
if CaseModel.remove_org_case(cid, org.id, current_user):
|
||||
return {"message": f"Org deleted from case {cid}"}, 200
|
||||
return {"message": f"Error Org deleted from case {cid}"}, 400
|
||||
return {"message": "Org not in case"}, 404
|
||||
return {"message": "Org not found"}, 404
|
||||
return {"message": "Permission denied"}, 403
|
||||
|
||||
|
||||
@api.route('/<cid>/take_task/<tid>', methods=['GET'])
|
||||
@api.doc(description='Assign current user to the task', params={'cid': 'id of a case', "tid": "id of a task"})
|
||||
class AssignTask(Resource):
|
||||
method_decorators = [editor_required, api_required]
|
||||
def get(self, cid, tid):
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
task = CommonModel.get_task(tid)
|
||||
|
||||
if task:
|
||||
if int(cid) == task.case_id:
|
||||
if TaskModel.assign_task(tid, user=current_user, current_user=current_user, flag_current_user=True):
|
||||
return {"message": f"Task Take"}, 200
|
||||
return {"message": f"Error Task Take"}, 400
|
||||
return {"message": "Task not in this case"}, 404
|
||||
return {"message": "Task not found"}, 404
|
||||
return {"message": "Permission denied"}, 403
|
||||
|
||||
|
||||
@api.route('/<cid>/remove_assignment/<tid>', methods=['GET'])
|
||||
@api.doc(description='Remove assigment of current user to the task', params={'cid': 'id of a case', "tid": "id of a task"})
|
||||
class RemoveOrgCase(Resource):
|
||||
method_decorators = [editor_required, api_required]
|
||||
def get(self, cid, tid):
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
if int(cid) == task.case_id:
|
||||
if TaskModel.remove_assign_task(tid, user=current_user, current_user=current_user, flag_current_user=True):
|
||||
return {"message": f"Removed from assignment"}, 200
|
||||
return {"message": f"Error Removed from assignment"}, 400
|
||||
return {"message": "Task not in this case"}, 404
|
||||
return {"message": "Task not found"}, 404
|
||||
return {"message": "Permission denied"}, 403
|
||||
|
||||
|
||||
@api.route('/<cid>/get_all_users', methods=['GET'])
|
||||
@api.doc(description='Get list of user that can be assign', params={'cid': 'id of a case'})
|
||||
class GetAllUsers(Resource):
|
||||
method_decorators = [api_required]
|
||||
def get(self, cid):
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
case = CommonModel.get_case(cid)
|
||||
if case:
|
||||
users_list = list()
|
||||
for org in CommonModel.get_all_users_core(case):
|
||||
for user in org.users:
|
||||
if not user == current_user:
|
||||
users_list.append(user.to_json())
|
||||
return {"users": users_list}, 200
|
||||
return {"message": "Case not found"}, 404
|
||||
|
||||
|
||||
@api.route('/<cid>/task/<tid>/assign_users', methods=['POST'])
|
||||
@api.doc(description='Assign users to a task', params={'cid': 'id of a case', "tid": "id of a task"})
|
||||
class AssignUser(Resource):
|
||||
method_decorators = [editor_required, api_required]
|
||||
@api.doc(params={"users_id": "List of user id"})
|
||||
def post(self, cid, tid):
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
if int(cid) == task.case_id:
|
||||
users_list = request.json["users_id"]
|
||||
for user in users_list:
|
||||
TaskModel.assign_task(tid, user=user, current_user=current_user, flag_current_user=False)
|
||||
return {"message": "Users Assigned"}, 200
|
||||
return {"message": "Task not in this case"}, 404
|
||||
return {"message": "Task not found"}, 404
|
||||
return {"message": "Permission denied"}, 403
|
||||
|
||||
|
||||
@api.route('/<cid>/task/<tid>/remove_assign_user', methods=['POST'])
|
||||
@api.doc(description='Remove an assign user to a task', params={'cid': 'id of a case', "tid": "id of a task"})
|
||||
class AssignUser(Resource):
|
||||
method_decorators = [editor_required, api_required]
|
||||
@api.doc(params={"user_id": "Id of a user"})
|
||||
def post(self, cid, tid):
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
if int(cid) == task.case_id:
|
||||
user_id = request.json["user_id"]
|
||||
if TaskModel.remove_assign_task(tid, user=user_id, current_user=current_user, flag_current_user=False):
|
||||
return {"message": "User Removed from assignment"}, 200
|
||||
return {"message": "Task not in this case"}, 404
|
||||
return {"message": "Task not found"}, 404
|
||||
return {"message": "Permission denied"}, 403
|
||||
|
||||
|
||||
|
||||
@api.route('/<cid>/task/<tid>/change_status', methods=['POST'])
|
||||
@api.doc(description='Change status of a task', params={'cid': 'id of a case', "tid": "id of a task"})
|
||||
class ChangeStatus(Resource):
|
||||
method_decorators = [editor_required, api_required]
|
||||
@api.doc(params={"status_id": "Id of the new status"})
|
||||
def post(self, cid, tid):
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
if int(cid) == task.case_id:
|
||||
if TaskModel.change_task_status(request.json["status_id"], task, current_user):
|
||||
return {"message": "Status changed"}, 200
|
||||
return {"message": "Task not in this case"}, 404
|
||||
return {"message": "Task not found"}, 404
|
||||
return {"message": "Permission denied"}, 403
|
||||
|
||||
|
||||
@api.route('/list_status', methods=['GET'])
|
||||
@api.doc(description='List all status')
|
||||
class ChangeStatus(Resource):
|
||||
method_decorators = [api_required]
|
||||
def get(self):
|
||||
return [status.to_json() for status in CommonModel.get_all_status()], 200
|
||||
|
||||
|
||||
@api.route('/<cid>/history', methods=['GET'])
|
||||
@api.doc(description='Get history of a case', params={'cid': 'id of a case'})
|
||||
class ChangeStatus(Resource):
|
||||
method_decorators = [api_required]
|
||||
def get(self, cid):
|
||||
case = CommonModel.get_case(cid)
|
||||
if case:
|
||||
history = CommonModel.get_history(case.uuid)
|
||||
if history:
|
||||
return {"history": history}
|
||||
return {"history": None}
|
||||
return {"message": "Case Not found"}, 404
|
||||
|
||||
|
||||
@api.route('/<cid>/task/<tid>/files')
|
||||
@api.doc(description='Get list of files', params={"cid": "id of a case", "tid": "id of a task"})
|
||||
class DownloadFile(Resource):
|
||||
method_decorators = [api_required]
|
||||
def get(self, cid, tid):
|
||||
case = CommonModel.get_case(cid)
|
||||
if case:
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
file_list = [file.to_json() for file in task.files]
|
||||
return {"files": file_list}, 200
|
||||
return {"message": "Task Not found"}, 404
|
||||
return {"message": "Case Not found"}, 404
|
||||
|
||||
@api.route('/<cid>/task/<tid>/upload_file')
|
||||
@api.doc(description='Upload a file')
|
||||
class UploadFile(Resource):
|
||||
method_decorators = [api_required]
|
||||
@api.doc(params={})
|
||||
def post(self, cid, tid):
|
||||
case = CommonModel.get_case(cid)
|
||||
if case:
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
if TaskModel.add_file_core(task, request.files, current_user):
|
||||
return {"message": "File added"}, 200
|
||||
return {"message": "Task Not found"}, 404
|
||||
return {"message": "Permission denied"}, 403
|
||||
return {"message": "Case Not found"}, 404
|
||||
|
||||
|
||||
@api.route('/<cid>/task/<tid>/download_file/<fid>')
|
||||
@api.doc(description='Download a file', params={"cid": "id of a case", "tid": "id of a task", "fid": "id of a file"})
|
||||
class DownloadFile(Resource):
|
||||
method_decorators = [api_required]
|
||||
def get(self, cid, tid, fid):
|
||||
case = CommonModel.get_case(cid)
|
||||
if case:
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
file = CommonModel.get_file(fid)
|
||||
if file and file in task.files:
|
||||
return TaskModel.download_file(file)
|
||||
return {"message": "Task Not found"}, 404
|
||||
return {"message": "Permission denied"}, 403
|
||||
return {"message": "Case Not found"}, 404
|
||||
|
||||
@api.route('/<cid>/task/<tid>/delete_file/<fid>')
|
||||
@api.doc(description='Delete a file', params={"cid": "id of a case", "tid": "id of a task", "fid": "id of a file"})
|
||||
class DeleteFile(Resource):
|
||||
method_decorators = [api_required]
|
||||
@api.doc(params={
|
||||
})
|
||||
def get(self, cid, tid, fid):
|
||||
case = CommonModel.get_case(cid)
|
||||
if case:
|
||||
current_user = CaseModelApi.get_user_api(request.headers["X-API-KEY"])
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
file = CommonModel.get_file(fid)
|
||||
if file and file in task.files:
|
||||
if TaskModel.delete_file(file, task, current_user):
|
||||
return {"message": "File Deleted"}, 200
|
||||
return {"message": "Task Not found"}, 404
|
||||
return {"message": "Permission denied"}, 403
|
||||
return {"message": "Case Not found"}, 404
|
|
@ -0,0 +1,610 @@
|
|||
import os
|
||||
import ast
|
||||
import uuid
|
||||
import datetime
|
||||
|
||||
from app.utils.utils import get_modules_list, MODULES, MODULES_CONFIG
|
||||
from .. import db
|
||||
from ..db_class.db import *
|
||||
from sqlalchemy import desc, and_
|
||||
from ..notification import notification_core as NotifModel
|
||||
from dateutil import relativedelta
|
||||
from ..tools.tools_core import create_case_from_template
|
||||
|
||||
from . import common_core as CommonModel
|
||||
from . import task_core as TaskModel
|
||||
|
||||
|
||||
def delete_case(cid, current_user):
|
||||
"""Delete a case by is id"""
|
||||
case = CommonModel.get_case(cid)
|
||||
if case:
|
||||
# Delete all tasks in the case
|
||||
for task in case.tasks:
|
||||
TaskModel.delete_task(task.id, current_user)
|
||||
|
||||
history_path = os.path.join(CommonModel.HISTORY_FOLDER, str(case.uuid))
|
||||
if os.path.isfile(history_path):
|
||||
try:
|
||||
os.remove(history_path)
|
||||
except:
|
||||
return False
|
||||
|
||||
NotifModel.create_notification_all_orgs(f"Case: '{case.id}-{case.title}' was deleted", cid, html_icon="fa-solid fa-trash", current_user=current_user)
|
||||
|
||||
Case_Tags.query.filter_by(case_id=case.id).delete()
|
||||
Case_Galaxy_Tags.query.filter_by(case_id=case.id).delete()
|
||||
Case_Org.query.filter_by(case_id=case.id).delete()
|
||||
db.session.delete(case)
|
||||
db.session.commit()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def complete_case(cid, current_user):
|
||||
"""Complete case by is id"""
|
||||
case = CommonModel.get_case(cid)
|
||||
if case is not None:
|
||||
case.completed = not case.completed
|
||||
if case.completed:
|
||||
case.status_id = Status.query.filter_by(name="Finished").first().id
|
||||
for task in case.tasks:
|
||||
TaskModel.complete_task(task.id, current_user)
|
||||
NotifModel.create_notification_all_orgs(f"Case: '{case.id}-{case.title}' is now completed", cid, html_icon="fa-solid fa-square-check", current_user=current_user)
|
||||
else:
|
||||
case.status_id = Status.query.filter_by(name="Created").first().id
|
||||
NotifModel.create_notification_all_orgs(f"Case: '{case.id}-{case.title}' is now revived", cid, html_icon="fa-solid fa-heart-circle-plus", current_user=current_user)
|
||||
|
||||
CommonModel.update_last_modif(cid)
|
||||
db.session.commit()
|
||||
CommonModel.save_history(case.uuid, current_user, "Case completed")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def create_case(form_dict, user):
|
||||
"""Add a case to the DB"""
|
||||
if "template_select" in form_dict and not 0 in form_dict["template_select"]:
|
||||
for template in form_dict["template_select"]:
|
||||
if Case_Template.query.get(template):
|
||||
case = create_case_from_template(template, form_dict["title_template"], user)
|
||||
else:
|
||||
deadline = CommonModel.deadline_check(form_dict["deadline_date"], form_dict["deadline_time"])
|
||||
case = Case(
|
||||
title=form_dict["title"],
|
||||
description=form_dict["description"],
|
||||
uuid=str(uuid.uuid4()),
|
||||
creation_date=datetime.datetime.now(tz=datetime.timezone.utc),
|
||||
last_modif=datetime.datetime.now(tz=datetime.timezone.utc),
|
||||
deadline=deadline,
|
||||
status_id=1,
|
||||
owner_org_id=user.org_id
|
||||
)
|
||||
db.session.add(case)
|
||||
db.session.commit()
|
||||
|
||||
for tags in form_dict["tags"]:
|
||||
tag = CommonModel.get_tag(tags)
|
||||
|
||||
case_tag = Case_Tags(
|
||||
tag_id=tag.id,
|
||||
case_id=case.id
|
||||
)
|
||||
db.session.add(case_tag)
|
||||
db.session.commit()
|
||||
|
||||
for clusters in form_dict["clusters"]:
|
||||
cluster = CommonModel.get_cluster_by_name(clusters)
|
||||
|
||||
case_galaxy = Case_Galaxy_Tags(
|
||||
cluster_id=cluster.id,
|
||||
case_id=case.id
|
||||
)
|
||||
db.session.add(case_galaxy)
|
||||
db.session.commit()
|
||||
|
||||
if "tasks_templates" in form_dict and not 0 in form_dict["tasks_templates"]:
|
||||
for tid in form_dict["tasks_templates"]:
|
||||
task = Task_Template.query.get(tid)
|
||||
t = Task(
|
||||
uuid=str(uuid.uuid4()),
|
||||
title=task.title,
|
||||
description=task.description,
|
||||
url=task.url,
|
||||
creation_date=datetime.datetime.now(tz=datetime.timezone.utc),
|
||||
last_modif=datetime.datetime.now(tz=datetime.timezone.utc),
|
||||
case_id=case.id,
|
||||
status_id=1
|
||||
)
|
||||
db.session.add(t)
|
||||
db.session.commit()
|
||||
|
||||
for t_t in Task_Template_Tags.query.filter_by(task_id=task.id).all():
|
||||
task_tag = Task_Tags(
|
||||
task_id=t.id,
|
||||
tag_id=t_t.tag_id
|
||||
)
|
||||
db.session.add(task_tag)
|
||||
db.session.commit()
|
||||
|
||||
for t_t in Task_Template_Galaxy_Tags.query.filter_by(task_id=task.id).all():
|
||||
task_galaxy = Task_Galaxy_Tags(
|
||||
task_id=t.id,
|
||||
cluster_id=t_t.cluster_id
|
||||
)
|
||||
db.session.add(task_galaxy)
|
||||
db.session.commit()
|
||||
|
||||
# Add the current user's org to the case
|
||||
case_org = Case_Org(
|
||||
case_id=case.id,
|
||||
org_id=user.org_id
|
||||
)
|
||||
db.session.add(case_org)
|
||||
db.session.commit()
|
||||
|
||||
CommonModel.save_history(case.uuid, user, "Case Created")
|
||||
|
||||
return case
|
||||
|
||||
|
||||
def edit_case(form_dict, cid, current_user):
|
||||
"""Edit a case to the DB"""
|
||||
case = CommonModel.get_case(cid)
|
||||
deadline = CommonModel.deadline_check(form_dict["deadline_date"], form_dict["deadline_time"])
|
||||
|
||||
case.title = form_dict["title"]
|
||||
case.description=form_dict["description"]
|
||||
case.deadline=deadline
|
||||
|
||||
## Tags
|
||||
case_tag_db = Case_Tags.query.filter_by(case_id=case.id).all()
|
||||
for tags in form_dict["tags"]:
|
||||
tag = CommonModel.get_tag(tags)
|
||||
|
||||
if not tags in case_tag_db:
|
||||
case_tag = Case_Tags(
|
||||
tag_id=tag.id,
|
||||
case_id=case.id
|
||||
)
|
||||
db.session.add(case_tag)
|
||||
db.session.commit()
|
||||
|
||||
for c_t_db in case_tag_db:
|
||||
if not c_t_db in form_dict["tags"]:
|
||||
Case_Tags.query.filter_by(id=c_t_db.id).delete()
|
||||
db.session.commit()
|
||||
|
||||
## Clusters
|
||||
case_cluster_db = Case_Galaxy_Tags.query.filter_by(case_id=case.id).all()
|
||||
for clusters in form_dict["clusters"]:
|
||||
cluster = CommonModel.get_cluster_by_name(clusters)
|
||||
|
||||
if not clusters in case_cluster_db:
|
||||
case_galaxy_tag = Case_Galaxy_Tags(
|
||||
cluster_id=cluster.id,
|
||||
case_id=case.id
|
||||
)
|
||||
db.session.add(case_galaxy_tag)
|
||||
db.session.commit()
|
||||
|
||||
for c_t_db in case_cluster_db:
|
||||
if not c_t_db in form_dict["clusters"]:
|
||||
Case_Galaxy_Tags.query.filter_by(id=c_t_db.id).delete()
|
||||
db.session.commit()
|
||||
|
||||
CommonModel.update_last_modif(cid)
|
||||
db.session.commit()
|
||||
|
||||
CommonModel.save_history(case.uuid, current_user, f"Case edited")
|
||||
|
||||
|
||||
def add_orgs_case(form_dict, cid, current_user):
|
||||
"""Add orgs to case in th DB"""
|
||||
for org_id in form_dict["org_id"]:
|
||||
case_org = Case_Org(
|
||||
case_id=cid,
|
||||
org_id=org_id
|
||||
)
|
||||
db.session.add(case_org)
|
||||
case = CommonModel.get_case(cid)
|
||||
NotifModel.create_notification_org(f"{CommonModel.get_org(org_id).name} add to case: '{case.id}-{case.title}'", cid, org_id, html_icon="fa-solid fa-sitemap", current_user=current_user)
|
||||
|
||||
CommonModel.update_last_modif(cid)
|
||||
db.session.commit()
|
||||
case = CommonModel.get_case(cid)
|
||||
CommonModel.save_history(case.uuid, current_user, f"Org {org_id} added")
|
||||
return True
|
||||
|
||||
|
||||
def remove_org_case(case_id, org_id, current_user):
|
||||
"""Remove an org from a case"""
|
||||
case_org = Case_Org.query.filter_by(case_id=case_id, org_id=org_id).first()
|
||||
if case_org:
|
||||
db.session.delete(case_org)
|
||||
|
||||
case = CommonModel.get_case(case_id)
|
||||
NotifModel.create_notification_org(f"{CommonModel.get_org(org_id).name} removed from case: '{case.id}-{case.title}'", case_id, org_id, html_icon="fa-solid fa-door-open", current_user=current_user)
|
||||
|
||||
CommonModel.update_last_modif(case_id)
|
||||
db.session.commit()
|
||||
case = CommonModel.get_case(case_id)
|
||||
CommonModel.save_history(case.uuid, current_user, f"Org {org_id} removed")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_present_in_case(case_id, user):
|
||||
"""Return if current user is present in a case"""
|
||||
orgs_in_case = CommonModel.get_orgs_in_case(case_id)
|
||||
|
||||
present_in_case = False
|
||||
for org in orgs_in_case:
|
||||
if org.id == user.org_id:
|
||||
present_in_case = True
|
||||
|
||||
return present_in_case
|
||||
|
||||
|
||||
def change_status_core(status, case, current_user):
|
||||
case.status_id = status
|
||||
CommonModel.update_last_modif(case.id)
|
||||
db.session.commit()
|
||||
CommonModel.save_history(case.uuid, current_user, "Case Status changed")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def regroup_case_info(cases, user, nb_pages=None):
|
||||
loc = dict()
|
||||
loc["cases"] = list()
|
||||
|
||||
for case in cases:
|
||||
present_in_case = get_present_in_case(case.id, user)
|
||||
case_loc = case.to_json()
|
||||
case_loc["present_in_case"] = present_in_case
|
||||
case_loc["current_user_permission"] = CommonModel.get_role(user).to_json()
|
||||
|
||||
loc["cases"].append(case_loc)
|
||||
if nb_pages:
|
||||
loc["nb_pages"] = nb_pages
|
||||
else:
|
||||
try:
|
||||
loc["nb_pages"] = cases.pages
|
||||
except:
|
||||
pass
|
||||
|
||||
return loc
|
||||
|
||||
|
||||
def build_case_query(page, completed, tags=None, taxonomies=None, galaxies=None, clusters=None, filter=None):
|
||||
query = Case.query
|
||||
conditions = [Case.completed == completed]
|
||||
|
||||
if tags or taxonomies:
|
||||
query = query.join(Case_Tags, Case_Tags.case_id == Case.id)
|
||||
query = query.join(Tags, Case_Tags.tag_id == Tags.id)
|
||||
if tags:
|
||||
tags = ast.literal_eval(tags)
|
||||
conditions.append(Tags.name.in_(list(tags)))
|
||||
|
||||
if taxonomies:
|
||||
taxonomies = ast.literal_eval(taxonomies)
|
||||
query = query.join(Taxonomy, Taxonomy.id == Tags.taxonomy_id)
|
||||
conditions.append(Taxonomy.name.in_(list(taxonomies)))
|
||||
|
||||
if clusters or galaxies:
|
||||
query = query.join(Case_Galaxy_Tags, Case_Galaxy_Tags.case_id == Case.id)
|
||||
query = query.join(Cluster, Case_Galaxy_Tags.cluster_id == Cluster.id)
|
||||
if clusters:
|
||||
clusters = ast.literal_eval(clusters)
|
||||
conditions.append(Cluster.name.in_(list(clusters)))
|
||||
|
||||
if galaxies:
|
||||
galaxies = ast.literal_eval(galaxies)
|
||||
query = query.join(Galaxy, Galaxy.id == Cluster.galaxy_id)
|
||||
conditions.append(Galaxy.name.in_(list(galaxies)))
|
||||
|
||||
if filter:
|
||||
query.order_by(desc(filter))
|
||||
|
||||
return query.filter(and_(*conditions)).paginate(page=page, per_page=25, max_per_page=50)
|
||||
|
||||
|
||||
def sort_by_status(page, taxonomies=[], galaxies=[], tags=[], clusters=[], or_and_taxo="true", or_and_galaxies="true", completed=False):
|
||||
cases = build_case_query(page, completed, tags, taxonomies, galaxies, clusters)
|
||||
|
||||
if tags:
|
||||
tags = ast.literal_eval(tags)
|
||||
if taxonomies:
|
||||
taxonomies = ast.literal_eval(taxonomies)
|
||||
|
||||
if galaxies:
|
||||
galaxies = ast.literal_eval(galaxies)
|
||||
if clusters:
|
||||
clusters = ast.literal_eval(clusters)
|
||||
|
||||
if tags or taxonomies or galaxies or clusters:
|
||||
if or_and_taxo == "false":
|
||||
glob_list = []
|
||||
|
||||
for case in cases:
|
||||
tags_db = case.to_json()["tags"]
|
||||
loc_tag = [tag["name"] for tag in tags_db]
|
||||
taxo_list = [Taxonomy.query.get(tag["taxonomy_id"]).name for tag in tags_db]
|
||||
|
||||
if (not tags or all(item in loc_tag for item in tags)) and \
|
||||
(not taxonomies or all(item in taxo_list for item in taxonomies)):
|
||||
glob_list.append(case)
|
||||
|
||||
cases = glob_list
|
||||
if or_and_galaxies == "false":
|
||||
glob_list = []
|
||||
|
||||
for case in cases:
|
||||
clusters_db = case.to_json()["clusters"]
|
||||
loc_cluster = [cluster["name"] for cluster in clusters_db]
|
||||
galaxies_list = [Galaxy.query.get(cluster["galaxy_id"]).name for cluster in clusters_db]
|
||||
|
||||
if (not clusters or all(item in loc_cluster for item in clusters)) and \
|
||||
(not galaxies or all(item in galaxies_list for item in galaxies)):
|
||||
glob_list.append(case)
|
||||
|
||||
cases = glob_list
|
||||
else:
|
||||
cases = Case.query.filter_by(completed=completed).paginate(page=page, per_page=25, max_per_page=50)
|
||||
return cases
|
||||
|
||||
|
||||
def sort_by_filter(filter, page, taxonomies=[], galaxies=[], tags=[], clusters=[], or_and_taxo="true", or_and_galaxies="true", completed=False):
|
||||
cases = build_case_query(page, completed, tags, taxonomies, galaxies, clusters, filter)
|
||||
nb_pages = cases.pages
|
||||
if tags:
|
||||
tags = ast.literal_eval(tags)
|
||||
if taxonomies:
|
||||
taxonomies = ast.literal_eval(taxonomies)
|
||||
|
||||
if galaxies:
|
||||
galaxies = ast.literal_eval(galaxies)
|
||||
if clusters:
|
||||
clusters = ast.literal_eval(clusters)
|
||||
|
||||
if tags or taxonomies or galaxies or clusters:
|
||||
if or_and_taxo == "false":
|
||||
glob_list = []
|
||||
|
||||
for case in cases:
|
||||
tags_db = case.to_json()["tags"]
|
||||
loc_tag = [tag["name"] for tag in tags_db]
|
||||
taxo_list = [Taxonomy.query.get(tag["taxonomy_id"]).name for tag in tags_db]
|
||||
|
||||
if (not tags or all(item in loc_tag for item in tags)) and \
|
||||
(not taxonomies or all(item in taxo_list for item in taxonomies)):
|
||||
glob_list.append(case)
|
||||
|
||||
cases = glob_list
|
||||
if or_and_galaxies == "false":
|
||||
glob_list = []
|
||||
|
||||
for case in cases:
|
||||
clusters_db = case.to_json()["clusters"]
|
||||
loc_cluster = [cluster["name"] for cluster in clusters_db]
|
||||
galaxies_list = [Galaxy.query.get(cluster["galaxy_id"]).name for cluster in clusters_db]
|
||||
|
||||
if (not clusters or all(item in loc_cluster for item in clusters)) and \
|
||||
(not galaxies or all(item in galaxies_list for item in galaxies)):
|
||||
glob_list.append(case)
|
||||
|
||||
cases = glob_list
|
||||
else:
|
||||
cases = Case.query.filter_by(completed=completed).order_by(desc(filter)).paginate(page=page, per_page=25, max_per_page=50)
|
||||
nb_pages = cases.pages
|
||||
|
||||
# for deadline filter, only case with a deadline defined is required
|
||||
loc = list()
|
||||
for case in cases:
|
||||
if getattr(case, filter):
|
||||
loc.append(case)
|
||||
return loc, nb_pages
|
||||
|
||||
|
||||
def fork_case_core(cid, case_title_fork, user):
|
||||
case_title_stored = CommonModel.get_case_by_title(case_title_fork)
|
||||
if case_title_stored:
|
||||
return {"message": "Error, title already exist"}
|
||||
case = CommonModel.get_case(cid)
|
||||
|
||||
case_json = case.to_json()
|
||||
case_json["title"] = case_title_fork
|
||||
|
||||
if case.deadline:
|
||||
case_json["deadline_date"] = datetime.datetime.strptime(case_json["deadline"].split(" ")[0], "%Y-%m-%d").date()
|
||||
case_json["deadline_time"] = datetime.datetime.strptime(case_json["deadline"].split(" ")[1], "%H:%M").time()
|
||||
else:
|
||||
case_json["deadline_date"] = None
|
||||
case_json["deadline_time"] = None
|
||||
|
||||
new_case = create_case(case_json, user)
|
||||
|
||||
for task in case.tasks:
|
||||
task_json = task.to_json()
|
||||
if task.deadline:
|
||||
task_json["deadline_date"] = datetime.datetime.strptime(task_json["deadline"].split(" ")[0], "%Y-%m-%d").date()
|
||||
task_json["deadline_time"] = datetime.datetime.strptime(task_json["deadline"].split(" ")[1], "%H:%M").time()
|
||||
else:
|
||||
task_json["deadline_date"] = None
|
||||
task_json["deadline_time"] = None
|
||||
|
||||
TaskModel.create_task(task_json, new_case.id, user)
|
||||
return new_case
|
||||
|
||||
|
||||
def create_template_from_case(cid, case_title_template):
|
||||
if Case_Template.query.filter_by(title=case_title_template).first():
|
||||
return {"message": "Error, title already exist"}
|
||||
|
||||
case = CommonModel.get_case(cid)
|
||||
new_template = Case_Template(
|
||||
uuid=str(uuid.uuid4()),
|
||||
title=case_title_template,
|
||||
description=case.description
|
||||
)
|
||||
db.session.add(new_template)
|
||||
db.session.commit()
|
||||
|
||||
for c_t in Case_Tags.query.filter_by(case_id=case.id).all():
|
||||
case_tag = Case_Template_Tags(
|
||||
case_id=new_template.id,
|
||||
tag_id=c_t.tag_id
|
||||
)
|
||||
db.session.add(case_tag)
|
||||
db.session.commit()
|
||||
|
||||
for c_t in Case_Galaxy_Tags.query.filter_by(case_id=case.id).all():
|
||||
case_cluster = Case_Template_Galaxy_Tags(
|
||||
template_id=new_template.id,
|
||||
cluster_id=c_t.cluster_id
|
||||
)
|
||||
db.session.add(case_cluster)
|
||||
db.session.commit()
|
||||
|
||||
for task in case.tasks:
|
||||
task_exist = Task_Template.query.filter_by(title=task.title).first()
|
||||
if not task_exist:
|
||||
task_template = Task_Template(
|
||||
uuid=str(uuid.uuid4()),
|
||||
title=task.title,
|
||||
description=task.description,
|
||||
url=task.url
|
||||
)
|
||||
db.session.add(task_template)
|
||||
db.session.commit()
|
||||
|
||||
## Tags
|
||||
for t_t in Task_Tags.query.filter_by(task_id=task.id).all():
|
||||
task_tag = Task_Template_Tags(
|
||||
task_id=task_template.id,
|
||||
tag_id=t_t.tag_id
|
||||
)
|
||||
db.session.add(task_tag)
|
||||
db.session.commit()
|
||||
|
||||
## Clusters
|
||||
for t_t in Task_Galaxy_Tags.query.filter_by(task_id=task.id).all():
|
||||
task_cluster = Task_Template_Galaxy_Tags(
|
||||
template_id=task_template.id,
|
||||
cluster_id=t_t.cluster_id
|
||||
)
|
||||
db.session.add(task_cluster)
|
||||
db.session.commit()
|
||||
|
||||
## Task Connectors
|
||||
for t_c in Task_Connector_Instance.query.filter_by(task_id=task.id).all():
|
||||
task_connector = Task_Template_Connector_Instance(
|
||||
template_id=task_template.id,
|
||||
instance_id=t_c.instance_id
|
||||
)
|
||||
db.session.add(task_connector)
|
||||
db.session.commit()
|
||||
|
||||
case_task_template = Case_Task_Template(
|
||||
case_id=new_template.id,
|
||||
task_id=task_template.id
|
||||
)
|
||||
db.session.add(case_task_template)
|
||||
db.session.commit()
|
||||
else:
|
||||
case_task_template = Case_Task_Template(
|
||||
case_id=new_template.id,
|
||||
task_id=task_exist.id
|
||||
)
|
||||
db.session.add(case_task_template)
|
||||
db.session.commit()
|
||||
|
||||
return new_template
|
||||
|
||||
|
||||
def change_recurring(form_dict, cid, current_user):
|
||||
case = CommonModel.get_case(cid)
|
||||
recurring_status = Status.query.filter_by(name="Recurring").first()
|
||||
created_status = Status.query.filter_by(name="Created").first()
|
||||
|
||||
if "once" in form_dict and form_dict["once"]:
|
||||
case.recurring_type = "once"
|
||||
case.recurring_date = form_dict["once"]
|
||||
case.status_id = recurring_status.id
|
||||
elif "daily" in form_dict and form_dict["daily"]:
|
||||
case.recurring_type = "daily"
|
||||
case.recurring_date = datetime.datetime.today() + datetime.timedelta(days=1)
|
||||
case.status_id = recurring_status.id
|
||||
elif "weekly" in form_dict and form_dict["weekly"]:
|
||||
case.recurring_type = "weekly"
|
||||
if form_dict["weekly"]<datetime.datetime.today().date():
|
||||
case.recurring_date = datetime.datetime.today() + datetime.timedelta(
|
||||
days=(form_dict["weekly"].weekday() - datetime.datetime.today().weekday() + 7)
|
||||
)
|
||||
else:
|
||||
case.recurring_date = form_dict["weekly"]
|
||||
case.status_id = recurring_status.id
|
||||
elif "monthly" in form_dict and form_dict["monthly"]:
|
||||
case.recurring_type = "monthly"
|
||||
if form_dict["monthly"]<datetime.datetime.today().date():
|
||||
case.recurring_date = form_dict["monthly"] + relativedelta.relativedelta(months=1)
|
||||
else:
|
||||
case.recurring_date = form_dict["monthly"]
|
||||
case.status_id = recurring_status.id
|
||||
elif "remove" in form_dict and form_dict["remove"]:
|
||||
case.recurring_type = None
|
||||
case.recurring_date = None
|
||||
case.status_id = created_status.id
|
||||
|
||||
for notif in Recurring_Notification.query.filter_by(case_id=cid).all():
|
||||
db.session.delete(notif)
|
||||
db.session.commit()
|
||||
else:
|
||||
return False
|
||||
|
||||
db.session.commit()
|
||||
CommonModel.save_history(case.uuid, current_user, "Recurring changed")
|
||||
return True
|
||||
|
||||
|
||||
def notify_user(task, user_id):
|
||||
case = CommonModel.get_case(task.case_id)
|
||||
message = f"Notify for task '{task.id}-{task.title}' of case '{case.id}-{case.title}'"
|
||||
NotifModel.create_notification_user(message, task.case_id, user_id=user_id, html_icon="fa-solid fa-bell")
|
||||
return True
|
||||
|
||||
|
||||
def notify_user_recurring(form_dict, case_id, orgs):
|
||||
for org in orgs:
|
||||
if f"check_{org.id}" in form_dict:
|
||||
for user in org.users:
|
||||
if not Recurring_Notification.query.filter_by(case_id=case_id, user_id=user.id).first():
|
||||
rec_notif = Recurring_Notification(case_id=case_id, user_id=user.id)
|
||||
db.session.add(rec_notif)
|
||||
db.session.commit()
|
||||
else:
|
||||
for user in org.users:
|
||||
if f"check_{org.id}_user_{user.id}" in form_dict:
|
||||
if not Recurring_Notification.query.filter_by(case_id=case_id, user_id=user.id).first():
|
||||
rec_notif = Recurring_Notification(case_id=case_id, user_id=user.id)
|
||||
db.session.add(rec_notif)
|
||||
db.session.commit()
|
||||
else:
|
||||
notif = Recurring_Notification.query.filter_by(case_id=case_id, user_id=user.id).first()
|
||||
if notif:
|
||||
db.session.delete(notif)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def get_modules():
|
||||
return MODULES_CONFIG
|
||||
|
||||
def get_instance_module_core(module, user_id):
|
||||
connector = CommonModel.get_connector_by_name(MODULES_CONFIG[module]["config"]["connector"])
|
||||
instance_list = list()
|
||||
for instance in connector.instances:
|
||||
if CommonModel.get_user_instance_both(user_id=user_id, instance_id=instance.id):
|
||||
instance_list.append(instance.to_json())
|
||||
return instance_list
|
|
@ -0,0 +1,160 @@
|
|||
from ..db_class.db import Case, User
|
||||
from datetime import datetime
|
||||
from . import common_core as CommonModel
|
||||
from ..utils.utils import check_tag
|
||||
|
||||
|
||||
def get_user_api(api_key):
|
||||
return User.query.filter_by(api_key=api_key).first()
|
||||
|
||||
|
||||
def verif_set_recurring(data_dict):
|
||||
if "once" in data_dict:
|
||||
try:
|
||||
data_dict["once"] = datetime.strptime(data_dict["once"], '%Y-%m-%d')
|
||||
except:
|
||||
return {"message": "once date bad format, YYYY-mm-dd"}
|
||||
if "weekly" in data_dict:
|
||||
try:
|
||||
data_dict["weekly"] = datetime.strptime(data_dict["weekly"], '%Y-%m-%d').date()
|
||||
except:
|
||||
return {"message": "weekly date bad format, YYYY-mm-dd"}
|
||||
if "monthly" in data_dict:
|
||||
try:
|
||||
data_dict["monthly"] = datetime.strptime(data_dict["monthly"], '%Y-%m-%d').date()
|
||||
except:
|
||||
return {"message": "monthly date bad format, YYYY-mm-dd"}
|
||||
return data_dict
|
||||
|
||||
|
||||
def verif_create_case_task(data_dict, isCase):
|
||||
if "title" not in data_dict or not data_dict["title"]:
|
||||
return {"message": "Please give a title"}
|
||||
elif Case.query.filter_by(title=data_dict["title"]).first():
|
||||
return {"message": "Title already exist"}
|
||||
|
||||
if "description" not in data_dict or not data_dict["description"]:
|
||||
data_dict["description"] = ""
|
||||
|
||||
if "deadline_date" in data_dict:
|
||||
try:
|
||||
data_dict["deadline_date"] = datetime.strptime(data_dict["deadline_date"], '%Y-%m-%d')
|
||||
except:
|
||||
return {"message": "deadline_date bad format"}
|
||||
else:
|
||||
data_dict["deadline_date"] = ""
|
||||
|
||||
if "deadline_time" in data_dict:
|
||||
try:
|
||||
data_dict["deadline_time"] = datetime.strptime(data_dict["deadline_time"], '%H-%M')
|
||||
except:
|
||||
return {"message": "deadline_time bad format"}
|
||||
else:
|
||||
data_dict["deadline_time"] = ""
|
||||
|
||||
if "tags" in data_dict:
|
||||
for tag in data_dict["tags"]:
|
||||
if not check_tag(tag):
|
||||
return {"message": f"Tag '{tag}' doesn't exist"}
|
||||
else:
|
||||
data_dict["tags"] = []
|
||||
|
||||
if "clusters" in data_dict:
|
||||
for cluster in data_dict["clusters"]:
|
||||
if not CommonModel.check_cluster(cluster):
|
||||
return {"message": f"Cluster '{cluster}' doesn't exist"}
|
||||
else:
|
||||
data_dict["clusters"] = []
|
||||
|
||||
if not isCase:
|
||||
if "url" not in data_dict or not data_dict["url"]:
|
||||
data_dict["url"] = ""
|
||||
|
||||
if "connectors" in data_dict:
|
||||
for connector in data_dict["connectors"]:
|
||||
if not CommonModel.check_connector(connector):
|
||||
return {"message": f"Connector '{connector}' doesn't exist"}
|
||||
else:
|
||||
data_dict["connectors"] = []
|
||||
|
||||
return data_dict
|
||||
|
||||
|
||||
|
||||
def common_verif(data_dict, case_task):
|
||||
if "description" not in data_dict or not data_dict["description"]:
|
||||
data_dict["description"] = case_task.description
|
||||
|
||||
if "deadline_date" in data_dict:
|
||||
try:
|
||||
data_dict["deadline_date"] = datetime.strptime(data_dict["deadline_date"], '%Y-%m-%d')
|
||||
except:
|
||||
return {"message": "date bad format"}
|
||||
elif case_task.deadline:
|
||||
data_dict["deadline_date"] = case_task.deadline.strftime('%Y-%m-%d')
|
||||
else:
|
||||
data_dict["deadline_date"] = ""
|
||||
|
||||
if "deadline_time" in data_dict:
|
||||
try:
|
||||
data_dict["deadline_time"] = datetime.strptime(data_dict["deadline_time"], '%H-%M')
|
||||
except:
|
||||
return {"message": "time bad format"}
|
||||
elif case_task.deadline:
|
||||
data_dict["deadline_time"] = case_task.deadline.strftime('%H-%M')
|
||||
else:
|
||||
data_dict["deadline_time"] = ""
|
||||
|
||||
if "tags" in data_dict:
|
||||
for tag in data_dict["tags"]:
|
||||
if not check_tag(tag):
|
||||
return {"message": f"Tag '{tag}' doesn't exist"}
|
||||
elif case_task.to_json()["tags"]:
|
||||
data_dict["tags"] = case_task.to_json()["tags"]
|
||||
else:
|
||||
data_dict["tags"] = []
|
||||
|
||||
if "clusters" in data_dict:
|
||||
for cluster in data_dict["clusters"]:
|
||||
if not CommonModel.check_cluster(cluster):
|
||||
return {"message": f"Cluster '{cluster}' doesn't exist"}
|
||||
elif case_task.to_json()["clusters"]:
|
||||
data_dict["clusters"] = case_task.to_json()["clusters"]
|
||||
else:
|
||||
data_dict["clusters"] = []
|
||||
|
||||
return data_dict
|
||||
|
||||
|
||||
def verif_edit_case(data_dict, case_id):
|
||||
case = CommonModel.get_case(case_id)
|
||||
if "title" not in data_dict or data_dict["title"] == case.title or not data_dict["title"]:
|
||||
data_dict["title"] = case.title
|
||||
elif Case.query.filter_by(title=data_dict["title"]).first():
|
||||
return {"message": "Title already exist"}
|
||||
|
||||
data_dict = common_verif(data_dict, case)
|
||||
|
||||
return data_dict
|
||||
|
||||
|
||||
def verif_edit_task(data_dict, task_id):
|
||||
task = CommonModel.get_task(task_id)
|
||||
if "title" not in data_dict or data_dict["title"] == task.title or not data_dict["title"]:
|
||||
data_dict["title"] = task.title
|
||||
|
||||
data_dict = common_verif(data_dict, task)
|
||||
|
||||
if "url" not in data_dict or not data_dict["url"]:
|
||||
data_dict["url"] = task.url
|
||||
|
||||
if "connectors" in data_dict:
|
||||
for connector in data_dict["connectors"]:
|
||||
if not CommonModel.check_connector(connector):
|
||||
return {"message": f"connector '{connector}' doesn't exist"}
|
||||
elif task.to_json()["connectors"]:
|
||||
data_dict["connectors"] = task.to_json()["connectors"]
|
||||
else:
|
||||
data_dict["connectors"] = []
|
||||
|
||||
return data_dict
|
|
@ -0,0 +1,251 @@
|
|||
import os
|
||||
import shutil
|
||||
import datetime
|
||||
|
||||
from flask import flash
|
||||
from .. import db
|
||||
from ..db_class.db import *
|
||||
from ..utils.utils import isUUID, create_specific_dir
|
||||
from sqlalchemy import desc, func
|
||||
from ..utils import utils
|
||||
|
||||
UPLOAD_FOLDER = os.path.join(os.getcwd(), "uploads")
|
||||
TEMP_FOLDER = os.path.join(os.getcwd(), "temp")
|
||||
HISTORY_FOLDER = os.path.join(os.getcwd(), "history")
|
||||
|
||||
|
||||
def get_case(cid):
|
||||
"""Return a case by is id"""
|
||||
if isUUID(cid):
|
||||
case = Case.query.filter_by(uuid=cid).first()
|
||||
elif str(cid).isdigit():
|
||||
case = Case.query.get(cid)
|
||||
else:
|
||||
case = None
|
||||
return case
|
||||
|
||||
def get_task(tid):
|
||||
"""Return a task by is id"""
|
||||
if isUUID(tid):
|
||||
case = Task.query.filter_by(uuid=tid).first()
|
||||
elif str(tid).isdigit():
|
||||
case = Task.query.get(tid)
|
||||
else:
|
||||
case = None
|
||||
return case
|
||||
|
||||
def get_all_cases():
|
||||
"""Return all cases"""
|
||||
return Case.query.filter_by(completed=False).order_by(desc(Case.last_modif))
|
||||
|
||||
def get_case_by_completed(completed):
|
||||
return Case.query.filter_by(completed=completed)
|
||||
|
||||
def get_case_by_title(title):
|
||||
return Case.query.where(func.lower(Case.title)==func.lower(title)).first()
|
||||
|
||||
def get_case_template_by_title(title):
|
||||
return Case_Template.query.filter_by(title=title).first()
|
||||
|
||||
|
||||
def get_task_templates():
|
||||
return Task_Template.query.all()
|
||||
|
||||
def search(text):
|
||||
"""Return cases containing text"""
|
||||
return Case.query.where(Case.title.contains(text), Case.completed==False).paginate(page=1, per_page=30, max_per_page=50)
|
||||
|
||||
|
||||
def get_all_users_core(case):
|
||||
return Org.query.join(Case_Org, Case_Org.case_id==case.id).where(Case_Org.org_id==Org.id).all()
|
||||
|
||||
|
||||
def get_role(user):
|
||||
"""Return role for the current user"""
|
||||
return Role.query.get(user.role_id)
|
||||
|
||||
|
||||
def get_org(oid):
|
||||
"""Return an org by is id"""
|
||||
return Org.query.get(oid)
|
||||
|
||||
def get_org_by_name(name):
|
||||
"""Return an org by is name"""
|
||||
return Org.query.filter_by(name=name).first()
|
||||
|
||||
def get_org_order_by_name():
|
||||
"""Return all orgs order by name"""
|
||||
return Org.query.order_by("name")
|
||||
|
||||
def get_org_in_case(org_id, case_id):
|
||||
return Case_Org.query.filter_by(case_id=case_id, org_id=org_id).first()
|
||||
|
||||
def get_org_in_case_by_case_id(case_id):
|
||||
return Case_Org.query.filter_by(case_id=case_id).all()
|
||||
|
||||
def get_orgs_in_case(case_id):
|
||||
"""Return orgs present in a case"""
|
||||
case_org = Case_Org.query.filter_by(case_id=case_id).all()
|
||||
return [Org.query.get(c_o.org_id) for c_o in case_org ]
|
||||
|
||||
|
||||
def get_file(fid):
|
||||
return File.query.get(fid)
|
||||
|
||||
def get_all_status():
|
||||
return Status.query.all()
|
||||
|
||||
def get_status(sid):
|
||||
return Status.query.get(sid).first()
|
||||
|
||||
|
||||
def get_recu_notif_user(case_id, user_id):
|
||||
return Recurring_Notification.query.filter_by(case_id=case_id, user_id=user_id).first()
|
||||
|
||||
|
||||
def get_taxonomies():
|
||||
return [taxo.to_json() for taxo in Taxonomy.query.filter_by(exclude=False).all()]
|
||||
|
||||
def get_galaxy(galaxy_id):
|
||||
return Galaxy.query.get(galaxy_id)
|
||||
|
||||
def get_galaxies():
|
||||
return [galax.to_json() for galax in Galaxy.query.filter_by(exclude=False).order_by('name').all()]
|
||||
|
||||
def get_clusters():
|
||||
return [cluster.to_json() for cluster in Cluster.query.all()]
|
||||
|
||||
def get_cluster_by_name(cluster):
|
||||
return Cluster.query.filter_by(name=cluster).first()
|
||||
|
||||
def get_clusters_galaxy(galaxies):
|
||||
out = dict()
|
||||
for galaxy in galaxies:
|
||||
out[galaxy] = [cluster.to_json() for cluster in Cluster.query.join(Galaxy, Galaxy.id==Cluster.galaxy_id).where(Galaxy.name==galaxy).all() if not cluster.exclude]
|
||||
return out
|
||||
|
||||
|
||||
def get_tags(taxos):
|
||||
out = dict()
|
||||
for taxo in taxos:
|
||||
out[taxo] = [tag.to_json() for tag in Taxonomy.query.filter_by(name=taxo).first().tags if not tag.exclude]
|
||||
return out
|
||||
|
||||
def get_tag(tag):
|
||||
return Tags.query.filter_by(name=tag).first()
|
||||
|
||||
|
||||
def get_case_tags(cid):
|
||||
return [tag.name for tag in Tags.query.join(Case_Tags, Case_Tags.tag_id==Tags.id).filter_by(case_id=cid).all()]
|
||||
|
||||
def get_case_clusters(cid):
|
||||
return [cluster for cluster in Cluster.query.join(Case_Galaxy_Tags, Case_Galaxy_Tags.cluster_id==Cluster.id).filter_by(case_id=cid).all()]
|
||||
|
||||
def get_task_tags(tid):
|
||||
return [tag.name for tag in Tags.query.join(Task_Tags, Task_Tags.tag_id==Tags.id).filter_by(task_id=tid).all()]
|
||||
|
||||
def get_task_clusters(tid):
|
||||
return [cluster for cluster in Cluster.query.join(Task_Galaxy_Tags, Task_Galaxy_Tags.cluster_id==Cluster.id).filter_by(task_id=tid).all()]
|
||||
|
||||
|
||||
def get_connectors():
|
||||
return Connector.query.all()
|
||||
|
||||
def get_connector(cid):
|
||||
return Connector.query.get(cid)
|
||||
|
||||
def get_connector_by_name(name):
|
||||
return Connector.query.where(Connector.name.like(name)).first()
|
||||
|
||||
def get_instance(iid):
|
||||
return Connector_Instance.query.get(iid)
|
||||
|
||||
def get_instance_by_name(name):
|
||||
return Connector_Instance.query.filter_by(name=name).first()
|
||||
|
||||
def get_task_connectors(tid):
|
||||
return Task_Connector_Instance.query.filter_by(task_id=tid).all()
|
||||
|
||||
def get_user_instance_both(user_id, instance_id):
|
||||
return User_Connector_Instance.query.filter_by(user_id=user_id, instance_id=instance_id).all()
|
||||
|
||||
def get_user_instance_by_instance(instance_id):
|
||||
"""Return a user instance by instance id"""
|
||||
return User_Connector_Instance.query.filter_by(instance_id=instance_id).first()
|
||||
|
||||
def get_history(case_uuid):
|
||||
try:
|
||||
path_history = os.path.join(HISTORY_FOLDER, str(case_uuid))
|
||||
with open(path_history, "r") as read_file:
|
||||
loc_file = read_file.read().splitlines()
|
||||
return loc_file
|
||||
except:
|
||||
return False
|
||||
|
||||
def save_history(case_uuid, current_user, message):
|
||||
create_specific_dir(HISTORY_FOLDER)
|
||||
path_history = os.path.join(HISTORY_FOLDER, str(case_uuid))
|
||||
with open(path_history, "a") as write_history:
|
||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M')
|
||||
write_history.write(f"[{now}]({current_user.first_name} {current_user.last_name}): {message}\n")
|
||||
|
||||
|
||||
def update_last_modif(case_id):
|
||||
"""Update 'last_modif' of a case"""
|
||||
case = Case.query.get(case_id)
|
||||
case.last_modif = datetime.datetime.now(tz=datetime.timezone.utc)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def update_last_modif_task(task_id):
|
||||
"""Update 'last_modif' of a task"""
|
||||
if task_id:
|
||||
task = Task.query.get(task_id)
|
||||
task.last_modif = datetime.datetime.now(tz=datetime.timezone.utc)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def deadline_check(date, time):
|
||||
"""Combine the date and the time if time exist"""
|
||||
deadline = None
|
||||
if date and time:
|
||||
deadline = datetime.datetime.combine(date, time)
|
||||
elif date:
|
||||
deadline = date
|
||||
|
||||
return deadline
|
||||
|
||||
|
||||
def delete_temp_folder():
|
||||
shutil.rmtree(TEMP_FOLDER)
|
||||
|
||||
|
||||
def check_cluster_db(cluster):
|
||||
return Cluster.query.filter_by(name=cluster).first()
|
||||
|
||||
def check_tag(tag_list):
|
||||
flag = True
|
||||
for tag in tag_list:
|
||||
if not utils.check_tag(tag):
|
||||
flag = False
|
||||
if not flag:
|
||||
flash("tag doesn't exist")
|
||||
return flag
|
||||
|
||||
def check_cluster(cluster_list):
|
||||
flag = True
|
||||
for cluster in cluster_list:
|
||||
if not check_cluster_db(cluster):
|
||||
flag = False
|
||||
if not flag:
|
||||
flash("cluster doesn't exist")
|
||||
return flag
|
||||
|
||||
def check_connector(connector_list):
|
||||
flag = True
|
||||
for connector in connector_list:
|
||||
if not get_instance_by_name(connector):
|
||||
flag = False
|
||||
if not flag:
|
||||
flash("Connector doesn't exist")
|
||||
return flag
|
|
@ -0,0 +1,116 @@
|
|||
from flask_wtf import FlaskForm
|
||||
from wtforms import ValidationError
|
||||
from wtforms.fields import (
|
||||
StringField,
|
||||
SubmitField,
|
||||
SelectMultipleField,
|
||||
TextAreaField,
|
||||
DateField,
|
||||
TimeField,
|
||||
HiddenField,
|
||||
BooleanField
|
||||
)
|
||||
from wtforms.validators import InputRequired, Length, Optional
|
||||
|
||||
from ..db_class.db import Case, Case_Org
|
||||
|
||||
|
||||
class CaseForm(FlaskForm):
|
||||
title = StringField('Title', validators=[Optional(), Length(1, 64)])
|
||||
description = TextAreaField('Description', validators=[Optional()])
|
||||
deadline_date = DateField('deadline_date', validators=[Optional()])
|
||||
deadline_time = TimeField("deadline_time", validators=[Optional()])
|
||||
template_select = SelectMultipleField(u'Templates', coerce=int)
|
||||
title_template = StringField('Title Template', validators=[Optional(), Length(1, 64)])
|
||||
tasks_templates = SelectMultipleField(u'Tasks Templates', coerce=int)
|
||||
submit = SubmitField('Create')
|
||||
|
||||
def validate_title(self, field):
|
||||
if not field.data and 0 in self.template_select.data:
|
||||
raise ValidationError("Need to select a title or a template")
|
||||
if Case.query.filter_by(title=field.data).first():
|
||||
raise ValidationError("The title already exist")
|
||||
|
||||
def validate_template_select(self, field):
|
||||
if 0 in field.data and not self.title.data:
|
||||
raise ValidationError("Need to select a template or a title")
|
||||
if not 0 in field.data and not self.title_template.data:
|
||||
raise ValidationError("Need a title for the case")
|
||||
|
||||
def validate_deadline_time(self, field):
|
||||
if field.data and not self.deadline_date.data:
|
||||
raise ValidationError("Choose a date")
|
||||
|
||||
def validate_title_template(self, field):
|
||||
if field.data and not self.template_select.data or 0 in self.template_select.data:
|
||||
raise ValidationError("A template need to be selected")
|
||||
if Case.query.filter_by(title=field.data).first():
|
||||
raise ValidationError("The title already exist")
|
||||
|
||||
class CaseEditForm(FlaskForm):
|
||||
title = StringField('Title', validators=[InputRequired(), Length(1, 64)])
|
||||
description = TextAreaField('Description', validators=[Optional()])
|
||||
deadline_date = DateField('deadline_date', validators=[Optional()])
|
||||
deadline_time = TimeField("deadline_time", validators=[Optional()])
|
||||
submit = SubmitField('Save')
|
||||
|
||||
def validate_deadline_time(self, field):
|
||||
if field.data and not self.deadline_date.data:
|
||||
raise ValidationError("Choose a date")
|
||||
|
||||
|
||||
class TaskForm(FlaskForm):
|
||||
title = StringField('Title', validators=[Optional(), Length(1, 64)])
|
||||
description = TextAreaField('Description', validators=[Optional()])
|
||||
url = StringField('Tool/Link', validators=[Optional(), Length(0, 64)])
|
||||
deadline_date = DateField('deadline_date', validators=[Optional()])
|
||||
deadline_time = TimeField("deadline_time", validators=[Optional()])
|
||||
template_select = SelectMultipleField(u'Templates', coerce=int)
|
||||
submit = SubmitField('Create')
|
||||
|
||||
def validate_title(self, field):
|
||||
if not field.data and 0 in self.template_select.data:
|
||||
raise ValidationError("Need to select a title or a template")
|
||||
|
||||
def validate_template_select(self, field):
|
||||
if 0 in field.data and not self.title.data:
|
||||
raise ValidationError("Need to select a template or a title")
|
||||
|
||||
def validate_deadline_time(self, field):
|
||||
if field.data and not self.deadline_date.data:
|
||||
raise ValidationError("Choose a date")
|
||||
|
||||
|
||||
|
||||
class TaskEditForm(FlaskForm):
|
||||
title = StringField('Title', validators=[InputRequired(), Length(1, 64)])
|
||||
description = TextAreaField('Description', validators=[Optional()])
|
||||
url = StringField('Tool/Link', validators=[Optional(), Length(0, 64)])
|
||||
deadline_date = DateField('deadline_date', validators=[Optional()])
|
||||
deadline_time = TimeField("deadline_time", validators=[Optional()])
|
||||
submit = SubmitField('Save')
|
||||
|
||||
def validate_deadline_time(self, field):
|
||||
if field.data and not self.deadline_date.data:
|
||||
raise ValidationError("Choose a date")
|
||||
|
||||
|
||||
class AddOrgsCase(FlaskForm):
|
||||
org_id = SelectMultipleField(u'Orgs', coerce=int)
|
||||
case_id = HiddenField("")
|
||||
submit = SubmitField('Add')
|
||||
|
||||
def validate_org_id(self, field):
|
||||
for org in field.data:
|
||||
if Case_Org.query.filter_by(case_id = self.case_id.data, org_id=org).first():
|
||||
raise ValidationError(f"Org {org} already in case")
|
||||
|
||||
|
||||
class RecurringForm(FlaskForm):
|
||||
case_id = HiddenField("")
|
||||
once = DateField('One day', validators=[Optional()])
|
||||
daily = BooleanField('Daily', validators=[Optional()])
|
||||
weekly = DateField("Start date", validators=[Optional()])
|
||||
monthly = DateField("Start date", validators=[Optional()])
|
||||
remove = BooleanField('Remove', validators=[Optional()])
|
||||
submit = SubmitField('Save')
|
|
@ -0,0 +1,464 @@
|
|||
from flask import Blueprint, render_template, redirect, jsonify, request, flash
|
||||
from .form import TaskEditForm, TaskForm
|
||||
from flask_login import login_required, current_user
|
||||
from . import case_core as CaseModel
|
||||
from . import common_core as CommonModel
|
||||
from . import task_core as TaskModel
|
||||
from ..decorators import editor_required
|
||||
from ..utils.utils import form_to_dict
|
||||
|
||||
task_blueprint = Blueprint(
|
||||
'task',
|
||||
__name__,
|
||||
template_folder='templates',
|
||||
static_folder='static'
|
||||
)
|
||||
|
||||
|
||||
@task_blueprint.route("/<cid>/create_task", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def create_task(cid):
|
||||
"""View of a case"""
|
||||
if CommonModel.get_case(cid):
|
||||
present_in_case = CaseModel.get_present_in_case(cid, current_user)
|
||||
if present_in_case or current_user.is_admin():
|
||||
form = TaskForm()
|
||||
form.template_select.choices = [(template.id, template.title) for template in CommonModel.get_task_templates()]
|
||||
form.template_select.choices.insert(0, (0," "))
|
||||
|
||||
if form.validate_on_submit():
|
||||
tag_list = request.form.getlist("tags_select")
|
||||
cluster_list = request.form.getlist("clusters_select")
|
||||
connector_list = request.form.getlist("connectors_select")
|
||||
if CommonModel.check_tag(tag_list):
|
||||
if CommonModel.check_cluster(cluster_list):
|
||||
form_dict = form_to_dict(form)
|
||||
form_dict["tags"] = tag_list
|
||||
form_dict["clusters"] = cluster_list
|
||||
form_dict["connectors"] = connector_list
|
||||
if TaskModel.create_task(form_dict, cid, current_user):
|
||||
flash("Task created", "success")
|
||||
else:
|
||||
flash("Error Task Created", "error")
|
||||
return redirect(f"/case/{cid}")
|
||||
return render_template("case/create_task.html", form=form)
|
||||
return render_template("case/create_task.html", form=form)
|
||||
return render_template("case/create_task.html", form=form)
|
||||
return redirect(f"/case/{cid}")
|
||||
return render_template("404.html")
|
||||
|
||||
@task_blueprint.route("/<cid>/edit_task/<tid>", methods=['GET','POST'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def edit_task(cid, tid):
|
||||
"""Edit the task"""
|
||||
if CommonModel.get_case(cid):
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
form = TaskEditForm()
|
||||
|
||||
if form.validate_on_submit():
|
||||
tag_list = request.form.getlist("tags_select")
|
||||
cluster_list = request.form.getlist("clusters_select")
|
||||
connector_list = request.form.getlist("connectors_select")
|
||||
if CommonModel.check_tag(tag_list):
|
||||
if CommonModel.check_cluster(cluster_list):
|
||||
form_dict = form_to_dict(form)
|
||||
form_dict["tags"] = tag_list
|
||||
form_dict["clusters"] = cluster_list
|
||||
form_dict["connectors"] = connector_list
|
||||
TaskModel.edit_task_core(form_dict, tid, current_user)
|
||||
flash("Task edited", "success")
|
||||
return redirect(f"/case/{cid}")
|
||||
return render_template("case/create_task.html", form=form)
|
||||
return render_template("case/create_task.html", form=form)
|
||||
else:
|
||||
task_modif = CommonModel.get_task(tid)
|
||||
form.description.data = task_modif.description
|
||||
form.title.data = task_modif.title
|
||||
form.url.data = task_modif.url
|
||||
form.deadline_date.data = task_modif.deadline
|
||||
form.deadline_time.data = task_modif.deadline
|
||||
|
||||
return render_template("case/edit_task.html", form=form)
|
||||
else:
|
||||
flash("Access denied", "error")
|
||||
return redirect(f"/case/{cid}")
|
||||
return render_template("404.html")
|
||||
|
||||
|
||||
@task_blueprint.route("/complete_task/<tid>", methods=['GET'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def complete_task(tid):
|
||||
"""Complete the task"""
|
||||
|
||||
task = CommonModel.get_task(str(tid))
|
||||
if task:
|
||||
if CaseModel.get_present_in_case(task.case_id, current_user) or current_user.is_admin():
|
||||
if TaskModel.complete_task(tid, current_user):
|
||||
return {"message": "Task completed", "toast_class": "success-subtle"}, 200
|
||||
return {"message": "Error task completed", "toast_class": "danger-subtle"}, 400
|
||||
return {"message": "Action not allowed", "toast_class": "warning-subtle"}, 401
|
||||
return {"message": "Task not found", "toast_class": "danger-subtle"}, 404
|
||||
|
||||
|
||||
|
||||
@task_blueprint.route("/<cid>/delete_task/<tid>", methods=['GET'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def delete_task(cid, tid):
|
||||
"""Delete the task"""
|
||||
if CommonModel.get_case(cid):
|
||||
if CommonModel.get_task(tid):
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
if TaskModel.delete_task(tid, current_user):
|
||||
return {"message": "Task deleted", "toast_class": "success-subtle"}, 200
|
||||
return {"message": "Error task deleted", "toast_class": "danger-subtle"}, 400
|
||||
return {"message": "Action not allowed", "toast_class": "warning-subtle"}, 401
|
||||
return {"message": "Task not found", "toast_class": "danger-subtle"}, 404
|
||||
return {"message": "Case not found", "toast_class": "danger-subtle"}, 404
|
||||
|
||||
|
||||
@task_blueprint.route("/<cid>/modif_note/<tid>", methods=['POST'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def modif_note(cid, tid):
|
||||
"""Modify note of the task"""
|
||||
if CommonModel.get_case(cid):
|
||||
if CommonModel.get_task(tid):
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
notes = request.json["notes"]
|
||||
if TaskModel.modif_note_core(tid, current_user, notes):
|
||||
return {"message": "Note added", "toast_class": "success-subtle"}, 200
|
||||
return {"message": "Error add/modify note", "toast_class": "danger-subtle"}, 400
|
||||
return {"message": "Action not allowed", "toast_class": "warning-subtle"}, 401
|
||||
return {"message": "Task not found", "toast_class": "danger-subtle"}, 404
|
||||
return {"message": "Case not found", "toast_class": "danger-subtle"}, 404
|
||||
|
||||
|
||||
@task_blueprint.route("/<cid>/get_note/<tid>", methods=['GET'])
|
||||
@editor_required
|
||||
def get_note(cid, tid):
|
||||
"""Get not of a task in text format"""
|
||||
if CommonModel.get_case(cid):
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
return {"note": task.notes}, 201
|
||||
return {"message": "Task not found", "toast_class": "danger-subtle"}, 404
|
||||
return {"message": "Case not found", "toast_class": "danger-subtle"}, 404
|
||||
|
||||
|
||||
@task_blueprint.route("/<cid>/take_task/<tid>", methods=['GET'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def take_task(cid, tid):
|
||||
"""Assign current user to the task"""
|
||||
if CommonModel.get_case(cid):
|
||||
if CommonModel.get_task(tid):
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
if TaskModel.assign_task(tid, user=current_user, current_user=current_user, flag_current_user=True):
|
||||
return {"message": "User Assigned", "toast_class": "success-subtle"}, 200
|
||||
return {"message": "Error assignment", "toast_class": "danger-subtle"}, 400
|
||||
return {"message": "Action not allowed", "toast_class": "warning-subtle"}, 401
|
||||
return {"message": "Task not found", "toast_class": "danger-subtle"}, 404
|
||||
return {"message": "Case not found", "toast_class": "danger-subtle"}, 404
|
||||
|
||||
|
||||
@task_blueprint.route("/<cid>/assign_users/<tid>", methods=['POST'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def assign_user(cid, tid):
|
||||
"""Assign a list of users to the task"""
|
||||
if CommonModel.get_case(cid):
|
||||
if "users_id" in request.json:
|
||||
users_list = request.json["users_id"]
|
||||
|
||||
if CommonModel.get_task(tid):
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
for user in users_list:
|
||||
TaskModel.assign_task(tid, user=user, current_user=current_user, flag_current_user=False)
|
||||
return {"message": "Users Assigned", "toast_class": "success-subtle"}, 200
|
||||
return {"message": "Action not allowed", "toast_class": "warning-subtle"}, 401
|
||||
return {"message": "Task not found", "toast_class": "danger-subtle"}, 404
|
||||
return {"message": "'users_id' is missing", "toast_class": "danger-subtle"}, 400
|
||||
return {"message": "Case not found", "toast_class": "danger-subtle"}, 404
|
||||
|
||||
|
||||
@task_blueprint.route("/<cid>/remove_assignment/<tid>", methods=['GET'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def remove_assign_task(cid, tid):
|
||||
"""Remove current user assignment to the task"""
|
||||
if CommonModel.get_case(cid):
|
||||
if CommonModel.get_task(tid):
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
if TaskModel.remove_assign_task(tid, user=current_user, current_user=current_user, flag_current_user=True):
|
||||
return {"message": "User Removed from assignment", "toast_class": "success-subtle"}, 200
|
||||
return {"message": "Error removed assignment", "toast_class": "danger-subtle"}, 400
|
||||
return {"message": "Action not allowed", "toast_class": "warning-subtle"}, 401
|
||||
return {"message": "Task not found", "toast_class": "danger-subtle"}, 404
|
||||
return {"message": "Case not found", "toast_class": "danger-subtle"}, 404
|
||||
|
||||
|
||||
@task_blueprint.route("/<cid>/remove_assigned_user/<tid>", methods=['POST'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def remove_assigned_user(cid, tid):
|
||||
"""Assign current user to the task"""
|
||||
if CommonModel.get_case(cid):
|
||||
if "user_id" in request.json:
|
||||
user_id = request.json["user_id"]
|
||||
if CommonModel.get_task(tid):
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
if TaskModel.remove_assign_task(tid, user=user_id, current_user=current_user, flag_current_user=False):
|
||||
return {"message": "User Removed from assignment", "toast_class": "success-subtle"}, 200
|
||||
return {"message": "Error removed assignment", "toast_class": "danger-subtle"}, 400
|
||||
return {"message": "Action not allowed", "toast_class": "warning-subtle"}, 401
|
||||
return {"message": "Task not found", "toast_class": "danger-subtle"}, 404
|
||||
return {"message": "'user_id' is missing", "toast_class": "danger-subtle"}, 400
|
||||
return {"message": "Case not found", "toast_class": "danger-subtle"}, 404
|
||||
|
||||
|
||||
@task_blueprint.route("/<cid>/change_task_status/<tid>", methods=['POST'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def change_task_status(cid, tid):
|
||||
"""Change the status of the task"""
|
||||
if CommonModel.get_case(cid):
|
||||
if "status" in request.json:
|
||||
status = request.json["status"]
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
if TaskModel.change_task_status(status, task, current_user):
|
||||
return {"message": "Status changed", "toast_class": "success-subtle"}, 200
|
||||
return {"message": "Error changed status", "toast_class": "danger-subtle"}, 400
|
||||
return {"message": "Action not allowed", "toast_class": "warning-subtle"}, 401
|
||||
return {"message": "Task not found", "toast_class": "danger-subtle"}, 404
|
||||
return {"message": "'status' is missing", "toast_class": "danger-subtle"}, 400
|
||||
return {"message": "Case not found", "toast_class": "danger-subtle"}, 404
|
||||
|
||||
|
||||
@task_blueprint.route("/task/<tid>/download_file/<fid>", methods=['GET'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def download_file(tid, fid):
|
||||
"""Download the file"""
|
||||
task = CommonModel.get_task(tid)
|
||||
file = CommonModel.get_file(fid)
|
||||
if file and file in task.files:
|
||||
if CaseModel.get_present_in_case(task.case_id, current_user) or current_user.is_admin():
|
||||
return TaskModel.download_file(file)
|
||||
return {"message": "Action not allowed", "toast_class": "warning-subtle"}, 401
|
||||
return {"message": "File not found", "toast_class": "danger-subtle"}, 404
|
||||
|
||||
|
||||
@task_blueprint.route("/task/<tid>/delete_file/<fid>", methods=['GET'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def delete_file(tid, fid):
|
||||
"""Delete the file"""
|
||||
task = CommonModel.get_task(tid)
|
||||
file = CommonModel.get_file(fid)
|
||||
if file and file in task.files:
|
||||
if CaseModel.get_present_in_case(task.case_id, current_user) or current_user.is_admin():
|
||||
if TaskModel.delete_file(file, task, current_user):
|
||||
return {"message": "File Deleted", "toast_class": "success-subtle"}, 200
|
||||
return {"message": "Error deleting file"}, 400
|
||||
return {"message": "Action not allowed", "toast_class": "warning-subtle"}, 401
|
||||
return {"message": "File not found", "toast_class": "danger-subtle"}, 404
|
||||
|
||||
|
||||
@task_blueprint.route("/<cid>/add_files/<tid>", methods=['POST'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def add_files(cid, tid):
|
||||
"""Add files to a task"""
|
||||
if CommonModel.get_case(cid):
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
if len(request.files) > 0:
|
||||
if TaskModel.add_file_core(task=task, files_list=request.files, current_user=current_user):
|
||||
return {"message":"Files added", "toast_class": "success-subtle"}, 200
|
||||
return {"message":"Something goes wrong adding files", "toast_class": "danger-subtle"}, 400
|
||||
return {"message":"No Files given", "toast_class": "warning-subtle"}, 400
|
||||
return {"message":"Task not found", "toast_class": "danger-subtle"}, 404
|
||||
return {"message":"Case not found", "toast_class": "danger-subtle"}, 404
|
||||
|
||||
|
||||
@task_blueprint.route("/<cid>/get_files/<tid>", methods=['GET'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def get_files(cid, tid):
|
||||
"""Get files of a task"""
|
||||
if CommonModel.get_case(cid):
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
file_list = [file.to_json() for file in task.files]
|
||||
return {"files": file_list}, 200
|
||||
return {"message":"Task not found", "toast_class": "danger-subtle"}, 404
|
||||
return {"message":"Case not found", "toast_class": "danger-subtle"}, 404
|
||||
|
||||
|
||||
@task_blueprint.route("/<cid>/sort_by_ongoing_task", methods=['GET'])
|
||||
@login_required
|
||||
def sort_by_ongoing_task(cid):
|
||||
"""Sort Task by living one"""
|
||||
case = CommonModel.get_case(cid)
|
||||
tags = request.args.get('tags')
|
||||
or_and_taxo = request.args.get("or_and_taxo")
|
||||
taxonomies = request.args.get('taxonomies')
|
||||
|
||||
galaxies = request.args.get('galaxies')
|
||||
clusters = request.args.get('clusters')
|
||||
or_and_galaxies = request.args.get("or_and_galaxies")
|
||||
|
||||
return TaskModel.sort_by_status_task_core(case, current_user, taxonomies, galaxies, tags, clusters, or_and_taxo, or_and_galaxies, completed=False)
|
||||
|
||||
|
||||
@task_blueprint.route("/<cid>/sort_by_finished_task", methods=['GET'])
|
||||
@login_required
|
||||
def sort_by_finished_task(cid):
|
||||
"""Sort task by finished one"""
|
||||
case = CommonModel.get_case(cid)
|
||||
tags = request.args.get('tags')
|
||||
or_and_taxo = request.args.get("or_and_taxo")
|
||||
taxonomies = request.args.get('taxonomies')
|
||||
|
||||
galaxies = request.args.get('galaxies')
|
||||
clusters = request.args.get('clusters')
|
||||
or_and_galaxies = request.args.get("or_and_galaxies")
|
||||
|
||||
return TaskModel.sort_by_status_task_core(case, current_user, taxonomies, galaxies, tags, clusters, or_and_taxo, or_and_galaxies, completed=True)
|
||||
|
||||
|
||||
@task_blueprint.route("/<cid>/tasks/ongoing", methods=['GET'])
|
||||
@login_required
|
||||
def ongoing_tasks_sort_by_filter(cid):
|
||||
"""Sort by filter for living task"""
|
||||
tags = request.args.get('tags')
|
||||
or_and_taxo = request.args.get("or_and_taxo")
|
||||
taxonomies = request.args.get('taxonomies')
|
||||
|
||||
galaxies = request.args.get('galaxies')
|
||||
clusters = request.args.get('clusters')
|
||||
or_and_galaxies = request.args.get("or_and_galaxies")
|
||||
filter = request.args.get('filter')
|
||||
|
||||
if filter:
|
||||
case = CommonModel.get_case(cid)
|
||||
return TaskModel.sort_tasks_by_filter(case, current_user, filter, taxonomies, galaxies, tags, clusters, or_and_taxo, or_and_galaxies, completed=False)
|
||||
return {"message": "No filter pass"}, 400
|
||||
|
||||
|
||||
@task_blueprint.route("/<cid>/tasks/finished", methods=['GET'])
|
||||
@login_required
|
||||
def finished_tasks_sort_by_filter(cid):
|
||||
"""Sort by filter for finished task"""
|
||||
tags = request.args.get('tags')
|
||||
or_and_taxo = request.args.get("or_and_taxo")
|
||||
taxonomies = request.args.get('taxonomies')
|
||||
filter = request.args.get('filter')
|
||||
|
||||
galaxies = request.args.get('galaxies')
|
||||
clusters = request.args.get('clusters')
|
||||
or_and_galaxies = request.args.get("or_and_galaxies")
|
||||
|
||||
if filter:
|
||||
case = CommonModel.get_case(cid)
|
||||
return TaskModel.sort_tasks_by_filter(case, current_user, filter, taxonomies, galaxies, tags, clusters, or_and_taxo, or_and_galaxies, completed=True)
|
||||
return {"message": "No filter pass"}, 400
|
||||
|
||||
|
||||
@task_blueprint.route("/<cid>/task/<tid>/notify_user", methods=['POST'])
|
||||
@login_required
|
||||
@editor_required
|
||||
def notify_user(cid, tid):
|
||||
"""Notify a user about a task"""
|
||||
if CommonModel.get_case(cid):
|
||||
if "user_id" in request.json:
|
||||
user = request.json["user_id"]
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
if CaseModel.get_present_in_case(cid, current_user) or current_user.is_admin():
|
||||
if CaseModel.notify_user(task, user):
|
||||
return {"message":"User notified", "toast_class": "success-subtle"}, 200
|
||||
return {"message":"Something goes wrong", "toast_class": "danger-subtle"}, 400
|
||||
return {"message":"Action not Allowed", "toast_class": "warning-subtle"}, 400
|
||||
return {"message":"Task not found", "toast_class": "danger-subtle"}, 404
|
||||
return {"message": "'user_id' is missing", "toast_class": "danger-subtle"}, 404
|
||||
return {"message": "Case not found", "toast_class": "danger-subtle"}, 404
|
||||
|
||||
|
||||
@task_blueprint.route("/<cid>/task/<tid>/export_notes", methods=['GET'])
|
||||
@login_required
|
||||
def export_notes(cid, tid):
|
||||
"""Export note of a task as pdf"""
|
||||
|
||||
case = CommonModel.get_case(cid)
|
||||
if case:
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
data_dict = dict(request.args)
|
||||
if "type" in data_dict:
|
||||
type_req = data_dict["type"]
|
||||
res = TaskModel.export_notes(task, type_req)
|
||||
CommonModel.delete_temp_folder()
|
||||
return res
|
||||
return {"message": "'type' is missing", 'toast_class': "warning-subtle"}, 400
|
||||
return {"message": "Task not found", 'toast_class': "danger-subtle"}, 404
|
||||
return {"message": "Case not found", 'toast_class': "danger-subtle"}, 404
|
||||
|
||||
|
||||
@task_blueprint.route("/get_taxonomies_task/<tid>", methods=['GET'])
|
||||
@login_required
|
||||
def get_taxonomies_case(tid):
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
tags = CommonModel.get_task_tags(task.id)
|
||||
taxonomies = []
|
||||
if tags:
|
||||
taxonomies = [tag.split(":")[0] for tag in tags]
|
||||
return {"tags": tags, "taxonomies": taxonomies}
|
||||
return {"message": "task Not found", 'toast_class': "danger-subtle"}, 404
|
||||
|
||||
@task_blueprint.route("/get_galaxies_task/<tid>", methods=['GET'])
|
||||
@login_required
|
||||
def get_galaxies_task(tid):
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
clusters = CommonModel.get_task_clusters(task.id)
|
||||
galaxies = []
|
||||
if clusters:
|
||||
for cluster in clusters:
|
||||
loc_g = CommonModel.get_galaxy(cluster.galaxy_id)
|
||||
if not loc_g.name in galaxies:
|
||||
galaxies.append(loc_g.name)
|
||||
index = clusters.index(cluster)
|
||||
clusters[index] = cluster.tag
|
||||
return {"clusters": clusters, "galaxies": galaxies}
|
||||
return {"message": "task Not found", 'toast_class': "danger-subtle"}, 404
|
||||
|
||||
|
||||
@task_blueprint.route("/get_connectors", methods=['GET'])
|
||||
@login_required
|
||||
def get_connectors():
|
||||
connectors_list = CommonModel.get_connectors()
|
||||
connectors_dict = dict()
|
||||
for connector in connectors_list:
|
||||
loc = list()
|
||||
for instance in connector.instances:
|
||||
if CommonModel.get_user_instance_both(user_id=current_user.id, instance_id=instance.id):
|
||||
loc.append(instance.to_json())
|
||||
if loc:
|
||||
connectors_dict[connector.name] = loc
|
||||
|
||||
return jsonify({"connectors": connectors_dict}), 200
|
||||
|
||||
@task_blueprint.route("/get_connectors_task/<tid>", methods=['GET'])
|
||||
@login_required
|
||||
def get_connectors_task(tid):
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
return {"connectors": [CommonModel.get_instance(task_instance.instance_id).name for task_instance in CommonModel.get_task_connectors(task.id) ]}
|
||||
return {"message": "task Not found", 'toast_class': "danger-subtle"}, 404
|
|
@ -0,0 +1,555 @@
|
|||
import os
|
||||
import ast
|
||||
import uuid
|
||||
import shutil
|
||||
import datetime
|
||||
import subprocess
|
||||
from .. import db
|
||||
from ..db_class.db import *
|
||||
from ..utils.utils import create_specific_dir
|
||||
|
||||
from sqlalchemy import desc, and_
|
||||
from flask import request, send_file
|
||||
from werkzeug.utils import secure_filename
|
||||
from ..notification import notification_core as NotifModel
|
||||
|
||||
from . import common_core as CommonModel
|
||||
|
||||
|
||||
UPLOAD_FOLDER = os.path.join(os.getcwd(), "uploads")
|
||||
FILE_FOLDER = os.path.join(UPLOAD_FOLDER, "files")
|
||||
TEMP_FOLDER = os.path.join(os.getcwd(), "temp")
|
||||
|
||||
|
||||
def delete_task(tid, current_user):
|
||||
"""Delete a task by is id"""
|
||||
task = CommonModel.get_task(tid)
|
||||
if task is not None:
|
||||
for file in task.files:
|
||||
try:
|
||||
os.remove(os.path.join(FILE_FOLDER, file.uuid))
|
||||
except:
|
||||
return False
|
||||
db.session.delete(file)
|
||||
db.session.commit()
|
||||
|
||||
case = CommonModel.get_case(task.case_id)
|
||||
task_users = Task_User.query.where(Task_User.task_id==task.id).all()
|
||||
for task_user in task_users:
|
||||
user = User.query.get(task_user.user_id)
|
||||
NotifModel.create_notification_user(f"Task '{task.id}-{task.title}' of case '{case.id}-{case.title}' was deleted", task.case_id, user_id=user.id, html_icon="fa-solid fa-trash")
|
||||
|
||||
Task_Tags.query.filter_by(task_id=task.id).delete()
|
||||
Task_Galaxy_Tags.query.filter_by(task_id=task.id).delete()
|
||||
Task_User.query.filter_by(task_id=task.id).delete()
|
||||
Task_Connector_Instance.query.filter_by(task_id=task.id).delete()
|
||||
db.session.delete(task)
|
||||
CommonModel.update_last_modif(task.case_id)
|
||||
db.session.commit()
|
||||
|
||||
CommonModel.save_history(case.uuid, current_user, f"Task '{task.title}' deleted")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def complete_task(tid, current_user):
|
||||
"""Complete task by is id"""
|
||||
task = CommonModel.get_task(tid)
|
||||
if task is not None:
|
||||
task.completed = not task.completed
|
||||
|
||||
case = CommonModel.get_case(task.case_id)
|
||||
task_users = Task_User.query.where(Task_User.task_id==task.id).all()
|
||||
if task.completed:
|
||||
task.status_id = Status.query.filter_by(name="Finished").first().id
|
||||
message = f"Task '{task.id}-{task.title}' of case '{case.id}-{case.title}' completed"
|
||||
else:
|
||||
task.status_id = Status.query.filter_by(name="Created").first().id
|
||||
message = f"Task '{task.id}-{task.title}' of case '{case.id}-{case.title}' revived"
|
||||
for task_user in task_users:
|
||||
user = User.query.get(task_user.user_id)
|
||||
if task.completed:
|
||||
message = f"Task '{task.id}-{task.title}' of case '{case.id}-{case.title}' completed"
|
||||
NotifModel.create_notification_user(message, task.case_id, user_id=user.id, html_icon="fa-solid fa-check")
|
||||
else:
|
||||
message = f"Task '{task.id}-{task.title}' of case '{case.id}-{case.title}' revived"
|
||||
NotifModel.create_notification_user(message, task.case_id, user_id=user.id, html_icon="fa-solid fa-heart-circle-bolt")
|
||||
|
||||
CommonModel.update_last_modif(task.case_id)
|
||||
CommonModel.update_last_modif_task(task.id)
|
||||
db.session.commit()
|
||||
CommonModel.save_history(case.uuid, current_user, f"Task '{task.title}' completed")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def create_task(form_dict, cid, current_user):
|
||||
"""Add a task to the DB"""
|
||||
if "template_select" in form_dict and not 0 in form_dict["template_select"]:
|
||||
template = Task_Template.query.get(form_dict["template_select"])
|
||||
task = Task(
|
||||
uuid=str(uuid.uuid4()),
|
||||
title=template.title,
|
||||
description=template.description,
|
||||
url=template.url,
|
||||
creation_date=datetime.datetime.now(tz=datetime.timezone.utc),
|
||||
last_modif=datetime.datetime.now(tz=datetime.timezone.utc),
|
||||
case_id=cid,
|
||||
status_id=1
|
||||
)
|
||||
db.session.add(task)
|
||||
db.session.commit()
|
||||
|
||||
for t_t in Task_Template_Tags.query.filter_by(task_id=task.id).all():
|
||||
task_tag = Task_Tags(
|
||||
task_id=task.id,
|
||||
tag_id=t_t.tag_id
|
||||
)
|
||||
db.session.add(task_tag)
|
||||
db.session.commit()
|
||||
|
||||
for t_t in Task_Template_Galaxy_Tags.query.filter_by(task_id=task.id).all():
|
||||
task_tag = Task_Galaxy_Tags(
|
||||
task_id=task.id,
|
||||
cluster_id=t_t.cluster_id
|
||||
)
|
||||
db.session.add(task_tag)
|
||||
db.session.commit()
|
||||
|
||||
for t_t in Task_Template_Connector_Instance.query.filter_by(template_id=task.id).all():
|
||||
task_instance = Task_Connector_Instance(
|
||||
task_id=task.id,
|
||||
instance_id=t_t.instance_id
|
||||
)
|
||||
db.session.add(task_instance)
|
||||
db.session.commit()
|
||||
else:
|
||||
deadline = CommonModel.deadline_check(form_dict["deadline_date"], form_dict["deadline_time"])
|
||||
|
||||
## Check if instance is from current user
|
||||
for instance in form_dict["connectors"]:
|
||||
instance = CommonModel.get_instance_by_name(instance)
|
||||
if not CommonModel.get_user_instance_by_instance(instance.id):
|
||||
return False
|
||||
|
||||
task = Task(
|
||||
uuid=str(uuid.uuid4()),
|
||||
title=form_dict["title"],
|
||||
description=form_dict["description"],
|
||||
url=form_dict["url"],
|
||||
creation_date=datetime.datetime.now(tz=datetime.timezone.utc),
|
||||
last_modif=datetime.datetime.now(tz=datetime.timezone.utc),
|
||||
deadline=deadline,
|
||||
case_id=cid,
|
||||
status_id=1
|
||||
)
|
||||
db.session.add(task)
|
||||
db.session.commit()
|
||||
|
||||
for tags in form_dict["tags"]:
|
||||
tag = CommonModel.get_tag(tags)
|
||||
|
||||
task_tag = Task_Tags(
|
||||
tag_id=tag.id,
|
||||
task_id=task.id
|
||||
)
|
||||
db.session.add(task_tag)
|
||||
db.session.commit()
|
||||
|
||||
for clusters in form_dict["clusters"]:
|
||||
cluster = CommonModel.get_cluster_by_name(clusters)
|
||||
|
||||
task_galaxy_tag = Task_Galaxy_Tags(
|
||||
cluster_id=cluster.id,
|
||||
task_id=task.id
|
||||
)
|
||||
db.session.add(task_galaxy_tag)
|
||||
db.session.commit()
|
||||
|
||||
for instance in form_dict["connectors"]:
|
||||
instance = CommonModel.get_instance_by_name(instance)
|
||||
task_instance = Task_Connector_Instance(
|
||||
task_id=task.id,
|
||||
instance_id=instance.id
|
||||
)
|
||||
db.session.add(task_instance)
|
||||
db.session.commit()
|
||||
|
||||
CommonModel.update_last_modif(cid)
|
||||
|
||||
case = CommonModel.get_case(cid)
|
||||
CommonModel.save_history(case.uuid, current_user, f"Task '{task.title}' Created")
|
||||
|
||||
return task
|
||||
|
||||
|
||||
def edit_task_core(form_dict, tid, current_user):
|
||||
"""Edit a task to the DB"""
|
||||
task = CommonModel.get_task(tid)
|
||||
deadline = CommonModel.deadline_check(form_dict["deadline_date"], form_dict["deadline_time"])
|
||||
|
||||
## Check if instance is from current user
|
||||
for instance in form_dict["connectors"]:
|
||||
instance = CommonModel.get_instance_by_name(instance)
|
||||
if not CommonModel.get_user_instance_by_instance(instance.id):
|
||||
return False
|
||||
|
||||
task.title = form_dict["title"]
|
||||
task.description=form_dict["description"]
|
||||
task.url=form_dict["url"]
|
||||
task.deadline=deadline
|
||||
|
||||
## Tags
|
||||
task_tag_db = Task_Tags.query.filter_by(task_id=task.id).all()
|
||||
for tags in form_dict["tags"]:
|
||||
tag = CommonModel.get_tag(tags)
|
||||
|
||||
if not tags in task_tag_db:
|
||||
task_tag = Task_Tags(
|
||||
tag_id=tag.id,
|
||||
task_id=task.id
|
||||
)
|
||||
db.session.add(task_tag)
|
||||
db.session.commit()
|
||||
|
||||
for c_t_db in task_tag_db:
|
||||
if not c_t_db in form_dict["tags"]:
|
||||
Task_Tags.query.filter_by(id=c_t_db.id).delete()
|
||||
db.session.commit()
|
||||
|
||||
## Clusters
|
||||
task_tag_db = Task_Galaxy_Tags.query.filter_by(task_id=task.id).all()
|
||||
for clusters in form_dict["clusters"]:
|
||||
cluster = CommonModel.get_cluster_by_name(clusters)
|
||||
|
||||
if not clusters in task_tag_db:
|
||||
task_tag = Task_Galaxy_Tags(
|
||||
cluster_id=cluster.id,
|
||||
task_id=task.id
|
||||
)
|
||||
db.session.add(task_tag)
|
||||
db.session.commit()
|
||||
|
||||
for c_t_db in task_tag_db:
|
||||
if not c_t_db in form_dict["clusters"]:
|
||||
Task_Galaxy_Tags.query.filter_by(id=c_t_db.id).delete()
|
||||
db.session.commit()
|
||||
|
||||
## Connectors
|
||||
task_connector_db = Task_Connector_Instance.query.filter_by(task_id=task.id).all()
|
||||
for connectors in form_dict["connectors"]:
|
||||
instance = CommonModel.get_instance_by_name(connectors)
|
||||
|
||||
if not connectors in task_connector_db:
|
||||
task_tag = Task_Connector_Instance(
|
||||
instance_id=instance.id,
|
||||
task_id=task.id
|
||||
)
|
||||
db.session.add(task_tag)
|
||||
db.session.commit()
|
||||
|
||||
for c_t_db in task_connector_db:
|
||||
if not c_t_db in form_dict["connectors"]:
|
||||
Task_Connector_Instance.query.filter_by(id=c_t_db.id).delete()
|
||||
db.session.commit()
|
||||
|
||||
CommonModel.update_last_modif(task.case_id)
|
||||
CommonModel.update_last_modif_task(task.id)
|
||||
db.session.commit()
|
||||
|
||||
case = CommonModel.get_case(task.case_id)
|
||||
CommonModel.save_history(case.uuid, current_user, f"Task '{task.title}' edited")
|
||||
|
||||
|
||||
def add_file_core(task, files_list, current_user):
|
||||
create_specific_dir(UPLOAD_FOLDER)
|
||||
create_specific_dir(FILE_FOLDER)
|
||||
for file in files_list:
|
||||
if files_list[file].filename:
|
||||
uuid_loc = str(uuid.uuid4())
|
||||
filename = secure_filename(files_list[file].filename)
|
||||
try:
|
||||
file_data = request.files[file].read()
|
||||
with open(os.path.join(FILE_FOLDER, uuid_loc), "wb") as write_file:
|
||||
write_file.write(file_data)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return False
|
||||
|
||||
f = File(
|
||||
name=filename,
|
||||
task_id=task.id,
|
||||
uuid = uuid_loc
|
||||
)
|
||||
db.session.add(f)
|
||||
CommonModel.update_last_modif(task.case_id)
|
||||
CommonModel.update_last_modif_task(task.id)
|
||||
db.session.commit()
|
||||
case = CommonModel.get_case(task.case_id)
|
||||
CommonModel.save_history(case.uuid, current_user, f"File added for task '{task.title}'")
|
||||
return True
|
||||
|
||||
def modif_note_core(tid, current_user, notes):
|
||||
"""Modify a noe of a task to the DB"""
|
||||
task = CommonModel.get_task(tid)
|
||||
if task:
|
||||
task.notes = notes
|
||||
CommonModel.update_last_modif(task.case_id)
|
||||
CommonModel.update_last_modif_task(task.id)
|
||||
db.session.commit()
|
||||
case = CommonModel.get_case(task.case_id)
|
||||
CommonModel.save_history(case.uuid, current_user, f"Notes for '{task.title}' modified")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def assign_task(tid, user, current_user, flag_current_user):
|
||||
"""Assign current user to a task"""
|
||||
task = CommonModel.get_task(tid)
|
||||
case = CommonModel.get_case(task.case_id)
|
||||
if task:
|
||||
if type(user) == str or type(user) == int:
|
||||
user = User.query.get(user)
|
||||
|
||||
task_user = Task_User(task_id=task.id, user_id=user.id)
|
||||
if not flag_current_user:
|
||||
NotifModel.create_notification_user(f"You have been assign to: '{task.id}-{task.title}' of case '{case.id}-{case.title}'", task.case_id, user_id=user.id, html_icon="fa-solid fa-hand")
|
||||
|
||||
if not Task_User.query.filter_by(task_id=task.id, user_id=user.id).first():
|
||||
db.session.add(task_user)
|
||||
CommonModel.update_last_modif(task.case_id)
|
||||
CommonModel.update_last_modif_task(task.id)
|
||||
db.session.commit()
|
||||
CommonModel.save_history(case.uuid, current_user, f"Task '{task.id}-{task.title}' assigned to {user.first_name} {user.last_name}")
|
||||
return True
|
||||
return False
|
||||
return False
|
||||
|
||||
def get_users_assign_task(task_id, user):
|
||||
"""Return users assigned to a task"""
|
||||
task_users = Task_User.query.filter_by(task_id=task_id).all()
|
||||
users = list()
|
||||
flag = False
|
||||
for task_user in task_users:
|
||||
u = User.query.get(task_user.user_id)
|
||||
# if current user is assigned to the task
|
||||
if u.id == user.id:
|
||||
flag = True
|
||||
users.append(u.to_json())
|
||||
return users, flag
|
||||
|
||||
|
||||
def remove_assign_task(tid, user, current_user, flag_current_user):
|
||||
"""Remove current user to the assignement to a task"""
|
||||
task = CommonModel.get_task(tid)
|
||||
case = CommonModel.get_case(task.case_id)
|
||||
if task:
|
||||
if type(user) == int or type(user) == str:
|
||||
user = User.query.get(user)
|
||||
task_users = Task_User.query.filter_by(task_id=task.id, user_id=user.id).all()
|
||||
if not flag_current_user:
|
||||
NotifModel.create_notification_user(f"Your assignment have been removed: '{task.id}-{task.title}' of case '{case.id}-{case.title}'", task.case_id, user_id=user.id, html_icon="fa-solid fa-handshake-slash")
|
||||
|
||||
for task_user in task_users:
|
||||
db.session.delete(task_user)
|
||||
|
||||
CommonModel.update_last_modif(task.case_id)
|
||||
CommonModel.update_last_modif_task(task.id)
|
||||
db.session.commit()
|
||||
CommonModel.save_history(case.uuid, current_user, f"Assignment '{task.title}' removed to {user.first_name} {user.last_name}")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def change_task_status(status, task, current_user):
|
||||
task.status_id = status
|
||||
CommonModel.update_last_modif(task.case_id)
|
||||
CommonModel.update_last_modif_task(task.id)
|
||||
db.session.commit()
|
||||
|
||||
case = CommonModel.get_case(task.case_id)
|
||||
CommonModel.save_history(case.uuid, current_user, f"Status changed for task '{task.title}'")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def download_file(file):
|
||||
return send_file(os.path.join(FILE_FOLDER, file.uuid), as_attachment=True, download_name=file.name)
|
||||
|
||||
|
||||
def delete_file(file, task, current_user):
|
||||
try:
|
||||
os.remove(os.path.join(FILE_FOLDER, file.uuid))
|
||||
except:
|
||||
return False
|
||||
|
||||
db.session.delete(file)
|
||||
db.session.commit()
|
||||
case = CommonModel.get_case(task.case_id)
|
||||
CommonModel.save_history(case.uuid, current_user, f"File deleted for task '{task.title}'")
|
||||
return True
|
||||
|
||||
|
||||
def get_task_info(tasks_list, user):
|
||||
tasks = list()
|
||||
for task in tasks_list:
|
||||
case = CommonModel.get_case(task.case_id)
|
||||
users, is_current_user_assigned = get_users_assign_task(task.id, user)
|
||||
file_list = list()
|
||||
for file in task.files:
|
||||
file_list.append(file.to_json())
|
||||
finalTask = task.to_json()
|
||||
finalTask["users"] = users
|
||||
finalTask["is_current_user_assigned"] = is_current_user_assigned
|
||||
finalTask["files"] = file_list
|
||||
finalTask["case_title"] = case.title
|
||||
|
||||
finalTask["instances"] = list()
|
||||
for task_connector in CommonModel.get_task_connectors(task.id):
|
||||
loc_instance = CommonModel.get_instance(task_connector.instance_id).to_json()
|
||||
loc_instance["icon"] = Icon_File.query.join(Connector_Icon, Connector_Icon.file_icon_id==Icon_File.id)\
|
||||
.join(Connector, Connector.icon_id==Connector_Icon.id)\
|
||||
.join(Connector_Instance, Connector_Instance.connector_id==Connector.id)\
|
||||
.where(Connector_Instance.id==task_connector.instance_id)\
|
||||
.first().uuid
|
||||
finalTask["instances"].append(loc_instance)
|
||||
|
||||
tasks.append(finalTask)
|
||||
return tasks
|
||||
|
||||
|
||||
def build_task_query(completed, tags=None, taxonomies=None, galaxies=None, clusters=None, filter=None):
|
||||
query = Task.query
|
||||
conditions = [Task.completed == completed]
|
||||
|
||||
if tags or taxonomies:
|
||||
query = query.join(Task_Tags, Task_Tags.task_id == Task.id)
|
||||
query = query.join(Tags, Task_Tags.tag_id == Tags.id)
|
||||
if tags:
|
||||
tags = ast.literal_eval(tags)
|
||||
conditions.append(Tags.name.in_(list(tags)))
|
||||
|
||||
if taxonomies:
|
||||
taxonomies = ast.literal_eval(taxonomies)
|
||||
query = query.join(Taxonomy, Taxonomy.id == Tags.taxonomy_id)
|
||||
conditions.append(Taxonomy.name.in_(list(taxonomies)))
|
||||
|
||||
if clusters or galaxies:
|
||||
query = query.join(Task_Galaxy_Tags, Task_Galaxy_Tags.task_id == Task.id)
|
||||
query = query.join(Cluster, Task_Galaxy_Tags.cluster_id == Cluster.id)
|
||||
if clusters:
|
||||
clusters = ast.literal_eval(clusters)
|
||||
conditions.append(Cluster.name.in_(list(clusters)))
|
||||
|
||||
if galaxies:
|
||||
galaxies = ast.literal_eval(galaxies)
|
||||
query = query.join(Galaxy, Galaxy.id == Cluster.galaxy_id)
|
||||
conditions.append(Galaxy.name.in_(list(galaxies)))
|
||||
|
||||
if filter:
|
||||
query.order_by(desc(filter))
|
||||
|
||||
return query.filter(and_(*conditions)).all()
|
||||
|
||||
def sort_by_status_task_core(case, user, taxonomies=[], galaxies=[], tags=[], clusters=[], or_and_taxo="true", or_and_galaxies="true", completed=False, no_info=False, filter=False):
|
||||
tasks = build_task_query(completed, tags, taxonomies, galaxies, clusters, filter)
|
||||
|
||||
if tags:
|
||||
tags = ast.literal_eval(tags)
|
||||
if taxonomies:
|
||||
taxonomies = ast.literal_eval(taxonomies)
|
||||
|
||||
if galaxies:
|
||||
galaxies = ast.literal_eval(galaxies)
|
||||
if clusters:
|
||||
clusters = ast.literal_eval(clusters)
|
||||
|
||||
if tags or taxonomies or galaxies or clusters:
|
||||
if or_and_taxo == "false":
|
||||
glob_list = []
|
||||
|
||||
for task in tasks:
|
||||
tags_db = task.to_json()["tags"]
|
||||
loc_tag = [tag["name"] for tag in tags_db]
|
||||
taxo_list = [Taxonomy.query.get(tag["taxonomy_id"]).name for tag in tags_db]
|
||||
|
||||
if (not tags or all(item in loc_tag for item in tags)) and \
|
||||
(not taxonomies or all(item in taxo_list for item in taxonomies)):
|
||||
glob_list.append(task)
|
||||
|
||||
tasks = glob_list
|
||||
if or_and_galaxies == "false":
|
||||
glob_list = []
|
||||
|
||||
for task in tasks:
|
||||
clusters_db = task.to_json()["clusters"]
|
||||
loc_cluster = [cluster["name"] for cluster in clusters_db]
|
||||
galaxies_list = [Galaxy.query.get(cluster["galaxy_id"]).name for cluster in clusters_db]
|
||||
|
||||
if (not clusters or all(item in loc_cluster for item in clusters)) and \
|
||||
(not galaxies or all(item in galaxies_list for item in galaxies)):
|
||||
glob_list.append(task)
|
||||
|
||||
tasks = glob_list
|
||||
else:
|
||||
tasks = Task.query.filter_by(case_id=case.id, completed=completed).all()
|
||||
|
||||
if no_info:
|
||||
return tasks
|
||||
return get_task_info(tasks, user)
|
||||
|
||||
|
||||
def sort_tasks_by_filter(case, user, filter, taxonomies=[], galaxies=[], tags=[], clusters=[], or_and_taxo="true", or_and_galaxies="true", completed=False):
|
||||
tasks_list = sort_by_status_task_core(case, user, taxonomies, galaxies, tags, clusters, or_and_taxo, or_and_galaxies, completed, no_info=True, filter=filter)
|
||||
|
||||
loc_list = list()
|
||||
if filter == "assigned_tasks":
|
||||
for task in tasks_list:
|
||||
if Task_User.query.filter_by(task_id=task.id).first():
|
||||
loc_list.append(task)
|
||||
tasks_list = loc_list
|
||||
|
||||
elif filter == "my_assignment":
|
||||
for task in tasks_list:
|
||||
if Task_User.query.filter_by(task_id=task.id, user_id=user.id).first():
|
||||
task.is_current_user_assigned = True
|
||||
loc_list.append(task)
|
||||
tasks_list = loc_list
|
||||
|
||||
elif filter == "deadline":
|
||||
# for deadline filter, only task with a deadline defined is required
|
||||
loc = list()
|
||||
for task in tasks_list:
|
||||
if getattr(task, filter):
|
||||
loc.append(task)
|
||||
tasks_list = loc
|
||||
else:
|
||||
# status, last_modif, title
|
||||
tasks_list.sort(key=lambda x: getattr(x, filter))
|
||||
|
||||
return get_task_info(tasks_list, user)
|
||||
|
||||
|
||||
def export_notes(task, type_req):
|
||||
if not os.path.isdir(TEMP_FOLDER):
|
||||
os.mkdir(TEMP_FOLDER)
|
||||
|
||||
download_filename = f"export_note_task_{task.id}.{type_req}"
|
||||
temp_md = os.path.join(TEMP_FOLDER, "index.md")
|
||||
temp_export = os.path.join(TEMP_FOLDER, f"output.{type_req}")
|
||||
|
||||
with open(temp_md, "w")as write_file:
|
||||
write_file.write(task.notes)
|
||||
if type_req == "pdf":
|
||||
process = subprocess.Popen(["pandoc", temp_md, "--pdf-engine=xelatex", "-V", "colorlinks=true", "-V", "linkcolor=blue", "-V", "urlcolor=red", "-V", "tocolor=gray"\
|
||||
"--number-sections", "--toc", "--template", "eisvogel", "-o", temp_export, "--filter=pandoc-mermaid"], stdout=subprocess.PIPE)
|
||||
elif type_req == "docx":
|
||||
process = subprocess.Popen(["pandoc", temp_md, "-o", temp_export, "--filter=mermaid-filter"], stdout=subprocess.PIPE)
|
||||
process.wait()
|
||||
|
||||
try:
|
||||
shutil.rmtree(os.path.join(os.getcwd(), "mermaid-images"))
|
||||
except:
|
||||
pass
|
||||
|
||||
return send_file(temp_export, as_attachment=True, download_name=download_filename)
|
|
@ -0,0 +1,49 @@
|
|||
from .. import db
|
||||
|
||||
|
||||
class Module(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
name = db.Column(db.String, index=True, unique=True)
|
||||
description = db.Column(db.String)
|
||||
is_active = db.Column(db.Boolean, default=True)
|
||||
request_on_query = db.Column(db.Boolean, default=False)
|
||||
|
||||
def to_json(self):
|
||||
json_dict = {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"is_active": self.is_active,
|
||||
"request_on_query": self.request_on_query
|
||||
}
|
||||
return json_dict
|
||||
|
||||
class Session_db(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
uuid = db.Column(db.String(36), index=True, unique=True)
|
||||
glob_query = db.Column(db.String)
|
||||
query_enter = db.Column(db.String)
|
||||
input_query = db.Column(db.String)
|
||||
config_module=db.Column(db.String)
|
||||
result=db.Column(db.String)
|
||||
nb_errors = db.Column(db.Integer, index=True)
|
||||
|
||||
def to_json(self):
|
||||
return
|
||||
|
||||
|
||||
class History(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
session_id = db.Column(db.Integer, index=True)
|
||||
|
||||
|
||||
class Config(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
name = db.Column(db.String, index=True, unique=True)
|
||||
|
||||
class Module_Config(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
module_id = db.Column(db.Integer, index=True)
|
||||
config_id = db.Column(db.Integer, index=True)
|
||||
value = db.Column(db.String, index=True)
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
from flask import Flask, Blueprint, render_template, request, jsonify
|
||||
from . import home_core as HomeModel
|
||||
from . import session as SessionModel
|
||||
|
||||
home_blueprint = Blueprint(
|
||||
'home',
|
||||
__name__,
|
||||
template_folder='templates',
|
||||
static_folder='static'
|
||||
)
|
||||
|
||||
|
||||
@home_blueprint.route("/")
|
||||
def home():
|
||||
return render_template("home.html")
|
||||
|
||||
@home_blueprint.route("/get_modules")
|
||||
def get_modules():
|
||||
"""Return all modules available"""
|
||||
expansion = ""
|
||||
hover = ""
|
||||
|
||||
if "expansion" in request.args:
|
||||
expansion = request.args.get("expansion")
|
||||
if "hover" in request.args:
|
||||
hover = request.args.get("hover")
|
||||
res = HomeModel.get_modules(expansion, hover)
|
||||
|
||||
if "message" in res:
|
||||
return res, 404
|
||||
return res, 200
|
||||
|
||||
@home_blueprint.route("/get_list_misp_attributes")
|
||||
def get_list_misp_attributes():
|
||||
"""Return all misp attributes for input and output"""
|
||||
expansion = ""
|
||||
hover = ""
|
||||
|
||||
if "expansion" in request.args:
|
||||
expansion = request.args.get("expansion")
|
||||
if "hover" in request.args:
|
||||
hover = request.args.get("hover")
|
||||
|
||||
res = HomeModel.get_list_misp_attributes(expansion, hover)
|
||||
|
||||
if "message" in res:
|
||||
return res, 404
|
||||
return res, 200
|
||||
|
||||
@home_blueprint.route("/run_modules", methods=['POST'])
|
||||
def run_modules():
|
||||
"""Run modules"""
|
||||
if "query" in request.json:
|
||||
if "input" in request.json:
|
||||
if "expansion" in request.json or "hover" in request.json:
|
||||
session = SessionModel.Session_class(request.json)
|
||||
session.start()
|
||||
SessionModel.sessions.append(session)
|
||||
return jsonify(session.status()), 201
|
||||
return {"message": "Need a module type"}, 400
|
||||
return {"message": "Need an input (misp attribute)"}, 400
|
||||
return {"message": "Need to type something"}, 400
|
||||
|
||||
@home_blueprint.route("/status/<sid>")
|
||||
def status(sid):
|
||||
"""Status of <sid> queue"""
|
||||
sess = HomeModel.get_session(sid)
|
||||
if sess:
|
||||
return jsonify(HomeModel.get_status_db(sess))
|
||||
else:
|
||||
for s in SessionModel.sessions:
|
||||
if s.id == sid:
|
||||
return jsonify(s.status())
|
||||
return jsonify({'message': 'Scan session not found'}), 404
|
||||
|
||||
@home_blueprint.route("/result/<sid>")
|
||||
def result(sid):
|
||||
"""Result of <sid> queue"""
|
||||
sess = HomeModel.get_session(sid)
|
||||
if sess:
|
||||
return jsonify(HomeModel.get_result_db(sess))
|
||||
else:
|
||||
for s in SessionModel.sessions:
|
||||
if s.id == sid:
|
||||
return jsonify(s.get_result())
|
||||
return jsonify({'message': 'Scan session not found'}), 404
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@home_blueprint.route("/modules_config")
|
||||
def modules_config():
|
||||
"""List all modules for configuration"""
|
||||
|
||||
return render_template("modules_config.html")
|
||||
|
||||
@home_blueprint.route("/modules_config_data")
|
||||
def modules_config_data():
|
||||
"""List all modules for configuration"""
|
||||
|
||||
modules_config = HomeModel.get_modules_config()
|
||||
return modules_config, 200
|
||||
|
||||
|
||||
@home_blueprint.route("/change_config", methods=["POST"])
|
||||
def change_config():
|
||||
"""Change configuation for a module"""
|
||||
if "module_name" in request.json["result_dict"]:
|
||||
res = HomeModel.change_config_core(request.json["result_dict"])
|
||||
if res:
|
||||
return {'message': 'Config changed', 'toast_class': "success-subtle"}, 200
|
||||
return {'message': 'Something went wrong', 'toast_class': "danger-subtle"}, 400
|
||||
return {'message': 'Need to pass "module_name"', 'toast_class': "warning-subtle"}, 400
|
||||
|
||||
@home_blueprint.route("/change_status", methods=["GET"])
|
||||
def change_status():
|
||||
"""Change the status of a module, active or unactive"""
|
||||
if "module_id" in request.args:
|
||||
res = HomeModel.change_status_core(request.args.get("module_id"))
|
||||
if res:
|
||||
return {'message': 'Module status changed', 'toast_class': "success-subtle"}, 200
|
||||
return {'message': 'Something went wrong', 'toast_class': "danger-subtle"}, 400
|
||||
return {'message': 'Need to pass "module_id"', 'toast_class': "warning-subtle"}, 400
|
||||
|
||||
|
||||
|
||||
@home_blueprint.route("/history", methods=["GET"])
|
||||
def history():
|
||||
"""View all history"""
|
||||
return render_template("history.html")
|
||||
|
||||
@home_blueprint.route("/get_history", methods=["GET"])
|
||||
def get_history():
|
||||
"""Get all history"""
|
||||
histories = HomeModel.get_history()
|
||||
return histories
|
|
@ -0,0 +1,133 @@
|
|||
import json
|
||||
from .utils.utils import query_get_module
|
||||
from . import db
|
||||
from .db_class.db import History, Module, Config, Module_Config, Session_db
|
||||
|
||||
|
||||
def get_module(mid):
|
||||
return Module.query.get(mid)
|
||||
|
||||
def get_module_by_name(name):
|
||||
return Module.query.filter_by(name=name).first()
|
||||
|
||||
def get_config(cid):
|
||||
return Config.query.get(cid)
|
||||
|
||||
def get_config_by_name(name):
|
||||
return Config.query.filter_by(name=name).first()
|
||||
|
||||
def get_module_config_module(mid):
|
||||
return Module_Config.query.filter_by(module_id=mid).all()
|
||||
|
||||
def get_module_config_both(mid, cid):
|
||||
return Module_Config.query.filter_by(module_id=mid, config_id=cid).first()
|
||||
|
||||
def get_session(sid):
|
||||
return Session_db.query.filter_by(uuid=sid).first()
|
||||
|
||||
def get_modules(expansion, hover):
|
||||
res = query_get_module()
|
||||
if not "message" in res:
|
||||
loc_list = dict()
|
||||
loc_list["expansion"] = list()
|
||||
loc_list["hover"] = list()
|
||||
for module in res:
|
||||
module_db = get_module_by_name(module["name"])
|
||||
module_loc = module
|
||||
module_loc["request_on_query"] = module_db.request_on_query
|
||||
if module_db.is_active:
|
||||
if expansion:
|
||||
if "expansion" in module["meta"]["module-type"]:
|
||||
loc_list["expansion"].append(module_loc)
|
||||
if hover:
|
||||
if "hover" in module["meta"]["module-type"]:
|
||||
loc_list["hover"].append(module_loc)
|
||||
loc_list["expansion"].sort(key=lambda x: x["name"])
|
||||
loc_list["hover"].sort(key=lambda x: x["name"])
|
||||
return loc_list
|
||||
return res
|
||||
|
||||
|
||||
def util_get_attr(module, loc_list):
|
||||
if "input" in module["mispattributes"]:
|
||||
for input in module["mispattributes"]["input"]:
|
||||
if not input in loc_list:
|
||||
loc_list.append(input)
|
||||
return loc_list
|
||||
|
||||
def get_list_misp_attributes(expansion, hover):
|
||||
res = query_get_module()
|
||||
if not "message" in res:
|
||||
loc_list = list()
|
||||
|
||||
for module in res:
|
||||
if get_module_by_name(module["name"]).is_active:
|
||||
if expansion:
|
||||
if "expansion" in module["meta"]["module-type"]:
|
||||
loc_list = util_get_attr(module, loc_list)
|
||||
if hover:
|
||||
if "hover" in module["meta"]["module-type"]:
|
||||
loc_list = util_get_attr(module, loc_list)
|
||||
loc_list.sort()
|
||||
return loc_list
|
||||
return res
|
||||
|
||||
|
||||
def get_modules_config():
|
||||
modules = Module.query.order_by(Module.name).all()
|
||||
modules_list = []
|
||||
for module in modules:
|
||||
loc_module = module.to_json()
|
||||
loc_module["config"] = []
|
||||
mcs = Module_Config.query.filter_by(module_id=module.id).all()
|
||||
for mc in mcs:
|
||||
conf = Config.query.get(mc.config_id)
|
||||
loc_module["config"].append({conf.name: mc.value})
|
||||
modules_list.append(loc_module)
|
||||
return modules_list
|
||||
|
||||
|
||||
def change_config_core(request_json):
|
||||
module = get_module_by_name(request_json["module_name"])
|
||||
for element in request_json:
|
||||
if not element == "module_name":
|
||||
config = get_config_by_name(element)
|
||||
if config:
|
||||
m_c = get_module_config_both(module.id, config.id)
|
||||
m_c.value = request_json[element]
|
||||
db.session.commit()
|
||||
module.request_on_query = request_json["request_on_query"]
|
||||
db.session.commit()
|
||||
return True
|
||||
|
||||
def change_status_core(module_id):
|
||||
module = get_module(module_id)
|
||||
module.is_active = not module.is_active
|
||||
db.session.commit()
|
||||
return True
|
||||
|
||||
|
||||
|
||||
def get_status_db(session):
|
||||
glob_query = json.loads(session.glob_query)
|
||||
result = json.loads(session.result)
|
||||
return{
|
||||
'id': session.uuid,
|
||||
'total': len(glob_query),
|
||||
'complete': len(glob_query),
|
||||
'remaining': 0,
|
||||
'registered': len(result),
|
||||
'stopped' : True,
|
||||
"nb_errors": session.nb_errors
|
||||
}
|
||||
|
||||
def get_result_db(session):
|
||||
return json.loads(session.result)
|
||||
|
||||
def get_history():
|
||||
histories_list = list()
|
||||
histories = History.query.all()
|
||||
for history in histories:
|
||||
session = Session_db.query.get(history.session_id)
|
||||
histories_list.append({"uuid": session.uuid, "query": session.query_enter, "modules": json.loads(session.glob_query), "input": session.input_query})
|
||||
return histories_list
|
|
@ -0,0 +1,161 @@
|
|||
import json
|
||||
from queue import Queue
|
||||
from threading import Thread
|
||||
from uuid import uuid4
|
||||
from .utils.utils import query_post_query, query_get_module
|
||||
from . import home_core as HomeModel
|
||||
import uuid
|
||||
from . import db
|
||||
from .db_class.db import History, Session_db
|
||||
from sqlalchemy import func
|
||||
|
||||
|
||||
sessions = list()
|
||||
|
||||
class Session_class:
|
||||
def __init__(self, request_json) -> None:
|
||||
self.id = str(uuid4())
|
||||
self.thread_count = 4
|
||||
self.jobs = Queue(maxsize=0)
|
||||
self.threads = []
|
||||
self.stopped = False
|
||||
self.result_stopped = dict()
|
||||
self.result = dict()
|
||||
self.expansion = self.expansion_setter(request_json)
|
||||
self.hover = self.hover_setter(request_json)
|
||||
self.query = request_json["query"]
|
||||
self.input_query = request_json["input"]
|
||||
self.glob_query = self.expansion + self.hover
|
||||
self.nb_errors = 0
|
||||
self.config_module = self.config_module_setter(request_json)
|
||||
|
||||
def expansion_setter(self, request_json):
|
||||
if "expansion" in request_json:
|
||||
return request_json["expansion"]
|
||||
return []
|
||||
|
||||
def hover_setter(self, request_json):
|
||||
if "hover" in request_json:
|
||||
return request_json["hover"]
|
||||
return []
|
||||
|
||||
def config_module_setter(self, request_json):
|
||||
if request_json["config"]:
|
||||
for query in self.glob_query:
|
||||
if not query in request_json["config"]:
|
||||
request_json["config"][query] = {}
|
||||
module = HomeModel.get_module_by_name(query)
|
||||
mcs = HomeModel.get_module_config_module(module.id)
|
||||
for mc in mcs:
|
||||
config_db = HomeModel.get_config(mc.config_id)
|
||||
request_json["config"][query][config_db.name] = mc.value
|
||||
return request_json["config"]
|
||||
return {}
|
||||
|
||||
def start(self):
|
||||
"""Start all worker"""
|
||||
for i in range(len(self.glob_query)):
|
||||
#need the index and the url in each queue item.
|
||||
self.jobs.put((i, self.glob_query[i]))
|
||||
for _ in range(self.thread_count):
|
||||
worker = Thread(target=self.process)
|
||||
worker.daemon = True
|
||||
worker.start()
|
||||
self.threads.append(worker)
|
||||
|
||||
def status(self):
|
||||
"""Status of the current queue"""
|
||||
if self.jobs.empty():
|
||||
self.stop()
|
||||
|
||||
total = len(self.glob_query)
|
||||
remaining = max(self.jobs.qsize(), len(self.threads))
|
||||
complete = total - remaining
|
||||
registered = len(self.result)
|
||||
|
||||
return {
|
||||
'id': self.id,
|
||||
'total': total,
|
||||
'complete': complete,
|
||||
'remaining': remaining,
|
||||
'registered': registered,
|
||||
'stopped' : self.stopped,
|
||||
"nb_errors": self.nb_errors
|
||||
}
|
||||
|
||||
def stop(self):
|
||||
"""Stop the current queue and worker"""
|
||||
self.jobs.queue.clear()
|
||||
|
||||
for worker in self.threads:
|
||||
worker.join(3.5)
|
||||
|
||||
self.threads.clear()
|
||||
sessions.remove(self)
|
||||
self.save_info()
|
||||
|
||||
def process(self):
|
||||
"""Threaded function for queue processing."""
|
||||
while not self.jobs.empty():
|
||||
work = self.jobs.get()
|
||||
|
||||
modules = query_get_module()
|
||||
loc_query = {}
|
||||
# If Misp format
|
||||
for module in modules:
|
||||
if module["name"] == work[1]:
|
||||
if "format" in module["mispattributes"]:
|
||||
loc_query = {
|
||||
"type": self.input_query,
|
||||
"value": self.query,
|
||||
"uuid": str(uuid.uuid4())
|
||||
}
|
||||
break
|
||||
|
||||
loc_config = {}
|
||||
if work[1] in self.config_module:
|
||||
loc_config = self.config_module[work[1]]
|
||||
|
||||
|
||||
if loc_query:
|
||||
send_to = {"module": work[1], "attribute": loc_query, "config": loc_config}
|
||||
else:
|
||||
send_to = {"module": work[1], self.input_query: self.query, "config": loc_config}
|
||||
res = query_post_query(send_to)
|
||||
print(res)
|
||||
if "error" in res:
|
||||
self.nb_errors += 1
|
||||
self.result[work[1]] = res
|
||||
|
||||
self.jobs.task_done()
|
||||
return True
|
||||
|
||||
def get_result(self):
|
||||
return self.result
|
||||
|
||||
def save_info(self):
|
||||
s = Session_db(
|
||||
uuid=str(self.id),
|
||||
glob_query=json.dumps(self.glob_query),
|
||||
query_enter=self.query,
|
||||
input_query=self.input_query,
|
||||
config_module=json.dumps(self.config_module),
|
||||
result=json.dumps(self.result),
|
||||
nb_errors=self.nb_errors
|
||||
)
|
||||
db.session.add(s)
|
||||
db.session.commit()
|
||||
|
||||
h = History(
|
||||
session_id=s.id
|
||||
)
|
||||
db.session.add(h)
|
||||
db.session.commit()
|
||||
|
||||
histories = History.query.all()
|
||||
while len(histories) > 100:
|
||||
history = History.query.filter_by(func.min(History.id))
|
||||
history.delete()
|
||||
histories = History.query.all()
|
||||
|
||||
return
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,591 @@
|
|||
/*!
|
||||
* Bootstrap Reboot v5.3.0-alpha1 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2022 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-primary-text: #0a58ca;
|
||||
--bs-secondary-text: #6c757d;
|
||||
--bs-success-text: #146c43;
|
||||
--bs-info-text: #087990;
|
||||
--bs-warning-text: #997404;
|
||||
--bs-danger-text: #b02a37;
|
||||
--bs-light-text: #6c757d;
|
||||
--bs-dark-text: #495057;
|
||||
--bs-primary-bg-subtle: #cfe2ff;
|
||||
--bs-secondary-bg-subtle: #f8f9fa;
|
||||
--bs-success-bg-subtle: #d1e7dd;
|
||||
--bs-info-bg-subtle: #cff4fc;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #9ec5fe;
|
||||
--bs-secondary-border-subtle: #e9ecef;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-info-border-subtle: #9eeaf9;
|
||||
--bs-warning-border-subtle: #ffe69c;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-light-border-subtle: #e9ecef;
|
||||
--bs-dark-border-subtle: #adb5bd;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||
--bs-tertiary-bg: #f8f9fa;
|
||||
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||
--bs-body-bg: #fff;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-link-color: #0d6efd;
|
||||
--bs-link-color-rgb: 13, 110, 253;
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
--bs-border-color: #dee2e6;
|
||||
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.5rem;
|
||||
--bs-border-radius-xl: 1rem;
|
||||
--bs-border-radius-2xl: 2rem;
|
||||
--bs-border-radius-pill: 50rem;
|
||||
--bs-box-shadow: 0 0.5rem 1rem rgba(var(--bs-body-color-rgb), 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(var(--bs-body-color-rgb), 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(var(--bs-body-color-rgb), 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(var(--bs-body-color-rgb), 0.075);
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-form-control-bg: var(--bs-body-bg);
|
||||
--bs-form-control-disabled-bg: var(--bs-secondary-bg);
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-breakpoint-xs: 0;
|
||||
--bs-breakpoint-sm: 576px;
|
||||
--bs-breakpoint-md: 768px;
|
||||
--bs-breakpoint-lg: 992px;
|
||||
--bs-breakpoint-xl: 1200px;
|
||||
--bs-breakpoint-xxl: 1400px;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
--bs-body-color: #adb5bd;
|
||||
--bs-body-color-rgb: 173, 181, 189;
|
||||
--bs-body-bg: #212529;
|
||||
--bs-body-bg-rgb: 33, 37, 41;
|
||||
--bs-emphasis-color: #f8f9fa;
|
||||
--bs-emphasis-color-rgb: 248, 249, 250;
|
||||
--bs-secondary-color: rgba(173, 181, 189, 0.75);
|
||||
--bs-secondary-color-rgb: 173, 181, 189;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(173, 181, 189, 0.5);
|
||||
--bs-tertiary-color-rgb: 173, 181, 189;
|
||||
--bs-tertiary-bg: #2b3035;
|
||||
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-primary-text: #6ea8fe;
|
||||
--bs-secondary-text: #dee2e6;
|
||||
--bs-success-text: #75b798;
|
||||
--bs-info-text: #6edff6;
|
||||
--bs-warning-text: #ffda6a;
|
||||
--bs-danger-text: #ea868f;
|
||||
--bs-light-text: #f8f9fa;
|
||||
--bs-dark-text: #dee2e6;
|
||||
--bs-primary-bg-subtle: #031633;
|
||||
--bs-secondary-bg-subtle: #212529;
|
||||
--bs-success-bg-subtle: #051b11;
|
||||
--bs-info-bg-subtle: #032830;
|
||||
--bs-warning-bg-subtle: #332701;
|
||||
--bs-danger-bg-subtle: #2c0b0e;
|
||||
--bs-light-bg-subtle: #343a40;
|
||||
--bs-dark-bg-subtle: #1a1d20;
|
||||
--bs-primary-border-subtle: #084298;
|
||||
--bs-secondary-border-subtle: #495057;
|
||||
--bs-success-border-subtle: #0f5132;
|
||||
--bs-info-border-subtle: #055160;
|
||||
--bs-warning-border-subtle: #664d03;
|
||||
--bs-danger-border-subtle: #842029;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-dark-border-subtle: #343a40;
|
||||
--bs-heading-color: #fff;
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-hover-color: #9ec5fe;
|
||||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 158, 197, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color, inherit);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: left;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
line-height: inherit;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
outline-offset: -2px;
|
||||
-webkit-appearance: textfield;
|
||||
}
|
||||
|
||||
/* rtl:raw:
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
*/
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,588 @@
|
|||
/*!
|
||||
* Bootstrap Reboot v5.3.0-alpha1 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2022 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-primary-text: #0a58ca;
|
||||
--bs-secondary-text: #6c757d;
|
||||
--bs-success-text: #146c43;
|
||||
--bs-info-text: #087990;
|
||||
--bs-warning-text: #997404;
|
||||
--bs-danger-text: #b02a37;
|
||||
--bs-light-text: #6c757d;
|
||||
--bs-dark-text: #495057;
|
||||
--bs-primary-bg-subtle: #cfe2ff;
|
||||
--bs-secondary-bg-subtle: #f8f9fa;
|
||||
--bs-success-bg-subtle: #d1e7dd;
|
||||
--bs-info-bg-subtle: #cff4fc;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #9ec5fe;
|
||||
--bs-secondary-border-subtle: #e9ecef;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-info-border-subtle: #9eeaf9;
|
||||
--bs-warning-border-subtle: #ffe69c;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-light-border-subtle: #e9ecef;
|
||||
--bs-dark-border-subtle: #adb5bd;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||
--bs-tertiary-bg: #f8f9fa;
|
||||
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||
--bs-body-bg: #fff;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-link-color: #0d6efd;
|
||||
--bs-link-color-rgb: 13, 110, 253;
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
--bs-border-color: #dee2e6;
|
||||
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.5rem;
|
||||
--bs-border-radius-xl: 1rem;
|
||||
--bs-border-radius-2xl: 2rem;
|
||||
--bs-border-radius-pill: 50rem;
|
||||
--bs-box-shadow: 0 0.5rem 1rem rgba(var(--bs-body-color-rgb), 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(var(--bs-body-color-rgb), 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(var(--bs-body-color-rgb), 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(var(--bs-body-color-rgb), 0.075);
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-form-control-bg: var(--bs-body-bg);
|
||||
--bs-form-control-disabled-bg: var(--bs-secondary-bg);
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-breakpoint-xs: 0;
|
||||
--bs-breakpoint-sm: 576px;
|
||||
--bs-breakpoint-md: 768px;
|
||||
--bs-breakpoint-lg: 992px;
|
||||
--bs-breakpoint-xl: 1200px;
|
||||
--bs-breakpoint-xxl: 1400px;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
--bs-body-color: #adb5bd;
|
||||
--bs-body-color-rgb: 173, 181, 189;
|
||||
--bs-body-bg: #212529;
|
||||
--bs-body-bg-rgb: 33, 37, 41;
|
||||
--bs-emphasis-color: #f8f9fa;
|
||||
--bs-emphasis-color-rgb: 248, 249, 250;
|
||||
--bs-secondary-color: rgba(173, 181, 189, 0.75);
|
||||
--bs-secondary-color-rgb: 173, 181, 189;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(173, 181, 189, 0.5);
|
||||
--bs-tertiary-color-rgb: 173, 181, 189;
|
||||
--bs-tertiary-bg: #2b3035;
|
||||
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-primary-text: #6ea8fe;
|
||||
--bs-secondary-text: #dee2e6;
|
||||
--bs-success-text: #75b798;
|
||||
--bs-info-text: #6edff6;
|
||||
--bs-warning-text: #ffda6a;
|
||||
--bs-danger-text: #ea868f;
|
||||
--bs-light-text: #f8f9fa;
|
||||
--bs-dark-text: #dee2e6;
|
||||
--bs-primary-bg-subtle: #031633;
|
||||
--bs-secondary-bg-subtle: #212529;
|
||||
--bs-success-bg-subtle: #051b11;
|
||||
--bs-info-bg-subtle: #032830;
|
||||
--bs-warning-bg-subtle: #332701;
|
||||
--bs-danger-bg-subtle: #2c0b0e;
|
||||
--bs-light-bg-subtle: #343a40;
|
||||
--bs-dark-bg-subtle: #1a1d20;
|
||||
--bs-primary-border-subtle: #084298;
|
||||
--bs-secondary-border-subtle: #495057;
|
||||
--bs-success-border-subtle: #0f5132;
|
||||
--bs-info-border-subtle: #055160;
|
||||
--bs-warning-border-subtle: #664d03;
|
||||
--bs-danger-border-subtle: #842029;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-dark-border-subtle: #343a40;
|
||||
--bs-heading-color: #fff;
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-hover-color: #9ec5fe;
|
||||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 158, 197, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color, inherit);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: right;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
line-height: inherit;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: right;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
outline-offset: -2px;
|
||||
-webkit-appearance: textfield;
|
||||
}
|
||||
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,172 @@
|
|||
|
||||
body {
|
||||
background-color: #fbfbfb;
|
||||
}
|
||||
|
||||
@media (min-width: 991.98px) {
|
||||
main {
|
||||
padding-left: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
span#goTop, span#project-version{
|
||||
position: fixed;
|
||||
right: 1em;
|
||||
bottom: 1em;
|
||||
background-color: #fafbfc;
|
||||
}
|
||||
|
||||
|
||||
.select2-container {
|
||||
padding: 5px
|
||||
}
|
||||
|
||||
.select2-selection__choice{
|
||||
background-color: #ced4da;
|
||||
filter: drop-shadow(-1px 1px 1px rgba(50, 50, 0, 0.5));
|
||||
}
|
||||
|
||||
|
||||
div#searchbox {
|
||||
display: flex;
|
||||
margin: 0 auto 0 auto;
|
||||
}
|
||||
#process-query {
|
||||
font-size: 1.1em;
|
||||
height: 2.8em;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px 0 0 5px;
|
||||
padding-left: .75em;
|
||||
width: 80%;
|
||||
outline: none;
|
||||
}
|
||||
button#query {
|
||||
color: white;
|
||||
background-color: #0d6efd;
|
||||
border: 0;
|
||||
border-radius: 0 5px 5px 0;
|
||||
height: 2.8em;
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
flex-grow: 1;
|
||||
outline: none;
|
||||
}
|
||||
button#query:hover {
|
||||
background-color: #2779bd;
|
||||
}
|
||||
|
||||
progress {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
height: 5px;
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
margin: 5px 0;
|
||||
}
|
||||
progress::-webkit-progress-bar {
|
||||
background-color: transparent;
|
||||
}
|
||||
progress::-webkit-progress-value {
|
||||
background-color: #3490dc;
|
||||
}
|
||||
progress::-moz-progress-bar {
|
||||
background-color: #3490dc;
|
||||
}
|
||||
span#status {
|
||||
float: right;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.checkbox-type-module{
|
||||
margin-left: 5px
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*JSON Parser*/
|
||||
#core-format-picker {
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
background-color: #f9f9f9;
|
||||
border: 1px solid #e1e1e1;
|
||||
}
|
||||
#core-format-picker .selectable-key {
|
||||
font-weight: bold;
|
||||
padding: 2px 3px;
|
||||
}
|
||||
#core-format-picker .selectable-key:hover {
|
||||
box-shadow: 0 0 5px #00000077;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#core-format-picker .selectable-value {
|
||||
padding: 2px 3px;
|
||||
}
|
||||
#core-format-picker .selectable-value:hover {
|
||||
box-shadow: 0 0 5px #00000077;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#core-format-picker .children-counter {
|
||||
background-color: #6b6b6b;
|
||||
color: #ffffff;
|
||||
padding: 2px 3px;
|
||||
margin: 0px 0.25rem;
|
||||
font-size: smaller;
|
||||
border-radius: 3px;
|
||||
}
|
||||
#core-format-picker .collaspe-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
#core-format-picker .collaspe-button:hover {
|
||||
color: #292929;
|
||||
cursor: pointer;
|
||||
}
|
||||
/**/
|
||||
|
||||
|
||||
|
||||
.round-button {
|
||||
width: 3%;
|
||||
}
|
||||
.round-button-circle {
|
||||
padding-bottom: 100%;
|
||||
border-radius: 50%;
|
||||
|
||||
background: #e13333;
|
||||
box-shadow: 0 0 3px gray;
|
||||
}
|
||||
.round-button-circle:hover {
|
||||
background:#c41313;
|
||||
cursor: pointer;
|
||||
}
|
||||
.round-button a {
|
||||
float:left;
|
||||
width:100%;
|
||||
padding-top:50%;
|
||||
padding-bottom:50%;
|
||||
line-height:1em;
|
||||
margin-top:-0.5em;
|
||||
text-align:center;
|
||||
color:#e2eaf3;
|
||||
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
.side-panel-config {
|
||||
background-color: white;
|
||||
box-shadow: rgba(0, 0, 0, 0.05) 0px 2px 5px 0px, rgba(0, 0, 0, 0.05) 0px 2px 10px 0px;
|
||||
border-radius: 10px;
|
||||
height: 75vh;
|
||||
position: fixed;
|
||||
max-width: calc((100%) / 2 - 1*var(--bs-gutter-x) * .5);
|
||||
right: calc(var(--bs-gutter-x) * .5);
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.side-panel-config {
|
||||
max-width: calc((100% - 200px) / 2 - 1*var(--bs-gutter-x) * .5);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,2 @@
|
|||
.json-view-item:not(.root-item){margin-left:15px}.value-key{color:var(--jtv-valueKey-color);font-weight:600;margin-left:10px;border-radius:2px;white-space:nowrap;padding:5px 5px 5px 10px}.value-key.can-select{cursor:pointer}.value-key.can-select:hover{background-color:#00000014}.value-key.can-select:focus{outline:2px solid var(--jtv-hover-color)}.data-key{font-size:100%;font-family:inherit;border:0;background-color:transparent;width:100%;color:var(--jtv-key-color);display:flex;align-items:center;border-radius:2px;font-weight:600;cursor:pointer;white-space:nowrap;padding:5px}.data-key:hover{background-color:var(--jtv-hover-color)}.data-key:focus{outline:2px solid var(--jtv-hover-color)}.data-key::-moz-focus-inner{border:0}.data-key .properties{font-weight:300;opacity:.9;margin-left:4px;-webkit-user-select:none;user-select:none}.chevron-arrow{flex-shrink:0;border-right:2px solid var(--jtv-arrow-color);border-bottom:2px solid var(--jtv-arrow-color);width:var(--jtv-arrow-size);height:var(--jtv-arrow-size);margin-right:20px;margin-left:5px;transform:rotate(-45deg)}.chevron-arrow.opened{margin-top:-3px;transform:rotate(45deg)}.root-item[data-v-1cbd6770]{--jtv-key-color: #0977e6;--jtv-valueKey-color: #073642;--jtv-string-color: #268bd2;--jtv-number-color: #2aa198;--jtv-boolean-color: #cb4b16;--jtv-null-color: #6c71c4;--jtv-arrow-size: 6px;--jtv-arrow-color: #444;--jtv-hover-color: rgba(0, 0, 0, .1);margin-left:0;width:100%;height:auto}.root-item.dark[data-v-1cbd6770]{--jtv-key-color: #80d8ff;--jtv-valueKey-color: #fdf6e3;--jtv-hover-color: rgba(255, 255, 255, .1);--jtv-arrow-color: #fdf6e3}
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,30 @@
|
|||
/* Sidebar */
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
/* padding: 65px 0 0; */
|
||||
box-shadow: 0 2px 5px 0 rgb(0 0 0 / 5%), 0 2px 10px 0 rgb(0 0 0 / 5%);
|
||||
width: 200px;
|
||||
z-index: 600;
|
||||
}
|
||||
|
||||
.sidebar .active {
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 5px 0 rgb(0 0 0 / 16%), 0 2px 10px 0 rgb(0 0 0 / 12%);
|
||||
}
|
||||
|
||||
#search-form{
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.container{
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.fa-circle {
|
||||
font-size: 40%;
|
||||
margin-right: 3px;
|
||||
vertical-align: middle;
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
Fonticons, Inc. (https://fontawesome.com)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Font Awesome Free License
|
||||
|
||||
Font Awesome Free is free, open source, and GPL friendly. You can use it for
|
||||
commercial projects, open source projects, or really almost whatever you want.
|
||||
Full Font Awesome Free license: https://fontawesome.com/license/free.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
|
||||
|
||||
The Font Awesome Free download is licensed under a Creative Commons
|
||||
Attribution 4.0 International License and applies to all icons packaged
|
||||
as SVG and JS file types.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Fonts: SIL OFL 1.1 License
|
||||
|
||||
In the Font Awesome Free download, the SIL OFL license applies to all icons
|
||||
packaged as web and desktop font files.
|
||||
|
||||
Copyright (c) 2023 Fonticons, Inc. (https://fontawesome.com)
|
||||
with Reserved Font Name: "Font Awesome".
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
SIL OPEN FONT LICENSE
|
||||
Version 1.1 - 26 February 2007
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting — in part or in whole — any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Code: MIT License (https://opensource.org/licenses/MIT)
|
||||
|
||||
In the Font Awesome Free download, the MIT license applies to all non-font and
|
||||
non-icon files.
|
||||
|
||||
Copyright 2023 Fonticons, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without limitation the rights to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Attribution
|
||||
|
||||
Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
|
||||
Awesome Free files already contain embedded comments with sufficient
|
||||
attribution, so you shouldn't need to do anything additional when using these
|
||||
files normally.
|
||||
|
||||
We've kept attribution comments terse, so we ask that you do not actively work
|
||||
to remove them from files, especially code. They're a great way for folks to
|
||||
learn about Font Awesome.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Brand Icons
|
||||
|
||||
All brand icons are trademarks of their respective owners. The use of these
|
||||
trademarks does not indicate endorsement of the trademark holder by Font
|
||||
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
|
||||
to represent the company, product, or service to which they refer.**
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,19 @@
|
|||
/*!
|
||||
* Font Awesome Free 6.3.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:root, :host {
|
||||
--fa-style-family-classic: 'Font Awesome 6 Free';
|
||||
--fa-font-regular: normal 400 1em/1 'Font Awesome 6 Free'; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Free';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); }
|
||||
|
||||
.far,
|
||||
.fa-regular {
|
||||
font-weight: 400; }
|
|
@ -0,0 +1,6 @@
|
|||
/*!
|
||||
* Font Awesome Free 6.3.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-regular:normal 400 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}.fa-regular,.far{font-weight:400}
|
|
@ -0,0 +1,19 @@
|
|||
/*!
|
||||
* Font Awesome Free 6.3.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:root, :host {
|
||||
--fa-style-family-classic: 'Font Awesome 6 Free';
|
||||
--fa-font-solid: normal 900 1em/1 'Font Awesome 6 Free'; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Free';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); }
|
||||
|
||||
.fas,
|
||||
.fa-solid {
|
||||
font-weight: 900; }
|
|
@ -0,0 +1,6 @@
|
|||
/*!
|
||||
* Font Awesome Free 6.3.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900}
|
|
@ -0,0 +1,638 @@
|
|||
/*!
|
||||
* Font Awesome Free 6.3.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
:root, :host {
|
||||
--fa-font-solid: normal 900 1em/1 'Font Awesome 6 Solid';
|
||||
--fa-font-regular: normal 400 1em/1 'Font Awesome 6 Regular';
|
||||
--fa-font-light: normal 300 1em/1 'Font Awesome 6 Light';
|
||||
--fa-font-thin: normal 100 1em/1 'Font Awesome 6 Thin';
|
||||
--fa-font-duotone: normal 900 1em/1 'Font Awesome 6 Duotone';
|
||||
--fa-font-sharp-solid: normal 900 1em/1 'Font Awesome 6 Sharp';
|
||||
--fa-font-sharp-regular: normal 400 1em/1 'Font Awesome 6 Sharp';
|
||||
--fa-font-brands: normal 400 1em/1 'Font Awesome 6 Brands'; }
|
||||
|
||||
svg:not(:root).svg-inline--fa, svg:not(:host).svg-inline--fa {
|
||||
overflow: visible;
|
||||
box-sizing: content-box; }
|
||||
|
||||
.svg-inline--fa {
|
||||
display: var(--fa-display, inline-block);
|
||||
height: 1em;
|
||||
overflow: visible;
|
||||
vertical-align: -.125em; }
|
||||
.svg-inline--fa.fa-2xs {
|
||||
vertical-align: 0.1em; }
|
||||
.svg-inline--fa.fa-xs {
|
||||
vertical-align: 0em; }
|
||||
.svg-inline--fa.fa-sm {
|
||||
vertical-align: -0.07143em; }
|
||||
.svg-inline--fa.fa-lg {
|
||||
vertical-align: -0.2em; }
|
||||
.svg-inline--fa.fa-xl {
|
||||
vertical-align: -0.25em; }
|
||||
.svg-inline--fa.fa-2xl {
|
||||
vertical-align: -0.3125em; }
|
||||
.svg-inline--fa.fa-pull-left {
|
||||
margin-right: var(--fa-pull-margin, 0.3em);
|
||||
width: auto; }
|
||||
.svg-inline--fa.fa-pull-right {
|
||||
margin-left: var(--fa-pull-margin, 0.3em);
|
||||
width: auto; }
|
||||
.svg-inline--fa.fa-li {
|
||||
width: var(--fa-li-width, 2em);
|
||||
top: 0.25em; }
|
||||
.svg-inline--fa.fa-fw {
|
||||
width: var(--fa-fw-width, 1.25em); }
|
||||
|
||||
.fa-layers svg.svg-inline--fa {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0; }
|
||||
|
||||
.fa-layers-text, .fa-layers-counter {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
text-align: center; }
|
||||
|
||||
.fa-layers {
|
||||
display: inline-block;
|
||||
height: 1em;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
vertical-align: -.125em;
|
||||
width: 1em; }
|
||||
.fa-layers svg.svg-inline--fa {
|
||||
-webkit-transform-origin: center center;
|
||||
transform-origin: center center; }
|
||||
|
||||
.fa-layers-text {
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
-webkit-transform-origin: center center;
|
||||
transform-origin: center center; }
|
||||
|
||||
.fa-layers-counter {
|
||||
background-color: var(--fa-counter-background-color, #ff253a);
|
||||
border-radius: var(--fa-counter-border-radius, 1em);
|
||||
box-sizing: border-box;
|
||||
color: var(--fa-inverse, #fff);
|
||||
line-height: var(--fa-counter-line-height, 1);
|
||||
max-width: var(--fa-counter-max-width, 5em);
|
||||
min-width: var(--fa-counter-min-width, 1.5em);
|
||||
overflow: hidden;
|
||||
padding: var(--fa-counter-padding, 0.25em 0.5em);
|
||||
right: var(--fa-right, 0);
|
||||
text-overflow: ellipsis;
|
||||
top: var(--fa-top, 0);
|
||||
-webkit-transform: scale(var(--fa-counter-scale, 0.25));
|
||||
transform: scale(var(--fa-counter-scale, 0.25));
|
||||
-webkit-transform-origin: top right;
|
||||
transform-origin: top right; }
|
||||
|
||||
.fa-layers-bottom-right {
|
||||
bottom: var(--fa-bottom, 0);
|
||||
right: var(--fa-right, 0);
|
||||
top: auto;
|
||||
-webkit-transform: scale(var(--fa-layers-scale, 0.25));
|
||||
transform: scale(var(--fa-layers-scale, 0.25));
|
||||
-webkit-transform-origin: bottom right;
|
||||
transform-origin: bottom right; }
|
||||
|
||||
.fa-layers-bottom-left {
|
||||
bottom: var(--fa-bottom, 0);
|
||||
left: var(--fa-left, 0);
|
||||
right: auto;
|
||||
top: auto;
|
||||
-webkit-transform: scale(var(--fa-layers-scale, 0.25));
|
||||
transform: scale(var(--fa-layers-scale, 0.25));
|
||||
-webkit-transform-origin: bottom left;
|
||||
transform-origin: bottom left; }
|
||||
|
||||
.fa-layers-top-right {
|
||||
top: var(--fa-top, 0);
|
||||
right: var(--fa-right, 0);
|
||||
-webkit-transform: scale(var(--fa-layers-scale, 0.25));
|
||||
transform: scale(var(--fa-layers-scale, 0.25));
|
||||
-webkit-transform-origin: top right;
|
||||
transform-origin: top right; }
|
||||
|
||||
.fa-layers-top-left {
|
||||
left: var(--fa-left, 0);
|
||||
right: auto;
|
||||
top: var(--fa-top, 0);
|
||||
-webkit-transform: scale(var(--fa-layers-scale, 0.25));
|
||||
transform: scale(var(--fa-layers-scale, 0.25));
|
||||
-webkit-transform-origin: top left;
|
||||
transform-origin: top left; }
|
||||
|
||||
.fa-1x {
|
||||
font-size: 1em; }
|
||||
|
||||
.fa-2x {
|
||||
font-size: 2em; }
|
||||
|
||||
.fa-3x {
|
||||
font-size: 3em; }
|
||||
|
||||
.fa-4x {
|
||||
font-size: 4em; }
|
||||
|
||||
.fa-5x {
|
||||
font-size: 5em; }
|
||||
|
||||
.fa-6x {
|
||||
font-size: 6em; }
|
||||
|
||||
.fa-7x {
|
||||
font-size: 7em; }
|
||||
|
||||
.fa-8x {
|
||||
font-size: 8em; }
|
||||
|
||||
.fa-9x {
|
||||
font-size: 9em; }
|
||||
|
||||
.fa-10x {
|
||||
font-size: 10em; }
|
||||
|
||||
.fa-2xs {
|
||||
font-size: 0.625em;
|
||||
line-height: 0.1em;
|
||||
vertical-align: 0.225em; }
|
||||
|
||||
.fa-xs {
|
||||
font-size: 0.75em;
|
||||
line-height: 0.08333em;
|
||||
vertical-align: 0.125em; }
|
||||
|
||||
.fa-sm {
|
||||
font-size: 0.875em;
|
||||
line-height: 0.07143em;
|
||||
vertical-align: 0.05357em; }
|
||||
|
||||
.fa-lg {
|
||||
font-size: 1.25em;
|
||||
line-height: 0.05em;
|
||||
vertical-align: -0.075em; }
|
||||
|
||||
.fa-xl {
|
||||
font-size: 1.5em;
|
||||
line-height: 0.04167em;
|
||||
vertical-align: -0.125em; }
|
||||
|
||||
.fa-2xl {
|
||||
font-size: 2em;
|
||||
line-height: 0.03125em;
|
||||
vertical-align: -0.1875em; }
|
||||
|
||||
.fa-fw {
|
||||
text-align: center;
|
||||
width: 1.25em; }
|
||||
|
||||
.fa-ul {
|
||||
list-style-type: none;
|
||||
margin-left: var(--fa-li-margin, 2.5em);
|
||||
padding-left: 0; }
|
||||
.fa-ul > li {
|
||||
position: relative; }
|
||||
|
||||
.fa-li {
|
||||
left: calc(var(--fa-li-width, 2em) * -1);
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: var(--fa-li-width, 2em);
|
||||
line-height: inherit; }
|
||||
|
||||
.fa-border {
|
||||
border-color: var(--fa-border-color, #eee);
|
||||
border-radius: var(--fa-border-radius, 0.1em);
|
||||
border-style: var(--fa-border-style, solid);
|
||||
border-width: var(--fa-border-width, 0.08em);
|
||||
padding: var(--fa-border-padding, 0.2em 0.25em 0.15em); }
|
||||
|
||||
.fa-pull-left {
|
||||
float: left;
|
||||
margin-right: var(--fa-pull-margin, 0.3em); }
|
||||
|
||||
.fa-pull-right {
|
||||
float: right;
|
||||
margin-left: var(--fa-pull-margin, 0.3em); }
|
||||
|
||||
.fa-beat {
|
||||
-webkit-animation-name: fa-beat;
|
||||
animation-name: fa-beat;
|
||||
-webkit-animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
-webkit-animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
-webkit-animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
-webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out);
|
||||
animation-timing-function: var(--fa-animation-timing, ease-in-out); }
|
||||
|
||||
.fa-bounce {
|
||||
-webkit-animation-name: fa-bounce;
|
||||
animation-name: fa-bounce;
|
||||
-webkit-animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
-webkit-animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
-webkit-animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
-webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1));
|
||||
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); }
|
||||
|
||||
.fa-fade {
|
||||
-webkit-animation-name: fa-fade;
|
||||
animation-name: fa-fade;
|
||||
-webkit-animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
-webkit-animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
-webkit-animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
-webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1));
|
||||
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); }
|
||||
|
||||
.fa-beat-fade {
|
||||
-webkit-animation-name: fa-beat-fade;
|
||||
animation-name: fa-beat-fade;
|
||||
-webkit-animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
-webkit-animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
-webkit-animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
-webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1));
|
||||
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); }
|
||||
|
||||
.fa-flip {
|
||||
-webkit-animation-name: fa-flip;
|
||||
animation-name: fa-flip;
|
||||
-webkit-animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
-webkit-animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
-webkit-animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
-webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out);
|
||||
animation-timing-function: var(--fa-animation-timing, ease-in-out); }
|
||||
|
||||
.fa-shake {
|
||||
-webkit-animation-name: fa-shake;
|
||||
animation-name: fa-shake;
|
||||
-webkit-animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
-webkit-animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
-webkit-animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
-webkit-animation-timing-function: var(--fa-animation-timing, linear);
|
||||
animation-timing-function: var(--fa-animation-timing, linear); }
|
||||
|
||||
.fa-spin {
|
||||
-webkit-animation-name: fa-spin;
|
||||
animation-name: fa-spin;
|
||||
-webkit-animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
-webkit-animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
-webkit-animation-duration: var(--fa-animation-duration, 2s);
|
||||
animation-duration: var(--fa-animation-duration, 2s);
|
||||
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
-webkit-animation-timing-function: var(--fa-animation-timing, linear);
|
||||
animation-timing-function: var(--fa-animation-timing, linear); }
|
||||
|
||||
.fa-spin-reverse {
|
||||
--fa-animation-direction: reverse; }
|
||||
|
||||
.fa-pulse,
|
||||
.fa-spin-pulse {
|
||||
-webkit-animation-name: fa-spin;
|
||||
animation-name: fa-spin;
|
||||
-webkit-animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
-webkit-animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
-webkit-animation-timing-function: var(--fa-animation-timing, steps(8));
|
||||
animation-timing-function: var(--fa-animation-timing, steps(8)); }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.fa-beat,
|
||||
.fa-bounce,
|
||||
.fa-fade,
|
||||
.fa-beat-fade,
|
||||
.fa-flip,
|
||||
.fa-pulse,
|
||||
.fa-shake,
|
||||
.fa-spin,
|
||||
.fa-spin-pulse {
|
||||
-webkit-animation-delay: -1ms;
|
||||
animation-delay: -1ms;
|
||||
-webkit-animation-duration: 1ms;
|
||||
animation-duration: 1ms;
|
||||
-webkit-animation-iteration-count: 1;
|
||||
animation-iteration-count: 1;
|
||||
-webkit-transition-delay: 0s;
|
||||
transition-delay: 0s;
|
||||
-webkit-transition-duration: 0s;
|
||||
transition-duration: 0s; } }
|
||||
|
||||
@-webkit-keyframes fa-beat {
|
||||
0%, 90% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1); }
|
||||
45% {
|
||||
-webkit-transform: scale(var(--fa-beat-scale, 1.25));
|
||||
transform: scale(var(--fa-beat-scale, 1.25)); } }
|
||||
|
||||
@keyframes fa-beat {
|
||||
0%, 90% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1); }
|
||||
45% {
|
||||
-webkit-transform: scale(var(--fa-beat-scale, 1.25));
|
||||
transform: scale(var(--fa-beat-scale, 1.25)); } }
|
||||
|
||||
@-webkit-keyframes fa-bounce {
|
||||
0% {
|
||||
-webkit-transform: scale(1, 1) translateY(0);
|
||||
transform: scale(1, 1) translateY(0); }
|
||||
10% {
|
||||
-webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0);
|
||||
transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); }
|
||||
30% {
|
||||
-webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em));
|
||||
transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); }
|
||||
50% {
|
||||
-webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0);
|
||||
transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); }
|
||||
57% {
|
||||
-webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em));
|
||||
transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); }
|
||||
64% {
|
||||
-webkit-transform: scale(1, 1) translateY(0);
|
||||
transform: scale(1, 1) translateY(0); }
|
||||
100% {
|
||||
-webkit-transform: scale(1, 1) translateY(0);
|
||||
transform: scale(1, 1) translateY(0); } }
|
||||
|
||||
@keyframes fa-bounce {
|
||||
0% {
|
||||
-webkit-transform: scale(1, 1) translateY(0);
|
||||
transform: scale(1, 1) translateY(0); }
|
||||
10% {
|
||||
-webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0);
|
||||
transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); }
|
||||
30% {
|
||||
-webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em));
|
||||
transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); }
|
||||
50% {
|
||||
-webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0);
|
||||
transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); }
|
||||
57% {
|
||||
-webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em));
|
||||
transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); }
|
||||
64% {
|
||||
-webkit-transform: scale(1, 1) translateY(0);
|
||||
transform: scale(1, 1) translateY(0); }
|
||||
100% {
|
||||
-webkit-transform: scale(1, 1) translateY(0);
|
||||
transform: scale(1, 1) translateY(0); } }
|
||||
|
||||
@-webkit-keyframes fa-fade {
|
||||
50% {
|
||||
opacity: var(--fa-fade-opacity, 0.4); } }
|
||||
|
||||
@keyframes fa-fade {
|
||||
50% {
|
||||
opacity: var(--fa-fade-opacity, 0.4); } }
|
||||
|
||||
@-webkit-keyframes fa-beat-fade {
|
||||
0%, 100% {
|
||||
opacity: var(--fa-beat-fade-opacity, 0.4);
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1); }
|
||||
50% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(var(--fa-beat-fade-scale, 1.125));
|
||||
transform: scale(var(--fa-beat-fade-scale, 1.125)); } }
|
||||
|
||||
@keyframes fa-beat-fade {
|
||||
0%, 100% {
|
||||
opacity: var(--fa-beat-fade-opacity, 0.4);
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1); }
|
||||
50% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(var(--fa-beat-fade-scale, 1.125));
|
||||
transform: scale(var(--fa-beat-fade-scale, 1.125)); } }
|
||||
|
||||
@-webkit-keyframes fa-flip {
|
||||
50% {
|
||||
-webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg));
|
||||
transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } }
|
||||
|
||||
@keyframes fa-flip {
|
||||
50% {
|
||||
-webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg));
|
||||
transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } }
|
||||
|
||||
@-webkit-keyframes fa-shake {
|
||||
0% {
|
||||
-webkit-transform: rotate(-15deg);
|
||||
transform: rotate(-15deg); }
|
||||
4% {
|
||||
-webkit-transform: rotate(15deg);
|
||||
transform: rotate(15deg); }
|
||||
8%, 24% {
|
||||
-webkit-transform: rotate(-18deg);
|
||||
transform: rotate(-18deg); }
|
||||
12%, 28% {
|
||||
-webkit-transform: rotate(18deg);
|
||||
transform: rotate(18deg); }
|
||||
16% {
|
||||
-webkit-transform: rotate(-22deg);
|
||||
transform: rotate(-22deg); }
|
||||
20% {
|
||||
-webkit-transform: rotate(22deg);
|
||||
transform: rotate(22deg); }
|
||||
32% {
|
||||
-webkit-transform: rotate(-12deg);
|
||||
transform: rotate(-12deg); }
|
||||
36% {
|
||||
-webkit-transform: rotate(12deg);
|
||||
transform: rotate(12deg); }
|
||||
40%, 100% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg); } }
|
||||
|
||||
@keyframes fa-shake {
|
||||
0% {
|
||||
-webkit-transform: rotate(-15deg);
|
||||
transform: rotate(-15deg); }
|
||||
4% {
|
||||
-webkit-transform: rotate(15deg);
|
||||
transform: rotate(15deg); }
|
||||
8%, 24% {
|
||||
-webkit-transform: rotate(-18deg);
|
||||
transform: rotate(-18deg); }
|
||||
12%, 28% {
|
||||
-webkit-transform: rotate(18deg);
|
||||
transform: rotate(18deg); }
|
||||
16% {
|
||||
-webkit-transform: rotate(-22deg);
|
||||
transform: rotate(-22deg); }
|
||||
20% {
|
||||
-webkit-transform: rotate(22deg);
|
||||
transform: rotate(22deg); }
|
||||
32% {
|
||||
-webkit-transform: rotate(-12deg);
|
||||
transform: rotate(-12deg); }
|
||||
36% {
|
||||
-webkit-transform: rotate(12deg);
|
||||
transform: rotate(12deg); }
|
||||
40%, 100% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg); } }
|
||||
|
||||
@-webkit-keyframes fa-spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg); }
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg); } }
|
||||
|
||||
@keyframes fa-spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg); }
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg); } }
|
||||
|
||||
.fa-rotate-90 {
|
||||
-webkit-transform: rotate(90deg);
|
||||
transform: rotate(90deg); }
|
||||
|
||||
.fa-rotate-180 {
|
||||
-webkit-transform: rotate(180deg);
|
||||
transform: rotate(180deg); }
|
||||
|
||||
.fa-rotate-270 {
|
||||
-webkit-transform: rotate(270deg);
|
||||
transform: rotate(270deg); }
|
||||
|
||||
.fa-flip-horizontal {
|
||||
-webkit-transform: scale(-1, 1);
|
||||
transform: scale(-1, 1); }
|
||||
|
||||
.fa-flip-vertical {
|
||||
-webkit-transform: scale(1, -1);
|
||||
transform: scale(1, -1); }
|
||||
|
||||
.fa-flip-both,
|
||||
.fa-flip-horizontal.fa-flip-vertical {
|
||||
-webkit-transform: scale(-1, -1);
|
||||
transform: scale(-1, -1); }
|
||||
|
||||
.fa-rotate-by {
|
||||
-webkit-transform: rotate(var(--fa-rotate-angle, none));
|
||||
transform: rotate(var(--fa-rotate-angle, none)); }
|
||||
|
||||
.fa-stack {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
height: 2em;
|
||||
position: relative;
|
||||
width: 2.5em; }
|
||||
|
||||
.fa-stack-1x,
|
||||
.fa-stack-2x {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: var(--fa-stack-z-index, auto); }
|
||||
|
||||
.svg-inline--fa.fa-stack-1x {
|
||||
height: 1em;
|
||||
width: 1.25em; }
|
||||
|
||||
.svg-inline--fa.fa-stack-2x {
|
||||
height: 2em;
|
||||
width: 2.5em; }
|
||||
|
||||
.fa-inverse {
|
||||
color: var(--fa-inverse, #fff); }
|
||||
|
||||
.sr-only,
|
||||
.fa-sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0; }
|
||||
|
||||
.sr-only-focusable:not(:focus),
|
||||
.fa-sr-only-focusable:not(:focus) {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0; }
|
||||
|
||||
.svg-inline--fa .fa-primary {
|
||||
fill: var(--fa-primary-color, currentColor);
|
||||
opacity: var(--fa-primary-opacity, 1); }
|
||||
|
||||
.svg-inline--fa .fa-secondary {
|
||||
fill: var(--fa-secondary-color, currentColor);
|
||||
opacity: var(--fa-secondary-opacity, 0.4); }
|
||||
|
||||
.svg-inline--fa.fa-swap-opacity .fa-primary {
|
||||
opacity: var(--fa-secondary-opacity, 0.4); }
|
||||
|
||||
.svg-inline--fa.fa-swap-opacity .fa-secondary {
|
||||
opacity: var(--fa-primary-opacity, 1); }
|
||||
|
||||
.svg-inline--fa mask .fa-primary,
|
||||
.svg-inline--fa mask .fa-secondary {
|
||||
fill: black; }
|
||||
|
||||
.fad.fa-inverse,
|
||||
.fa-duotone.fa-inverse {
|
||||
color: var(--fa-inverse, #fff); }
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,26 @@
|
|||
/*!
|
||||
* Font Awesome Free 6.3.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'FontAwesome';
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); }
|
||||
|
||||
@font-face {
|
||||
font-family: 'FontAwesome';
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); }
|
||||
|
||||
@font-face {
|
||||
font-family: 'FontAwesome';
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype");
|
||||
unicode-range: U+F003,U+F006,U+F014,U+F016-F017,U+F01A-F01B,U+F01D,U+F022,U+F03E,U+F044,U+F046,U+F05C-F05D,U+F06E,U+F070,U+F087-F088,U+F08A,U+F094,U+F096-F097,U+F09D,U+F0A0,U+F0A2,U+F0A4-F0A7,U+F0C5,U+F0C7,U+F0E5-F0E6,U+F0EB,U+F0F6-F0F8,U+F10C,U+F114-F115,U+F118-F11A,U+F11C-F11D,U+F133,U+F147,U+F14E,U+F150-F152,U+F185-F186,U+F18E,U+F190-F192,U+F196,U+F1C1-F1C9,U+F1D9,U+F1DB,U+F1E3,U+F1EA,U+F1F7,U+F1F9,U+F20A,U+F247-F248,U+F24A,U+F24D,U+F255-F25B,U+F25D,U+F271-F274,U+F278,U+F27B,U+F28C,U+F28E,U+F29C,U+F2B5,U+F2B7,U+F2BA,U+F2BC,U+F2BE,U+F2C0-F2C1,U+F2C3,U+F2D0,U+F2D2,U+F2D4,U+F2DC; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'FontAwesome';
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-v4compatibility.woff2") format("woff2"), url("../webfonts/fa-v4compatibility.ttf") format("truetype");
|
||||
unicode-range: U+F041,U+F047,U+F065-F066,U+F07D-F07E,U+F080,U+F08B,U+F08E,U+F090,U+F09A,U+F0AC,U+F0AE,U+F0B2,U+F0D0,U+F0D6,U+F0E4,U+F0EC,U+F10A-F10B,U+F123,U+F13E,U+F148-F149,U+F14C,U+F156,U+F15E,U+F160-F161,U+F163,U+F175-F178,U+F195,U+F1F8,U+F219,U+F27A; }
|
|
@ -0,0 +1,6 @@
|
|||
/*!
|
||||
* Font Awesome Free 6.3.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype");unicode-range:u+f003,u+f006,u+f014,u+f016-f017,u+f01a-f01b,u+f01d,u+f022,u+f03e,u+f044,u+f046,u+f05c-f05d,u+f06e,u+f070,u+f087-f088,u+f08a,u+f094,u+f096-f097,u+f09d,u+f0a0,u+f0a2,u+f0a4-f0a7,u+f0c5,u+f0c7,u+f0e5-f0e6,u+f0eb,u+f0f6-f0f8,u+f10c,u+f114-f115,u+f118-f11a,u+f11c-f11d,u+f133,u+f147,u+f14e,u+f150-f152,u+f185-f186,u+f18e,u+f190-f192,u+f196,u+f1c1-f1c9,u+f1d9,u+f1db,u+f1e3,u+f1ea,u+f1f7,u+f1f9,u+f20a,u+f247-f248,u+f24a,u+f24d,u+f255-f25b,u+f25d,u+f271-f274,u+f278,u+f27b,u+f28c,u+f28e,u+f29c,u+f2b5,u+f2b7,u+f2ba,u+f2bc,u+f2be,u+f2c0-f2c1,u+f2c3,u+f2d0,u+f2d2,u+f2d4,u+f2dc}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-v4compatibility.woff2) format("woff2"),url(../webfonts/fa-v4compatibility.ttf) format("truetype");unicode-range:u+f041,u+f047,u+f065-f066,u+f07d-f07e,u+f080,u+f08b,u+f08e,u+f090,u+f09a,u+f0ac,u+f0ae,u+f0b2,u+f0d0,u+f0d6,u+f0e4,u+f0ec,u+f10a-f10b,u+f123,u+f13e,u+f148-f149,u+f14c,u+f156,u+f15e,u+f160-f161,u+f163,u+f175-f178,u+f195,u+f1f8,u+f219,u+f27a}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,22 @@
|
|||
/*!
|
||||
* Font Awesome Free 6.3.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 5 Brands';
|
||||
font-display: block;
|
||||
font-weight: 400;
|
||||
src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-display: block;
|
||||
font-weight: 900;
|
||||
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-display: block;
|
||||
font-weight: 400;
|
||||
src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); }
|
|
@ -0,0 +1,6 @@
|
|||
/*!
|
||||
* Font Awesome Free 6.3.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
@font-face{font-family:"Font Awesome 5 Brands";font-display:block;font-weight:400;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:900;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:400;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}
|
|
@ -0,0 +1,152 @@
|
|||
// animating icons
|
||||
// --------------------------
|
||||
|
||||
.@{fa-css-prefix}-beat {
|
||||
animation-name: ~'@{fa-css-prefix}-beat';
|
||||
animation-delay: ~'var(--@{fa-css-prefix}-animation-delay, 0s)';
|
||||
animation-direction: ~'var(--@{fa-css-prefix}-animation-direction, normal)';
|
||||
animation-duration: ~'var(--@{fa-css-prefix}-animation-duration, 1s)';
|
||||
animation-iteration-count: ~'var(--@{fa-css-prefix}-animation-iteration-count, infinite)';
|
||||
animation-timing-function: ~'var(--@{fa-css-prefix}-animation-timing, ease-in-out)';
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-bounce {
|
||||
animation-name: ~'@{fa-css-prefix}-beat';
|
||||
animation-delay: ~'var(--@{fa-css-prefix}-animation-delay, 0s)';
|
||||
animation-direction: ~'var(--@{fa-css-prefix}-animation-direction, normal)';
|
||||
animation-duration: ~'var(--@{fa-css-prefix}-animation-duration, 1s)';
|
||||
animation-iteration-count: ~'var(--@{fa-css-prefix}-animation-iteration-count, infinite)';
|
||||
animation-timing-function: ~'var(--@{fa-css-prefix}-animation-timing, cubic-bezier(0.280, 0.840, 0.420, 1))';
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-fade {
|
||||
animation-name: ~'@{fa-css-prefix}-fade';
|
||||
animation-delay: ~'var(--@{fa-css-prefix}-animation-delay, 0s)';
|
||||
animation-direction: ~'var(--@{fa-css-prefix}-animation-direction, normal)';
|
||||
animation-duration: ~'var(--@{fa-css-prefix}-animation-duration, 1s)';
|
||||
animation-iteration-count: ~'var(--@{fa-css-prefix}-animation-iteration-count, infinite)';
|
||||
animation-timing-function: ~'var(--@{fa-css-prefix}-animation-timing, cubic-bezier(.4,0,.6,1))';
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-beat-fade {
|
||||
animation-name: ~'@{fa-css-prefix}-beat-fade';
|
||||
animation-delay: ~'var(--@{fa-css-prefix}-animation-delay, 0s)';
|
||||
animation-direction: ~'var(--@{fa-css-prefix}-animation-direction, normal)';
|
||||
animation-duration: ~'var(--@{fa-css-prefix}-animation-duration, 1s)';
|
||||
animation-iteration-count: ~'var(--@{fa-css-prefix}-animation-iteration-count, infinite)';
|
||||
animation-timing-function: ~'var(--@{fa-css-prefix}-animation-timing, cubic-bezier(.4,0,.6,1))';
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-flip {
|
||||
animation-name: ~'@{fa-css-prefix}-flip';
|
||||
animation-delay: ~'var(--@{fa-css-prefix}-animation-delay, 0s)';
|
||||
animation-direction: ~'var(--@{fa-css-prefix}-animation-direction, normal)';
|
||||
animation-duration: ~'var(--@{fa-css-prefix}-animation-duration, 1s)';
|
||||
animation-iteration-count: ~'var(--@{fa-css-prefix}-animation-iteration-count, infinite)';
|
||||
animation-timing-function: ~'var(--@{fa-css-prefix}-animation-timing, ease-in-out)';
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-shake {
|
||||
animation-name: ~'@{fa-css-prefix}-shake';
|
||||
animation-delay: ~'var(--@{fa-css-prefix}-animation-delay, 0s)';
|
||||
animation-direction: ~'var(--@{fa-css-prefix}-animation-direction, normal)';
|
||||
animation-duration: ~'var(--@{fa-css-prefix}-animation-duration, 1s)';
|
||||
animation-iteration-count: ~'var(--@{fa-css-prefix}-animation-iteration-count, infinite)';
|
||||
animation-timing-function: ~'var(--@{fa-css-prefix}-animation-timing, ease-in-out)';
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-spin {
|
||||
animation-name: ~'@{fa-css-prefix}-spin';
|
||||
animation-delay: ~'var(--@{fa-css-prefix}-animation-delay, 0s)';
|
||||
animation-direction: ~'var(--@{fa-css-prefix}-animation-direction, normal)';
|
||||
animation-duration: ~'var(--@{fa-css-prefix}-animation-duration, 2s)';
|
||||
animation-iteration-count: ~'var(--@{fa-css-prefix}-animation-iteration-count, infinite)';
|
||||
animation-timing-function: ~'var(--@{fa-css-prefix}-animation-timing, linear)';
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-spin-reverse {
|
||||
--@{fa-css-prefix}-animation-direction: reverse;
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-pulse,
|
||||
.@{fa-css-prefix}-spin-pulse {
|
||||
animation-name: ~'@{fa-css-prefix}-spin';
|
||||
animation-direction: ~'var(--@{fa-css-prefix}-animation-direction, normal)';
|
||||
animation-duration: ~'var(--@{fa-css-prefix}-animation-duration, 1s)';
|
||||
animation-iteration-count: ~'var(--@{fa-css-prefix}-animation-iteration-count, infinite)';
|
||||
animation-timing-function: ~'var(--@{fa-css-prefix}-animation-timing, steps(8));';
|
||||
}
|
||||
|
||||
// if agent or operating system prefers reduced motion, disable animations
|
||||
// see: https://www.smashingmagazine.com/2020/09/design-reduced-motion-sensitivities/
|
||||
// see: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.@{fa-css-prefix}-beat,
|
||||
.@{fa-css-prefix}-bounce,
|
||||
.@{fa-css-prefix}-fade,
|
||||
.@{fa-css-prefix}-beat-fade,
|
||||
.@{fa-css-prefix}-flip,
|
||||
.@{fa-css-prefix}-pulse,
|
||||
.@{fa-css-prefix}-shake,
|
||||
.@{fa-css-prefix}-spin,
|
||||
.@{fa-css-prefix}-spin-pulse {
|
||||
animation-delay: -1ms;
|
||||
animation-duration: 1ms;
|
||||
animation-iteration-count: 1;
|
||||
transition-delay: 0s;
|
||||
transition-duration: 0s;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ~'@{fa-css-prefix}-beat' {
|
||||
0%, 90% { transform: scale(1); }
|
||||
45% { transform: ~'scale(var(--@{fa-css-prefix}-beat-scale, 1.25))'; }
|
||||
}
|
||||
|
||||
@keyframes ~'@{fa-css-prefix}-bounce' {
|
||||
0% { transform: scale(1,1) translateY(0); }
|
||||
10% { transform: ~'scale(var(--#{$fa-css-prefix}-bounce-start-scale-x, 1.1),var(--#{$fa-css-prefix}-bounce-start-scale-y, 0.9))' translateY(0); }
|
||||
30% { transform: ~'scale(var(--#{$fa-css-prefix}-bounce-jump-scale-x, 0.9),var(--#{$fa-css-prefix}-bounce-jump-scale-y, 1.1))' ~'translateY(var(--#{$fa-css-prefix}-bounce-height, -0.5em))'; }
|
||||
50% { transform: ~'scale(var(--#{$fa-css-prefix}-bounce-land-scale-x, 1.05),var(--#{$fa-css-prefix}-bounce-land-scale-y, 0.95))' translateY(0); }
|
||||
57% { transform: ~'scale(1,1) translateY(var(--#{$fa-css-prefix}-bounce-rebound, -0.125em))'; }
|
||||
64% { transform: scale(1,1) translateY(0); }
|
||||
100% { transform: scale(1,1) translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes ~'@{fa-css-prefix}-fade' {
|
||||
50% { opacity: ~'var(--@{fa-css-prefix}-fade-opacity, 0.4)'; }
|
||||
}
|
||||
|
||||
@keyframes ~'@{fa-css-prefix}-beat-fade' {
|
||||
0%, 100% {
|
||||
opacity: ~'var(--@{fa-css-prefix}-beat-fade-opacity, 0.4)';
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: ~'scale(var(--@{fa-css-prefix}-beat-fade-scale, 1.125))';
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ~'@{fa-css-prefix}-flip' {
|
||||
50% {
|
||||
transform: ~'rotate3d(var(--@{fa-css-prefix}-flip-x, 0), var(--@{fa-css-prefix}-flip-y, 1), var(--@{fa-css-prefix}-flip-z, 0), var(--@{fa-css-prefix}-flip-angle, -180deg))';
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ~'@{fa-css-prefix}-shake' {
|
||||
0% { transform: rotate(-15deg); }
|
||||
4% { transform: rotate(15deg); }
|
||||
8%, 24% { transform: rotate(-18deg); }
|
||||
12%, 28% { transform: rotate(18deg); }
|
||||
16% { transform: rotate(-22deg); }
|
||||
20% { transform: rotate(22deg); }
|
||||
32% { transform: rotate(-12deg); }
|
||||
36% { transform: rotate(12deg); }
|
||||
40%, 100% { transform: rotate(0deg); }
|
||||
}
|
||||
|
||||
@keyframes ~'@{fa-css-prefix}-spin' {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// bordered + pulled icons
|
||||
// -------------------------
|
||||
|
||||
.@{fa-css-prefix}-border {
|
||||
border-color: ~'var(--@{fa-css-prefix}-border-color, @{fa-border-color})';
|
||||
border-radius: ~'var(--@{fa-css-prefix}-border-radius, @{fa-border-radius})';
|
||||
border-style: ~'var(--@{fa-css-prefix}-border-style, @{fa-border-style})';
|
||||
border-width: ~'var(--@{fa-css-prefix}-border-width, @{fa-border-width})';
|
||||
padding: ~'var(--@{fa-css-prefix}-border-padding, @{fa-border-padding})';
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-pull-left {
|
||||
float: left;
|
||||
margin-right: ~'var(--@{fa-css-prefix}-pull-margin, @{fa-pull-margin})';
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-pull-right {
|
||||
float: right;
|
||||
margin-left: ~'var(--@{fa-css-prefix}-pull-margin, @{fa-pull-margin})';
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// base icon class definition
|
||||
// -------------------------
|
||||
|
||||
.@{fa-css-prefix} {
|
||||
font-family: ~"var(--@{fa-css-prefix}-style-family, '@{fa-style-family}')";
|
||||
font-weight: ~'var(--@{fa-css-prefix}-style, @{fa-style})';
|
||||
}
|
||||
|
||||
.@{fa-css-prefix},
|
||||
.fas,
|
||||
.@{fa-css-prefix}-solid,
|
||||
.fass,
|
||||
.@{fa-css-prefix}-sharp,
|
||||
.far,
|
||||
.@{fa-css-prefix}-regular,
|
||||
.fab,
|
||||
.@{fa-css-prefix}-brands {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: ~'var(--@{fa-css-prefix}-display, @{fa-display})';
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
text-rendering: auto;
|
||||
}
|
||||
|
||||
.fas,
|
||||
.@{fa-css-prefix}-classic,
|
||||
.@{fa-css-prefix}-solid,
|
||||
.far,
|
||||
.@{fa-css-prefix}-regular {
|
||||
font-family: 'Font Awesome 6 Free';
|
||||
}
|
||||
|
||||
.fab,
|
||||
.@{fa-css-prefix}-brands {
|
||||
font-family: 'Font Awesome 6 Brands';
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// fixed-width icons
|
||||
// -------------------------
|
||||
|
||||
.@{fa-css-prefix}-fw {
|
||||
text-align: center;
|
||||
width: @fa-fw-width;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// specific icon class definition
|
||||
// -------------------------
|
||||
|
||||
/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
|
||||
readers do not read off random characters that represent icons */
|
||||
|
||||
each(.fa-icons(), {
|
||||
.@{fa-css-prefix}-@{key}::before { content: @value; }
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
// icons in a list
|
||||
// -------------------------
|
||||
|
||||
.@{fa-css-prefix}-ul {
|
||||
list-style-type: none;
|
||||
margin-left: ~'var(--@{fa-css-prefix}-li-margin, @{fa-li-margin})';
|
||||
padding-left: 0;
|
||||
|
||||
> li { position: relative; }
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-li {
|
||||
left: calc(~'var(--@{fa-css-prefix}-li-width, @{fa-li-width})' * -1);
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: ~'var(--@{fa-css-prefix}-li-width, @{fa-li-width})';
|
||||
line-height: inherit;
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
// mixins
|
||||
// --------------------------
|
||||
|
||||
// base rendering for an icon
|
||||
.fa-icon() {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: inline-block;
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
font-weight: normal;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
// sets relative font-sizing and alignment (in _sizing)
|
||||
.fa-size(@font-size) {
|
||||
font-size: (@font-size / @fa-size-scale-base) * 1em; // converts step in sizing scale into an em-based value that's relative to the scale's base
|
||||
line-height: (1 / @font-size) * 1em; // sets the line-height of the icon back to that of it's parent
|
||||
vertical-align: ((6 / @font-size) - (3 / 8)) * 1em; // vertically centers the icon taking into account the surrounding text's descender
|
||||
}
|
||||
|
||||
// only display content to screen readers
|
||||
// see: https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/
|
||||
// see: https://hugogiraudel.com/2016/10/13/css-hide-and-seek/
|
||||
.fa-sr-only() {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
// use in conjunction with .sr-only to only display content when it's focused
|
||||
.fa-sr-only-focusable() {
|
||||
&:not(:focus) {
|
||||
.fa-sr-only();
|
||||
}
|
||||
}
|
||||
|
||||
// sets a specific icon family to use alongside style + icon mixins
|
||||
.fa-family-classic() {
|
||||
font-family: 'Font Awesome 6 Free';
|
||||
}
|
||||
|
||||
// convenience mixins for declaring pseudo-elements by CSS variable,
|
||||
// including all style-specific font properties, and both the ::before
|
||||
// and ::after elements in the duotone case.
|
||||
.fa-icon-solid(@fa-var) {
|
||||
.fa-icon;
|
||||
.fa-solid;
|
||||
|
||||
&::before {
|
||||
content: @fa-var;
|
||||
}
|
||||
}
|
||||
|
||||
.fa-icon-regular(@fa-var) {
|
||||
.fa-icon;
|
||||
.fa-regular;
|
||||
|
||||
&::before {
|
||||
content: @fa-var;
|
||||
}
|
||||
}
|
||||
|
||||
.fa-icon-brands(@fa-var) {
|
||||
.fa-icon;
|
||||
.fa-brands;
|
||||
|
||||
&::before {
|
||||
content: @fa-var;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// rotating + flipping icons
|
||||
// -------------------------
|
||||
|
||||
.@{fa-css-prefix}-rotate-90 {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-rotate-180 {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-rotate-270 {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-flip-horizontal {
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-flip-vertical {
|
||||
transform: scale(1, -1);
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-flip-both,
|
||||
.@{fa-css-prefix}-flip-horizontal.@{fa-css-prefix}-flip-vertical {
|
||||
transform: scale(-1, -1);
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-rotate-by {
|
||||
transform: rotate(~'var(--@{fa-css-prefix}-rotate-angle, none)');
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// screen-reader utilities
|
||||
// -------------------------
|
||||
|
||||
// only display content to screen readers
|
||||
.sr-only,
|
||||
.@{fa-css-prefix}-sr-only {
|
||||
.fa-sr-only();
|
||||
}
|
||||
|
||||
// use in conjunction with .sr-only to only display content when it's focused
|
||||
.sr-only-focusable,
|
||||
.@{fa-css-prefix}-sr-only-focusable {
|
||||
.fa-sr-only-focusable();
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,19 @@
|
|||
// sizing icons
|
||||
// -------------------------
|
||||
|
||||
// literal magnification scale
|
||||
.sizes-literal(@factor) when (@factor > 0) {
|
||||
.sizes-literal((@factor - 1));
|
||||
|
||||
.@{fa-css-prefix}-@{factor}x {
|
||||
font-size: (@factor * 1em);
|
||||
}
|
||||
}
|
||||
.sizes-literal(10);
|
||||
|
||||
// step-based scale (with alignment)
|
||||
each(.fa-sizes(), {
|
||||
.@{fa-css-prefix}-@{key} {
|
||||
.fa-size(@value);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,31 @@
|
|||
// stacking icons
|
||||
// -------------------------
|
||||
|
||||
.@{fa-css-prefix}-stack {
|
||||
display: inline-block;
|
||||
height: 2em;
|
||||
line-height: 2em;
|
||||
position: relative;
|
||||
vertical-align: @fa-stack-vertical-align;
|
||||
width: @fa-stack-width;
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x {
|
||||
left: 0;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
z-index: ~'var(--@{fa-css-prefix}-stack-z-index, @{fa-stack-z-index})';
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-stack-1x {
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-stack-2x {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.@{fa-css-prefix}-inverse {
|
||||
color: ~'var(--@{fa-css-prefix}-inverse, @{fa-inverse})';
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,29 @@
|
|||
/*!
|
||||
* Font Awesome Free 6.3.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
@import "_variables.less";
|
||||
|
||||
:root, :host {
|
||||
--@{fa-css-prefix}-style-family-brands: 'Font Awesome 6 Brands';
|
||||
--@{fa-css-prefix}-font-brands: normal 400 1em/1 'Font Awesome 6 Brands';
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Brands';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: @fa-font-display;
|
||||
src: url('@{fa-font-path}/fa-brands-400.woff2') format('woff2'),
|
||||
url('@{fa-font-path}/fa-brands-400.ttf') format('truetype');
|
||||
}
|
||||
|
||||
.fab,
|
||||
.@{fa-css-prefix}-brands {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
each(.fa-brand-icons(), {
|
||||
.@{fa-css-prefix}-@{key}:before { content: @value; }
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
/*!
|
||||
* Font Awesome Free 6.3.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
// Font Awesome core compile (Web Fonts-based)
|
||||
// -------------------------
|
||||
|
||||
@import "_variables.less";
|
||||
@import "_mixins.less";
|
||||
@import "_core.less";
|
||||
@import "_sizing.less";
|
||||
@import "_fixed-width.less";
|
||||
@import "_list.less";
|
||||
@import "_bordered-pulled.less";
|
||||
@import "_animated.less";
|
||||
@import "_rotated-flipped.less";
|
||||
@import "_stacked.less";
|
||||
@import "_icons.less";
|
||||
@import "_screen-reader.less";
|
|
@ -0,0 +1,25 @@
|
|||
/*!
|
||||
* Font Awesome Free 6.3.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2023 Fonticons, Inc.
|
||||
*/
|
||||
@import "_variables.less";
|
||||
|
||||
:root, :host {
|
||||
--@{fa-css-prefix}-style-family-classic: '@{fa-style-family}';
|
||||
--@{fa-css-prefix}-font-regular: normal 400 1em/1 '@{fa-style-family}';
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Free';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: @fa-font-display;
|
||||
src: url('@{fa-font-path}/fa-regular-400.woff2') format('woff2'),
|
||||
url('@{fa-font-path}/fa-regular-400.ttf') format('truetype');
|
||||
}
|
||||
|
||||
.far,
|
||||
.@{fa-css-prefix}-regular {
|
||||
font-weight: 400;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue