first sync
Some checks failed
Deployment Verification / deploy-and-test (push) Failing after 29s

This commit is contained in:
2025-03-04 07:59:21 +01:00
parent 9cdcf486b6
commit 506716e703
1450 changed files with 577316 additions and 62 deletions

View File

@@ -0,0 +1,381 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# Copyright (C) 2021 - Airbus CyberSecurity (SAS)
# ir@cyberactionlab.net
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import marshmallow
# IMPORTS ------------------------------------------------
from datetime import datetime
from datetime import timedelta
from flask import Blueprint
from flask import redirect
from flask import render_template
from flask import request
from flask import session
from flask import url_for
from flask_login import current_user
from flask_login import logout_user
from flask_wtf import FlaskForm
from sqlalchemy import distinct
from app import app
from app import db
from app.datamgmt.dashboard.dashboard_db import get_global_task, list_user_cases, list_user_reviews
from app.datamgmt.dashboard.dashboard_db import get_tasks_status
from app.datamgmt.dashboard.dashboard_db import list_global_tasks
from app.datamgmt.dashboard.dashboard_db import list_user_tasks
from app.forms import CaseGlobalTaskForm
from app.iris_engine.module_handler.module_handler import call_modules_hook
from app.iris_engine.utils.tracker import track_activity
from app.models.authorization import User
from app.models.cases import Cases
from app.models.models import CaseTasks
from app.models.models import GlobalTasks
from app.models.models import TaskStatus
from app.models.models import UserActivity
from app.schema.marshables import CaseTaskSchema, CaseSchema, CaseDetailsSchema
from app.schema.marshables import GlobalTasksSchema
from app.util import ac_api_requires
from app.util import ac_requires
from app.util import not_authenticated_redirection_url
from app.util import response_error
from app.util import response_success
# CONTENT ------------------------------------------------
dashboard_blueprint = Blueprint(
'index',
__name__,
template_folder='templates'
)
# Logout user
@dashboard_blueprint.route('/logout')
def logout():
"""
Logout function. Erase its session and redirect to index i.e login
:return: Page
"""
if session['current_case']:
current_user.ctx_case = session['current_case']['case_id']
current_user.ctx_human_case = session['current_case']['case_name']
db.session.commit()
track_activity("user '{}' has been logged-out".format(current_user.user), ctx_less=True, display_in_ui=False)
logout_user()
return redirect(not_authenticated_redirection_url(request_url='/'))
@dashboard_blueprint.route('/dashboard/case_charts', methods=['GET'])
@ac_api_requires()
def get_cases_charts(caseid):
"""
Get case charts
:return: JSON
"""
res = Cases.query.with_entities(
Cases.open_date
).filter(
Cases.open_date > (datetime.utcnow() - timedelta(days=365))
).order_by(
Cases.open_date
).all()
retr = [[], []]
rk = {}
for case in res:
month = "{}/{}/{}".format(case.open_date.day, case.open_date.month, case.open_date.year)
if month in rk:
rk[month] += 1
else:
rk[month] = 1
retr = [list(rk.keys()), list(rk.values())]
return response_success("", retr)
@dashboard_blueprint.route('/')
def root():
if app.config['DEMO_MODE_ENABLED'] == 'True':
return redirect(url_for('demo-landing.demo_landing'))
return redirect(url_for('index.index'))
@dashboard_blueprint.route('/dashboard')
@ac_requires()
def index(caseid, url_redir):
"""
Index page. Load the dashboard data, create the add customer form
:return: Page
"""
if url_redir:
return redirect(url_for('index.index', cid=caseid if caseid is not None else 1, redirect=True))
msg = None
# Retrieve the dashboard data from multiple sources.
# Quite fast as it is only counts.
user_open_case = Cases.query.filter(
Cases.owner_id == current_user.id,
Cases.close_date == None
).count()
data = {
"user_open_count": user_open_case,
"cases_open_count": Cases.query.filter(Cases.close_date == None).count(),
"cases_count": Cases.query.with_entities(distinct(Cases.case_id)).count(),
}
# Create the customer form to be able to quickly add a customer
form = FlaskForm()
return render_template('index.html', data=data, form=form, msg=msg)
@dashboard_blueprint.route('/global/tasks/list', methods=['GET'])
@ac_api_requires()
def get_gtasks(caseid):
tasks_list = list_global_tasks()
if tasks_list:
output = [c._asdict() for c in tasks_list]
else:
output = []
ret = {
"tasks_status": get_tasks_status(),
"tasks": output
}
return response_success("", data=ret)
@dashboard_blueprint.route('/user/cases/list', methods=['GET'])
@ac_api_requires()
def list_own_cases(caseid):
cases = list_user_cases(
request.args.get('show_closed', 'false', type=str).lower() == 'true'
)
return response_success("", data=CaseDetailsSchema(many=True).dump(cases))
@dashboard_blueprint.route('/global/tasks/<int:cur_id>', methods=['GET'])
@ac_api_requires()
def view_gtask(cur_id, caseid):
task = get_global_task(task_id=cur_id)
if not task:
return response_error(f'Global task ID {cur_id} not found')
return response_success("", data=task._asdict())
@dashboard_blueprint.route('/user/tasks/list', methods=['GET'])
@ac_api_requires()
def get_utasks(caseid):
ct = list_user_tasks()
if ct:
output = [c._asdict() for c in ct]
else:
output = []
ret = {
"tasks_status": get_tasks_status(),
"tasks": output
}
return response_success("", data=ret)
@dashboard_blueprint.route('/user/reviews/list', methods=['GET'])
@ac_api_requires()
def get_reviews(caseid):
ct = list_user_reviews()
if ct:
output = [c._asdict() for c in ct]
else:
output = []
return response_success("", data=output)
@dashboard_blueprint.route('/user/tasks/status/update', methods=['POST'])
@ac_api_requires()
def utask_statusupdate(caseid):
jsdata = request.get_json()
if not jsdata:
return response_error("Invalid request")
jsdata = request.get_json()
if not jsdata:
return response_error("Invalid request")
case_id = jsdata.get('case_id') if jsdata.get('case_id') else caseid
task_id = jsdata.get('task_id')
task = CaseTasks.query.filter(CaseTasks.id == task_id, CaseTasks.task_case_id == case_id).first()
if not task:
return response_error(f"Invalid case task ID {task_id} for case {case_id}")
status_id = jsdata.get('task_status_id')
status = TaskStatus.query.filter(TaskStatus.id == status_id).first()
if not status:
return response_error(f"Invalid task status ID {status_id}")
task.task_status_id = status_id
try:
db.session.commit()
except Exception as e:
return response_error(f"Unable to update task. Error {e}")
task_schema = CaseTaskSchema()
return response_success("Updated", data=task_schema.dump(task))
@dashboard_blueprint.route('/global/tasks/add/modal', methods=['GET'])
@ac_api_requires()
def add_gtask_modal(caseid):
task = GlobalTasks()
form = CaseGlobalTaskForm()
form.task_assignee_id.choices = [(user.id, user.name) for user in User.query.filter(User.active == True).order_by(User.name).all()]
form.task_status_id.choices = [(a.id, a.status_name) for a in get_tasks_status()]
return render_template("modal_add_global_task.html", form=form, task=task, uid=current_user.id, user_name=None)
@dashboard_blueprint.route('/global/tasks/add', methods=['POST'])
@ac_api_requires()
def add_gtask(caseid):
try:
gtask_schema = GlobalTasksSchema()
request_data = call_modules_hook('on_preload_global_task_create', data=request.get_json(), caseid=caseid)
gtask = gtask_schema.load(request_data)
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
gtask.task_userid_update = current_user.id
gtask.task_open_date = datetime.utcnow()
gtask.task_last_update = datetime.utcnow()
gtask.task_last_update = datetime.utcnow()
try:
db.session.add(gtask)
db.session.commit()
except Exception as e:
return response_error(msg="Data error", data=e.__str__(), status=400)
gtask = call_modules_hook('on_postload_global_task_create', data=gtask, caseid=caseid)
track_activity("created new global task \'{}\'".format(gtask.task_title), caseid=caseid)
return response_success('Task added', data=gtask_schema.dump(gtask))
@dashboard_blueprint.route('/global/tasks/update/<int:cur_id>/modal', methods=['GET'])
@ac_api_requires()
def edit_gtask_modal(cur_id, caseid):
form = CaseGlobalTaskForm()
task = GlobalTasks.query.filter(GlobalTasks.id == cur_id).first()
form.task_assignee_id.choices = [(user.id, user.name) for user in
User.query.filter(User.active == True).order_by(User.name).all()]
form.task_status_id.choices = [(a.id, a.status_name) for a in get_tasks_status()]
# Render the task
form.task_title.render_kw = {'value': task.task_title}
form.task_description.data = task.task_description
user_name, = User.query.with_entities(User.name).filter(User.id == task.task_userid_update).first()
return render_template("modal_add_global_task.html", form=form, task=task,
uid=task.task_assignee_id, user_name=user_name)
@dashboard_blueprint.route('/global/tasks/update/<int:cur_id>', methods=['POST'])
@ac_api_requires()
def edit_gtask(cur_id, caseid):
form = CaseGlobalTaskForm()
task = GlobalTasks.query.filter(GlobalTasks.id == cur_id).first()
form.task_assignee_id.choices = [(user.id, user.name) for user in User.query.filter(User.active == True).order_by(User.name).all()]
form.task_status_id.choices = [(a.id, a.status_name) for a in get_tasks_status()]
if not task:
return response_error(msg="Data error", data="Invalid task ID", status=400)
try:
gtask_schema = GlobalTasksSchema()
request_data = call_modules_hook('on_preload_global_task_update', data=request.get_json(),
caseid=caseid)
gtask = gtask_schema.load(request_data, instance=task)
gtask.task_userid_update = current_user.id
gtask.task_last_update = datetime.utcnow()
db.session.commit()
gtask = call_modules_hook('on_postload_global_task_update', data=gtask, caseid=caseid)
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
track_activity("updated global task {} (status {})".format(task.task_title, task.task_status_id), caseid=caseid)
return response_success('Task updated', data=gtask_schema.dump(gtask))
@dashboard_blueprint.route('/global/tasks/delete/<int:cur_id>', methods=['POST'])
@ac_api_requires()
def gtask_delete(cur_id, caseid):
call_modules_hook('on_preload_global_task_delete', data=cur_id, caseid=caseid)
if not cur_id:
return response_error("Missing parameter")
data = GlobalTasks.query.filter(GlobalTasks.id == cur_id).first()
if not data:
return response_error("Invalid global task ID")
GlobalTasks.query.filter(GlobalTasks.id == cur_id).delete()
db.session.commit()
call_modules_hook('on_postload_global_task_delete', data=request.get_json(), caseid=caseid)
track_activity("deleted global task ID {}".format(cur_id), caseid=caseid)
return response_success("Task deleted")

