Initial Upload
This commit is contained in:
112
reports/apirequests-email-template.twig
Normal file
112
reports/apirequests-email-template.twig
Normal file
@@ -0,0 +1,112 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "base-report.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span>
|
||||
</div>
|
||||
<p></p>
|
||||
|
||||
<table class="saved-report-table">
|
||||
{% if metadata.logType == 'audit' %}
|
||||
<tr>
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "User Name" %}</th>
|
||||
<th>{% trans "User ID" %}</th>
|
||||
<th>{% trans "Application" %}</th>
|
||||
<th>{% trans "Request ID" %}</th>
|
||||
<th>{% trans "Method" %}</th>
|
||||
<th>{% trans "Url" %}</th>
|
||||
<th>{% trans "Entity" %}</th>
|
||||
<th>{% trans "Entity ID" %}</th>
|
||||
<th>{% trans "Message" %}</th>
|
||||
<th>{% trans "Details" %}</th>
|
||||
</tr>
|
||||
{% for item in tableData %}
|
||||
<tr>
|
||||
<td>{{ item.logDate }}</td>
|
||||
<td>{{ item.userName }}</td>
|
||||
<td>{{ item.userId }}</td>
|
||||
<td>{{ item.applicationName }}</td>
|
||||
<td>{{ item.requestId }}</td>
|
||||
<td>{{ item.method }}</td>
|
||||
<td>{{ item.url }}</td>
|
||||
<td>{{ item.entity }}</td>
|
||||
<td>{{ item.entityId }}</td>
|
||||
<td>{{ item.message }}</td>
|
||||
<td>{{ item.objectAfter }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% elseif metadata.logType == 'debug' %}
|
||||
<tr>
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "UserName" %}</th>
|
||||
<th>{% trans "User ID" %}</th>
|
||||
<th>{% trans "Application" %}</th>
|
||||
<th>{% trans "Request ID" %}</th>
|
||||
<th>{% trans "Method" %}</th>
|
||||
<th>{% trans "Url" %}</th>
|
||||
<th>{% trans "Level" %}</th>
|
||||
<th>{% trans "Details" %}</th>
|
||||
</tr>
|
||||
{% for item in tableData %}
|
||||
<tr>
|
||||
<td>{{ item.logDate }}</td>
|
||||
<td>{{ item.userName }}</td>
|
||||
<td>{{ item.userId }}</td>
|
||||
<td>{{ item.applicationName }}</td>
|
||||
<td>{{ item.requestId }}</td>
|
||||
<td>{{ item.method }}</td>
|
||||
<td>{{ item.url }}</td>
|
||||
<td>{{ item.type }}</td>
|
||||
<td>{{ item.message }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "UserName" %}</th>
|
||||
<th>{% trans "User ID" %}</th>
|
||||
<th>{% trans "Application" %}</th>
|
||||
<th>{% trans "Request ID" %}</th>
|
||||
<th>{% trans "Method" %}</th>
|
||||
<th>{% trans "Url" %}</th>
|
||||
</tr>
|
||||
{% for item in tableData %}
|
||||
<tr>
|
||||
<td>{{ item.startTime }}</td>
|
||||
<td>{{ item.userName }}</td>
|
||||
<td>{{ item.userId }}</td>
|
||||
<td>{{ item.applicationName }}</td>
|
||||
<td>{{ item.requestId }}</td>
|
||||
<td>{{ item.method }}</td>
|
||||
<td>{{ item.url }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</table>
|
||||
<br/>
|
||||
<span>{{ placeholder }}</span>
|
||||
<img src="{{ src|raw }}" >
|
||||
{% endblock %}
|
||||
390
reports/apirequests-report-form.twig
Normal file
390
reports/apirequests-report-form.twig
Normal file
@@ -0,0 +1,390 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block title %}{% trans "Report: API Requests History" %} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
{% include "report-schedule-buttons.twig" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<span>{% trans "API Requests History" %}</span>
|
||||
</div>
|
||||
|
||||
{% include "report-selector.twig" %}
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-refresh-on-form-submit="false">
|
||||
<div class="XiboFilterCustom">
|
||||
<div class="FilterDiv card-body" id="apiRequestsHistoryFilter">
|
||||
<form class="form-inline">
|
||||
{% set title %}{% trans "Range" %}{% endset %}
|
||||
{{ inline.dateRangeFilter("reportFilter", title, "", "", "", "", "") }}
|
||||
|
||||
{% set title %}{% trans "User" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("user.search") },
|
||||
{ name: "data-search-term", value: "userName" },
|
||||
{ name: "data-id-property", value: "userId" },
|
||||
{ name: "data-text-property", value: "userName" },
|
||||
] %}
|
||||
{{ inline.dropdown("userId", "single", title, "", null, "userId", "userName", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Request ID" %}{% endset %}
|
||||
{{ inline.number('requestId', title) }}
|
||||
|
||||
{% set title = "Report Type"|trans %}
|
||||
{% set options = [
|
||||
{ id: 'requests', value: "Requests"|trans },
|
||||
{ id: 'audit', value: "Audit"|trans },
|
||||
{ id: 'debug', value: "Debug"|trans },
|
||||
] %}
|
||||
{{ inline.dropdown("type", "single", title, "requests", options, "id", "value", helpText) }}
|
||||
|
||||
<div class="w-100">
|
||||
<a id="applyBtn" class="btn btn-success">
|
||||
<span>{% trans "Apply" %}</span>
|
||||
</a>
|
||||
<span id="imageLoader" class=""></span>
|
||||
<span id="applyWarning" class="text-warning" style="display:none; padding-left: 10px">{% trans "Warning: This may return a lot of data and may take several minutes to process." %}</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card Body -->
|
||||
<div class="card-body api-requests-history-audit">
|
||||
<!-- DATATABLE -->
|
||||
<div class="table-container" id="table_wrapper">
|
||||
<table id="apiRequestsHistoryAuditGrid"
|
||||
class="table xibo-table table-striped"
|
||||
style="width: 100%"
|
||||
data-url="{{ url_for("report.data", {name: 'apirequests'}) }}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "User Name" %}</th>
|
||||
<th>{% trans "User ID" %}</th>
|
||||
<th>{% trans "Application" %}</th>
|
||||
<th>{% trans "Request ID" %}</th>
|
||||
<th>{% trans "Method" %}</th>
|
||||
<th>{% trans "Url" %}</th>
|
||||
<th>{% trans "Entity" %}</th>
|
||||
<th>{% trans "Entity ID" %}</th>
|
||||
<th>{% trans "Message" %}</th>
|
||||
<th>{% trans "Details" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body api-requests-history-log d-none">
|
||||
<!-- DATATABLE -->
|
||||
<div class="table-container" id="table_wrapper">
|
||||
<table id="apiRequestsHistoryLogGrid"
|
||||
class="table xibo-table table-striped"
|
||||
style="width: 100%"
|
||||
data-url="{{ url_for("report.data", {name: 'apirequests'}) }}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "UserName" %}</th>
|
||||
<th>{% trans "User ID" %}</th>
|
||||
<th>{% trans "Application" %}</th>
|
||||
<th>{% trans "Request ID" %}</th>
|
||||
<th>{% trans "Method" %}</th>
|
||||
<th>{% trans "Url" %}</th>
|
||||
<th>{% trans "Level" %}</th>
|
||||
<th>{% trans "Details" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body api-requests-history d-none">
|
||||
<!-- DATATABLE -->
|
||||
<div class="table-container" id="table_wrapper">
|
||||
<table id="apiRequestsHistoryGrid"
|
||||
class="table xibo-table table-striped"
|
||||
style="width: 100%"
|
||||
data-url="{{ url_for("report.data", {name: 'apirequests'}) }}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "UserName" %}</th>
|
||||
<th>{% trans "User ID" %}</th>
|
||||
<th>{% trans "Application" %}</th>
|
||||
<th>{% trans "Request ID" %}</th>
|
||||
<th>{% trans "Method" %}</th>
|
||||
<th>{% trans "Url" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
{% verbatim %}
|
||||
<script type="text/x-handlebars-template" id="table-array-viewer">
|
||||
<a class="arrayViewerToggle" href="#"><span class="fa fa-search"></span></a>
|
||||
<table class="arrayViewer table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ col1 }}</th>
|
||||
<th>{{ col2 }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each items}}
|
||||
<tr>
|
||||
<td>{{ @key }}</td>
|
||||
<td>{{ this }}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</script>
|
||||
{% endverbatim %}
|
||||
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
|
||||
$(function() {
|
||||
var arrayViewer = Handlebars.compile($('#table-array-viewer').html());
|
||||
|
||||
$('[data-toggle="popover"]').popover();
|
||||
let $report = $('#apiRequestsHistoryFilter');
|
||||
let $auditDataTable = $('#apiRequestsHistoryAuditGrid');
|
||||
let auditTable = createAuditTable($auditDataTable);
|
||||
let $logDataTable = $('#apiRequestsHistoryLogGrid');
|
||||
let logTable = createLogTable($logDataTable);
|
||||
let $requestsDataTable = $('#apiRequestsHistoryGrid');
|
||||
let requestsTable = createRequestsTable($requestsDataTable);
|
||||
|
||||
let result; // XHR get data result
|
||||
let reportType = $report.find('#type').val();
|
||||
|
||||
let imageLoader = $("#imageLoader");
|
||||
let $warning = $("#applyWarning");
|
||||
let $applyBtn = $("#applyBtn");
|
||||
|
||||
// Get Data
|
||||
function getData(url) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
data: $report.find('form').serialize(),
|
||||
success: function success(data) {
|
||||
result = data;
|
||||
$applyBtn.removeClass('disabled');
|
||||
imageLoader.removeClass('fa fa-spinner fa-spin loading-icon');
|
||||
|
||||
if (reportType === 'audit') {
|
||||
$('.api-requests-history-audit').removeClass('d-none');
|
||||
$('.api-requests-history-log').addClass('d-none');
|
||||
$('.api-requests-history').addClass('d-none');
|
||||
setTabularData(auditTable, result.table);
|
||||
} else if (reportType === 'debug') {
|
||||
$('.api-requests-history-log').removeClass('d-none');
|
||||
$('.api-requests-history-audit').addClass('d-none');
|
||||
$('.api-requests-history').addClass('d-none');
|
||||
setTabularData(logTable, result.table);
|
||||
} else {
|
||||
$('.api-requests-history').removeClass('d-none');
|
||||
$('.api-requests-history-audit').addClass('d-none');
|
||||
$('.api-requests-history-log').addClass('d-none');
|
||||
setTabularData(requestsTable, result.table);
|
||||
}
|
||||
},
|
||||
error: function error(xhr, textStatus, _error) {
|
||||
$applyBtn.removeClass('disabled');
|
||||
toastr.error(xhr.responseJSON.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setTabularData(table, data) {
|
||||
table.clear().draw();
|
||||
|
||||
if (Object.keys(data).length > 0) {
|
||||
table.rows.add( data ).draw()
|
||||
}
|
||||
|
||||
if ($report.find('#type').val() === 'audit') {
|
||||
$('.arrayViewerToggle').click(function () {
|
||||
$(this).parent().find('.arrayViewer').toggle();
|
||||
});
|
||||
}
|
||||
|
||||
table.columns.adjust().draw();
|
||||
}
|
||||
|
||||
// Apply
|
||||
$applyBtn.click(function () {
|
||||
reportType = $report.find('#type').val();
|
||||
$(this).addClass('disabled');
|
||||
imageLoader.addClass('fa fa-spinner fa-spin loading-icon');
|
||||
getData($auditDataTable.data().url);
|
||||
});
|
||||
|
||||
$("#refreshGrid").click(function () {
|
||||
reportType = $report.find('#type').val();
|
||||
if (reportType === 'audit') {
|
||||
auditTable.ajax.reload();
|
||||
} else if (reportType === 'debug') {
|
||||
logTable.ajax.reload();
|
||||
} else {
|
||||
requestsTable.ajax.reload();
|
||||
}
|
||||
});
|
||||
|
||||
function createAuditTable($dataTable) {
|
||||
return $dataTable.DataTable({
|
||||
language: dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
searching: false,
|
||||
paging: true,
|
||||
bInfo: false,
|
||||
stateSave: true,
|
||||
bDestroy: true,
|
||||
processing: true,
|
||||
responsive: true,
|
||||
order: [[0, 'desc']],
|
||||
data: {},
|
||||
columns: [
|
||||
{data: 'logDate', "render": dataTableDateFromIso},
|
||||
{data: 'userName'},
|
||||
{data: 'userId'},
|
||||
{data: 'applicationName'},
|
||||
{data: 'requestId'},
|
||||
{data: 'method'},
|
||||
{data: 'url'},
|
||||
{data: 'entity', responsivePriority: 2},
|
||||
{
|
||||
name: 'entityId',
|
||||
responsivePriority: 2,
|
||||
data : function (data) {
|
||||
if (data.entityId === 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return data.entityId;
|
||||
}
|
||||
},
|
||||
{data: 'message'},
|
||||
{
|
||||
"data": function (data, type, row, meta) {
|
||||
if (type != "display")
|
||||
return "";
|
||||
|
||||
return arrayViewer(
|
||||
{"col1": "{% trans "Property" %}", "col2": "{% trans "Value" %}", "items": data.objectAfter}
|
||||
);
|
||||
},
|
||||
"sortable": false,
|
||||
responsivePriority: 1
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
function createLogTable($dataTable) {
|
||||
return $dataTable.DataTable({
|
||||
language: dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
searching: false,
|
||||
paging: true,
|
||||
bInfo: false,
|
||||
stateSave: true,
|
||||
bDestroy: true,
|
||||
processing: true,
|
||||
responsive: true,
|
||||
order: [[0, 'desc']],
|
||||
data: {},
|
||||
columns: [
|
||||
{data: 'logDate', "render": dataTableDateFromIso},
|
||||
{data: 'userName'},
|
||||
{data: 'userId'},
|
||||
{data: 'applicationName'},
|
||||
{data: 'requestId'},
|
||||
{data: 'method'},
|
||||
{data: 'url'},
|
||||
{data: 'type'},
|
||||
{data: 'message'},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
function createRequestsTable($dataTable) {
|
||||
return $dataTable.DataTable({
|
||||
language: dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
searching: false,
|
||||
paging: true,
|
||||
bInfo: false,
|
||||
stateSave: true,
|
||||
bDestroy: true,
|
||||
processing: true,
|
||||
responsive: true,
|
||||
order: [[0, 'desc']],
|
||||
data: {},
|
||||
columns: [
|
||||
{data: 'startTime'},
|
||||
{data: 'userName'},
|
||||
{data: 'userId'},
|
||||
{data: 'applicationName'},
|
||||
{data: 'requestId'},
|
||||
{data: 'method'},
|
||||
{data: 'url'},
|
||||
]
|
||||
})
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
216
reports/apirequests-report-preview.twig
Normal file
216
reports/apirequests-report-preview.twig
Normal file
@@ -0,0 +1,216 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
<button class="btn btn-info XiboRedirectButton" href="{{ url_for("savedreport.view") }}"><i class="fa fa-eye" aria-hidden="true"></i> {% trans "Saved Reports" %}</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<i class="fa fa-list"></i>
|
||||
{{ metadata.title }}
|
||||
<span class="small">({% trans "Generated on: " %}{{ metadata.generatedOn }})</span>
|
||||
<div><span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}">
|
||||
{% if metadata.logType == 'audit' %}
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="apiRequestsHistoryAuditReportPreview" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "User Name" %}</th>
|
||||
<th>{% trans "User ID" %}</th>
|
||||
<th>{% trans "Application" %}</th>
|
||||
<th>{% trans "Request ID" %}</th>
|
||||
<th>{% trans "Method" %}</th>
|
||||
<th>{% trans "Url" %}</th>
|
||||
<th>{% trans "Entity" %}</th>
|
||||
<th>{% trans "Entity ID" %}</th>
|
||||
<th>{% trans "Message" %}</th>
|
||||
<th>{% trans "Details" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% elseif metadata.logType == 'debug' %}
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="apiRequestsHistoryDebugReportPreview" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "UserName" %}</th>
|
||||
<th>{% trans "User ID" %}</th>
|
||||
<th>{% trans "Application" %}</th>
|
||||
<th>{% trans "Request ID" %}</th>
|
||||
<th>{% trans "Method" %}</th>
|
||||
<th>{% trans "Url" %}</th>
|
||||
<th>{% trans "Level" %}</th>
|
||||
<th>{% trans "Details" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="apiRequestsHistoryReportPreview" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "UserName" %}</th>
|
||||
<th>{% trans "User ID" %}</th>
|
||||
<th>{% trans "Application" %}</th>
|
||||
<th>{% trans "Request ID" %}</th>
|
||||
<th>{% trans "Method" %}</th>
|
||||
<th>{% trans "Url" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
$(function() {
|
||||
const outputData = {{ table|json_encode|raw }};
|
||||
|
||||
const type = '{{ metadata.logType }}';
|
||||
if (type === 'audit') {
|
||||
const auditTable = $("#apiRequestsHistoryAuditReportPreview").DataTable({
|
||||
language: dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
paging: false,
|
||||
ordering: false,
|
||||
info: false,
|
||||
order: [[0, 'desc']],
|
||||
searching: false,
|
||||
data: outputData,
|
||||
columns: [
|
||||
{data: 'logDate', "render": dataTableDateFromIso},
|
||||
{data: 'userName'},
|
||||
{data: 'userId'},
|
||||
{data: 'applicationName'},
|
||||
{data: 'requestId'},
|
||||
{data: 'method'},
|
||||
{data: 'url'},
|
||||
{data: 'entity'},
|
||||
{
|
||||
name: 'entityId',
|
||||
responsivePriority: 2,
|
||||
data : function (data) {
|
||||
if (data.entityId === 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return data.entityId;
|
||||
}
|
||||
},
|
||||
{data: 'message'},
|
||||
{data: 'objectAfter'}
|
||||
]
|
||||
});
|
||||
|
||||
auditTable.on('draw', dataTableDraw);
|
||||
auditTable.on('processing.dt', function(e, settings, processing) {
|
||||
dataTableProcessing(e, settings, processing);
|
||||
});
|
||||
} else if (type === 'debug') {
|
||||
const debugTable = $("#apiRequestsHistoryDebugReportPreview").DataTable({
|
||||
language: dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
paging: false,
|
||||
ordering: false,
|
||||
info: false,
|
||||
order: [[0, 'desc']],
|
||||
searching: false,
|
||||
data: outputData,
|
||||
columns: [
|
||||
{data: 'logDate', "render": dataTableDateFromIso},
|
||||
{data: 'userName'},
|
||||
{data: 'userId'},
|
||||
{data: 'applicationName'},
|
||||
{data: 'requestId'},
|
||||
{data: 'method'},
|
||||
{data: 'url'},
|
||||
{data: 'type'},
|
||||
{data: 'message'},
|
||||
]
|
||||
});
|
||||
|
||||
debugTable.on('draw', dataTableDraw);
|
||||
debugTable.on('processing.dt', function(e, settings, processing) {
|
||||
dataTableProcessing(e, settings, processing);
|
||||
});
|
||||
} else {
|
||||
const requestsTable = $("#apiRequestsHistoryReportPreview").DataTable({
|
||||
language: dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
paging: false,
|
||||
ordering: false,
|
||||
info: false,
|
||||
order: [[0, 'desc']],
|
||||
searching: false,
|
||||
data: outputData,
|
||||
columns: [
|
||||
{data: 'startTime'},
|
||||
{data: 'userName'},
|
||||
{data: 'userId'},
|
||||
{data: 'applicationName'},
|
||||
{data: 'requestId'},
|
||||
{data: 'method'},
|
||||
{data: 'url'},
|
||||
]
|
||||
});
|
||||
|
||||
requestsTable.on('draw', dataTableDraw);
|
||||
requestsTable.on('processing.dt', function(e, settings, processing) {
|
||||
dataTableProcessing(e, settings, processing);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
95
reports/apirequests-schedule-form-add.twig
Normal file
95
reports/apirequests-schedule-form-add.twig
Normal file
@@ -0,0 +1,95 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "form-base.twig" %}
|
||||
{% import "forms.twig" as forms %}
|
||||
|
||||
{% block formTitle %}
|
||||
{% trans "Add Report Schedule" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block formButtons %}
|
||||
{% trans "Cancel" %}, XiboDialogClose()
|
||||
{% trans "Save" %}, $("#reportScheduleAddForm").submit()
|
||||
{% endblock %}
|
||||
|
||||
{% block formHtml %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form id="reportScheduleAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("reportschedule.add") }}">
|
||||
{{ forms.hidden("hiddenFields", hiddenFields) }}
|
||||
{{ forms.hidden("reportName", reportName) }}
|
||||
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{% set helpText %}{% trans "The name for this report schedule" %}{% endset %}
|
||||
{{ forms.input("name", title, "", helpText, "", "required") }}
|
||||
|
||||
{% set title %}{% trans "Frequency" %}{% endset %}
|
||||
{% set helpText %}{% trans "Select how frequently you would like this report to run" %}{% endset %}
|
||||
{% set daily %}{% trans "Daily" %}{% endset %}
|
||||
{% set weekly %}{% trans "Weekly" %}{% endset %}
|
||||
{% set monthly %}{% trans "Monthly" %}{% endset %}
|
||||
{% set yearly %}{% trans "Yearly" %}{% endset %}
|
||||
{% set options = [
|
||||
{ name: "daily", filter: daily },
|
||||
{ name: "weekly", filter: weekly },
|
||||
{ name: "monthly", filter: monthly },
|
||||
{ name: "yearly", filter: yearly },
|
||||
] %}
|
||||
{{ forms.dropdown("filter", "single", title, "", options, "name", "filter", helpText) }}
|
||||
|
||||
{% set title %}{% trans "User" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("user.search") },
|
||||
{ name: "data-search-term", value: "userName" },
|
||||
{ name: "data-id-property", value: "userId" },
|
||||
{ name: "data-text-property", value: "userName" },
|
||||
] %}
|
||||
{{ forms.dropdown("userId", "single", title, "", null, "userId", "userName", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title = "Report Type"|trans %}
|
||||
{% set options = [
|
||||
{ id: 'requests', value: "Requests"|trans },
|
||||
{ id: 'audit', value: "Audit"|trans },
|
||||
{ id: 'debug', value: "Debug"|trans },
|
||||
] %}
|
||||
{{ forms.dropdown("type", "single", title, "audit", options, "id", "value", helpText) }}
|
||||
|
||||
{% set title %}{% trans "Start Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to run this report. Leave blank to run from the next collection point." %}{% endset %}
|
||||
{{ forms.dateTime("fromDt", title, "", helpText, "starttime-control") }}
|
||||
|
||||
{% set title %}{% trans "End Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to end the schedule. Leave blank to run indefinitely." %}{% endset %}
|
||||
{{ forms.dateTime("toDt", title, "", helpText, "endtime-control") }}
|
||||
|
||||
{% set title %}{% trans "Email addresses" %}{% endset %}
|
||||
{% set helpText %}{% trans "Additional emails separated by a comma." %}{% endset %}
|
||||
{{ forms.inputWithTags("nonusers", title, nonusers, helpText) }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
14
reports/apirequests.report
Normal file
14
reports/apirequests.report
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "apirequests",
|
||||
"description": "API Requests History",
|
||||
"class": "\\Xibo\\Report\\ApiRequests",
|
||||
"type": "Report",
|
||||
"output_type": "table",
|
||||
"color":"orange",
|
||||
"fa_icon": "fa-th",
|
||||
"sort_order": 5,
|
||||
"hidden": 0,
|
||||
"category": "Audit",
|
||||
"feature": "admin",
|
||||
"adminOnly": 1
|
||||
}
|
||||
48
reports/bandwidth-email-template.twig
Normal file
48
reports/bandwidth-email-template.twig
Normal file
@@ -0,0 +1,48 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "base-report.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span>
|
||||
</div>
|
||||
<p></p>
|
||||
<span>{{ placeholder }}</span>
|
||||
<img src="{{ src|raw }}" >
|
||||
<p></p>
|
||||
|
||||
<table class="saved-report-table">
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{% trans "Bandwidth" %}</th>
|
||||
<th>{% trans "Unit" %}</th>
|
||||
</tr>
|
||||
{% for item in tableData %}
|
||||
<tr>
|
||||
<td>{{ item.label }}</td>
|
||||
<td>{{ item.bandwidth }}</td>
|
||||
<td>{{ item.unit }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
||||
251
reports/bandwidth-report-form.twig
Normal file
251
reports/bandwidth-report-form.twig
Normal file
@@ -0,0 +1,251 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2020 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block title %}{% trans "Report: Bandwidth" %} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
{% include "report-schedule-buttons.twig" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<span>{% trans "Bandwidth" %}</span>
|
||||
</div>
|
||||
|
||||
{% include "report-selector.twig" %}
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-refresh-on-form-submit="false">
|
||||
<div class="XiboFilterCustom card bg-light mb-3">
|
||||
<div class="FilterDiv card-body" id="bandwidthReport">
|
||||
<!-- Form Filter -->
|
||||
<form class="form-inline">
|
||||
{% set title %}{% trans "From Date" %}{% endset %}
|
||||
{{ inline.dateMonth("bandwidthFromDt", title, defaults.toDate, "", "", "", "") }}
|
||||
|
||||
{% set title %}{% trans "To Date" %}{% endset %}
|
||||
{{ inline.dateMonth("bandwidthToDt", title, defaults.toDate, "", "", "", "") }}
|
||||
|
||||
{% set title %}{% trans "Display" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("display.search") },
|
||||
{ name: "data-search-term", value: "display" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "displayId" },
|
||||
{ name: "data-text-property", value: "display" }
|
||||
] %}
|
||||
{{ inline.dropdown("displayId", "single", title, "", null, "displayId", "display", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
<div class="w-100">
|
||||
<a id="applyBtn" class="btn btn-success">
|
||||
<span>{% trans "Apply" %}</span>
|
||||
</a>
|
||||
<span id="imageLoader" class=""></span>
|
||||
<span id="applyWarning" class="text-warning" style="display:none; padding-left: 10px">{% trans "Warning: This may return a lot of data and may take several minutes to process." %}</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card Header -->
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-tabs card-header-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="chart-tab" data-toggle="tab" href="#chartTab" role="tab"
|
||||
aria-controls="chartTab" aria-selected="true">Chart</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="tabular-tab" data-toggle="tab" href="#tabularTab" role="tab"
|
||||
aria-controls="tabularTab" aria-selected="false">Tabular</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Card Body -->
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<!-- CHART TAB-->
|
||||
<div class="tab-pane active" id="chartTab" role="tabpanel" aria-labelledby="chart-tab">
|
||||
<div class="chart-container" style="height:550px;">
|
||||
<canvas id="canvas" style="clear:both; margin-top:25px;" height="70%"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TABULAR TAB-->
|
||||
<div class="tab-pane show" id="tabularTab" role="tabpanel" aria-labelledby="tabular-tab">
|
||||
<!-- DATATABLE -->
|
||||
<div class="table-container" id="table_wrapper">
|
||||
<table id="bandwidthTbl"
|
||||
class="table xibo-table table-striped table-full-width"
|
||||
style="width: 100%"
|
||||
data-url="/report/data/bandwidth"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Bandwidth</th>
|
||||
<th>Unit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
$('[data-toggle="popover"]').popover();
|
||||
|
||||
let $report = $("#bandwidthReport");
|
||||
let $dataTable = $('#bandwidthTbl'); // Datatable
|
||||
let chart = null; // Chart
|
||||
let result; // XHR get data result
|
||||
|
||||
let imageLoader = $("#imageLoader");
|
||||
let $applyBtn = $("#applyBtn");
|
||||
|
||||
// Initialize table with empty data
|
||||
let table = $dataTable.DataTable({
|
||||
searching: false,
|
||||
paging: true,
|
||||
bInfo: false,
|
||||
stateSave: true,
|
||||
bDestroy: true,
|
||||
processing: true,
|
||||
ordering: false,
|
||||
data: {},
|
||||
columns: [
|
||||
{
|
||||
data: 'label',
|
||||
'sortable': false,
|
||||
},
|
||||
{
|
||||
data: 'bandwidth',
|
||||
'sortable': false,
|
||||
},
|
||||
{
|
||||
data: 'unit',
|
||||
'sortable': false,
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
// Get Data
|
||||
function getData(url) {
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
data: $report.find("form").serialize(),
|
||||
success: function success(data) {
|
||||
result = data;
|
||||
$applyBtn.removeClass('disabled');
|
||||
imageLoader.removeClass('fa fa-spinner fa-spin loading-icon');
|
||||
|
||||
// Based on tab load data
|
||||
if ($('.nav-tabs .nav-item a.active').attr("href") === '#tabularTab') {
|
||||
setTabularData(table, result.table);
|
||||
} else {
|
||||
setChartData(result.chart);
|
||||
}
|
||||
},
|
||||
error: function error(xhr, textStatus, _error) {
|
||||
$applyBtn.removeClass('disabled');
|
||||
toastr.error(xhr.responseJSON.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setTabularData(table, data) {
|
||||
table.clear().draw();
|
||||
|
||||
if (Object.keys(data).length > 0) {
|
||||
table.rows.add( data ).draw()
|
||||
}
|
||||
}
|
||||
|
||||
function setChartData(data) {
|
||||
|
||||
setTimeout(function() {
|
||||
$applyBtn.removeClass('disabled');
|
||||
}, 300);
|
||||
|
||||
if (chart !== undefined && chart !== null) {
|
||||
chart.destroy();
|
||||
}
|
||||
|
||||
// Create our chart
|
||||
chart = new Chart($("#canvas"), data);
|
||||
}
|
||||
|
||||
// Tab shown/click load relevant table/chart
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
let activeTab = $(e.target).attr("href")
|
||||
if (result) {
|
||||
if (activeTab === '#tabularTab') {
|
||||
setTabularData(table, result.table);
|
||||
} else {
|
||||
setChartData(result.chart);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Apply
|
||||
$applyBtn.click(function () {
|
||||
$(this).addClass('disabled');
|
||||
imageLoader.addClass('fa fa-spinner fa-spin loading-icon');
|
||||
getData($dataTable.data().url);
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
95
reports/bandwidth-report-preview.twig
Normal file
95
reports/bandwidth-report-preview.twig
Normal file
@@ -0,0 +1,95 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2020 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
<button class="btn btn-info XiboRedirectButton" href="{{ url_for("savedreport.view") }}"><i class="fa fa-eye" aria-hidden="true"></i> {% trans "Saved Reports" %}</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<i class="fa fa-list"></i>
|
||||
{{ metadata.title }}
|
||||
<span class="small">({% trans "Generated on: " %}{{ metadata.generatedOn }})</span>
|
||||
<div><span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}">
|
||||
|
||||
<div class="XiboData card pt-3">
|
||||
<canvas id="canvas" style="clear:both; margin-top:25px;"></canvas>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="bandwidthTbl" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{% trans "Bandwidth" %}</th>
|
||||
<th>{% trans "Unit" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
|
||||
let reportChart = new Chart($("#canvas"), {{ chart|json_encode|raw }});
|
||||
let outputData = {{ table|json_encode|raw }};
|
||||
|
||||
// Grid
|
||||
let table = $("#bandwidthTbl").DataTable({
|
||||
"searching": false,
|
||||
"paging": true,
|
||||
"ordering": false,
|
||||
"data": outputData,
|
||||
"columns": [
|
||||
{ "data": 'label' },
|
||||
{ "data": 'bandwidth' },
|
||||
{ "data": 'unit' },
|
||||
]
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
90
reports/bandwidth-schedule-form-add.twig
Normal file
90
reports/bandwidth-schedule-form-add.twig
Normal file
@@ -0,0 +1,90 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "form-base.twig" %}
|
||||
{% import "forms.twig" as forms %}
|
||||
|
||||
{% block formTitle %}
|
||||
{% trans "Add Report Schedule" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block formButtons %}
|
||||
{% trans "Cancel" %}, XiboDialogClose()
|
||||
{% trans "Save" %}, $("#reportScheduleAddForm").submit()
|
||||
{% endblock %}
|
||||
|
||||
{% block formHtml %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form id="reportScheduleAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("reportschedule.add") }}">
|
||||
|
||||
{{ forms.hidden("reportName", reportName) }}
|
||||
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{% set helpText %}{% trans "The name for this report schedule" %}{% endset %}
|
||||
{{ forms.input("name", title, "", helpText, "", "required") }}
|
||||
|
||||
{% set title %}{% trans "Frequency" %}{% endset %}
|
||||
{% set helpText %}{% trans "Select how frequently you would like this report to run" %}{% endset %}
|
||||
{% set daily %}{% trans "Daily" %}{% endset %}
|
||||
{% set monthly %}{% trans "Monthly" %}{% endset %}
|
||||
{% set yearly %}{% trans "Yearly" %}{% endset %}
|
||||
{% set options = [
|
||||
{ name: "daily", filter: daily },
|
||||
{ name: "monthly", filter: monthly },
|
||||
{ name: "yearly", filter: yearly },
|
||||
] %}
|
||||
{{ forms.dropdown("filter", "single", title, "", options, "name", "filter", helpText) }}
|
||||
|
||||
{% set title %}{% trans "Display" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("display.search") },
|
||||
{ name: "data-search-term", value: "display" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "displayId" },
|
||||
{ name: "data-text-property", value: "display" }
|
||||
] %}
|
||||
{{ forms.dropdown("displayId", "single", title, "", null, "displayId", "display", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Start Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to run this report. Leave blank to run from the next collection point." %}{% endset %}
|
||||
{{ forms.dateTime("fromDt", title, "", helpText, "starttime-control") }}
|
||||
|
||||
{% set title %}{% trans "End Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to end the schedule. Leave blank to run indefinitely." %}{% endset %}
|
||||
{{ forms.dateTime("toDt", title, "", helpText, "endtime-control") }}
|
||||
|
||||
{% set title %}{% trans "Should an email be sent?" %}{% endset %}
|
||||
{{ forms.checkbox("sendEmail", title, sendEmail) }}
|
||||
|
||||
{% set title %}{% trans "Email addresses" %}{% endset %}
|
||||
{% set helpText %}{% trans "Additional emails separated by a comma." %}{% endset %}
|
||||
{{ forms.inputWithTags("nonusers", title, nonusers, helpText) }}
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
14
reports/bandwidth.report
Normal file
14
reports/bandwidth.report
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "bandwidth",
|
||||
"description": "Display Statistics: Bandwidth",
|
||||
"class": "\\Xibo\\Report\\Bandwidth",
|
||||
"type": "Chart",
|
||||
"output_type": "chart",
|
||||
"color":"green",
|
||||
"fa_icon": "fa-bar-chart",
|
||||
"sort_order": 3,
|
||||
"hidden": 0,
|
||||
"category": "Display",
|
||||
"feature": "displays.reporting",
|
||||
"adminOnly": 0
|
||||
}
|
||||
31
reports/base-report.twig
Normal file
31
reports/base-report.twig
Normal file
@@ -0,0 +1,31 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
{% if logo %}
|
||||
<div>
|
||||
<img class="logo right" src="{{ logo }}">
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="header">{{ header }}</div>
|
||||
<div class="title">{{ title }} <span class="small">({% trans "Generated on: " %}{{ metadata.generatedOn }})</span></div>
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
48
reports/campaign-proofofplay-email-template.twig
Normal file
48
reports/campaign-proofofplay-email-template.twig
Normal file
@@ -0,0 +1,48 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "base-report.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span>
|
||||
</div>
|
||||
<p></p>
|
||||
|
||||
<table class="saved-report-table">
|
||||
<tr>
|
||||
<th>{% trans "Period" %}</th>
|
||||
<th>{% trans "Ad Plays" %}</th>
|
||||
<th>{% trans "Ad Duration" %}</th>
|
||||
<th>{% trans "Audience Impressions" %}</th>
|
||||
</tr>
|
||||
{% for item in tableData %}
|
||||
<tr>
|
||||
<td>{{ item.labelDate }}</td>
|
||||
<td>{{ item.adPlays }}</td>
|
||||
<td>{{ item.adDuration }}</td>
|
||||
<td>{{ item.impressions }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
||||
529
reports/campaign-proofofplay-report-form.twig
Normal file
529
reports/campaign-proofofplay-report-form.twig
Normal file
@@ -0,0 +1,529 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block title %}{% trans "Report: Campaign Proof of Play" %} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
{% include "report-schedule-buttons.twig" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<span>{% trans "Campaign Proof of Play" %}</span>
|
||||
</div>
|
||||
|
||||
{% include "report-selector.twig" %}
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-grid-name="campaignProofOfPlayView" data-refresh-on-form-submit="false">
|
||||
<div class="XiboFilterCustom card bg-light mb-3">
|
||||
<div class="FilterDiv card-body" id="campaignProofOfPlayReport">
|
||||
<!-- Form Filter -->
|
||||
<form class="form-inline">
|
||||
{% set title %}{% trans "Range" %}{% endset %}
|
||||
{% set range %}{% trans "Select a range" %}{% endset %}
|
||||
{% set wholecampaign %}{% trans "Whole Campaign" %}{% endset %}
|
||||
{% set today %}{% trans "Today" %}{% endset %}
|
||||
{% set yesterday %}{% trans "Yesterday" %}{% endset %}
|
||||
{% set thisweek %}{% trans "This Week" %}{% endset %}
|
||||
{% set thismonth %}{% trans "This Month" %}{% endset %}
|
||||
{% set thisyear %}{% trans "This Year" %}{% endset %}
|
||||
{% set lastweek %}{% trans "Last Week" %}{% endset %}
|
||||
{% set lastmonth %}{% trans "Last Month" %}{% endset %}
|
||||
{% set lastyear %}{% trans "Last Year" %}{% endset %}
|
||||
{% set options = [
|
||||
{ filterName: "", reportFilter: range },
|
||||
{ filterName: "wholecampaign", reportFilter: wholecampaign },
|
||||
{ filterName: "today", reportFilter: today },
|
||||
{ filterName: "yesterday", reportFilter: yesterday },
|
||||
{ filterName: "thisweek", reportFilter: thisweek },
|
||||
{ filterName: "thismonth", reportFilter: thismonth },
|
||||
{ filterName: "thisyear", reportFilter: thisyear },
|
||||
{ filterName: "lastweek", reportFilter: lastweek },
|
||||
{ filterName: "lastmonth", reportFilter: lastmonth },
|
||||
{ filterName: "lastyear", reportFilter: lastyear },
|
||||
] %}
|
||||
{{ inline.dropdown("reportFilter", "single", title, "today", options, "filterName", "reportFilter") }}
|
||||
|
||||
{% set title %}{% trans "From Date" %}{% endset %}
|
||||
{{ inline.date("statsFromDt", title, defaults.fromDateOneDay, "", "stats-from-dt", "", "") }}
|
||||
|
||||
{% set title %}{% trans "To Date" %}{% endset %}
|
||||
{{ inline.date("statsToDt", title, defaults.toDate, "", "stats-to-dt", "", "") }}
|
||||
|
||||
{% set title %}{% trans "Group by" %}{% endset %}
|
||||
{% set hour %}{% trans "Hour" %}{% endset %}
|
||||
{% set day %}{% trans "Day" %}{% endset %}
|
||||
{% set week %}{% trans "Week" %}{% endset %}
|
||||
{% set month %}{% trans "Month" %}{% endset %}
|
||||
|
||||
{% set options = [
|
||||
{ name: "hour", filter: hour },
|
||||
{ name: "day", filter: day },
|
||||
{ name: "week", filter: week },
|
||||
{ name: "month", filter: month },
|
||||
] %}
|
||||
{{ inline.dropdown("groupBy", "single", title, "day", options, "name", "filter") }}
|
||||
|
||||
{% set title %}{% trans "Display" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("display.search") },
|
||||
{ name: "data-search-term", value: "display" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "displayId" },
|
||||
{ name: "data-text-property", value: "display" }
|
||||
] %}
|
||||
{{ inline.dropdown("displayId", "single", title, "", null, "displayId", "display", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("displayGroup.search") },
|
||||
{ name: "data-search-term", value: "displayGroup" },
|
||||
{ name: "data-id-property", value: "displayGroupId" },
|
||||
{ name: "data-text-property", value: "displayGroup" }
|
||||
] %}
|
||||
{{ inline.dropdown("displayGroupId[]", "dropdownmulti", title, "", null, "displayGroupId", "displayGroup", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{# Campaign list only. #}
|
||||
{% set attributes = [
|
||||
{ name: "data-search-url", value: url_for("campaign.search") },
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
] %}
|
||||
|
||||
{% set title %}{% trans "Campaign" %}{% endset %}
|
||||
{% set helpText %}{% trans "Please select a Campaign" %}{% endset %}
|
||||
{{ inline.dropdown("parentCampaignId", "single", title, "", null, "campaignId", "campaign", "", "", "", "", "", attributes) }}
|
||||
|
||||
<div class="w-100">
|
||||
<a id="applyBtn" class="btn btn-success">
|
||||
<span>{% trans "Apply" %}</span>
|
||||
</a>
|
||||
<span id="imageLoader" class=""></span>
|
||||
<span id="applyWarning" class="text-warning" style="display:none; padding-left: 10px">{% trans "Warning: This may return a lot of data and may take several minutes to process." %}</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Card Header -->
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-tabs card-header-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="tabular-tab" data-toggle="tab" href="#tabularTab" role="tab"
|
||||
aria-controls="tabularTab" aria-selected="true">Tabular</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Card Body -->
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<!-- TABULAR TAB-->
|
||||
<div class="tab-pane active" id="tabularTab" role="tabpanel" aria-labelledby="tabular-tab">
|
||||
<!-- DATATABLE -->
|
||||
<div class="table-container" id="table_wrapper">
|
||||
<table id="stats"
|
||||
class="table xibo-table table-striped table-full-width"
|
||||
style="width: 100%"
|
||||
data-state-preference-name="proofOfPlayGrid"
|
||||
data-url="/report/data/campaignProofOfPlay">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Period" %}</th>
|
||||
<th>{% trans "Ad Plays" %}</th>
|
||||
<th>{% trans "Ad Duration" %}</th>
|
||||
<th>{% trans "Audience Impressions" %}</th>
|
||||
<th>{% trans "Spend" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
$(document).ready(function() {
|
||||
let $dataTable = $('#stats'); // Datatable
|
||||
let chart = null; // Chart
|
||||
let result; // XHR get data result
|
||||
|
||||
let imageLoader = $("#imageLoader");
|
||||
let $warning = $("#applyWarning");
|
||||
let $applyBtn = $("#applyBtn");
|
||||
|
||||
// Report Filter
|
||||
let reportFilter = $("#reportFilter"); // Report Filter
|
||||
|
||||
// Grid
|
||||
let table = $dataTable.DataTable({
|
||||
"language": dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
stateLoadCallback: dataTableStateLoadCallback,
|
||||
stateSaveCallback: dataTableStateSaveCallback,
|
||||
drawCallback: function( settings ) {
|
||||
setTimeout(function() {
|
||||
$("#applyBtn").removeClass('disabled');
|
||||
}, 300);
|
||||
},
|
||||
filter: false,
|
||||
"order": [[0, "asc"]],
|
||||
data:{},
|
||||
"columns": [
|
||||
{"data": "labelDate"},
|
||||
{"data": "adPlays"},
|
||||
{"data": "adDuration"},
|
||||
{
|
||||
"data": "impressions",
|
||||
"render": dataTableRoundDecimal
|
||||
},
|
||||
{
|
||||
"data": "spend",
|
||||
"render": dataTableRoundDecimal
|
||||
},
|
||||
],
|
||||
footerCallback: function (row, data, start, end, display) {
|
||||
let api = this.api();
|
||||
|
||||
// Total over all pages
|
||||
let totalAdPlays = api.column(1).data().reduce(function (a, b) {
|
||||
return a + b;
|
||||
}, 0);
|
||||
let totalAdPlaysCurrentPage = api.column(1, { page: 'current'}).data().reduce(function (a, b) {
|
||||
return a + b;
|
||||
}, 0);
|
||||
|
||||
let totalAdDuration = api.column(2).data().reduce(function (a, b) {
|
||||
return a + b;
|
||||
}, 0);
|
||||
let totalAdDurationCurrentPage = api.column(2, { page: 'current'}).data().reduce(function (a, b) {
|
||||
return a + b;
|
||||
}, 0);
|
||||
|
||||
let totalImpression = api.column(3).data().reduce(function (a, b) {
|
||||
return a + b;
|
||||
}, 0);
|
||||
let totalImpressionCurrentPage = api.column(3, { page: 'current'}).data().reduce(function (a, b) {
|
||||
return a + b;
|
||||
}, 0);
|
||||
|
||||
let totalSpend = api.column(4).data().reduce(function (a, b) {
|
||||
return a + b;
|
||||
}, 0);
|
||||
let totalSpendCurrentPage = api.column(4, { page: 'current'}).data().reduce(function (a, b) {
|
||||
return a + b;
|
||||
}, 0);
|
||||
|
||||
// Update footer
|
||||
$(api.column(1).footer()).html(totalAdPlaysCurrentPage + ' (' + totalAdPlays + ' total)');
|
||||
$(api.column(2).footer()).html(totalAdDurationCurrentPage + ' (' + totalAdDuration + ' total)');
|
||||
$(api.column(3).footer()).html(parseFloat(totalImpressionCurrentPage).toFixed(2) + ' (' + parseFloat(totalImpression).toFixed(2) + ' total)');
|
||||
$(api.column(4).footer()).html(parseFloat(totalSpendCurrentPage).toFixed(2) + ' (' + parseFloat(totalSpend).toFixed(2) + ' total)');
|
||||
},
|
||||
});
|
||||
|
||||
// Get Data
|
||||
function getData(url) {
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
data: $("#stats").closest(".XiboGrid").find(".FilterDiv form").serializeObject(),
|
||||
success: function success(data) {
|
||||
result = data;
|
||||
$applyBtn.removeClass('disabled');
|
||||
imageLoader.removeClass('fa fa-spinner fa-spin loading-icon');
|
||||
|
||||
// Based on tab load data
|
||||
if ($('.nav-tabs .nav-item a.active').attr("href") === '#tabularTab') {
|
||||
setTabularData(table, result.table);
|
||||
} else {
|
||||
setChartData(result.chart);
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
toastr.error(result.error);
|
||||
}
|
||||
},
|
||||
error: function error(xhr, textStatus, _error) {
|
||||
$applyBtn.removeClass('disabled');
|
||||
toastr.error(xhr.responseJSON.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setTabularData(table, data) {
|
||||
table.clear().draw();
|
||||
|
||||
if (Object.keys(data).length > 0) {
|
||||
table.rows.add( data ).draw()
|
||||
}
|
||||
}
|
||||
|
||||
function setChartData(data) {
|
||||
|
||||
setTimeout(function() {
|
||||
$applyBtn.removeClass('disabled');
|
||||
}, 300);
|
||||
|
||||
if (chart !== undefined && chart !== null) {
|
||||
chart.destroy();
|
||||
}
|
||||
|
||||
// Create our chart
|
||||
chart = new Chart($("#canvas"), data);
|
||||
}
|
||||
|
||||
// Tab shown/click load relevant table/chart
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
let activeTab = $(e.target).attr("href")
|
||||
if (result) {
|
||||
if (activeTab === '#tabularTab') {
|
||||
setTabularData(table, result.table);
|
||||
} else {
|
||||
setChartData(result.chart);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
dataTableAddButtons(table, $('#stats_wrapper').find('.dataTables_buttons'));
|
||||
|
||||
// Apply
|
||||
$applyBtn.click(function () {
|
||||
$(this).addClass('disabled');
|
||||
imageLoader.addClass('fa fa-spinner fa-spin loading-icon');
|
||||
getData($dataTable.data().url);
|
||||
});
|
||||
|
||||
// If we select a displayId we hide the display group filter
|
||||
$('#displayId').off('change').change( function() {
|
||||
|
||||
let displayId = $('#displayId').val();
|
||||
if (displayId) {
|
||||
$('select[name="displayGroupId[]"] option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().hide();
|
||||
} else {
|
||||
$('#displayId option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().show();
|
||||
}
|
||||
});
|
||||
|
||||
// Hide / Show FromDt and ToDt
|
||||
function checkReportFilter(reportFilter) {
|
||||
if (reportFilter.val() === '' || reportFilter.val() === undefined) {
|
||||
$(".stats-from-dt").show();
|
||||
$(".stats-to-dt").show();
|
||||
} else {
|
||||
$(".stats-from-dt").hide();
|
||||
$(".stats-to-dt").hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the difference of number of days of a selected range
|
||||
let calculateDaysShowHideWarn = function() {
|
||||
|
||||
let fromDt = moment($("#statsFromDt").val());
|
||||
let toDt = moment($("#statsToDt").val());
|
||||
|
||||
let days = toDt.diff(fromDt, 'days');
|
||||
|
||||
$warning.hide();
|
||||
if ( days >= 30) {
|
||||
$warning.show();
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
$("#statsFromDtLink").change( function() {
|
||||
calculateDaysShowHideWarn();
|
||||
});
|
||||
|
||||
$("#statsToDtLink").change( function() {
|
||||
calculateDaysShowHideWarn();
|
||||
});
|
||||
|
||||
let checkFilterAndApply = function() {
|
||||
|
||||
reportFilter.off('change').change( function() {
|
||||
let value = reportFilter.val();
|
||||
|
||||
// Hide / Show FromDt and ToDt
|
||||
checkReportFilter(reportFilter);
|
||||
|
||||
// Hide / Show Warning
|
||||
$warning.hide();
|
||||
if ( value === '') {
|
||||
calculateDaysShowHideWarn();
|
||||
} else if ( value === 'thismonth' || value === 'lastmonth' || value === 'thisyear' || value === 'lastyear') {
|
||||
$warning.show();
|
||||
}
|
||||
});
|
||||
|
||||
let anchorReportAddBtn = $("button#reportAddBtn");
|
||||
anchorReportAddBtn.attr("href", "{{ url_for("reportschedule.add.form") }}?reportName=campaignProofOfPlay" );
|
||||
|
||||
};
|
||||
|
||||
checkReportFilter(reportFilter);
|
||||
checkFilterAndApply();
|
||||
|
||||
|
||||
var $campaignSelect = $('#parentCampaignId');
|
||||
$campaignSelect.select2({
|
||||
ajax: {
|
||||
url: $campaignSelect.data("searchUrl"),
|
||||
dataType: "json",
|
||||
delay: 250,
|
||||
placeholder: 'Campaign',
|
||||
allowClear: true,
|
||||
data: function(params) {
|
||||
|
||||
var query = {
|
||||
isLayoutSpecific: 0,
|
||||
retired: 0,
|
||||
totalDuration: 0,
|
||||
name: params.term,
|
||||
start: 0,
|
||||
length: 10,
|
||||
columns: [
|
||||
{
|
||||
"data": "isLayoutSpecific"
|
||||
},
|
||||
{
|
||||
"data": "campaign"
|
||||
}
|
||||
],
|
||||
order: [
|
||||
{
|
||||
"column": 0,
|
||||
"dir": "asc"
|
||||
},
|
||||
{
|
||||
"column": 1,
|
||||
"dir": "asc"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Set the start parameter based on the page number
|
||||
if (params.page != null) {
|
||||
query.start = (params.page - 1) * 10;
|
||||
}
|
||||
|
||||
return query;
|
||||
},
|
||||
processResults: function(data, params) {
|
||||
|
||||
var results = [];
|
||||
var campaigns = [];
|
||||
|
||||
$.each(data.data, function(index, element) {
|
||||
campaigns.push({
|
||||
"id": element.campaignId,
|
||||
"text": element.campaign
|
||||
});
|
||||
});
|
||||
|
||||
results.push({
|
||||
"text": $campaignSelect.data('transCampaigns'),
|
||||
"children": campaigns
|
||||
})
|
||||
|
||||
|
||||
var page = params.page || 1;
|
||||
page = (page > 1) ? page - 1 : page;
|
||||
|
||||
return {
|
||||
results: results,
|
||||
pagination: {
|
||||
more: (page * 10 < data.recordsTotal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function campaignProofOfPlayScheduleCallback() {
|
||||
|
||||
let $displayId = $('#campaignProofOfPlayReport #displayId');
|
||||
let $newDisplayId = $('#campaignProofofplayScheduleAddForm #displayId');
|
||||
|
||||
appendOptions($newDisplayId, $displayId.find('option:selected').clone());
|
||||
}
|
||||
|
||||
function appendOptions(element, options) {
|
||||
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
|
||||
let option = options[i];
|
||||
element.append(option).trigger('change');
|
||||
$(option).prop('selected', true);
|
||||
element.trigger({
|
||||
type: 'select2:select',
|
||||
params: {
|
||||
data: option
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
97
reports/campaign-proofofplay-report-preview.twig
Normal file
97
reports/campaign-proofofplay-report-preview.twig
Normal file
@@ -0,0 +1,97 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
<button class="btn btn-info XiboRedirectButton" href="{{ url_for("savedreport.view") }}"><i class="fa fa-eye" aria-hidden="true"></i> {% trans "Saved Reports" %}</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<i class="fa fa-list"></i>
|
||||
{{ metadata.title }}
|
||||
<span class="small">({% trans "Generated on: " %}{{ metadata.generatedOn }})</span>
|
||||
<div><span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}">
|
||||
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="stats" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Period</th>
|
||||
<th>Ad Plays</th>
|
||||
<th>Ad Duration</th>
|
||||
<th>Audience Impressions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
$(document).ready(function() {
|
||||
|
||||
let outputData = {{ table|json_encode|raw }};
|
||||
|
||||
// Grid
|
||||
let table = $("#stats").DataTable({
|
||||
"language": dataTablesLanguage,
|
||||
"dom": dataTablesTemplate,
|
||||
"paging": false,
|
||||
"ordering": false,
|
||||
"info": false,
|
||||
"order": [[1, "asc"]],
|
||||
"searching": false,
|
||||
"data": outputData,
|
||||
"columns": [
|
||||
{"data": "labelDate"},
|
||||
{"data": "adPlays"},
|
||||
{"data": "adDuration"},
|
||||
{"data": "impressions"}
|
||||
]
|
||||
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
100
reports/campaign-proofofplay-schedule-form-add.twig
Normal file
100
reports/campaign-proofofplay-schedule-form-add.twig
Normal file
@@ -0,0 +1,100 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
{% extends "form-base.twig" %}
|
||||
{% import "forms.twig" as forms %}
|
||||
|
||||
{% block formTitle %}
|
||||
{% trans "Add Report Schedule" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block formButtons %}
|
||||
{% trans "Cancel" %}, XiboDialogClose()
|
||||
{% trans "Save" %}, $("#campaignProofofplayScheduleAddForm").submit()
|
||||
{% endblock %}
|
||||
|
||||
{% block callBack %}campaignProofOfPlayScheduleCallback{% endblock %}
|
||||
|
||||
{% block formHtml %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form id="campaignProofofplayScheduleAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("reportschedule.add") }}">
|
||||
|
||||
{{ forms.hidden("hiddenFields", hiddenFields) }}
|
||||
{{ forms.hidden("reportName", reportName) }}
|
||||
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{% set helpText %}{% trans "The name for this report schedule" %}{% endset %}
|
||||
{{ forms.input("name", title, "", helpText, "", "required") }}
|
||||
|
||||
{% set title %}{% trans "Frequency" %}{% endset %}
|
||||
{% set helpText %}{% trans "Select how frequently you would like this report to run" %}{% endset %}
|
||||
{% set daily %}{% trans "Daily" %}{% endset %}
|
||||
{% set weekly %}{% trans "Weekly" %}{% endset %}
|
||||
{% set monthly %}{% trans "Monthly" %}{% endset %}
|
||||
{% set yearly %}{% trans "Yearly" %}{% endset %}
|
||||
{% set options = [
|
||||
{ name: "daily", filter: daily },
|
||||
{ name: "weekly", filter: weekly },
|
||||
{ name: "monthly", filter: monthly },
|
||||
{ name: "yearly", filter: yearly },
|
||||
] %}
|
||||
{{ forms.dropdown("filter", "single", title, "", options, "name", "filter", helpText) }}
|
||||
|
||||
{% set title %}{% trans "Display" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("display.search") },
|
||||
{ name: "data-search-term", value: "display" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-default-values", value: displayId },
|
||||
{ name: "data-id-property", value: "displayId" },
|
||||
{ name: "data-text-property", value: "display" }
|
||||
] %}
|
||||
{{ forms.dropdown("displayId", "single", title, "", null, "displayId", "display", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("displayGroup.search") },
|
||||
{ name: "data-search-term", value: "displayGroup" },
|
||||
{ name: "data-id-property", value: "displayGroupId" },
|
||||
{ name: "data-text-property", value: "displayGroup" }
|
||||
] %}
|
||||
{{ forms.dropdown("displayGroupId[]", "dropdownmulti", title, "", null, "displayGroupId", "displayGroup", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Should an email be sent?" %}{% endset %}
|
||||
{{ forms.checkbox("sendEmail", title, sendEmail) }}
|
||||
|
||||
{% set title %}{% trans "Email addresses" %}{% endset %}
|
||||
{% set helpText %}{% trans "Additional emails separated by a comma." %}{% endset %}
|
||||
{{ forms.inputWithTags("nonusers", title, nonusers, helpText) }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
516
reports/display-adplays-report-form.twig
Normal file
516
reports/display-adplays-report-form.twig
Normal file
@@ -0,0 +1,516 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block title %}{% trans "Report: Display Ad Plays" %} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
{% include "report-schedule-buttons.twig" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<span>{% trans "Display Ad Plays" %}</span>
|
||||
</div>
|
||||
|
||||
{% include "report-selector.twig" %}
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-grid-name="displayAdPlayView" data-refresh-on-form-submit="false">
|
||||
<div class="XiboFilterCustom card bg-light mb-3">
|
||||
<div class="FilterDiv card-body" id="report">
|
||||
<!-- Form Filter -->
|
||||
<form class="form-inline">
|
||||
{% set title %}{% trans "Range" %}{% endset %}
|
||||
{% set range %}{% trans "Select a range" %}{% endset %}
|
||||
{% set wholecampaign %}{% trans "Whole Campaign" %}{% endset %}
|
||||
{% set today %}{% trans "Today" %}{% endset %}
|
||||
{% set yesterday %}{% trans "Yesterday" %}{% endset %}
|
||||
{% set thisweek %}{% trans "This Week" %}{% endset %}
|
||||
{% set thismonth %}{% trans "This Month" %}{% endset %}
|
||||
{% set thisyear %}{% trans "This Year" %}{% endset %}
|
||||
{% set lastweek %}{% trans "Last Week" %}{% endset %}
|
||||
{% set lastmonth %}{% trans "Last Month" %}{% endset %}
|
||||
{% set lastyear %}{% trans "Last Year" %}{% endset %}
|
||||
{% set options = [
|
||||
{ filterName: "", reportFilter: range },
|
||||
{ filterName: "wholecampaign", reportFilter: wholecampaign },
|
||||
{ filterName: "today", reportFilter: today },
|
||||
{ filterName: "yesterday", reportFilter: yesterday },
|
||||
{ filterName: "thisweek", reportFilter: thisweek },
|
||||
{ filterName: "thismonth", reportFilter: thismonth },
|
||||
{ filterName: "thisyear", reportFilter: thisyear },
|
||||
{ filterName: "lastweek", reportFilter: lastweek },
|
||||
{ filterName: "lastmonth", reportFilter: lastmonth },
|
||||
{ filterName: "lastyear", reportFilter: lastyear },
|
||||
] %}
|
||||
{{ inline.dropdown("reportFilter", "single", title, "today", options, "filterName", "reportFilter") }}
|
||||
|
||||
{% set title %}{% trans "From Date" %}{% endset %}
|
||||
{{ inline.date("fromDt", title, defaults.fromDateOneDay, "", "from-dt", "", "") }}
|
||||
|
||||
{% set title %}{% trans "To Date" %}{% endset %}
|
||||
{{ inline.date("toDt", title, defaults.toDate, "", "to-dt", "", "") }}
|
||||
|
||||
{% set title %}{% trans "Group by" %}{% endset %}
|
||||
{% set hour %}{% trans "Hour" %}{% endset %}
|
||||
{% set day %}{% trans "Day" %}{% endset %}
|
||||
{% set week %}{% trans "Week" %}{% endset %}
|
||||
{% set month %}{% trans "Month" %}{% endset %}
|
||||
|
||||
{% set options = [
|
||||
{ name: "hour", filter: hour },
|
||||
{ name: "day", filter: day },
|
||||
{ name: "week", filter: week },
|
||||
{ name: "month", filter: month },
|
||||
] %}
|
||||
{{ inline.dropdown("groupBy", "single", title, "today", options, "name", "filter") }}
|
||||
|
||||
{% set title %}{% trans "Display" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("display.search") },
|
||||
{ name: "data-search-term", value: "display" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "displayId" },
|
||||
{ name: "data-text-property", value: "display" }
|
||||
] %}
|
||||
{{ inline.dropdown("displayId", "single", title, "", null, "displayId", "display", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("displayGroup.search") },
|
||||
{ name: "data-search-term", value: "displayGroup" },
|
||||
{ name: "data-id-property", value: "displayGroupId" },
|
||||
{ name: "data-text-property", value: "displayGroup" }
|
||||
] %}
|
||||
{{ inline.dropdown("displayGroupId[]", "dropdownmulti", title, "", null, "displayGroupId", "displayGroup", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{# Campaign list only. #}
|
||||
{% set attributes = [
|
||||
{ name: "data-search-url", value: url_for("campaign.search") },
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
] %}
|
||||
|
||||
{% set title %}{% trans "Campaign" %}{% endset %}
|
||||
{% set helpText %}{% trans "Please select a Campaign" %}{% endset %}
|
||||
{{ inline.dropdown("parentCampaignId", "single", title, "", null, "campaignId", "campaign", "", "", "", "", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Layout" %}{% endset %}
|
||||
{% set helpText %}{% trans "This field is required when the Type selected is Layout" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("layout.search") },
|
||||
{ name: "data-search-term", value: "layout" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "layoutId" },
|
||||
{ name: "data-text-property", value: "layout" }
|
||||
] %}
|
||||
{{ inline.dropdown("layoutId", "single", title, "", null, "layoutId", "layout", helpText, "pagedSelect layout-select", "", "l", "", attributes) }}
|
||||
|
||||
<div class="w-100">
|
||||
<a id="applyBtn" class="btn btn-success">
|
||||
<span>{% trans "Apply" %}</span>
|
||||
</a>
|
||||
<span id="applyWarning" class="text-warning" style="display:none; padding-left: 10px">{% trans "Warning: This may return a lot of data and may take several minutes to process." %}</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Card Header -->
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-tabs card-header-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="chart-tab" data-toggle="tab" href="#chartTab" role="tab"
|
||||
aria-controls="chartTab" aria-selected="true">Chart</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="tabular-tab" data-toggle="tab" href="#tabularTab" role="tab"
|
||||
aria-controls="tabularTab" aria-selected="false">Tabular</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Card Body -->
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<!-- CHART TAB-->
|
||||
<div class="tab-pane active" id="chartTab" role="tabpanel" aria-labelledby="chart-tab">
|
||||
<div class="chart-container" style="height:550px;">
|
||||
<canvas id="canvas" style="clear:both; margin-top:25px;" height="70%"></canvas>
|
||||
<img id="imageLoader" style="display: block; margin: auto;" src="{{ theme.uri("img/loader.gif") }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TABULAR TAB-->
|
||||
<div class="tab-pane show" id="tabularTab" role="tabpanel" aria-labelledby="tabular-tab">
|
||||
<!-- DATATABLE -->
|
||||
<div class="table-container" id="table_wrapper">
|
||||
<table id="stats"
|
||||
class="table xibo-table table-striped table-full-width"
|
||||
style="width: 100%"
|
||||
data-state-preference-name="displayAdPlayGrid"
|
||||
data-url="/report/data/displayAdPlay">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Period" %}</th>
|
||||
<th>{% trans "Ad Plays" %}</th>
|
||||
<th>{% trans "Impressions" %}</th>
|
||||
<th>{% trans "Spend" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
$(document).ready(function() {
|
||||
let $dataTable = $('#stats'); // Datatable
|
||||
let chart = null; // Chart
|
||||
let result; // XHR get data result
|
||||
|
||||
let imageLoader = $("#imageLoader");
|
||||
let $warning = $("#applyWarning");
|
||||
let $applyBtn = $("#applyBtn");
|
||||
|
||||
// Report Filter
|
||||
let reportFilter = $("#reportFilter"); // Report Filter
|
||||
|
||||
// Initialize table with empty data
|
||||
let table = $dataTable.DataTable({
|
||||
"language": dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
stateLoadCallback: dataTableStateLoadCallback,
|
||||
stateSaveCallback: dataTableStateSaveCallback,
|
||||
drawCallback: function( settings ) {
|
||||
setTimeout(function() {
|
||||
$("#applyBtn").removeClass('disabled');
|
||||
}, 300);
|
||||
},
|
||||
filter: false,
|
||||
order: [[1, "asc"]],
|
||||
data:{},
|
||||
"columns": [
|
||||
{"data": "labelDate"},
|
||||
{"data": "adPlays"},
|
||||
{
|
||||
"data": "impressions",
|
||||
"render": dataTableRoundDecimal
|
||||
},
|
||||
{
|
||||
"data": "spend",
|
||||
"render": dataTableRoundDecimal
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
// Get Data
|
||||
function getData(url) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
data: $("#stats").closest(".XiboGrid").find(".FilterDiv form").serializeObject(),
|
||||
success: function success(data) {
|
||||
result = data;
|
||||
$applyBtn.removeClass('disabled');
|
||||
|
||||
// Based on tab load data
|
||||
if ($('.nav-tabs .nav-item a.active').attr("href") === '#tabularTab') {
|
||||
setTabularData(table, result.table);
|
||||
} else {
|
||||
setChartData(result.chart);
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
toastr.error(result.error);
|
||||
}
|
||||
},
|
||||
error: function error(xhr, textStatus, _error) {
|
||||
$applyBtn.removeClass('disabled');
|
||||
toastr.error(xhr.responseJSON.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setTabularData(table, data) {
|
||||
table.clear().draw();
|
||||
|
||||
// Sort table by period
|
||||
if (Object.keys(data).length > 0) {
|
||||
table.rows.add(data).order([0, 'asc']).draw()
|
||||
}
|
||||
}
|
||||
|
||||
function setChartData(data) {
|
||||
imageLoader.show();
|
||||
|
||||
setTimeout(function() {
|
||||
$applyBtn.removeClass('disabled');
|
||||
}, 300);
|
||||
|
||||
imageLoader.hide();
|
||||
if (chart !== undefined && chart !== null) {
|
||||
chart.destroy();
|
||||
}
|
||||
|
||||
// Create our chart
|
||||
chart = new Chart($("#canvas"), data);
|
||||
}
|
||||
|
||||
// Tab shown/click load relevant table/chart
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
let activeTab = $(e.target).attr("href")
|
||||
if (result) {
|
||||
if (activeTab === '#tabularTab') {
|
||||
setTabularData(table, result.table);
|
||||
} else {
|
||||
setChartData(result.chart);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
dataTableAddButtons(table, $('#stats_wrapper').find('.dataTables_buttons'));
|
||||
|
||||
// Apply
|
||||
$applyBtn.click(function () {
|
||||
$(this).addClass('disabled');
|
||||
getData($dataTable.data().url);
|
||||
});
|
||||
|
||||
// If we select a displayId we hide the display group filter
|
||||
$('#displayId').off('change').change( function() {
|
||||
|
||||
let displayId = $('#displayId').val();
|
||||
if (displayId) {
|
||||
$('select[name="displayGroupId[]"] option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().hide();
|
||||
} else {
|
||||
$('#displayId option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().show();
|
||||
}
|
||||
});
|
||||
|
||||
// Hide / Show FromDt and ToDt
|
||||
function checkReportFilter(reportFilter) {
|
||||
if (reportFilter.val() === '' || reportFilter.val() === undefined) {
|
||||
$(".from-dt").show();
|
||||
$(".to-dt").show();
|
||||
} else {
|
||||
$(".from-dt").hide();
|
||||
$(".to-dt").hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the difference of number of days of a selected range
|
||||
let calculateDaysShowHideWarn = function() {
|
||||
|
||||
let fromDt = moment($("#fromDt").val());
|
||||
let toDt = moment($("#toDt").val());
|
||||
|
||||
let days = toDt.diff(fromDt, 'days');
|
||||
|
||||
$warning.hide();
|
||||
if ( days >= 30) {
|
||||
$warning.show();
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
$("#fromDtLink").change( function() {
|
||||
calculateDaysShowHideWarn();
|
||||
});
|
||||
|
||||
$("#toDtLink").change( function() {
|
||||
calculateDaysShowHideWarn();
|
||||
});
|
||||
|
||||
let checkFilterAndApply = function() {
|
||||
|
||||
reportFilter.off('change').change( function() {
|
||||
let value = reportFilter.val();
|
||||
|
||||
// Hide / Show FromDt and ToDt
|
||||
checkReportFilter(reportFilter);
|
||||
|
||||
// Hide / Show Warning
|
||||
$warning.hide();
|
||||
if ( value === '') {
|
||||
calculateDaysShowHideWarn();
|
||||
} else if ( value === 'thismonth' || value === 'lastmonth' || value === 'thisyear' || value === 'lastyear') {
|
||||
$warning.show();
|
||||
}
|
||||
});
|
||||
|
||||
let anchorReportAddBtn = $("button#reportAddBtn");
|
||||
|
||||
anchorReportAddBtn.attr("href", "{{ url_for("reportschedule.add.form") }}?reportName=displayAdPlay" );
|
||||
|
||||
};
|
||||
|
||||
imageLoader.hide();
|
||||
checkReportFilter(reportFilter);
|
||||
checkFilterAndApply();
|
||||
|
||||
|
||||
var $campaignSelect = $('#parentCampaignId');
|
||||
$campaignSelect.select2({
|
||||
ajax: {
|
||||
url: $campaignSelect.data("searchUrl"),
|
||||
dataType: "json",
|
||||
delay: 250,
|
||||
placeholder: 'Campaign',
|
||||
allowClear: true,
|
||||
data: function(params) {
|
||||
|
||||
var query = {
|
||||
isLayoutSpecific: 0,
|
||||
retired: 0,
|
||||
totalDuration: 0,
|
||||
name: params.term,
|
||||
start: 0,
|
||||
length: 10,
|
||||
columns: [
|
||||
{
|
||||
"data": "isLayoutSpecific"
|
||||
},
|
||||
{
|
||||
"data": "campaign"
|
||||
}
|
||||
],
|
||||
order: [
|
||||
{
|
||||
"column": 0,
|
||||
"dir": "asc"
|
||||
},
|
||||
{
|
||||
"column": 1,
|
||||
"dir": "asc"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Set the start parameter based on the page number
|
||||
if (params.page != null) {
|
||||
query.start = (params.page - 1) * 10;
|
||||
}
|
||||
|
||||
return query;
|
||||
},
|
||||
processResults: function(data, params) {
|
||||
|
||||
var results = [];
|
||||
var campaigns = [];
|
||||
|
||||
$.each(data.data, function(index, element) {
|
||||
campaigns.push({
|
||||
"id": element.campaignId,
|
||||
"text": element.campaign
|
||||
});
|
||||
});
|
||||
|
||||
results.push({
|
||||
"text": $campaignSelect.data('transCampaigns'),
|
||||
"children": campaigns
|
||||
})
|
||||
|
||||
|
||||
var page = params.page || 1;
|
||||
page = (page > 1) ? page - 1 : page;
|
||||
|
||||
return {
|
||||
results: results,
|
||||
pagination: {
|
||||
more: (page * 10 < data.recordsTotal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function reportScheduleCallback() {
|
||||
|
||||
let $displayId = $('#report #displayId');
|
||||
let $newDisplayId = $('#reportScheduleAddForm #displayId');
|
||||
|
||||
appendOptions($newDisplayId, $displayId.find('option:selected').clone());
|
||||
}
|
||||
|
||||
function appendOptions(element, options) {
|
||||
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
|
||||
let option = options[i];
|
||||
element.append(option).trigger('change');
|
||||
$(option).prop('selected', true);
|
||||
element.trigger({
|
||||
type: 'select2:select',
|
||||
params: {
|
||||
data: option
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
98
reports/display-adplays-report-preview.twig
Normal file
98
reports/display-adplays-report-preview.twig
Normal file
@@ -0,0 +1,98 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
<button class="btn btn-info XiboRedirectButton" href="{{ url_for("savedreport.view") }}"><i class="fa fa-eye" aria-hidden="true"></i> {% trans "Saved Reports" %}</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<i class="fa fa-list"></i>
|
||||
{{ metadata.title }}
|
||||
<span class="small">({% trans "Generated on: " %}{{ metadata.generatedOn }})</span>
|
||||
<div><span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}">
|
||||
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="stats" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Period" %}</th>
|
||||
<th>{% trans "Ad Plays" %}</th>
|
||||
<th>{% trans "Impressions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
$(document).ready(function() {
|
||||
|
||||
let outputData = {{ table|json_encode|raw }};
|
||||
|
||||
// Grid
|
||||
let table = $("#stats").DataTable({
|
||||
"language": dataTablesLanguage,
|
||||
"dom": dataTablesTemplate,
|
||||
"paging": false,
|
||||
"ordering": false,
|
||||
"info": false,
|
||||
"order": [[1, "asc"]],
|
||||
"searching": false,
|
||||
"data": outputData,
|
||||
"columns": [
|
||||
{
|
||||
"data": "labelDate",
|
||||
"render": dataTableDateFromIso
|
||||
},
|
||||
{"data": "adPlays"},
|
||||
{"data": "impressions"}
|
||||
]
|
||||
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
100
reports/display-adplays-schedule-form-add.twig
Normal file
100
reports/display-adplays-schedule-form-add.twig
Normal file
@@ -0,0 +1,100 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
{% extends "form-base.twig" %}
|
||||
{% import "forms.twig" as forms %}
|
||||
|
||||
{% block formTitle %}
|
||||
{% trans "Add Report Schedule" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block formButtons %}
|
||||
{% trans "Cancel" %}, XiboDialogClose()
|
||||
{% trans "Save" %}, $("#reportScheduleAddForm").submit()
|
||||
{% endblock %}
|
||||
|
||||
{% block callBack %}reportScheduleCallback{% endblock %}
|
||||
|
||||
{% block formHtml %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form id="reportScheduleAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("reportschedule.add") }}">
|
||||
|
||||
{{ forms.hidden("hiddenFields", hiddenFields) }}
|
||||
{{ forms.hidden("reportName", reportName) }}
|
||||
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{% set helpText %}{% trans "The name for this report schedule" %}{% endset %}
|
||||
{{ forms.input("name", title, "", helpText, "", "required") }}
|
||||
|
||||
{% set title %}{% trans "Frequency" %}{% endset %}
|
||||
{% set helpText %}{% trans "Select how frequently you would like this report to run" %}{% endset %}
|
||||
{% set daily %}{% trans "Daily" %}{% endset %}
|
||||
{% set weekly %}{% trans "Weekly" %}{% endset %}
|
||||
{% set monthly %}{% trans "Monthly" %}{% endset %}
|
||||
{% set yearly %}{% trans "Yearly" %}{% endset %}
|
||||
{% set options = [
|
||||
{ name: "daily", filter: daily },
|
||||
{ name: "weekly", filter: weekly },
|
||||
{ name: "monthly", filter: monthly },
|
||||
{ name: "yearly", filter: yearly },
|
||||
] %}
|
||||
{{ forms.dropdown("filter", "single", title, "", options, "name", "filter", helpText) }}
|
||||
|
||||
{% set title %}{% trans "Display" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("display.search") },
|
||||
{ name: "data-search-term", value: "display" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-default-values", value: displayId },
|
||||
{ name: "data-id-property", value: "displayId" },
|
||||
{ name: "data-text-property", value: "display" }
|
||||
] %}
|
||||
{{ forms.dropdown("displayId", "single", title, "", null, "displayId", "display", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("displayGroup.search") },
|
||||
{ name: "data-search-term", value: "displayGroup" },
|
||||
{ name: "data-id-property", value: "displayGroupId" },
|
||||
{ name: "data-text-property", value: "displayGroup" }
|
||||
] %}
|
||||
{{ forms.dropdown("displayGroupId[]", "dropdownmulti", title, "", null, "displayGroupId", "displayGroup", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Should an email be sent?" %}{% endset %}
|
||||
{{ forms.checkbox("sendEmail", title, sendEmail) }}
|
||||
|
||||
{% set title %}{% trans "Email addresses" %}{% endset %}
|
||||
{% set helpText %}{% trans "Additional emails separated by a comma." %}{% endset %}
|
||||
{{ forms.inputWithTags("nonusers", title, nonusers, helpText) }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
704
reports/display-percentage-report-form.twig
Normal file
704
reports/display-percentage-report-form.twig
Normal file
@@ -0,0 +1,704 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block title %}{% trans "Report: Display Played Percentage" %} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
{% include "report-schedule-buttons.twig" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<span>{% trans "Display Played Percentage" %}</span>
|
||||
</div>
|
||||
|
||||
{% include "report-selector.twig" %}
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-grid-name="displayPercentageView" data-refresh-on-form-submit="false">
|
||||
<div class="XiboFilterCustom card bg-light mb-3">
|
||||
<div class="FilterDiv card-body" id="displayPercentageReport">
|
||||
<!-- Form Filter -->
|
||||
<form class="form-inline">
|
||||
{# Campaign list only. #}
|
||||
{% set attributes = [
|
||||
{ name: "data-search-url", value: url_for("campaign.search") },
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
] %}
|
||||
|
||||
{% set title %}{% trans "Campaign" %} * {% endset %}
|
||||
{% set helpText %}{% trans "Please select a Campaign" %}{% endset %}
|
||||
{{ inline.dropdown("parentCampaignId", "single", title, "", null, "campaignId", "campaign", "", "", "", "", "", attributes) }}
|
||||
|
||||
<div class="w-100">
|
||||
<a id="applyBtn" class="btn btn-success">
|
||||
<span>{% trans "Apply" %}</span>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card Header -->
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-tabs card-header-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="tabular-tab" data-toggle="tab" href="#tabularTab" role="tab"
|
||||
aria-controls="tabularTab" aria-selected="true">Tabular</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="spend-chart-tab" data-toggle="tab" href="#spendChartTab" role="tab"
|
||||
aria-controls="chartTab" aria-selected="false">Chart (Spend)</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="playtime-chart-tab" data-toggle="tab" href="#playtimeChartTab" role="tab"
|
||||
aria-controls="chartTab" aria-selected="false">Chart (Playtime)</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Card Body -->
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
|
||||
<!-- TABULAR TAB-->
|
||||
<div class="tab-pane active" id="tabularTab" role="tabpanel" aria-labelledby="tabular-tab">
|
||||
<!-- DATATABLE -->
|
||||
<div class="table-container" id="table_wrapper">
|
||||
<table id="displayPercentageTbl"
|
||||
class="table xibo-table table-striped table-full-width"
|
||||
style="width: 100%"
|
||||
data-state-preference-name="displayPercentageGrid"
|
||||
data-url="/report/data/displayPercentage">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Display" %}</th>
|
||||
<th>{% trans "Spend(%)" %}</th>
|
||||
<th>{% trans "Playtime(%)" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CHART TAB-->
|
||||
<div class="tab-pane show" id="spendChartTab" role="tabpanel" aria-labelledby="spend-chart-tab">
|
||||
|
||||
<div id="otherSpend" style="position: absolute;">
|
||||
<div class="switch-other-main">
|
||||
<input type="checkbox" id="otherSpendSwitch" checked
|
||||
data-chart-name="spendChart" data-chart-canvas="spendChartCanvas"
|
||||
data-on-text="Main" data-off-text="Other"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-container" style="height:550px;">
|
||||
<canvas id="spendChartCanvas"
|
||||
{# style="clear:both; margin-top:25px;" height="70%"#}
|
||||
></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CHART TAB-->
|
||||
<div class="tab-pane show" id="playtimeChartTab" role="tabpanel" aria-labelledby="playtime-chart-tab">
|
||||
|
||||
<div id="otherPlaytime" style="position: absolute;">
|
||||
<div class="switch-other-main">
|
||||
<input type="checkbox" id="otherPlaytimeSwitch" checked
|
||||
data-chart-name="playtimeChart" data-chart-canvas="playtimeChartCanvas"
|
||||
data-on-text="Main" data-off-text="Other"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-container" style="height:550px;">
|
||||
<canvas id="playtimeChartCanvas"
|
||||
{# style="clear:both; margin-top:25px;" height="70%"#}
|
||||
></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
let $report = $("#displayPercentageReport");
|
||||
let $dataTable = $('#displayPercentageTbl'); // Datatable
|
||||
|
||||
// Charts
|
||||
let $spendChartCanvas = $('#spendChartCanvas');
|
||||
let $playtimeChartCanvas = $('#playtimeChartCanvas');
|
||||
|
||||
let chart = null; // Chart
|
||||
let result; // XHR get data result
|
||||
|
||||
let imageLoader = $("#imageLoader");
|
||||
let $warning = $("#applyWarning");
|
||||
let $applyBtn = $("#applyBtn");
|
||||
|
||||
// Initialize table with empty data
|
||||
let table = $dataTable.DataTable({
|
||||
"language": dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
stateLoadCallback: dataTableStateLoadCallback,
|
||||
stateSaveCallback: dataTableStateSaveCallback,
|
||||
filter: false,
|
||||
order: [[1, "asc"]],
|
||||
data:{},
|
||||
"columns": [
|
||||
{
|
||||
data: 'label',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
data: 'spendData',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
data: 'playtimeDuration',
|
||||
sortable: false
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
// Get Data
|
||||
function getData(url) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
data: $("#displayPercentageTbl").closest(".XiboGrid").find(".FilterDiv form").serializeObject(),
|
||||
success: function success(data) {
|
||||
result = data;
|
||||
$applyBtn.removeClass('disabled');
|
||||
|
||||
// Based on tab load data
|
||||
let activeTabHref = $('.nav-tabs .nav-item a.active').attr("href");
|
||||
if (activeTabHref === '#spendChartTab') {
|
||||
setSpendChartData($spendChartCanvas, result.table);
|
||||
} else if (activeTabHref === '#playtimeChartTab') {
|
||||
setPlaytimeChartData($playtimeChartCanvas, result.table);
|
||||
} else {
|
||||
setTabularData(table, result.table);
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
toastr.error(result.error);
|
||||
}
|
||||
},
|
||||
error: function error(xhr, textStatus, _error) {
|
||||
$applyBtn.removeClass('disabled');
|
||||
toastr.error(xhr.responseJSON.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setTabularData(table, data) {
|
||||
table.clear().draw();
|
||||
|
||||
if (Object.keys(data).length > 0) {
|
||||
// Sort table by display spend
|
||||
table.rows.add(data).order([1, 'desc']).draw()
|
||||
}
|
||||
}
|
||||
|
||||
$('.switch-other-main').hide();
|
||||
|
||||
function drawChart(chartData, configOptions, $ctx) {
|
||||
|
||||
return new Chart($ctx, {
|
||||
type: 'doughnut',
|
||||
plugins: [ChartDataLabels],
|
||||
data: chartData,
|
||||
options: configOptions,
|
||||
});
|
||||
}
|
||||
|
||||
function updateChartData(chartName, labels, chartData, bgColor) {
|
||||
eval(chartName).data.labels = labels;
|
||||
eval(chartName).data.datasets[0].data = chartData;
|
||||
eval(chartName).data.datasets[0].backgroundColor = bgColor;
|
||||
eval(chartName).update();
|
||||
}
|
||||
|
||||
function updateConfigAsNewObject(chartName, total) {
|
||||
eval(chartName).options = {
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top'
|
||||
},
|
||||
datalabels: {
|
||||
render: 'percentage',
|
||||
fontColor: 'white',
|
||||
precision: 2,
|
||||
}
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
animation: {
|
||||
onComplete: function onComplete(animation) {
|
||||
// hideLoading();
|
||||
}
|
||||
}
|
||||
};
|
||||
eval(chartName).update();
|
||||
}
|
||||
|
||||
$("#otherSpendSwitch, #otherPlaytimeSwitch").bootstrapSwitch({
|
||||
onSwitchChange: function(e, state) {
|
||||
let chartName = $(this).attr("data-chart-name");
|
||||
let mainTotal = $(this).attr("data-main-total");
|
||||
|
||||
// Main Chart Info
|
||||
let mainChartData = $(this).attr("data-main-data").split(",");
|
||||
let mainLabels = $(this).attr("data-main-labels").split(",");
|
||||
let mainBgColor = $(this).attr("data-main-bg-color").split(",");
|
||||
|
||||
// Other Chart Info
|
||||
let otherChartData = $(this).attr("data-other-data").split(",");
|
||||
let otherLabels = $(this).attr("data-other-labels").split(",");
|
||||
let otherBgColor = $(this).attr("data-other-bg-color").split(",");
|
||||
|
||||
if (state) {
|
||||
updateChartData(chartName, mainLabels, mainChartData, mainBgColor);
|
||||
} else {
|
||||
updateChartData(chartName, otherLabels, otherChartData, otherBgColor);
|
||||
}
|
||||
|
||||
// Update chart config when other/main button pressed
|
||||
updateConfigAsNewObject(chartName, mainTotal);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Set chart data
|
||||
let spendChart;
|
||||
function setSpendChartData($chartCanvas, response) {
|
||||
$('.chart-container .alert-info').remove();
|
||||
if (spendChart !== undefined && spendChart !== null) {
|
||||
spendChart.destroy();
|
||||
}
|
||||
|
||||
let mainChartTotal = 0;
|
||||
$.each(response, function (index, element) {
|
||||
// Get total
|
||||
mainChartTotal += element.spendData;
|
||||
});
|
||||
|
||||
// Convert our data into a dataset we can use for this chart.
|
||||
let labels = [];
|
||||
let data = [];
|
||||
let backgroundColor = [];
|
||||
let sum = 0;
|
||||
|
||||
let otherChartTotal = 0;
|
||||
let otherChartData = [];
|
||||
let otherChartLabels = [];
|
||||
let otherChartBgColor = [];
|
||||
$.each(response, function(index, element) {
|
||||
let percent = 0;
|
||||
percent = element.spendData / mainChartTotal * 100;
|
||||
|
||||
if (percent < 10) {
|
||||
// Keep track of total of Other
|
||||
otherChartTotal += element.spendData;
|
||||
otherChartData.push(element.spendData);
|
||||
otherChartLabels.push(element.label);
|
||||
otherChartBgColor.push(element.backgroundColor);
|
||||
} else {
|
||||
labels.push(element.label);
|
||||
data.push(element.spendData);
|
||||
backgroundColor.push(element.backgroundColor);
|
||||
}
|
||||
sum += element.spendData;
|
||||
});
|
||||
|
||||
if (sum <= 0) {
|
||||
// Show a message
|
||||
if ($('.chart-container .alert-info').length <= 0) {
|
||||
$('.chart-container').append($('<div class="alert alert-info">No display data for Campaign.</div>'))
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let $otherSpendBtns = $('#otherSpend .switch-other-main');
|
||||
|
||||
|
||||
if (otherChartData.length === 1) {
|
||||
// We push only on other slice in main chart
|
||||
labels.push(otherChartLabels);
|
||||
data.push(otherChartData);
|
||||
backgroundColor.push(otherChartBgColor);
|
||||
} else if (otherChartData.length > 1) {
|
||||
// We have other slice in chart
|
||||
// Combine the Other slice in Main Chart
|
||||
labels.push('Other');
|
||||
data.push(otherChartTotal);
|
||||
|
||||
// Set background color of "Other Slice"
|
||||
backgroundColor.push('#808080');
|
||||
|
||||
// Show two buttons Other and Main
|
||||
$otherSpendBtns.show();
|
||||
}
|
||||
|
||||
let chartData = {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Display Played Percentage',
|
||||
data: data,
|
||||
backgroundColor: backgroundColor,
|
||||
}],
|
||||
};
|
||||
let configOptions = {
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
datalabels: {
|
||||
render: 'percentage',
|
||||
fontColor: 'white',
|
||||
precision: 2,
|
||||
},
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
animation: {
|
||||
onComplete: function(animation) {
|
||||
// hideLoading();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
spendChart = drawChart(chartData, configOptions, $('#spendChartCanvas'));
|
||||
|
||||
// Update chart config
|
||||
updateConfigAsNewObject('spendChart', mainChartTotal);
|
||||
|
||||
$otherSpendBtns.find("input").attr("data-other-labels", otherChartLabels);
|
||||
$otherSpendBtns.find("input").attr("data-other-data", otherChartData);
|
||||
$otherSpendBtns.find("input").attr("data-other-bg-color", otherChartBgColor);
|
||||
|
||||
$otherSpendBtns.find("input").attr("data-main-labels", labels);
|
||||
$otherSpendBtns.find("input").attr("data-main-data", data);
|
||||
$otherSpendBtns.find("input").attr("data-main-bg-color", backgroundColor);
|
||||
|
||||
$otherSpendBtns.find("input").attr("data-main-total", mainChartTotal);
|
||||
|
||||
}
|
||||
|
||||
let playtimeChart;
|
||||
function setPlaytimeChartData($chartCanvas, response) {
|
||||
$('.chart-container .alert-info').remove();
|
||||
if (playtimeChart !== undefined && playtimeChart !== null) {
|
||||
playtimeChart.destroy();
|
||||
}
|
||||
|
||||
let mainChartTotal = 0;
|
||||
$.each(response, function (index, element) {
|
||||
// Get total
|
||||
mainChartTotal += element.playtimeDuration;
|
||||
});
|
||||
|
||||
// Convert our data into a dataset we can use for this chart.
|
||||
let labels = [];
|
||||
let data = [];
|
||||
let backgroundColor = [];
|
||||
let sum = 0;
|
||||
|
||||
let otherChartTotal = 0;
|
||||
let otherChartData = [];
|
||||
let otherChartLabels = [];
|
||||
let otherChartBgColor = [];
|
||||
$.each(response, function(index, element) {
|
||||
let percent = 0;
|
||||
percent = element.playtimeDuration / mainChartTotal * 100;
|
||||
|
||||
if (percent < 10) {
|
||||
// Keep track of total of Other
|
||||
otherChartTotal += element.playtimeDuration;
|
||||
otherChartData.push(element.playtimeDuration);
|
||||
otherChartLabels.push(element.label);
|
||||
otherChartBgColor.push(element.backgroundColor);
|
||||
} else {
|
||||
labels.push(element.label);
|
||||
data.push(element.playtimeDuration);
|
||||
backgroundColor.push(element.backgroundColor);
|
||||
}
|
||||
sum += element.playtimeDuration;
|
||||
});
|
||||
|
||||
if (sum <= 0) {
|
||||
// Show a message
|
||||
if ($('.chart-container .alert-info').length <= 0) {
|
||||
$('.chart-container').append($('<div class="alert alert-info">No display data for Campaign.</div>'))
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let $otherPlaytimeBtns = $('#otherPlaytime .switch-other-main');
|
||||
|
||||
if (otherChartData.length === 1) {
|
||||
// We push only on other slice in main chart
|
||||
labels.push(otherChartLabels);
|
||||
data.push(otherChartData);
|
||||
backgroundColor.push(otherChartBgColor);
|
||||
} else if (otherChartData.length > 1) {
|
||||
// We have other slice in chart
|
||||
// Combine the Other slice in Main Chart
|
||||
labels.push('Other');
|
||||
data.push(otherChartTotal);
|
||||
backgroundColor.push('#808080');
|
||||
|
||||
// Show two buttons Other and Main
|
||||
$otherPlaytimeBtns.show();
|
||||
}
|
||||
|
||||
let chartData = {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Display Played Percentage',
|
||||
data: data,
|
||||
backgroundColor: backgroundColor,
|
||||
}],
|
||||
};
|
||||
let configOptions = {
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
datalabels: {
|
||||
render: 'percentage',
|
||||
fontColor: 'white',
|
||||
precision: 2,
|
||||
},
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
animation: {
|
||||
onComplete: function(animation) {
|
||||
// hideLoading();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
playtimeChart = drawChart(chartData, configOptions, $('#playtimeChartCanvas'));
|
||||
|
||||
// Update chart config
|
||||
updateConfigAsNewObject('playtimeChart', mainChartTotal);
|
||||
|
||||
$otherPlaytimeBtns.find("input").attr("data-other-labels", otherChartLabels);
|
||||
$otherPlaytimeBtns.find("input").attr("data-other-data", otherChartData);
|
||||
$otherPlaytimeBtns.find("input").attr("data-other-bg-color", otherChartBgColor);
|
||||
|
||||
$otherPlaytimeBtns.find("input").attr("data-main-labels", labels);
|
||||
$otherPlaytimeBtns.find("input").attr("data-main-data", data);
|
||||
$otherPlaytimeBtns.find("input").attr("data-main-bg-color", backgroundColor);
|
||||
|
||||
$otherPlaytimeBtns.find("input").attr("data-main-total", mainChartTotal);
|
||||
|
||||
}
|
||||
|
||||
// Tab shown/click load relevant table/chart
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
let activeTab = $(e.target).attr("href")
|
||||
if (result) {
|
||||
if (activeTab === '#spendChartTab') {
|
||||
setSpendChartData($spendChartCanvas, result.table);
|
||||
} else if (activeTab === '#playtimeChartTab') {
|
||||
setPlaytimeChartData($playtimeChartCanvas, result.table);
|
||||
} else {
|
||||
setTabularData(table, result.table);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// todo
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
dataTableAddButtons(table, $('#stats_wrapper').find('.dataTables_buttons'));
|
||||
|
||||
// Apply
|
||||
$applyBtn.click(function () {
|
||||
$(this).addClass('disabled');
|
||||
checkFilterAndApply();
|
||||
getData($dataTable.data().url);
|
||||
});
|
||||
|
||||
|
||||
let checkFilterAndApply = function() {
|
||||
|
||||
let reportAddBtn = $("button#reportAddBtn");
|
||||
let $parentCampaign = $('#parentCampaignId');
|
||||
|
||||
// If we select a displayId we hide the display group filter
|
||||
$parentCampaign.off('change').change( function() {
|
||||
|
||||
let parentCampaignId = $(this).val();
|
||||
if (parentCampaignId) {
|
||||
$applyBtn.removeClass('disabled');
|
||||
reportAddBtn.removeClass('disabled');
|
||||
} else {
|
||||
$("#parentCampaignId option").remove();
|
||||
$applyBtn.addClass('disabled');
|
||||
reportAddBtn.addClass('disabled');
|
||||
}
|
||||
});
|
||||
reportAddBtn.attr("href", "{{ url_for("reportschedule.add.form") }}?parentCampaignId=" + $parentCampaign.val() + "&reportName=displayPercentage" );
|
||||
};
|
||||
|
||||
imageLoader.hide();
|
||||
$applyBtn.addClass('disabled');
|
||||
checkFilterAndApply();
|
||||
|
||||
// Bind to form change
|
||||
$report.on('change', function() {
|
||||
checkFilterAndApply();
|
||||
});
|
||||
|
||||
var $campaignSelect = $('#parentCampaignId');
|
||||
$campaignSelect.select2({
|
||||
ajax: {
|
||||
url: $campaignSelect.data("searchUrl"),
|
||||
dataType: "json",
|
||||
delay: 250,
|
||||
placeholder: 'Campaign',
|
||||
allowClear: true,
|
||||
data: function(params) {
|
||||
|
||||
var query = {
|
||||
isLayoutSpecific: 0,
|
||||
retired: 0,
|
||||
totalDuration: 0,
|
||||
name: params.term,
|
||||
start: 0,
|
||||
length: 10,
|
||||
columns: [
|
||||
{
|
||||
"data": "isLayoutSpecific"
|
||||
},
|
||||
{
|
||||
"data": "campaign"
|
||||
}
|
||||
],
|
||||
order: [
|
||||
{
|
||||
"column": 0,
|
||||
"dir": "asc"
|
||||
},
|
||||
{
|
||||
"column": 1,
|
||||
"dir": "asc"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Set the start parameter based on the page number
|
||||
if (params.page != null) {
|
||||
query.start = (params.page - 1) * 10;
|
||||
}
|
||||
|
||||
return query;
|
||||
},
|
||||
processResults: function(data, params) {
|
||||
|
||||
var results = [];
|
||||
var campaigns = [];
|
||||
|
||||
$.each(data.data, function(index, element) {
|
||||
campaigns.push({
|
||||
"id": element.campaignId,
|
||||
"text": element.campaign
|
||||
});
|
||||
});
|
||||
|
||||
results.push({
|
||||
"text": $campaignSelect.data('transCampaigns'),
|
||||
"children": campaigns
|
||||
})
|
||||
|
||||
|
||||
var page = params.page || 1;
|
||||
page = (page > 1) ? page - 1 : page;
|
||||
|
||||
return {
|
||||
results: results,
|
||||
pagination: {
|
||||
more: (page * 10 < data.recordsTotal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function reportScheduleCallback() {
|
||||
|
||||
let $parentCampaignId = $('#report #parentCampaignId');
|
||||
let $newParentCampaignId = $('#reportScheduleAddForm #parentCampaignId');
|
||||
|
||||
appendOptions($newParentCampaignId, $parentCampaignId.find('option:selected').clone());
|
||||
}
|
||||
|
||||
function appendOptions(element, options) {
|
||||
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
|
||||
let option = options[i];
|
||||
element.append(option).trigger('change');
|
||||
$(option).prop('selected', true);
|
||||
element.trigger({
|
||||
type: 'select2:select',
|
||||
params: {
|
||||
data: option
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
101
reports/display-percentage-report-preview.twig
Normal file
101
reports/display-percentage-report-preview.twig
Normal file
@@ -0,0 +1,101 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
<button class="btn btn-info XiboRedirectButton" href="{{ url_for("savedreport.view") }}"><i class="fa fa-eye" aria-hidden="true"></i> {% trans "Saved Reports" %}</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<i class="fa fa-list"></i>
|
||||
{{ metadata.title }}
|
||||
<span class="small">({% trans "Generated on: " %}{{ metadata.generatedOn }})</span>
|
||||
<div><span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}">
|
||||
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="stats" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Display" %}</th>
|
||||
<th>{% trans "Spend(%)" %}</th>
|
||||
<th>{% trans "Playtime(%)" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
$(document).ready(function() {
|
||||
|
||||
let outputData = {{ table|json_encode|raw }};
|
||||
|
||||
// Grid
|
||||
let table = $("#stats").DataTable({
|
||||
"language": dataTablesLanguage,
|
||||
"dom": dataTablesTemplate,
|
||||
"paging": false,
|
||||
"ordering": false,
|
||||
"info": false,
|
||||
"order": [[1, "asc"]],
|
||||
"searching": false,
|
||||
"data": outputData,
|
||||
"columns": [
|
||||
{
|
||||
data: 'label',
|
||||
},
|
||||
{
|
||||
data: 'spendData',
|
||||
},
|
||||
{
|
||||
data: 'playtimeDuration',
|
||||
}
|
||||
]
|
||||
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
72
reports/display-percentage-schedule-form-add.twig
Normal file
72
reports/display-percentage-schedule-form-add.twig
Normal file
@@ -0,0 +1,72 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
{% extends "form-base.twig" %}
|
||||
{% import "forms.twig" as forms %}
|
||||
|
||||
{% block formTitle %}
|
||||
{% trans "Add Report Schedule" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block formButtons %}
|
||||
{% trans "Cancel" %}, XiboDialogClose()
|
||||
{% trans "Save" %}, $("#reportScheduleAddForm").submit()
|
||||
{% endblock %}
|
||||
|
||||
{% block callBack %}reportScheduleCallback{% endblock %}
|
||||
|
||||
{% block formHtml %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form id="reportScheduleAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("reportschedule.add") }}">
|
||||
|
||||
{{ forms.hidden("hiddenFields", hiddenFields) }}
|
||||
{{ forms.hidden("reportName", reportName) }}
|
||||
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{% set helpText %}{% trans "The name for this report schedule" %}{% endset %}
|
||||
{{ forms.input("name", title, "", helpText, "", "required") }}
|
||||
|
||||
{% set title %}{% trans "Frequency" %}{% endset %}
|
||||
{% set helpText %}{% trans "Select how frequently you would like this report to run" %}{% endset %}
|
||||
{% set daily %}{% trans "Daily" %}{% endset %}
|
||||
{% set weekly %}{% trans "Weekly" %}{% endset %}
|
||||
{% set monthly %}{% trans "Monthly" %}{% endset %}
|
||||
{% set yearly %}{% trans "Yearly" %}{% endset %}
|
||||
{% set options = [
|
||||
{ name: "daily", filter: daily },
|
||||
{ name: "weekly", filter: weekly },
|
||||
{ name: "monthly", filter: monthly },
|
||||
{ name: "yearly", filter: yearly },
|
||||
] %}
|
||||
{{ forms.dropdown("filter", "single", title, "", options, "name", "filter", helpText) }}
|
||||
|
||||
{% set title %}{% trans "Should an email be sent?" %}{% endset %}
|
||||
{{ forms.checkbox("sendEmail", title, sendEmail) }}
|
||||
|
||||
{% set title %}{% trans "Email addresses" %}{% endset %}
|
||||
{% set helpText %}{% trans "Additional emails separated by a comma." %}{% endset %}
|
||||
{{ forms.inputWithTags("nonusers", title, nonusers, helpText) }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
56
reports/displayalerts-email-template.twig
Normal file
56
reports/displayalerts-email-template.twig
Normal file
@@ -0,0 +1,56 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "base-report.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span>
|
||||
</div>
|
||||
<p></p>
|
||||
|
||||
<table class="saved-report-table">
|
||||
<tr>
|
||||
<th>{% trans "Display ID" %}</th>
|
||||
<th>{% trans "Display" %}</th>
|
||||
<th>{% trans "Event Type" %}</th>
|
||||
<th>{% trans "Start" %}</th>
|
||||
<th>{% trans "End" %}</th>
|
||||
<th>{% trans "Reference ID" %}</th>
|
||||
<th>{% trans "Detail" %}</th>
|
||||
</tr>
|
||||
{% for item in tableData %}
|
||||
<tr>
|
||||
<td>{{ item.displayId }}</td>
|
||||
<td>{{ item.display }}</td>
|
||||
<td>{{ item.eventType }}</td>
|
||||
<td>{{ item.start }}</td>
|
||||
<td>{{ item.end }}</td>
|
||||
<td>{{ item.refId }}</td>
|
||||
<td>{{ item.detail }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<br/>
|
||||
<span>{{ placeholder }}</span>
|
||||
<img src="{{ src|raw }}" >
|
||||
{% endblock %}
|
||||
315
reports/displayalerts-report-form.twig
Normal file
315
reports/displayalerts-report-form.twig
Normal file
@@ -0,0 +1,315 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block title %}{% trans "Report: Display Alerts" %} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
{% include "report-schedule-buttons.twig" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<span>{% trans "Display Alerts" %}</span>
|
||||
</div>
|
||||
|
||||
{% include "report-selector.twig" %}
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-refresh-on-form-submit="false">
|
||||
<div class="XiboFilterCustom">
|
||||
<div class="FilterDiv card-body" id="displayAlertsFilter">
|
||||
<form class="form-inline">
|
||||
{% set title %}{% trans "Range" %}{% endset %}
|
||||
{{ inline.dateRangeFilter("reportFilter", title, "", "", "", "", "") }}
|
||||
|
||||
{% set title %}{% trans "Display" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("display.search") },
|
||||
{ name: "data-search-term", value: "display" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "displayId" },
|
||||
{ name: "data-text-property", value: "display" }
|
||||
] %}
|
||||
{{ inline.dropdown("displayId", "single", title, "", null, "displayId", "display", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("displayGroup.search") },
|
||||
{ name: "data-search-term", value: "displayGroup" },
|
||||
{ name: "data-id-property", value: "displayGroupId" },
|
||||
{ name: "data-text-property", value: "displayGroup" }
|
||||
] %}
|
||||
{{ inline.dropdown("displayGroupId[]", "dropdownmulti", title, "", null, "displayGroupId", "displayGroup", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Event Type" %}{% endset %}
|
||||
{% set options = [
|
||||
{ id: -1, value: "" },
|
||||
{ id: 1, value: "Display Up/down" },
|
||||
{ id: 2, value: "App Start" },
|
||||
{ id: 3, value: "Power Cycle" },
|
||||
{ id: 4, value: "Network Cycle" },
|
||||
{ id: 5, value: "TV Monitoring" },
|
||||
{ id: 6, value: "Player Fault" },
|
||||
{ id: 7, value: "Command" },
|
||||
{ id: 8, value: "Other" }
|
||||
] %}
|
||||
{{ inline.dropdown("eventType", "single", title, -1, options, "id", "value") }}
|
||||
|
||||
{% if currentUser.featureEnabled("tag.tagging") %}
|
||||
{% set title %}{% trans "Tags" %}{% endset %}
|
||||
{% set exactTagTitle %}{% trans "Exact match?" %}{% endset %}
|
||||
{% set logicalOperatorTitle %}{% trans "When filtering by multiple Tags, which logical operator should be used?" %}{% endset %}
|
||||
{% set helpText %}{% trans "A comma separated list of tags to filter by. Enter --no-tag to see items without tags." %}{% endset %}
|
||||
{{ inline.inputWithTags("tags", title, null, helpText, null, null, null, "exactTags", exactTagTitle, logicalOperatorTitle) }}
|
||||
{% endif %}
|
||||
|
||||
{% set title %}{% trans "Only show currently logged in?" %}{% endset %}
|
||||
{{ inline.checkbox("onlyLoggedIn", title) }}
|
||||
|
||||
<div class="w-100">
|
||||
<a id="applyBtn" class="btn btn-success">
|
||||
<span>{% trans "Apply" %}</span>
|
||||
</a>
|
||||
<span id="imageLoader" class=""></span>
|
||||
<span id="applyWarning" class="text-warning" style="display:none; padding-left: 10px">{% trans "Warning: This may return a lot of data and may take several minutes to process." %}</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card Body -->
|
||||
<div class="card-body">
|
||||
<!-- DATATABLE -->
|
||||
<div class="table-container" id="table_wrapper">
|
||||
<table id="displayAlertsGrid"
|
||||
class="table xibo-table table-striped table-full-width"
|
||||
style="width: 100%"
|
||||
data-url="{{ url_for("report.data", {name: 'displayalerts'}) }}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Display ID" %}</th>
|
||||
<th>{% trans "Display" %}</th>
|
||||
<th>{% trans "Event Type" %}</th>
|
||||
<th>{% trans "Start" %}</th>
|
||||
<th>{% trans "End" %}</th>
|
||||
<th>{% trans "Duration" %}</th>
|
||||
<th>{% trans "Reference" %}</th>
|
||||
<th>{% trans "Detail" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
$('[data-toggle="popover"]').popover();
|
||||
|
||||
let $report = $("#displayAlertsFilter");
|
||||
let $dataTable = $('#displayAlertsGrid'); // Datatable
|
||||
let result; // XHR get data result
|
||||
let reportData = '';
|
||||
|
||||
let imageLoader = $("#imageLoader");
|
||||
let $warning = $("#applyWarning");
|
||||
let $applyBtn = $("#applyBtn");
|
||||
|
||||
// Initialize table with empty data
|
||||
let table = $dataTable.DataTable({
|
||||
language: dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
searching: false,
|
||||
paging: true,
|
||||
bInfo: false,
|
||||
stateSave: true,
|
||||
bDestroy: true,
|
||||
processing: true,
|
||||
order: [[3, 'desc']],
|
||||
data: {},
|
||||
columns: [
|
||||
{data: 'displayId'},
|
||||
{data: 'display'},
|
||||
{data: 'eventType', "sortable": false},
|
||||
{
|
||||
name: 'start',
|
||||
data: function(data) {
|
||||
if(data.start) {
|
||||
return moment(data.start, 'X').format(jsDateFormat)
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'end',
|
||||
data: function(data) {
|
||||
if(data.end) {
|
||||
return moment(data.end, 'X').format(jsDateFormat)
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'duration',
|
||||
data: function(data) {
|
||||
if (data.start && data.end) {
|
||||
let durationData = moment.duration(data.end - data.start, "seconds");
|
||||
let dataM = '';
|
||||
|
||||
let months = durationData.months();
|
||||
if (months > 0) {
|
||||
durationData.subtract(moment.duration(months, 'months'));
|
||||
dataM += months + '{% trans "month" %} ';
|
||||
}
|
||||
|
||||
let days = durationData.days();
|
||||
durationData.subtract(moment.duration(days, 'days'));
|
||||
dataM += days + '{% trans "day" %} ';
|
||||
|
||||
let hours = durationData.hours();
|
||||
durationData.subtract(moment.duration(hours, 'hours'));
|
||||
dataM += hours + '{% trans "hr" %} ';
|
||||
|
||||
let minutes = durationData.minutes();
|
||||
durationData.subtract(moment.duration(minutes, 'minutes'));
|
||||
dataM += minutes + '{% trans "min" %} ';
|
||||
|
||||
let seconds = durationData.seconds();
|
||||
dataM += seconds + '{% trans "sec" %} ';
|
||||
|
||||
return dataM;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
},
|
||||
{data: 'refId'},
|
||||
{data: 'detail'}
|
||||
]
|
||||
});
|
||||
|
||||
// Get Data
|
||||
function getData(url) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
data: $report.find("form").serialize(),
|
||||
success: function success(data) {
|
||||
result = data;
|
||||
$applyBtn.removeClass('disabled');
|
||||
imageLoader.removeClass('fa fa-spinner fa-spin loading-icon');
|
||||
setTabularData(table, result.table);
|
||||
},
|
||||
error: function error(xhr, textStatus, _error) {
|
||||
$applyBtn.removeClass('disabled');
|
||||
toastr.error(xhr.responseJSON.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setTabularData(table, data) {
|
||||
table.clear().draw();
|
||||
|
||||
if (Object.keys(data).length > 0) {
|
||||
table.rows.add( data ).draw()
|
||||
}
|
||||
}
|
||||
|
||||
// Apply
|
||||
$applyBtn.click(function () {
|
||||
$(this).addClass('disabled');
|
||||
imageLoader.addClass('fa fa-spinner fa-spin loading-icon');
|
||||
getData($dataTable.data().url);
|
||||
});
|
||||
|
||||
// If we select a displayId we hide the display group filter
|
||||
$('#displayId').off('change').change( function() {
|
||||
let displayId = $('#displayId').val();
|
||||
if (displayId) {
|
||||
$('select[name="displayGroupId[]"] option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().hide();
|
||||
} else {
|
||||
$('#displayId option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().show();
|
||||
}
|
||||
});
|
||||
|
||||
$("#refreshGrid").click(function () {
|
||||
table.ajax.reload();
|
||||
});
|
||||
});
|
||||
|
||||
function displayAlertsReportScheduleFormOpen(dialog) {
|
||||
// If we select a displayId we hide the display group filter
|
||||
$('#reportScheduleAddForm #displayId').off('change').change( function() {
|
||||
|
||||
let displayId = $('#reportScheduleAddForm #displayId').val();
|
||||
if (displayId) {
|
||||
$('select[name="displayGroupId[]"] option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().parent().hide();
|
||||
} else {
|
||||
$('#reportScheduleAddForm #displayId option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().parent().show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
122
reports/displayalerts-report-preview.twig
Normal file
122
reports/displayalerts-report-preview.twig
Normal file
@@ -0,0 +1,122 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2020 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
<button class="btn btn-info XiboRedirectButton" href="{{ url_for("savedreport.view") }}"><i class="fa fa-eye" aria-hidden="true"></i> {% trans "Saved Reports" %}</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<i class="fa fa-list"></i>
|
||||
{{ metadata.title }}
|
||||
<span class="small">({% trans "Generated on: " %}{{ metadata.generatedOn }})</span>
|
||||
<div><span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}">
|
||||
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="displayAlertsReportPreview" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Display ID" %}</th>
|
||||
<th>{% trans "Display" %}</th>
|
||||
<th>{% trans "Event Type" %}</th>
|
||||
<th>{% trans "Start" %}</th>
|
||||
<th>{% trans "End" %}</th>
|
||||
<th>{% trans "Reference" %}</th>
|
||||
<th>{% trans "Detail" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
var outputData = {{ table|json_encode|raw }};
|
||||
|
||||
var table = $("#displayAlertsReportPreview").DataTable({
|
||||
language: dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
paging: false,
|
||||
ordering: false,
|
||||
info: false,
|
||||
order: [[3, 'desc']],
|
||||
searching: false,
|
||||
data: outputData,
|
||||
columns: [
|
||||
{data: 'displayId'},
|
||||
{data: 'display'},
|
||||
{data: 'eventType', "sortable": false},
|
||||
{
|
||||
name: 'start',
|
||||
data: function(data) {
|
||||
if(data.start) {
|
||||
return moment(data.start, 'X').format(jsDateFormat)
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'end',
|
||||
data: function(data) {
|
||||
if(data.end) {
|
||||
return moment(data.end, 'X').format(jsDateFormat)
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
{data: 'refId'},
|
||||
{data: 'detail'}
|
||||
]
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', function(e, settings, processing) {
|
||||
dataTableProcessing(e, settings, processing);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
106
reports/displayalerts-schedule-form-add.twig
Normal file
106
reports/displayalerts-schedule-form-add.twig
Normal file
@@ -0,0 +1,106 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "form-base.twig" %}
|
||||
{% import "forms.twig" as forms %}
|
||||
|
||||
{% block formTitle %}
|
||||
{% trans "Add Report Schedule" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block formButtons %}
|
||||
{% trans "Cancel" %}, XiboDialogClose()
|
||||
{% trans "Save" %}, $("#reportScheduleAddForm").submit()
|
||||
{% endblock %}
|
||||
|
||||
{% block callBack %}displayAlertsReportScheduleFormOpen{% endblock %}
|
||||
|
||||
{% block formHtml %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form id="reportScheduleAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("reportschedule.add") }}">
|
||||
{{ forms.hidden("hiddenFields", hiddenFields) }}
|
||||
{{ forms.hidden("reportName", reportName) }}
|
||||
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{% set helpText %}{% trans "The name for this report schedule" %}{% endset %}
|
||||
{{ forms.input("name", title, "", helpText, "", "required") }}
|
||||
|
||||
{% set title %}{% trans "Frequency" %}{% endset %}
|
||||
{% set helpText %}{% trans "Select how frequently you would like this report to run" %}{% endset %}
|
||||
{% set daily %}{% trans "Daily" %}{% endset %}
|
||||
{% set weekly %}{% trans "Weekly" %}{% endset %}
|
||||
{% set monthly %}{% trans "Monthly" %}{% endset %}
|
||||
{% set yearly %}{% trans "Yearly" %}{% endset %}
|
||||
{% set options = [
|
||||
{ name: "daily", filter: daily },
|
||||
{ name: "weekly", filter: weekly },
|
||||
{ name: "monthly", filter: monthly },
|
||||
{ name: "yearly", filter: yearly },
|
||||
] %}
|
||||
{{ forms.dropdown("filter", "single", title, "", options, "name", "filter", helpText) }}
|
||||
|
||||
{% set title %}{% trans "Display" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("display.search") },
|
||||
{ name: "data-search-term", value: "display" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "displayId" },
|
||||
{ name: "data-text-property", value: "display" }
|
||||
] %}
|
||||
{{ forms.dropdown("displayId", "single", title, "", null, "displayId", "display", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("displayGroup.search") },
|
||||
{ name: "data-search-term", value: "displayGroup" },
|
||||
{ name: "data-id-property", value: "displayGroupId" },
|
||||
{ name: "data-text-property", value: "displayGroup" }
|
||||
] %}
|
||||
{{ forms.dropdown("displayGroupId[]", "dropdownmulti", title, "", null, "displayGroupId", "displayGroup", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Start Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to run this report. Leave blank to run from the next collection point." %}{% endset %}
|
||||
{{ forms.dateTime("fromDt", title, "", helpText, "starttime-control") }}
|
||||
|
||||
{% set title %}{% trans "End Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to end the schedule. Leave blank to run indefinitely." %}{% endset %}
|
||||
{{ forms.dateTime("toDt", title, "", helpText, "endtime-control") }}
|
||||
|
||||
{% set title %}{% trans "Should an email be sent?" %}{% endset %}
|
||||
{{ forms.checkbox("sendEmail", title, sendEmail) }}
|
||||
|
||||
{% set title %}{% trans "Email addresses" %}{% endset %}
|
||||
{% set helpText %}{% trans "Additional emails separated by a comma." %}{% endset %}
|
||||
{{ forms.inputWithTags("nonusers", title, nonusers, helpText) }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
14
reports/displayalerts.report
Normal file
14
reports/displayalerts.report
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "displayalerts",
|
||||
"description": "Display Alerts",
|
||||
"class": "\\Xibo\\Report\\DisplayAlerts",
|
||||
"type": "Report",
|
||||
"output_type": "table",
|
||||
"color":"orange",
|
||||
"fa_icon": "fa-bell",
|
||||
"sort_order": 5,
|
||||
"hidden": 0,
|
||||
"category": "Display",
|
||||
"feature": "displays.reporting",
|
||||
"adminOnly": 0
|
||||
}
|
||||
48
reports/distribution-email-template.twig
Normal file
48
reports/distribution-email-template.twig
Normal file
@@ -0,0 +1,48 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "base-report.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span>
|
||||
</div>
|
||||
<p></p>
|
||||
<span>{{ placeholder }}</span>
|
||||
<img src="{{ src|raw }}" >
|
||||
<p></p>
|
||||
|
||||
<table class="saved-report-table">
|
||||
<tr>
|
||||
<th>{% trans "Period" %}</th>
|
||||
<th>{% trans "Duration" %}</th>
|
||||
<th>{% trans "Count" %}</th>
|
||||
</tr>
|
||||
{% for item in tableData %}
|
||||
<tr>
|
||||
<td>{{ item.label }}</td>
|
||||
<td>{{ item.duration }}</td>
|
||||
<td>{{ item.count }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
||||
562
reports/distribution-report-form.twig
Normal file
562
reports/distribution-report-form.twig
Normal file
@@ -0,0 +1,562 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2019 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block title %}{% trans "Report: Distribution by Layout, Media or Event" %} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
{% include "report-schedule-buttons.twig" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<span>{% trans "Distribution by Layout, Media or Event" %}</span>
|
||||
<span class="fa fa-info-circle widget-title-info px-1" data-toggle="popover" data-trigger="hover" data-placement="bottom" data-content="{% trans "This chart shows an aggregate duration and number of plays for the selected Layout, Media or Event. Please select your Range and Type below. Where the Range crosses period boundaries the data is aggregated into the correct period - i.e 1 week grouped by hourly produces 24 periods." %}"></span>
|
||||
</div>
|
||||
|
||||
{% include "report-selector.twig" %}
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-refresh-on-form-submit="false">
|
||||
<div class="XiboFilterCustom card bg-light mb-3">
|
||||
<div class="FilterDiv card-body" id="distributionReport">
|
||||
<!-- Form Filter -->
|
||||
<form class="form-inline">
|
||||
{% set title %}{% trans "Filter" %}{% endset %}
|
||||
{% set range %}{% trans "Select a range" %}{% endset %}
|
||||
{% set today %}{% trans "Today" %}{% endset %}
|
||||
{% set yesterday %}{% trans "Yesterday" %}{% endset %}
|
||||
{% set thisweek %}{% trans "This Week" %}{% endset %}
|
||||
{% set thismonth %}{% trans "This Month" %}{% endset %}
|
||||
{% set thisyear %}{% trans "This Year" %}{% endset %}
|
||||
{% set lastweek %}{% trans "Last Week" %}{% endset %}
|
||||
{% set lastmonth %}{% trans "Last Month" %}{% endset %}
|
||||
{% set lastyear %}{% trans "Last Year" %}{% endset %}
|
||||
{% set options = [
|
||||
{ filterName: "", reportFilter: range },
|
||||
{ filterName: "today", reportFilter: today },
|
||||
{ filterName: "yesterday", reportFilter: yesterday },
|
||||
{ filterName: "thisweek", reportFilter: thisweek },
|
||||
{ filterName: "thismonth", reportFilter: thismonth },
|
||||
{ filterName: "thisyear", reportFilter: thisyear },
|
||||
{ filterName: "lastweek", reportFilter: lastweek },
|
||||
{ filterName: "lastmonth", reportFilter: lastmonth },
|
||||
{ filterName: "lastyear", reportFilter: lastyear },
|
||||
] %}
|
||||
{{ inline.dropdown("reportFilter", "single", title, "today", options, "filterName", "reportFilter") }}
|
||||
|
||||
{% set title %}{% trans "From Date" %}{% endset %}
|
||||
{{ inline.date("statsFromDt", title, defaults.fromDateOneDay, "", "stats-from-dt", "", "") }}
|
||||
|
||||
{% set title %}{% trans "To Date" %}{% endset %}
|
||||
{{ inline.date("statsToDt", title, defaults.toDate, "", "stats-to-dt", "", "") }}
|
||||
|
||||
{% set title %}{% trans "Group by" %}{% endset %}
|
||||
{% set byhour %}{% trans "Hour" %}{% endset %}
|
||||
{% set bydayofweek %}{% trans "Day of week" %}{% endset %}
|
||||
{% set bydayofmonth %}{% trans "Day of month" %}{% endset %}
|
||||
{% set options = [
|
||||
{ filterName: "byhour", groupByFilter: byhour },
|
||||
{ filterName: "bydayofweek", groupByFilter: bydayofweek },
|
||||
{ filterName: "bydayofmonth", groupByFilter: bydayofmonth },
|
||||
] %}
|
||||
{{ inline.dropdown("groupByFilter", "single", title, "", options, "filterName", "groupByFilter", "", "group-by-filter") }}
|
||||
|
||||
{% set title %}{% trans "Type" %}{% endset %}
|
||||
{% set layout %}{% trans "Layout" %}{% endset %}
|
||||
{% set media %}{% trans "Media" %}{% endset %}
|
||||
{% set event %}{% trans "Event" %}{% endset %}
|
||||
{% set options = [
|
||||
{ typeid: "layout", type: layout },
|
||||
{ typeid: "media", type: media },
|
||||
{ typeid: "event", type: event },
|
||||
] %}
|
||||
{{ inline.dropdown("type", "single", title, "", options, "typeid", "type") }}
|
||||
|
||||
{% set title %}{% trans "Layout" %} *{% endset %}
|
||||
{% set helpText %}{% trans "This field is required when the Type selected is Layout" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("layout.search") },
|
||||
{ name: "data-search-term", value: "layout" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "layoutId" },
|
||||
{ name: "data-text-property", value: "layout" }
|
||||
] %}
|
||||
|
||||
{{ inline.dropdown("layoutId", "single", title, "", null, "layoutId", "layout", helpText, "pagedSelect layout-select", "", "l", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Media" %} *{% endset %}
|
||||
{% set helpText %}{% trans "This field is required when the Type selected is Media" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("library.search") },
|
||||
{ name: "data-search-term", value: "media" },
|
||||
{ name: "data-id-property", value: "mediaId" },
|
||||
{ name: "data-text-property", value: "name" }
|
||||
] %}
|
||||
{{ inline.dropdown("mediaId", "single", title, "", null, "mediaId", "name", helpText, "pagedSelect media-select", "", "m", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Tag" %} *{% endset %}
|
||||
{% set helpText %}{% trans "This field is required when the Type selected is Event" %}{% endset %}
|
||||
{{ inline.input("eventTag", title, "", helpText, "tag-text") }}
|
||||
|
||||
{% set title %}{% trans "Display" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("display.search") },
|
||||
{ name: "data-search-term", value: "display" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "displayId" },
|
||||
{ name: "data-text-property", value: "display" }
|
||||
] %}
|
||||
{{ inline.dropdown("displayId", "single", title, "", null, "displayId", "display", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("displayGroup.search") },
|
||||
{ name: "data-search-term", value: "displayGroup" },
|
||||
{ name: "data-id-property", value: "displayGroupId" },
|
||||
{ name: "data-text-property", value: "displayGroup" }
|
||||
] %}
|
||||
{{ inline.dropdown("displayGroupId[]", "dropdownmulti", title, "", null, "displayGroupId", "displayGroup", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
<div class="w-100">
|
||||
<a id="applyBtn" class="btn btn-success">
|
||||
<span>{% trans "Apply" %}</span>
|
||||
</a>
|
||||
<span id="imageLoader" class=""></span>
|
||||
<span id="applyWarning" class="text-warning" style="display:none; padding-left: 10px">{% trans "Warning: This may return a lot of data and may take several minutes to process." %}</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Card Header -->
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-tabs card-header-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="chart-tab" data-toggle="tab" href="#chartTab" role="tab"
|
||||
aria-controls="chartTab" aria-selected="true">Chart</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="tabular-tab" data-toggle="tab" href="#tabularTab" role="tab"
|
||||
aria-controls="tabularTab" aria-selected="false">Tabular</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div> <!-- Card Body -->
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<!-- CHART TAB-->
|
||||
<div class="tab-pane active" id="chartTab" role="tabpanel" aria-labelledby="chart-tab">
|
||||
<div class="chart-container" style="height:550px;">
|
||||
<canvas id="canvas" style="clear:both; margin-top:25px;" height="70%"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TABULAR TAB-->
|
||||
<div class="tab-pane show" id="tabularTab" role="tabpanel" aria-labelledby="tabular-tab">
|
||||
<!-- DATATABLE -->
|
||||
<div class="table-container" id="table_wrapper">
|
||||
<table id="distributionTbl"
|
||||
class="table xibo-table table-striped table-full-width"
|
||||
style="width: 100%"
|
||||
data-url="/report/data/distributionReport"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Period" %}</th>
|
||||
<th>{% trans "Duration" %}</th>
|
||||
<th>{% trans "Count" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
$('[data-toggle="popover"]').popover();
|
||||
|
||||
let $report = $("#distributionReport");
|
||||
let $dataTable = $('#distributionTbl'); // Datatable
|
||||
let chart = null; // Chart
|
||||
let result; // XHR get data result
|
||||
|
||||
let imageLoader = $("#imageLoader");
|
||||
let $warning = $("#applyWarning");
|
||||
let $applyBtn = $("#applyBtn");
|
||||
let type = $("#type");
|
||||
let mediaSelect =$(".media-select");
|
||||
let layoutSelect =$(".layout-select");
|
||||
let eventTagCls =$(".tag-text");
|
||||
let reportFilter = $("#reportFilter"); // Report Filter
|
||||
|
||||
// Initialize table with empty data
|
||||
let table = $dataTable.DataTable({
|
||||
searching: false,
|
||||
paging: true,
|
||||
bInfo: false,
|
||||
stateSave: true,
|
||||
bDestroy: true,
|
||||
processing: true,
|
||||
ordering: false,
|
||||
data: {},
|
||||
columns: [
|
||||
{
|
||||
data: 'label',
|
||||
'sortable': false,
|
||||
},
|
||||
{
|
||||
data: 'duration',
|
||||
'sortable': false,
|
||||
},
|
||||
{
|
||||
data: 'count',
|
||||
'sortable': false,
|
||||
}
|
||||
],
|
||||
footerCallback: function (row, data, start, end, display) {
|
||||
let api = this.api();
|
||||
|
||||
// Total over all pages
|
||||
let totalDuration = api.column(1).data().map(Number).reduce((a, b) => a + b, 0);
|
||||
let totalNumberPlays = api.column(2).data().map(Number).reduce((a, b) => a + b, 0);
|
||||
let totalDurationPage = api.column(1, { page: 'current'}).data().map(Number)
|
||||
.reduce((a, b) => a + b, 0);
|
||||
let totalNumberPlaysPage = api.column(2, { page: 'current'}).data().map(Number)
|
||||
.reduce((a, b) => a + b, 0);
|
||||
|
||||
// Update footer
|
||||
$(api.column(1).footer())
|
||||
.html(`${totalDurationPage} (${totalDuration} total)`);
|
||||
$(api.column(2).footer())
|
||||
.html(`${Math.floor(totalNumberPlaysPage)} (${Math.floor(totalNumberPlays)} total)`);
|
||||
},
|
||||
});
|
||||
|
||||
// Get Data
|
||||
function getData(url) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
data: $report.find("form").serialize(),
|
||||
success: function success(data) {
|
||||
result = data;
|
||||
$applyBtn.removeClass('disabled');
|
||||
imageLoader.removeClass('fa fa-spinner fa-spin loading-icon');
|
||||
|
||||
// Based on tab load data
|
||||
if ($('.nav-tabs .nav-item a.active').attr("href") === '#tabularTab') {
|
||||
setTabularData(table, result.table);
|
||||
} else {
|
||||
setChartData(result.chart);
|
||||
}
|
||||
},
|
||||
error: function error(xhr, textStatus, _error) {
|
||||
$applyBtn.removeClass('disabled');
|
||||
toastr.error(xhr.responseJSON.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setTabularData(table, data) {
|
||||
table.clear().draw();
|
||||
|
||||
if (Object.keys(data).length > 0) {
|
||||
table.rows.add( data ).draw()
|
||||
}
|
||||
}
|
||||
|
||||
function setChartData(data) {
|
||||
|
||||
setTimeout(function() {
|
||||
$applyBtn.removeClass('disabled');
|
||||
}, 300);
|
||||
|
||||
if (chart !== undefined && chart !== null) {
|
||||
chart.destroy();
|
||||
}
|
||||
|
||||
// Create our chart
|
||||
chart = new Chart($("#canvas"), data);
|
||||
}
|
||||
|
||||
// Tab shown/click load relevant table/chart
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
let activeTab = $(e.target).attr("href")
|
||||
if (result) {
|
||||
if (activeTab === '#tabularTab') {
|
||||
setTabularData(table, result.table);
|
||||
} else {
|
||||
setChartData(result.chart);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Apply
|
||||
$applyBtn.click(function () {
|
||||
$(this).addClass('disabled');
|
||||
imageLoader.addClass('fa fa-spinner fa-spin loading-icon');
|
||||
checkFilterAndApply();
|
||||
|
||||
getData($dataTable.data().url);
|
||||
});
|
||||
|
||||
// If we select a displayId we hide the display group filter
|
||||
$('#displayId').off('change').change( function() {
|
||||
|
||||
let displayId = $('#displayId').val();
|
||||
if (displayId) {
|
||||
$('select[name="displayGroupId[]"] option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().hide();
|
||||
} else {
|
||||
$('#displayId option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().show();
|
||||
}
|
||||
});
|
||||
|
||||
// Calculate the difference of number of days of a selected range
|
||||
let calculateDaysShowHideWarn = function() {
|
||||
|
||||
let fromDt = moment($("#statsFromDt").val());
|
||||
let toDt = moment($("#statsToDt").val());
|
||||
|
||||
let days = toDt.diff(fromDt, 'days');
|
||||
|
||||
$warning.hide();
|
||||
if ( days >= 30) {
|
||||
$warning.show();
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
$("#statsFromDtLink").change( function() {
|
||||
calculateDaysShowHideWarn();
|
||||
});
|
||||
|
||||
$("#statsToDtLink").change( function() {
|
||||
calculateDaysShowHideWarn();
|
||||
});
|
||||
|
||||
// Enable/Disable Schedule Btn
|
||||
let checkEnableSchedule = function() {
|
||||
|
||||
// Schedule button enable/disable - start
|
||||
let mediaVal = $("#mediaId").val();
|
||||
let layoutVal = $("#layoutId").val();
|
||||
let eventTagVal = $("#eventTag").val();
|
||||
let reportAddBtn = $("button#reportAddBtn");
|
||||
|
||||
let typeVal = $("#type").val();
|
||||
|
||||
if ( typeVal === 'layout') {
|
||||
if (layoutVal == null) {
|
||||
reportAddBtn.addClass('disabled');
|
||||
reportAddBtn.removeAttr('href');
|
||||
} else {
|
||||
reportAddBtn.removeClass('disabled');
|
||||
reportAddBtn.attr("href", "{{ url_for("reportschedule.add.form") }}?type=" + typeVal + "&layoutId=" + layoutVal + "&reportName=distributionReport" );
|
||||
reportAddBtn.removeAttr('title');
|
||||
|
||||
}
|
||||
} else if ( typeVal === 'media') {
|
||||
if (mediaVal == null) {
|
||||
reportAddBtn.addClass('disabled');
|
||||
reportAddBtn.removeAttr('href');
|
||||
} else {
|
||||
reportAddBtn.removeClass('disabled');
|
||||
reportAddBtn.attr("href", "{{ url_for("reportschedule.add.form") }}?type=" + typeVal + "&mediaId=" + mediaVal + "&reportName=distributionReport" );
|
||||
reportAddBtn.removeAttr('title');
|
||||
|
||||
}
|
||||
} else if ( typeVal === 'event') {
|
||||
if (eventTagVal == null) {
|
||||
reportAddBtn.addClass('disabled');
|
||||
reportAddBtn.removeAttr('href');
|
||||
} else {
|
||||
reportAddBtn.removeClass('disabled');
|
||||
reportAddBtn.attr("href", "{{ url_for("reportschedule.add.form") }}?type=" + typeVal + "&eventTag=" + eventTagVal + "&reportName=distributionReport" );
|
||||
reportAddBtn.removeAttr('title');
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Hide / Show FromDt and ToDt
|
||||
function checkReportFilter(reportFilter) {
|
||||
if (reportFilter.val() === '' || reportFilter.val() === undefined) {
|
||||
$(".stats-from-dt").show();
|
||||
$(".stats-to-dt").show();
|
||||
} else {
|
||||
$(".stats-from-dt").hide();
|
||||
$(".stats-to-dt").hide();
|
||||
}
|
||||
}
|
||||
|
||||
let checkFilterAndApply = function() {
|
||||
|
||||
reportFilter.off('change').change( function() {
|
||||
|
||||
let value = reportFilter.val();
|
||||
|
||||
// Hide / Show FromDt and ToDt
|
||||
checkReportFilter(reportFilter);
|
||||
|
||||
// Hide / Show Warning
|
||||
$warning.hide();
|
||||
if ( value === '') {
|
||||
calculateDaysShowHideWarn();
|
||||
} else if ( value === 'thismonth' || value === 'lastmonth' || value === 'thisyear' || value === 'lastyear') {
|
||||
$warning.show();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
type.off('change').change( function() {
|
||||
|
||||
let value = type.val();
|
||||
if (value === 'media') {
|
||||
// show media and clear/hide the layout
|
||||
$("#layoutId").val("");
|
||||
$("#layoutId option").remove();
|
||||
layoutSelect.hide();
|
||||
|
||||
$("#eventTag").val("");
|
||||
eventTagCls.hide();
|
||||
|
||||
mediaSelect.show();
|
||||
|
||||
|
||||
} else if (value === 'layout') {
|
||||
// show layout and clear/hide the media
|
||||
$("#mediaId").val("");
|
||||
$("#mediaId option").remove();
|
||||
mediaSelect.hide();
|
||||
|
||||
$("#eventTag").val("");
|
||||
eventTagCls.hide();
|
||||
|
||||
layoutSelect.show();
|
||||
|
||||
} else if (value === 'event') {
|
||||
// clear/hide the media and layout
|
||||
$("#mediaId").val("");
|
||||
$("#mediaId option").remove();
|
||||
$("#layoutId").val("");
|
||||
$("#layoutId option").remove();
|
||||
mediaSelect.hide();
|
||||
layoutSelect.hide();
|
||||
|
||||
// show tag
|
||||
eventTagCls.show();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
type.val('layout');
|
||||
mediaSelect.hide();
|
||||
eventTagCls.hide();
|
||||
|
||||
checkReportFilter(reportFilter);
|
||||
checkFilterAndApply();
|
||||
|
||||
$applyBtn.addClass('disabled');
|
||||
checkEnableSchedule();
|
||||
|
||||
// Bind to form change
|
||||
$report.on('change', function() {
|
||||
checkEnableSchedule();
|
||||
|
||||
let layoutVal = $("#layoutId").val();
|
||||
let mediaVal = $("#mediaId").val();
|
||||
let eventVal = $("#eventTag").val();
|
||||
|
||||
if ((layoutVal === null || layoutVal === '' || layoutVal === undefined) &&
|
||||
(mediaVal === null || mediaVal === '' || mediaVal === undefined) &&
|
||||
(eventVal === null || eventVal === '' || eventVal === undefined) ) {
|
||||
$applyBtn.addClass('disabled');
|
||||
} else {
|
||||
$applyBtn.removeClass('disabled');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function distributionScheduleCallback(dialog) {
|
||||
|
||||
// If we select a displayId we hide the display group filter
|
||||
$('#reportScheduleAddForm #displayId').off('change').change( function() {
|
||||
|
||||
let displayId = $('#reportScheduleAddForm #displayId').val();
|
||||
if (displayId) {
|
||||
$('select[name="displayGroupId[]"] option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().parent().hide();
|
||||
} else {
|
||||
$('#reportScheduleAddForm #displayId option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().parent().show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
96
reports/distribution-report-preview.twig
Normal file
96
reports/distribution-report-preview.twig
Normal file
@@ -0,0 +1,96 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2019 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
<button class="btn btn-info XiboRedirectButton" href="{{ url_for("savedreport.view") }}"><i class="fa fa-eye" aria-hidden="true"></i> {% trans "Saved Reports" %}</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<i class="fa fa-list"></i>
|
||||
{{ metadata.title }}
|
||||
<span class="small">({% trans "Generated on: " %}{{ metadata.generatedOn }})</span>
|
||||
<div><span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}">
|
||||
<div class="XiboData card pt-3">
|
||||
<canvas id="canvas" style="clear:both; margin-top:25px"></canvas>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="stats" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Period" %}</th>
|
||||
<th>{% trans "Duration" %}</th>
|
||||
<th>{% trans "Count" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
$(document).ready(function() {
|
||||
|
||||
let reportChart = new Chart($("#canvas"), {{ chart|json_encode|raw }});
|
||||
|
||||
let outputData = {{ table|json_encode|raw }};
|
||||
|
||||
// Grid
|
||||
let table = $("#stats").DataTable({
|
||||
"searching": false,
|
||||
"paging": true,
|
||||
"ordering": false,
|
||||
"data": outputData,
|
||||
"columns": [
|
||||
{ "data": 'label' },
|
||||
{ "data": 'duration' },
|
||||
{ "data": 'count' },
|
||||
]
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
119
reports/distribution-schedule-form-add.twig
Normal file
119
reports/distribution-schedule-form-add.twig
Normal file
@@ -0,0 +1,119 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
{% extends "form-base.twig" %}
|
||||
{% import "forms.twig" as forms %}
|
||||
|
||||
{% block formTitle %}
|
||||
{{ formTitle }}
|
||||
{% endblock %}
|
||||
|
||||
{% block formButtons %}
|
||||
{% trans "Cancel" %}, XiboDialogClose()
|
||||
{% trans "Save" %}, $("#reportScheduleAddForm").submit()
|
||||
{% endblock %}
|
||||
|
||||
{% block callBack %}distributionScheduleCallback{% endblock %}
|
||||
|
||||
{% block formHtml %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form id="reportScheduleAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("reportschedule.add") }}">
|
||||
|
||||
{{ forms.hidden("hiddenFields", hiddenFields) }}
|
||||
{{ forms.hidden("reportName", reportName) }}
|
||||
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{% set helpText %}{% trans "The name for this report schedule" %}{% endset %}
|
||||
{{ forms.input("name", title, "", helpText, "", "required") }}
|
||||
|
||||
{% set title %}{% trans "Frequency" %}{% endset %}
|
||||
{% set helpText %}{% trans "Select how frequently you would like this report to run" %}{% endset %}
|
||||
{% set daily %}{% trans "Daily" %}{% endset %}
|
||||
{% set weekly %}{% trans "Weekly" %}{% endset %}
|
||||
{% set monthly %}{% trans "Monthly" %}{% endset %}
|
||||
{% set yearly %}{% trans "Yearly" %}{% endset %}
|
||||
{% set options = [
|
||||
{ name: "daily", filter: daily },
|
||||
{ name: "weekly", filter: weekly },
|
||||
{ name: "monthly", filter: monthly },
|
||||
{ name: "yearly", filter: yearly },
|
||||
] %}
|
||||
{{ forms.dropdown("filter", "single", title, "", options, "name", "filter", helpText) }}
|
||||
|
||||
{% set title %}{% trans "Group by" %}{% endset %}
|
||||
{% set byhour %}{% trans "Hour" %}{% endset %}
|
||||
{% set bydayofweek %}{% trans "Day of week" %}{% endset %}
|
||||
{% set bydayofmonth %}{% trans "Day of month" %}{% endset %}
|
||||
{% set options = [
|
||||
{ filterName: "byhour", groupByFilter: byhour },
|
||||
{ filterName: "bydayofweek", groupByFilter: bydayofweek },
|
||||
{ filterName: "bydayofmonth", groupByFilter: bydayofmonth },
|
||||
] %}
|
||||
{{ forms.dropdown("groupByFilter", "single", title, "", options, "filterName", "groupByFilter", "", "group-by-filter") }}
|
||||
|
||||
{% set title %}{% trans "Display" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("display.search") },
|
||||
{ name: "data-search-term", value: "display" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "displayId" },
|
||||
{ name: "data-text-property", value: "display" }
|
||||
] %}
|
||||
{{ forms.dropdown("displayId", "single", title, "", null, "displayId", "display", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("displayGroup.search") },
|
||||
{ name: "data-search-term", value: "displayGroup" },
|
||||
{ name: "data-id-property", value: "displayGroupId" },
|
||||
{ name: "data-text-property", value: "displayGroup" }
|
||||
] %}
|
||||
{{ forms.dropdown("displayGroupId[]", "dropdownmulti", title, "", null, "displayGroupId", "displayGroup", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Start Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to run this report. Leave blank to run from the next collection point." %}{% endset %}
|
||||
{{ forms.dateTime("fromDt", title, "", helpText, "starttime-control") }}
|
||||
|
||||
{% set title %}{% trans "End Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to end the schedule. Leave blank to run indefinitely." %}{% endset %}
|
||||
{{ forms.dateTime("toDt", title, "", helpText, "endtime-control") }}
|
||||
|
||||
{% set title %}{% trans "Should an email be sent?" %}{% endset %}
|
||||
{{ forms.checkbox("sendEmail", title, sendEmail) }}
|
||||
|
||||
{% set title %}{% trans "Email addresses" %}{% endset %}
|
||||
{% set helpText %}{% trans "Additional emails separated by a comma." %}{% endset %}
|
||||
{{ forms.inputWithTags("nonusers", title, nonusers, helpText) }}
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
14
reports/distribution.report
Normal file
14
reports/distribution.report
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "distributionReport",
|
||||
"description": "Chart: Distribution by Layout, Media or Event",
|
||||
"class": "\\Xibo\\Report\\DistributionReport",
|
||||
"type": "Chart",
|
||||
"output_type": "both",
|
||||
"color":"green",
|
||||
"fa_icon": "fa-bar-chart",
|
||||
"sort_order": 3,
|
||||
"hidden": 0,
|
||||
"category": "Proof of Play",
|
||||
"feature": "proof-of-play",
|
||||
"adminOnly": 0
|
||||
}
|
||||
53
reports/libraryusage-email-template.twig
Normal file
53
reports/libraryusage-email-template.twig
Normal file
@@ -0,0 +1,53 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "base-report.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span>
|
||||
</div>
|
||||
|
||||
<table class="saved-report-table">
|
||||
<tr>
|
||||
<th>{% trans "ID" %}</th>
|
||||
<th>{% trans "User" %}</th>
|
||||
<th>{% trans "Usage" %}</th>
|
||||
<th>{% trans "Count Files" %}</th>
|
||||
</tr>
|
||||
{% for item in tableData %}
|
||||
<tr>
|
||||
<td>{{ item.userId }}</td>
|
||||
<td>{{ item.userName }}</td>
|
||||
<td>{{ item.bytesUsedFormatted }}</td>
|
||||
<td>{{ item.numFiles }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% for key,item in multipleCharts %}
|
||||
<div style="text-align: center">{{ key|replace({'_': " "}) }}</div>
|
||||
<img src="{{ item|raw }}" >
|
||||
<p></p>
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
||||
231
reports/libraryusage-report-form.twig
Normal file
231
reports/libraryusage-report-form.twig
Normal file
@@ -0,0 +1,231 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2020 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block title %}{% trans "Report: Library Usage" %} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
<button class="btn btn-success XiboFormButton" title="Schedule" id="reportAddBtn" href="{{ url_for("reportschedule.add.form") }}?reportName=libraryusage"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Schedule" %}</button>
|
||||
<button class="btn btn-info XiboRedirectButton" href="{{ url_for("savedreport.view") }}?reportName=libraryusage"><i class="fa fa-eye" aria-hidden="true"></i> {% trans "Saved Reports" %}</button>
|
||||
<button class="btn btn-primary XiboRedirectButton" href="{{ url_for("reportschedule.view") }}?reportName=libraryusage"><i class="fa fa-th-list" aria-hidden="true"></i> {% trans "Report Schedules" %}</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<span>{% trans "Library Usage" %}</span>
|
||||
</div>
|
||||
<div class="widget-navigation-menu">
|
||||
<ul class="nav nav-pills">
|
||||
<li role="presentation" class="nav-item"><a class="nav-link" href="{{ url_for("report.view") }}">{% trans "All Reports" %}</a></li>
|
||||
<li role="presentation" class="nav-item dropdown">
|
||||
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> Reports <span class="caret"></span> </a>
|
||||
<div class="dropdown-menu">
|
||||
{% for reports in defaults.availableReports %}
|
||||
{% for report in reports %}
|
||||
{% if report.hidden == 0 and report.category == 'Library'%}
|
||||
<a class="dropdown-item" href="{{ url_for("report.form", {name: report.name}) }}">{{ report.description }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-refresh-on-form-submit="false">
|
||||
<div class="XiboFilter card mb-3 bg-light">
|
||||
<div class="FilterDiv card-body" id="Filter">
|
||||
<form class="form-inline">
|
||||
{% set attributes = [
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" }
|
||||
] %}
|
||||
|
||||
{% set title %}{% trans "User" %}{% endset %}
|
||||
{% set userFilterOptions = [{userId: null, user: ""}]|merge(defaults.users) %}
|
||||
{{ inline.dropdown("userId", "single", title, "", userFilterOptions, "userId", "userName", "", "selectPicker", "", "u", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "User Group" %}{% endset %}
|
||||
{% set groupFilterOptions = [{groupId: null, group: ""}]|merge(defaults.groups) %}
|
||||
{{ inline.dropdown("groupId", "single", title, "", groupFilterOptions, "groupId", "group", "", "selectPicker", "", "g", "", attributes) }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="libraryUsage" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "ID" %}</th>
|
||||
<th>{% trans "User" %}</th>
|
||||
<th>{% trans "Usage" %}</th>
|
||||
<th>{% trans "Count Files" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<i class="fa fa-tasks"></i>
|
||||
{% if libraryLimitSet != "" %}
|
||||
{% trans %}Library Usage. Limit {{ libraryLimit }}{% endtrans %}
|
||||
{% else %}
|
||||
{% trans "Library Usage" %}
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="widget-body medium no-padding">
|
||||
<canvas id="libraryChart" style="clear:both;" width="350" height="220"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<i class="fa fa-user"></i>
|
||||
{% trans "User Percentage Usage" %}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="widget-body medium no-padding">
|
||||
<canvas id="userChart" style="clear:both;" width="350" height="220"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
var userChart = null;
|
||||
|
||||
var table = $("#libraryUsage").DataTable({
|
||||
"language": dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
serverSide: true,
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
filter: false,
|
||||
searchDelay: 3000,
|
||||
ajax: {
|
||||
url: "{{ url_for("report.data", {name: reportName}) }}",
|
||||
data: function (d) {
|
||||
$.extend(d, $("#libraryUsage").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
|
||||
},
|
||||
dataFilter: function(data){
|
||||
let json = $.parseJSON(data);
|
||||
json.recordsFiltered = json.recordsTotal;
|
||||
json.data = json.table;
|
||||
|
||||
return JSON.stringify( json ); // return JSON string
|
||||
}
|
||||
},
|
||||
"columns": [
|
||||
{ data: "userId" },
|
||||
{ data: "userName" },
|
||||
{ data: "bytesUsedFormatted" },
|
||||
{ data: "numFiles" }
|
||||
]
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', function(e, settings, processing) {
|
||||
dataTableProcessing(e, settings, processing);
|
||||
|
||||
if (!processing) {
|
||||
// Render a pie chart
|
||||
if (userChart !== undefined && userChart !== null) {
|
||||
userChart.destroy();
|
||||
}
|
||||
// Organise our rows into datasets for the chart
|
||||
var totalSize = 0;
|
||||
var userData = [];
|
||||
var userLabels = [];
|
||||
|
||||
$.each(table.data(), function(index, el) {
|
||||
totalSize += el.bytesUsed;
|
||||
});
|
||||
|
||||
$.each(table.data(), function(index, el) {
|
||||
userData.push(((el.bytesUsed/totalSize)*100).toFixed(2));
|
||||
userLabels.push(el.userName);
|
||||
});
|
||||
|
||||
var colours = [];
|
||||
for(var i = 0; i < userData.length; i++) {
|
||||
colours.push($c.rand());
|
||||
}
|
||||
|
||||
// Create our chart
|
||||
userChart = new Chart($("#userChart"), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
datasets: [{
|
||||
data: userData,
|
||||
backgroundColor: colours
|
||||
}],
|
||||
labels: userLabels
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: false
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Create a lovely library pie chart
|
||||
var libraryData = {{ defaults.libraryWidgetData|raw }};
|
||||
var colours = [];
|
||||
for(var i = 0; i < libraryData.length; i++) {
|
||||
colours.push($c.rand());
|
||||
}
|
||||
var libraryChart = new Chart($("#libraryChart"), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
datasets: [{
|
||||
data: libraryData,
|
||||
backgroundColor: colours
|
||||
}],
|
||||
labels: {{ defaults.libraryWidgetLabels|raw }}
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: false
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
133
reports/libraryusage-report-preview.twig
Normal file
133
reports/libraryusage-report-preview.twig
Normal file
@@ -0,0 +1,133 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2020 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
<button class="btn btn-info XiboRedirectButton" href="{{ url_for("savedreport.view") }}"><i class="fa fa-eye" aria-hidden="true"></i> {% trans "Saved Reports" %}</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<i class="fa fa-list"></i>
|
||||
{{ metadata.title }}
|
||||
<span class="small">({% trans "Generated on: " %}{{ metadata.generatedOn }})</span>
|
||||
<div><span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}">
|
||||
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="libraryUsage" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "ID" %}</th>
|
||||
<th>{% trans "User" %}</th>
|
||||
<th>{% trans "Usage" %}</th>
|
||||
<th>{% trans "Count Files" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<i class="fa fa-tasks"></i>
|
||||
{% if libraryLimitSet != "" %}
|
||||
{% trans %}Library Usage. Limit {{ libraryLimit }}{% endtrans %}
|
||||
{% else %}
|
||||
{% trans "Library Usage" %}
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="widget-body medium no-padding">
|
||||
<canvas id="libraryChart" style="clear:both;" width="350" height="220"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<i class="fa fa-user"></i>
|
||||
{% trans "User Percentage Usage" %}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="widget-body medium no-padding">
|
||||
<canvas id="userChart" style="clear:both;" width="350" height="220"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
|
||||
var outputData = {{ table|json_encode|raw }};
|
||||
|
||||
// Grid
|
||||
var table = $("#libraryUsage").DataTable({
|
||||
"language": dataTablesLanguage,
|
||||
"dom": dataTablesTemplate,
|
||||
"paging": false,
|
||||
"ordering": false,
|
||||
"info": false,
|
||||
"order": [[1, "asc"]],
|
||||
"searching": false,
|
||||
data: outputData,
|
||||
columns: [
|
||||
{ data: "userId" },
|
||||
{ data: "userName" },
|
||||
{ data: "bytesUsedFormatted" },
|
||||
{ data: "numFiles" }
|
||||
]
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
|
||||
// User Percentage Usage
|
||||
var userChart = new Chart($("#userChart"), {{ chart.User_Percentage_Usage|json_encode|raw }});
|
||||
|
||||
// Library Usage
|
||||
var libraryChart = new Chart($("#libraryChart"), {{ chart.Library_Usage|json_encode|raw }});
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
93
reports/libraryusage-schedule-form-add.twig
Normal file
93
reports/libraryusage-schedule-form-add.twig
Normal file
@@ -0,0 +1,93 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "form-base.twig" %}
|
||||
{% import "forms.twig" as forms %}
|
||||
|
||||
{% block formTitle %}
|
||||
{% trans "Add Report Schedule" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block formButtons %}
|
||||
{% trans "Cancel" %}, XiboDialogClose()
|
||||
{% trans "Save" %}, $("#reportScheduleAddForm").submit()
|
||||
{% endblock %}
|
||||
|
||||
{% block formHtml %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form id="reportScheduleAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("reportschedule.add") }}">
|
||||
|
||||
{{ forms.hidden("hiddenFields", hiddenFields) }}
|
||||
{{ forms.hidden("reportName", reportName) }}
|
||||
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{% set helpText %}{% trans "The name for this report schedule" %}{% endset %}
|
||||
{{ forms.input("name", title, "", helpText, "", "required") }}
|
||||
|
||||
{% set title %}{% trans "Frequency" %}{% endset %}
|
||||
{% set helpText %}{% trans "Select how frequently you would like this report to run" %}{% endset %}
|
||||
{% set daily %}{% trans "Daily" %}{% endset %}
|
||||
{% set weekly %}{% trans "Weekly" %}{% endset %}
|
||||
{% set monthly %}{% trans "Monthly" %}{% endset %}
|
||||
{% set yearly %}{% trans "Yearly" %}{% endset %}
|
||||
{% set options = [
|
||||
{ name: "daily", filter: daily },
|
||||
{ name: "weekly", filter: weekly },
|
||||
{ name: "monthly", filter: monthly },
|
||||
{ name: "yearly", filter: yearly },
|
||||
] %}
|
||||
{{ forms.dropdown("filter", "single", title, "", options, "name", "filter", helpText) }}
|
||||
|
||||
{% set attributes = [
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" }
|
||||
] %}
|
||||
|
||||
{% set title %}{% trans "User" %}{% endset %}
|
||||
{% set userFilterOptions = [{userId: null, user: ""}]|merge(users) %}
|
||||
{{ forms.dropdown("userId", "single", title, "", userFilterOptions, "userId", "userName", "", "selectPicker", "", "u", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "User Group" %}{% endset %}
|
||||
{% set groupFilterOptions = [{groupId: null, group: ""}]|merge(groups) %}
|
||||
{{ forms.dropdown("groupId", "single", title, "", groupFilterOptions, "groupId", "group", "", "selectPicker", "", "g", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Start Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to run this report. Leave blank to run from the next collection point." %}{% endset %}
|
||||
{{ forms.dateTime("fromDt", title, "", helpText, "starttime-control") }}
|
||||
|
||||
{% set title %}{% trans "End Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to end the schedule. Leave blank to run indefinitely." %}{% endset %}
|
||||
{{ forms.dateTime("toDt", title, "", helpText, "endtime-control") }}
|
||||
|
||||
{% set title %}{% trans "Should an email be sent?" %}{% endset %}
|
||||
{{ forms.checkbox("sendEmail", title, sendEmail) }}
|
||||
|
||||
{% set title %}{% trans "Email addresses" %}{% endset %}
|
||||
{% set helpText %}{% trans "Additional emails separated by a comma." %}{% endset %}
|
||||
{{ forms.inputWithTags("nonusers", title, nonusers, helpText) }}
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
14
reports/libraryusage.report
Normal file
14
reports/libraryusage.report
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "libraryusage",
|
||||
"description": "Library Usage",
|
||||
"class": "\\Xibo\\Report\\LibraryUsage",
|
||||
"type": "Report",
|
||||
"output_type": "both",
|
||||
"color":"green",
|
||||
"fa_icon": "fa-th",
|
||||
"sort_order": 3,
|
||||
"hidden": 0,
|
||||
"category": "Library",
|
||||
"feature": "admin",
|
||||
"adminOnly": 1
|
||||
}
|
||||
62
reports/mobile-proofofplay-email-template.twig
Normal file
62
reports/mobile-proofofplay-email-template.twig
Normal file
@@ -0,0 +1,62 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "base-report.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span>
|
||||
</div>
|
||||
<p></p>
|
||||
|
||||
<table class="saved-report-table">
|
||||
<tr>
|
||||
<th>{% trans "Start" %}</th>
|
||||
<th>{% trans "End" %}</th>
|
||||
<th>{% trans "Display Id" %}</th>
|
||||
<th>{% trans "Display" %}</th>
|
||||
<th>{% trans "Layout Id" %}</th>
|
||||
<th>{% trans "Layout" %}</th>
|
||||
<th>{% trans "Start Latitude" %}</th>
|
||||
<th>{% trans "Start Longitude" %}</th>
|
||||
<th>{% trans "End Latitude" %}</th>
|
||||
<th>{% trans "End Longitude" %}</th>
|
||||
<th>{% trans "Duration" %}</th>
|
||||
</tr>
|
||||
{% for item in tableData %}
|
||||
<tr>
|
||||
<td>{{ item.from }}</td>
|
||||
<td>{{ item.to }}</td>
|
||||
<td>{{ item.displayId }}</td>
|
||||
<td>{{ item.display }}</td>
|
||||
<td>{{ item.layoutId }}</td>
|
||||
<td>{{ item.layout }}</td>
|
||||
<td>{{ item.startLat }}</td>
|
||||
<td>{{ item.startLong }}</td>
|
||||
<td>{{ item.endLat }}</td>
|
||||
<td>{{ item.endLong }}</td>
|
||||
<td>{{ item.duration }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
||||
514
reports/mobile-proofofplay-report-form.twig
Normal file
514
reports/mobile-proofofplay-report-form.twig
Normal file
@@ -0,0 +1,514 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block title %}{% trans "Report: Mobile Proof of Play" %} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
{% include "report-schedule-buttons.twig" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<span>{% trans "Mobile Proof of Play" %}</span>
|
||||
</div>
|
||||
|
||||
{% include "report-selector.twig" %}
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-grid-name="mobileProofOfPlayView" data-refresh-on-form-submit="false">
|
||||
<div class="XiboFilterCustom card bg-light mb-3">
|
||||
<div class="FilterDiv card-body" id="report">
|
||||
<!-- Form Filter -->
|
||||
<form class="form-inline">
|
||||
{% set title %}{% trans "Range" %}{% endset %}
|
||||
{% set range %}{% trans "Select a range" %}{% endset %}
|
||||
{% set wholecampaign %}{% trans "Whole Campaign" %}{% endset %}
|
||||
{% set today %}{% trans "Today" %}{% endset %}
|
||||
{% set yesterday %}{% trans "Yesterday" %}{% endset %}
|
||||
{% set thisweek %}{% trans "This Week" %}{% endset %}
|
||||
{% set thismonth %}{% trans "This Month" %}{% endset %}
|
||||
{% set thisyear %}{% trans "This Year" %}{% endset %}
|
||||
{% set lastweek %}{% trans "Last Week" %}{% endset %}
|
||||
{% set lastmonth %}{% trans "Last Month" %}{% endset %}
|
||||
{% set lastyear %}{% trans "Last Year" %}{% endset %}
|
||||
{% set options = [
|
||||
{ filterName: "", reportFilter: range },
|
||||
{ filterName: "wholecampaign", reportFilter: wholecampaign },
|
||||
{ filterName: "today", reportFilter: today },
|
||||
{ filterName: "yesterday", reportFilter: yesterday },
|
||||
{ filterName: "thisweek", reportFilter: thisweek },
|
||||
{ filterName: "thismonth", reportFilter: thismonth },
|
||||
{ filterName: "thisyear", reportFilter: thisyear },
|
||||
{ filterName: "lastweek", reportFilter: lastweek },
|
||||
{ filterName: "lastmonth", reportFilter: lastmonth },
|
||||
{ filterName: "lastyear", reportFilter: lastyear },
|
||||
] %}
|
||||
{{ inline.dropdown("reportFilter", "single", title, "today", options, "filterName", "reportFilter") }}
|
||||
|
||||
{% set title %}{% trans "From Date" %}{% endset %}
|
||||
{{ inline.date("fromDt", title, defaults.fromDateOneDay, "", "from-dt", "", "") }}
|
||||
|
||||
{% set title %}{% trans "To Date" %}{% endset %}
|
||||
{{ inline.date("toDt", title, defaults.toDate, "", "to-dt", "", "") }}
|
||||
|
||||
{% set title %}{% trans "Display" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("display.search") },
|
||||
{ name: "data-search-term", value: "display" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "displayId" },
|
||||
{ name: "data-text-property", value: "display" }
|
||||
] %}
|
||||
{{ inline.dropdown("displayId", "single", title, "", null, "displayId", "display", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("displayGroup.search") },
|
||||
{ name: "data-search-term", value: "displayGroup" },
|
||||
{ name: "data-id-property", value: "displayGroupId" },
|
||||
{ name: "data-text-property", value: "displayGroup" }
|
||||
] %}
|
||||
{{ inline.dropdown("displayGroupId[]", "dropdownmulti", title, "", null, "displayGroupId", "displayGroup", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{# Campaign list only. #}
|
||||
{% set attributes = [
|
||||
{ name: "data-search-url", value: url_for("campaign.search") },
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
] %}
|
||||
|
||||
{% set title %}{% trans "Campaign" %}{% endset %}
|
||||
{% set helpText %}{% trans "Please select a Campaign" %}{% endset %}
|
||||
{{ inline.dropdown("parentCampaignId", "single", title, "", null, "campaignId", "campaign", "", "", "", "", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Layout" %}{% endset %}
|
||||
{% set helpText %}{% trans "This field is required when the Type selected is Layout" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("layout.search") },
|
||||
{ name: "data-search-term", value: "layout" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "layoutId" },
|
||||
{ name: "data-text-property", value: "layout" }
|
||||
] %}
|
||||
{{ inline.dropdown("layoutId", "single", title, "", null, "layoutId", "layout", helpText, "pagedSelect layout-select", "", "l", "", attributes) }}
|
||||
|
||||
<div class="w-100">
|
||||
<a id="applyBtn" class="btn btn-success">
|
||||
<span>{% trans "Apply" %}</span>
|
||||
</a>
|
||||
<span id="applyWarning" class="text-warning" style="display:none; padding-left: 10px">{% trans "Warning: This may return a lot of data and may take several minutes to process." %}</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Card Header -->
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-tabs card-header-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="tabular-tab" data-toggle="tab" href="#tabularTab" role="tab"
|
||||
aria-controls="tabularTab" aria-selected="true">Tabular</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Card Body -->
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<!-- TABULAR TAB-->
|
||||
<div class="tab-pane active" id="tabularTab" role="tabpanel" aria-labelledby="tabular-tab">
|
||||
<!-- DATATABLE -->
|
||||
<div class="table-container" id="table_wrapper">
|
||||
<table id="stats"
|
||||
class="table xibo-table table-striped table-full-width"
|
||||
style="width: 100%"
|
||||
data-state-preference-name="proofOfPlayGrid"
|
||||
data-url="/report/data/mobileProofOfPlay">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Start" %}</th>
|
||||
<th>{% trans "End" %}</th>
|
||||
<th>{% trans "Display Id" %}</th>
|
||||
<th>{% trans "Display" %}</th>
|
||||
<th>{% trans "Layout Id" %}</th>
|
||||
<th>{% trans "Layout" %}</th>
|
||||
<th>{% trans "Start Latitude" %}</th>
|
||||
<th>{% trans "Start Longitude" %}</th>
|
||||
<th>{% trans "End Latitude" %}</th>
|
||||
<th>{% trans "End Longitude" %}</th>
|
||||
<th>{% trans "Duration" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
$(document).ready(function() {
|
||||
let $dataTable = $('#stats'); // Datatable
|
||||
let chart = null; // Chart
|
||||
let result; // XHR get data result
|
||||
|
||||
let imageLoader = $("#imageLoader");
|
||||
let $warning = $("#applyWarning");
|
||||
let $applyBtn = $("#applyBtn");
|
||||
|
||||
// Report Filter
|
||||
let reportFilter = $("#reportFilter"); // Report Filter
|
||||
|
||||
// Grid
|
||||
let table = $dataTable.DataTable({
|
||||
"language": dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
stateLoadCallback: dataTableStateLoadCallback,
|
||||
stateSaveCallback: dataTableStateSaveCallback,
|
||||
drawCallback: function( settings ) {
|
||||
setTimeout(function() {
|
||||
$("#applyBtn").removeClass('disabled');
|
||||
}, 300);
|
||||
},
|
||||
filter: false,
|
||||
"order": [[0, "asc"]],
|
||||
data:{},
|
||||
"columns": [
|
||||
{
|
||||
"data": "from",
|
||||
"render": dataTableDateFromIso
|
||||
},
|
||||
{
|
||||
"data": "to",
|
||||
"render": dataTableDateFromIso
|
||||
},
|
||||
{"data": "displayId"},
|
||||
{"data": "display"},
|
||||
{"data": "layoutId"},
|
||||
{"data": "layout"},
|
||||
{"data": "startLat"},
|
||||
{"data": "startLong"},
|
||||
{"data": "endLat"},
|
||||
{"data": "endLong"},
|
||||
{"data": "duration"}
|
||||
],
|
||||
});
|
||||
|
||||
// Get Data
|
||||
function getData(url) {
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
data: $("#stats").closest(".XiboGrid").find(".FilterDiv form").serializeObject(),
|
||||
success: function success(data) {
|
||||
result = data;
|
||||
$applyBtn.removeClass('disabled');
|
||||
|
||||
// Based on tab load data
|
||||
if ($('.nav-tabs .nav-item a.active').attr("href") === '#tabularTab') {
|
||||
setTabularData(table, result.table);
|
||||
} else {
|
||||
setChartData(result.chart);
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
toastr.error(result.error);
|
||||
}
|
||||
},
|
||||
error: function error(xhr, textStatus, _error) {
|
||||
$applyBtn.removeClass('disabled');
|
||||
toastr.error(xhr.responseJSON.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setTabularData(table, data) {
|
||||
table.clear().draw();
|
||||
|
||||
if (Object.keys(data).length > 0) {
|
||||
table.rows.add( data ).draw()
|
||||
}
|
||||
}
|
||||
|
||||
function setChartData(data) {
|
||||
imageLoader.show();
|
||||
|
||||
setTimeout(function() {
|
||||
$applyBtn.removeClass('disabled');
|
||||
}, 300);
|
||||
|
||||
imageLoader.hide();
|
||||
if (chart !== undefined && chart !== null) {
|
||||
chart.destroy();
|
||||
}
|
||||
|
||||
// Create our chart
|
||||
chart = new Chart($("#canvas"), data);
|
||||
}
|
||||
|
||||
// Tab shown/click load relevant table/chart
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
let activeTab = $(e.target).attr("href")
|
||||
if (result) {
|
||||
if (activeTab === '#tabularTab') {
|
||||
setTabularData(table, result.table);
|
||||
} else {
|
||||
setChartData(result.chart);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
dataTableAddButtons(table, $('#stats_wrapper').find('.dataTables_buttons'));
|
||||
|
||||
// Apply
|
||||
$applyBtn.click(function () {
|
||||
$(this).addClass('disabled');
|
||||
getData($dataTable.data().url);
|
||||
});
|
||||
|
||||
// If we select a displayId we hide the display group filter
|
||||
$('#displayId').off('change').change( function() {
|
||||
|
||||
let displayId = $('#displayId').val();
|
||||
if (displayId) {
|
||||
$('select[name="displayGroupId[]"] option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().hide();
|
||||
} else {
|
||||
$('#displayId option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().show();
|
||||
}
|
||||
});
|
||||
|
||||
// Hide / Show FromDt and ToDt
|
||||
function checkReportFilter(reportFilter) {
|
||||
if (reportFilter.val() === '' || reportFilter.val() === undefined) {
|
||||
$(".from-dt").show();
|
||||
$(".to-dt").show();
|
||||
} else {
|
||||
$(".from-dt").hide();
|
||||
$(".to-dt").hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the difference of number of days of a selected range
|
||||
let calculateDaysShowHideWarn = function() {
|
||||
|
||||
let fromDt = moment($("#fromDt").val());
|
||||
let toDt = moment($("#toDt").val());
|
||||
|
||||
let days = toDt.diff(fromDt, 'days');
|
||||
|
||||
$warning.hide();
|
||||
if ( days >= 30) {
|
||||
$warning.show();
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
$("#fromDtLink").change( function() {
|
||||
calculateDaysShowHideWarn();
|
||||
});
|
||||
|
||||
$("#toDtLink").change( function() {
|
||||
calculateDaysShowHideWarn();
|
||||
});
|
||||
|
||||
let checkFilterAndApply = function() {
|
||||
|
||||
reportFilter.off('change').change( function() {
|
||||
let value = reportFilter.val();
|
||||
|
||||
// Hide / Show FromDt and ToDt
|
||||
checkReportFilter(reportFilter);
|
||||
|
||||
// Hide / Show Warning
|
||||
$warning.hide();
|
||||
if ( value === '') {
|
||||
calculateDaysShowHideWarn();
|
||||
} else if ( value === 'thismonth' || value === 'lastmonth' || value === 'thisyear' || value === 'lastyear') {
|
||||
$warning.show();
|
||||
}
|
||||
});
|
||||
|
||||
let anchorReportAddBtn = $("button#reportAddBtn");
|
||||
let type = $("#type").val();
|
||||
let tagsType = $("#tagsType").val();
|
||||
let tags = $("#tags").val();
|
||||
let exactTags = $('#exactTags').is(":checked");
|
||||
|
||||
anchorReportAddBtn.attr("href", "{{ url_for("reportschedule.add.form") }}?reportName=mobileProofOfPlay" );
|
||||
|
||||
};
|
||||
|
||||
checkReportFilter(reportFilter);
|
||||
checkFilterAndApply();
|
||||
|
||||
|
||||
var $campaignSelect = $('#parentCampaignId');
|
||||
$campaignSelect.select2({
|
||||
ajax: {
|
||||
url: $campaignSelect.data("searchUrl"),
|
||||
dataType: "json",
|
||||
delay: 250,
|
||||
placeholder: 'Campaign',
|
||||
allowClear: true,
|
||||
data: function(params) {
|
||||
|
||||
var query = {
|
||||
isLayoutSpecific: 0,
|
||||
retired: 0,
|
||||
totalDuration: 0,
|
||||
name: params.term,
|
||||
start: 0,
|
||||
length: 10,
|
||||
columns: [
|
||||
{
|
||||
"data": "isLayoutSpecific"
|
||||
},
|
||||
{
|
||||
"data": "campaign"
|
||||
}
|
||||
],
|
||||
order: [
|
||||
{
|
||||
"column": 0,
|
||||
"dir": "asc"
|
||||
},
|
||||
{
|
||||
"column": 1,
|
||||
"dir": "asc"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Set the start parameter based on the page number
|
||||
if (params.page != null) {
|
||||
query.start = (params.page - 1) * 10;
|
||||
}
|
||||
|
||||
return query;
|
||||
},
|
||||
processResults: function(data, params) {
|
||||
|
||||
var results = [];
|
||||
var campaigns = [];
|
||||
|
||||
$.each(data.data, function(index, element) {
|
||||
campaigns.push({
|
||||
"id": element.campaignId,
|
||||
"text": element.campaign
|
||||
});
|
||||
});
|
||||
|
||||
results.push({
|
||||
"text": $campaignSelect.data('transCampaigns'),
|
||||
"children": campaigns
|
||||
})
|
||||
|
||||
|
||||
var page = params.page || 1;
|
||||
page = (page > 1) ? page - 1 : page;
|
||||
|
||||
return {
|
||||
results: results,
|
||||
pagination: {
|
||||
more: (page * 10 < data.recordsTotal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function reportScheduleCallback() {
|
||||
|
||||
let $displayId = $('#report #displayId');
|
||||
let $newDisplayId = $('#reportScheduleAddForm #displayId');
|
||||
|
||||
appendOptions($newDisplayId, $displayId.find('option:selected').clone());
|
||||
}
|
||||
|
||||
function appendOptions(element, options) {
|
||||
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
|
||||
let option = options[i];
|
||||
element.append(option).trigger('change');
|
||||
$(option).prop('selected', true);
|
||||
element.trigger({
|
||||
type: 'select2:select',
|
||||
params: {
|
||||
data: option
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
117
reports/mobile-proofofplay-report-preview.twig
Normal file
117
reports/mobile-proofofplay-report-preview.twig
Normal file
@@ -0,0 +1,117 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
<button class="btn btn-info XiboRedirectButton" href="{{ url_for("savedreport.view") }}"><i class="fa fa-eye" aria-hidden="true"></i> {% trans "Saved Reports" %}</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<i class="fa fa-list"></i>
|
||||
{{ metadata.title }}
|
||||
<span class="small">({% trans "Generated on: " %}{{ metadata.generatedOn }})</span>
|
||||
<div><span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}">
|
||||
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="stats" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Start" %}</th>
|
||||
<th>{% trans "End" %}</th>
|
||||
<th>{% trans "Display Id" %}</th>
|
||||
<th>{% trans "Display" %}</th>
|
||||
<th>{% trans "Layout Id" %}</th>
|
||||
<th>{% trans "Layout" %}</th>
|
||||
<th>{% trans "Start Latitude" %}</th>
|
||||
<th>{% trans "Start Longitude" %}</th>
|
||||
<th>{% trans "End Latitude" %}</th>
|
||||
<th>{% trans "End Longitude" %}</th>
|
||||
<th>{% trans "Duration" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
$(document).ready(function() {
|
||||
|
||||
let outputData = {{ table|json_encode|raw }};
|
||||
|
||||
// Grid
|
||||
let table = $("#stats").DataTable({
|
||||
"language": dataTablesLanguage,
|
||||
"dom": dataTablesTemplate,
|
||||
"paging": false,
|
||||
"ordering": false,
|
||||
"info": false,
|
||||
"order": [[1, "asc"]],
|
||||
"searching": false,
|
||||
"data": outputData,
|
||||
"columns": [
|
||||
{
|
||||
"data": "from",
|
||||
"render": dataTableDateFromIso
|
||||
},
|
||||
{
|
||||
"data": "to",
|
||||
"render": dataTableDateFromIso
|
||||
},
|
||||
{"data": "displayId"},
|
||||
{"data": "display"},
|
||||
{"data": "layoutId"},
|
||||
{"data": "layout"},
|
||||
{"data": "startLat"},
|
||||
{"data": "startLong"},
|
||||
{"data": "endLat"},
|
||||
{"data": "endLong"},
|
||||
{"data": "duration"}
|
||||
]
|
||||
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
100
reports/mobile-proofofplay-schedule-form-add.twig
Normal file
100
reports/mobile-proofofplay-schedule-form-add.twig
Normal file
@@ -0,0 +1,100 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
{% extends "form-base.twig" %}
|
||||
{% import "forms.twig" as forms %}
|
||||
|
||||
{% block formTitle %}
|
||||
{% trans "Add Report Schedule" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block formButtons %}
|
||||
{% trans "Cancel" %}, XiboDialogClose()
|
||||
{% trans "Save" %}, $("#reportScheduleAddForm").submit()
|
||||
{% endblock %}
|
||||
|
||||
{% block callBack %}reportScheduleCallback{% endblock %}
|
||||
|
||||
{% block formHtml %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form id="reportScheduleAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("reportschedule.add") }}">
|
||||
|
||||
{{ forms.hidden("hiddenFields", hiddenFields) }}
|
||||
{{ forms.hidden("reportName", reportName) }}
|
||||
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{% set helpText %}{% trans "The name for this report schedule" %}{% endset %}
|
||||
{{ forms.input("name", title, "", helpText, "", "required") }}
|
||||
|
||||
{% set title %}{% trans "Frequency" %}{% endset %}
|
||||
{% set helpText %}{% trans "Select how frequently you would like this report to run" %}{% endset %}
|
||||
{% set daily %}{% trans "Daily" %}{% endset %}
|
||||
{% set weekly %}{% trans "Weekly" %}{% endset %}
|
||||
{% set monthly %}{% trans "Monthly" %}{% endset %}
|
||||
{% set yearly %}{% trans "Yearly" %}{% endset %}
|
||||
{% set options = [
|
||||
{ name: "daily", filter: daily },
|
||||
{ name: "weekly", filter: weekly },
|
||||
{ name: "monthly", filter: monthly },
|
||||
{ name: "yearly", filter: yearly },
|
||||
] %}
|
||||
{{ forms.dropdown("filter", "single", title, "", options, "name", "filter", helpText) }}
|
||||
|
||||
{% set title %}{% trans "Display" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("display.search") },
|
||||
{ name: "data-search-term", value: "display" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-default-values", value: displayId },
|
||||
{ name: "data-id-property", value: "displayId" },
|
||||
{ name: "data-text-property", value: "display" }
|
||||
] %}
|
||||
{{ forms.dropdown("displayId", "single", title, "", null, "displayId", "display", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("displayGroup.search") },
|
||||
{ name: "data-search-term", value: "displayGroup" },
|
||||
{ name: "data-id-property", value: "displayGroupId" },
|
||||
{ name: "data-text-property", value: "displayGroup" }
|
||||
] %}
|
||||
{{ forms.dropdown("displayGroupId[]", "dropdownmulti", title, "", null, "displayGroupId", "displayGroup", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Should an email be sent?" %}{% endset %}
|
||||
{{ forms.checkbox("sendEmail", title, sendEmail) }}
|
||||
|
||||
{% set title %}{% trans "Email addresses" %}{% endset %}
|
||||
{% set helpText %}{% trans "Additional emails separated by a comma." %}{% endset %}
|
||||
{{ forms.inputWithTags("nonusers", title, nonusers, helpText) }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
66
reports/proofofplay-email-template.twig
Normal file
66
reports/proofofplay-email-template.twig
Normal file
@@ -0,0 +1,66 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "base-report.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span>
|
||||
</div>
|
||||
<p></p>
|
||||
|
||||
<table class="saved-report-table">
|
||||
<tr>
|
||||
<th>{% trans "Type" %}</th>
|
||||
<th>{% trans "Display ID" %}</th>
|
||||
<th>{% trans "Display" %}</th>
|
||||
<th>{% trans "Campaign" %}</th>
|
||||
<th>{% trans "Layout ID" %}</th>
|
||||
<th>{% trans "Layout" %}</th>
|
||||
<th>{% trans "Widget ID" %}</th>
|
||||
<th>{% trans "Media" %}</th>
|
||||
<th>{% trans "Tag" %}</th>
|
||||
<th>{% trans "Number of Plays" %}</th>
|
||||
<th>{% trans "Total Duration (s)" %}</th>
|
||||
<th>{% trans "First Shown" %}</th>
|
||||
<th>{% trans "Last Shown" %}</th>
|
||||
</tr>
|
||||
{% for item in tableData %}
|
||||
<tr>
|
||||
<td>{{ item.type }}</td>
|
||||
<td>{{ item.displayId }}</td>
|
||||
<td>{{ item.display }}</td>
|
||||
<td>{{ item.parentCampaign }}</td>
|
||||
<td>{{ item.layoutId }}</td>
|
||||
<td>{{ item.layout }}</td>
|
||||
<td>{{ item.widgetId }}</td>
|
||||
<td>{{ item.media }}</td>
|
||||
<td>{{ item.tag }}</td>
|
||||
<td>{{ item.numberPlays }}</td>
|
||||
<td>{{ item.duration }}</td>
|
||||
<td>{{ item.minStart }}</td>
|
||||
<td>{{ item.maxEnd }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
||||
691
reports/proofofplay-report-form.twig
Normal file
691
reports/proofofplay-report-form.twig
Normal file
@@ -0,0 +1,691 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2021 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block title %}{% trans "Report: Proof of Play" %} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
{% include "report-schedule-buttons.twig" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<span>{% trans "Proof of Play" %}</span>
|
||||
</div>
|
||||
|
||||
{% include "report-selector.twig" %}
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-grid-name="proofOfPlayView" data-refresh-on-form-submit="false">
|
||||
<div class="XiboFilterCustom card bg-light mb-3">
|
||||
<div class="FilterDiv card-body" id="proofofplayReport">
|
||||
<!-- Form Filter -->
|
||||
<form class="form-inline">
|
||||
{% set title %}{% trans "Range" %}{% endset %}
|
||||
{% set range %}{% trans "Select a range" %}{% endset %}
|
||||
{% set today %}{% trans "Today" %}{% endset %}
|
||||
{% set yesterday %}{% trans "Yesterday" %}{% endset %}
|
||||
{% set thisweek %}{% trans "This Week" %}{% endset %}
|
||||
{% set thismonth %}{% trans "This Month" %}{% endset %}
|
||||
{% set thisyear %}{% trans "This Year" %}{% endset %}
|
||||
{% set lastweek %}{% trans "Last Week" %}{% endset %}
|
||||
{% set lastmonth %}{% trans "Last Month" %}{% endset %}
|
||||
{% set lastyear %}{% trans "Last Year" %}{% endset %}
|
||||
{% set options = [
|
||||
{ filterName: "", reportFilter: range },
|
||||
{ filterName: "today", reportFilter: today },
|
||||
{ filterName: "yesterday", reportFilter: yesterday },
|
||||
{ filterName: "thisweek", reportFilter: thisweek },
|
||||
{ filterName: "thismonth", reportFilter: thismonth },
|
||||
{ filterName: "thisyear", reportFilter: thisyear },
|
||||
{ filterName: "lastweek", reportFilter: lastweek },
|
||||
{ filterName: "lastmonth", reportFilter: lastmonth },
|
||||
{ filterName: "lastyear", reportFilter: lastyear },
|
||||
] %}
|
||||
{{ inline.dropdown("reportFilter", "single", title, "today", options, "filterName", "reportFilter") }}
|
||||
|
||||
{% set title %}{% trans "From Date" %}{% endset %}
|
||||
{{ inline.date("statsFromDt", title, defaults.fromDateOneDay, "", "stats-from-dt", "", "") }}
|
||||
|
||||
{% set title %}{% trans "Time" %}{% endset %}
|
||||
{{ inline.time("statsFromDtTime", title, "00:00", "", "stats-from-dt-time") }}
|
||||
|
||||
{% set title %}{% trans "To Date" %}{% endset %}
|
||||
{{ inline.date("statsToDt", title, defaults.toDate, "", "stats-to-dt", "", "") }}
|
||||
|
||||
{% set title %}{% trans "Time" %}{% endset %}
|
||||
{{ inline.time("statsToDtTime", title, "00:00", "", "stats-to-dt-time") }}
|
||||
|
||||
{% set title %}{% trans "Group By" %}{% endset %}
|
||||
{% set options = [
|
||||
{ id: "display", name: "Display" },
|
||||
{ id: "displayGroup", name: "Display Group"|trans },
|
||||
{ id: "tag", name: "Tag"|trans }
|
||||
] %}
|
||||
{{ inline.dropdown("groupBy", "single", title, "", options, "id", "name", "") }}
|
||||
|
||||
{% set title %}{% trans "Display" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("display.search") },
|
||||
{ name: "data-search-term", value: "display" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "displayId" },
|
||||
{ name: "data-text-property", value: "display" }
|
||||
] %}
|
||||
{{ inline.dropdown("displayId", "single", title, "", null, "displayId", "display", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("displayGroup.search") },
|
||||
{ name: "data-search-term", value: "displayGroup" },
|
||||
{ name: "data-id-property", value: "displayGroupId" },
|
||||
{ name: "data-text-property", value: "displayGroup" }
|
||||
] %}
|
||||
{{ inline.dropdown("displayGroupId[]", "dropdownmulti", title, "", null, "displayGroupId", "displayGroup", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("layout.search") },
|
||||
{ name: "data-search-term", value: "layout" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "layoutId" },
|
||||
{ name: "data-text-property", value: "layout" }
|
||||
] %}
|
||||
|
||||
{% set title %}{% trans "Layout" %}{% endset %}
|
||||
{{ inline.dropdown("layoutId[]", "dropdownmulti", title, "", null, "layoutId", "layout", "", "pagedSelect", "", "l", "", attributes) }}
|
||||
|
||||
{# Campaign list only. #}
|
||||
{% set attributes = [
|
||||
{ name: "data-search-url", value: url_for("campaign.search") },
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
] %}
|
||||
|
||||
{% set title %}{% trans "Campaign" %}{% endset %}
|
||||
{% set helpText %}{% trans "Please select a Campaign" %}{% endset %}
|
||||
{{ inline.dropdown("parentCampaignId", "single", title, "", null, "campaignId", "campaign", "", "", "", "", "", attributes) }}
|
||||
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("library.search") },
|
||||
{ name: "data-search-term", value: "media" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "mediaId" },
|
||||
{ name: "data-text-property", value: "name" }
|
||||
] %}
|
||||
{% set title %}{% trans "Media" %}{% endset %}
|
||||
{{ inline.dropdown("mediaId[]", "dropdownmulti", title, "", null, "mediaId", "name", "", "pagedSelect", "", "m", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Type" %}{% endset %}
|
||||
{% set layout %}{% trans "Layout" %}{% endset %}
|
||||
{% set media %}{% trans "Media" %}{% endset %}
|
||||
{% set widget %}{% trans "Widget" %}{% endset %}
|
||||
{% set event %}{% trans "Event" %}{% endset %}
|
||||
{% set options = [
|
||||
{ typeid: "", type: null },
|
||||
{ typeid: "layout", type: layout },
|
||||
{ typeid: "media", type: media },
|
||||
{ typeid: "widget", type: widget },
|
||||
{ typeid: "event", type: event }
|
||||
] %}
|
||||
{{ inline.dropdown("type", "single", title, "", options, "typeid", "type") }}
|
||||
|
||||
{% set title %}{% trans "Tags from" %}{% endset %}
|
||||
{% set dg %}{% trans "Display" %}{% endset %}
|
||||
{% set layout %}{% trans "Layout" %}{% endset %}
|
||||
{% set media %}{% trans "Media" %}{% endset %}
|
||||
{% set options = [
|
||||
{ tagsTypeid: "dg", tagsType: dg },
|
||||
{ tagsTypeid: "layout", tagsType: layout },
|
||||
{ tagsTypeid: "media", tagsType: media }
|
||||
] %}
|
||||
{{ inline.dropdown("tagsType", "single", title, "dg", options, "tagsTypeid", "tagsType") }}
|
||||
|
||||
{% if currentUser.featureEnabled("tag.tagging") %}
|
||||
{% set title %}{% trans "Tags" %}{% endset %}
|
||||
{% set exactMatchTitle %}{% trans "Should Tags filter by exact match?" %}{% endset %}
|
||||
{% set logicalOperatorTitle %}{% trans "When filtering by multiple Tags, which logical operator should be used?" %}{% endset %}
|
||||
{% set helpText %}{% trans "A comma separated list of tags to filter by. Enter a tag|tag value to filter tags with values. Enter --no-tag to filter all items without tags. Enter - before a tag or tag value to exclude from results." %}{% endset %}
|
||||
{{ inline.inputWithTags("tags", title, null, helpText, null, null, null, "exactTags", exactMatchTitle, logicalOperatorTitle) }}
|
||||
{% endif %}
|
||||
|
||||
<div class="w-100">
|
||||
<a id="applyBtn" class="btn btn-success">
|
||||
<span>{% trans "Apply" %}</span>
|
||||
</a>
|
||||
<span id="imageLoader" class=""></span>
|
||||
<span id="applyWarning" class="text-warning" style="display:none; padding-left: 10px">{% trans "Warning: This may return a lot of data and may take several minutes to process." %}</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Card Header -->
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-tabs card-header-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="tabular-tab" data-toggle="tab" href="#tabularTab" role="tab"
|
||||
aria-controls="tabularTab" aria-selected="true">Tabular</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Card Body -->
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<!-- TABULAR TAB-->
|
||||
<div class="tab-pane active" id="tabularTab" role="tabpanel" aria-labelledby="tabular-tab">
|
||||
<!-- DATATABLE -->
|
||||
<div class="table-container" id="table_wrapper">
|
||||
<table id="stats"
|
||||
class="table xibo-table table-striped table-full-width"
|
||||
style="width: 100%"
|
||||
data-state-preference-name="proofOfPlayGrid"
|
||||
data-url="/report/data/proofofplayReport">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Type" %}</th>
|
||||
<th>{% trans "Display ID" %}</th>
|
||||
<th>{% trans "Display" %}</th>
|
||||
<th>{% trans "Display Group ID" %}</th>
|
||||
<th>{% trans "Display Group" %}</th>
|
||||
<th>{% trans "Tag ID" %}</th>
|
||||
<th>{% trans "Tag Name" %}</th>
|
||||
<th>{% trans "Campaign" %}</th>
|
||||
<th>{% trans "Layout ID" %}</th>
|
||||
<th>{% trans "Layout" %}</th>
|
||||
<th>{% trans "Widget ID" %}</th>
|
||||
<th>{% trans "Media" %}</th>
|
||||
<th>{% trans "Tag" %}</th>
|
||||
<th>{% trans "Number of Plays" %}</th>
|
||||
<th>{% trans "Total Duration" %}</th>
|
||||
<th>{% trans "Total Duration (s)" %}</th>
|
||||
<th title="{{ "NB: proof of play records which span your range are returned in this report."|trans }}">{% trans "First Period Shown" %}</th>
|
||||
<th title="{{ "NB: proof of play records which span your range are returned in this report."|trans }}">{% trans "Last Period Shown" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
$(document).ready(function() {
|
||||
let $report = $("#proofofplayReport");
|
||||
let $dataTable = $('#stats'); // Datatable
|
||||
let chart = null; // Chart
|
||||
let result; // XHR get data result
|
||||
|
||||
let imageLoader = $("#imageLoader");
|
||||
let $warning = $("#applyWarning");
|
||||
let $applyBtn = $("#applyBtn");
|
||||
|
||||
// Report Filter
|
||||
let reportFilter = $("#reportFilter"); // Report Filter
|
||||
|
||||
// Grid
|
||||
let table = $dataTable.DataTable({
|
||||
"language": dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
stateLoadCallback: dataTableStateLoadCallback,
|
||||
stateSaveCallback: dataTableStateSaveCallback,
|
||||
drawCallback: function( settings ) {
|
||||
setTimeout(function() {
|
||||
$("#applyBtn").removeClass('disabled');
|
||||
}, 300);
|
||||
|
||||
// We hide empty columns and display appropriate columns (ie ID and name)
|
||||
switch ($('select[name="groupBy"]').val()) {
|
||||
case 'displayGroup':
|
||||
$(this.api().columns([1, 2, 5, 6]).visible(false));
|
||||
$(this.api().columns([3, 4]).visible(true));
|
||||
break;
|
||||
case 'tag':
|
||||
$(this.api().columns([1,2, 3, 4]).visible(false));
|
||||
$(this.api().columns([5, 6]).visible(true));
|
||||
break;
|
||||
default:
|
||||
$(this.api().columns([3, 4, 5, 6]).visible(false));
|
||||
$(this.api().columns([1, 2]).visible(true));
|
||||
}
|
||||
},
|
||||
filter: false,
|
||||
"order": [[1, "asc"]],
|
||||
data:{},
|
||||
"columns": [
|
||||
{"data": "type"},
|
||||
{"data": "displayId"},
|
||||
{"data": "display"},
|
||||
{"data": "displayGroupId"},
|
||||
{"data": "displayGroup"},
|
||||
{"data": "tagId"},
|
||||
{"data": "tagName"},
|
||||
{"data": "parentCampaign"},
|
||||
{"data": "layoutId"},
|
||||
{"data": "layout"},
|
||||
{"data": "widgetId"},
|
||||
{"data": "media"},
|
||||
{"data": "tag"},
|
||||
{"data": "numberPlays"},
|
||||
{
|
||||
"data": "duration",
|
||||
"render": function (data, type, row, meta) {
|
||||
if (type != "display")
|
||||
return "";
|
||||
|
||||
var durationData = moment.duration(data, "seconds");
|
||||
var dataM = '';
|
||||
|
||||
var months = durationData.months();
|
||||
if (months > 0) {
|
||||
durationData.subtract(moment.duration(months,'months'));
|
||||
dataM += months + '{% trans "month" %} ';
|
||||
}
|
||||
|
||||
var days = durationData.days();
|
||||
durationData.subtract(moment.duration(days,'days'));
|
||||
dataM += days + '{% trans "day" %} ';
|
||||
|
||||
var hours = durationData.hours();
|
||||
durationData.subtract(moment.duration(hours,'hours'));
|
||||
dataM += hours + '{% trans "hr" %} ';
|
||||
|
||||
var minutes = durationData.minutes();
|
||||
durationData.subtract(moment.duration(minutes,'minutes'));
|
||||
dataM += minutes + '{% trans "min" %} ';
|
||||
|
||||
var seconds = durationData.seconds();
|
||||
dataM += seconds + '{% trans "sec" %} ';
|
||||
|
||||
return dataM;
|
||||
}
|
||||
},
|
||||
{"data": "duration"},
|
||||
{"data": "minStart"},
|
||||
{"data": "maxEnd"}
|
||||
],
|
||||
footerCallback: function (row, data, start, end, display) {
|
||||
let api = this.api();
|
||||
|
||||
// Total over all pages
|
||||
let totalNumberPlays = api.column(13).data().reduce(function (a, b) {
|
||||
return a + b;
|
||||
}, 0);
|
||||
let totalDuration = api.column(15).data().reduce(function (a, b) {
|
||||
return a + b;
|
||||
}, 0);
|
||||
let totalNumberPlaysPage = api.column(13, { page: 'current'}).data().reduce(function (a, b) {
|
||||
return a + b;
|
||||
}, 0);
|
||||
let totalDurationPage = api.column(15, { page: 'current'}).data().reduce(function (a, b) {
|
||||
return a + b;
|
||||
}, 0);
|
||||
|
||||
// Update footer
|
||||
$(api.column(13).footer()).html(totalNumberPlaysPage + ' (' + totalNumberPlays + ' total)');
|
||||
$(api.column(15).footer()).html(Math.floor(totalDurationPage) + ' (' + Math.floor(totalDuration) + ' total)');
|
||||
},
|
||||
});
|
||||
|
||||
// table.on('draw', dataTableDraw);
|
||||
// table.on('processing.dt', dataTableProcessing);
|
||||
// dataTableAddButtons(table, $('#stats_wrapper').find('.dataTables_buttons'));
|
||||
|
||||
// Get Data
|
||||
function getData(url) {
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
data: $("#stats").closest(".XiboGrid").find(".FilterDiv form").serializeObject(),
|
||||
success: function success(data) {
|
||||
result = data;
|
||||
$applyBtn.removeClass('disabled');
|
||||
imageLoader.removeClass('fa fa-spinner fa-spin loading-icon');
|
||||
|
||||
// Based on tab load data
|
||||
if ($('.nav-tabs .nav-item a.active').attr("href") === '#tabularTab') {
|
||||
setTabularData(table, result.table);
|
||||
} else {
|
||||
setChartData(result.chart);
|
||||
}
|
||||
},
|
||||
error: function error(xhr, textStatus, _error) {
|
||||
$applyBtn.removeClass('disabled');
|
||||
toastr.error(xhr.responseJSON.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setTabularData(table, data) {
|
||||
table.clear().draw();
|
||||
|
||||
if (Object.keys(data).length > 0) {
|
||||
table.rows.add( data ).draw()
|
||||
}
|
||||
}
|
||||
|
||||
function setChartData(data) {
|
||||
|
||||
setTimeout(function() {
|
||||
$applyBtn.removeClass('disabled');
|
||||
}, 300);
|
||||
|
||||
if (chart !== undefined && chart !== null) {
|
||||
chart.destroy();
|
||||
}
|
||||
|
||||
// Create our chart
|
||||
chart = new Chart($("#canvas"), data);
|
||||
}
|
||||
|
||||
// Tab shown/click load relevant table/chart
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
let activeTab = $(e.target).attr("href")
|
||||
if (result) {
|
||||
if (activeTab === '#tabularTab') {
|
||||
setTabularData(table, result.table);
|
||||
} else {
|
||||
setChartData(result.chart);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
dataTableAddButtons(table, $('#stats_wrapper').find('.dataTables_buttons'));
|
||||
|
||||
// Apply
|
||||
$applyBtn.click(function () {
|
||||
$(this).addClass('disabled');
|
||||
imageLoader.addClass('fa fa-spinner fa-spin loading-icon');
|
||||
getData($dataTable.data().url);
|
||||
});
|
||||
|
||||
// If we select a displayId, we hide the display group filter
|
||||
$('#displayId').off('change').change( function() {
|
||||
let displayId = $('#displayId').val();
|
||||
|
||||
if (displayId) {
|
||||
$('select[name="displayGroupId[]"] option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().hide();
|
||||
$('select[name="groupBy[]"] option').remove();
|
||||
$('select[name="groupBy"]').parent().hide();
|
||||
} else {
|
||||
$('#displayId option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().show();
|
||||
$('select[name="groupBy"]').parent().show();
|
||||
}
|
||||
});
|
||||
|
||||
// If we select a groupBy data, we hide the display filter
|
||||
$("select[name='groupBy']").on('change', function() {
|
||||
let optionSelected = $(this).find("option:selected").val();
|
||||
|
||||
if (optionSelected === 'displayGroup') {
|
||||
$('select[name="groupBy"]').parent().show();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().show();
|
||||
} else {
|
||||
$('select[name="displayGroupId[]"] option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().hide();
|
||||
}
|
||||
|
||||
if (optionSelected === 'display') {
|
||||
$("select[name='displayId']").parent().show();
|
||||
} else {
|
||||
$("select[name='displayId']").parent().hide();
|
||||
}
|
||||
});
|
||||
|
||||
// Hide / Show FromDt and ToDt
|
||||
function checkReportFilter(reportFilter) {
|
||||
if (reportFilter.val() === '' || reportFilter.val() === undefined) {
|
||||
$(".stats-from-dt").show();
|
||||
$(".stats-to-dt").show();
|
||||
$(".stats-from-dt-time").show();
|
||||
$(".stats-to-dt-time").show();
|
||||
} else {
|
||||
$(".stats-from-dt").hide();
|
||||
$(".stats-to-dt").hide();
|
||||
$(".stats-from-dt-time").hide();
|
||||
$(".stats-to-dt-time").hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the difference of number of days of a selected range
|
||||
let calculateDaysShowHideWarn = function() {
|
||||
|
||||
let fromDt = moment($("#statsFromDt").val());
|
||||
let toDt = moment($("#statsToDt").val());
|
||||
|
||||
let days = toDt.diff(fromDt, 'days');
|
||||
|
||||
$warning.hide();
|
||||
if ( days >= 30) {
|
||||
$warning.show();
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
$("#statsFromDtLink").change( function() {
|
||||
calculateDaysShowHideWarn();
|
||||
});
|
||||
|
||||
$("#statsToDtLink").change( function() {
|
||||
calculateDaysShowHideWarn();
|
||||
});
|
||||
|
||||
let checkFilterAndApply = function() {
|
||||
|
||||
reportFilter.off('change').change( function() {
|
||||
let value = reportFilter.val();
|
||||
|
||||
// Hide / Show FromDt and ToDt
|
||||
checkReportFilter(reportFilter);
|
||||
|
||||
// Hide / Show Warning
|
||||
$warning.hide();
|
||||
if ( value === '') {
|
||||
calculateDaysShowHideWarn();
|
||||
} else if ( value === 'thismonth' || value === 'lastmonth' || value === 'thisyear' || value === 'lastyear') {
|
||||
$warning.show();
|
||||
}
|
||||
});
|
||||
|
||||
let anchorReportAddBtn = $("button#reportAddBtn");
|
||||
let type = $("#type").val();
|
||||
let tagsType = $("#tagsType").val();
|
||||
let tags = $("#tags").val();
|
||||
let exactTags = $('#exactTags').is(":checked");
|
||||
|
||||
anchorReportAddBtn.attr("href", "{{ url_for("reportschedule.add.form") }}?type=" + type + "&tagsType=" + tagsType + "&tags=" + tags + "&exactTags=" + exactTags
|
||||
+ "&reportName=proofofplayReport" );
|
||||
|
||||
};
|
||||
|
||||
checkReportFilter(reportFilter);
|
||||
checkFilterAndApply();
|
||||
|
||||
|
||||
var $campaignSelect = $('#parentCampaignId');
|
||||
$campaignSelect.select2({
|
||||
ajax: {
|
||||
url: $campaignSelect.data("searchUrl"),
|
||||
dataType: "json",
|
||||
delay: 250,
|
||||
placeholder: 'Campaign',
|
||||
allowClear: true,
|
||||
data: function(params) {
|
||||
|
||||
var query = {
|
||||
isLayoutSpecific: 0,
|
||||
retired: 0,
|
||||
totalDuration: 0,
|
||||
name: params.term,
|
||||
start: 0,
|
||||
length: 10,
|
||||
columns: [
|
||||
{
|
||||
"data": "isLayoutSpecific"
|
||||
},
|
||||
{
|
||||
"data": "campaign"
|
||||
}
|
||||
],
|
||||
order: [
|
||||
{
|
||||
"column": 0,
|
||||
"dir": "asc"
|
||||
},
|
||||
{
|
||||
"column": 1,
|
||||
"dir": "asc"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Set the start parameter based on the page number
|
||||
if (params.page != null) {
|
||||
query.start = (params.page - 1) * 10;
|
||||
}
|
||||
|
||||
return query;
|
||||
},
|
||||
processResults: function(data, params) {
|
||||
|
||||
var results = [];
|
||||
var campaigns = [];
|
||||
|
||||
$.each(data.data, function(index, element) {
|
||||
campaigns.push({
|
||||
"id": element.campaignId,
|
||||
"text": element.campaign
|
||||
});
|
||||
});
|
||||
|
||||
results.push({
|
||||
"text": $campaignSelect.data('transCampaigns'),
|
||||
"children": campaigns
|
||||
})
|
||||
|
||||
|
||||
var page = params.page || 1;
|
||||
page = (page > 1) ? page - 1 : page;
|
||||
|
||||
return {
|
||||
results: results,
|
||||
pagination: {
|
||||
more: (page * 10 < data.recordsTotal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function proofOfPlayScheduleCallback() {
|
||||
|
||||
let $displayId = $('#proofofplayReport #displayId');
|
||||
let $layoutId = $('#proofofplayReport [id="layoutId[]"]');
|
||||
let $mediaId = $('#proofofplayReport [id="mediaId[]"]');
|
||||
let $newDisplayId = $('#proofofplayScheduleAddForm #displayId');
|
||||
let $newLayoutId = $('#proofofplayScheduleAddForm [id="layoutId[]"]');
|
||||
let $newMediaId = $('#proofofplayScheduleAddForm [id="mediaId[]"]');
|
||||
|
||||
appendOptions($newDisplayId, $displayId.find('option:selected').clone());
|
||||
appendOptions($newLayoutId, $layoutId.find('option:selected').clone());
|
||||
appendOptions($newMediaId, $mediaId.find('option:selected').clone());
|
||||
}
|
||||
|
||||
function appendOptions(element, options) {
|
||||
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
|
||||
let option = options[i];
|
||||
element.append(option).trigger('change');
|
||||
$(option).prop('selected', true);
|
||||
element.trigger({
|
||||
type: 'select2:select',
|
||||
params: {
|
||||
data: option
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
149
reports/proofofplay-report-preview.twig
Normal file
149
reports/proofofplay-report-preview.twig
Normal file
@@ -0,0 +1,149 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2019 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
<button class="btn btn-info XiboRedirectButton" href="{{ url_for("savedreport.view") }}"><i class="fa fa-eye" aria-hidden="true"></i> {% trans "Saved Reports" %}</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<i class="fa fa-list"></i>
|
||||
{{ metadata.title }}
|
||||
<span class="small">({% trans "Generated on: " %}{{ metadata.generatedOn }})</span>
|
||||
<div><span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}">
|
||||
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="stats" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Type" %}</th>
|
||||
<th>{% trans "Display ID" %}</th>
|
||||
<th>{% trans "Display" %}</th>
|
||||
<th>{% trans "Campaign" %}</th>
|
||||
<th>{% trans "Layout ID" %}</th>
|
||||
<th>{% trans "Layout" %}</th>
|
||||
<th>{% trans "Widget ID" %}</th>
|
||||
<th>{% trans "Media" %}</th>
|
||||
<th>{% trans "Tag" %}</th>
|
||||
<th>{% trans "Number of Plays" %}</th>
|
||||
<th>{% trans "Total Duration" %}</th>
|
||||
<th>{% trans "Total Duration (s)" %}</th>
|
||||
<th>{% trans "First Shown" %}</th>
|
||||
<th>{% trans "Last Shown" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
$(document).ready(function() {
|
||||
|
||||
let outputData = {{ table|json_encode|raw }};
|
||||
|
||||
// Grid
|
||||
let table = $("#stats").DataTable({
|
||||
"language": dataTablesLanguage,
|
||||
"dom": dataTablesTemplate,
|
||||
"paging": false,
|
||||
"ordering": false,
|
||||
"info": false,
|
||||
"order": [[1, "asc"]],
|
||||
"searching": false,
|
||||
"data": outputData,
|
||||
"columns": [
|
||||
{ "data": 'type' },
|
||||
{ "data": 'displayId' },
|
||||
{ "data": 'display' },
|
||||
{"data": "parentCampaign"},
|
||||
{ "data": 'layoutId' },
|
||||
{ "data": 'layout' },
|
||||
{ "data": 'widgetId' },
|
||||
{ "data": 'media' },
|
||||
{ "data": 'tag' },
|
||||
{ "data": 'numberPlays' },
|
||||
{
|
||||
"data": "duration",
|
||||
"render": function (data, type, row, meta) {
|
||||
if (type !== "display")
|
||||
return "";
|
||||
|
||||
let durationData = moment.duration(data, "seconds");
|
||||
let dataM = '';
|
||||
|
||||
let months = durationData.months();
|
||||
if (months > 0) {
|
||||
durationData.subtract(moment.duration(months,'months'));
|
||||
dataM += months + '{% trans "month" %} ';
|
||||
}
|
||||
|
||||
let days = durationData.days();
|
||||
durationData.subtract(moment.duration(days,'days'));
|
||||
dataM += days + '{% trans "day" %} ';
|
||||
|
||||
let hours = durationData.hours();
|
||||
durationData.subtract(moment.duration(hours,'hours'));
|
||||
dataM += hours + '{% trans "hr" %} ';
|
||||
|
||||
let minutes = durationData.minutes();
|
||||
durationData.subtract(moment.duration(minutes,'minutes'));
|
||||
dataM += minutes + '{% trans "min" %} ';
|
||||
|
||||
let seconds = durationData.seconds();
|
||||
dataM += seconds + '{% trans "sec" %} ';
|
||||
|
||||
return dataM;
|
||||
}
|
||||
},
|
||||
{ "data": 'duration' },
|
||||
{ "data": 'minStart' },
|
||||
{ "data": 'maxEnd' },
|
||||
]
|
||||
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
189
reports/proofofplay-schedule-form-add.twig
Normal file
189
reports/proofofplay-schedule-form-add.twig
Normal file
@@ -0,0 +1,189 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
{% extends "form-base.twig" %}
|
||||
{% import "forms.twig" as forms %}
|
||||
|
||||
{% block formTitle %}
|
||||
{% trans "Add Report Schedule" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block formButtons %}
|
||||
{% trans "Cancel" %}, XiboDialogClose()
|
||||
{% trans "Save" %}, $("#proofofplayScheduleAddForm").submit()
|
||||
{% endblock %}
|
||||
|
||||
{% block callBack %}proofOfPlayScheduleCallback{% endblock %}
|
||||
|
||||
{% block formHtml %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form id="proofofplayScheduleAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("reportschedule.add") }}">
|
||||
|
||||
{{ forms.hidden("hiddenFields", hiddenFields) }}
|
||||
{{ forms.hidden("reportName", reportName) }}
|
||||
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{% set helpText %}{% trans "The name for this report schedule" %}{% endset %}
|
||||
{{ forms.input("name", title, "", helpText, "", "required") }}
|
||||
|
||||
{% set title %}{% trans "Frequency" %}{% endset %}
|
||||
{% set helpText %}{% trans "Select how frequently you would like this report to run" %}{% endset %}
|
||||
{% set daily %}{% trans "Daily" %}{% endset %}
|
||||
{% set weekly %}{% trans "Weekly" %}{% endset %}
|
||||
{% set monthly %}{% trans "Monthly" %}{% endset %}
|
||||
{% set yearly %}{% trans "Yearly" %}{% endset %}
|
||||
{% set options = [
|
||||
{ name: "daily", filter: daily },
|
||||
{ name: "weekly", filter: weekly },
|
||||
{ name: "monthly", filter: monthly },
|
||||
{ name: "yearly", filter: yearly },
|
||||
] %}
|
||||
{{ forms.dropdown("filter", "single", title, "", options, "name", "filter", helpText) }}
|
||||
|
||||
{% set title %}{% trans "Start Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to run this report. Leave blank to run from the next collection point." %}{% endset %}
|
||||
{{ forms.dateTime("fromDt", title, "", helpText, "starttime-control") }}
|
||||
|
||||
{% set title %}{% trans "End Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to end the schedule. Leave blank to run indefinitely." %}{% endset %}
|
||||
{{ forms.dateTime("toDt", title, "", helpText, "endtime-control") }}
|
||||
|
||||
{% set title %}{% trans "Display" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("display.search") },
|
||||
{ name: "data-search-term", value: "display" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-default-values", value: displayId },
|
||||
{ name: "data-id-property", value: "displayId" },
|
||||
{ name: "data-text-property", value: "display" }
|
||||
] %}
|
||||
{{ forms.dropdown("displayId", "single", title, "", null, "displayId", "display", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("displayGroup.search") },
|
||||
{ name: "data-search-term", value: "displayGroup" },
|
||||
{ name: "data-id-property", value: "displayGroupId" },
|
||||
{ name: "data-text-property", value: "displayGroup" }
|
||||
] %}
|
||||
{{ inline.dropdown("displayGroupId[]", "dropdownmulti", title, "", null, "displayGroupId", "displayGroup", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("layout.search") },
|
||||
{ name: "data-search-term", value: "layout" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "layoutId" },
|
||||
{ name: "data-text-property", value: "layout" }
|
||||
] %}
|
||||
|
||||
{% set title %}{% trans "Layout" %}{% endset %}
|
||||
{{ forms.dropdown("layoutId[]", "dropdownmulti", title, "", null, "layoutId", "layout", "", "pagedSelect", "", "l", "", attributes) }}
|
||||
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("library.search") },
|
||||
{ name: "data-search-term", value: "media" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "mediaId" },
|
||||
{ name: "data-text-property", value: "name" }
|
||||
] %}
|
||||
{% set title %}{% trans "Media" %}{% endset %}
|
||||
{{ forms.dropdown("mediaId[]", "dropdownmulti", title, "", null, "mediaId", "name", "", "pagedSelect", "", "m", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Type" %}{% endset %}
|
||||
{% set layout %}{% trans "Layout" %}{% endset %}
|
||||
{% set media %}{% trans "Media" %}{% endset %}
|
||||
{% set widget %}{% trans "Widget" %}{% endset %}
|
||||
{% set event %}{% trans "Event" %}{% endset %}
|
||||
{% set options = [
|
||||
{ typeid: "", type: null },
|
||||
{ typeid: "layout", type: layout },
|
||||
{ typeid: "media", type: media },
|
||||
{ typeid: "widget", type: widget },
|
||||
{ typeid: "event", type: event }
|
||||
] %}
|
||||
{{ forms.dropdown("type", "single", title, type, options, "typeid", "type") }}
|
||||
|
||||
{% set title %}{% trans "Sort by" %}{% endset %}
|
||||
{% set widgetId %}{% trans "WidgetId" %}{% endset %}
|
||||
{% set type %}{% trans "Type" %}{% endset %}
|
||||
{% set display %}{% trans "Display" %}{% endset %}
|
||||
{% set displayId %}{% trans "Display ID" %}{% endset %}
|
||||
{% set media %}{% trans "Media" %}{% endset %}
|
||||
{% set layout %}{% trans "Layout" %}{% endset %}
|
||||
{% set layoutId %}{% trans "Layout ID" %}{% endset %}
|
||||
{% set tag %}{% trans "Tag" %}{% endset %}
|
||||
{% set options = [
|
||||
{ sortbyid: "widgetId", sortby: widgetId },
|
||||
{ sortbyid: "type", sortby: type },
|
||||
{ sortbyid: "display", sortby: display },
|
||||
{ sortbyid: "displayId", sortby: displayId },
|
||||
{ sortbyid: "media", sortby: media },
|
||||
{ sortbyid: "layout", sortby: layout },
|
||||
{ sortbyid: "layoutId", sortby: layoutId },
|
||||
{ sortbyid: "tag", sortby: tag }
|
||||
] %}
|
||||
{{ forms.dropdown("sortBy", "single", title, "", options, "sortbyid", "sortby") }}
|
||||
|
||||
{% set title %}{% trans "Tags from" %}{% endset %}
|
||||
{% set dg %}{% trans "Display Group" %}{% endset %}
|
||||
{% set layout %}{% trans "Layout" %}{% endset %}
|
||||
{% set media %}{% trans "Media" %}{% endset %}
|
||||
{% set options = [
|
||||
{ tagsTypeid: "dg", tagsType: dg },
|
||||
{ tagsTypeid: "layout", tagsType: layout },
|
||||
{ tagsTypeid: "media", tagsType: media }
|
||||
] %}
|
||||
{{ forms.dropdown("tagsType", "single", title, tagsType, options, "tagsTypeid", "tagsType") }}
|
||||
|
||||
{% set title %}{% trans "Tags" %}{% endset %}
|
||||
{% set exactTagTitle %}{% trans "Should Tags filter by exact match?" %}{% endset %}
|
||||
{% set logicalOperatorTitle %}{% trans "When filtering by multiple Tags, which logical operator should be used?" %}{% endset %}
|
||||
{% set helpText %}{% trans "A comma separated list of tags to filter by. Enter a tag|tag value to filter tags with values. Enter --no-tag to filter all items without tags. Enter - before a tag or tag value to exclude from results." %}{% endset %}
|
||||
{{ forms.inputWithTags("tags", title, tags, helpText, "", "", "", "exactTag", exactTagTitle, logicalOperatorTitle, 0, 'OR') }}
|
||||
|
||||
{% set title %}{% trans "Should an email be sent?" %}{% endset %}
|
||||
{{ forms.checkbox("sendEmail", title, sendEmail) }}
|
||||
|
||||
{% set title %}{% trans "Email addresses" %}{% endset %}
|
||||
{% set helpText %}{% trans "Additional emails separated by a comma." %}{% endset %}
|
||||
{{ forms.inputWithTags("nonusers", title, nonusers, helpText) }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
14
reports/proofofplay.report
Normal file
14
reports/proofofplay.report
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "proofofplayReport",
|
||||
"description": "Proof of Play",
|
||||
"class": "\\Xibo\\Report\\ProofOfPlay",
|
||||
"type": "Report",
|
||||
"output_type": "table",
|
||||
"color":"blue",
|
||||
"fa_icon": "fa-th",
|
||||
"sort_order": 1,
|
||||
"hidden": 0,
|
||||
"category": "Proof of Play",
|
||||
"feature": "proof-of-play",
|
||||
"adminOnly": 0
|
||||
}
|
||||
56
reports/sessionhistory-email-template.twig
Normal file
56
reports/sessionhistory-email-template.twig
Normal file
@@ -0,0 +1,56 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "base-report.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span>
|
||||
</div>
|
||||
<p></p>
|
||||
|
||||
<table class="saved-report-table">
|
||||
<tr>
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "User Name" %}</th>
|
||||
<th>{% trans "User ID" %}</th>
|
||||
<th>{% trans "IP Address" %}</th>
|
||||
<th>{% trans "Session ID" %}</th>
|
||||
<th>{% trans "Message" %}</th>
|
||||
<th>{% trans "Object" %}</th>
|
||||
</tr>
|
||||
{% for item in tableData %}
|
||||
<tr>
|
||||
<td>{{ item.logDate }}</td>
|
||||
<td>{{ item.userName }}</td>
|
||||
<td>{{ item.userId }}</td>
|
||||
<td>{{ item.ipAddress }}</td>
|
||||
<td>{{ item.sessionHistoryId }}</td>
|
||||
<td>{{ item.message }}</td>
|
||||
<td>{{ item.objectAfter }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<br/>
|
||||
<span>{{ placeholder }}</span>
|
||||
<img src="{{ src|raw }}" >
|
||||
{% endblock %}
|
||||
411
reports/sessionhistory-report-form.twig
Normal file
411
reports/sessionhistory-report-form.twig
Normal file
@@ -0,0 +1,411 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block title %}{% trans "Report: Session History" %} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
{% include "report-schedule-buttons.twig" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<span>{% trans "Session History" %}</span>
|
||||
</div>
|
||||
|
||||
{% include "report-selector.twig" %}
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-refresh-on-form-submit="false">
|
||||
<div class="XiboFilterCustom">
|
||||
<div class="FilterDiv card-body" id="sessionHistoryFilter">
|
||||
<form class="form-inline">
|
||||
{% set title %}{% trans "Range" %}{% endset %}
|
||||
{{ inline.dateRangeFilter("reportFilter", title, "", "", "", "", "") }}
|
||||
|
||||
{% set title %}{% trans "User" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("user.search") },
|
||||
{ name: "data-search-term", value: "userName" },
|
||||
{ name: "data-id-property", value: "userId" },
|
||||
{ name: "data-text-property", value: "userName" },
|
||||
] %}
|
||||
{{ inline.dropdown("userId", "single", title, "", null, "userId", "userName", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Session History ID" %}{% endset %}
|
||||
{{ inline.number('sessionHistoryId', title) }}
|
||||
|
||||
{% set title = "Report Type"|trans %}
|
||||
{% set options = [
|
||||
{ id: 'audit', value: "Audit"|trans },
|
||||
{ id: 'debug', value: "Debug"|trans },
|
||||
{ id: 'sessions', value: "Sessions"|trans },
|
||||
] %}
|
||||
{{ inline.dropdown("type", "single", title, "sessions", options, "id", "value", helpText) }}
|
||||
|
||||
<div class="w-100">
|
||||
<a id="applyBtn" class="btn btn-success">
|
||||
<span>{% trans "Apply" %}</span>
|
||||
</a>
|
||||
<span id="imageLoader" class=""></span>
|
||||
<span id="applyWarning" class="text-warning" style="display:none; padding-left: 10px">{% trans "Warning: This may return a lot of data and may take several minutes to process." %}</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card Body -->
|
||||
<div class="card-body session-history-audit">
|
||||
<!-- DATATABLE -->
|
||||
<div class="table-container" id="table_wrapper">
|
||||
<table id="sessionHistoryAuditGrid"
|
||||
class="table xibo-table table-striped table-full-width"
|
||||
style="width: 100%"
|
||||
data-url="{{ url_for("report.data", {name: 'sessionhistory'}) }}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "User Name" %}</th>
|
||||
<th>{% trans "User ID" %}</th>
|
||||
<th>{% trans "IP Address" %}</th>
|
||||
<th>{% trans "Session ID" %}</th>
|
||||
<th>{% trans "Entity" %}</th>
|
||||
<th>{% trans "Entity ID" %}</th>
|
||||
<th>{% trans "Message" %}</th>
|
||||
<th>{% trans "Details" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body session-history-debug d-none">
|
||||
<!-- DATATABLE -->
|
||||
<div class="table-container" id="table_wrapper">
|
||||
<table id="sessionHistoryDebugGrid"
|
||||
class="table xibo-table table-striped table-full-width"
|
||||
style="width: 100%"
|
||||
data-url="{{ url_for("report.data", {name: 'sessionhistory'}) }}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "UserName" %}</th>
|
||||
<th>{% trans "User ID" %}</th>
|
||||
<th>{% trans "IP Address" %}</th>
|
||||
<th>{% trans "Session ID" %}</th>
|
||||
<th>{% trans "Channel" %}</th>
|
||||
<th>{% trans "Function" %}</th>
|
||||
<th>{% trans "Level" %}</th>
|
||||
<th>{% trans "Page" %}</th>
|
||||
<th>{% trans "Details" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body session-history-log d-none">
|
||||
<!-- DATATABLE -->
|
||||
<div class="table-container" id="table_wrapper">
|
||||
<table id="sessionHistoryLogGrid"
|
||||
class="table xibo-table table-striped table-full-width"
|
||||
style="width: 100%"
|
||||
data-url="{{ url_for("report.data", {name: 'sessionhistory'}) }}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Start Date" %}</th>
|
||||
<th>{% trans "End Date" %}</th>
|
||||
<th>{% trans "Duration" %}</th>
|
||||
<th>{% trans "UserName" %}</th>
|
||||
<th>{% trans "User Type" %}</th>
|
||||
<th>{% trans "IP Address" %}</th>
|
||||
<th>{% trans "Session ID" %}</th>
|
||||
<th>{% trans "Browser" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
{% verbatim %}
|
||||
<script type="text/x-handlebars-template" id="table-array-viewer">
|
||||
<a class="arrayViewerToggle" href="#"><span class="fa fa-search"></span></a>
|
||||
<table class="arrayViewer table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ col1 }}</th>
|
||||
<th>{{ col2 }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each items}}
|
||||
<tr>
|
||||
<td>{{ @key }}</td>
|
||||
<td>{{ this }}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</script>
|
||||
{% endverbatim %}
|
||||
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
|
||||
$(document).ready(function() {
|
||||
var arrayViewer = Handlebars.compile($('#table-array-viewer').html());
|
||||
|
||||
$('[data-toggle="popover"]').popover();
|
||||
let $report = $('#sessionHistoryFilter');
|
||||
let $auditDataTable = $('#sessionHistoryAuditGrid');
|
||||
let auditTable = createAuditTable($auditDataTable);
|
||||
let $logDataTable = $('#sessionHistoryDebugGrid');
|
||||
let logTable = createLogTable($logDataTable);
|
||||
let $sessionDataTable = $('#sessionHistoryLogGrid');
|
||||
let sessionTable = createSessionTable($sessionDataTable);
|
||||
|
||||
let result; // XHR get data result
|
||||
let reportType = $report.find('#type').val();
|
||||
|
||||
let imageLoader = $("#imageLoader");
|
||||
let $warning = $("#applyWarning");
|
||||
let $applyBtn = $("#applyBtn");
|
||||
|
||||
// Get Data
|
||||
function getData(url) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
data: $report.find('form').serialize(),
|
||||
success: function success(data) {
|
||||
result = data;
|
||||
reportType = $report.find('#type').val();
|
||||
$applyBtn.removeClass('disabled');
|
||||
imageLoader.removeClass('fa fa-spinner fa-spin loading-icon');
|
||||
if (reportType === 'audit') {
|
||||
$('.session-history-audit').removeClass('d-none');
|
||||
$('.session-history-debug').addClass('d-none');
|
||||
$('.session-history-log').addClass('d-none');
|
||||
setTabularData(auditTable, result.table);
|
||||
} else if (reportType === 'debug') {
|
||||
$('.session-history-audit').addClass('d-none');
|
||||
$('.session-history-log').addClass('d-none');
|
||||
$('.session-history-debug').removeClass('d-none');
|
||||
setTabularData(logTable, result.table);
|
||||
} else {
|
||||
$('.session-history-audit').addClass('d-none');
|
||||
$('.session-history-debug').addClass('d-none');
|
||||
$('.session-history-log').removeClass('d-none');
|
||||
setTabularData(sessionTable, result.table);
|
||||
}
|
||||
},
|
||||
error: function error(xhr, textStatus, _error) {
|
||||
$applyBtn.removeClass('disabled');
|
||||
toastr.error(xhr.responseJSON.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setTabularData(table, data) {
|
||||
table.clear().draw();
|
||||
|
||||
if (Object.keys(data).length > 0) {
|
||||
table.rows.add( data ).draw()
|
||||
}
|
||||
|
||||
if ($report.find('#type').val() === 'audit') {
|
||||
$('.arrayViewerToggle').click(function () {
|
||||
$(this).parent().find('.arrayViewer').toggle();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Apply
|
||||
$applyBtn.click(function () {
|
||||
$(this).addClass('disabled');
|
||||
imageLoader.addClass('fa fa-spinner fa-spin loading-icon');
|
||||
getData($auditDataTable.data().url);
|
||||
});
|
||||
|
||||
$("#refreshGrid").click(function () {
|
||||
reportType = $report.find('#type').val();
|
||||
|
||||
if (reportType=== 'audit') {
|
||||
auditTable.ajax.reload();
|
||||
} else if (reportType=== 'debug') {
|
||||
logTable.ajax.reload();
|
||||
} else {
|
||||
sessionTable.ajax.reload();
|
||||
}
|
||||
});
|
||||
|
||||
function createAuditTable($dataTable) {
|
||||
return $dataTable.DataTable({
|
||||
language: dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
searching: false,
|
||||
paging: true,
|
||||
bInfo: false,
|
||||
stateSave: true,
|
||||
bDestroy: true,
|
||||
processing: true,
|
||||
order: [[0, 'desc']],
|
||||
data: {},
|
||||
columns: [
|
||||
{data: 'logDate', "render": dataTableDateFromIso},
|
||||
{data: 'userName'},
|
||||
{data: 'userId'},
|
||||
{data: 'ipAddress'},
|
||||
{data: 'sessionHistoryId'},
|
||||
{data: 'entity', responsivePriority: 2},
|
||||
{
|
||||
name: 'entityId',
|
||||
responsivePriority: 2,
|
||||
data : function (data) {
|
||||
if (data.entityId === 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return data.entityId;
|
||||
}
|
||||
},
|
||||
{data: 'message'},
|
||||
{
|
||||
"data": function (data, type, row, meta) {
|
||||
if (type != "display")
|
||||
return "";
|
||||
|
||||
return arrayViewer(
|
||||
{"col1": "{% trans "Property" %}", "col2": "{% trans "Value" %}", "items": data.objectAfter}
|
||||
);
|
||||
},
|
||||
"sortable": false,
|
||||
responsivePriority: 1
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
function createLogTable($dataTable) {
|
||||
return $dataTable.DataTable({
|
||||
language: dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
searching: false,
|
||||
paging: true,
|
||||
bInfo: false,
|
||||
stateSave: true,
|
||||
bDestroy: true,
|
||||
processing: true,
|
||||
order: [[0, 'desc']],
|
||||
data: {},
|
||||
columns: [
|
||||
{data: 'logDate', "render": dataTableDateFromIso},
|
||||
{data: 'userName'},
|
||||
{data: 'userId'},
|
||||
{data: 'ipAddress'},
|
||||
{data: 'sessionHistoryId'},
|
||||
{data: 'channel'},
|
||||
{data: 'function'},
|
||||
{data: 'type'},
|
||||
{data: 'page'},
|
||||
{data: 'message'},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
function createSessionTable($dataTable) {
|
||||
return $dataTable.DataTable({
|
||||
language: dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
searching: false,
|
||||
paging: true,
|
||||
bInfo: false,
|
||||
stateSave: true,
|
||||
bDestroy: true,
|
||||
processing: true,
|
||||
order: [[0, 'desc']],
|
||||
data: {},
|
||||
columns: [
|
||||
{data: 'startTime', "render": dataTableDateFromIso},
|
||||
{data: 'endTime', "render": dataTableDateFromIso},
|
||||
{data: 'duration'},
|
||||
{data: 'userName'},
|
||||
{data: 'userType'},
|
||||
{data: 'ipAddress'},
|
||||
{data: 'sessionId'},
|
||||
{data: 'userAgent'},
|
||||
]
|
||||
})
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
104
reports/sessionhistory-report-preview.twig
Normal file
104
reports/sessionhistory-report-preview.twig
Normal file
@@ -0,0 +1,104 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
<button class="btn btn-info XiboRedirectButton" href="{{ url_for("savedreport.view") }}"><i class="fa fa-eye" aria-hidden="true"></i> {% trans "Saved Reports" %}</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<i class="fa fa-list"></i>
|
||||
{{ metadata.title }}
|
||||
<span class="small">({% trans "Generated on: " %}{{ metadata.generatedOn }})</span>
|
||||
<div><span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}">
|
||||
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="sessionHistoryReportPreview" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "User Name" %}</th>
|
||||
<th>{% trans "User ID" %}</th>
|
||||
<th>{% trans "IP Address" %}</th>
|
||||
<th>{% trans "Session ID" %}</th>
|
||||
<th>{% trans "Message" %}</th>
|
||||
<th>{% trans "Object" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
var outputData = {{ table|json_encode|raw }};
|
||||
|
||||
var table = $("#sessionHistoryReportPreview").DataTable({
|
||||
language: dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
paging: false,
|
||||
ordering: false,
|
||||
info: false,
|
||||
order: [[0, 'desc']],
|
||||
searching: false,
|
||||
data: outputData,
|
||||
columns: [
|
||||
{data: 'logDate',},
|
||||
{data: 'userName'},
|
||||
{data: 'userId'},
|
||||
{data: 'ipAddress'},
|
||||
{data: 'sessionHistoryId'},
|
||||
{data: 'message'},
|
||||
{data: 'objectAfter'},
|
||||
]
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', function(e, settings, processing) {
|
||||
dataTableProcessing(e, settings, processing);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
94
reports/sessionhistory-schedule-form-add.twig
Normal file
94
reports/sessionhistory-schedule-form-add.twig
Normal file
@@ -0,0 +1,94 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "form-base.twig" %}
|
||||
{% import "forms.twig" as forms %}
|
||||
|
||||
{% block formTitle %}
|
||||
{% trans "Add Report Schedule" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block formButtons %}
|
||||
{% trans "Cancel" %}, XiboDialogClose()
|
||||
{% trans "Save" %}, $("#reportScheduleAddForm").submit()
|
||||
{% endblock %}
|
||||
|
||||
{% block formHtml %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form id="reportScheduleAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("reportschedule.add") }}">
|
||||
{{ forms.hidden("hiddenFields", hiddenFields) }}
|
||||
{{ forms.hidden("reportName", reportName) }}
|
||||
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{% set helpText %}{% trans "The name for this report schedule" %}{% endset %}
|
||||
{{ forms.input("name", title, "", helpText, "", "required") }}
|
||||
|
||||
{% set title %}{% trans "Frequency" %}{% endset %}
|
||||
{% set helpText %}{% trans "Select how frequently you would like this report to run" %}{% endset %}
|
||||
{% set daily %}{% trans "Daily" %}{% endset %}
|
||||
{% set weekly %}{% trans "Weekly" %}{% endset %}
|
||||
{% set monthly %}{% trans "Monthly" %}{% endset %}
|
||||
{% set yearly %}{% trans "Yearly" %}{% endset %}
|
||||
{% set options = [
|
||||
{ name: "daily", filter: daily },
|
||||
{ name: "weekly", filter: weekly },
|
||||
{ name: "monthly", filter: monthly },
|
||||
{ name: "yearly", filter: yearly },
|
||||
] %}
|
||||
{{ forms.dropdown("filter", "single", title, "", options, "name", "filter", helpText) }}
|
||||
|
||||
{% set title %}{% trans "User" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("user.search") },
|
||||
{ name: "data-search-term", value: "userName" },
|
||||
{ name: "data-id-property", value: "userId" },
|
||||
{ name: "data-text-property", value: "userName" },
|
||||
] %}
|
||||
{{ forms.dropdown("userId", "single", title, "", null, "userId", "userName", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title = "Report Type"|trans %}
|
||||
{% set options = [
|
||||
{ id: 'audit', value: "Audit"|trans },
|
||||
{ id: 'debug', value: "Debug"|trans },
|
||||
] %}
|
||||
{{ forms.dropdown("type", "single", title, "audit", options, "id", "value", helpText) }}
|
||||
|
||||
{% set title %}{% trans "Start Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to run this report. Leave blank to run from the next collection point." %}{% endset %}
|
||||
{{ forms.dateTime("fromDt", title, "", helpText, "starttime-control") }}
|
||||
|
||||
{% set title %}{% trans "End Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to end the schedule. Leave blank to run indefinitely." %}{% endset %}
|
||||
{{ forms.dateTime("toDt", title, "", helpText, "endtime-control") }}
|
||||
|
||||
{% set title %}{% trans "Email addresses" %}{% endset %}
|
||||
{% set helpText %}{% trans "Additional emails separated by a comma." %}{% endset %}
|
||||
{{ forms.inputWithTags("nonusers", title, nonusers, helpText) }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
14
reports/sessionhistory.report
Normal file
14
reports/sessionhistory.report
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "sessionhistory",
|
||||
"description": "Session History",
|
||||
"class": "\\Xibo\\Report\\SessionHistory",
|
||||
"type": "Report",
|
||||
"output_type": "table",
|
||||
"color":"orange",
|
||||
"fa_icon": "fa-th",
|
||||
"sort_order": 5,
|
||||
"hidden": 0,
|
||||
"category": "Audit",
|
||||
"feature": "admin",
|
||||
"adminOnly": 1
|
||||
}
|
||||
48
reports/summary-email-template.twig
Normal file
48
reports/summary-email-template.twig
Normal file
@@ -0,0 +1,48 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "base-report.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span>
|
||||
</div>
|
||||
<p></p>
|
||||
<span>{{ placeholder }}</span>
|
||||
<img src="{{ src|raw }}" >
|
||||
<p></p>
|
||||
|
||||
<table class="saved-report-table">
|
||||
<tr>
|
||||
<th>{% trans "Period" %}</th>
|
||||
<th>{% trans "Duration" %}</th>
|
||||
<th>{% trans "Count" %}</th>
|
||||
</tr>
|
||||
{% for item in tableData %}
|
||||
<tr>
|
||||
<td>{{ item.label }}</td>
|
||||
<td>{{ item.duration }}</td>
|
||||
<td>{{ item.count }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
||||
621
reports/summary-report-form.twig
Normal file
621
reports/summary-report-form.twig
Normal file
@@ -0,0 +1,621 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block title %}{% trans "Report: Summary by Layout, Media or Event" %} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
{% include "report-schedule-buttons.twig" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<span>{% trans "Summary by Layout, Media or Event" %}</span>
|
||||
<span class="fa fa-info-circle widget-title-info px-1" data-toggle="popover" data-trigger="hover" data-placement="bottom" data-content="{% trans "This chart shows an aggregate duration and number of plays for the selected Layout, Media or Event. Please select your Range and Type below. Where the Range crosses period boundaries a new period is generated - i.e 1 week grouped by hourly produces 24 x 7 periods." %}"></span>
|
||||
</div>
|
||||
|
||||
{% include "report-selector.twig" %}
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-refresh-on-form-submit="false">
|
||||
<div class="XiboFilterCustom card bg-light mb-3">
|
||||
<div class="FilterDiv card-body" id="summaryReport">
|
||||
<!-- Form Filter -->
|
||||
<form class="form-inline">
|
||||
{% set title %}{% trans "Range" %}{% endset %}
|
||||
{% set range %}{% trans "Select a range" %}{% endset %}
|
||||
{% set today %}{% trans "Today" %}{% endset %}
|
||||
{% set yesterday %}{% trans "Yesterday" %}{% endset %}
|
||||
{% set thisweek %}{% trans "This Week" %}{% endset %}
|
||||
{% set thismonth %}{% trans "This Month" %}{% endset %}
|
||||
{% set thisyear %}{% trans "This Year" %}{% endset %}
|
||||
{% set lastweek %}{% trans "Last Week" %}{% endset %}
|
||||
{% set lastmonth %}{% trans "Last Month" %}{% endset %}
|
||||
{% set lastyear %}{% trans "Last Year" %}{% endset %}
|
||||
{% set options = [
|
||||
{ filterName: "", reportFilter: range },
|
||||
{ filterName: "today", reportFilter: today },
|
||||
{ filterName: "yesterday", reportFilter: yesterday },
|
||||
{ filterName: "thisweek", reportFilter: thisweek },
|
||||
{ filterName: "thismonth", reportFilter: thismonth },
|
||||
{ filterName: "thisyear", reportFilter: thisyear },
|
||||
{ filterName: "lastweek", reportFilter: lastweek },
|
||||
{ filterName: "lastmonth", reportFilter: lastmonth },
|
||||
{ filterName: "lastyear", reportFilter: lastyear },
|
||||
] %}
|
||||
{{ inline.dropdown("reportFilter", "single", title, "today", options, "filterName", "reportFilter") }}
|
||||
|
||||
{% set title %}{% trans "From Date" %}{% endset %}
|
||||
{{ inline.date("statsFromDt", title, defaults.fromDateOneDay, "", "stats-from-dt", "", "") }}
|
||||
|
||||
{% set title %}{% trans "To Date" %}{% endset %}
|
||||
{{ inline.date("statsToDt", title, defaults.toDate, "", "stats-to-dt", "", "") }}
|
||||
|
||||
{% set title %}{% trans "Group by" %}{% endset %}
|
||||
{% set byday %}{% trans "Day" %}{% endset %}
|
||||
{% set byweek %}{% trans "Week" %}{% endset %}
|
||||
{% set bymonth %}{% trans "Month" %}{% endset %}
|
||||
{% set options = [
|
||||
{ filterName: "byday", groupByFilter: byday },
|
||||
{ filterName: "byweek", groupByFilter: byweek },
|
||||
{ filterName: "bymonth", groupByFilter: bymonth },
|
||||
] %}
|
||||
{{ inline.dropdown("groupByFilter", "single", title, "", options, "filterName", "groupByFilter", "", "group-by-filter") }}
|
||||
|
||||
{% set title %}{% trans "Type" %}{% endset %}
|
||||
{% set layout %}{% trans "Layout" %}{% endset %}
|
||||
{% set media %}{% trans "Media" %}{% endset %}
|
||||
{% set event %}{% trans "Event" %}{% endset %}
|
||||
{% set options = [
|
||||
{ typeid: "layout", type: layout },
|
||||
{ typeid: "media", type: media },
|
||||
{ typeid: "event", type: event },
|
||||
] %}
|
||||
{{ inline.dropdown("type", "single", title, "", options, "typeid", "type") }}
|
||||
|
||||
{% set title %}{% trans "Layout" %} *{% endset %}
|
||||
{% set helpText %}{% trans "This field is required when the Type selected is Layout" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("layout.search") },
|
||||
{ name: "data-search-term", value: "layout" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "layoutId" },
|
||||
{ name: "data-text-property", value: "layout" }
|
||||
] %}
|
||||
|
||||
{{ inline.dropdown("layoutId", "single", title, "", null, "layoutId", "layout", helpText, "pagedSelect layout-select", "", "l", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Media" %} *{% endset %}
|
||||
{% set helpText %}{% trans "This field is required when the Type selected is Media" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("library.search") },
|
||||
{ name: "data-search-term", value: "media" },
|
||||
{ name: "data-id-property", value: "mediaId" },
|
||||
{ name: "data-text-property", value: "name" }
|
||||
] %}
|
||||
{{ inline.dropdown("mediaId", "single", title, "", null, "mediaId", "name", helpText, "pagedSelect media-select", "", "m", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Tag" %} *{% endset %}
|
||||
{% set helpText %}{% trans "This field is required when the Type selected is Event" %}{% endset %}
|
||||
{{ inline.input("eventTag", title, "", helpText, "tag-text") }}
|
||||
|
||||
{% set title %}{% trans "Display" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("display.search") },
|
||||
{ name: "data-search-term", value: "display" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "displayId" },
|
||||
{ name: "data-text-property", value: "display" }
|
||||
] %}
|
||||
{{ inline.dropdown("displayId", "single", title, "", null, "displayId", "display", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("displayGroup.search") },
|
||||
{ name: "data-search-term", value: "displayGroup" },
|
||||
{ name: "data-id-property", value: "displayGroupId" },
|
||||
{ name: "data-text-property", value: "displayGroup" }
|
||||
] %}
|
||||
{{ inline.dropdown("displayGroupId[]", "dropdownmulti", title, "", null, "displayGroupId", "displayGroup", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
<div class="w-100">
|
||||
<a id="applyBtn" class="btn btn-success">
|
||||
<span>{% trans "Apply" %}</span>
|
||||
</a>
|
||||
<span id="imageLoader" class=""></span>
|
||||
<span id="applyWarning" class="text-warning" style="display:none; padding-left: 10px">{% trans "Warning: This may return a lot of data and may take several minutes to process." %}</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Card Header -->
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-tabs card-header-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="chart-tab" data-toggle="tab" href="#chartTab" role="tab"
|
||||
aria-controls="chartTab" aria-selected="true">Chart</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="tabular-tab" data-toggle="tab" href="#tabularTab" role="tab"
|
||||
aria-controls="tabularTab" aria-selected="false">Tabular</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Card Body -->
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<!-- CHART TAB-->
|
||||
<div class="tab-pane active" id="chartTab" role="tabpanel" aria-labelledby="chart-tab">
|
||||
<div class="chart-container" style="height:550px;">
|
||||
<canvas id="canvas" style="clear:both; margin-top:25px;" height="70%"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TABULAR TAB-->
|
||||
<div class="tab-pane show" id="tabularTab" role="tabpanel" aria-labelledby="tabular-tab">
|
||||
<!-- DATATABLE -->
|
||||
<div class="table-container" id="table_wrapper">
|
||||
<table id="summaryTbl"
|
||||
class="table xibo-table table-striped table-full-width"
|
||||
style="width: 100%"
|
||||
data-url="/report/data/summaryReport"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Period" %}</th>
|
||||
<th>{% trans "Duration" %}</th>
|
||||
<th>{% trans "Count" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
$('[data-toggle="popover"]').popover();
|
||||
|
||||
let $report = $("#summaryReport");
|
||||
let $dataTable = $('#summaryTbl'); // Datatable
|
||||
let chart = null; // Chart
|
||||
let result; // XHR get data result
|
||||
|
||||
let imageLoader = $("#imageLoader");
|
||||
let $warning = $("#applyWarning");
|
||||
let $applyBtn = $("#applyBtn");
|
||||
let type = $("#type");
|
||||
let groupByFilter = $("#groupByFilter");
|
||||
let groupByFilterCls = $(".group-by-filter");
|
||||
let mediaSelect =$(".media-select");
|
||||
let layoutSelect =$(".layout-select");
|
||||
let eventTagCls =$(".tag-text");
|
||||
let reportFilter = $("#reportFilter"); // Report Filter
|
||||
|
||||
// Initialize table with empty data
|
||||
let table = $dataTable.DataTable({
|
||||
searching: false,
|
||||
paging: true,
|
||||
bInfo: false,
|
||||
stateSave: true,
|
||||
bDestroy: true,
|
||||
processing: true,
|
||||
ordering: false,
|
||||
data: {},
|
||||
columns: [
|
||||
{
|
||||
data: 'label',
|
||||
'sortable': false,
|
||||
},
|
||||
{
|
||||
data: 'duration',
|
||||
'sortable': false,
|
||||
},
|
||||
{
|
||||
data: 'count',
|
||||
'sortable': false,
|
||||
}
|
||||
],
|
||||
footerCallback: function (row, data, start, end, display) {
|
||||
let api = this.api();
|
||||
|
||||
// Total over all pages
|
||||
let totalDuration = api.column(1).data().map(Number).reduce((a, b) => a + b, 0);
|
||||
let totalNumberPlays = api.column(2).data().map(Number).reduce((a, b) => a + b, 0);
|
||||
let totalDurationPage = api.column(1, { page: 'current'}).data().map(Number)
|
||||
.reduce((a, b) => a + b, 0);
|
||||
let totalNumberPlaysPage = api.column(2, { page: 'current'}).data().map(Number)
|
||||
.reduce((a, b) => a + b, 0);
|
||||
|
||||
// Update footer
|
||||
$(api.column(1).footer())
|
||||
.html(`${totalDurationPage} (${totalDuration} total)`);
|
||||
$(api.column(2).footer())
|
||||
.html(`${Math.floor(totalNumberPlaysPage)} (${Math.floor(totalNumberPlays)} total)`);
|
||||
},
|
||||
});
|
||||
|
||||
// Get Data
|
||||
function getData(url) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
data: $report.find("form").serialize(),
|
||||
success: function success(data) {
|
||||
result = data;
|
||||
$applyBtn.removeClass('disabled');
|
||||
imageLoader.removeClass('fa fa-spinner fa-spin loading-icon');
|
||||
|
||||
// Based on tab load data
|
||||
if ($('.nav-tabs .nav-item a.active').attr("href") === '#tabularTab') {
|
||||
setTabularData(table, result.table);
|
||||
} else {
|
||||
setChartData(result.chart);
|
||||
}
|
||||
},
|
||||
error: function error(xhr, textStatus, _error) {
|
||||
$applyBtn.removeClass('disabled');
|
||||
toastr.error(xhr.responseJSON.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setTabularData(table, data) {
|
||||
table.clear().draw();
|
||||
|
||||
if (Object.keys(data).length > 0) {
|
||||
table.rows.add( data ).draw()
|
||||
}
|
||||
}
|
||||
|
||||
function setChartData(data) {
|
||||
|
||||
setTimeout(function() {
|
||||
$applyBtn.removeClass('disabled');
|
||||
}, 300);
|
||||
|
||||
if (chart !== undefined && chart !== null) {
|
||||
chart.destroy();
|
||||
}
|
||||
|
||||
// Create our chart
|
||||
chart = new Chart($("#canvas"), data);
|
||||
}
|
||||
|
||||
// Tab shown/click load relevant table/chart
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
let activeTab = $(e.target).attr("href")
|
||||
if (result) {
|
||||
if (activeTab === '#tabularTab') {
|
||||
setTabularData(table, result.table);
|
||||
} else {
|
||||
setChartData(result.chart);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Apply
|
||||
$applyBtn.click(function () {
|
||||
$(this).addClass('disabled');
|
||||
imageLoader.addClass('fa fa-spinner fa-spin loading-icon');
|
||||
checkFilterAndApply();
|
||||
|
||||
getData($dataTable.data().url);
|
||||
});
|
||||
|
||||
// If we select a displayId we hide the display group filter
|
||||
$('#displayId').off('change').change( function() {
|
||||
|
||||
let displayId = $('#displayId').val();
|
||||
if (displayId) {
|
||||
$('select[name="displayGroupId[]"] option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().hide();
|
||||
} else {
|
||||
$('#displayId option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().show();
|
||||
}
|
||||
});
|
||||
|
||||
// Calculate the difference of number of days of a selected range
|
||||
let calculateDaysShowHideWarn = function() {
|
||||
|
||||
let fromDt = moment($("#statsFromDt").val());
|
||||
let toDt = moment($("#statsToDt").val());
|
||||
|
||||
let days = toDt.diff(fromDt, 'days');
|
||||
|
||||
$warning.hide();
|
||||
if ( days >= 30) {
|
||||
$warning.show();
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
$("#statsFromDtLink").change( function() {
|
||||
calculateDaysShowHideWarn();
|
||||
});
|
||||
|
||||
$("#statsToDtLink").change( function() {
|
||||
calculateDaysShowHideWarn();
|
||||
});
|
||||
|
||||
// Hide / Show FromDt and ToDt
|
||||
function checkReportFilter(reportFilter) {
|
||||
let value = reportFilter.val();
|
||||
let collectionMonth = [
|
||||
{id: 'byday', description: "{% trans "Day" %}"},
|
||||
{id: 'byweek', description: "{% trans "Week" %}"},
|
||||
];
|
||||
let collectionYear = [
|
||||
{id: 'byday', description: "{% trans "Day" %}"},
|
||||
{id: 'byweek', description: "{% trans "Week" %}"},
|
||||
{id: 'bymonth', description: "{% trans "Month" %}"}
|
||||
];
|
||||
|
||||
if (value === '' || value === undefined) {
|
||||
// show the from date and to date
|
||||
$(".stats-from-dt").show();
|
||||
$(".stats-to-dt").show();
|
||||
|
||||
$("#groupByFilter option").remove();
|
||||
groupByFilterCls.show();
|
||||
$.each(collectionYear, function(index, item) {
|
||||
groupByFilter.append(
|
||||
$("<option></option>")
|
||||
.text(item.description)
|
||||
.val(item.id)
|
||||
);
|
||||
});
|
||||
|
||||
} else {
|
||||
// hide the from date and to date
|
||||
$(".stats-from-dt").hide();
|
||||
$(".stats-to-dt").hide();
|
||||
|
||||
// Group by filter
|
||||
$("#groupByFilter option").remove();
|
||||
groupByFilterCls.show();
|
||||
|
||||
if (value === 'thismonth' || value === 'lastmonth') {
|
||||
|
||||
$.each(collectionMonth, function(index, item) {
|
||||
groupByFilter.append(
|
||||
$("<option></option>")
|
||||
.text(item.description)
|
||||
.val(item.id)
|
||||
);
|
||||
});
|
||||
|
||||
} else if (value === 'thisyear' || value === 'lastyear') {
|
||||
$.each(collectionYear, function(index, item) {
|
||||
groupByFilter.append(
|
||||
$("<option></option>")
|
||||
.text(item.description)
|
||||
.val(item.id)
|
||||
);
|
||||
});
|
||||
groupByFilter.val('bymonth'); //by default
|
||||
|
||||
} else {
|
||||
groupByFilterCls.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let checkFilterAndApply = function() {
|
||||
|
||||
reportFilter.off('change').change( function() {
|
||||
|
||||
let value = reportFilter.val();
|
||||
|
||||
// Hide / Show FromDt and ToDt
|
||||
checkReportFilter(reportFilter);
|
||||
|
||||
// Hide / Show Warning
|
||||
$warning.hide();
|
||||
if ( value === '') {
|
||||
calculateDaysShowHideWarn();
|
||||
} else if ( value === 'thismonth' || value === 'lastmonth' || value === 'thisyear' || value === 'lastyear') {
|
||||
$warning.show();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
type.off('change').change( function() {
|
||||
|
||||
let value = type.val();
|
||||
if (value === 'media') {
|
||||
// show media and clear/hide the layout
|
||||
$("#layoutId").val("");
|
||||
$("#layoutId option").remove();
|
||||
layoutSelect.hide();
|
||||
|
||||
$("#eventTag").val("");
|
||||
eventTagCls.hide();
|
||||
|
||||
mediaSelect.show();
|
||||
|
||||
|
||||
} else if (value === 'layout') {
|
||||
// show layout and clear/hide the media
|
||||
$("#mediaId").val("");
|
||||
$("#mediaId option").remove();
|
||||
mediaSelect.hide();
|
||||
|
||||
$("#eventTag").val("");
|
||||
eventTagCls.hide();
|
||||
|
||||
layoutSelect.show();
|
||||
|
||||
} else if (value === 'event') {
|
||||
// clear/hide the media and layout
|
||||
$("#mediaId").val("");
|
||||
$("#mediaId option").remove();
|
||||
$("#layoutId").val("");
|
||||
$("#layoutId option").remove();
|
||||
mediaSelect.hide();
|
||||
layoutSelect.hide();
|
||||
|
||||
// show tag
|
||||
eventTagCls.show();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// Enable/Disable Schedule Btn
|
||||
let checkEnableSchedule = function() {
|
||||
|
||||
let mediaVal = $("#mediaId").val();
|
||||
let layoutVal = $("#layoutId").val();
|
||||
let eventTagVal = $("#eventTag").val();
|
||||
let reportAddBtn = $("button#reportAddBtn");
|
||||
|
||||
let typeVal = $("#type").val();
|
||||
|
||||
if ( typeVal === 'layout') {
|
||||
if (layoutVal == null) {
|
||||
reportAddBtn.addClass('disabled');
|
||||
reportAddBtn.removeAttr('href');
|
||||
} else {
|
||||
reportAddBtn.removeClass('disabled');
|
||||
reportAddBtn.attr("href", "{{ url_for("reportschedule.add.form") }}?type=" + typeVal + "&layoutId=" + layoutVal + "&reportName=summaryReport" );
|
||||
reportAddBtn.removeAttr('title');
|
||||
|
||||
}
|
||||
} else if ( typeVal === 'media') {
|
||||
if (mediaVal == null) {
|
||||
reportAddBtn.addClass('disabled');
|
||||
reportAddBtn.removeAttr('href');
|
||||
} else {
|
||||
reportAddBtn.removeClass('disabled');
|
||||
reportAddBtn.attr("href", "{{ url_for("reportschedule.add.form") }}?type=" + typeVal + "&mediaId=" + mediaVal + "&reportName=summaryReport" );
|
||||
reportAddBtn.removeAttr('title');
|
||||
|
||||
}
|
||||
} else if ( typeVal === 'event') {
|
||||
if (eventTagVal == null) {
|
||||
reportAddBtn.addClass('disabled');
|
||||
reportAddBtn.removeAttr('href');
|
||||
} else {
|
||||
reportAddBtn.removeClass('disabled');
|
||||
reportAddBtn.attr("href", "{{ url_for("reportschedule.add.form") }}?type=" + typeVal + "&eventTag=" + eventTagVal + "&reportName=summaryReport" );
|
||||
reportAddBtn.removeAttr('title');
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
type.val('layout');
|
||||
mediaSelect.hide();
|
||||
eventTagCls.hide();
|
||||
|
||||
// reportFilter.val('');
|
||||
// groupByFilter.val('byday');
|
||||
|
||||
checkReportFilter(reportFilter);
|
||||
checkFilterAndApply();
|
||||
|
||||
$applyBtn.addClass('disabled');
|
||||
checkEnableSchedule();
|
||||
|
||||
// Bind to form change
|
||||
$report.on('change', function() {
|
||||
checkEnableSchedule();
|
||||
|
||||
let layoutVal = $("#layoutId").val();
|
||||
let mediaVal = $("#mediaId").val();
|
||||
let eventVal = $("#eventTag").val();
|
||||
|
||||
if ((layoutVal === null || layoutVal === '' || layoutVal === undefined) &&
|
||||
(mediaVal === null || mediaVal === '' || mediaVal === undefined) &&
|
||||
(eventVal === null || eventVal === '' || eventVal === undefined) ) {
|
||||
|
||||
$applyBtn.addClass('disabled');
|
||||
|
||||
} else {
|
||||
$applyBtn.removeClass('disabled');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function summaryScheduleCallback(dialog) {
|
||||
|
||||
// If we select a displayId we hide the display group filter
|
||||
$('#reportScheduleAddForm #displayId').off('change').change( function() {
|
||||
|
||||
let displayId = $('#reportScheduleAddForm #displayId').val();
|
||||
if (displayId) {
|
||||
$('select[name="displayGroupId[]"] option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().parent().hide();
|
||||
} else {
|
||||
$('#reportScheduleAddForm #displayId option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().parent().show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
97
reports/summary-report-preview.twig
Normal file
97
reports/summary-report-preview.twig
Normal file
@@ -0,0 +1,97 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2019 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
<button class="btn btn-info XiboRedirectButton" href="{{ url_for("savedreport.view") }}"><i class="fa fa-eye" aria-hidden="true"></i> {% trans "Saved Reports" %}</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<i class="fa fa-list"></i>
|
||||
{{ metadata.title }}
|
||||
<span class="small">({% trans "Generated on: " %}{{ metadata.generatedOn }})</span>
|
||||
<div><span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}">
|
||||
|
||||
<div class="XiboData card pt-3">
|
||||
<canvas id="canvas" style="clear:both; margin-top:25px;"></canvas>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="stats" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Period" %}</th>
|
||||
<th>{% trans "Duration" %}</th>
|
||||
<th>{% trans "Count" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
$(document).ready(function() {
|
||||
|
||||
let reportChart = new Chart($("#canvas"), {{ chart|json_encode|raw }});
|
||||
|
||||
let outputData = {{ table|json_encode|raw }};
|
||||
|
||||
// Grid
|
||||
let table = $("#stats").DataTable({
|
||||
"searching": false,
|
||||
"paging": true,
|
||||
"ordering": false,
|
||||
"data": outputData,
|
||||
"columns": [
|
||||
{ "data": 'label' },
|
||||
{ "data": 'duration' },
|
||||
{ "data": 'count' },
|
||||
]
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
98
reports/summary-report-schedule-form-add.twig
Normal file
98
reports/summary-report-schedule-form-add.twig
Normal file
@@ -0,0 +1,98 @@
|
||||
{#
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#}
|
||||
{% extends "form-base.twig" %}
|
||||
{% import "forms.twig" as forms %}
|
||||
|
||||
{% block formTitle %}
|
||||
{{ formTitle }}
|
||||
{% endblock %}
|
||||
|
||||
{% block formButtons %}
|
||||
{% trans "Cancel" %}, XiboDialogClose()
|
||||
{% trans "Save" %}, $("#reportScheduleAddForm").submit()
|
||||
{% endblock %}
|
||||
|
||||
{% block callBack %}summaryScheduleCallback{% endblock %}
|
||||
|
||||
{% block formHtml %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form id="reportScheduleAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("reportschedule.add") }}">
|
||||
|
||||
{{ forms.hidden("hiddenFields", hiddenFields) }}
|
||||
{{ forms.hidden("reportName", reportName) }}
|
||||
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{% set helpText %}{% trans "The name for this report schedule" %}{% endset %}
|
||||
{{ forms.input("name", title, "", helpText, "", "required") }}
|
||||
|
||||
{% set title %}{% trans "Frequency" %}{% endset %}
|
||||
{% set helpText %}{% trans "Select how frequently you would like this report to run" %}{% endset %}
|
||||
{{ forms.dropdown("filter", "single", title, "", filters, "filter", "name", helpText) }}
|
||||
|
||||
{% set title %}{% trans "Display" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("display.search") },
|
||||
{ name: "data-search-term", value: "display" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "displayId" },
|
||||
{ name: "data-text-property", value: "display" }
|
||||
] %}
|
||||
{{ forms.dropdown("displayId", "single", title, "", null, "displayId", "display", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("displayGroup.search") },
|
||||
{ name: "data-search-term", value: "displayGroup" },
|
||||
{ name: "data-id-property", value: "displayGroupId" },
|
||||
{ name: "data-text-property", value: "displayGroup" }
|
||||
] %}
|
||||
{{ forms.dropdown("displayGroupId[]", "dropdownmulti", title, "", null, "displayGroupId", "displayGroup", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Start Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to run this report. Leave blank to run from the next collection point." %}{% endset %}
|
||||
{{ forms.dateTime("fromDt", title, "", helpText, "starttime-control") }}
|
||||
|
||||
{% set title %}{% trans "End Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to end the schedule. Leave blank to run indefinitely." %}{% endset %}
|
||||
{{ forms.dateTime("toDt", title, "", helpText, "endtime-control") }}
|
||||
|
||||
{% set title %}{% trans "Should an email be sent?" %}{% endset %}
|
||||
{{ forms.checkbox("sendEmail", title, sendEmail) }}
|
||||
|
||||
{% set title %}{% trans "Email addresses" %}{% endset %}
|
||||
{% set helpText %}{% trans "Additional emails separated by a comma." %}{% endset %}
|
||||
{{ forms.inputWithTags("nonusers", title, nonusers, helpText) }}
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
14
reports/summary.report
Normal file
14
reports/summary.report
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "summaryReport",
|
||||
"description": "Chart: Summary by Layout, Media or Event",
|
||||
"class": "\\Xibo\\Report\\SummaryReport",
|
||||
"type": "Chart",
|
||||
"output_type": "both",
|
||||
"color":"red",
|
||||
"fa_icon": "fa-bar-chart",
|
||||
"sort_order": 2,
|
||||
"hidden": 0,
|
||||
"category": "Proof of Play",
|
||||
"feature": "proof-of-play",
|
||||
"adminOnly": 0
|
||||
}
|
||||
66
reports/timeconnected-email-template.twig
Normal file
66
reports/timeconnected-email-template.twig
Normal file
@@ -0,0 +1,66 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "base-report.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span>
|
||||
</div>
|
||||
<span class="small" style="background-color: #d2d2d2; width:150px;">Grey is disconnected %</span>
|
||||
<span class="small" style="background-color: #5cb85c; width:150px;">Green is connected %</span>
|
||||
<p></p>
|
||||
|
||||
<table class=" ">
|
||||
<tbody>
|
||||
|
||||
{% set displays = tableData.displays %}
|
||||
{% set timeConnected = tableData.timeConnected %}
|
||||
|
||||
{% for key,displayStat in timeConnected %}
|
||||
|
||||
<tr>
|
||||
{% for option in displays[key] %}
|
||||
<th colspan="2">{{ option }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
|
||||
{% for item in displayStat %}
|
||||
<tr>
|
||||
{% for displayData in item %}
|
||||
{% set percent = "0%" %}
|
||||
{% if displayData.percent > 0 %}
|
||||
{% set percent = displayData.percent~"%" %}
|
||||
{% endif %}
|
||||
|
||||
<td style="width:180px; text-align: right">{{ displayData.label }} - {{ displayData.percent }}%</td>
|
||||
<td>
|
||||
<progress id="file" value="{{ displayData.percent }}" max="100"> {{ displayData.percent }}% </progress>
|
||||
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
220
reports/timeconnected-report-form.twig
Normal file
220
reports/timeconnected-report-form.twig
Normal file
@@ -0,0 +1,220 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2020 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block title %}{% trans "Report: Time Connected" %} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<style>
|
||||
table {
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 20px 0;
|
||||
padding: 20px 0 0;
|
||||
}
|
||||
th {
|
||||
text-align: center;
|
||||
}
|
||||
td.display-label {
|
||||
padding-right: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
td.display-percent {
|
||||
border: 1px solid #408640;
|
||||
}
|
||||
.note-box {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
float:left;
|
||||
}
|
||||
</style>
|
||||
{% include "report-schedule-buttons.twig" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<span>{% trans "Time Connected" %}</span>
|
||||
</div>
|
||||
|
||||
{% include "report-selector.twig" %}
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-refresh-on-form-submit="false">
|
||||
<div class="pull-right p-3">
|
||||
<div>
|
||||
<div class="note-box" style="background-color: #03a9f4;"></div>
|
||||
<div style="padding-left: 20px">Blue is disconnected %</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="note-box" style="background-color: #5cb85c;"></div>
|
||||
<div style="padding-left: 20px">Green is connected %</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="XiboFilterCustom card bg-light mb-3">
|
||||
<div class="FilterDiv card-body" id="timeconnected">
|
||||
<form class="form-inline">
|
||||
{% set title %}{% trans "Range" %}{% endset %}
|
||||
{{ inline.dateRangeFilter("reportFilter", title, "", "", "", "", "") }}
|
||||
|
||||
{% set title %}{% trans "Group by" %}{% endset %}
|
||||
{% set byhour %}{% trans "Hour" %}{% endset %}
|
||||
{% set bydayofmonth %}{% trans "Day of month" %}{% endset %}
|
||||
{% set options = [
|
||||
{ filterName: "byhour", groupByFilter: byhour },
|
||||
{ filterName: "bydayofmonth", groupByFilter: bydayofmonth },
|
||||
] %}
|
||||
{{ inline.dropdown("groupByFilter", "single", title, "", options, "filterName", "groupByFilter", "", "group-by-filter") }}
|
||||
|
||||
{% set title %}{% trans "Display/Display Groups" %}{% endset %}
|
||||
{% set helpText %}{% trans "Please select one or more displays / groups for this notification to be shown on - Layouts will need the notification widget." %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" }
|
||||
] %}
|
||||
{% set transGroups %}{% trans "Groups" %}{% endset %}
|
||||
{% set transDisplays %}{% trans "Display" %}{% endset %}
|
||||
{% set optionGroups = [
|
||||
{id: "group", label: transGroups},
|
||||
{id: "display", label: transDisplays}
|
||||
] %}
|
||||
{{ inline.dropdown("displayGroupId[]", "dropdownmulti", title, displayGroupIds, {group: defaults.displayGroups, display: defaults.displays}, "displayGroupId", "displayGroup", helpText, "selectPicker", "", "", "", attributes, optionGroups) }}
|
||||
|
||||
<div class="w-100">
|
||||
<a id="applyBtn" class="btn btn-success">
|
||||
<span>{% trans "Apply" %}</span>
|
||||
</a>
|
||||
<span id="imageLoader" class=""></span>
|
||||
<span id="applyWarning" class="text-warning" style="display:none; padding-left: 10px">{% trans "Warning: This may return a lot of data and may take several minutes to process." %}</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="records_table"></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
|
||||
$(function () {
|
||||
$('[data-toggle="popover"]').popover();
|
||||
});
|
||||
|
||||
let imageLoader = $("#imageLoader");
|
||||
|
||||
function setReport() {
|
||||
$("#records_table").empty();
|
||||
$.ajax({
|
||||
type: "get",
|
||||
url: "{{ url_for("report.data", {name: reportName}) }}",
|
||||
cache: false,
|
||||
dataType: "json",
|
||||
data: $("#timeconnected").find("form").serialize(),
|
||||
success: function(response) {
|
||||
|
||||
setTimeout(function() {
|
||||
$("#applyBtn").removeClass('disabled');
|
||||
imageLoader.removeClass('fa fa-spinner fa-spin loading-icon');
|
||||
}, 300);
|
||||
|
||||
|
||||
$('.XiboData').find(".form-error").remove();
|
||||
if (response.success === false) {
|
||||
$('.XiboData').append('<div class="alert alert-danger form-error">'+ response.message +'</div>');
|
||||
return;
|
||||
}
|
||||
|
||||
$.each(response.table.timeConnected, function(index, displayStat) {
|
||||
var $displayHeader = $('<tr>');
|
||||
$.each(response.table.displays[index], function(i, displayName) {
|
||||
$displayHeader.append($('<th colspan="2">').text(displayName));
|
||||
$displayHeader.appendTo('#records_table').html();
|
||||
});
|
||||
|
||||
// Display Statistics
|
||||
$.each(displayStat, function(periodId, item) {
|
||||
var $tr = $('<tr>');
|
||||
|
||||
$.each(item, function(displayId, displayData) {
|
||||
|
||||
var percent= '';
|
||||
if (displayData.percent > 0) {
|
||||
percent = Math.round((displayData.percent + Number.EPSILON) * 100) / 100 + "%";
|
||||
}
|
||||
|
||||
var $label = $('<td class="display-label" style="width:300px">').text(displayData.label);
|
||||
var $percentage = $('<td class="display-percent" style="width:300px">').css({"background-color": "#03a9f4", "color": "white"}).append(
|
||||
$('<div>').css({"background-color": "#5cb85c", "width": percent}).text(percent)
|
||||
);
|
||||
|
||||
$tr.append($label);
|
||||
$tr.append($percentage);
|
||||
});
|
||||
|
||||
$tr.appendTo('#records_table').html();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
// Init
|
||||
var applyBtn = $("#applyBtn");
|
||||
|
||||
// Enable/Disable Schedule Btn
|
||||
var checkEnableSchedule = function() {
|
||||
|
||||
// Schedule button enable/disable - start
|
||||
var anchorReportAddBtn = $("button#reportAddBtn");
|
||||
|
||||
anchorReportAddBtn.attr("href", "{{ url_for("reportschedule.add.form") }}?reportName=timeconnected" );
|
||||
};
|
||||
|
||||
|
||||
// Report Filter
|
||||
checkEnableSchedule();
|
||||
|
||||
// Bind to form change
|
||||
$("#timeconnected").on('change', function() {
|
||||
checkEnableSchedule();
|
||||
});
|
||||
|
||||
// Apply
|
||||
applyBtn.click(function () {
|
||||
$(this).addClass('disabled');
|
||||
imageLoader.addClass('fa fa-spinner fa-spin loading-icon');
|
||||
setReport();
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
114
reports/timeconnected-report-preview.twig
Normal file
114
reports/timeconnected-report-preview.twig
Normal file
@@ -0,0 +1,114 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2020 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
<button class="btn btn-info XiboRedirectButton" href="{{ url_for("savedreport.view") }}"><i class="fa fa-eye" aria-hidden="true"></i> {% trans "Saved Reports" %}</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<i class="fa fa-list"></i>
|
||||
{{ metadata.title }}
|
||||
<span class="small">({% trans "Generated on: " %}{{ metadata.generatedOn }})</span>
|
||||
<div><span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
table {
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 20px 0;
|
||||
padding: 20px 0 0;
|
||||
}
|
||||
th {
|
||||
text-align: center;
|
||||
}
|
||||
td.display-label {
|
||||
padding-right: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
td.display-percent {
|
||||
border: 1px solid #408640;
|
||||
}
|
||||
.note-box {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
float:left;
|
||||
}
|
||||
</style>
|
||||
<div class="widget-body">
|
||||
<div class="pull-right">
|
||||
<div>
|
||||
<div class="note-box" style="background-color: #03a9f4;"></div>
|
||||
<div style="padding-left: 20px">Blue is disconnected %</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="note-box" style="background-color: #5cb85c;"></div>
|
||||
<div style="padding-left: 20px">Green is Connected %</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="XiboGrid" id="{{ random() }}" style="margin-top: 40px;">
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="" class="">
|
||||
<tbody>
|
||||
{% for key,displayStat in table.timeConnected %}
|
||||
<tr>
|
||||
{% for option in table.displays[key] %}
|
||||
<th colspan="2">{{ option }} </th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% for item in displayStat %}
|
||||
<tr>
|
||||
{% for displayData in item %}
|
||||
{% set percent = "0%" %}
|
||||
{% if displayData.percent > 0 %}
|
||||
{% set percent = displayData.percent~"%" %}
|
||||
{% endif %}
|
||||
|
||||
<td class="display-label" style="width:300px">{{ displayData.label }}</td>
|
||||
<td class="display-percent" style="width:300px; background-color:#03a9f4; color:white">
|
||||
<div style="background-color:#5cb85c; width:{{ percent }}">
|
||||
{% if displayData.percent > 0 %}{{ displayData.percent }}%{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
101
reports/timeconnected-schedule-form-add.twig
Normal file
101
reports/timeconnected-schedule-form-add.twig
Normal file
@@ -0,0 +1,101 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "form-base.twig" %}
|
||||
{% import "forms.twig" as forms %}
|
||||
|
||||
{% block formTitle %}
|
||||
{% trans "Add Report Schedule" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block formButtons %}
|
||||
{% trans "Cancel" %}, XiboDialogClose()
|
||||
{% trans "Save" %}, $("#reportScheduleAddForm").submit()
|
||||
{% endblock %}
|
||||
|
||||
{% block formHtml %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form id="reportScheduleAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("reportschedule.add") }}">
|
||||
|
||||
{{ forms.hidden("hiddenFields", hiddenFields) }}
|
||||
{{ forms.hidden("reportName", reportName) }}
|
||||
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{% set helpText %}{% trans "The name for this report schedule" %}{% endset %}
|
||||
{{ forms.input("name", title, "", helpText, "", "required") }}
|
||||
|
||||
{% set title %}{% trans "Frequency" %}{% endset %}
|
||||
{% set helpText %}{% trans "Select how frequently you would like this report to run" %}{% endset %}
|
||||
{% set daily %}{% trans "Daily" %}{% endset %}
|
||||
{% set weekly %}{% trans "Weekly" %}{% endset %}
|
||||
{% set monthly %}{% trans "Monthly" %}{% endset %}
|
||||
{% set yearly %}{% trans "Yearly" %}{% endset %}
|
||||
{% set options = [
|
||||
{ name: "daily", filter: daily },
|
||||
{ name: "weekly", filter: weekly },
|
||||
{ name: "monthly", filter: monthly },
|
||||
{ name: "yearly", filter: yearly },
|
||||
] %}
|
||||
{{ forms.dropdown("filter", "single", title, "", options, "name", "filter", helpText) }}
|
||||
|
||||
{% set title %}{% trans "Group by" %}{% endset %}
|
||||
{% set byhour %}{% trans "Hour" %}{% endset %}
|
||||
{% set bydayofmonth %}{% trans "Day of month" %}{% endset %}
|
||||
{% set options = [
|
||||
{ filterName: "byhour", groupByFilter: byhour },
|
||||
{ filterName: "bydayofmonth", groupByFilter: bydayofmonth },
|
||||
] %}
|
||||
{{ forms.dropdown("groupByFilter", "single", title, "", options, "filterName", "groupByFilter", "", "group-by-filter") }}
|
||||
|
||||
{% set title %}{% trans "Display/Display Groups" %}{% endset %}
|
||||
{% set helpText %}{% trans "Please select one or more displays / groups for this notification to be shown on - Layouts will need the notification widget." %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" }
|
||||
] %}
|
||||
{% set transGroups %}{% trans "Groups" %}{% endset %}
|
||||
{% set transDisplays %}{% trans "Display" %}{% endset %}
|
||||
{% set optionGroups = [
|
||||
{id: "group", label: transGroups},
|
||||
{id: "display", label: transDisplays}
|
||||
] %}
|
||||
{{ forms.dropdown("displayGroupIds[]", "dropdownmulti", title, displayGroupIds, {group: displayGroups, display: displays}, "displayGroupId", "displayGroup", helpText, "selectPicker", "", "", "", attributes, optionGroups) }}
|
||||
|
||||
{% set title %}{% trans "Start Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to run this report. Leave blank to run from the next collection point." %}{% endset %}
|
||||
{{ forms.dateTime("fromDt", title, "", helpText, "starttime-control") }}
|
||||
|
||||
{% set title %}{% trans "End Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to end the schedule. Leave blank to run indefinitely." %}{% endset %}
|
||||
{{ forms.dateTime("toDt", title, "", helpText, "endtime-control") }}
|
||||
|
||||
{% set title %}{% trans "Should an email be sent?" %}{% endset %}
|
||||
{{ forms.checkbox("sendEmail", title, sendEmail) }}
|
||||
|
||||
{% set title %}{% trans "Email addresses" %}{% endset %}
|
||||
{% set helpText %}{% trans "Additional emails separated by a comma." %}{% endset %}
|
||||
{{ forms.inputWithTags("nonusers", title, nonusers, helpText) }}
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
14
reports/timeconnected.report
Normal file
14
reports/timeconnected.report
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "timeconnected",
|
||||
"description": "Time Connected",
|
||||
"class": "\\Xibo\\Report\\TimeConnected",
|
||||
"type": "Report",
|
||||
"output_type": "table",
|
||||
"color":"orange",
|
||||
"fa_icon": "fa-th",
|
||||
"sort_order": 4,
|
||||
"hidden": 0,
|
||||
"category": "Display",
|
||||
"feature": "displays.reporting",
|
||||
"adminOnly": 0
|
||||
}
|
||||
52
reports/timedisconnectedsummary-email-template.twig
Normal file
52
reports/timedisconnectedsummary-email-template.twig
Normal file
@@ -0,0 +1,52 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "base-report.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span>
|
||||
</div>
|
||||
<p></p>
|
||||
|
||||
<table class="saved-report-table">
|
||||
<tr>
|
||||
<th>{% trans "Display ID" %}</th>
|
||||
<th>{% trans "Display" %}</th>
|
||||
<th>{% trans "Time Disconnected" %}</th>
|
||||
<th>{% trans "Time Connected" %}</th>
|
||||
<th>{% trans "Units" %}</th>
|
||||
</tr>
|
||||
{% for item in tableData %}
|
||||
<tr>
|
||||
<td>{{ item.displayId }}</td>
|
||||
<td>{{ item.display }}</td>
|
||||
<td>{{ item.timeDisconnected }}</td>
|
||||
<td>{{ item.timeConnected }}</td>
|
||||
<td>{{ item.postUnits }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<br/>
|
||||
<span>{{ placeholder }}</span>
|
||||
<img src="{{ src|raw }}" >
|
||||
{% endblock %}
|
||||
339
reports/timedisconnectedsummary-report-form.twig
Normal file
339
reports/timedisconnectedsummary-report-form.twig
Normal file
@@ -0,0 +1,339 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2020 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block title %}{% trans "Report: Time Connected Summary" %} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
{% include "report-schedule-buttons.twig" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<span>{% trans "Time Connected Summary" %}</span>
|
||||
</div>
|
||||
|
||||
{% include "report-selector.twig" %}
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-refresh-on-form-submit="false">
|
||||
<div class="XiboFilterCustom">
|
||||
<div class="FilterDiv card-body" id="timeDisconnectedReport">
|
||||
<form class="form-inline">
|
||||
{% set title %}{% trans "Range" %}{% endset %}
|
||||
{{ inline.dateRangeFilter("reportFilter", title, "", "", "", "", "") }}
|
||||
|
||||
{% set title %}{% trans "Group By" %}{% endset %}
|
||||
{% set options = [
|
||||
{ id: "display", name: "Display"|trans },
|
||||
{ id: "displayGroup", name: "Display Group"|trans },
|
||||
] %}
|
||||
{{ inline.dropdown("groupBy", "single", title, "", options, "id", "name", "") }}
|
||||
|
||||
{% set title %}{% trans "Display" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("display.search") },
|
||||
{ name: "data-search-term", value: "display" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "displayId" },
|
||||
{ name: "data-text-property", value: "display" }
|
||||
] %}
|
||||
{{ inline.dropdown("displayId", "single", title, "", null, "displayId", "display", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "200px" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("displayGroup.search") },
|
||||
{ name: "data-search-term", value: "displayGroup" },
|
||||
{ name: "data-id-property", value: "displayGroupId" },
|
||||
{ name: "data-text-property", value: "displayGroup" }
|
||||
] %}
|
||||
{{ inline.dropdown("displayGroupId[]", "dropdownmulti", title, "", null, "displayGroupId", "displayGroup", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% if currentUser.featureEnabled("tag.tagging") %}
|
||||
{% set title %}{% trans "Tags" %}{% endset %}
|
||||
{% set exactTagTitle %}{% trans "Exact match?" %}{% endset %}
|
||||
{% set logicalOperatorTitle %}{% trans "When filtering by multiple Tags, which logical operator should be used?" %}{% endset %}
|
||||
{% set helpText %}{% trans "A comma separated list of tags to filter by. Enter --no-tag to see items without tags." %}{% endset %}
|
||||
{{ inline.inputWithTags("tags", title, null, helpText, null, null, null, "exactTags", exactTagTitle, logicalOperatorTitle) }}
|
||||
{% endif %}
|
||||
|
||||
{% set title %}{% trans "Only show currently logged in?" %}{% endset %}
|
||||
{{ inline.checkbox("onlyLoggedIn", title) }}
|
||||
|
||||
<div class="w-100">
|
||||
<a id="applyBtn" class="btn btn-success">
|
||||
<span>{% trans "Apply" %}</span>
|
||||
</a>
|
||||
<span id="imageLoader" class=""></span>
|
||||
<span id="applyWarning" class="text-warning" style="display:none; padding-left: 10px">{% trans "Warning: This may return a lot of data and may take several minutes to process." %}</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card Header -->
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-tabs card-header-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="chart-tab" data-toggle="tab" href="#chartTab" role="tab"
|
||||
aria-controls="chartTab" aria-selected="true">Chart</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="tabular-tab" data-toggle="tab" href="#tabularTab" role="tab"
|
||||
aria-controls="tabularTab" aria-selected="false">Tabular</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Card Body -->
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<!-- CHART TAB-->
|
||||
<div class="tab-pane active" id="chartTab" role="tabpanel" aria-labelledby="chart-tab">
|
||||
<div class="chart-container" style="height:550px;">
|
||||
<canvas id="canvas" style="clear:both; margin-top:25px;" height="70%"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TABULAR TAB-->
|
||||
<div class="tab-pane show" id="tabularTab" role="tabpanel" aria-labelledby="tabular-tab">
|
||||
<!-- DATATABLE -->
|
||||
<div class="table-container" id="table_wrapper">
|
||||
<table id="timeDisconnectedTbl"
|
||||
class="table xibo-table table-striped table-full-width"
|
||||
style="width: 100%"
|
||||
data-url="/report/data/timedisconnectedsummary"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Display ID" %}</th>
|
||||
<th>{% trans "Display" %}</th>
|
||||
<th>{% trans "Display Group ID" %}</th>
|
||||
<th>{% trans "Display Group" %}</th>
|
||||
<th>{% trans "Time Disconnected" %}</th>
|
||||
<th>{% trans "Time Connected" %}</th>
|
||||
<th>{% trans "Average Time Disconnected" %}</th>
|
||||
<th>{% trans "Average Time Connected" %}</th>
|
||||
<th>{% trans "Availability" %}</th>
|
||||
<th>{% trans "Units" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
$('[data-toggle="popover"]').popover();
|
||||
|
||||
let $report = $("#timeDisconnectedReport");
|
||||
let $dataTable = $('#timeDisconnectedTbl'); // Datatable
|
||||
let chart = null;
|
||||
let result; // XHR get data result
|
||||
|
||||
let imageLoader = $("#imageLoader");
|
||||
let $warning = $("#applyWarning");
|
||||
let $applyBtn = $("#applyBtn");
|
||||
|
||||
// Initialize table with empty data
|
||||
let table = $dataTable.DataTable({
|
||||
language: dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
searching: false,
|
||||
paging: true,
|
||||
bInfo: false,
|
||||
stateSave: true,
|
||||
bDestroy: true,
|
||||
processing: true,
|
||||
ordering: false,
|
||||
data: {},
|
||||
columns: [
|
||||
{"data": "displayId"},
|
||||
{"data": "display"},
|
||||
{"data": "displayGroupId"},
|
||||
{"data": "displayGroup"},
|
||||
{"data": "timeDisconnected", "sortable": false},
|
||||
{"data": "timeConnected", "sortable": false},
|
||||
{"data": "avgTimeDisconnected", "sortable": false},
|
||||
{"data": "avgTimeConnected", "sortable": false},
|
||||
{"data": "availabilityPercentage", "sortable": false},
|
||||
{"data": "postUnits", "sortable": false},
|
||||
],
|
||||
drawCallback: function () {
|
||||
if ($('select[name="groupBy"]').val() === 'displayGroup') {
|
||||
// Hide 'Display ID and Display' columns
|
||||
$(this.api().columns([0, 1]).visible(false));
|
||||
$(this.api().columns([2, 3, 6, 7]).visible(true));
|
||||
} else {
|
||||
// Hide 'Display Group ID, Display Group, Avg Time Connected/Disconnected' columns
|
||||
$(this.api().columns([2, 3, 6, 7]).visible(false));
|
||||
$(this.api().columns([0, 1]).visible(true));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Get Data
|
||||
function getData(url) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
data: $report.find("form").serialize(),
|
||||
success: function success(data) {
|
||||
result = data;
|
||||
$applyBtn.removeClass('disabled');
|
||||
imageLoader.removeClass('fa fa-spinner fa-spin loading-icon');
|
||||
|
||||
// Based on tab load data
|
||||
if ($('.nav-tabs .nav-item a.active').attr("href") === '#tabularTab') {
|
||||
setTabularData(table, result.table);
|
||||
} else {
|
||||
setChartData(result.chart);
|
||||
}
|
||||
},
|
||||
error: function error(xhr, textStatus, _error) {
|
||||
$applyBtn.removeClass('disabled');
|
||||
toastr.error(xhr.responseJSON.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setTabularData(table, data) {
|
||||
table.clear().draw();
|
||||
|
||||
if (Object.keys(data).length > 0) {
|
||||
table.rows.add( data ).draw()
|
||||
}
|
||||
}
|
||||
|
||||
function setChartData(data) {
|
||||
|
||||
setTimeout(function() {
|
||||
$applyBtn.removeClass('disabled');
|
||||
}, 300);
|
||||
|
||||
if (chart !== undefined && chart !== null) {
|
||||
chart.destroy();
|
||||
}
|
||||
|
||||
// Create our chart
|
||||
chart = new Chart($("#canvas"), data);
|
||||
}
|
||||
|
||||
// Tab shown/click load relevant table/chart
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
let activeTab = $(e.target).attr("href")
|
||||
if (result) {
|
||||
if (activeTab === '#tabularTab') {
|
||||
setTabularData(table, result.table);
|
||||
} else {
|
||||
setChartData(result.chart);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Apply
|
||||
$applyBtn.click(function () {
|
||||
$(this).addClass('disabled');
|
||||
imageLoader.addClass('fa fa-spinner fa-spin loading-icon');
|
||||
getData($dataTable.data().url);
|
||||
});
|
||||
|
||||
// If we select a displayId we hide the display group filter
|
||||
$('#displayId').off('change').change( function() {
|
||||
|
||||
let displayId = $('#displayId').val();
|
||||
if (displayId) {
|
||||
$('select[name="displayGroupId[]"] option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().hide();
|
||||
$('select[name="groupBy[]"] option').remove();
|
||||
$('select[name="groupBy"]').parent().hide();
|
||||
} else {
|
||||
$('#displayId option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().show();
|
||||
$('select[name="groupBy"]').parent().show();
|
||||
}
|
||||
});
|
||||
|
||||
$("#refreshGrid").click(function () {
|
||||
table.ajax.reload();
|
||||
});
|
||||
});
|
||||
|
||||
function timeDisconnectedScheduleCallback(dialog) {
|
||||
|
||||
// If we select a displayId we hide the display group filter
|
||||
$('#reportScheduleAddForm #displayId').off('change').change( function() {
|
||||
|
||||
let displayId = $('#reportScheduleAddForm #displayId').val();
|
||||
if (displayId) {
|
||||
$('select[name="displayGroupId[]"] option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().parent().hide();
|
||||
$('select[name="groupBy[]"] option').remove();
|
||||
$('select[name="groupBy"]').parent().hide();
|
||||
} else {
|
||||
$('#reportScheduleAddForm #displayId option').remove();
|
||||
$('select[name="displayGroupId[]"]').next(".select2-container").parent().parent().show();
|
||||
$('select[name="groupBy"]').parent().show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
114
reports/timedisconnectedsummary-report-preview.twig
Normal file
114
reports/timedisconnectedsummary-report-preview.twig
Normal file
@@ -0,0 +1,114 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2020 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
<button class="btn btn-info XiboRedirectButton" href="{{ url_for("savedreport.view") }}"><i class="fa fa-eye" aria-hidden="true"></i> {% trans "Saved Reports" %}</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-title">
|
||||
<i class="fa fa-list"></i>
|
||||
{{ metadata.title }}
|
||||
<span class="small">({% trans "Generated on: " %}{{ metadata.generatedOn }})</span>
|
||||
<div><span class="small">{% trans "From" %} {{ metadata.periodStart }} {% trans "To" %} {{ metadata.periodEnd }}</span></div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}">
|
||||
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="stats" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Display ID" %}</th>
|
||||
<th>{% trans "Display" %}</th>
|
||||
<th>{% trans "Time Disconnected" %}</th>
|
||||
<th>{% trans "Time Connected" %}</th>
|
||||
<th>{% trans "Units" %}</th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget">
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}">
|
||||
<div class="XiboData" style="margin-top: 25px;">
|
||||
<canvas id="availabilityChart" style="clear:both; margin-top:25px;" height="330"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
var outputData = {{ table|json_encode|raw }};
|
||||
|
||||
var table = $("#stats").DataTable({
|
||||
"language": dataTablesLanguage,
|
||||
"dom": dataTablesTemplate,
|
||||
"paging": false,
|
||||
"ordering": false,
|
||||
"info": false,
|
||||
"order": [[1, "asc"]],
|
||||
"searching": false,
|
||||
data: outputData,
|
||||
"columns": [
|
||||
{"data": "displayId"},
|
||||
{"data": "display"},
|
||||
{"data": "timeDisconnected", "sortable": false},
|
||||
{"data": "timeConnected", "sortable": false},
|
||||
{"data": "postUnits", "sortable": false}
|
||||
]
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', function(e, settings, processing) {
|
||||
dataTableProcessing(e, settings, processing);
|
||||
});
|
||||
|
||||
var availabilityChart = new Chart($("#availabilityChart"), {{ chart|json_encode|raw }});
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
108
reports/timedisconnectedsummary-schedule-form-add.twig
Normal file
108
reports/timedisconnectedsummary-schedule-form-add.twig
Normal file
@@ -0,0 +1,108 @@
|
||||
{#
|
||||
/**
|
||||
* Copyright (C) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#}
|
||||
{% extends "form-base.twig" %}
|
||||
{% import "forms.twig" as forms %}
|
||||
|
||||
{% block formTitle %}
|
||||
{% trans "Add Report Schedule" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block formButtons %}
|
||||
{% trans "Cancel" %}, XiboDialogClose()
|
||||
{% trans "Save" %}, $("#reportScheduleAddForm").submit()
|
||||
{% endblock %}
|
||||
|
||||
{% block callBack %}timeDisconnectedScheduleCallback{% endblock %}
|
||||
|
||||
{% block formHtml %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form id="reportScheduleAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("reportschedule.add") }}">
|
||||
|
||||
{{ forms.hidden("hiddenFields", hiddenFields) }}
|
||||
{{ forms.hidden("reportName", reportName) }}
|
||||
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{% set helpText %}{% trans "The name for this report schedule" %}{% endset %}
|
||||
{{ forms.input("name", title, "", helpText, "", "required") }}
|
||||
|
||||
{% set title %}{% trans "Frequency" %}{% endset %}
|
||||
{% set helpText %}{% trans "Select how frequently you would like this report to run" %}{% endset %}
|
||||
{% set daily %}{% trans "Daily" %}{% endset %}
|
||||
{% set weekly %}{% trans "Weekly" %}{% endset %}
|
||||
{% set monthly %}{% trans "Monthly" %}{% endset %}
|
||||
{% set yearly %}{% trans "Yearly" %}{% endset %}
|
||||
{% set options = [
|
||||
{ name: "daily", filter: daily },
|
||||
{ name: "weekly", filter: weekly },
|
||||
{ name: "monthly", filter: monthly },
|
||||
{ name: "yearly", filter: yearly },
|
||||
] %}
|
||||
{{ forms.dropdown("filter", "single", title, "", options, "name", "filter", helpText) }}
|
||||
|
||||
{% set title %}{% trans "Display" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("display.search") },
|
||||
{ name: "data-search-term", value: "display" },
|
||||
{ name: "data-search-term-tags", value: "tags" },
|
||||
{ name: "data-id-property", value: "displayId" },
|
||||
{ name: "data-text-property", value: "display" }
|
||||
] %}
|
||||
{{ forms.dropdown("displayId", "single", title, "", null, "displayId", "display", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||
{% set attributes = [
|
||||
{ name: "data-width", value: "100%" },
|
||||
{ name: "data-allow-clear", value: "true" },
|
||||
{ name: "data-placeholder--id", value: null },
|
||||
{ name: "data-placeholder--value", value: "" },
|
||||
{ name: "data-search-url", value: url_for("displayGroup.search") },
|
||||
{ name: "data-search-term", value: "displayGroup" },
|
||||
{ name: "data-id-property", value: "displayGroupId" },
|
||||
{ name: "data-text-property", value: "displayGroup" }
|
||||
] %}
|
||||
{{ forms.dropdown("displayGroupId[]", "dropdownmulti", title, "", null, "displayGroupId", "displayGroup", "", "pagedSelect", "", "d", "", attributes) }}
|
||||
|
||||
{% set title %}{% trans "Start Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to run this report. Leave blank to run from the next collection point." %}{% endset %}
|
||||
{{ forms.dateTime("fromDt", title, "", helpText, "starttime-control") }}
|
||||
|
||||
{% set title %}{% trans "End Time" %}{% endset %}
|
||||
{% set helpText %}{% trans "Set a future date and time to end the schedule. Leave blank to run indefinitely." %}{% endset %}
|
||||
{{ forms.dateTime("toDt", title, "", helpText, "endtime-control") }}
|
||||
|
||||
{% set title %}{% trans "Should an email be sent?" %}{% endset %}
|
||||
{{ forms.checkbox("sendEmail", title, sendEmail) }}
|
||||
|
||||
{% set title %}{% trans "Email addresses" %}{% endset %}
|
||||
{% set helpText %}{% trans "Additional emails separated by a comma." %}{% endset %}
|
||||
{{ forms.inputWithTags("nonusers", title, nonusers, helpText) }}
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
14
reports/timedisconnectedsummary.report
Normal file
14
reports/timedisconnectedsummary.report
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "timedisconnectedsummary",
|
||||
"description": "Time Connected Summary",
|
||||
"class": "\\Xibo\\Report\\TimeDisconnectedSummary",
|
||||
"type": "Report",
|
||||
"output_type": "both",
|
||||
"color":"orange",
|
||||
"fa_icon": "fa-tasks",
|
||||
"sort_order": 4,
|
||||
"hidden": 0,
|
||||
"category": "Display",
|
||||
"feature": "displays.reporting",
|
||||
"adminOnly": 0
|
||||
}
|
||||
Reference in New Issue
Block a user