This commit is contained in:
381
iris-web/source/app/blueprints/dashboard/dashboard_routes.py
Normal file
381
iris-web/source/app/blueprints/dashboard/dashboard_routes.py
Normal 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")
|
397
iris-web/source/app/blueprints/dashboard/templates/index.html
Normal file
397
iris-web/source/app/blueprints/dashboard/templates/index.html
Normal 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 %}
|
@@ -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>
|
||||
|
Reference in New Issue
Block a user