View File

@@ -0,0 +1,397 @@
{% extends "layouts/default.html" %}
{% block title %} Dashboard {% endblock title %}
{% block stylesheets %}
<link rel="stylesheet" href="/static/assets/css/suggestags.css">
<link href="/static/assets/css/dataTables.contextualActions.min.css" rel="stylesheet">
<link href="/static/assets/css/dataTables.select.min.css" rel="stylesheet">
{% endblock stylesheets %}
{% block content %}
<div class="panel-header bg-primary-gradient mt--4">
<div class="page-inner py-5">
<div class="d-flex align-items-left align-items-md-center flex-column flex-md-row ">
<div>
<h2 class="text-white pb-2 fw-bold">Dashboard</h2>
</div>
<div class="ml-md-auto py-2 py-md-0">
<a href="/manage/cases?cid={{session['current_case'].case_id}}" class="btn btn-white btn-border btn-round mr-2">
<span class="btn-label">
<i class="fa fa-plus"></i>
</span>
Add case
</a>
</div>
</div>
<div class="chart-container mt--2 mb--2">
<canvas id="htmlLegendsChart" style="display: block; width: auto; height: 100px;" width="auto" height="100px" class="chartjs-render-monitor"></canvas>
</div>
</div>
</div>
<div class="page-inner mt--5">
<div class="row row-card-no-pd">
<div class="col-sm-6 col-md-4">
<div class="card card-stats card-round">
<div class="card-body ">
<div class="row">
<div class="col-5">
<div class="icon-big text-center">
<i class="flaticon-file-1 text-success"></i>
</div>
</div>
<div class="col-7 col-stats">
<div class="numbers">
<p class="card-category">Cases (open / all)</p>
<h4 class="card-title">{{ data.cases_open_count }} / {{ data.cases_count }}</h4>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4">
<div class="card card-stats card-round">
<div class="card-body ">
<div class="row">
<div class="col-5">
<div class="icon-big text-center">
<i class="flaticon-suitcase text-warning"></i>
</div>
</div>
<div class="col-7 col-stats">
<div class="numbers">
<p class="card-category">Attributed open cases</p>
<h4 class="card-title">{{ data.user_open_count }}</h4>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4">
<div class="card card-stats card-round">
<div class="card-body ">
<div class="row">
<div class="col-5">
<div class="icon-big text-center">
<i id='icon_user_task' class=""></i>
</div>
</div>
<div class="col-7 col-stats">
<div class="numbers">
<p class="card-category">Attributed open tasks</p>
<h4 class="card-title" id="user_attr_count"></h4>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row" id="rowPendingCasesReview" style="display: none;">
<div class="col-md-12">
<section class="card">
<div class="card-header">
<div class="card-title">Attributed cases review
<div class="text-faded float-right">
<small id="ureviews_last_updated"></small>
<button type="button" class="btn btn-xs btn-dark ml-2"
onclick="update_ureviews_list();">Refresh
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="table-responsive" id="ureviews_table_wrapper">
<table class="table display table-striped table-hover" width="100%" cellspacing="0" id="ureview_table" >
<thead>
<tr>
<th>Case name</th>
<th>Review Status</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Case name</th>
<th>Review Status</th>
</tr>
</tfoot>
</table>
</div>
</div>
</section>
</div>
</div>
<div class="row">
<div class="col-md-12">
{{ form.hidden_tag() }}
<section class="card">
<div class="card-header">
<div class="card-title">Attributed open tasks
<div class="text-faded float-right">
<small id="utasks_last_updated"></small>
<button type="button" class="btn btn-xs btn-dark ml-2"
onclick="update_utasks_list();">Refresh
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="table-responsive" id="utasks_table_wrapper">
<table class="table display table-striped table-hover" width="100%" cellspacing="0" id="utasks_table" >
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Status</th>
<th>Case</th>
<th>Last update</th>
<th>Tags</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Title</th>
<th>Description</th>
<th>Status</th>
<th>Case</th>
<th>Last update</th>
<th>Tags</th>
</tr>
</tfoot>
</table>
</div>
</div>
</section>
</div>
</div>
<div class="row">
<div class="col-md-12">
<section class="card">
<div class="card-header">
<div class="card-title">Attributed open cases
<div class="text-faded float-right">
<small id="ucases_last_updated"></small>
<button type="button" class="btn btn-xs btn-dark ml-2"
onclick="update_ucases_list(true);">Show closed cases
</button>
<button type="button" class="btn btn-xs btn-dark ml-2"
onclick="update_ucases_list();">Refresh
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="table-responsive" id="ucases_table_wrapper">
<table class="table display table-striped table-hover" width="100%" cellspacing="0" id="ucases_table" >
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Client</th>
<th>Opening date</th>
<th>Tags</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Title</th>
<th>Description</th>
<th>Client</th>
<th>Opening date</th>
<th>Tags</th>
</tr>
</tfoot>
</table>
</div>
</div>
</section>
</div>
</div>
<div class="row">
<div class="col-md-12">
<section class="card">
<div class="card-header">
<div class="card-title">Global tasks
<div class="text-faded float-right">
<small id="tasks_last_updated"></small>
<button type="button" class="btn btn-xs btn-dark ml-2"
onclick="add_gtask();">
Add global task
</button>
<button type="button" class="btn btn-xs btn-dark ml-2"
onclick="update_gtasks_list();">Refresh
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="table-responsive" id="gtasks_table_wrapper">
<table class="table display table-striped table-hover" width="100%" cellspacing="0" id="gtasks_table" >
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Status</th>
<th>Assigned to</th>
<th>Last update</th>
<th>Tags</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Title</th>
<th>Description</th>
<th>Status</th>
<th>Assigned to</th>
<th>Last update</th>
<th>Tags</th>
</tr>
</tfoot>
</table>
</div>
</div>
</section>
</div>
</div>
</div>
<div class="modal " tabindex="-1" role="dialog" id="modal_add_gtask" data-backdrop="true">
<div class="modal-xl modal-dialog" role="document">
<div class="modal-content" id="modal_add_gtask_content">
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
{% endblock content %}
{% block javascripts %}
<script src="/static/assets/js/plugin/tagsinput/suggesttag.js"></script>
<script src="/static/assets/js/plugin/select/select2.js"></script>
<script src="/static/assets/js/plugin/datatables/dataTables.cellEdit.js"></script>
<script src="/static/assets/js/plugin/datatables/dataTables.buttons.min.js"></script>
<script src="/static/assets/js/plugin/datatables/dataTables.contextualActions.min.js"></script>
<script src="/static/assets/js/plugin/datatables/dataTables.select.min.js"></script>
<script src="/static/assets/js/plugin/datatables/buttons.html5.min.js"></script>
<script src="/static/assets/js/plugin/datatables/buttons.print.min.js"></script>
<script src="/static/assets/js/iris/dashboard.js"></script>
<script src="/static/assets/js/core/charts.js"></script>
<script>
htmlLegendsChart = document.getElementById('htmlLegendsChart').getContext('2d');
$.ajax({
url: '/dashboard/case_charts' + case_param(),
type: "GET",
dataType: "JSON",
success: function (data) {
jsdata = data;
if (jsdata.status == "success") {
// Chart with HTML Legends
var gradientStroke = htmlLegendsChart.createLinearGradient(500, 0, 100, 0);
gradientStroke.addColorStop(0, '#177dff');
gradientStroke.addColorStop(1, '#80b6f4');
var gradientFill = htmlLegendsChart.createLinearGradient(500, 0, 100, 0);
gradientFill.addColorStop(0, "rgba(23, 125, 255, 0.7)");
gradientFill.addColorStop(1, "rgba(128, 182, 244, 0.3)");
var myHtmlLegendsChart = new Chart(htmlLegendsChart, {
type: 'line',
data: {
labels: jsdata.data[0],
datasets: [ {
label: "Open case",
borderColor: gradientStroke,
pointBackgroundColor: gradientStroke,
pointRadius: 0,
backgroundColor: gradientFill,
legendColor: '#fff',
fill: true,
borderWidth: 1,
data: jsdata.data[1]
}]
},
options : {
responsive: true,
maintainAspectRatio: false,
legend: {
display: false
},
tooltips: {
bodySpacing: 4,
mode:"nearest",
intersect: 0,
position:"nearest",
xPadding:10,
yPadding:10,
caretPadding:10
},
layout:{
padding:{left:15,right:15,top:15,bottom:15}
},
scales: {
yAxes: [{
ticks: {
display: false
},
gridLines: {
drawTicks: false,
display: false
}
}],
xAxes: [{
gridLines: {
zeroLineColor: "transparent",
display: false
},
ticks: {
padding: 20,
fontColor: "rgba(0,0,0,0.5)",
fontStyle: "500",
display: false
}
}]
},
legendCallback: function(chart) {
var text = [];
text.push('<ul class="' + chart.id + '-legend html-legend">');
for (var i = 0; i < chart.data.datasets.length; i++) {
text.push('<li><span style="background-color:' + chart.data.datasets[i].legendColor + '"></span>');
if (chart.data.datasets[i].label) {
text.push(chart.data.datasets[i].label);
}
text.push('</li>');
}
text.push('</ul>');
return text.join('');
}
}
});
//var myLegendContainer = document.getElementById("myChartLegend");
// generate HTML legend
//myLegendContainer.innerHTML = myHtmlLegendsChart.generateLegend();
}
},
error: function (error) {
notify_error(error);
}
});
</script>
{% endblock javascripts %}

View File

@@ -0,0 +1,97 @@
<div class="modal-header">
<div class="col md-12">
<div class="row">
<div class="col">
<h4 class="modal-title mt-1 mr-4">{% if task.id %} Task ID #{{ task.id }}{% else %} Add global task {% endif %}
{% if task.id %}
<i class="fas fa-info-circle ml-3" data-toggle="popover"
title="Task info"
data-content="Last updated {{ task.task_last_update }} by {{ user_name }}."></i>
{% endif %}
</h4>
<small><i class="text-muted">{% if task.task_uuid %}#{{ task.task_uuid }}{% endif %}</i></small>
</div>
<div class="col ">
<div class="row float-right">
<button type="button" class="float-right btn bg-transparent" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true"><i class="fa fa-times"></i></span></button>
</div>
</div>
</div>
</div>
</div>
<div class="modal-body">
<div class="container col-md-12">
<form method="post" action="" id="form_new_gtask">
<div class="col-md-12 col-lg-12 col-sm-12">
{{ form.hidden_tag() }}
<div class="form-group mt-3">
<label for="task_assignee" class="placeholder">Assigned to</label>
{{ form.task_assignee_id(class="selectpicker pl--6 col-5") }}
<label for="task_status" class="placeholder">Status</label>
{{ form.task_status_id(class="selectpicker pl--6 col-5") }}
</div>
<div class="form-group">
<label for="task_title" class="placeholder">{{ form.task_title.label.text }} *</label>
{{ form.task_title(class='form-control col-md-12 col-sm-12', autocomplete="off") }}
</div>
<div class="form-group mt-3">
<label for="task_description" class="placeholder">{{ form.task_description.label.text }}</label>
{{ form.task_description(class='form-control col-md-12 col-sm-12 sizable-textarea', autocomplete="off") }}
</div>
<div class="form-group mt-3">
<label for="task_tags">Task tags
</label>
<input type="text" id="task_tags"
class="form-control col-md-12" {% if task.task_tags %} value="{{ task.task_tags }}" {% endif %}/>
</div>
</div>
{% if task.id %}
<button type="button" class="btn btn-outline-danger mt-5"
onclick="delete_gtask({{ task.id }});">Delete</button>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
onclick="update_gtask({{ task.id }});">Update</button>
{% else %}
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_gtask">Save</button>
{% endif %}
</form>
</div>
</div>
<script>
$('#task_tags').amsifySuggestags({
printValues: false,
suggestions: [ {% for tag in tags %} '{{ tag }}', {% endfor %} ]
});
$('#task_assignee_id').selectpicker({
liveSearch: true,
title: "None",
style: "Bootstrap 4: 'btn-outline-primary'",
});
$('#task_status_id').selectpicker({
liveSearch: true,
title: "None",
style: "Bootstrap 4: 'btn-outline-primary'",
});
{% if uid %}
$('#task_assignee_id').selectpicker('val', '{{uid}}');
{% endif %}
{% if task.task_status_id %}
$('#task_status_id').selectpicker('val', '{{task.task_status_id}}');
{% else %}
$('#task_status_id').selectpicker('val', '1');
{% endif %}
$('[data-toggle="popover"]').popover();
</script>