Initial Upload

This commit is contained in:
Matt Batchelder
2025-12-02 10:32:59 -05:00
commit 05ce0da296
2240 changed files with 467811 additions and 0 deletions

56
views/about-page.twig Normal file
View File

@@ -0,0 +1,56 @@
{#
/**
* Copyright (C) 2021 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 "non-authed.twig" %}
{% block title %}{{ "About"|trans }} | {% endblock %}
{% block style %}
<style type="text/css">
.about-container {
padding: 19px 29px 29px;
margin: 10px auto 20px;
background-color: #fff;
border: 1px solid #e5e5e5;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.05);
-moz-box-shadow: 0 1px 2px rgba(0,0,0,.05);
box-shadow: 0 1px 2px rgba(0,0,0,.05);
}
.login-logo {
width: 200px;
}
</style>
{% endblock %}
{% block header %}{% endblock %}
{% block contentClass %}{% endblock %}
{% block content %}
<p><a href="{{ theme.getThemeConfig("theme_url") }}"><img class="login-logo" src="{{ theme.uri("img/xibologo.png") }}" alt="Logo"></a></p>
<a class="btn btn-info" href="{{ url_for("home") }}">{% trans "Home" %}</a>
<div class="about-container">
{% include "licence.twig" %}
</div>
{% endblock %}

11
views/about-text.twig Normal file
View File

@@ -0,0 +1,11 @@
{% extends "form-base.twig" %}
{% block formTitle %}{% trans "About" %}{% endblock %}
{% block formButtons %}
{% trans "Close" %}, XiboDialogClose()
{% endblock %}
{% block formHtml %}
{% include "licence.twig" %}
{% endblock %}

View File

@@ -0,0 +1,48 @@
{#
/**
* Copyright (C) 2023 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 "connector-form-edit.twig" %}
{% import "forms.twig" as forms %}
{% block connectorFormFields %}
<h3>Alpha Vantage</h3>
<p>Alpha Vantage provides enterprise-grade financial market data through a set of powerful and developer-friendly data APIs and spreadsheets.</p>
{% if interface.isProviderSetting("apiKey") %}
<p>{{ "Your platform provider has configured this connector for you."|trans }}</p>
{% else %}
<p>An API key is needed to enable this connector. Register <a href="https://www.alphavantage.co/support/#api-key">here</a> for free to get yours.</p>
{% set title %}{% trans "API Key" %}{% endset %}
{% set helpText %}{% trans "Enter your API Key from Alpha Advantage" %}{% endset %}
{{ forms.input("apiKey", title, interface.getSetting("apiKey"), helpText) }}
{% set title %}{% trans "Paid plan?" %}{% endset %}
{% set helpText %}{% trans "Is the above key on a paid plan? You may want to use a paid plan for real time FX rates." %}{% endset %}
{{ forms.checkbox("isPaidPlan", title, interface.getSetting("isPaidPlan"), helpText) }}
{% set title %}{% trans "Cache Period" %}{% endset %}
{% set helpText %}{% trans "This module uses 3rd party data. Please enter the number of seconds you would like to cache results." %}{% endset %}
{{ forms.input("cachePeriod", title, interface.getSetting("cachePeriod", 3600), helpText) }}
{% endif %}
{% endblock %}

View 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 "authed.twig" %}
{% import "inline.twig" as inline %}
{% block pageContent %}
<div class="widget w-50 mx-auto">
<div class="widget-title">{% trans "Authorize Request" %}</div>
<div class="widget-body">
<div class="card mx-auto my-auto">
{% if application.coverImage %}<img class="card-img" style="opacity: 0.4; object-fit: cover" src="{{ application.coverImage }}" alt="Card image">
<div class="card-img-overlay" style="opacity: unset">
{% endif %}
<div class="card-body">
{% if application.logo %}<div class="logo" style="margin-bottom: 30px"><img src="{{ application.logo }}" class="card-img-top" alt="{{ application.getName() }}" style="width: 150px;"></div>{% endif %}
<h3 class="card-title">{% if application.companyName %} {{ application.companyName }} - {% endif %} {{ authParams.client.getName() }}</h3>
<h5 class="card-text">{{ "would like access to the following scopes"|trans }}:</h5>
<ul class="card-text" style="margin-bottom: 50px">
{% for scope in scopes %}
<li>
{{ scope.description|trans|raw }}
</li>
{% endfor %}
</ul>
{% if application.description %}<h5 class="card-text" style="margin-bottom: 30px">{{ application.description }}</h5>{% endif %}
{% if application.termsUrl %}<h5 class="card-text"><a href="{{ application.termsUrl }}">{% trans "Terms" %}</a></h5>{% endif %}
{% if application.privacyUrl %}<h5 class="card-text"><a href="{{ application.privacyUrl }}">{% trans "Privacy Policy" %}</a></h5>{% endif %}
</div>
{% if application.coverImage %}<img class="card-img" style="opacity: 0.4; object-fit: cover" src="{{ application.coverImage }}" alt="Card image">
</div>
{% endif %}
</div>
<form method="post" action="{{ url_for("application.authorize") }}" {% if approved %}style="display: none"{% endif %}>
<div class="text-right">
<input type="hidden" name="{{ csrfKey }}" value="{{ csrfToken }}" />
<input type="submit" id="deny" class="btn btn-danger" value="Deny" name="authorization">
<input type="submit" id="approve" class="btn btn-success" value="Approve" name="authorization">
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,46 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Add Application" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#applicationFormSubmit").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="applicationFormSubmit" class="XiboForm form-horizontal" method="post" action="{{ url_for("application.add") }}"
data-next-form-url="{{ url_for("application.edit.form", {id:':id'}) }}" data-next-form-id-property="key">
{% set title %}{% trans "Application Name" %}{% endset %}
{{ forms.input("name", title) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Delete Application" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Yes" %}, $("#applicationDeleteForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="applicationDeleteForm" class="XiboForm form-horizontal" method="delete" action="{{ url_for("application.delete", {"id": client.key}) }}">
{% set message %}{% trans "Are you sure you want to delete this application? This cannot be undone" %}{% endset %}
{{ forms.message(message) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,154 @@
{#
/**
* Copyright (C) 2023 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 "Edit Application" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#applicationFormSubmit").submit()
{% endblock %}
{% block callBack %}copyFromSecretInput{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#general" role="tab" data-toggle="tab"><span>{% trans "General" %}</span></a></li>
<li class="nav-item tabForAuthCode"><a class="nav-link" href="#advanced" role="tab" data-toggle="tab"><span>{% trans "Advanced" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#scopes" role="tab" data-toggle="tab"><span>{% trans "Sharing" %}</span></a></li>
</ul>
<form id="applicationFormSubmit" class="XiboForm form-horizontal" method="put" action="{{ url_for("application.edit", {id: client.key}) }}">
<div class="tab-content">
<div class="tab-pane active" id="general">
{% set title %}{% trans "Application Name" %}{% endset %}
{{ forms.input("name", title, client.name) }}
{% set title %}{% trans "Client Id" %}{% endset %}
{{ forms.disabled("clientId", title, client.key, "", "", "disabled") }}
{% set title %}{% trans "Client Secret" %}{% endset %}
{% set buttonTitle %}{% trans "Copy to Clipboard" %}{% endset %}
{{ forms.inputWithButton("clientSecret", title, client.secret, "", "", "readonly='readonly'", "", "copy-button", buttonTitle, "button", "") }}
{% set title %}{% trans "Reset Secret?" %}{% endset %}
{% set helpText %}{% trans "Reset your client secret to prevent access from any existing application." %}{% endset %}
{{ forms.checkbox("resetKeys", title, 0, helpText) }}
{% set message %}{% trans "Selecting only one of the Authorisation Code or Client Credentials grants improves security by allowing us to revoke access tokens more effectively." %}{% endset %}
{{ forms.message(message, "alert alert-info") }}
{% set title %}{% trans "Authorization Code?" %}{% endset %}
{% set helpText %}{% trans "Allow the Authorization Code Grant for this Client?" %}{% endset %}
{{ forms.checkbox("authCode", title, client.authCode, helpText) }}
{% set title %}{% trans "Client Credentials?" %}{% endset %}
{% set helpText %}{% trans "Allow the Client Credentials Grant for this Client?" %}{% endset %}
{{ forms.checkbox("clientCredentials", title, client.clientCredentials, helpText) }}
{% set title %}{% trans "Is Confidential?" %}{% endset %}
{% set helpText %}{% trans "Can this Application keep a secret?" %}{% endset %}
{{ forms.checkbox("isConfidential", title, client.isConfidential, helpText) }}
{% set title %}{% trans "New Redirect URI" %}{% endset %}
{% set helpText %}{% trans "White listed redirect URI's that will be allowed, only application for Authorization Code Grants" %}{% endset %}
{{ forms.input("redirectUri[]", title, "", helpText) }}
<div class="form-group row">
<span class="control-label col-sm-2">{{ "Existing Redirect URI"|trans }}:</span>
</div>
{% for url in client.redirectUris %}
{{ forms.input("redirectUri[]", "", url.redirectUri) }}
{% endfor %}
</div>
<div class="tab-pane" id="advanced">
{% set message %}{% trans "Below information will be displayed for User on Application authorization page" %}{% endset %}
{{ forms.message(message, "alert alert-info") }}
{% set title %}{% trans "Application Description" %}{% endset %}
{{ forms.textarea("description", title, client.description, title) }}
{% set title %}{% trans "Logo" %}{% endset %}
{% set helpText %}{% trans "Url pointing to the logo" %}{% endset %}
{{ forms.input("logo", title, client.logo, helpText) }}
{% set title %}{% trans "Cover Image" %}{% endset %}
{% set helpText %}{% trans "Url pointing to the Cover Image" %}{% endset %}
{{ forms.input("coverImage", title, client.coverImage, helpText) }}
{% set title %}{% trans "Company Name" %}{% endset %}
{{ forms.input("companyName", title, client.companyName) }}
{% set title %}{% trans "Terms URL" %}{% endset %}
{% set helpText %}{% trans "Url pointing to the terms for this Application" %}{% endset %}
{{ forms.input("termsUrl", title, client.termsUrl, helpText) }}
{% set title %}{% trans "Privacy Url" %}{% endset %}
{% set helpText %}{% trans "Url pointing to the privacy policy for this Application" %}{% endset %}
{{ forms.input("privacyUrl", title, client.privacyUrl, helpText) }}
</div>
<div class="tab-pane" id="scopes">
{% set message %}{% trans "Select sharing to grant to this application (scopes)." %}{% endset %}
{{ forms.message(message, 'alert alert-info') }}
{% set message2 %}{% trans "Scopes grant the Application access to specific routes, all GET,POST and PUT calls for the selected scopes, will be available to use by this Application." %}{% endset %}
{{ forms.message(message2, 'alert alert-info') }}
{% set message3 %}{% trans "The delete scopes are separate, without these Application will not have access to delete any existing content" %}{% endset %}
{{ forms.message(message3, 'alert alert-info') }}
<div class="form-group row">
<span class="control-label col-sm-2">{{ "Scopes"|trans }}:</span>
</div>
{% for scope in scopes %}
{% set title %}{{ scope.description }}{% endset %}
{% set id %}scope_{{ scope.id }}{% endset %}
{{ forms.checkbox(id, title, scope.getUnmatchedProperty('selected')) }}
{% endfor %}
{% set title %}{% trans "Owner" %}{% endset %}
{% set helpText %}{% trans "Set the owner of this Application. Leave empty to keep the current owner. If you are not an admin you will not be able to reverse this action" %}{% 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-search-term-tags", value: "tags" },
{ name: "data-id-property", value: "userId" },
{ name: "data-text-property", value: "userName" },
{ name: "data-anchor-element", value: "#applicationFormSubmit"}
] %}
{{ forms.dropdown("userId", "single", title, "", null, "userId", "userName", helpText, "pagedSelect", "", "", "", attributes) }}
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,281 @@
{#
/**
* 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 "authed.twig" %}
{% import "inline.twig" as inline %}
{% block title %}{{ "Applications"|trans }} | {% endblock %}
{% block actionMenu %}
<div class="widget-action-menu pull-right">
<button class="btn btn-success XiboFormButton" title="{% trans "Add an Application" %}" href="{{ url_for("application.add.form") }}"> <i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Application" %}</button>
<button class="btn btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
</div>
{% endblock %}
{% block pageContent %}
<div class="widget">
<div class="widget-title">{% trans "Applications" %}</div>
<div class="widget-body">
<div class="XiboGrid" id="{{ random() }}">
<div class="XiboFilter card mb-3 bg-light">
<div class="FilterDiv card-body" id="Filter">
<form class="form-inline">
{% set title %}{% trans "Name" %}{% endset %}
{{ inline.inputNameGrid('name', title) }}
</form>
</div>
</div>
<div class="XiboData card pt-3">
<table id="applications" class="table table-striped">
<thead>
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Owner" %}</th>
<th class="rowMenu"></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="widget mt-2">
<div class="widget-title">{% trans "Connectors" %}</div>
<div class="widget-body">
<div id="connectors" class="card-deck">
{% if theme.getThemeConfig("app_name") == "Xibo" %}
<div class="card p3 mt-2" style="min-width: 250px; max-width: 250px;">
<img class="card-img-top" style="max-height: 250px" src="{{ theme.rootUri() }}theme/default/img/connectors/canva_logo.png" alt="Canva">
<div class="card-body">
<h5 class="card-title">Canva</h5>
<p class="card-text">
Publish your designs from Canva to Xibo at the push of a button.
<br/>
<br/>
This connector is configured in Canva using the "Publish menu".
</p>
</div>
<div class="card-footer">
<a class="btn btn-primary" href="https://canva.com" target="_blank">Visit Canva</a>
</div>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% block javaScript %}
<script type="text/javascript" nonce="{{ cspNonce }}">
{% autoescape "js" %}
var copyToClipboardTrans = "{{ "Copy to Clipboard"|trans }}";
var couldNotCopyTrans = "{{ "Could not copy"|trans }}";
var copiedTrans = "{{ "Copied!"|trans }}";
{% endautoescape %}
var table;
$(document).ready(function() {
table = $('#applications').DataTable({
language: dataTablesLanguage,
dom: dataTablesTemplate,
serverSide: true,
stateSave: true,
responsive: true,
stateDuration: 0,
stateLoadCallback: dataTableStateLoadCallback,
stateSaveCallback: dataTableStateSaveCallback,
filter: false,
searchDelay: 3000,
"order": [[ 0, "asc"]],
ajax: {
url: "{{ url_for('application.search') }}",
data: function (d) {
$.extend(d, $('#applications').closest(".XiboGrid").find(".FilterDiv form").serializeObject());
}
},
"columns": [
{ "data": "name", "render": dataTableSpacingPreformatted },
{ "data": "owner" },
{
"orderable": false,
responsivePriority: 1,
"data": dataTableButtonsColumn
}
]
});
table.on('draw', dataTableDraw);
table.on('processing.dt', dataTableProcessing);
dataTableAddButtons(table, $('#applications_wrapper').find('.dataTables_buttons'));
$("#refreshGrid").click(function () {
table.ajax.reload();
});
// Connectors
loadConnectors();
});
function loadConnectors() {
var connectorTemplate = Handlebars.compile($('#template-connector-cards').html());
var $connectorContainer = $('#connectors');
$connectorContainer.find('.connector').remove();
$.ajax({
type: 'GET',
url: '{{ url_for("connector.search") }}?isVisible=1&showUninstalled=1',
cache: false,
dataType:"json",
success: function(xhr, textStatus, error) {
$.each(xhr.data, function(index, element) {
if (element.isHidden) {
return;
}
element.configureUrl = '{{ url_for("connector.edit.form", {id: ":id"}) }}'.replace(':id', element.connectorId);
element.proxyUrl = '{{ url_for("connector.edit.form.proxy", {id: ":id", method: ":method"}) }}'.replace(':id', element.connectorId);
element.thumbnail = element.thumbnail || 'theme/default/img/thumbs/placeholder.png';
if (!element.thumbnail.startsWith('http')) {
element.thumbnail = '{{ theme.rootUri() }}' + element.thumbnail;
}
element.enabledIcon = (element.isEnabled) ? 'fa-check' : 'fa-times';
element.classNameLast = element.className.substr(element.className.lastIndexOf('\\') + 1);
$connectorContainer.append(connectorTemplate(element));
});
// Raise an event to say we've been successful.
$connectorContainer.trigger('connectors.loaded');
// Bind to any configure buttons.
XiboInitialise('#connectors');
}
});
}
function connectorFormSubmit() {
XiboFormSubmit($('#connectorEditForm'), null, function() {
loadConnectors();
});
}
function copyFromSecretInput(dialog) {
// Initialize the tooltip.
$('#copy-button').tooltip();
$('#copy-button').bind('click', function() {
var input = $('#clientSecret');
// Select the input to copy
input.focus();
input.select();
// Try to copy to clipboard and give feedback
try {
var success = document.execCommand('copy');
if (success) {
$('#copy-button').trigger('copied', [copiedTrans]);
} else {
$('#copy-button').trigger('copied', [couldNotCopyTrans]);
}
} catch (err) {
$('#copy-button').trigger('copied', [couldNotCopyTrans]);
}
// Unselect the input
input.blur();
});
// Handler for updating the tooltip message.
$('#copy-button').bind('copied', function(event, message) {
const $self = $(this);
$(this).tooltip('hide')
.attr('data-original-title', message)
.tooltip('show');
setTimeout(function() {
$self.tooltip('hide').attr('data-original-title', copyToClipboardTrans);
}, 1000);
});
// Auth Code change
onAuthCodeChanged(dialog);
$(dialog).find('#authCode').on('change', function() {
onAuthCodeChanged(dialog);
});
}
function onAuthCodeChanged(dialog) {
var authCode = $(dialog).find("#authCode").is(":checked");
var $authCodeTab = $(dialog).find(".tabForAuthCode");
if (authCode) {
$authCodeTab.removeClass("d-none");
} else {
$authCodeTab.addClass("d-none");
}
}
</script>
{% for js in connectorJavaScript %}
{% include js ~ ".twig" %}
{% endfor %}
{% endblock %}
{% block javaScriptTemplates %}
{{ parent() }}
{% verbatim %}
<script type="text/x-handlebars-template" id="template-connector-cards">
<div class="connector card p3 mt-2" style="min-width: 250px; max-width: 250px;"
data-proxy-url="{{proxyUrl}}"
data-connector-class-name="{{className}}"
data-connector-class-name-last="{{classNameLast}}"
data-connector-id="{{ connectorId }}">
{{#if thumbnail}}<img class="card-img-top" style="max-height: 250px" src="{{ thumbnail }}" alt="{{ title }}">{{/if}}
<div class="card-body">
<h5 class="card-title">{{ title }}</h5>
<p class="card-text">
{{ description }}
<br/>
<br/>
{{#if isInstalled }}
{% endverbatim %}{{ "Enabled"|trans }}{% verbatim %}: <span class="fa {{ enabledIcon }}"></span>
{{/if}}
{{#unless isInstalled }}
{% endverbatim %}{{ "Installed"|trans }}{% verbatim %}: <span class="fa fa-times"></span>
{{/unless}}
</p>
</div>
<div class="card-footer">
<button class="btn btn-primary XiboFormButton" href="{{ configureUrl }}">
{% endverbatim %}{{ "Configure"|trans }}{% verbatim %}
</button>
</div>
</div>
</script>
{% endverbatim %}
{% endblock %}

View File

@@ -0,0 +1,48 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Output Audit Trail as CSV" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Export" %}, auditLogExportFormSubmit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="auditLogExportForm" class="XiboManualForm form-horizontal" method="get" action="{{ url_for("auditLog.export") }}">
{% set title %}{% trans "From Date" %}{% endset %}
{{ forms.date("filterFromDt", title) }}
{% set title %}{% trans "To Date" %}{% endset %}
{{ forms.date("filterToDt", title) }}
</form>
</div>
</div>
{% endblock %}

188
views/auditlog-page.twig Normal file
View File

@@ -0,0 +1,188 @@
{#
/**
* 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 %}{{ "Audit Log"|trans }} | {% endblock %}
{% block actionMenu %}
<div class="widget-action-menu pull-right">
<button class="btn btn-success XiboFormButton" title="{% trans "Export raw data to CSV" %}" href="{{ url_for("auditLog.export.form") }}"><i class="fa fa-cloud-upload" aria-hidden="true"></i> {% trans "Export" %}</button>
<button class="btn btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
</div>
{% endblock %}
{% block pageContent %}
<div class="widget">
<div class="widget-title">{% trans "Audit Log" %}</div>
<div class="widget-body">
<div class="XiboGrid" id="{{ random() }}" data-grid-name="auditView">
<div class="XiboFilter card mb-3 bg-light">
<div class="FilterDiv card-body" id="Filter">
<form class="form-inline">
{% set title %}{% trans "From Date" %}{% endset %}
{{ inline.date("fromDt", title) }}
{% set title %}{% trans "To Date" %}{% endset %}
{{ inline.date("toDt", title) }}
{% set title %}{% trans "User" %}{% endset %}
{{ inline.input("user", title) }}
{% set title %}{% trans "Entity" %}{% endset %}
{{ inline.input("entity", title) }}
{% set title %}{% trans "Entity ID" %}{% endset %}
{{ inline.input("entityId", title) }}
{% set title %}{% trans "IP Address" %}{% endset %}
{{ inline.input("ipAddress", title) }}
{% set title %}{% trans "Message" %}{% endset %}
{{ inline.input("message", title) }}
</form>
</div>
</div>
<div class="XiboData card pt-3">
<table id="logs" class="table table-striped">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Date" %}</th>
<th>{% trans "User" %}</th>
<th>{% trans "Entity" %}</th>
<th>{% trans "Entity ID" %}</th>
<th>{% trans "IP Address" %}</th>
<th>{% trans "Message" %}</th>
<th>{% trans "Object" %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</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" data-state-preference-name="auditlogGrid">
<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());
var table = $("#logs").DataTable({
"language": dataTablesLanguage,
dom: dataTablesTemplate,
serverSide: true,
stateSave: true,
responsive: true,
stateDuration: 0,
stateLoadCallback: dataTableStateLoadCallback,
stateSaveCallback: dataTableStateSaveCallback,
filter: false,
searchDelay: 3000,
"order": [[0, "desc"]],
ajax: {
url: "{{ url_for("auditLog.search") }}",
"data": function (d) {
$.extend(d, $("#logs").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
}
},
"columns": [
{"data": "logId", responsivePriority: 2},
{"data": "logDate", "render": dataTableDateFromUnix, responsivePriority: 2},
{"data": "userName", responsivePriority: 2},
{"data": "entity", responsivePriority: 2},
{
"name": "entityId",
responsivePriority: 2,
"data" : function (data) {
if (data.entityId === 0) {
return ''
}
return data.entityId;
}
},
{"data": "ipAddress", responsivePriority: 2},
{"data": "message", responsivePriority: 1},
{
"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
}
]
});
table.on('draw', function (e, settings) {
dataTableDraw(e, settings);
$(".arrayViewerToggle").click(function () {
$(this).parent().find(".arrayViewer").toggle();
});
});
table.on('processing.dt', dataTableProcessing);
dataTableAddButtons(table, $('#logs_wrapper').find('.dataTables_buttons'));
$("#refreshGrid").click(function () {
table.ajax.reload();
});
});
function auditLogExportFormSubmit() {
$("#auditLogExportForm").submit();
XiboDialogClose();
}
</script>
{% endblock %}

View File

@@ -0,0 +1,23 @@
<li class="dropdown nav-item item">
<a id="navbarNotificationMenuLink" href="#" class="nav-link notification-drawer-icon" data-toggle="dropdown">
<i class="fa fa-bell"></i> {% if notificationCount > 0 %}<span class="badge red">{{ notificationCount }}</span>{% endif %}
</a>
<div class="dropdown-menu dropdown-menu-right notification-drawer">
<h6 class="dropdown-header">{% trans "Notifications" %}</h6>
{% if notifications|length > 0 %}
<div class="dropdown-divider"></div>
{% endif %}
{% for notification in notifications %}
{% if notification.notificationId %}
<a class="XiboFormButton notification dropdown-item" href="{{ url_for("notification.show", {id: notification.notificationId}) }}"><span class="{% if notification.read %}notification-unread{% else %}notification-read{% endif %}">{{ notification.subject }} - <span class="notification-date">{{ notification.releaseDt }}</span></span></a>
{% else %}
<a href="#" class="notification dropdown-item"><span class="fa fa-exclamation-circle"></span> {{ notification.subject }} - <span class="notification-date">{{ notification.releaseDt }}</span></a>
{% endif %}
{% endfor %}
{% if currentUser.featureEnabled("notification.centre") %}
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="{{ url_for("notification.view") }}">{% trans "Notification Centre" %}</a>
{% endif %}
</div>
</li>

181
views/authed-sidebar.twig Normal file
View File

@@ -0,0 +1,181 @@
<div id="sidebar-wrapper">
<ul class="sidebar">
<li class="sidebar-main"><a href="{{ url_for("home") }}">{% trans "Dashboard" %}</a></li>
{% if currentUser.featureEnabled("schedule.view") %}
<li class="sidebar-list"><a href="{{ url_for("schedule.view") }}">{% trans "Schedule" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("daypart.view") %}
<li class="sidebar-list"><a href="{{ url_for("daypart.view") }}">{% trans "Dayparting" %}</a></li>
{% endif %}
{% set countViewable = currentUser.featureEnabledCount(["campaign.view", "layout.view", "template.view", "resolution.view"]) %}
{% if countViewable > 0 %}
<li class="sidebar-title"><a>{% trans "Design" %}</a></li>
{% if currentUser.featureEnabled("campaign.view") %}
<li class="sidebar-list"><a href="{{ url_for("campaign.view") }}">{% trans "Campaigns" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("layout.view") %}
<li class="sidebar-list"><a href="{{ url_for("layout.view") }}">{% trans "Layouts" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("template.view") %}
<li class="sidebar-list"><a href="{{ url_for("template.view") }}">{% trans "Templates" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("resolution.view") %}
<li class="sidebar-list"><a href="{{ url_for("resolution.view") }}">{% trans "Resolutions" %}</a></li>
{% endif %}
{% endif %}
{% set countViewable = currentUser.featureEnabledCount(["library.view", "playlist.view", "dataset.view", "menuBoard.view"]) %}
{% if countViewable > 0 %}
<li class="sidebar-title"><a>{% trans "Library" %}</a></li>
{% if currentUser.featureEnabled("playlist.view") %}
<li class="sidebar-list"><a href="{{ url_for("playlist.view") }}">{% trans "Playlists" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("library.view") %}
<li class="sidebar-list"><a href="{{ url_for("library.view") }}">{% trans "Media" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("dataset.view") %}
<li class="sidebar-list"><a href="{{ url_for("dataset.view") }}">{% trans "DataSets" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("menuBoard.view") %}
<li class="sidebar-list"><a href="{{ url_for("menuBoard.view") }}">{% trans "Menu Boards" %}</a></li>
{% endif %}
{% endif %}
{% set countViewable = currentUser.featureEnabledCount(["displays.view", "displaygroup.view", "displayprofile.view", "playersoftware.view", "command.view"]) %}
{% if countViewable > 0 %}
<li class="sidebar-title"><a>{% trans "Displays" %}</a></li>
{% if currentUser.featureEnabled("displays.view") %}
<li class="sidebar-list"><a href="{{ url_for("display.view") }}">{% trans "Displays" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("displaygroup.view") %}
<li class="sidebar-list"><a href="{{ url_for("displaygroup.view") }}">{% trans "Display Groups" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("display.syncView") %}
<li class="sidebar-list"><a href="{{ url_for("syncgroup.view") }}">{% trans "Sync Groups" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("displayprofile.view") %}
<li class="sidebar-list"><a href="{{ url_for("displayprofile.view") }}">{% trans "Display Settings" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("playersoftware.view") %}
<li class="sidebar-list"><a href="{{ url_for("playersoftware.view") }}">{% trans "Player Versions" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("command.view") %}
<li class="sidebar-list"><a href="{{ url_for("command.view") }}">{% trans "Commands" %}</a></li>
{% endif %}
{% endif %}
{% if currentUser.featureEnabled("users.view") and (currentUser.isGroupAdmin() or currentUser.isSuperAdmin()) %}
{% set userMenuViewable = true %}
{% else %}
{% set userMenuViewable = false %}
{% endif %}
{% set countViewable = currentUser.featureEnabledCount(["usergroup.view", "module.view", "transition.view", "task.view"]) %}
{% if countViewable > 0 or userMenuViewable %}
<li class="sidebar-title"><a>{% trans "Administration" %}</a></li>
{% if userMenuViewable %}
<li class="sidebar-list"><a href="{{ url_for("user.view") }}">{% trans "Users" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("usergroup.view") %}
<li class="sidebar-list"><a href="{{ url_for("group.view") }}">{% trans "User Groups" %}</a></li>
{% endif %}
{% if currentUser.isSuperAdmin() %}
<li class="sidebar-list"><a href="{{ url_for("admin.view") }}">{% trans "Settings" %}</a></li>
{% endif %}
{% if currentUser.isSuperAdmin() %}
<li class="sidebar-list"><a href="{{ url_for("application.view") }}">{% trans "Applications" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("module.view") %}
<li class="sidebar-list"><a href="{{ url_for("module.view") }}">{% trans "Modules" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("transition.view") %}
<li class="sidebar-list"><a href="{{ url_for("transition.view") }}">{% trans "Transitions" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("task.view") %}
<li class="sidebar-list"><a href="{{ url_for("task.view") }}">{% trans "Tasks" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("tag.view") %}
<li class="sidebar-list"><a href="{{ url_for("tag.view") }}">{% trans "Tags" %}</a></li>
{% endif %}
{% if currentUser.isSuperAdmin() %}
<li class="sidebar-list"><a href="{{ url_for("folders.view") }}">{% trans "Folders" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("font.view") %}
<li class="sidebar-list"><a href="{{ url_for("font.view") }}">{% trans "Fonts" %}</a></li>
{% endif %}
{% endif %}
{% set countViewable = currentUser.featureEnabledCount(["report.view", "report.scheduling", "report.saving"]) %}
{% if countViewable > 0 %}
<li class="sidebar-title"><a>{% trans "Reporting" %}</a></li>
{% if currentUser.featureEnabled("report.view") %}
<li class="sidebar-list"><a href="{{ url_for("report.view") }}">{% trans "All Reports" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("report.scheduling") %}
<li class="sidebar-list"><a href="{{ url_for("reportschedule.view") }}">{% trans "Report Schedules" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("report.saving") %}
<li class="sidebar-list"><a href="{{ url_for("savedreport.view") }}">{% trans "Saved Reports" %}</a></li>
{% endif %}
{% endif %}
{% set countViewable = currentUser.featureEnabledCount(["log.view", "sessions.view", "auditlog.view", "fault.view"]) %}
{% if countViewable > 0 %}
<li class="sidebar-title"><a>{% trans "Advanced" %}</a></li>
{% if currentUser.featureEnabled("log.view") %}
<li class="sidebar-list"><a href="{{ url_for("log.view") }}">{% trans "Log" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("sessions.view") %}
<li class="sidebar-list"><a href="{{ url_for("sessions.view") }}">{% trans "Sessions" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("auditlog.view") %}
<li class="sidebar-list"><a href="{{ url_for("auditlog.view") }}">{% trans "Audit Trail" %}</a></li>
{% endif %}
{% if currentUser.featureEnabled("fault.view") %}
<li class="sidebar-list"><a href="{{ url_for("fault.view") }}">{% trans "Report Fault" %}</a></li>
{% endif %}
{% endif %}
{% set countViewable = currentUser.featureEnabledCount(["developer.edit"]) %}
{% if countViewable > 0 %}
<li class="sidebar-title"><a>{% trans "Developer" %}</a></li>
{% if currentUser.featureEnabled("developer.edit") %}
<li class="sidebar-list"><a href="{{ url_for("developer.templates.view") }}">{% trans "Module Templates" %}</a></li>
{% endif %}
{% endif %}
</ul>
</div>

341
views/authed-topbar.twig Normal file
View File

@@ -0,0 +1,341 @@
{#
/**
* Copyright (C) 2023 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/>.
*/
#}
<ul class="nav navbar-nav">
<li class="nav-item"><a class="nav-link" href="{{ url_for("home") }}">{% trans "Dashboard" %}</a></li>
{% set countViewable = currentUser.featureEnabledCount(["schedule.view", "daypart.view"]) %}
{% set groupElementClass = (countViewable > 1) ? 'dropdown-item' : 'nav-link' %}
{% if countViewable > 0 %}
{% if countViewable > 1 %}
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{% trans "Schedule" %} <span class="caret"></span></a>
<div class="dropdown-menu">
{% else %}
<li class="nav-item">
{% endif %}
{% if currentUser.featureEnabled("schedule.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("schedule.view") }}">{% trans "Schedule" %}</a>
{% endif %}
{% if currentUser.featureEnabled("daypart.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("daypart.view") }}">{% trans "Dayparting" %}</a>
{% endif %}
{% if countViewable > 1 %}
</div>
{% endif %}
</li>
{% endif %}
{% set countViewable = currentUser.featureEnabledCount(["campaign.view", "layout.view", "template.view", "resolution.view"]) %}
{% set groupElementClass = (countViewable > 1) ? 'dropdown-item' : 'nav-link' %}
{% if countViewable > 0 %}
{% if countViewable > 1 %}
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{% trans "Design" %} <span class="caret"></span></a>
<div class="dropdown-menu">
{% else %}
<li class="nav-item">
{% endif %}
{% if currentUser.featureEnabled("campaign.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("campaign.view") }}">{% trans "Campaigns" %}</a>
{% endif %}
{% if currentUser.featureEnabled("layout.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("layout.view") }}">{% trans "Layouts" %}</a>
{% endif %}
{% if currentUser.featureEnabled("template.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("template.view") }}">{% trans "Templates" %}</a>
{% endif %}
{% if currentUser.featureEnabled("resolution.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("resolution.view") }}">{% trans "Resolutions" %}</a>
{% endif %}
{% if countViewable > 1 %}
</div>
{% endif %}
</li>
{% endif %}
{% set countViewable = currentUser.featureEnabledCount(["library.view", "playlist.view", "dataset.view", "menuBoard.view"]) %}
{% set groupElementClass = (countViewable > 1) ? 'dropdown-item' : 'nav-link' %}
{% if countViewable > 0 %}
{% if countViewable > 1 %}
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{% trans "Library" %} <span class="caret"></span></a>
<div class="dropdown-menu">
{% else %}
<li class="nav-item">
{% endif %}
{% if currentUser.featureEnabled("playlist.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("playlist.view") }}">{% trans "Playlists" %}</a>
{% endif %}
{% if currentUser.featureEnabled("library.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("library.view") }}">{% trans "Media" %}</a>
{% endif %}
{% if currentUser.featureEnabled("dataset.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("dataset.view") }}">{% trans "DataSets" %}</a>
{% endif %}
{% if currentUser.featureEnabled("menuBoard.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("menuBoard.view") }}">{% trans "Menu Boards" %}</a>
{% endif %}
{% if countViewable > 1 %}
</div>
{% endif %}
</li>
{% endif %}
{% set countViewable = currentUser.featureEnabledCount(["displays.view", "displaygroup.view", "displayprofile.view", "playersoftware.view", "command.view"]) %}
{% set groupElementClass = (countViewable > 1) ? 'dropdown-item' : 'nav-link' %}
{% if countViewable > 0 %}
{% if countViewable > 1 %}
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{% trans "Displays" %} <span class="caret"></span></a>
<div class="dropdown-menu">
{% else %}
<li class="nav-item">
{% endif %}
{% if currentUser.featureEnabled("displays.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("display.view") }}">{% trans "Displays" %}</a>
{% endif %}
{% if currentUser.featureEnabled("displaygroup.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("displaygroup.view") }}">{% trans "Display Groups" %}</a>
{% endif %}
{% if currentUser.featureEnabled("display.syncView") %}
<a class="{{ groupElementClass }}" href="{{ url_for("syncgroup.view") }}">{% trans "Sync Groups" %}</a>
{% endif %}
{% if currentUser.featureEnabled("displayprofile.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("displayprofile.view") }}">{% trans "Display Settings" %}</a>
{% endif %}
{% if currentUser.featureEnabled("playersoftware.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("playersoftware.view") }}">{% trans "Player Versions" %}</a>
{% endif %}
{% if currentUser.featureEnabled("command.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("command.view") }}">{% trans "Commands" %}</a>
{% endif %}
{% if countViewable > 1 %}
</div>
{% endif %}
</li>
{% endif %}
{% if currentUser.featureEnabled("users.view") and (currentUser.isGroupAdmin() or currentUser.isSuperAdmin()) %}
{% set userMenuViewable = true %}
{% else %}
{% set userMenuViewable = false %}
{% endif %}
{% set countViewable = currentUser.featureEnabledCount(["usergroup.view", "module.view", "transition.view", "task.view"]) %}
{% set groupElementClass = (countViewable > 1 or (countViewable == 1 and userMenuViewable)) ? 'dropdown-item' : 'nav-link' %}
{% if countViewable > 0 or userMenuViewable %}
{% if countViewable > 1 or (countViewable == 1 and userMenuViewable) %}
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{% trans "Administration" %} <span class="caret"></span></a>
<div class="dropdown-menu">
{% endif %}
{% if userMenuViewable %}
{% if countViewable == 0 %}
<li class="nav-item">
{% endif %}
<a class="{{ groupElementClass }}" href="{{ url_for("user.view") }}">{% trans "Users" %}</a>
{% if countViewable == 0 %}
</li>
{% endif %}
{% endif %}
{% if currentUser.featureEnabled("usergroup.view") %}
{% if countViewable == 0 %}
<li class="nav-item">
{% endif %}
<a class="{{ groupElementClass }}" href="{{ url_for("group.view") }}">{% trans "User Groups" %}</a>
{% if countViewable == 0 %}
</li>
{% endif %}
{% endif %}
{% if currentUser.isSuperAdmin() %}
{% if countViewable == 0 %}
<li class="nav-item">
{% endif %}
<a class="{{ groupElementClass }}" href="{{ url_for("admin.view") }}">{% trans "Settings" %}</a>
{% if countViewable == 0 %}
</li>
{% endif %}
{% endif %}
{% if currentUser.isSuperAdmin() %}
{% if countViewable == 0 %}
<li class="nav-item">
{% endif %}
<a class="{{ groupElementClass }}" href="{{ url_for("application.view") }}">{% trans "Applications" %}</a>
{% if countViewable == 0 %}
</li>
{% endif %}
{% endif %}
{% if currentUser.featureEnabled("module.view") %}
{% if countViewable == 0 %}
<li class="nav-item">
{% endif %}
<a class="{{ groupElementClass }}" href="{{ url_for("module.view") }}">{% trans "Modules" %}</a>
{% if countViewable == 0 %}
</li>
{% endif %}
{% endif %}
{% if currentUser.featureEnabled("transition.view") %}
{% if countViewable == 0 %}
<li class="nav-item">
{% endif %}
<a class="{{ groupElementClass }}" href="{{ url_for("transition.view") }}">{% trans "Transitions" %}</a>
{% if countViewable == 0 %}
</li>
{% endif %}
{% endif %}
{% if currentUser.featureEnabled("task.view") %}
{% if countViewable == 0 %}
<li class="nav-item">
{% endif %}
<a class="{{ groupElementClass }}" href="{{ url_for("task.view") }}">{% trans "Tasks" %}</a>
{% if countViewable == 0 %}
</li>
{% endif %}
{% endif %}
{% if currentUser.featureEnabled("tag.view") %}
{% if countViewable == 0 %}
<li class="nav-item">
{% endif %}
<a class="{{ groupElementClass }}" href="{{ url_for("tag.view") }}">{% trans "Tags" %}</a>
{% if countViewable == 0 %}
</li>
{% endif %}
{% endif %}
{% if currentUser.isSuperAdmin() %}
{% if countViewable == 0 %}
<li class="nav-item">
{% endif %}
<a class="{{ groupElementClass }}" href="{{ url_for("folders.view") }}">{% trans "Folders" %}</a>
{% if countViewable == 0 %}
</li>
{% endif %}
{% endif %}
{% if currentUser.featureEnabled("font.view") %}
{% if countViewable == 0 %}
<li class="nav-item">
{% endif %}
<a class="{{ groupElementClass }}" href="{{ url_for("font.view") }}">{% trans "Fonts" %}</a>
{% if countViewable == 0 %}
</li>
{% endif %}
{% endif %}
{% if countViewable > 1 or (countViewable == 1 and userMenuViewable) %}
</div>
{% endif %}
{% if countViewable > 1 or (countViewable == 1 and userMenuViewable) %}
</li>
{% endif %}
{% endif %}
{% set countViewable = currentUser.featureEnabledCount(["report.view", "report.scheduling", "report.saving"]) %}
{% set groupElementClass = (countViewable > 1) ? 'dropdown-item' : 'nav-link' %}
{% if countViewable > 0 %}
{% if countViewable > 1 %}
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{% trans "Reporting" %} <span class="caret"></span></a>
<div class="dropdown-menu">
{% else %}
<li class="nav-item">
{% endif %}
{% if currentUser.featureEnabled("report.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("report.view") }}">{% trans "All Reports" %}</a>
{% endif %}
{% if currentUser.featureEnabled("report.scheduling") %}
<a class="{{ groupElementClass }}" href="{{ url_for("reportschedule.view") }}">{% trans "Report Schedules" %}</a>
{% endif %}
{% if currentUser.featureEnabled("report.saving") %}
<a class="{{ groupElementClass }}" href="{{ url_for("savedreport.view") }}">{% trans "Saved Reports" %}</a>
{% endif %}
{% if countViewable > 1 %}
</div>
{% endif %}
</li>
{% endif %}
{% set countViewable = currentUser.featureEnabledCount(["log.view", "sessions.view", "auditlog.view", "fault.view"]) %}
{% set groupElementClass = (countViewable > 1) ? 'dropdown-item' : 'nav-link' %}
{% if countViewable > 0 %}
{% if countViewable > 1 %}
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{% trans "Advanced" %} <span class="caret"></span></a>
<div class="dropdown-menu">
{% else %}
<li class="nav-item">
{% endif %}
{% if currentUser.featureEnabled("log.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("log.view") }}">{% trans "Log" %}</a>
{% endif %}
{% if currentUser.featureEnabled("sessions.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("sessions.view") }}">{% trans "Sessions" %}</a>
{% endif %}
{% if currentUser.featureEnabled("auditlog.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("auditlog.view") }}">{% trans "Audit Trail" %}</a>
{% endif %}
{% if currentUser.featureEnabled("fault.view") %}
<a class="{{ groupElementClass }}" href="{{ url_for("fault.view") }}">{% trans "Report Fault" %}</a>
{% endif %}
{% if countViewable > 1 %}
</div>
{% endif %}
</li>
{% endif %}
{% set countViewable = currentUser.featureEnabledCount(["developer.edit"]) %}
{% if countViewable > 0 %}
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{% trans "Developer" %} <span class="caret"></span></a>
<div class="dropdown-menu">
{% if currentUser.featureEnabled("developer.edit") %}
<a class="dropdown-item" href="{{ url_for("developer.templates.view") }}">{% trans "Module Templates" %}</a>
{% endif %}
</div>
</li>
{% endif %}
</ul>

View File

@@ -0,0 +1,28 @@
<li class="dropdown nav-item item">
<a href="#" class="nav-link" data-toggle="dropdown" id="navbarUserMenu">
<img class="nav-avatar" src="{{ theme.uri("img/avatar.jpg") }}" />
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarUserMenu">
<h6 class="dropdown-header">{{ currentUser.userName }}<br/>
<div id="XiboClock">{{ clock }}</div>
</h6>
<div class="dropdown-divider"></div>
<a class="dropdown-item XiboFormButton" href="{{ url_for("user.preferences.form") }}" title="{% trans "Preferences" %}">{% trans "Preferences" %}</a>
{% if currentUser.featureEnabled("user.profile") %}
<a class="dropdown-item XiboFormButton" href="{{ url_for("user.edit.profile.form") }}" title="{% trans "Edit Profile" %}">{% trans "Edit Profile" %}</a>
{% endif %}
<a class="dropdown-item XiboFormButton" href="{{ url_for("user.applications") }}" title="{% trans "View my authenticated applications" %}">{% trans "My Applications" %}</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" id="reshowWelcomeMenuItem" href="{{ url_for("welcome.view") }}">{% trans "Reshow welcome" %}</a>
<a class="dropdown-item XiboFormButton" href="{{ url_for("about") }}" title="{% trans "About the CMS" %}">{% trans "About" %}</a>
{% if not hideLogout %}
<div class="dropdown-divider"></div>
<a class="dropdown-item" title="{% trans "Logout" %}" href="{{ logoutUrl }}">{% trans "Logout" %}</a>
{% endif %}
</div>
</li>

134
views/authed.twig Normal file
View File

@@ -0,0 +1,134 @@
{#
/**
* Copyright (C) 2020-2025 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.twig" %}
{% block content %}
{% set horizontalNav = currentUser.getOptionValue("navigationMenuPosition", theme.getSetting("NAVIGATION_MENU_POSITION", "vertical")) == "horizontal" %}
{% if not hideNavigation %}
{% set hideNavigation = currentUser.getOptionValue("hideNavigation", "0") %}
{% endif %}
<div {% if hideNavigation == "0" and not horizontalNav and not forceHide %}id="page-wrapper"{% endif %} class="active">
{% if hideNavigation == "0" and not forceHide %}
{% if horizontalNav %}
<nav class="navbar navbar-default navbar-expand-lg">
<a class="navbar-brand xibo-logo-container" href="#">
<img class="xibo-logo" src="{{ theme.uri("img/xibologo.png") }}">
</a>
<!-- Brand and toggle get grouped for better mobile display -->
<button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#navbar-collapse-1" aria-controls="navbarNav" aria-expanded="false">
<span class="fa fa-bars"></span>
</button>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="navbar-collapse collapse justify-content-between" id="navbar-collapse-1">
{% include "authed-topbar.twig" %}
<ul class="nav navbar-nav navbar-right">
{% include "authed-theme-topbar.twig" ignore missing %}
{% if currentUser.featureEnabled("drawer") %}
{% include "authed-notification-drawer.twig" %}
{% endif %}
{% include "authed-user-menu.twig" %}
</ul>
</div><!-- /.navbar-collapse -->
</nav>
{% else %}
<div class="navbar-collapse navbar-collapse-side collapse" id="navbar-collapse-1">
{% include "authed-sidebar.twig" %}
</div>
{% endif %}
{% endif %}
<div id="content-wrapper">
<div class="page-content">
{% if not horizontalNav or hideNavigation == "1" or forceHide %}
<div class="row header header-side">
<div class="col-sm-12">
<div class="meta pull-left xibo-logo-container">
<div class="page"><img class="xibo-logo" src="{{ theme.uri("img/xibologo.png") }}"></div>
</div>
{% if not forceHide %}
{% if not hideNavigation == "1" %}
<button type="button" class="pull-right navbar-toggler navbar-toggler-side" data-toggle="collapse" data-target="#navbar-collapse-1" aria-controls="navbarNav" aria-expanded="false">
<span class="fa fa-bars"></span>
</button>
{% endif %}
<div class="user pull-right">
{% include "authed-user-menu.twig" %}
</div>
{% if currentUser.featureEnabled("drawer") %}
<div class="user user-notif pull-right">
{% include "authed-notification-drawer.twig" %}
</div>
{% endif %}
{% include "authed-theme-topbar.twig" ignore missing %}
{% endif %}
</div>
</div>
{% endif %}
<div class="row">
<div class="col-sm-12">
{% block actionMenu %}{% endblock %}
{% if settings.INSTANCE_SUSPENDED == "partial" %}
<div class="alert alert-warning">{{ "CMS suspended. Displays will show cached content. Please contact your administrator."|trans }}</div>
{% endif %}
{% block pageContent %}{% endblock %}
</div>
</div>
<div class="row">
<div class="col-sm-12">
{% block pageFooter %}{% endblock %}
</div>
</div>
</div>
</div>
</div>
{% set helpLinks = helpService.getLinksForPage(route) %}
{% set faultViewEnabled = currentUser.featureEnabled("fault.view") %}
{# Hide in mobile view (sm/<768px) #}
<div id="help-pane" class="d-none d-md-flex help-pane"
data-help-links="{{ helpLinks|json_encode }}"
data-url-help-landing-page={{ helpService.getLandingPage() }}
data-fault-view-enabled={{faultViewEnabled}}
data-fault-view-url={{ url_for("fault.view") }}
>
<div class="help-pane-container" style="display: none;">
</div>
<div class="help-pane-btn">
<i class="fas fa-question"></i>
</div>
</div>
{% endblock %}
{% block javaScriptTemplates %}
{# File upload templates and scripts #}
{% include "include-file-upload.twig" %}
{% endblock %}

56
views/base-install.twig Normal file
View File

@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{ theme.getThemeConfig("theme_title") }}</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="token" content="{{ csrfToken }}"/>
<meta name="public-path" content="{{ theme.rootUri() }}"/>
<link rel="shortcut icon" href="{{ theme.uri("img/favicon.ico") }}" />
{# Import CSS bundle from dist #}
<script src="{{ theme.rootUri() }}dist/style.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
<link href="{{ theme.uri("css/xibo.css") }}?v={{ version }}&rev={{revision}}" rel="stylesheet" media="screen">
<link href="{{ theme.uri("css/install.css") }}" rel="stylesheet" media="screen">
</head>
<body>
<!-- Copyright 2006-2021 Xibo Signage Ltd. Part of the Xibo Open Source Digital Signage Solution. Released under the AGPLv3 or later. -->
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
{% set themeName = theme.getThemeConfig("app_name") %}
{% set header %}{% trans %}{{ themeName }} Installation{% endtrans %}{% endset %}
<a class="navbar-brand" href="#">{{ header }}</a>
</div>
</div>
</div>
{% block jumboTron %}
{% endblock %}
<div class="container main-container">
{% if session.error %}
<div class="alert alert-danger">
{{ session['error'] }}
</div>
{% endif %}
{% block stepContent %}
{% endblock %}
</div>
<div class="footer">
<div class="container text-center">
<img class="logo" src="{{ theme.uri("img/xibologo.png") }}">
</div>
</div>
<script src="{{ theme.rootUri() }}dist/vendor.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
<script src="{{ theme.rootUri() }}dist/core/install.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
</body>
</html>

72
views/base.twig Normal file
View File

@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="{{ translate.jsShortLocale }}">
<head>
<title>{% block title %}{% endblock %}{{ theme.getThemeConfig("theme_title") }}</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="token" content="{{ csrfToken }}"/>
<meta name="public-path" content="{{ theme.rootUri() }}"/>
<link rel="shortcut icon" href="{{ theme.uri("img/favicon.ico") }}" />
{# Import CSS bundle from dist #}
<script src="{{ theme.rootUri() }}dist/style.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
{# Import user made CSS from theme #}
<link href="{{ theme.uri("css/dashboard.css") }}?v={{ version }}&rev={{revision}}" rel="stylesheet" media="screen">
<link href="{{ theme.uri("css/timeline.css") }}?v={{ version }}&rev={{revision}}" rel="stylesheet" media="screen">
<link href="{{ theme.uri("css/xibo.css") }}?v={{ version }}&rev={{revision}}" rel="stylesheet" media="screen">
<link href="{{ theme.uri("css/calendar.css") }}?v={{ version }}&rev={{revision}}" rel="stylesheet" media="screen">
<link href="{{ theme.uri("css/override.css") }}?v={{ version }}&rev={{revision}}" rel="stylesheet" media="screen">
<link href="{{ theme.uri("css/print.css") }}?v={{ version }}&rev={{revision}}" rel="stylesheet">
<link href="{{ url_for("library.font.css") }}" rel="stylesheet">
<!-- Copyright 2006-{{ 'now' | date('Y') }} Xibo Signage Ltd. Part of the Xibo Open Source Digital Signage Solution. Released under the AGPLv3 or later. -->
<!-- Please be sure you read this before removing the Source/About links from your theme: http://bit.ly/agplv3 -->
{% block headContent %}{% endblock %}
</head>
<body>
{% block content %}{% endblock %}
<script type="text/javascript" nonce="{{ cspNonce }}">
var CALENDAR_TYPE = "{{ settings.CALENDAR_TYPE }}";
var jsLocale = "{{ translate.jsLocale }}";
var jsShortLocale = "{{ translate.jsShortLocale }}";
</script>
{# Import JS bundle from dist #}
<script src="{{ theme.rootUri() }}dist/vendor.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
{# Import JS system tools #}
<script src="{{ theme.rootUri() }}dist/systemTools.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
{# Import JS templates #}
<script src="{{ theme.rootUri() }}dist/templates.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
{# Import XIBO js files #}
<script src="{{ theme.rootUri() }}dist/datatables.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
<script src="{{ theme.rootUri() }}dist/xibo.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
{# Dates #}
{% if settings.CALENDAR_TYPE == "Jalali" %}
<script src="{{ theme.rootUri() }}dist/vendor/calendar/js/calendar-jalali.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
<script type="text/javascript" nonce="{{ cspNonce }}">
moment.loadPersian();
</script>
{% else %}
<script src="{{ theme.rootUri() }}dist/vendor/calendar/js/calendar.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
{% endif %}
{# Handle the inclusion of i18n #}
{% set calendarTranslation %}dist/vendor/calendar/js/language/{{ translate.jsShortLocale }}.js{% endset %}
{% if theme.fileExists(calendarTranslation) %}
<script src="{{ theme.rootUri() }}{{ calendarTranslation }}" nonce="{{ cspNonce }}"></script>
{% endif %}
{% include "globalTranslations.twig" %}
{% include "globalVars.twig" %}
{% include "globalConfig.twig" %}
{% block javaScriptTemplates %}{% endblock %}
{% block javaScript %}{% endblock %}
{% include "theme-javascript.twig" ignore missing %}
</body>
</html>

294
views/campaign-builder.twig Normal file
View File

@@ -0,0 +1,294 @@
{#
/**
* 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 %}
{% import "forms.twig" as forms %}
{% block title %}{% set campaignName = campaign.campaign %}{% trans %}{{ campaignName }} - Campaign Builder{% endtrans %} | {% endblock %}
{% set hideNavigation = "1" %}
{% block pageContent %}
<div id="campaign-builder"
data-campaign-id="{{ campaign.campaignId }}">
<div class="back-button">
<a id="backBtn" class="btn btn-primary" href="{{ url_for("campaign.view") }}">
<i class="fa fa-angle-left"></i>
<span>{{ "Back"|trans }}</span>
</a>
</div>
<div class="widget mt-3">
<div class="widget-body">
<div class="row">
<div class="col-12">
<div class="campaign-title">
<h1>{{ campaign.campaign }}</h1>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#tab-general" role="tab" data-toggle="tab"><span>{% trans "General" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#tab-refs" role="tab" data-toggle="tab"><span>{% trans "Reference" %}</span></a></li>
</ul>
<form id="campaign-builder-form-details" class="XiboForm form-horizontal"
method="put"
action="{{ url_for("campaign.edit", {id: campaign.campaignId}) }}">
<div class="tab-content">
<div class="tab-pane active" id="tab-general">
{% set title %}{% trans "Name" %}{% endset %}
{% set helpText %}{% trans "The Name for this Campaign" %}{% endset %}
{{ forms.input("name", title, campaign.campaign, helpText) }}
{% set title %}{% trans "Start Date" %}{% endset %}
{% set helpText %}{% trans "Select the start date for this campaign" %}{% endset %}
{{ forms.date("startDt", title, campaign.getStartDt(), helpText, "starttime-control", "required", "") }}
{% set title %}{% trans "End Date" %}{% endset %}
{% set helpText %}{% trans "Select the end date for this campaign" %}{% endset %}
{{ forms.date("endDt", title, campaign.getEndDt(), helpText, "endtime-control", "required", "") }}
{% set title %}{% trans "Display" %}{% endset %}
{% set helpText %}{% trans "Please select one or more displays / groups for this event to be shown on." %}{% endset %}
{% set attributes = [
{ name: "data-search-url", value: url_for("displayGroup.search") },
{ name: "data-trans-groups", value: "Groups"|trans },
{ name: "data-trans-display", value: "Display"|trans },
{ name: "data-search-term", value: "displayGroup" },
{ name: "data-id-property", value: "displayGroupId" },
{ name: "data-text-property", value: "displayGroup" },
] %}
{{ forms.dropdown("displayGroupIds[]", "dropdownmulti", title, displayGroupIds, displayGroups, "displayGroupId", "displayGroup", helpText, "", "", "", "", attributes) }}
{% set title %}{% trans "Target Type" %}{% endset %}
{% set helpText %}{% trans "How would you like to set the target for this campaign?" %}{% endset %}
{% set options = [
{ id: "plays", name: "Plays"|trans },
{ id: "budget", name: "Budget"|trans },
{ id: "imp", name: "Impressions"|trans },
] %}
{{ forms.dropdown("targetType", "single", title, campaign.targetType, options, "id", "name", helpText, "campaign-type-ad") }}
{% set title %}{% trans "Target" %}{% endset %}
{% set helpText %}{% trans "What is the target number for this Campaign over its entire playtime" %}{% endset %}
{{ forms.number("target", title, campaign.target, helpText) }}
{% if currentUser.featureEnabled("tag.tagging") %}
{% set title %}{% trans "Tags" %}{% endset %}
{% set helpText %}{% trans "Tags for this Campaign - Comma separated string of Tags or Tag|Value format. If you choose a Tag that has associated values, they will be shown for selection below." %}{% endset %}
{{ forms.inputWithTags("tags", title, campaign.getTagString(), helpText, 'tags-with-value') }}
<p id="loadingValues" style="margin-left: 17%"></p>
{% set title %}{% trans "Tag value" %}{% endset %}
{{ forms.dropdown("tagValue", "single", title, "", options, "key", "value") }}
<div id="tagValueContainer">
{% set title %}{% trans "Tag value" %}{% endset %}
{% set helpText %}{% trans "Please provide the value for this Tag and confirm by pressing enter on your keyboard." %}{% endset %}
{{ forms.input("tagValueInput", title, "", helpText) }}
</div>
<div id="tagValueRequired" class="alert alert-info">
<p>{% trans "This tag requires a set value, please select one from the Tag value dropdown or provide Tag value in the dedicated field." %}</p>
</div>
{% endif %}
</div>
<div class="tab-pane" id="tab-refs">
{{ forms.message("Add reference fields if needed"|trans) }}
{% set title %}{% trans "Reference 1" %}{% endset %}
{{ forms.input("ref1", title, campaign.ref1, null) }}
{% set title %}{% trans "Reference 2" %}{% endset %}
{{ forms.input("ref2", title, campaign.ref2, null) }}
{% set title %}{% trans "Reference 3" %}{% endset %}
{{ forms.input("ref3", title, campaign.ref3, null) }}
{% set title %}{% trans "Reference 4" %}{% endset %}
{{ forms.input("ref4", title, campaign.ref4, null) }}
{% set title %}{% trans "Reference 5" %}{% endset %}
{{ forms.input("ref5", title, campaign.ref5, null) }}
</div>
</div>
{{ forms.button("Save"|trans, "submit", null, null, null, "btn-success") }}
</form>
</div>
<div class="col-lg-6">
<div class="row">
<div class="col-md-12">
{# Layouts #}
{% set attributes = [
{ name: "data-search-url", value: url_for("layout.search") },
{ name: "data-search-term", value: "layout" },
{ name: "data-search-term-tags", value: "tags" },
{ name: "data-trans-layout", value: "Layout"|trans },
{ name: "data-id-property", value: "layoutId" },
{ name: "data-text-property", value: "layout" },
{ name: "data-placeholder--id", value: null },
{ name: "data-placeholder--value", value: "Add a layout"|trans },
] %}
{% set title %}{% trans "Add Layout" %}{% endset %}
{% set helpText %}{% trans "Please select a Layout to add to this Campaign" %}{% endset %}
{{ forms.dropdown("layoutId", "single", title, event.campaignId, null, "campaignId", "campaign", helpText, "layout-control", "", "", "", attributes) }}
</div>
</div>
<div class="row">
<div class="col-md-12">
<table id="table-campaign-builder-layout-assignments"
data-search-url="{{ url_for("campaign.search") }}?campaignId={{ campaign.campaignId }}&embed=layouts"
data-assignment-delete-url="{{ url_for("campaign.layout.remove.form", {id: campaign.campaignId}) }}"
class="table table-striped">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Name" %}</th>
<th>{% trans "Duration" %}</th>
<th>{% trans "Day Parts" %}</th>
<th>{% trans "Days of the week" %}</th>
<th>{% trans "Geofence" %}</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="row">
<div class="col-md-12">
<h5>Time - {{ stats.complete }}%</h5>
<div class="progress">
<div class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar"
aria-valuenow="{{ stats.complete }}"
aria-valuemin="0"
aria-valuemax="100"
style="width: {{ stats.complete }}%"></div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<h5>Target - {{ stats.target }}%</h5>
<div class="progress">
<div class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar"
aria-valuenow="{{ stats.target }}"
aria-valuemin="0"
aria-valuemax="100"
style="width: {{ stats.target }}%"></div>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="row">
<div class="col-md-12">
<table class="table table-striped">
<tbody>
<tr>
<td>{{ "Plays"|trans }}</td>
<td>{{ campaign.plays }}</td>
</tr>
<tr>
<td>{{ "Spend"|trans }}</td>
<td>{{ campaign.spend }}</td>
</tr>
<tr>
<td>{{ "Impressions"|trans }}</td>
<td>{{ campaign.impressions }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="alert alert-info">
{{ "Stats need to be enabled on the Displays and Layouts selected on this campaign"|trans }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javaScript %}
{# Initialise JS variables and translations #}
<script type="text/javascript" nonce="{{ cspNonce }}" defer>
{# JS variables #}
var campaignBuilderDefaultVars = {
campaignAssignLayoutURL: "{{ url_for('campaign.assign.layout', {id: campaign.campaignId}) }}",
campaignRemoveLayoutURL: "{{ url_for('campaign.remove.layout', {id: campaign.campaignId}) }}",
defaultLat: "{{ settings.DEFAULT_LAT }}",
defaultLong: "{{ settings.DEFAULT_LONG }}",
dayPartSearchURL: "{{ url_for('daypart.search')}}" + "?isAlways=0&isCustom=0",
};
{# Custom translations #}
{% autoescape "js" %}
{# Insert custom translations here #}
var campaignBuilderTrans = {
saveButton: '{{ "Save"|trans }}',
cancelButton: '{{ "Cancel"|trans }}',
addLayoutFormTitle: '{{ "Add Layout"|trans }}',
editLayoutFormTitle: '{{ "Edit Layout"|trans }}',
assignmentEditButton: '{{ "Edit"|trans }}',
assignmentDeleteButton: '{{ "Delete"|trans }}',
daysOfWeek: {
monday: '{{ "Monday"|trans }}',
tuesday: '{{ "Tuesday"|trans }}',
wednesday: '{{ "Wednesday"|trans }}',
thursday: '{{ "Thursday"|trans }}',
friday: '{{ "Friday"|trans }}',
saturday: '{{ "Saturday"|trans }}',
sunday: '{{ "Sunday"|trans }}',
},
daysOfWeekDropdownTitle: '{{ "Days of the week"|trans }}',
daysOfWeekDropdownHelpText: '{{ "Which days of the week should the layout be active?"|trans }}',
dayPartDropdownTitle: '{{ "Dayparting"|trans }}',
dayPartDropdownHelpText:'{{ "Should this layout only be shown on selected day parts?"|trans }}',
builderMessage: '{{ "Draw areas on the map where you want this layout to play"|trans }}',
};
{% endautoescape %}
</script>
<script src="{{ theme.rootUri() }}dist/leaflet.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
<script src="{{ theme.rootUri() }}dist/campaignBuilder.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
{% endblock %}

View 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Add Campaign" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, campaignFormSubmit($('#campaignAddForm'))
{% endblock %}
{% block callBack %}campaignAddFormOpen{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="campaignAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("campaign.add") }}"
data-submit-call-back="campaignAddFormSubmitCallback"
data-edit-form-url="{{ url_for("campaign.edit.form", {id: ":id"}) }}"
data-gettag="{{ url_for("tag.getByName") }}">
{% if currentUser.featureEnabled('folder.view') %}
<div class="form-group row">
<label class="col-sm-2 control-label">{% trans "Folder" %}</label>
<div class="col-sm-10">
<button type="button" class="btn btn-info" id="select-folder-button" data-toggle="modal" data-target="#folder-tree-form-modal">{% trans "Select Folder" %}</button>
<span id="selectedFormFolder"></span>
</div>
</div>
{{ forms.hidden('folderId') }}
{% endif %}
{% if currentUser.featureEnabled('ad.campaign') %}
{% set title %}{% trans "Type" %}{% endset %}
{% set helpText %}{% trans "What type of Campaign would you like to create?" %}{% endset %}
{% set options = [
{ id: "list", name: "Layout list"|trans },
{ id: "ad", name: "Ad Campaign"|trans }
] %}
{{ forms.dropdown("type", "single", title, "both", options, "id", "name", helpText) }}
{% endif %}
{% set title %}{% trans "Name" %}{% endset %}
{% set helpText %}{% trans "The Name for this Campaign" %}{% endset %}
{{ forms.input("name", title, "", helpText) }}
{% if currentUser.featureEnabled("tag.tagging") %}
{% set title %}{% trans "Tags" %}{% endset %}
{% set helpText %}{% trans "Tags for this Campaign - Comma separated string of Tags or Tag|Value format. If you choose a Tag that has associated values, they will be shown for selection below." %}{% endset %}
{{ forms.inputWithTags("tags", title, "", helpText, 'tags-with-value') }}
<p id="loadingValues" style="margin-left: 17%"></p>
{% set title %}{% trans "Tag value" %}{% endset %}
{{ forms.dropdown("tagValue", "single", title, "", options, "key", "value") }}
<div id="tagValueContainer">
{% set title %}{% trans "Tag value" %}{% endset %}
{% set helpText %}{% trans "Please provide the value for this Tag and confirm by pressing enter on your keyboard." %}{% endset %}
{{ forms.input("tagValueInput", title, "", helpText) }}
</div>
<div id="tagValueRequired" class="alert alert-info">
<p>{% trans "This tag requires a set value, please select one from the Tag value dropdown or provide Tag value in the dedicated field." %}</p>
</div>
{% endif %}
{% set title %}{% trans "Enable cycle based playback" %}{% endset %}
{% set helpText %}{% trans "When cycle based playback is enabled only 1 Layout from this Campaign will be played each time it is in a Schedule loop. The same Layout will be shown until the 'Play count' is achieved." %}{% endset %}
{{ forms.checkbox("cyclePlaybackEnabled", title, 0, helpText, "campaign-type-list", null, false, "input-cycle-playback-enabled") }}
{% set title %}{% trans "Play count" %}{% endset %}
{% set helpText %}{% trans "In cycle based playback, how many plays should each Layout have before moving on?" %}{% endset %}
{{ forms.number("playCount", title, "", helpText, "cycle-based-playback campaign-type-list") }}
{% set title %}{% trans "List play order" %}{% endset %}
{% set helpText %}{% trans "When this campaign is scheduled alongside another campaign with the same display order, how should the layouts in both campaigns be ordered?" %}{% endset %}
{% set options = [
{ id: "round", name: "Round-robin"|trans },
{ id: "block", name: "Block"|trans },
] %}
{{ forms.dropdown("listPlayOrder", "single", title, "round", options, "id", "name", helpText, "campaign-type-list no-cycle-based-playback") }}
{% set title %}{% trans "Target Type" %}{% endset %}
{% set helpText %}{% trans "How would you like to set the target for this campaign?" %}{% endset %}
{% set options = [
{ id: "plays", name: "Plays"|trans },
{ id: "budget", name: "Budget"|trans },
{ id: "imp", name: "Impressions"|trans },
] %}
{{ forms.dropdown("targetType", "single", title, "both", options, "id", "name", helpText, "campaign-type-ad") }}
{% set title %}{% trans "Target" %}{% endset %}
{% set helpText %}{% trans "What is the target number for this Campaign over its entire playtime" %}{% endset %}
{{ forms.number("target", title, null, helpText, "campaign-type-ad") }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,48 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% set name = campaign.campaign %}
{% trans %}Copy {{ name }}{% endtrans %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#campaignCopyForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="campaignCopyForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("campaign.copy", {id: campaign.campaignId}) }}">
{% set title %}{% trans "Name" %}{% endset %}
{% set helpText %}{% trans "The Name for this Campaign" %}{% endset %}
{% set name %}{{ campaign.campaign }} 2{% endset %}
{{ forms.input("name", title, name, helpText,"","required") }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Delete Campaign" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Yes" %}, $("#campaignDeleteForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="campaignDeleteForm" class="XiboForm form-horizontal" method="delete" action="{{ url_for("campaign.delete", {"id": campaign.campaignId}) }}">
{% set message %}{% trans "Are you sure you want to delete this campaign? This cannot be undone" %}{% endset %}
{{ forms.message(message) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,226 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% import "inline.twig" as inline %}
{% block formTitle %}
{% set campaignName = campaign.campaign %}
{% trans %}Edit Campaign "{{ campaignName }}"{% endtrans %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, campaignFormSubmit($('#campaignEditForm'))
{% endblock %}
{% block callBack %}campaignAssignLayoutsFormOpen{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#tab-general" role="tab" data-toggle="tab"><span>{% trans "General" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#tab-refs" role="tab" data-toggle="tab"><span>{% trans "Reference" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#tab-layouts" role="tab" data-toggle="tab"><span>{% trans "Layouts" %}</span></a></li>
</ul>
<form id="campaignEditForm" class="XiboForm form-horizontal" method="put"
action="{{ url_for("campaign.edit", {"id": campaign.campaignId}) }}"
data-gettag="{{ url_for("tag.getByName") }}">
<div class="tab-content">
<div class="tab-pane active" id="tab-general">
{% if currentUser.featureEnabled('folder.view') %}
<div class="form-group row">
<label class="col-sm-2 control-label">{% trans "Current Folder" %}</label>
<div class="col-sm-10" style="padding-top: 7px">
<span id="originalFormFolder"></span>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 control-label">{% trans "Move to Selected Folder:" %}</label>
<div class="col-sm-10">
<button type="button" class="btn btn-info" id="select-folder-button" data-toggle="modal" data-target="#folder-tree-form-modal">{% trans "Select Folder" %}</button>
<span id="selectedFormFolder"></span>
</div>
</div>
{{ forms.hidden('folderId', campaign.folderId) }}
{% endif %}
{% set title %}{% trans "Name" %}{% endset %}
{% set helpText %}{% trans "The Name for this Campaign" %}{% endset %}
{{ forms.input("name", title, campaign.campaign, helpText) }}
{% if currentUser.featureEnabled("tag.tagging") %}
{% set title %}{% trans "Tags" %}{% endset %}
{% set helpText %}{% trans "Tags for this Campaign - Comma separated string of Tags or Tag|Value format. If you choose a Tag that has associated values, they will be shown for selection below." %}{% endset %}
{{ forms.inputWithTags("tags", title, campaign.getTagString(), helpText, 'tags-with-value') }}
<p id="loadingValues" style="margin-left: 17%"></p>
{% set title %}{% trans "Tag value" %}{% endset %}
{{ forms.dropdown("tagValue", "single", title, "", options, "key", "value") }}
<div id="tagValueContainer">
{% set title %}{% trans "Tag value" %}{% endset %}
{% set helpText %}{% trans "Please provide the value for this Tag and confirm by pressing enter on your keyboard." %}{% endset %}
{{ forms.input("tagValueInput", title, "", helpText) }}
</div>
<div id="tagValueRequired" class="alert alert-info">
<p>{% trans "This tag requires a set value, please select one from the Tag value dropdown or provide Tag value in the dedicated field." %}</p>
</div>
{% endif %}
{% set title %}{% trans "Enable cycle based playback" %}{% endset %}
{% set helpText %}{% trans "When cycle based playback is enabled only 1 Layout from this Campaign will be played each time it is in a Schedule loop. The same Layout will be shown until the 'Play count' is achieved." %}{% endset %}
{{ forms.checkbox("cyclePlaybackEnabled", title, campaign.cyclePlaybackEnabled, helpText, "campaign-type-list", null, false, "input-cycle-playback-enabled") }}
{% set title %}{% trans "Play count" %}{% endset %}
{% set helpText %}{% trans "In cycle based playback, how many plays should each Layout have before moving on?" %}{% endset %}
{{ forms.number("playCount", title, campaign.playCount, helpText, "cycle-based-playback campaign-type-list") }}
{% set title %}{% trans "List play order" %}{% endset %}
{% set helpText %}{% trans "When this campaign is scheduled alongside another campaign with the same display order, how should the layouts in both campaigns be ordered?" %}{% endset %}
{% set options = [
{ id: "round", name: "Round-robin"|trans },
{ id: "block", name: "Block"|trans },
] %}
{{ forms.dropdown("listPlayOrder", "single", title, campaign.listPlayOrder, options, "id", "name", helpText, "campaign-type-list no-cycle-based-playback") }}
</div>
<div class="tab-pane" id="tab-refs">
{{ forms.message("Add reference fields if needed"|trans) }}
{% set title %}{% trans "Reference 1" %}{% endset %}
{{ forms.input("ref1", title, campaign.ref1, null) }}
{% set title %}{% trans "Reference 2" %}{% endset %}
{{ forms.input("ref2", title, campaign.ref2, null) }}
{% set title %}{% trans "Reference 3" %}{% endset %}
{{ forms.input("ref3", title, campaign.ref3, null) }}
{% set title %}{% trans "Reference 4" %}{% endset %}
{{ forms.input("ref4", title, campaign.ref4, null) }}
{% set title %}{% trans "Reference 5" %}{% endset %}
{{ forms.input("ref5", title, campaign.ref5, null) }}
</div>
<div class="tab-pane" id="tab-layouts">
<div id="assignLayouts"></div>
{{ forms.hidden("manageLayouts", 0) }}
<div class="row">
<div class="col-md-12 card p-3 mb-3 bg-light">
<div id="LayoutAssign" class="card p-3 mb-3 bg-light" data-url="{{ url_for("campaign.assign.layout", {id: campaign.campaignId}) }}">
<div>
<ul id="LayoutAssignSortable" data-layouts="{{layouts|json_encode()}}"></ul>
</div>
</div>
<div class="XiboGrid" id="{{ random() }}" data-grid-name="layoutAssignView">
<div class="layoutAssignFilterOptions XiboFilter">
<div class="form-inline">
{% set title %}{% trans "ID" %}{% endset %}
{{ inline.number("campaignId", title) }}
{% set title %}{% trans "Name" %}{% endset %}
{{ inline.input("layout", title) }}
{% 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 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", exactTagTitle, logicalOperatorTitle) }}
{% endif %}
{% if currentUser.featureEnabled("folder.view") %}
{% set title %}{% trans "Folder Filter" %}{% endset %}
{% set attributes = [
{ name: "data-width", value: "100%" },
{ name: "data-search-url", value: url_for("folders.search") },
{ name: "data-filter-options", value: '{"gridView":1}' },
{ name: "data-search-term", value: "folderName" },
{ name: "data-id-property", value: "folderId" },
{ name: "data-text-property", value: "text" },
{ name: "data-initial-key", value: "folderId" },
{ name: "data-allow-clear", value: "true" },
{ name: "data-placeholder--id", value: null },
{ name: "data-placeholder--value", value: "" }
] %}
{{ inline.dropdown("folderId", "single", title, "", null, "", "", helpText, "pagedSelect", "", "", "", attributes) }}
{% endif %}
{% set title %}{% trans "Owner" %}{% endset %}
{% set helpText %}{% trans "Show items owned by the selected 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-search-term-tags", value: "tags" },
{ name: "data-id-property", value: "userId" },
{ name: "data-text-property", value: "userName" },
{ name: "data-initial-key", value: "userId" },
] %}
{{ inline.dropdown("userId", "single", title, "", null, "userId", "userName", helpText, "pagedSelect", "", "", "", attributes) }}
{% set title %}{% trans "Orientation" %}{% endset %}
{% set option1 = "All"|trans %}
{% set option2 = "Landscape"|trans %}
{% set option3 = "Portrait"|trans %}
{% set values = [{id: '', value: option1}, {id: 'landscape', value: option2}, {id: 'portrait', value: option3}] %}
{{ inline.dropdown("orientation", "single", title, '', values, "id", "value") }}
</div>
</div>
<div class="XiboData card pt-3">
<table id="layoutAssignments" class="table table-striped"
style="width:100%"
data-state-preference-name="campaignLayoutAssignGrid">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Name" %}</th>
<th>{% trans "Status" %}</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,26 @@
{% extends "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% set layout = layout.layout %}
{% trans %}Remove {{ layout }}{% endtrans %}
{% endblock %}
{% block formButtons %}
{% trans "No" %}, XiboDialogClose()
{% trans "Yes" %}, $("#campaignRemoveLayout").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="campaignRemoveLayout" class="XiboForm form-horizontal" method="delete"
data-submit-call-back="refreshLayoutAssignmentsTable"
action="{{ url_for("campaign.remove.layout", {id: campaign.campaignId}) }}?layoutId={{ layout.layoutId }}&displayOrder={{ layout.displayOrder }}">
{% set message %}{% trans "Are you sure you want remove this layout from the campaign?" %}{% endset %}
{{ forms.message(message) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,59 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% set name = layout.layout %}
{% trans %} Layout {{ name }} {% endtrans %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#layoutSelectFolderForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="layoutSelectFolderForm" class="XiboForm form-horizontal" method="put" action="{{ url_for("campaign.selectfolder", {id: campaign.campaignId}) }}">
<div class="form-group row">
<label class="col-sm-2 control-label">{% trans "Current Folder" %}</label>
<div class="col-sm-10" style="padding-top: 7px">
<span id="originalFormFolder"></span>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 control-label">{% trans "Move to Selected Folder:" %}</label>
<div class="col-sm-10">
<div class="card p-3 mb-3 bg-light" id="container-folder-form-tree"></div>
</div>
</div>
{{ forms.hidden('folderId', campaign.folderId) }}
</form>
</div>
</div>
{% endblock %}

173
views/campaign-page.twig Normal file
View File

@@ -0,0 +1,173 @@
{#
/**
* 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 %}{{ "Campaigns"|trans }} | {% endblock %}
{% block actionMenu %}
<div class="widget-action-menu pull-right">
{% if currentUser.featureEnabled("campaign.add") %}
<button class="btn btn-success XiboFormButton" title="{% trans "Add a new Campaign" %}" href="{{ url_for("campaign.add.form") }}"> <i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Campaign" %}</button>
{% endif %}
<button class="btn btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
</div>
{% endblock %}
{% block pageContent %}
<div class="widget">
<div class="widget-title">{% trans "Campaigns" %}</div>
<div class="widget-body">
<div class="XiboGrid" id="{{ random() }}" data-grid-name="campaignView">
<div class="XiboFilter card mb-3 bg-light">
<div class="FilterDiv card-body" id="Filter">
<form class="form-inline">
{% set title %}{% trans "Name" %}{% endset %}
{{ inline.inputNameGrid('name', title) }}
{% 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 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", exactTagTitle, logicalOperatorTitle) }}
{% endif %}
{% set title %}{% trans "Layouts" %}{% endset %}
{% set values = [{id: 0, value: ""}, {id: 2, value: "Yes"}, {id: 1, value: "No"}] %}
{{ inline.dropdown("hasLayouts", "single", title, 0, values, "id", "value") }}
{{ inline.hidden("folderId") }}
{% set title %}{% trans "Layout ID" %}{% endset %}
{{ inline.number("layoutId", title, layoutId) }}
{% if currentUser.featureEnabled('ad.campaign') %}
{% set title %}{% trans "Type" %}{% endset %}
{% set options = [
{ id: null, name: "" },
{ id: "list", name: "Layout list"|trans },
{ id: "ad", name: "Ad Campaign"|trans }
] %}
{{ inline.dropdown("type", "single", title, "both", options, "id", "name", helpText) }}
{% endif %}
{% set title %}{% trans "Cycle Based Playback" %}{% endset %}
{% set enabled %}{% trans "Enabled" %}{% endset %}
{% set disabled %}{% trans "Disabled" %}{% endset %}
{% set options = [
{ optionid: "", option: "" },
{ optionid: 0, option: disabled},
{ optionid: 1, option: enabled}
] %}
{{ inline.dropdown("cyclePlaybackEnabled", "single", title, "", options, "optionid", "option") }}
</form>
</div>
</div>
<div class="grid-with-folders-container">
<div class="grid-folder-tree-container p-3" id="grid-folder-filter">
<input id="jstree-search" class="form-control" type="text" placeholder="{% trans "Search" %}">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="folder-tree-clear-selection-button">
<label class="form-check-label" for="folder-tree-clear-selection-button" title="{% trans "Search in all folders" %}">{% trans "All Folders" %}</label>
</div>
<div class="folder-search-no-results d-none">
<p>{% trans 'No Folders matching the search term' %}</p>
</div>
<div id="container-folder-tree"></div>
</div>
<div class="folder-controller d-none">
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
<div id="breadcrumbs" class="mt-2 pl-2"></div>
</div>
<div id="datatable-container">
<div class="XiboData card py-3">
<table id="campaigns" class="table table-striped" data-content-type="campaign" data-content-id-name="campaignId" data-state-preference-name="campaignGrid" style="width: 100%;">
<thead>
<tr>
<th>{% trans "Name" %}</th>
{% if currentUser.featureEnabled('ad.campaign') %}
<th>{% trans "Type" %}</th>
<th>{% trans "Start Date" %}</th>
<th>{% trans "End Date" %}</th>
{% endif %}
<th>{% trans "# Layouts" %}</th>
{% if currentUser.featureEnabled("tag.tagging") %}<th>{% trans "Tags" %}</th>{% endif %}
<th>{% trans "Duration" %}</th>
<th>{% trans "Cycle based Playback" %}</th>
<th>{% trans "Play Count" %}</th>
{% if currentUser.featureEnabled('ad.campaign') %}
<th>{% trans "Target Type" %}</th>
<th>{% trans "Target" %}</th>
<th>{% trans "Plays" %}</th>
<th>{% trans "Spend" %}</th>
<th>{% trans "Impressions" %}</th>
{% endif %}
<th>{% trans "Ref 1" %}</th>
<th>{% trans "Ref 2" %}</th>
<th>{% trans "Ref 3" %}</th>
<th>{% trans "Ref 4" %}</th>
<th>{% trans "Ref 5" %}</th>
<th>{% trans "Created At" %}</th>
<th>{% trans "Modified At" %}</th>
<th>{% trans "Modified By" %}</th>
<th class="rowMenu"></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javaScript %}
{# Initialise JS variables and translations #}
<script type="text/javascript" nonce="{{ cspNonce }}" defer>
{# JS variables #}
var campaignSearchURL = "{{ url_for('campaign.search') }}";
var layoutSearchURL = "{{ url_for('layout.search') }}";
var folderViewEnabled = "{{ currentUser.featureEnabled('folder.view') }}";
var adCampaignEnabled = "{{ currentUser.featureEnabled('ad.campaign') }}";
var taggingEnabled = "{{ currentUser.featureEnabled('tag.tagging') }}";
{# Custom translations #}
var campaignPageTrans = {
list: "{% trans "List" %}",
ad: "{% trans "Ad" %}",
plays: "{% trans "Plays" %}",
budget: "{% trans "Budget" %}",
impressions: "{% trans "Impressions" %}",
};
</script>
{# Add page source code bundle #}
<script src="{{ theme.rootUri() }}dist/pages/campaign-page.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
{% endblock %}

112
views/campaign-preview.twig Normal file
View File

@@ -0,0 +1,112 @@
{#
/**
* 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 pageContent %}
<div class="widget">
<div class="widget-title">
{% set campaignName = campaign.campaign %}
{% if campaign.isLayoutSpecific %}
{% trans %}Layout Preview for {{ campaignName }}{% endtrans %}
{% else %}
{% trans %}Campaign Preview for {{ campaignName }}{% endtrans %}
{% endif %}
</div>
<div class="widget-body">
<p><b>{% trans "total duration" %}</b> {{ duration|datehms }} <i>({% trans "hours:min:sec" %})</i></p>
<p><b>{% trans "number of layouts" %}</b> : {{ campaign.numberLayouts }} </p>
<div class="row clearfix">
{% for extendedLayout in extendedLayouts %}
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="row">
<div class="col-sm-12">
<div class="embed-responsive embed-responsive-4by3">
<div class="embed-responsive-item preview-container"
data-url="{{ url_for('layout.preview', { "id": extendedLayout.layout.layoutId }) }}">
<div id="preview_canvas_container_{{extendedLayout.layout.layoutId}}" style="width: 100%; height: 100%;"></div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-8">
<small>{% trans "id" %}:</small> {{ extendedLayout.layout.layoutId }}<br/>
<small>{% trans "name" %}:</small> {{ extendedLayout.layout.layout }}<br/>
<small>{% trans "duration" %}:</small> {{ extendedLayout.duration|datehms }}<br/>
</div>
<div class="col-sm-4">
<a class="btn btn-white" href="{{ url_for("layout.preview", {id: extendedLayout.layout.layoutId}) }}" target="_blank">
{% trans "Open full screen" %}
<span class="fa fa-tablet"></span>
</a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endblock %}
{% block javaScript %}
<script src="{{ theme.rootUri() }}dist/preview.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
<script src="{{ theme.rootUri() }}dist/vendor/html5preloader/html5Preloader.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
<script type="text/javascript" nonce="{{ cspNonce }}">
var previewTranslations = {};
// Translations we want always available
{% autoescape "js" %}
previewTranslations.actionControllerTitle = "{{ "Webhook Controller"|trans }}";
previewTranslations.navigateToLayout = "{{ "Navigate to layout with code [layoutTag]?"|trans }}";
previewTranslations.emptyRegionMessage = "{{ "Empty region!"|trans }}";
previewTranslations.nextItem = "{{ "Next Item"|trans }}";
previewTranslations.previousItem = "{{ "Previous Item"|trans }}";
previewTranslations.navWidget = "{{ "Navigate to Widget"|trans }}";
previewTranslations.navLayout = "{{ "Navigate to Layout"|trans }}";
previewTranslations.widgetId = "{{ "Widget ID"|trans }}";
previewTranslations.layoutCode = "{{ "Layout Code"|trans }}";
previewTranslations.target = "{{ "Target"|trans }}";
{% endautoescape %}
(function($){
$(document).ready(function(){
{% for extendedLayout in extendedLayouts %}
var iframe = $('<iframe>', {
src: '{{ url_for("layout.preview", {id: extendedLayout.layout.layoutId}) }}', // URL to load in the iframe
id: 'player_frame_{{extendedLayout.layout.layoutId}}',
frameborder: 0,
css: {
position: 'absolute',
'pointer-events': 'unset',
top: 0,
left: 0
}
});
// Append the iframe to the container
$('#preview_canvas_container_{{extendedLayout.layout.layoutId}}').append(iframe);
{% endfor %}
});
}(jQuery));
</script>
{% endblock %}

View File

@@ -0,0 +1,98 @@
{#
/**
* Copyright (C) 2025 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 Command" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#commandAddForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#general" role="tab" data-toggle="tab"><span>{% trans "General" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#description" role="tab" data-toggle="tab"><span>{% trans "Description" %}</span></a></li>
</ul>
<form id="commandAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("command.add") }}">
<div class="tab-content">
<div class="tab-pane active" id="general">
{% set title %}{% trans "Name" %}{% endset %}
{% set helpText %}{% trans "The Name for this Command" %}{% endset %}
{{ forms.input("command", title, "", helpText, "", "required") }}
{% set title %}{% trans "Code" %}{% endset %}
{% set helpText %}{% trans "A reference code for this command which is used to identify the command internally." %}{% endset %}
{{ forms.input("code", title, "", helpText, "", "required") }}
{% set fieldId = "commandString" %}
{% set title %}{% trans "Command" %}{% endset %}
{% set helpText %}{% trans "The Command String for this Command. An override for this can be provided in Display Settings." %}{% endset %}
{{ forms.input(fieldId, title, "", helpText, "XiboCommand") }}
{% set fieldId = "validationString" %}
{% set title %}{% trans "Validation" %}{% endset %}
{% set helpText %}{% trans "The Validation String for this Command. An override for this can be provided in Display Settings." %}{% endset %}
{{ forms.input(fieldId, title, "", helpText) }}
{% set options = [
{ optionid: "android", option: "Android" },
{ optionid: "chromeOS", option: "ChromeOS" },
{ optionid: "linux", option: "Linux" },
{ optionid: "sssp", option: "Tizen" },
{ optionid: "lg", option: "webOS" },
{ optionid: "windows", option: "Windows" },
] %}
{% set title %}{% trans "Available on" %}{% endset %}
{% set helpText %}{% trans "Leave empty if this command should be available on all types of Display." %}{% endset %}
{{ forms.dropdown("availableOn[]", "dropdownmulti", title, "", options, "optionid", "option", helpText, "selectPicker") }}
{% set options = [
{ optionid: "never", option: "Never" },
{ optionid: "success", option: "Success" },
{ optionid: "failure", option: "Failure" },
{ optionid: "always", option: "Always" },
] %}
{% set title %}{% trans "Create Alert On" %}{% endset %}
{% set helpText %}{% trans "On command execution, when should a Display alert be created?" %}{% endset %}
{{ forms.dropdown("createAlertOn", "single", title, "never", options, "optionid", "option", helpText) }}
</div>
<div class="tab-pane" id="description">
{% set title %}{% trans "Description" %}{% endset %}
{% set helpText %}{% trans "This should be a textual description of what the command is trying to achieve. It should not be the command string." %}{% endset %}
{{ forms.textarea("description", title, "", helpText, "", "", 10) }}
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Delete Command" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Yes" %}, $("#commandDeleteForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="commandDeleteForm" class="XiboForm form-horizontal" method="delete" action="{{ url_for("command.delete", {"id": command.commandId}) }}">
{% set message %}{% trans "Are you sure you want to delete this command? This cannot be undone" %}{% endset %}
{{ forms.message(message) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,100 @@
{#
/**
* Copyright (C) 2025 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 "Edit Command" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#commandEditForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#general" role="tab" data-toggle="tab"><span>{% trans "General" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#description" role="tab" data-toggle="tab"><span>{% trans "Description" %}</span></a></li>
</ul>
<form id="commandEditForm" class="XiboForm form-horizontal" method="put" action="{{ url_for("command.edit", {id: command.commandId}) }}">
<div class="tab-content">
<div class="tab-pane active" id="general">
{% set title %}{% trans "Name" %}{% endset %}
{% set helpText %}{% trans "The Name for this Command" %}{% endset %}
{{ forms.input("command", title, command.command, helpText, "", "required") }}
{% set title %}{% trans "Reference" %}{% endset %}
{% set helpText %}{% trans "A reference code for this command which is used to identify the command internally." %}{% endset %}
{{ forms.disabled("code", title, command.code, helpText, "", "required") }}
{% set fieldId = "commandString" %}
{% set title %}{% trans "Command" %}{% endset %}
{% set helpText %}{% trans "The Command String for this Command. An override for this can be provided in Display Settings." %}{% endset %}
{{ forms.input(fieldId, title, command.commandString, helpText, "XiboCommand") }}
{% set fieldId = "validationString" %}
{% set title %}{% trans "Validation" %}{% endset %}
{% set helpText %}{% trans "The Validation String for this Command. An override for this can be provided in Display Settings." %}{% endset %}
{{ forms.input(fieldId, title, command.validationString, helpText) }}
{% set options = [
{ optionid: "android", option: "Android" },
{ optionid: "chromeOS", option: "ChromeOS" },
{ optionid: "linux", option: "Linux" },
{ optionid: "sssp", option: "Tizen" },
{ optionid: "lg", option: "webOS" },
{ optionid: "windows", option: "Windows" },
] %}
{% set title %}{% trans "Available on" %}{% endset %}
{% set helpText %}{% trans "Leave empty if this command should be available on all types of Display." %}{% endset %}
{{ forms.dropdown("availableOn[]", "dropdownmulti", title, command.getAvailableOn(), options, "optionid", "option", helpText, "selectPicker") }}
{% set options = [
{ optionid: "never", option: "Never" },
{ optionid: "success", option: "Success" },
{ optionid: "failure", option: "Failure" },
{ optionid: "always", option: "Always" },
] %}
{% set title %}{% trans "Create Alert On" %}{% endset %}
{% set helpText %}{% trans "On command execution, when should a Display alert be created?" %}{% endset %}
{{ forms.dropdown("createAlertOn", "single", title, command.createAlertOn, options, "optionid", "option", helpText) }}
</div>
<div class="tab-pane" id="description">
{% set title %}{% trans "Description" %}{% endset %}
{% set helpText %}{% trans "This should be a textual description of what the command is trying to achieve. It should not be the command string." %}{% endset %}
{{ forms.textarea("description", title, command.description, helpText, "", "", 10) }}
</div>
</div>
</form>
</div>
</div>
{% endblock %}

149
views/command-page.twig Normal file
View File

@@ -0,0 +1,149 @@
{#
/**
* Copyright (C) 2020-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 %}{{ "Commands"|trans }} | {% endblock %}
{% block actionMenu %}
<div class="widget-action-menu pull-right">
{% if currentUser.featureEnabled("command.add") %}
<button class="btn btn-success XiboFormButton" href="{{ url_for("command.add.form") }}"><i class="fa fa-terminal" aria-hidden="true"></i> {% trans "Add Command" %}</button>
{% endif %}
<button class="btn btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
</div>
{% endblock %}
{% block pageContent %}
<div class="widget">
<div class="widget-title">{% trans "Commands" %}</div>
<div class="widget-body">
<div class="XiboGrid" id="{{ random() }}">
<div class="XiboFilter card mb-3 bg-light">
<div class="FilterDiv card-body" id="Filter">
<form class="form-inline">
{% set title %}{% trans "Name" %}{% endset %}
{{ inline.inputNameGrid('command', title) }}
{% set title %}{% trans "Code" %}{% endset %}
{{ inline.inputNameGrid('code', title, null, 'useRegexForCode', 'logicalOperatorCode') }}
</form>
</div>
</div>
<div class="XiboData card pt-3">
<table id="commands" class="table table-striped" data-state-preference-name="commandGrid">
<thead>
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Code" %}</th>
<th>{% trans "Available On" %}</th>
<th>{% trans "Description" %}</th>
<th>{% trans "Sharing" %}</th>
<th class="rowMenu"></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javaScript %}
<script type="text/javascript" nonce="{{ cspNonce }}">
var table = $("#commands").DataTable({ "language": dataTablesLanguage,
dom: dataTablesTemplate,
serverSide: true,
stateSave: true,
stateDuration: 0,
responsive: true,
stateLoadCallback: dataTableStateLoadCallback,
stateSaveCallback: dataTableStateSaveCallback,
filter: false,
searchDelay: 3000,
"order": [[ 1, "asc"]],
ajax: {
"url": "{{ url_for("command.search") }}",
"data": function(d) {
$.extend(d, $("#commands").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
}
},
"columns": [
{ "data": "command", "render": dataTableSpacingPreformatted , responsivePriority: 2},
{ "data": "code" , responsivePriority: 2},
{
"data": "availableOn",
responsivePriority: 3,
"render": function(data, type) {
if (type !== "display")
return data;
var returnData = '';
if (typeof data !== undefined && data != null) {
var arrayOfTags = data.split(',');
returnData += '<div class="permissionsDiv">';
for (var i = 0; i < arrayOfTags.length; i++) {
var name = arrayOfTags[i];
if (name !== '') {
returnData += '<li class="badge ' + ((name === 'lg') ? '' : 'capitalize') + '">'
+ name.replace("lg", "webOS").replace("sssp", "Tizen") + '</span></li>'
}
}
returnData += '</div>';
}
return returnData;
}
},
{ "data": "description", responsivePriority: 3 },
{
"data": "groupsWithPermissions",
responsivePriority: 3,
"render": dataTableCreatePermissions
},
{
"orderable": false,
responsivePriority: 1,
"data": dataTableButtonsColumn
}
]
});
table.on('draw', dataTableDraw);
table.on('processing.dt', dataTableProcessing);
dataTableAddButtons(table, $('#commands_wrapper').find('.dataTables_buttons'));
$("#refreshGrid").click(function () {
table.ajax.reload();
});
</script>
{% endblock %}

View File

@@ -0,0 +1,61 @@
{#
/**
* 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 "Configure Connector" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, connectorFormSubmit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="connectorEditForm" class="form-horizontal" method="put" action="{{ url_for("connector.edit", {id: connector.connectorId}) }}">
{% block connectorFormFields %}
{% endblock %}
{% set title %}{% trans "Enabled?" %}{% endset %}
{% set helpText %}{% trans "When enabled, this Connector will start providing the services it lists in its description." %}{% endset %}
{{ forms.checkbox("isEnabled", title, connector.isEnabled, helpText) }}
{% if not connector.isSystem %}
{% if connector.isInstalled %}
{% set title %}{% trans "Uninstall?" %}{% endset %}
{% set helpText %}{% trans "Tick to uninstall this Connector. All settings will be removed." %}{% endset %}
{{ forms.checkbox("shouldUninstall", title, false, helpText) }}
{% else %}
{{ forms.message("This connector will be installed when you save."|trans, "", "alert alert-info") }}
{% endif %}
{% endif %}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,100 @@
{#
/**
* 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 pageContent %}
{% include "theme-dashboard-message.twig" ignore missing %}
<div id="dashbuttons">
{% if currentUser.featureEnabled("schedule.view") %}
<div class="dashicons">
<a href="{{ url_for("schedule.view") }}">
<img src="{{ theme.uri("img/dashboard/scheduleview.png") }}" class="dash_button" />
<span class="dash_text">{% trans "Schedule" %}</span>
</a>
</div>
{% endif %}
{% if currentUser.featureEnabled("layout.view") %}
<div class="dashicons">
<a href="{{ url_for("layout.view") }}">
<img src="{{ theme.uri("img/dashboard/presentations.png") }}" class="dash_button" />
<span class="dash_text">{% trans "Layouts" %}</span>
</a>
</div>
{% endif %}
{% if currentUser.featureEnabled("library.view") %}
<div class="dashicons">
<a href="{{ url_for("library.view") }}">
<img src="{{ theme.uri("img/dashboard/content.png") }}" class="dash_button" />
<span class="dash_text">{% trans "Library" %}</span>
</a>
</div>
{% endif %}
{% if currentUser.featureEnabled("template.view") %}
<div class="dashicons">
<a href="{{ url_for("template.view") }}">
<img src="{{ theme.uri("img/dashboard/layouts.png") }}" class="dash_button" />
<span class="dash_text">{% trans "Templates" %}</span>
</a>
</div>
{% endif %}
{% if currentUser.featureEnabled("users.view") and (currentUser.isGroupAdmin() or currentUser.isSuperAdmin()) %}
<div class="dashicons">
<a href="{{ url_for("user.view") }}">
<img src="{{ theme.uri("img/dashboard/users.png") }}" class="dash_button" />
<span class="dash_text">{% trans "Users" %}</span>
</a>
</div>
{% endif %}
{% if currentUser.isSuperUser() %}
<div class="dashicons">
<a href="{{ url_for("admin.view") }}">
<img src="{{ theme.uri("img/dashboard/settings.png") }}" class="dash_button" />
<span class="dash_text">{% trans "Settings" %}</span>
</a>
</div>
{% endif %}
<div class="dashicons">
<a href="{{ url_for("license.view") }}">
<img src="{{ theme.uri("img/dashboard/license.png") }}" class="dash_button" />
<span class="dash_text">{% trans "About" %}</span>
</a>
</div>
{% if currentUser.featureEnabled("troubleshooting.fault") %}
<div class="dashicons">
<a href="{{ url_for("fault.view") }}">
<img src="{{ theme.uri("img/dashboard/help.png") }}" class="dash_button" />
<span class="dash_text">{% trans "Report Fault" %}</span>
</a>
</div>
{% endif %}
</div>
{% endblock %}

View File

@@ -0,0 +1,952 @@
{#
/**
* Copyright (C) 2021 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 pageContent %}
{% include "theme-dashboard-message.twig" ignore missing %}
<div class="row">
<div class="col-xl-3 col-md-6 col-12">
<div class="widget">
<div class="widget-body p-3 p-xl-2">
<div class="widget-icon orange pull-left">
<i class="fa fa-desktop"></i>
</div>
<div class="widget-content pull-left">
<div class="title displays-count">0</div>
<div class="comment displays-comment" data-title="{% trans "Display" %}" data-title-plural="{% trans "Displays" %}"></div>
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 col-12">
<div class="widget">
<div class="widget-body p-3 p-xl-2">
<div class="widget-icon red pull-left">
<i class="fa fa-tasks"></i>
</div>
<div class="widget-content pull-left">
<div class="title">{{ librarySize }}</div>
<div class="comment">{% trans "Library Size" %}</div>
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 col-12">
<div class="widget">
<div class="widget-body p-3 p-xl-2">
<div class="widget-icon green pull-left">
<i class="fa fa-users"></i>
</div>
<div class="widget-content pull-left">
<div class="title">{{ countUsers }}</div>
<div class="comment">{% if countUsers == 1 %}{% trans "User" %}{% else %}{% trans "Users" %}{% endif %}</div>
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 col-12">
<div class="widget">
<div class="widget-body p-3 p-xl-2">
<div class="widget-icon blue pull-left">
<i class="fa fa-cogs"></i>
</div>
<div class="widget-content pull-left">
{% if embeddedWidget != "" %}
{{ embeddedWidget|raw }}
{% else %}
<div class="title">{{ nowShowing }}</div>
<div class="comment">{% trans "Now Showing" %}</div>
{% endif %}
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="widget">
<div class="widget-title">
<i class="fa fa-cloud-download"></i>
{% if xmdsLimit != "" %}
{% trans %}Bandwidth Usage. Limit {{ xmdsLimit }}{% endtrans %}
{% else %}
{% trans %}Bandwidth Usage ({{ bandwidthSuffix }}){% endtrans %}
{% endif %}
{% if currentUser.featureEnabled("displays.reporting") %}
<a class="pull-right" href="/report/form/bandwidth">{% trans "More Statistics" %}</a>
{% endif %}
<div class="clearfix"></div>
</div>
<div class="widget-body medium no-padding">
<canvas id="bandwidthChart" style="clear:both;" height="230"></canvas>
</div>
</div>
</div>
<div class="col-lg-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>
<div class="row">
<div class="col-lg-6">
<div class="widget">
<div class="widget-title">
<i class="fa fa-desktop"></i>
{% trans "Display Activity" %}
<div class="clearfix"></div>
</div>
<div class="widget-body medium no-padding">
<div class="table-responsive">
<table id="displays" class="table">
<thead>
<tr>
<th>{% trans "Display" %}</th>
<th>{% trans "Logged In" %}</th>
<th>{% trans "Authorised" %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="widget news-widget">
<div class="widget-title">
<i class="fa fa-book"></i>
{% trans "Latest News" %}
<div class="clearfix"></div>
</div>
<div class="widget-body medium">
{% if latestNews|length > 0 %}
{% for news in latestNews %}
<div class="article">
<h4 class="article_title">{{ news.title }} <small class="article_date">{{ news.date }}</small></h4>
<p>{{ news.description|raw }} {% if news.link %}<a href="{{ news.link }}" title="Read" target="_blank">{% trans "Full Article" %}</a>.{% endif %}</p>
</div>
{% endfor %}
{% endif %}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="widget">
<div class="widget-title">
<i class="fa fa-desktop"></i>
{% trans "Display Status" %}
<div class="clearfix"></div>
</div>
<div class="widget-body medium no-padding" style="overflow: hidden;">
<div style="text-align: center; height: 10px; margin-bottom: 5px"><span>{% trans "Click on the chart for a breakdown" %}</span></div>
<div style="position: relative; height: 235px">
<canvas id="displayStatusChart" style="clear:both;"></canvas>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="widget">
<div class="widget-title">
<i class="fa fa-cloud-download"></i>
{% trans "Display Content Status" %}
<div class="clearfix"></div>
</div>
<div class="widget-body medium no-padding" style="overflow: hidden;">
<div style="text-align: center; height: 10px; margin-bottom: 5px"><span>{% trans "Click on the chart for a breakdown" %}</span></div>
<div style="position: relative; height: 235px">
<canvas id="displayContentChart" style="clear:both;"></canvas>
</div>
</div>
</div>
</div>
</div>
<div class="row d-none" id="displayGroupStatusChartRow">
<div class="col-lg-6">
<div class="widget">
<div class="widget-title">
<i class="fa fa-desktop"></i>
<span id="dGStatusTitle">{% trans "Display Groups Status" %}</span>
<div class="clearfix"></div>
</div>
<div class="widget-body medium no-padding" style="overflow: hidden;">
<div style="text-align: center; height: 10px; margin-bottom: 5px"><span>{% trans "Click on the chart to view Display information" %}</span></div>
<div style="position: relative; height: 235px;">
<canvas id="displayGroupStatusChart" style="clear:both;"></canvas>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="widget">
<div class="widget-title">
<i class="fa fa-desktop"></i>
<span id="dGContentTitle"> {% trans "Display Groups Content Status" %}</span>
<div class="clearfix"></div>
</div>
<div class="widget-body medium no-padding" style="overflow: hidden;">
<div style="text-align: center; height: 10px; margin-bottom: 5px"><span>{% trans "Click on the chart to view Display information" %}</span></div>
<div style="position: relative; height: 235px">
<canvas id="displayGroupContentStatusChart" style="clear:both;"></canvas>
</div>
</div>
</div>
</div>
</div>
<div class="widget d-none" id="displaysGridTable">
<div class="widget-title">{% trans "Displays" %}
{% if currentUser.featureEnabled("displays.view") %}
<a class="pull-right" href="{{ url_for('display.view') }}">{% trans "Displays Page" %}</a>
{% endif %}
</div>
<div class="widget-body">
<div class="XiboGrid" id="{{ random() }}" data-grid-name="displayGridView">
<div class="XiboFilter card mb-3 bg-light d-none">
<div class="FilterDiv card-body" id="Filter">
<form class="form-inline d-block">
<div class="tab-content">
<div class="tab-pane active" id="filter-general">
{{ inline.hidden("displayGroupId") }}
{{ inline.hidden("mediaInventoryStatus") }}
{{ inline.hidden("loggedIn") }}
</div>
</div>
</form>
</div>
</div>
<div class="XiboData card pt-3">
<table id="displaysGrid" class="table table-striped" data-state-preference-name="statusDashboardDisplays" style="width: 100%;">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Display" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Authorised?" %}</th>
<th>{% trans "Current Layout" %}</th>
<th>{% trans "Storage Available" %}</th>
<th>{% trans "Storage Total" %}</th>
<th>{% trans "Storage Free %" %}</th>
<th>{% trans "Description" %}</th><th>{% trans "Orientation" %}</th>
<th>{% trans "Resolution" %}</th>
{% if currentUser.featureEnabled("tag.tagging") %}<th>{% trans "Tags" %}</th>{% endif %}
<th>{% trans "Default Layout" %}</th>
<th>{% trans "Interleave Default" %}</th>
<th>{% trans "Email Alert" %}</th>
<th>{% trans "Logged In" %}</th>
<th>{% trans "Last Accessed" %}</th>
<th>{% trans "Display Profile" %}</th>
<th>{% trans "Version" %}</th>
<th>{% trans "Device Name" %}</th>
<th>{% trans "IP Address" %}</th>
<th>{% trans "Mac Address" %}</th>
<th>{% trans "Timezone" %}</th>
<th>{% trans "Latitude" %}</th>
<th>{% trans "Longitude" %}</th>
<th>{% trans "Screen shot?" %}</th>
<th>{% trans "Thumbnail" %}</th>
<th>{% trans "CMS Transfer?" %}</th>
<th>{% trans "Bandwidth Limit" %}</th>
<th>{% trans "Last Command" %}</th>
<th>{% trans "XMR Registered" %}</th>
<th>{% trans "Commercial Licence" %}</th>
<th>{% trans "Remote" %}</th>
<th>{% trans "Created Date" %}</th>
<th>{% trans "Modified Date" %}</th>
<th>{% trans "Faults?" %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javaScript %}
<script type="text/javascript" nonce="{{ cspNonce }}">
const stringToColour = function(str) {
let hash = 0;
str.split('').forEach(char => {
hash = char.charCodeAt(0) + ((hash << 5) - hash);
});
let colour = '#';
for (let i = 0; i < 3; i++) {
const value = (hash >> (i * 8)) & 0xff
colour += value.toString(16).padStart(2, '0');
}
return colour;
}
var displayGroupStatusChart = null;
var displayGroupContentStatusChart = null;
var displayGroupId = null;
var loggedInStatus = null;
var mediaInventoryStatus = null;
var displayGroupIdsContent = [];
var displayGroupIdsStatus = [];
var displayGridTable = null
// Create our chart
var bandwidthChart = new Chart($("#bandwidthChart"), {
type: "bar",
data: {{ bandwidthWidget|raw }},
options: {
scales: {
xAxes: [{
stacked: {% if xmdsLimit %}true{% else %}false{% endif %}
}],
yAxes: [{
scaleLabel: {
display: true,
labelString: "{{ bandwidthSuffix }}",
},
stacked: {% if xmdsLimit %}true{% else %}false{% endif %}
}]
},
legend: {
display: false
},
maintainAspectRatio: false,
}
});
var libraryData = {{ libraryWidgetData|raw }};
const libraryLabels = {{ libraryWidgetLabels|raw }};
var colours = new Array();
for (var i = 0; i < libraryData.length; i++) {
colours.push(stringToColour(libraryLabels[i]));
}
var libraryChart = new Chart($("#libraryChart"), {
type: 'pie',
data: {
datasets: [{
data: libraryData,
backgroundColor: colours
}],
labels: {{ libraryWidgetLabels|raw }}
},
options: {
maintainAspectRatio: false
}
});
$('.article_date').each(function(index, element) {
// Replace the ISO date with a nice formatted date "for humans"
const date = $(element).html();
if (date) {
$(element).html(moment(date, systemDateFormat).fromNow());
}
});
var table = $("#displays").DataTable({
"language": dataTablesLanguage,
serverSide: true,
stateSave: true,
responsive: true,
stateDuration: 0,
stateLoadCallback: dataTableStateLoadCallback,
stateSaveCallback: dataTableStateSaveCallback,
filter: false,
searchDelay: 3000,
order: [[0, "asc"]],
ajax: {
"url": "{{ url_for("statusdashboard.displays") }}",
"data": function (dataToSend) {
//make a new object so as not to destroy the input.
var data = {};
data.draw = dataToSend.draw;
data.length = dataToSend.length;
data.start = dataToSend.start;
data.order = dataToSend.order;
data.columns = [];
$.each(dataToSend.columns, function (index, e) {
var col = {};
col.data = e.data;
if (e.name != null && e.name !== "")
col.name = e.name;
data.columns.push(col);
});
$.extend(data, $("#displays").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
return data;
}
},
createdRow: function (row, data, index) {
if (data.mediaInventoryStatus === 1) {
$(row).addClass('table-success');
} else if (data.mediaInventoryStatus === 1) {
$(row).addClass('table-danger');
} else {
$(row).addClass('table-warning');
}
},
lengthChange: false,
"columns": [
{"data": "display"},
{"data": "loggedIn", "render": dataTableTickCrossColumn},
{"data": "licensed", "render": dataTableTickCrossColumn}
]
});
table.on('processing.dt', dataTableProcessing);
table.on('draw', function(e, settings) {
var $displaysComment = $(".displays-comment");
var total = table.page.info().recordsTotal;
$(".displays-count").html(total);
$displaysComment.html((total > 0) ? $displaysComment.data().titlePlural : $displaysComment.data().title)
});
var displayData {% if displayStatus|raw %}= {{ displayStatus|raw }}{% endif %};
var displayStatusChart = new Chart($("#displayStatusChart"), {
type: 'doughnut',
data: {
datasets: [{
data: displayData,
backgroundColor: ["rgb(0,255,0)", "rgb(255,0,0)"]
}],
labels: [translations.online, translations.offline]
},
options: {
maintainAspectRatio: false
}
});
var displayMediaStatusData {% if displayMediaStatus|raw %}= {{ displayMediaStatus|raw }}{% endif %};
var displayContentChart = new Chart($("#displayContentChart"), {
type: 'doughnut',
data: {
datasets: [{
data: displayMediaStatusData,
backgroundColor: ["rgb(0,255,0)", "rgb(255,0,0)"]
}],
labels: [translations.upToDate, translations.notUpToDate]
},
options: {
maintainAspectRatio: false
}
});
$("#displayStatusChart").click(function(evt){
var activePoints = displayStatusChart.getElementsAtEvent(evt);
if (activePoints[0]) {
var chartData = activePoints[0]['_chart'].config.data;
var index = activePoints[0]['_index'];
var label = chartData.labels[index];
if (label == translations.online) {
loggedInStatus = 1;
$('#dGStatusTitle').css('color', 'rgb(0,255,0)')
} else {
loggedInStatus = 0;
$('#dGStatusTitle').css('color', 'rgb(255,0,0)')
}
if (displayGroupStatusChart !== undefined && displayGroupStatusChart !== null) {
displayGroupStatusChart.destroy();
}
$.ajax({
type: "get",
url: "{{ url_for("statusdashboard.displayGroups") }}",
data: {
status: label
},
success: function (result) {
if (result.success === true) {
var displayGroupNames = JSON.parse(result.data.displayGroupNames);
var displaysAssigned = JSON.parse(result.data.displayGroupMembers);
displayGroupIdsStatus = JSON.parse(result.data.displayGroupIds);
var coloursDG = [];
for(var i = 0; i < displayGroupNames.length; i++) {
coloursDG.push($c.rand());
}
displayGroupStatusChart = new Chart($("#displayGroupStatusChart"), {
type: 'doughnut',
data: {
datasets: [{
data: displaysAssigned,
backgroundColor: coloursDG
}],
labels: displayGroupNames
},
options: {
maintainAspectRatio: false,
}
});
$("#displayGroupStatusChartRow").removeClass('d-none');
}
}
});
}
}
);
$("#displayContentChart").click(function(evt){
var activePoints = displayContentChart.getElementsAtEvent(evt);
if (activePoints[0]) {
var chartData = activePoints[0]['_chart'].config.data;
var index = activePoints[0]['_index'];
var label = chartData.labels[index];
if (label == translations.upToDate) {
mediaInventoryStatus = 1;
$('#dGContentTitle').css('color', 'rgb(0,255,0)')
} else {
mediaInventoryStatus = -1;
$('#dGContentTitle').css('color', 'rgb(255,0,0)')
}
if (displayGroupContentStatusChart !== undefined && displayGroupContentStatusChart !== null) {
displayGroupContentStatusChart.destroy();
}
$.ajax({
type: "get",
url: "{{ url_for("statusdashboard.displayGroups") }}",
data: {
inventoryStatus: label
},
success: function (result) {
if (result.success === true) {
var displayGroupNames = JSON.parse(result.data.displayGroupNames);
var displaysAssigned = JSON.parse(result.data.displayGroupMembers);
displayGroupIdsContent = JSON.parse(result.data.displayGroupIds);
var coloursDG = [];
for(var i = 0; i < displayGroupNames.length; i++) {
coloursDG.push($c.rand());
}
displayGroupContentStatusChart = new Chart($("#displayGroupContentStatusChart"), {
type: 'doughnut',
data: {
datasets: [{
data: displaysAssigned,
backgroundColor: coloursDG
}],
labels: displayGroupNames
},
options: {
maintainAspectRatio: false
}
});
$("#displayGroupStatusChartRow").removeClass('d-none');
}
}
});
}
}
);
$("#displayGroupStatusChart").click(function(evt) {
var activePoints = displayGroupStatusChart.getElementsAtEvent(evt);
$('#displayGroupId').val("");
$('#mediaInventoryStatus').val("");
$('#loggedIn').val("");
if (activePoints[0]) {
var chartData = activePoints[0]['_chart'].config.data;
var index = activePoints[0]['_index'];
displayGroupId = displayGroupIdsStatus[index];
$('#displayGroupId').val(displayGroupId);
$('#loggedIn').val(loggedInStatus);
handleDisplaysGrid();
$("#displaysGridTable").removeClass('d-none');
}
}
);
$("#displayGroupContentStatusChart").click(function(evt){
var activePoints = displayGroupContentStatusChart.getElementsAtEvent(evt);
if (activePoints[0]) {
$('#displayGroupId').val("");
$('#mediaInventoryStatus').val("");
$('#loggedIn').val("");
var chartData = activePoints[0]['_chart'].config.data;
var index = activePoints[0]['_index'];
displayGroupId = displayGroupIdsContent[index];
$('#displayGroupId').val(displayGroupId);
$('#mediaInventoryStatus').val(mediaInventoryStatus);
handleDisplaysGrid();
$("#displaysGridTable").removeClass('d-none');
}
}
);
function handleDisplaysGrid() {
if (displayGridTable != null) {
displayGridTable.ajax.reload();
} else {
displayGridTable = $("#displaysGrid").DataTable({
"language": dataTablesLanguage,
dom: dataTablesTemplate,
serverSide: true,
stateSave: true,
stateDuration: 0,
stateLoadCallback: dataTableStateLoadCallback,
stateSaveCallback: dataTableStateSaveCallback,
filter: false,
searchDelay: 3000,
responsive: true,
order: [[1, "asc"]],
ajax: {
"url": "{{ url_for("display.search") }}",
"data": function (dataToSend) {
//make a new object so as not to destroy the input.
var data = {};
data.draw = dataToSend.draw;
data.length = dataToSend.length;
data.start = dataToSend.start;
data.order = dataToSend.order;
data.columns = [];
$.each(dataToSend.columns, function (index, e) {
var col = {};
col.data = e.data;
if (e.name != null && e.name != "")
col.name = e.name;
data.columns.push(col);
});
$.extend(data, $("#displaysGrid").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
return data;
}
},
createdRow: function (row, data, index) {
if (data.mediaInventoryStatus === 1) {
$(row).addClass('table-success');
} else if (data.mediaInventoryStatus === 1) {
$(row).addClass('table-danger');
} else {
$(row).addClass('table-warning');
}
},
"columns": [
{"data": "displayId", responsivePriority: 2},
{"data": "display", responsivePriority: 2},
{
"data": "mediaInventoryStatus",
responsivePriority: 2,
"render": function (data, type, row) {
if (type != "display")
return data;
var icon = "";
if (data == 1)
icon = "fa-check";
else if (data == 2)
icon = "fa-times";
else
icon = "fa-cloud-download";
return '<span class="fa ' + icon + '" title="' + (row.statusDescription) + '"></span>';
}
},
{"data": "licensed", "render": dataTableTickCrossColumn, responsivePriority: 3},
{"data": "currentLayout", "visible": false, "sortable": false, responsivePriority: 5},
{
"data": "storageAvailableSpace",
responsivePriority: 5,
"visible": false,
"render": function (data, type, row) {
if (type != "display")
return data;
return row.storageAvailableSpaceFormatted;
}
},
{
"data": "storageTotalSpace",
responsivePriority: 5,
"visible": false,
"render": function (data, type, row) {
if (type != "display")
return data;
return row.storageTotalSpaceFormatted;
}
},
{"data": "storagePercentage", "visible": false, "sortable": false, responsivePriority: 5},
{"data": "description", "visible": false, responsivePriority: 4},
{"data": "orientation", "visible": false, responsivePriority: 6},
{"data": "resolution", "visible": false, responsivePriority: 6},
{% if currentUser.featureEnabled("tag.tagging") %}
{
"name": "tags",
responsivePriority: 3,
"sortable": false,
"visible": false,
"data": dataTableCreateTags
},
{% endif %}
{"data": "defaultLayout", "visible": false, responsivePriority: 4},
{"data": "incSchedule", "render": dataTableTickCrossColumn, "visible": false, responsivePriority: 5},
{"data": "emailAlert", "render": dataTableTickCrossColumn, "visible": false, responsivePriority: 5},
{"data": "loggedIn", "render": dataTableTickCrossColumn, responsivePriority: 3},
{"data": "lastAccessed", "render": dataTableDateFromUnix, responsivePriority: 4},
{
"name": "displayProfileId",
responsivePriority: 5,
"data": function (data, type) {
return data.displayProfile;
},
"visible": false
},
{
"name": "clientSort",
responsivePriority: 4,
"data": function (data) {
if (data.clientType === 'lg') {
data.clientType = 'webOS'
}
return data.clientType + ' ' + data.clientVersion + '-' + data.clientCode;
},
"visible": false
},
{"data": "deviceName", "visible": false, responsivePriority: 5},
{"data": "clientAddress", "visible": false, responsivePriority: 6},
{"data": "macAddress", responsivePriority: 5},
{"data": "timeZone", "visible": false, responsivePriority: 5},
{"data": "latitude", "visible": false, responsivePriority: 6},
{"data": "longitude", "visible": false, responsivePriority: 6},
{"data": "screenShotRequested", "render": dataTableTickCrossColumn, "visible": false, "name": "screenShotRequested", responsivePriority: 7},
{
"name": "thumbnail",
responsivePriority: 4,
"orderable": false,
"data": function (data, type) {
if (type != "display")
return data.thumbnail;
if (data.thumbnail != "") {
return '<a class="display-screenshot-container" data-toggle="lightbox" data-type="image" href="' + data.thumbnail + '"><img class="display-screenshot" src="' + data.thumbnail + '" data-display-id="'+ data.displayId +'" data-type="'+ data.clientType +'" /></a>';
}
else {
return "";
}
},
"visible": false
},
{
"data": "isCmsTransferInProgress",
"render": dataTableTickCrossColumn,
"visible": false,
"name": "isCmsTransferInProgress"
},
{
"name": "bandwidthLimit",
responsivePriority: 5,
"data": null,
"render": {"_": "bandwidthLimit", "display": "bandwidthLimitFormatted", "sort": "bandwidthLimit"},
"visible": false
},
{
"data": "lastCommandSuccess",
responsivePriority: 6,
"render": function (data, type, row) {
if (type != "display")
return data;
var icon = "";
if (data == 1)
icon = "fa-check";
else if (data == 0)
icon = "fa-times";
else
icon = "fa-question";
return "<span class='fa " + icon + "'></span>";
},
"visible": false
},
{
"data": "xmrChannel",
responsivePriority: 6,
"render": function (data, type, row) {
if (type === "export") {
return (data !== null && data !== "") ? 1 : 0;
}
if (type != "display")
return data;
var icon = "";
if (data != null && data != "")
icon = "fa-check";
else
icon = "fa-times";
return "<span class='fa " + icon + "'></span>";
},
"visible": false
},
{
"data": "commercialLicence",
"name": "commercialLicence",
responsivePriority: 5,
"render": function (data, type, row) {
if (type != "display")
return data;
var icon = "";
if (data == 3) {
return "N/A";
} else {
if (data == 1) {
icon = "fa-check";
} else if (data == 0) {
icon = "fa-times";
} else if (data == 2) {
icon = "fa-clock-o";
}
return '<span class="fa ' + icon + '" title="' + (row.commercialLicenceDescription) + '"></span>';
}
},
"visible": false
},
{
"name": "remote",
"data": null,
responsivePriority: 4,
"render": function (data, type, row) {
if (type === "display") {
var html = "<div class='remote-icons'>";
if ("{{ settings.SHOW_DISPLAY_AS_VNCLINK }}" !== "" && row.clientAddress != null && row.clientAddress !== "") {
var link = "{{ settings.SHOW_DISPLAY_AS_VNCLINK }}".replace('%s', row.clientAddress);
html += '<a href="' + link + '" title="{{ "VNC to this Display"|trans }}" target="{{ settings.SHOW_DISPLAY_AS_VNC_TGT }}">'
+ '<i class="fa fa-eye"></i></a>';
}
if (row.teamViewerLink !== "") {
html += '<a href="' + row.teamViewerLink + '" title="{{ "TeamViewer to this Display"|trans }}" target="_blank">'
+ '<img src="{{ theme.rootUri() }}theme/default/img/remote_icons/teamviewer.png" alt="TeamViewer Icon"></a>';
}
if (row.webkeyLink !== "") {
html += '<a href="' + row.webkeyLink + '" title="{{ "Webkey to this Display"|trans }}" target="_blank">'
+ '<img src="{{ theme.rootUri() }}theme/default/img/remote_icons/webkey.png" alt="Webkey Icon"></a>';
}
return html + "</div>";
} else if (type === "export") {
if (row.teamViewerLink !== "") {
return "TeamViewer: " + row.teamViewerLink;
}
if (row.webkeyLink !== "") {
return "Webkey: " + row.webkeyLink;
}
if (row.teamViewerLink === "" && row.webkeyLink === "") {
return "";
}
} else {
return "";
}
},
"visible": true,
"orderable": false
},
{"data": "createdDt", "visible": false, responsivePriority: 6},
{"data": "modifiedDt", "visible": false, responsivePriority: 6},
{
"data": "countFaults",
"name": "countFaults",
responsivePriority: 3,
"render": function (data, type, row) {
if (type != "display") {
return data;
}
if (data > 0) {
return '<span class="badge" style="background-color: red; color: white">'+(row.countFaults)+'</span>';
} else {
return '';
}
}
},
]
});
displayGridTable.on('draw', dataTableDraw);
displayGridTable.on('draw', { form: $("#displaysGrid").closest(".XiboGrid").find(".FilterDiv form") }, dataTableCreateTagEvents);
displayGridTable.on('processing.dt', dataTableProcessing);
dataTableAddButtons(displayGridTable, $('#displaysGrid_wrapper').find('.dataTables_buttons'))}
}
</script>
{% endblock %}

View File

@@ -0,0 +1,139 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Add Column" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#dataSetColumnAddForm").submit()
{% endblock %}
{% block callBack %}dataSetColumnFormOpen{% endblock %}
{% block formFieldActions %}
[
{
"field": "dataSetColumnTypeId", "trigger": "init",
"operation": "equals", "value": "1",
"actions": { ".formula": { "display": "none" }, ".listContent": { "display": "" }, ".remoteField": { "display": "none" }, ".helperFields": { "display": "" } }
},{
"field": "dataSetColumnTypeId", "trigger": "change",
"operation": "equals", "value": "1",
"actions": { ".formula": { "display": "none" }, ".listContent": { "display": "" }, ".remoteField": { "display": "none" }, ".helperFields": { "display": "" } }
},
{
"field": "dataSetColumnTypeId", "trigger": "init",
"operation": "equals", "value": "2",
"actions": { ".formula": { "display": "" }, ".listContent": { "display": "none" }, ".remoteField": { "display": "none" }, ".helperFields": { "display": "none" } }
},{
"field": "dataSetColumnTypeId", "trigger": "change",
"operation": "equals", "value": "2",
"actions": { ".formula": { "display": "" }, ".listContent": { "display": "none" }, ".remoteField": { "display": "none" }, ".helperFields": { "display": "none" } }
},
{
"field": "dataSetColumnTypeId", "trigger": "init",
"operation": "equals", "value": "3",
"actions": { ".formula": { "display": "none" }, ".listContent": { "display": "none" }, ".remoteField": { "display": "" }, ".helperFields": { "display": "none" } }
},{
"field": "dataSetColumnTypeId", "trigger": "change",
"operation": "equals", "value": "3",
"actions": { ".formula": { "display": "none" }, ".listContent": { "display": "none" }, ".remoteField": { "display": "" }, ".helperFields": { "display": "none" } }
}
]
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="dataSetColumnAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("dataSet.column.add", {id: dataSet.dataSetId}) }}">
{% set title %}{% trans "Heading" %}{% endset %}
{% set helpText %}{% trans "The heading for this Column. You cannot use a column name with spaces." %}{% endset %}
{{ forms.input("heading", title, "", helpText, "", "required") }}
{% set title %}{% trans "Column Type" %}{% endset %}
{% set helpText %}{% trans "Select the Column Type" %}{% endset %}
{{ forms.dropdown("dataSetColumnTypeId", "single", title, "", dataSetColumnTypes, "dataSetColumnTypeId", "dataSetColumnType", helpText) }}
{% set title %}{% trans "Data Type" %}{% endset %}
{% set helpText %}{% trans "The DataType of the Intended Data" %}{% endset %}
{{ forms.dropdown("dataTypeId", "single", title, "", dataTypes, "dataTypeId", "dataType", helpText) }}
{% set title %}{% trans "List Content" %}{% endset %}
{% set helpText %}{% trans "A comma separated list of items to present in a combo box" %}{% endset %}
{{ forms.input("listContent", title, "", helpText, "listContent") }}
{% set title %}{% trans "Remote Data Path" %}{% endset %}
{% if dataSet.sourceId == 1 %}
{% set helpText %}{% trans "Give the JSON-path in the remote data for the value that you want to fill this column. This path should be relative to the DataRoot configured on the DataSet." %}{% endset %}
{% else %}
{% set helpText %}{% trans "Provide Column number relative to the spreadsheet, numeration starts from 0 ie to get values from Column A from spreadsheet to this column enter 0" %}{% endset %}
{% endif %}
{{ forms.input("remoteField", title, dataSetColumn.remoteField, helpText, "remoteField") }}
{% set title %}{% trans "Column Order" %}{% endset %}
{% set helpText %}{% trans "The order this column should be displayed in when entering data" %}{% endset %}
{{ forms.number("columnOrder", title, "", helpText) }}
{% set title %}{% trans "Tooltip" %}{% endset %}
{% set helpText %}{% trans "Optional message to be displayed under the input when entering data for this column" %}{% endset %}
{{ forms.input("tooltip", title, "", helpText, 'helperFields') }}
{% set title %}{% trans "Formula" %}{% endset %}
{% set helpText %}{% trans "Enter a MySQL statement suitable to use in a 'SELECT' statement" %}{% endset %}
{{ forms.input("formula", title, "", helpText, "formula") }}
{% set title %}{% trans "Filter?" %}{% endset %}
{% set helpText %}{% trans "Show as a filter option on the Data Entry Page?" %}{% endset %}
{{ forms.checkbox("showFilter", title, "", helpText) }}
{% set title %}{% trans "Date Format" %}
<span class="fa fa-info-circle date-format-table"
data-toggle="popover"
data-trigger="hover">
</span>
{% endset %}
{% set helpText %}{% trans "Enter a PHP date format to parse the dates from the source." %}{% endset %}
{{ forms.input("dateFormat", title, dataSetColumn.dateFormat, helpText, 'dateFormat') }}
{% set title %}{% trans "Sort?" %}{% endset %}
{% set helpText %}{% trans "Enable sorting on the Data Entry Page? We recommend that the number of sortable columns is kept to a minimum." %}{% endset %}
{{ forms.checkbox("showSort", title, "", helpText) }}
{% set title %}{% trans "Required?" %}{% endset %}
{% set helpText %}{% trans "Should the value for this Column be required?" %}{% endset %}
{{ forms.checkbox("isRequired", title, "", helpText, 'helperFields') }}
{% set message %}{% trans "Two substitutions are available for Formula columns: [DisplayId] and [DisplayGeoLocation]. They will be substituted at run time with the Display ID / Display Geo Location (MySQL GEOMETRY)." %}{% endset %}
{% set message2 %}{% trans "Client side formula is also available for Formula columns : $dateFormat(columnName,format,language), for example $dateFormat(date,l,de), would return textual representation of a day in German language from the full date in date column" %}{% endset %}
{{ forms.message(message, "alert alert-info formula") }}
{{ forms.message(message2, "alert alert-info formula") }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Delete this Column?" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Yes" %}, $("#dataSetColumnDeleteForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="dataSetColumnDeleteForm" class="XiboForm form-horizontal" method="delete" action="{{ url_for("dataSet.column.delete", {id: dataSet.dataSetId, colId: dataSetColumn.dataSetColumnId}) }}">
{% set message %}{% trans "Are you sure you want to delete?" %}{% endset %}
{{ forms.message(message) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,139 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Edit Column" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#dataSetColumnEditForm").submit()
{% endblock %}
{% block callBack %}dataSetColumnFormOpen{% endblock %}
{% block formFieldActions %}
[
{
"field": "dataSetColumnTypeId", "trigger": "init",
"operation": "equals", "value": "1",
"actions": { ".formula": { "display": "none" }, ".listContent": { "display": "" }, ".remoteField": { "display": "none" }, ".helperFields": { "display": "" } }
},{
"field": "dataSetColumnTypeId", "trigger": "change",
"operation": "equals", "value": "1",
"actions": { ".formula": { "display": "none" }, ".listContent": { "display": "" }, ".remoteField": { "display": "none" }, ".helperFields": { "display": "" } }
},
{
"field": "dataSetColumnTypeId", "trigger": "init",
"operation": "equals", "value": "2",
"actions": { ".formula": { "display": "" }, ".listContent": { "display": "none" }, ".remoteField": { "display": "none" }, ".helperFields": { "display": "none" } }
},{
"field": "dataSetColumnTypeId", "trigger": "change",
"operation": "equals", "value": "2",
"actions": { ".formula": { "display": "" }, ".listContent": { "display": "none" }, ".remoteField": { "display": "none" }, ".helperFields": { "display": "none" } }
},
{
"field": "dataSetColumnTypeId", "trigger": "init",
"operation": "equals", "value": "3",
"actions": { ".formula": { "display": "none" }, ".listContent": { "display": "none" }, ".remoteField": { "display": "" }, ".helperFields": { "display": "none" } }
},{
"field": "dataSetColumnTypeId", "trigger": "change",
"operation": "equals", "value": "3",
"actions": { ".formula": { "display": "none" }, ".listContent": { "display": "none" }, ".remoteField": { "display": "" }, ".helperFields": { "display": "none" } }
}
]
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="dataSetColumnEditForm" class="XiboForm form-horizontal" method="put" action="{{ url_for("dataSet.column.edit", {id: dataSet.dataSetId, colId: dataSetColumn.dataSetColumnId}) }}">
{% set title %}{% trans "Heading" %}{% endset %}
{% set helpText %}{% trans "The heading for this Column. You cannot use a column name with spaces." %}{% endset %}
{{ forms.input("heading", title, dataSetColumn.heading, helpText, "", "required") }}
{% set title %}{% trans "Column Type" %}{% endset %}
{% set helpText %}{% trans "Select the Column Type" %}{% endset %}
{{ forms.dropdown("dataSetColumnTypeId", "single", title, dataSetColumn.dataSetColumnTypeId, dataSetColumnTypes, "dataSetColumnTypeId", "dataSetColumnType", helpText) }}
{% set title %}{% trans "Data Type" %}{% endset %}
{% set helpText %}{% trans "The DataType of the Intended Data" %}{% endset %}
{{ forms.dropdown("dataTypeId", "single", title, dataSetColumn.dataTypeId, dataTypes, "dataTypeId", "dataType", helpText) }}
{% set title %}{% trans "List Content" %}{% endset %}
{% set helpText %}{% trans "A comma separated list of items to present in a combo box" %}{% endset %}
{{ forms.input("listContent", title, dataSetColumn.listContent, helpText, "listContent") }}
{% set title %}{% trans "Remote Data Path" %}{% endset %}
{% if dataSet.sourceId == 1 %}
{% set helpText %}{% trans "Give the JSON-path in the remote data for the value that you want to fill this column. This path should be relative to the DataRoot configured on the DataSet." %}{% endset %}
{% else %}
{% set helpText %}{% trans "Provide Column number relative to the spreadsheet, numeration starts from 0 ie to get values from Column A from spreadsheet to this column enter 0" %}{% endset %}
{% endif %}
{{ forms.input("remoteField", title, dataSetColumn.remoteField, helpText, "remoteField") }}
{% set title %}{% trans "Column Order" %}{% endset %}
{% set helpText %}{% trans "The order this column should be displayed in when entering data" %}{% endset %}
{{ forms.number("columnOrder", title, dataSetColumn.columnOrder, helpText) }}
{% set title %}{% trans "Tooltip" %}{% endset %}
{% set helpText %}{% trans "Optional message to be displayed under the input when entering data for this column" %}{% endset %}
{{ forms.input("tooltip", title, dataSetColumn.tooltip, helpText, 'helperFields') }}
{% set title %}{% trans "Formula" %}{% endset %}
{% set helpText %}{% trans "Enter a MySQL statement suitable to use in a 'SELECT' statement" %}{% endset %}
{{ forms.input("formula", title, dataSetColumn.formula, helpText, "formula") }}
{% set title %}{% trans "Filter?" %}{% endset %}
{% set helpText %}{% trans "Show as a filter option on the Data Entry Page?" %}{% endset %}
{{ forms.checkbox("showFilter", title, dataSetColumn.showFilter, helpText) }}
{% set title %}{% trans "Date Format" %}
<span class="fa fa-info-circle date-format-table"
data-toggle="popover"
data-trigger="hover">
</span>
{% endset %}
{% set helpText %}{% trans "Enter a PHP date format to parse the dates from the source." %}{% endset %}
{{ forms.input("dateFormat", title, dataSetColumn.dateFormat, helpText, 'dateFormat') }}
{% set title %}{% trans "Sort?" %}{% endset %}
{% set helpText %}{% trans "Enable sorting on the Data Entry Page? We recommend that the number of sortable columns is kept to a minimum." %}{% endset %}
{{ forms.checkbox("showSort", title, dataSetColumn.showSort, helpText) }}
{% set title %}{% trans "Required?" %}{% endset %}
{% set helpText %}{% trans "Should the value for this Column be required?" %}{% endset %}
{{ forms.checkbox("isRequired", title, dataSetColumn.isRequired, helpText, 'helperFields') }}
{% set message %}{% trans "Two substitutions are available for Formula columns: [DisplayId] and [DisplayGeoLocation]. They will be substituted at run time with the Display ID / Display Geo Location (MySQL GEOMETRY)." %}{% endset %}
{% set message2 %}{% trans "Client side formula is also available for Formula columns : $dateFormat(columnName,format,language), for example $dateFormat(date,l,de), would return textual representation of a day in German language from the full date in date column" %}{% endset %}
{{ forms.message(message, "alert alert-info formula") }}
{{ forms.message(message2, "alert alert-info formula") }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,153 @@
{#
/**
* 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 %}
{% set dataSetName = dataSet.dataSet %}
{% block title %}{% trans %}Columns for {{ dataSetName }}{% endtrans %} | {% endblock %}
{% block actionMenu %}
<div class="widget-action-menu pull-right">
<button class="btn btn-success XiboFormButton" title="{% trans "Add Column" %}" href="{{ url_for("dataSet.column.add.form", {id: dataSet.dataSetId}) }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Column" %}</button>
<button class="btn btn-info XiboRedirectButton" title="{% trans "View Data" %}" href="{{ url_for("dataSet.view.data", {id: dataSet.dataSetId}) }}"><i class="fa fa-eye" aria-hidden="true"></i> {% trans "View Data" %}</button>
<button class="btn btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
</div>
{% endblock %}
{% block pageContent %}
{% set widgetTitle %}{% trans %}Columns for {{ dataSetName }}{% endtrans %}{% endset %}
<div class="widget">
<div class="widget-title">{{ widgetTitle }}</div>
<div class="widget-body">
<div class="XiboGrid" id="{{ random() }}">
<div class="XiboFilter">
<div class="FilterDiv card-body" id="Filter">
<form class="form-inline">
</form>
</div>
</div>
<div class="XiboData card pt-3">
<table id="datasets" class="table table-striped" data-state-preference-name="dataSetColumnGrid">
<thead>
<tr>
<th>{% trans "Heading" %}</th>
<th>{% trans "DataType" %}</th>
<th>{% trans "Column Type" %}</th>
<th>{% trans "List Content" %}</th>
<th>{% trans "Tooltip" %}</th>
<th>{% trans "Order" %}</th>
<th>{% trans "Required?" %}</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javaScript %}
<script type="text/javascript" nonce="{{ cspNonce }}">
var table = $("#datasets").DataTable({
"language": dataTablesLanguage,
dom: dataTablesTemplate,
serverSide: true,
stateSave: true,
stateDuration: 0,
stateLoadCallback: dataTableStateLoadCallback,
stateSaveCallback: dataTableStateSaveCallback,
filter: false,
responsive: true,
searchDelay: 3000,
"order": [[ 0, "asc"]],
ajax: {
"url": "{{ url_for("dataSet.column.search", {id: dataSet.dataSetId}) }}",
"data": function(d) {
$.extend(d, $("#datasets").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
}
},
"columns": [
{ "data": "heading", "render": dataTableSpacingPreformatted },
{ "data": "dataType" },
{ "data": "dataSetColumnType" },
{ "data": "listContent" },
{ "data": "tooltip" },
{ "data": "columnOrder" },
{
"data": "isRequired",
"render": dataTableTickCrossColumn,
"visible": false,
"name": "isRequired"
},
{
"orderable": false,
responsivePriority: 1,
"data": dataTableButtonsColumn
}
]
});
table.on('draw', dataTableDraw);
table.on('processing.dt', dataTableProcessing);
dataTableAddButtons(table, $('#datasets_wrapper').find('.dataTables_buttons'));
$("#refreshGrid").click(function () {
table.ajax.reload();
});
function dataSetColumnFormOpen(dialog) {
formHelpers.setupPhpDateFormatPopover(dialog)
// remote DataSet source
onDataTypeChanged(dialog);
$(dialog).find('#dataTypeId, #dataSetColumnTypeId').on('change', function() {
onDataTypeChanged(dialog);
});
}
function onDataTypeChanged(dialog)
{
var dataTypeId = $(dialog).find('#dataTypeId').val();
var dataSetColumnTypeId = $(dialog).find('#dataSetColumnTypeId').val();
var $dateFormat = $(dialog).find('.dateFormat');
if (dataSetColumnTypeId == 3 && dataTypeId == 3) {
$dateFormat.removeClass('d-none')
} else {
$dateFormat.addClass('d-none')
}
}
</script>
<style>
.popover{
max-width: 70%;
}
</style>
{% endblock %}

View File

@@ -0,0 +1,276 @@
{#
/*
* 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 %}
{% import "forms.twig" as forms %}
{% block title %}{% set dataSetName = dataSet.dataSet %}{% trans %}{{ dataSetName }} - Data Connector{% endtrans %} | {% endblock %}
{% set hideNavigation = "1" %}
{% block pageContent %}
<div id="data-connector-builder"
data-data-set-id="{{ dataSet.dataSetId }}">
<div class="back-button">
<a id="backBtn" class="btn btn-primary" href="{{ url_for("dataset.view") }}">
<i class="fa fa-angle-left"></i>
<span>{{ "Back"|trans }}</span>
</a>
</div>
<div class="widget mt-3">
<div class="widget-body">
<div class="row">
<div class="col-12">
<div class="data-set-title">
<h1>{{ dataSetName }}</h1>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6 {% if dataSet.dataConnectorSource != 'user_defined' %}hidden{% endif %}">
<form id="dataconnector-builder-form" class="XiboForm form-horizontal"
method="put"
action="{{ url_for("dataSet.dataConnector.update", {id: dataSet.dataSetId}) }}"
data-submit-call-back="onSubmitCallback"
>
<div class="form-group row code-input-group xibo-code-input">
<div class="col-sm-12">
<small class="form-text text-muted">{{ "Data Connector JavaScript"|trans }}</small>
<textarea class="form-control d-none code-input" id="input_script" name="dataConnectorScript" rows="30" data-code-type="javascript">{% if script %}{{ script }}{% else %}window.onInit = function() {
}{% endif %}</textarea>
<div class="code-input-editor-container" style="height: 70vh;">
<div class="code-input-editor"></div>
</div>
</div>
</div>
{{ forms.button("Save"|trans, "submit", null, null, null, "btn-success " ~ (dataSet.dataConnectorSource != 'user_defined' ? 'disabled' : '')) }}
</form>
</div>
<div class="col-lg-6">
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item">
<a class="nav-link" href="#tab-testParams" role="tab" data-toggle="tab">
<span>{% trans "Test Params" %}</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="#tab-logs" role="tab" data-toggle="tab">
<span>{% trans "Logs" %}</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#tab-dataSet" role="tab" data-toggle="tab">
<span>{% trans "DataSet Data" %}</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#tab-otherData" role="tab" data-toggle="tab">
<span>{% trans "Other Data" %}</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#tab-scheduleCriteria" role="tab" data-toggle="tab">
<span>{% trans "Schedule Criteria" %}</span>
</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane" id="tab-testParams">
{{ inline.message("You can test passing parameters that would otherwise be set when this Data Connector is scheduled."|trans, "alert alert-info") }}
{{ inline.input("dataSetRealtimeTestParams", "Test Parameters"|trans) }}
</div>
<div class="tab-pane active" id="tab-logs">
<pre id="dataconnector-logs"></pre>
</div>
<div class="tab-pane" id="tab-dataSet">
<div class="table-container">
<table id="dataconnector-main-data" class="table">
<thead>
{% for column in dataSet.getColumn() %}
<th>{{ column.heading }}</th>
{% endfor %}
<th>Unmapped</th>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div class="tab-pane" id="tab-otherData">
<pre id="dataconnector-other-data"></pre>
</div>
<div class="tab-pane" id="tab-scheduleCriteria">
<div class="table-container">
<table id="dataconnector-schedule-criteria" class="table">
<thead>
<th>{{ "Metric"|trans }}</th>
<th>{{ "Value"|trans }}</th>
<th>TTL</th>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row" style="display: none;" id="dataconnector-script"></div>
</div>
{% endblock %}
{% block javaScript %}
<script type="text/javascript" nonce="{{ cspNonce }}">
$(function() {
const $script = $('#dataconnector-script');
const $scriptParams = $('#dataSetRealtimeTestParams');
const $mainData = $('#dataconnector-main-data');
const $otherData = $('#dataconnector-other-data');
const $scheduleCriteria = $('#dataconnector-schedule-criteria');
const $logs = $('#dataconnector-logs');
let otherData = {};
let criteria = {};
// Set up a channel which will broadcast data
const channel = new BroadcastChannel('xiboDC');
// Set our script params from local storage if we have them
$scriptParams.val(localStorage.getItem('dataSetRealtimeTestParams'));
// Output the iframe containing the window
$script.html('<iframe src="{{ url_for("dataSet.dataConnector.test", {id: dataSet.dataSetId}) }}" />');
// Window message to receive data and logs.
window.receiveData = function(type, data) {
if (type === 'loaded') {
console.debug('Script loaded');
$script.find('iframe')[0].contentWindow.xiboDC.initialise({{ dataSet.dataSetId }}, $scriptParams.val());
} else if (type === 'log') {
$logs.prepend('[' + moment().format('YY-MM-DD HH:mm:ss') + '] ' + data + '\n');
} else if (type === 'set') {
// Update the table
// if the dataKey matches my connector's DataSetId, then render out a table
if (data.dataKey == '{{ dataSet.dataSetId }}') {
// Data is always set as a string
const events = JSON.parse(data.data);
if (Array.isArray(events)) {
const $tableBody = $mainData.find('tbody');
$tableBody.find('tr').remove();
$.each(events, function (rowIndex, row) {
// Make a new row
let html = '<tr>';
{% for column in dataSet.getColumn() %}
html += '<td data-id="{{ column.heading }}"></td>';
{% endfor %}
html += '<td data-id="unmatched"></td></tr>';
const $newRow = $(html);
$tableBody.append($newRow);
// Do we have a column for this item
$.each(row, function (colIndex, col) {
if ($newRow.find('td[data-id=' + colIndex).length > 0) {
$newRow.find('td[data-id=' + colIndex).append(row[colIndex]);
} else {
$newRow.find('td[data-id=unmatched').append(colIndex + ': ' + row[colIndex] + '<br/>');
}
});
});
} else {
// Treat it as other data.
otherData[data.dataKey] = data.data;
$otherData.html(JSON.stringify(otherData, null, 4));
}
} else {
// Grab the existing "other data" and see if there is a matching key.
otherData[data.dataKey] = data.data;
$otherData.html(JSON.stringify(otherData, null, 4));
}
// Broadcast to interested parties.
// Use the original data.data (which is a string)
channel.postMessage({type: 'xiboDC_data', dataKey: data.dataKey, data: data.data});
} else if (type === 'notify') {
// Log
$logs.prepend('[' + moment().format('YY-MM-DD HH:mm:ss') + '] Notify for ' + data + '\n');
channel.postMessage({type: "xiboDC_notify", dataKey: data});
} else if (type === 'criteria') {
// Schedule criteria, update in the table.
criteria[data.dataKey] = data.data;
const $tableBody = $scheduleCriteria.find('tbody');
$.each(criteria, function (key, value) {
$tableBody.append('<tr><td>' + key + '</td><td>' + value.value + '</td><td>' + value.ttl + '</td></tr>');
});
}
}
window.makeRequest = function (path, {type, headers, data, done, error} = {}) {
$.ajax('{{ url_for("dataSet.dataConnector.request", {id: dataSet.dataSetId}) }}', {
data: {
url: path,
method: type,
headers: headers,
body: data
},
success: function(data, textStatus, jqXHR) {
if (typeof(done) == 'function') {
done(jqXHR.status, data);
}
},
error: function(jqXHR, textStatus, errorThrown) {
if (typeof(done) == 'function') {
error(jqXHR.status, jqXHR.responseText);
}
}
});
}
// Refresh the iframe.
window.onSubmitCallback = function(xhr, form) {
$script.find('iframe')[0].contentWindow.location.reload();
}
$scriptParams.on('change', function() {
$script.find('iframe')[0].contentWindow.xiboDC.initialise({{ dataSet.dataSetId }}, $scriptParams.val());
localStorage.setItem('dataSetRealtimeTestParams', $scriptParams.val());
});
});
</script>
{# Add code editor bundle #}
<script type="text/javascript" src="{{ theme.rootUri() }}dist/codeEditor.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}" defer></script>
{% endblock %}

View File

@@ -0,0 +1,138 @@
{#
/*
* 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/>.
*/
#}
<!DOCTYPE html>
<html>
<head>
<title>Data Connector Test</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="public-path" content="{{ theme.rootUri() }}"/>
<link rel="shortcut icon" href="{{ theme.uri("img/favicon.ico") }}" />
</head>
<body>
<script type="text/javascript" nonce="{{ cspNonce }}">
window.xiboDC = (function() {
'use strict';
const mainLib = {
/**
* Inject the data connector event parameters and dataSetId
* @param {string} dataSetId - The id of the dataset
* @param {string} dataSetParameters - A url string of parameters
*/
initialise: function(dataSetId, dataSetParameters) {
window.dataSetId = dataSetId;
new URLSearchParams(dataSetParameters).forEach(function (value, key) {
window[key] = value;
});
if (typeof (window.onInit) == 'function') {
window.onInit();
}
},
/**
* Set the realtime into the player. Called from Data Connector.
* @param {string} dataKey A dataKey to store this data
* @param {String} data The data as string
* @param {Object} options - Request options
* @param {callback} options.done Optional
* @param {callback} options.error Optional
*/
setData: function(dataKey, data, {done, error} = {}) {
// Persist the data we've been given
window.parent.receiveData('set', {
dataKey: dataKey,
data: data
});
if (typeof (done) == 'function') {
done(true);
}
},
/**
* Notify main application that we have new data. Called from data collector.
* @param {string} dataKey - The key of the data that has been changed.
*/
notifyHost: function(dataKey) {
// Update the table.
window.parent.receiveData('notify', dataKey);
},
/**
* Make a request to the configured server/player
* @param {string} path - Request path
* @param {Object} [options] - Optional params
* @param {string} [options.type]
* @param {Object[]} [options.headers]
* Request headers in the format {key: key, value: value}
* @param {Object} [options.data]
* @param {callback} [options.done]
* @param {callback} [options.error]
*/
makeRequest: function(path, {type, headers, data, done, error} = {}) {
window.parent.makeRequest(path, {type, headers, data, done, error});
},
/**
* Set Schedule Criteria
* @param {string} metric The Metric Name
* @param {string} value The Value
* @param {int} ttl A TTL in seconds
*/
setCriteria: function(metric, value, ttl) {
window.parent.receiveData('criteria', {
dataKey: metric,
data: {
metric: metric,
value: value || null,
ttl: ttl,
}
});
},
}
return mainLib;
})();
// Capture console logs and report out.
(function () {
const log = console.log;
console.log = function () {
log.apply(this, Array.prototype.slice.call(arguments));
window.parent.receiveData('log', Array.prototype.slice.call(arguments));
};
}());
// Say when we're loaded.
window.onload = function () {
window.parent.receiveData('loaded', null);
}
</script>
<script type="text/javascript" nonce="{{ cspNonce }}">
{{ script|raw }}
</script>
</body>
</html>

View File

@@ -0,0 +1,77 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Add Data" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Next" %}, XiboDialogApply("#dataSetDataAdd")
{% trans "Save" %}, $("#dataSetDataAdd").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="dataSetDataAdd" class="XiboForm form-horizontal" method="post" action="{{ url_for("dataSet.data.add", {id: dataSet.dataSetId}) }}">
{% for col in dataSet.getColumn() %}
{% if col.dataSetColumnTypeId == 1 %}
{% set fieldId = "dataSetColumnId_#{col.dataSetColumnId}" %}
{% if col.isRequired == 1 %}
{% set validation = "required" %}
{% else %}
{% set validation = "" %}
{% endif %}
{# Field depending on what data type we have #}
{% if col.dataTypeId == 2 %}
{{ forms.number(fieldId, col.heading, "", col.tooltip, "", validation) }}
{% elseif col.dataTypeId == 3 %}
{{ forms.dateTime(fieldId, col.heading, "", col.tooltip, "", validation) }}
{% elseif col.dataTypeId == 5 %}
{% set selectImage %}{% trans "Select an Image" %}{% endset %}
{% set attributes = [
{ 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" },
{ name: "data-filter-options", value: '{"type":"image"}' },
{ name: "data-allow-clear", value: "true"},
{ name: "data-placeholder", value: selectImage}
] %}
{{ forms.dropdown(fieldId, "single", col.heading, "", null, "mediaId", "name", col.tooltip, "pagedSelect", validation, "", "", attributes) }}
{% elseif col.listContent != "" %}
{{ forms.dropdown(fieldId, "single", col.heading, heading, [""]|merge(col.listContentArray()), "", "", col.tooltip, "", validation) }}
{% else %}
{{ forms.input(fieldId, col.heading, "", col.tooltip, "", validation) }}
{% endif %}
{% endif %}
{% endfor %}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Delete Row" %}
{% endblock %}
{% block formButtons %}
{% trans "No" %}, XiboDialogClose()
{% trans "Yes" %}, $("#dataSetDataDelete").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="dataSetDataDelete" class="XiboForm form-horizontal" method="delete" action="{{ url_for("dataSet.data.delete", {id: dataSet.dataSetId, rowId: row.id}) }}">
{% set message %}{% trans "Are you sure you want to delete this row?" %}{% endset %}
{{ forms.message(message) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,80 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Edit Data" %}
{% endblock %}
{% block formButtons %}
{% trans "Delete" %}, XiboSwapDialog("{{ url_for("dataSet.data.delete.form", {id: dataSet.dataSetId, rowId: row.id}) }}")
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#dataSetEditData").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="dataSetEditData" class="XiboForm form-horizontal" method="put" action="{{ url_for("dataSet.data.edit", {id: dataSet.dataSetId, rowId: row.id}) }}">
{% for col in dataSet.getColumn() %}
{% if col.dataSetColumnTypeId == 1 %}
{% set fieldId = "dataSetColumnId_#{col.dataSetColumnId}" %}
{% set heading = attribute(row, col.heading) %}
{% if col.isRequired == 1 %}
{% set validation = "required" %}
{% else %}
{% set validation = "" %}
{% endif %}
{# Field depending on what data type we have #}
{% if col.dataTypeId == 2 %}
{{ forms.number(fieldId, col.heading, heading, col.tooltip, "", validation) }}
{% elseif col.dataTypeId == 3 %}
{{ forms.dateTime(fieldId, col.heading, heading, col.tooltip, "", validation) }}
{% elseif col.dataTypeId == 5 %}
{% set attributes = [
{ 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" },
{ name: "data-filter-options", value: '{"type":"image"}' },
{ name: "data-allow-clear", value: "true"},
{ name: "data-placeholder", value: "Select an Image"}
] %}
{% set images = attribute(row, "__images") %}
{% set images = attribute(images, col.dataSetColumnId) %}
{{ forms.dropdown(fieldId, "single", col.heading, heading, [images], "mediaId", "name", col.tooltip, "pagedSelect", validation, "", "", attributes) }}
{% elseif col.listContent != "" %}
{{ forms.dropdown(fieldId, "single", col.heading, heading, [""]|merge(col.listContentArray()), "", "", col.tooltip, "", validation) }}
{% else %}
{{ forms.input(fieldId, col.heading, heading, col.tooltip, "", validation) }}
{% endif %}
{% endif %}
{% endfor %}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,304 @@
{#
/**
* 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 %}
{% set dataSetName = dataSet.dataSet %}
{% block title %}{% trans %}Data Entry for {{ dataSetName }}{% endtrans %} | {% endblock %}
{% block actionMenu %}
<div class="widget-action-menu pull-right">
<button class="btn btn-success XiboFormButton addRowButton" title="{% trans "Add a row to the end of this DataSet" %}" href="{{ url_for("dataSet.data.add.form", {"id": dataSet.dataSetId}) }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Row" %}</button>
<button class="btn btn-primary toggleMultiSelectMode" id="toggleMultiSelectMode" title="{% trans "Click to toggle between Data Edit and Multi Select modes" %}"><i class="fa fa-object-group" aria-hidden="true"></i> <span class="button-text">{% trans "Multi Select Mode" %}</span></button>
<button class="btn btn-danger d-none deleteSelectedRows" id="deleteSelectedRows" title="{% trans "Click to delete selected rows" %}" disabled="disabled"><i class="fa fa-trash" aria-hidden="true"></i> <span class="button-text">{% trans "Delete Rows" %}</span></button>
<button class="btn btn-info XiboRedirectButton" href="{{ url_for("dataSet.column.view", {"id": dataSet.dataSetId}) }}"><i class="fa fa-columns" aria-hidden="true"></i> {% trans "View Columns" %}</button>
<button class="btn btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
</div>
{% endblock %}
{% block pageContent %}
{% set widgetTitle %}{% trans %}Data Entry for {{ dataSetName }}{% endtrans %}{% endset %}
<div class="widget">
<div class="widget-title">{{ widgetTitle }} | <strong class="dataset-edit-title-mode widget-sub-title">{% trans "Edit Mode" %}</strong><span title="{% trans "Click on any row to edit" %}" class="badge badge-pill badge-secondary px-1 mx-1">?</span></div>
<div class="widget-body">
<div class="XiboGrid" id="{{ random() }}">
<div class="XiboFilter">
<div class="FilterDiv card-body" id="Filter">
<form class="form-inline">
{% for col in dataSet.getColumn() %}
{% if col.dataSetColumnTypeId == 1 and (col.dataTypeId == 1 or col.dataTypeId == 2) and col.showFilter == 1 %}
{{ inline.input(col.heading, col.heading) }}
{% endif %}
{% endfor %}
</form>
</div>
</div>
<div class="XiboData card pt-3">
<table id="datasets" class="table table-striped" data-image-url="{{ url_for("library.download", {"id":":id"}) }}?preview=1&width=150&height=150">
<thead>
<tr>
<th>{% trans "ID" %}</th>
{% for col in dataSet.getColumn() %}
<th>{{ col.heading }}</th>
{% endfor %}
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javaScript %}
<script type="text/javascript" nonce="{{ cspNonce }}">
var imageUrl = $("#datasets").data("imageUrl");
var cols = [];
var editMode = true;
var editModeTitleTrans = "{% trans "Edit Mode" %}";
var multiSelectTitleTrans = "{% trans "Multi Select Mode" %}";
var editModeHelpTrans = "{% trans "Click on any row to edit" %}";
var multiSelectHelpTrans = "{% trans "Select one or more rows to delete" %}";
const entityMap = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
'/': '&#x2F;',
'`': '&#x60;',
'=': '&#x3D;'
};
function sanitizeHtml(string) {
return String(string).replace(/[&<>"'`=\/]/g, function (s) {
return entityMap[s];
});
}
function validateHTMLData(str) {
let doc = new DOMParser().parseFromString(str, "text/html");
// If valid html, sanitize and format as a code
if (Array.from(doc.body.childNodes).some(node => node.nodeType === 1)) {
return `<code>${sanitizeHtml(str)}</code>`;
}
return str;
}
cols.push({ "name": "id", "data": "id" });
{% for col in dataSet.getColumn() %}
{% if col.dataTypeId == 3 %}
cols.push({ "data": "{{ col.heading }}", "orderable": {% if col.showSort == 1 %}true{% else %}false{% endif %}, "render": dataTableDateFromIso });
{% elseif col.dataTypeId == 5 %}
cols.push({ "data": "{{ col.heading }}", "orderable": {% if col.showSort == 1 %}true{% else %}false{% endif %}, "render": function(data, type, row) {
if (type != "display")
return data;
if (data == null)
return "";
return '<img src="' + imageUrl.replace(":id", data) + '"/>';
}
});
{% else %}
cols.push({
"data": "{{ col.heading }}",
"orderable": {% if col.showSort == 1 %}true{% else %}false{% endif %},
"render": function(data) {
return validateHTMLData(data);
}
});
{% endif %}
{% endfor %}
cols.push({
"orderable": false,
"data": function(data, type, row, meta) {
if (type != "display")
return "";
var url = "{{ url_for("dataSet.data.delete.form", {"id": dataSet.dataSetId, "rowId":':rowId'}) }}".replace(":rowId", data.id);
return "<a href=\"" + url + "\" class=\"XiboFormButton\"><span class=\"fa fa-times\"></span></a>";
}
});
var table = $("#datasets").DataTable({ "language": dataTablesLanguage,
dom: dataTablesTemplate,
serverSide: true, stateSave: true, stateDuration: 0,
filter: false,
responsive: true,
searchDelay: 3000,
"order": [[ 0, "asc"]],
ajax: {
"url": "{{ url_for("dataSet.data.search", {id: dataSet.dataSetId}) }}",
"data": function(dataToSend) {
var data = {};
data.draw = dataToSend.draw;
data.length = dataToSend.length;
data.start = dataToSend.start;
data.order = dataToSend.order;
data.columns = [];
$.each(dataToSend.columns, function (index, e) {
var col = {};
col.data = e.data;
if (e.orderable) {
data.columns.push(col);
} else {
data.columns.push({});
}
});
$.extend(data, $("#datasets").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
return data;
},
complete: function (response) {
if (response.responseJSON.data.exception != '' && response.responseJSON.data.exception != undefined) {
var error = response.responseJSON.data.exception;
$("#datasets tbody").html(error)
}
}
},
"columns": cols
});
table.on('draw', dataTableDraw);
table.on('processing.dt', dataTableProcessing);
dataTableAddButtons(table, $('#datasets_wrapper').find('.dataTables_buttons'));
$("#refreshGrid").click(function () {
table.ajax.reload();
});
var dataSetEditFormOpen = _.debounce(function() {
var data = table.row($(this)).data();
// Load the edit form.
var editUrl = "{{ url_for("dataSet.data.edit.form", {"id": dataSet.dataSetId, "rowId":':rowId'}) }}".replace(":rowId", data.id);
XiboFormRender(editUrl);
}, 500, true);
// default is Edit mode, hence this should be our onclick event and deleteRows button should be hidden.
$("#datasets tbody").on('click', 'tr', dataSetEditFormOpen);
// Toggle between Edit and Multi Select modes
// we also switch between two different onclick events depending on the mode and show/hide deleteRows button.
$('#toggleMultiSelectMode').on("click", function(e) {
e.preventDefault();
if (editMode) {
// switch to multi select mode
editMode = false;
$(this).find('.button-text').text(editModeTitleTrans);
$(this).find('i').removeClass('fa-object-group').addClass('fa-edit');
$('.dataset-edit-title-mode').text(multiSelectTitleTrans);
$('.widget-title .badge').attr('title', multiSelectHelpTrans);
$("#datasets tbody").off('click', 'tr');
$('#datasets tbody tr.selected').removeClass('selected');
$(".deleteSelectedRows").removeClass('d-none');
$('#datasets tbody').on('click', 'tr', function(ev) {
// See if element has class already
var hasClass = $(ev.currentTarget).hasClass('selected')
// Add class to the clicked one ( or remove it if there was already there )
$(ev.currentTarget).toggleClass('selected', !hasClass);
// Change Delete button state according to the number of selected rows
if ($('#datasets tbody tr.selected').length > 0) {
$(".deleteSelectedRows").removeAttr('disabled');
} else {
$(".deleteSelectedRows").attr('disabled', 'disabled');
}
});
} else {
// switch to edit mode
editMode = true;
$(this).find('.button-text').text(multiSelectTitleTrans);
$(this).find('i').removeClass('fa-edit').addClass('fa-object-group');
$('.dataset-edit-title-mode').text(editModeTitleTrans);
$('.widget-title .badge').attr('title', editModeHelpTrans);
$(".deleteSelectedRows").addClass('d-none');
$("#datasets tbody").off('click', 'tr');
$('#datasets tbody tr.selected').removeClass('selected');
$(".deleteSelectedRows").attr('disabled', 'disabled');
$("#datasets tbody").on('click', 'tr', dataSetEditFormOpen);
}
});
// get selected rows and their ids then pass it to dataSet data delete ajax call and reload the grid when done.
$('#deleteSelectedRows').on("click", function(e) {
e.preventDefault();
var rows = [];
var rowIds = [];
var processedRows = 0;
$('#datasets tbody tr.selected').each(function(idx, ele) {
rows.push(table.row(ele).data());
});
for (var i = 0; i < rows.length; i++) {
rowIds.push(rows[i].id);
}
rowIds.forEach(function(id) {
$.ajax({
url: "{{ url_for("dataSet.data.delete", {"id": dataSet.dataSetId, "rowId":':rowId'}) }}".replace(":rowId", id),
type: "DELETE",
success: function (data) {
processedRows++;
if (processedRows === rowIds.length) {
table.ajax.reload();
}
}
});
});
});
// handle add row button when we don't have any value columns in the dataset
var valueColumns = {{ dataSet.columns|filter(column => column.dataSetColumnTypeId == '1')|json_encode|raw }};
var valueColumnsWarningMessage = "{% trans "No value columns have been configured for this dataset. Please configure your columns accordingly." %}";
if(valueColumns.length === 0) {
// remove class xibo form open to prevent opening the form
$('.addRowButton')
.addClass('disabled')
.removeClass('XiboFormButton')
.on('click', function(e) {
// show toast notification
toastr.error(valueColumnsWarningMessage);
});
}
</script>
{% endblock %}

306
views/dataset-form-add.twig Normal file
View File

@@ -0,0 +1,306 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Add DataSet" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#dataSetAddForm").submit()
{% endblock %}
{% block callBack %}dataSetFormOpen{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#general" role="tab" data-toggle="tab"><span>{% trans "General" %}</span></a></li>
<li class="nav-item tabForRemoteDataSet"><a class="nav-link" href="#gateway" role="tab" data-toggle="tab"><span>{% trans "Remote" %}</span></a></li>
<li class="nav-item tabForRemoteDataSet"><a class="nav-link" href="#auth" role="tab" data-toggle="tab"><span>{% trans "Authentication" %}</span></a></li>
<li class="nav-item tabForRemoteDataSet"><a class="nav-link" href="#data" role="tab" data-toggle="tab"><span>{% trans "Data" %}</span></a></li>
<li class="nav-item tabForRemoteDataSet"><a class="nav-link" href="#params" role="tab" data-toggle="tab"><span>{% trans "Advanced" %}</span></a></li>
</ul>
<form id="dataSetAddForm" class="XiboForm custom-validation form-horizontal" method="post" action="{{ url_for("dataSet.add") }}">
<div class="tab-content">
<div class="tab-pane active" id="general">
{% if currentUser.featureEnabled('folder.view') %}
<div class="form-group row">
<label class="col-sm-2 control-label">{% trans "Folder" %}</label>
<div class="col-sm-10">
<button type="button" class="btn btn-info" id="select-folder-button" data-toggle="modal" data-target="#folder-tree-form-modal">{% trans "Select Folder" %}</button>
<span id="selectedFormFolder"></span>
</div>
</div>
{{ forms.hidden('folderId') }}
{% endif %}
{% set title %}{% trans "Name" %}{% endset %}
{% set helpText %}{% trans "A name for this DataSet" %}{% endset %}
{{ forms.input("dataSet", title, "", helpText, "", "required") }}
{% set title %}{% trans "Description" %}{% endset %}
{% set helpText %}{% trans "An optional description" %}{% endset %}
{{ forms.input("description", title, "", helpText) }}
{% set title %}{% trans "Code" %}{% endset %}
{% set helpText %}{% trans "A code which can be used to lookup this DataSet - usually for an API application" %}{% endset %}
{{ forms.input("code", title, "", helpText) }}
{% set title %}{% trans "Remote?" %}{% endset %}
{% set helpText %}{% trans "Is this DataSet connected to a remote data source?" %}{% endset %}
{{ forms.checkbox("isRemote", title, 0, helpText) }}
{% if currentUser.featureEnabled("dataset.realtime") %}
{% set title %}{% trans "Real time?" %}{% endset %}
{% set helpText %}{% trans "Is this DataSet connected to a real time data source?" %}{% endset %}
{{ forms.checkbox("isRealTime", title, 0, helpText) }}
{% endif %}
<div class="d-none" id="dataSourceField">
{% set title %}{% trans "Data Connector Source" %}{% endset %}
{% set helpText %}{% trans "Select data connector source." %}{% endset %}
{{ forms.dropdown("dataConnectorSource", "single", title, "", dataConnectorSources, "id", "name", helpText) }}
</div>
</div>
<div class="tab-pane" id="gateway">
{% set title %}{% trans "Method" %}{% endset %}
{% set helpText %}{% trans "What type of request needs to be made to get the remote data?" %}{% endset %}
{% set request_get %}{% trans "GET" %}{% endset %}
{% set request_post %}{% trans "POST" %}{% endset %}
{% set options = [
{ typeid: "GET", type: request_get },
{ typeid: "POST", type: request_post }
] %}
{{ forms.dropdown("method", "single", title, "", options, "typeid", "type", helpText) }}
{% set title %}{% trans "URI" %}{% endset %}
{% set helpText %}{% trans "The URI of the Remote Dataset used for GET and POST." %}{% endset %}
{{ forms.input("uri", title, "", helpText, "", "required") }}
<div class="form-group row">
<div class="col-sm-12">
<div class="help-block">
<strong>{% trans "Replacements" %}</strong><br>
{% trans "Request date: {{DATE}}" %}<br>
{% trans "Request time: {{TIME}}" %}<br>
{% trans "Dependant fields: {{COL.NAME}} where NAME is a FieldName from the dependant DataSet" %}<br>
</div>
<div class="help-block">
{% trans "Data to add to this request. This should be URL encoded, e.g. paramA=1&amp;paramB=2." %}
</div>
<textarea class="form-control" name="postData" id="postData" rows="10"></textarea>
</div>
</div>
</div>
<div class="tab-pane" id="auth">
{% set title %}{% trans "Authentication" %}{% endset %}
{% set helpText %}{% trans "Select the authentication requirements for the remote data source. These will be added to the request." %}{% endset %}
{% set auth_none %}{% trans "None" %}{% endset %}
{% set auth_basic %}{% trans "Basic" %}{% endset %}
{% set auth_digest %}{% trans "Digest" %}{% endset %}
{% set auth_ntlm %}{% trans "NTLM" %}{% endset %}
{% set auth_bearer %}{% trans "Bearer" %}{% endset %}
{% set options = [
{ typeid: "none", type: auth_none },
{ typeid: "basic", type: auth_basic },
{ typeid: "digest", type: auth_digest },
{ typeid: "ntlm", type: auth_ntlm },
{ typeid: "bearer", type: auth_bearer }
] %}
{{ forms.dropdown("authentication", "single", title, "", options, "typeid", "type", helpText) }}
{% set title %}{% trans "Username" %}{% endset %}
{% set helpText %}{% trans "Enter the authentication Username" %}{% endset %}
{{ forms.input("username", title, "", helpText, "auth-field-username", "") }}
{% set title %}{% trans "Password" %}{% endset %}
{% set helpText %}{% trans "Corresponding Password" %}{% endset %}
{{ forms.input("password", title, "", helpText, "auth-field-password", "") }}
{% set title %}{% trans "Custom Headers" %}{% endset %}
{% set helpText %}{% trans "Comma separated string of custom HTTP headers in headerName:headerValue format" %}{% endset %}
{{ forms.input("customHeaders", title, "", helpText) }}
{% set title %}{% trans "User Agent" %}{% endset %}
{% set helpText %}{% trans "Optionally set specific User Agent for this request, provide only the value, relevant header will be added automatically" %}{% endset %}
{{ forms.input("userAgent", title, "", helpText) }}
</div>
<div class="tab-pane" id="data">
{% set title %}{% trans "Source" %}{% endset %}
{% set helpText %}{% trans "Select source type of the provided remote Dataset URL" %}{% endset %}
{% set json %}{% trans "JSON" %}{% endset %}
{% set csv %}{% trans "CSV" %}{% endset %}
{% set options = [
{ sourceId: 1, source: json },
{ sourceId: 2, source: csv },
] %}
{{ forms.dropdown("sourceId", "single", title, 1, options, "sourceId", "source", helpText) }}
{% set title %}{% trans "Data root" %}{% endset %}
{% set helpText %}{% trans "Please enter the element in your remote data which we should use as the starting point when we match the remote Columns. This should be an array or an object. You can use the test button below to see the structure that is returned." %}{% endset %}
{{ forms.input("dataRoot", title, "", helpText, "json-source-field", "") }}
{% set title %}{% trans "CSV separator" %}{% endset %}
{% set helpText %}{% trans "What separator should be used for CSV source?" %}{% endset %}
{% set comma %}{% trans "Comma" %} (,){% endset %}
{% set semicolon %}{% trans "Semicolon" %} (;){% endset %}
{% set space %}{% trans "Space" %} ( ){% endset %}
{% set tab %}{% trans "Tab" %} (\t){% endset %}
{% set pipe %}{% trans "Pipe" %} (|){% endset %}
{% set options = [
{ separatorId: ',', separator: comma },
{ separatorId: ';', separator: semicolon },
{ separatorId: ' ', separator: space },
{ separatorId: '\t', separator: tab },
{ separatorId: '|', separator: pipe },
] %}
{{ forms.dropdown("csvSeparator", "single", title, ',', options, "separatorId", "separator", helpText, "csv-source-field") }}
{% set title %}{% trans "Ignore first row?" %}{% endset %}
{% set helpText %}{% trans "For CSV source, should the first row be ignored?" %}{% endset %}
{{ forms.checkbox("ignoreFirstRow", title, 0, helpText, "csv-source-field") }}
<div class="form-group row">
<div class="offset-sm-2 col-sm-10">
<a class="btn btn-white" id="dataSetRemoteTestButton" href="#">
{% trans "Test data URL" %}
</a>
</div>
</div>
{% set title %}{% trans "Aggregation" %}{% endset %}
{% set helpText %}{% trans "Aggregate received data by the given method" %}{% endset %}
{% set summ_none %}{% trans "None" %}{% endset %}
{% set summ_sum %}{% trans "Summarize" %}{% endset %}
{% set summ_count %}{% trans "Count" %}{% endset %}
{% set options = [
{ typeid: "none", type: summ_none },
{ typeid: "sum", type: summ_sum },
{ typeid: "count", type: summ_count }
] %}
{{ forms.dropdown("summarize", "single", title, "", options, "typeid", "type", helpText, "json-source-field") }}
<div class="form-group row json-source-field">
<label class="col-sm-2 control-label" for="summarizeField">{% trans "By Field" %}</label>
<div class="col-sm-10">
<input class="form-control" type="text" name="summarizeField" id="summarizeField" />
<div class="help-block">
{% trans "Using JSON syntax enter the path below the Data root by which the above aggregation should be applied." %}<br>
{% trans "Summarize: Values in this field will be summarized and stored in one column." %}<br>
{% trans "Count: All individual values in this field will be counted and stored in one Column for each value" %}<br>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="params">
{% set title %}{% trans "Refresh" %}{% endset %}
{% set helpText %}{% trans "How often should this remote data be fetched and imported?" %}{% endset %}
{% set refresh_everytime %}{% trans "Constantly" %}{% endset %}
{% set refresh_hour %}{% trans "Hourly" %}{% endset %}
{% set refresh_day %}{% trans "Daily" %}{% endset %}
{% set refresh_week %}{% trans "Weekly" %}{% endset %}
{% set refresh_two_week %}{% trans "Every two Weeks" %}{% endset %}
{% set refresh_month %}{% trans "Monthly" %}{% endset %}
{% set refresh_quater %}{% trans "Quaterly" %}{% endset %}
{% set refresh_year %}{% trans "Yearly" %}{% endset %}
{% set options = [
{ typeid: "0", type: refresh_everytime },
{ typeid: "3600", type: refresh_hour },
{ typeid: "86400", type: refresh_day },
{ typeid: "604800", type: refresh_week },
{ typeid: "1209600", type: refresh_two_week },
{ typeid: "2419200", type: refresh_month },
{ typeid: "7257600", type: refresh_quater },
{ typeid: "29030400", type: refresh_year }
] %}
{{ forms.dropdown("refreshRate", "single", title, "", options, "typeid", "type", helpText) }}
{% set title %}{% trans "Truncate DataSet" %}{% endset %}
{% set helpText %}{% trans "Select when you would like the Data to be truncated out of this DataSet. The criteria is assessed when synchronisation occurs and is truncated before adding new data." %}{% endset %}
{% set truncate_never %}{% trans "Never" %}{% endset %}
{% set truncate_everytime %}{% trans "Always" %}{% endset %}
{% set truncate_hour %}{% trans "Hourly" %}{% endset %}
{% set truncate_day %}{% trans "Daily" %}{% endset %}
{% set truncate_week %}{% trans "Weekly" %}{% endset %}
{% set truncate_two_week %}{% trans "Every two Weeks" %}{% endset %}
{% set truncate_month %}{% trans "Monthly" %}{% endset %}
{% set truncate_quater %}{% trans "Quaterly" %}{% endset %}
{% set truncate_year %}{% trans "Yearly" %}{% endset %}
{% set truncate_two_year %}{% trans "Every second Year" %}{% endset %}
{% set options = [
{ typeid: "0", type: truncate_never },
{ typeid: "1", type: truncate_everytime },
{ typeid: "3600", type: truncate_hour },
{ typeid: "86400", type: truncate_day },
{ typeid: "604800", type: truncate_week },
{ typeid: "1209600", type: truncate_two_week },
{ typeid: "2419200", type: truncate_month },
{ typeid: "7257600", type: truncate_quater },
{ typeid: "29030400", type: truncate_year },
{ typeid: "58060800", type: truncate_two_year }
] %}
{{ forms.dropdown("clearRate", "single", title, "1", options, "typeid", "type", helpText) }}
{% set title %}{% trans "Truncate with no new data?" %}{% endset %}
{% set helpText %}{% trans "Should the DataSet data be truncated even if no new data is pulled from the source?" %}{% endset %}
{{ forms.checkbox("truncateOnEmpty", title, 0, helpText) }}
{% set title %}{% trans "Depends on DataSet" %}{% endset %}
{% set dataSets = [{dataSetId: null, dataSet: ""}]|merge(dataSets) %}
{% set helpText %}{% trans "The DataSet you select here will be processed in advance and have its values available for subsitution in the data to add to this request on the Remote tab." %}{% endset %}
{{ forms.dropdown("runsAfter", "single", title, "", dataSets, "dataSetId", "dataSet", helpText) }}
{% set title %}{% trans "Row Limit" %}{% endset %}
{% set helpText %}{% trans "Optionally provide a row limit for this DataSet. When left empty the DataSet row limit from CMS Settings will be used." %}{% endset %}
{{ forms.number("rowLimit", title, "", helpText) }}
{% set title %}{% trans "Limit Policy" %}{% endset %}
{% set helpText %}{% trans "What should happen when this Dataset reaches the row limit?" %}{% endset %}
{% set stop %}{% trans "Stop Syncing" %}{% endset %}
{% set fifo %}{% trans "First In First Out" %}{% endset %}
{% set truncate %}{% trans "Truncate" %}{% endset %}
{% set options = [
{ typeid: "stop", type: stop },
{ typeid: "fifo", type: fifo },
{ typeid: "truncate", type: truncate },
] %}
{{ forms.dropdown("limitPolicy", "single", title, "", options, "typeid", "type", helpText) }}
</div>
</div>
</form>
<div id="datasetRemoteTestRequestResult" class="col-sm-12 bg-light"></div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{#
/**
* Copyright (C) 2023 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 "Clear DataSet cache" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#dataSetClearCacheForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="dataSetClearCacheForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("dataSet.clear.cache", {"id": dataSet.dataSetId}) }}">
{% set message %}{% trans "Should the cache for this remote DataSet be cleared?" %}{% endset %}
{{ forms.message(message) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,60 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% set dataSetName = dataSet.dataSet %}
{% trans %}Copy {{ dataSetName }}{% endtrans %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#dataSetCopyForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="dataSetCopyForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("dataSet.copy", {"id": dataSet.dataSetId}) }}">
{% set title %}{% trans "Name" %}{% endset %}
{% set helpText %}{% trans "A name for this DataSet" %}{% endset %}
{% set dataSetName %}{{ dataSet.dataSet }} 2{% endset %}
{{ forms.input("dataSet", title, dataSetName, helpText, "", "required") }}
{% set title %}{% trans "Description" %}{% endset %}
{% set helpText %}{% trans "An optional description" %}{% endset %}
{{ forms.input("description", title, "", helpText) }}
{% set title %}{% trans "Code" %}{% endset %}
{% set helpText %}{% trans "A code which can be used to lookup this DataSet - usually for an API application" %}{% endset %}
{{ forms.input("code", title, "", helpText) }}
{% set title %}{% trans "Copy rows?" %}{% endset %}
{% set helpText %}{% trans "Should we copy all the row data from the original dataSet?" %}{% endset %}
{{ forms.checkbox("copyRows", title, 0, helpText) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,49 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Delete this DataSet?" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Yes" %}, $("#dataSetDeleteForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="dataSetDeleteForm" class="XiboForm form-horizontal" method="delete" action="{{ url_for("dataSet.delete", {id: dataSet.dataSetId}) }}">
{% set message %}{% trans "Are you sure you want to delete?" %}{% endset %}
{{ forms.message(message) }}
{% set title %}{% trans "Delete any associated data?" %}{% endset %}
{% set helpText %}{% trans "Please tick the box if you would like to delete all the Data contained in this DataSet" %}{% endset %}
{{ forms.checkbox("deleteData", title, "", helpText) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,327 @@
{#
/*
* Copyright (C) 2023 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 "Edit DataSet" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#dataSetEditForm").submit()
{% endblock %}
{% block callBack %}dataSetFormOpen{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#general" role="tab" data-toggle="tab"><span>{% trans "General" %}</span></a></li>
<li class="nav-item tabForRemoteDataSet"><a class="nav-link" href="#gateway" role="tab" data-toggle="tab"><span>{% trans "Remote" %}</span></a></li>
<li class="nav-item tabForRemoteDataSet"><a class="nav-link" href="#auth" role="tab" data-toggle="tab"><span>{% trans "Authentication" %}</span></a></li>
<li class="nav-item tabForRemoteDataSet"><a class="nav-link" href="#data" role="tab" data-toggle="tab"><span>{% trans "Data" %}</span></a></li>
<li class="nav-item tabForRemoteDataSet"><a class="nav-link" href="#params" role="tab" data-toggle="tab"><span>{% trans "Advanced" %}</span></a></li>
</ul>
<form id="dataSetEditForm" class="XiboForm custom-validation form-horizontal" method="put" action="{{ url_for("dataSet.edit", {id: dataSet.dataSetId}) }}">
<input type="hidden" name="testDataSetId" value="{{ dataSet.dataSetId }}"/>
<div class="tab-content">
<div class="tab-pane active" id="general">
{% if currentUser.featureEnabled('folder.view') %}
<div class="form-group row">
<label class="col-sm-2 control-label">{% trans "Current Folder" %}</label>
<div class="col-sm-10" style="padding-top: 7px">
<span id="originalFormFolder"></span>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 control-label">{% trans "Move to Selected Folder:" %}</label>
<div class="col-sm-10">
<button type="button" class="btn btn-info" id="select-folder-button" data-toggle="modal" data-target="#folder-tree-form-modal">{% trans "Select Folder" %}</button>
<span id="selectedFormFolder"></span>
</div>
</div>
{{ forms.hidden('folderId', dataSet.folderId) }}
{% endif %}
{% set title %}{% trans "Name" %}{% endset %}
{% set helpText %}{% trans "A name for this DataSet" %}{% endset %}
{{ forms.input("dataSet", title, dataSet.dataSet, helpText, "", "required") }}
{% set title %}{% trans "Description" %}{% endset %}
{% set helpText %}{% trans "An optional description" %}{% endset %}
{{ forms.input("description", title, dataSet.description, helpText) }}
{% set title %}{% trans "Code" %}{% endset %}
{% set helpText %}{% trans "A code which can be used to lookup this DataSet - usually for an API application" %}{% endset %}
{{ forms.input("code", title, dataSet.code, helpText) }}
{% set title %}{% trans "Remote?" %}{% endset %}
{% set helpText %}{% trans "Is this DataSet connected to a remote data source?" %}{% endset %}
{{ forms.checkbox("isRemote", title, dataSet.isRemote, helpText) }}
{% set title %}{% trans "Real time?" %}{% endset %}
{% set helpText %}{% trans "Is this DataSet connected to a real time data source?" %}{% endset %}
{{ forms.checkbox("isRealTime", title, dataSet.isRealTime, helpText) }}
<div class="d-none" id="dataSourceField">
{% set title %}{% trans "Data Connector Source" %}{% endset %}
{% set helpText %}{% trans "Select data connector source." %}{% endset %}
{{ forms.dropdown("dataConnectorSource", "single", title, dataSet.dataConnectorSource, dataConnectorSources, "id", "name", helpText) }}
</div>
{% if dataSet.isRemote %}
{% set columnCount = dataSet.columns|filter(column => column.dataSetColumnTypeId == '3') %}
{% if columnCount|length == 0 %}
{{ forms.message("No remote columns have been configured for this dataset. Please configure your columns accordingly."|trans, "alert alert-warning") }}
{% endif %}
{% else %}
{% set columnCount = dataSet.columns|filter(column => column.dataSetColumnTypeId == '1') %}
{% if columnCount|length == 0 %}
{{ forms.message("No value columns have been configured for this dataset. Please configure your columns accordingly."|trans, "alert alert-warning") }}
{% endif %}
{% endif %}
{% if dataSet.isActive() %}
{{ forms.message("This DataSet has been accessed or updated recently, which means the CMS will keep it active."|trans, "alert alert-success") }}
{% endif %}
</div>
<div class="tab-pane" id="gateway">
{% set title %}{% trans "Method" %}{% endset %}
{% set helpText %}{% trans "What type of request needs to be made to get the remote data?" %}{% endset %}
{% set request_get %}{% trans "GET" %}{% endset %}
{% set request_post %}{% trans "POST" %}{% endset %}
{% set options = [
{ typeid: "GET", type: request_get },
{ typeid: "POST", type: request_post }
] %}
{{ forms.dropdown("method", "single", title, dataSet.method, options, "typeid", "type", helpText) }}
{% set title %}{% trans "URI" %}{% endset %}
{% set helpText %}{% trans "The URI of the Remote Dataset used for GET and POST." %}{% endset %}
{{ forms.input("uri", title, dataSet.uri, helpText, "", "required") }}
<div class="form-group row">
<div class="col-sm-12">
<div class="help-block">
<strong>{% trans "Replacements" %}</strong><br>
{% trans "Request date: {{DATE}}" %}<br>
{% trans "Request time: {{TIME}}" %}<br>
{% trans "Dependant fields: {{COL.NAME}} where NAME is a FieldName from the dependant DataSet" %}<br>
</div>
<div class="help-block">
{% trans "Data to add to this request. This should be URL encoded, e.g. paramA=1&amp;paramB=2." %}
</div>
<textarea class="form-control" name="postData" id="postData" rows="10">{{ dataSet.postData }}</textarea>
</div>
</div>
</div>
<div class="tab-pane" id="auth">
{% set title %}{% trans "Authentication" %}{% endset %}
{% set helpText %}{% trans "Select the authentication requirements for the remote data source. These will be added to the request." %}{% endset %}
{% set auth_none %}{% trans "None" %}{% endset %}
{% set auth_basic %}{% trans "Basic" %}{% endset %}
{% set auth_digest %}{% trans "Digest" %}{% endset %}
{% set auth_ntlm %}{% trans "NTLM" %}{% endset %}
{% set auth_bearer %}{% trans "Bearer" %}{% endset %}
{% set options = [
{ typeid: "none", type: auth_none },
{ typeid: "basic", type: auth_basic },
{ typeid: "digest", type: auth_digest },
{ typeid: "ntlm", type: auth_ntlm },
{ typeid: "bearer", type: auth_bearer }
] %}
{{ forms.dropdown("authentication", "single", title, dataSet.authentication, options, "typeid", "type", helpText) }}
{% set title %}{% trans "Username" %}{% endset %}
{% set helpText %}{% trans "Enter the authentication Username" %}{% endset %}
{{ forms.input("username", title, dataSet.username, helpText, "auth-field-username", "") }}
{% set title %}{% trans "Password" %}{% endset %}
{% set helpText %}{% trans "Corresponding Password" %}{% endset %}
{{ forms.input("password", title, dataSet.password, helpText, "auth-field-password", "") }}
{% set title %}{% trans "Custom Headers" %}{% endset %}
{% set helpText %}{% trans "Comma separated string of custom HTTP headers in headerName:headerValue format" %}{% endset %}
{{ forms.input("customHeaders", title, dataSet.customHeaders, helpText) }}
{% set title %}{% trans "User Agent" %}{% endset %}
{% set helpText %}{% trans "Optionally set specific User Agent for this request, provide only the value, relevant header will be added automatically" %}{% endset %}
{{ forms.input("userAgent", title, dataSet.userAgent, helpText) }}
</div>
<div class="tab-pane" id="data">
{% set title %}{% trans "Source" %}{% endset %}
{% set helpText %}{% trans "Select source type of the provided remote Dataset URL" %}{% endset %}
{% set json %}{% trans "JSON" %}{% endset %}
{% set csv %}{% trans "CSV" %}{% endset %}
{% set options = [
{ sourceId: 1, source: json },
{ sourceId: 2, source: csv },
] %}
{{ forms.dropdown("sourceId", "single", title, dataSet.sourceId, options, "sourceId", "source", helpText) }}
{% set title %}{% trans "Data root" %}{% endset %}
{% set helpText %}{% trans "Please enter the element in your remote data which we should use as the starting point when we match the remote Columns. This should be an array or an object. You can use the test button below to see the structure that is returned." %}{% endset %}
{{ forms.input("dataRoot", title, dataSet.dataRoot, helpText, "json-source-field", "") }}
{% set title %}{% trans "CSV separator" %}{% endset %}
{% set helpText %}{% trans "What separator should be used for CSV source?" %}{% endset %}
{% set comma %}{% trans "Comma" %} (,){% endset %}
{% set semicolon %}{% trans "Semicolon" %} (;){% endset %}
{% set space %}{% trans "Space" %} ( ){% endset %}
{% set tab %}{% trans "Tab" %} (\t){% endset %}
{% set pipe %}{% trans "Pipe" %} (|){% endset %}
{% set options = [
{ separatorId: ',', separator: comma },
{ separatorId: ';', separator: semicolon },
{ separatorId: ' ', separator: space },
{ separatorId: '\t', separator: tab },
{ separatorId: '|', separator: pipe },
] %}
{{ forms.dropdown("csvSeparator", "single", title, dataSet.csvSeparator, options, "separatorId", "separator", helpText, "csv-source-field") }}
{% set title %}{% trans "Ignore first row?" %}{% endset %}
{% set helpText %}{% trans "For CSV source, should the first row be ignored?" %}{% endset %}
{{ forms.checkbox("ignoreFirstRow", title, dataSet.ignoreFirstRow, helpText, 'csv-source-field') }}
<div class="form-group row">
<div class="offset-sm-2 col-sm-10">
<a class="btn btn-white" id="dataSetRemoteTestButton" href="#">
{% trans "Test data URL" %}
</a>
</div>
</div>
{% set title %}{% trans "Aggregation" %}{% endset %}
{% set helpText %}{% trans "Aggregate received data by the given method" %}{% endset %}
{% set summ_none %}{% trans "None" %}{% endset %}
{% set summ_sum %}{% trans "Summarize" %}{% endset %}
{% set summ_count %}{% trans "Count" %}{% endset %}
{% set options = [
{ typeid: "none", type: summ_none },
{ typeid: "sum", type: summ_sum },
{ typeid: "count", type: summ_count }
] %}
{{ forms.dropdown("summarize", "single", title, dataSet.summarize, options, "typeid", "type", helpText, "json-source-field") }}
<div class="form-group row json-source-field">
<label class="col-sm-2 control-label" for="summarizeField">{% trans "By Field" %}</label>
<div class="col-sm-10">
<input class="form-control" type="text" name="summarizeField" id="summarizeField" value="{{ dataSet.summarizeField }}"/>
<div class="help-block">
{% trans "Using JSON syntax enter the path below the Data root by which the above aggregation should be applied." %}<br>
{% trans "Summarize: Values in this field will be summarized and stored in one column." %}<br>
{% trans "Count: All individual values in this field will be counted and stored in one Column for each value" %}<br>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="params">
{% set title %}{% trans "Refresh" %}{% endset %}
{% set helpText %}{% trans "How often should this remote data be fetched and imported?" %}{% endset %}
{% set refresh_everytime %}{% trans "Constantly" %}{% endset %}
{% set refresh_hour %}{% trans "Hourly" %}{% endset %}
{% set refresh_day %}{% trans "Daily" %}{% endset %}
{% set refresh_week %}{% trans "Weekly" %}{% endset %}
{% set refresh_two_week %}{% trans "Every two Weeks" %}{% endset %}
{% set refresh_month %}{% trans "Monthly" %}{% endset %}
{% set refresh_quater %}{% trans "Quaterly" %}{% endset %}
{% set refresh_year %}{% trans "Yearly" %}{% endset %}
{% set options = [
{ typeid: "0", type: refresh_everytime },
{ typeid: "3600", type: refresh_hour },
{ typeid: "86400", type: refresh_day },
{ typeid: "604800", type: refresh_week },
{ typeid: "1209600", type: refresh_two_week },
{ typeid: "2419200", type: refresh_month },
{ typeid: "7257600", type: refresh_quater },
{ typeid: "29030400", type: refresh_year }
] %}
{{ forms.dropdown("refreshRate", "single", title, dataSet.refreshRate, options, "typeid", "type", helpText) }}
{% set title %}{% trans "Truncate DataSet" %}{% endset %}
{% set helpText %}{% trans "Select when you would like the Data to be truncated out of this DataSet. The criteria is assessed when synchronisation occurs and is truncated before adding new data." %}{% endset %}
{% set truncate_never %}{% trans "Never" %}{% endset %}
{% set truncate_everytime %}{% trans "Always" %}{% endset %}
{% set truncate_hour %}{% trans "Hourly" %}{% endset %}
{% set truncate_day %}{% trans "Daily" %}{% endset %}
{% set truncate_week %}{% trans "Weekly" %}{% endset %}
{% set truncate_two_week %}{% trans "Every two Weeks" %}{% endset %}
{% set truncate_month %}{% trans "Monthly" %}{% endset %}
{% set truncate_quater %}{% trans "Quaterly" %}{% endset %}
{% set truncate_year %}{% trans "Yearly" %}{% endset %}
{% set truncate_two_year %}{% trans "Every second Year" %}{% endset %}
{% set options = [
{ typeid: "0", type: truncate_never },
{ typeid: "1", type: truncate_everytime },
{ typeid: "3600", type: truncate_hour },
{ typeid: "86400", type: truncate_day },
{ typeid: "604800", type: truncate_week },
{ typeid: "1209600", type: truncate_two_week },
{ typeid: "2419200", type: truncate_month },
{ typeid: "7257600", type: truncate_quater },
{ typeid: "29030400", type: truncate_year },
{ typeid: "58060800", type: truncate_two_year }
] %}
{{ forms.dropdown("clearRate", "single", title, dataSet.clearRate, options, "typeid", "type", helpText) }}
{% set title %}{% trans "Truncate with no new data?" %}{% endset %}
{% set helpText %}{% trans "Should the DataSet data be truncated even if no new data is pulled from the source?" %}{% endset %}
{{ forms.checkbox("truncateOnEmpty", title, dataSet.truncateOnEmpty, helpText) }}
{% set title %}{% trans "Depends on DataSet" %}{% endset %}
{% set dataSets = [{dataSetId: null, dataSet: ""}]|merge(dataSets) %}
{% set helpText %}{% trans "The DataSet you select here will be processed in advance and have its values available for subsitution in the data to add to this request on the Remote tab." %}{% endset %}
{{ forms.dropdown("runsAfter", "single", title, dataSet.runsAfter, dataSets, "dataSetId", "dataSet", helpText) }}
{% set title %}{% trans "Row Limit" %}{% endset %}
{% set helpText %}{% trans "Optionally provide a row limit for this DataSet. When left empty the DataSet row limit from CMS Settings will be used." %}{% endset %}
{{ forms.number("rowLimit", title, dataSet.rowLimit, helpText) }}
{% set title %}{% trans "Limit Policy" %}{% endset %}
{% set helpText %}{% trans "What should happen when this Dataset reaches the row limit?" %}{% endset %}
{% set stop %}{% trans "Stop Syncing" %}{% endset %}
{% set fifo %}{% trans "First In First Out" %}{% endset %}
{% set truncate %}{% trans "Truncate" %}{% endset %}
{% set options = [
{ typeid: "stop", type: stop },
{ typeid: "fifo", type: fifo },
{ typeid: "truncate", type: truncate },
] %}
{{ forms.dropdown("limitPolicy", "single", title, dataSet.limitPolicy, options, "typeid", "type", helpText) }}
</div>
</div>
</form>
<div id="datasetRemoteTestRequestResult" class="col-sm-12 bg-light"></div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,58 @@
{#
/**
* Copyright (C) 2024 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 %}
{% set name = dataSet.dataSet %}
{% trans %} Data set {{ name }} {% endtrans %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#dataSetSelectFolderForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="dataSetSelectFolderForm" class="XiboForm form-horizontal" method="put" action="{{ url_for("dataSet.selectfolder", {id: dataSet.dataSetId}) }}">
<div class="form-group row">
<label class="col-sm-2 control-label">{% trans "Current Folder" %}</label>
<div class="col-sm-10" style="padding-top: 7px">
<span id="originalFormFolder"></span>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 control-label">{% trans "Move to Selected Folder:" %}</label>
<div class="col-sm-10">
<div class="card p-3 mb-3 bg-light" id="container-folder-form-tree"></div>
</div>
</div>
{{ forms.hidden('folderId', dataSet.folderId) }}
</form>
</div>
</div>
{% endblock %}

584
views/dataset-page.twig Normal file
View File

@@ -0,0 +1,584 @@
{#
/**
* 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 %}
{% import "forms.twig" as forms %}
{% block title %}{{ "DataSets"|trans }} | {% endblock %}
{% block actionMenu %}
<div class="widget-action-menu pull-right">
{% if currentUser.featureEnabled("dataset.add") %}
<button class="btn btn-success XiboFormButton btns" title="{% trans "Add a new DataSet" %}" href="{{ url_for("dataSet.add.form") }}"> <i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add DataSet" %}</button>
{% endif %}
<button class="btn btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
</div>
{% endblock %}
{% block pageContent %}
<div class="widget">
<div class="widget-title">{% trans "DataSets" %}</div>
<div class="widget-body">
<div class="XiboGrid" id="{{ random() }}" data-grid-name="dataSetView">
<div class="XiboFilter card mb-3 bg-light">
<div class="FilterDiv card-body" id="Filter">
<form class="form-inline" onsubmit="return false">
{% set title %}{% trans "Name" %}{% endset %}
{{ inline.inputNameGrid('dataSet', title) }}
{% set title %}{% trans "Code" %}{% endset %}
{% set helpText %}{% trans "Show items which match the provided code" %}{% endset %}
{{ inline.input("code", title, "", helpText) }}
{% set title %}{% trans "Owner" %}{% endset %}
{% set helpText %}{% trans "Show items owned by the selected 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-search-term-tags", value: "tags" },
{ name: "data-id-property", value: "userId" },
{ name: "data-text-property", value: "userName" },
{ name: "data-initial-key", value: "userId" },
] %}
{{ inline.dropdown("userId", "single", title, "", null, "userId", "userName", helpText, "pagedSelect", "", "", "", attributes) }}
{{ inline.hidden("folderId") }}
</form>
</div>
</div>
<div class="grid-with-folders-container">
<div class="grid-folder-tree-container p-3" id="grid-folder-filter">
<input id="jstree-search" class="form-control" type="text" placeholder="{% trans "Search" %}">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="folder-tree-clear-selection-button">
<label class="form-check-label" for="folder-tree-clear-selection-button" title="{% trans "Search in all folders" %}">{% trans "All Folders" %}</label>
</div>
<div class="folder-search-no-results d-none">
<p>{% trans 'No Folders matching the search term' %}</p>
</div>
<div id="container-folder-tree"></div>
</div>
<div class="folder-controller d-none">
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
<div id="breadcrumbs" class="mt-2 pl-2"></div>
</div>
<div id="datatable-container">
<div class="XiboData card py-3">
<table id="datasets" class="table table-striped" data-state-preference-name="dataSetGrid" style="width: 100%;">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Name" %}</th>
<th>{% trans "Description" %}</th>
<th>{% trans "Code" %}</th>
<th>{% trans "Remote?" %}</th>
<th>{% trans "Real time?" %}</th>
<th>{% trans "Owner" %}</th>
<th>{% trans "Sharing" %}</th>
<th>{% trans "Last Sync" %}</th>
<th>{% trans "Data Last Modified" %}</th>
<th class="rowMenu"></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javaScript %}
<script type="text/javascript" nonce="{{ cspNonce }}">
var table;
$(document).ready(function() {
{% if not currentUser.featureEnabled("folder.view") %}
disableFolders();
{% endif %}
table = $("#datasets").DataTable({
"language": dataTablesLanguage,
dom: dataTablesTemplate,
serverSide: true,
stateSave: true,
stateDuration: 0,
responsive: true,
stateLoadCallback: dataTableStateLoadCallback,
stateSaveCallback: dataTableStateSaveCallback,
filter: false,
searchDelay: 3000,
"order": [[ 0, "asc"]],
ajax: {
"url": "{{ url_for("dataSet.search") }}",
"data": function(d) {
$.extend(d, $("#datasets").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
}
},
"columns": [
{ "data": "dataSetId", responsivePriority: 2 },
{ "data": "dataSet", "render": dataTableSpacingPreformatted, responsivePriority: 2 },
{ "data": "description", responsivePriority: 4 },
{ "data": "code", responsivePriority: 3 },
{
"data": "isRemote",
responsivePriority: 3,
"render": dataTableTickCrossColumn
},
{
data: 'isRealTime',
responsivePriority: 3,
render: dataTableTickCrossColumn,
},
{ "data": "owner", responsivePriority: 3 },
{
"data": "groupsWithPermissions",
responsivePriority: 3,
"render": dataTableCreatePermissions
},
{
"data": "lastSync",
responsivePriority: 4,
"render": dataTableDateFromUnix
},
{
"data": "lastDataEdit",
responsivePriority: 4,
"render": dataTableDateFromUnix
},
{
"orderable": false,
responsivePriority: 1,
"data": dataTableButtonsColumn
}
]
});
table.on('draw', function(e, settings) {
dataTableDraw(e, settings);
// Upload form
$(".dataSetImportForm").click(function(e) {
e.preventDefault();
var template = Handlebars.compile($("#template-dataset-upload").html());
var data = table.row($(this).closest("tr")).data();
var columns = [];
var i = 1;
$.each(data.columns, function (index, element) {
if (element.dataSetColumnTypeId === 1) {
element.index = i;
columns.push(element);
i++;
}
});
// Handle bars and open a dialog
bootbox.dialog({
message: template({
trans: {
addFiles: "{% trans "Add CSV Files" %}",
startUpload: "{% trans "Start upload" %}",
cancelUpload: "{% trans "Cancel upload" %}",
processing: "{% trans "Processing..." %}"
},
upload: {
maxSize: {{ libraryUpload.maxSize }},
maxSizeMessage: "{{ libraryUpload.maxSizeMessage }}",
validExt: "{{ libraryUpload.validExt }}",
utf8Message: "{% trans "If the CSV file contains non-ASCII characters please ensure the file is UTF-8 encoded" %}"
},
columns: columns
}),
title: "{% trans "CSV Import" %}",
size: 'large',
buttons: {
main: {
label: "{% trans "Done" %}",
className: "btn-primary btn-bb-main",
callback: function() {
table.ajax.reload();
XiboDialogClose();
}
}
}
}).on('shown.bs.modal', function() {
// Configure the upload form
var url = "{{ url_for("dataSet.import", {id: ':id'}) }}".replace(":id", data.dataSetId);
var form = $(this).find("form");
var refreshSessionInterval;
// Initialize the jQuery File Upload widget:
form.fileupload({
url: url,
disableImageResize: true
});
// Upload server status check for browsers with CORS support:
if ($.support.cors) {
$.ajax({
url: url,
type: 'HEAD'
}).fail(function () {
$('<span class="alert alert-error"/>')
.text('Upload server currently unavailable - ' + new Date())
.appendTo(form);
});
}
// Enable iframe cross-domain access via redirect option:
form.fileupload(
'option',
'redirect',
window.location.href.replace(
/\/[^\/]*$/,
'/cors/result.html?%s'
)
);
form.bind('fileuploadsubmit', function (e, data) {
var inputs = data.context.find(':input');
if (inputs.filter('[required][value=""]').first().focus().length) {
return false;
}
data.formData = inputs.serializeArray().concat(form.serializeArray());
inputs.filter("input").prop("disabled", true);
}).bind('fileuploadstart', function (e, data) {
// Show progress data
form.find('.fileupload-progress .progress-extended').show();
form.find('.fileupload-progress .progress-end').hide();
if (form.fileupload("active") <= 0)
refreshSessionInterval = setInterval("XiboPing('" + pingUrl + "?refreshSession=true')", 1000 * 60 * 3);
return true;
}).bind('fileuploaddone', function (e, data) {
if (refreshSessionInterval != null && form.fileupload("active") <= 0)
clearInterval(refreshSessionInterval);
}).bind('fileuploadprogressall', function (e, data) {
// Hide progress data and show processing
if(data.total > 0 && data.loaded == data.total) {
form.find('.fileupload-progress .progress-extended').hide();
form.find('.fileupload-progress .progress-end').show();
}
}).bind('fileuploadadded fileuploadcompleted fileuploadfinished', function (e, data) {
// Get uploaded and downloaded files and toggle Done button
var filesToUploadCount = form.find('tr.template-upload').length;
var $button = form.parents('.modal:first').find('button.btn-bb-main');
if(filesToUploadCount == 0) {
$button.removeAttr('disabled');
} else {
$button.attr('disabled', 'disabled');
}
});
});
});
});
table.on('processing.dt', dataTableProcessing);
dataTableAddButtons(table, $('#datasets_wrapper').find('.dataTables_buttons'));
$("#refreshGrid").click(function () {
table.ajax.reload();
});
});
function dataSetFormOpen(dialog) {
// Bind the remote dataset test button
$(dialog).find("#dataSetRemoteTestButton").on('click', function() {
var $form = $(dialog).find("form");
XiboRemoteRequest("{{ url_for("dataSet.test.remote") }}", $form.serializeObject(), function(response) {
if (!response.success || !$.trim(response.data.entries)) {
response.data = response.message;
}
$("#datasetRemoteTestRequestResult").html('<pre style="height: 300px; overflow: scroll">' + JSON.stringify(response.data, null, 3) + '</pre>');
});
});
// Set up some dependencies between the isRemote checkbox and the tabs related to remote datasets
onRemoteFieldChanged(dialog);
// show data source dropdown if real time is checked
onIsRealTimeFieldChanged(dialog);
$(dialog).find("#isRemote").on('change', function() {
onRemoteFieldChanged(dialog);
});
$(dialog).find("#isRealTime").on('change', function() {
onIsRealTimeFieldChanged(dialog);
});
// Auth field
onAuthenticationFieldChanged(dialog);
$(dialog).find("#authentication").on('change', function() {
onAuthenticationFieldChanged(dialog);
});
// remote DataSet source
onSourceFieldChanged(dialog);
$(dialog).find('#sourceId').on('change', function() {
onSourceFieldChanged(dialog);
});
// Validate form manually because
// uri field depends on isRemote being checked
if (forms != undefined) {
const $form = $(dialog).find('form');
forms.validateForm(
$form, // form
$form.parent(), // container
{
submitHandler: XiboFormSubmit,
rules: {
uri: {
required: function(element) {
return $form.find('#isRemote').is(':checked')
},
},
},
},
);
}
}
function onIsRealTimeFieldChanged(dialog) {
var isRealTime = $(dialog).find("#isRealTime").is(":checked");
var dataSourceField = $(dialog).find("#dataSourceField");
var dataConnectorSource = $(dialog).find("#dataConnectorSource");
if (isRealTime) {
// show and enable data connector source
dataSourceField.removeClass("d-none");
dataConnectorSource.prop('disabled', false)
} else {
// hide and disable data connector source
dataSourceField.addClass("d-none");
dataConnectorSource.prop('disabled', true)
}
}
function onRemoteFieldChanged(dialog) {
var isRemote = $(dialog).find("#isRemote").is(":checked");
var $remoteTabs = $(dialog).find(".tabForRemoteDataSet");
if (isRemote) {
$remoteTabs.removeClass("d-none");
} else {
$remoteTabs.addClass("d-none");
}
}
function onAuthenticationFieldChanged(dialog) {
var authentication = $(dialog).find("#authentication").val();
var $authFieldUserName = $(dialog).find(".auth-field-username");
var $authFieldPassword = $(dialog).find(".auth-field-password");
if (authentication === "none") {
$authFieldUserName.addClass("d-none");
$authFieldPassword.addClass("d-none");
} else if (authentication === "bearer") {
$authFieldUserName.addClass("d-none");
$authFieldPassword.removeClass("d-none");
} else {
$authFieldUserName.removeClass("d-none");
$authFieldPassword.removeClass("d-none");
}
}
function onSourceFieldChanged(dialog) {
var sourceId = $(dialog).find('#sourceId').val();
var $jsonSource = $(dialog).find(".json-source-field");
var $csvSource = $(dialog).find(".csv-source-field");
if (sourceId == 1) {
$jsonSource.removeClass('d-none');
$csvSource.addClass('d-none');
} else {
$jsonSource.addClass('d-none');
$csvSource.removeClass('d-none');
}
}
function deleteMultiSelectFormOpen(dialog) {
{% set message = 'Delete any associated data?' %}
var $input = $('<input type=checkbox id="deleteData" name="deleteData"> {{ message|trans|e }} </input>');
$input.on('change', function() {
dialog.data().commitData = {deleteData: $(this).val()};
});
$(dialog).find('.modal-body').append($input);
}
</script>
{% endblock %}
{% block javaScriptTemplates %}
{{ parent() }}
{% verbatim %}
<script type="text/x-handlebars-template" id="template-dataset-upload">
<form class="form-horizontal" method="post" enctype="multipart/form-data" data-max-file-size="{{ upload.maxSize }}" data-accept-file-types="/(\.|\/)csv/i">
<div class="row fileupload-buttonbar">
<div class="card p-3 mb-3 bg-light">
{{ upload.maxSizeMessage }} <br>
{{ upload.utf8Message }}
</div>
<div class="col-md-7">
<!-- The fileinput-button span is used to style the file input field as button -->
<span class="btn btn-success fileinput-button">
<i class="fa fa-plus"></i>
<span>{{ trans.addFiles }}</span>
<input type="file" name="files">
</span>
<button type="submit" class="btn btn-primary start">
<i class="fa fa-upload"></i>
<span>{{ trans.startUpload }}</span>
</button>
<button type="reset" class="btn btn-warning cancel">
<i class="fa fa-ban"></i>
<span>{{ trans.cancelUpload }}</span>
</button>
<!-- The loading indicator is shown during file processing -->
<span class="fileupload-loading"></span>
</div>
<!-- The global progress information -->
<div class="col-md-4 fileupload-progress fade">
<!-- The global progress bar -->
<div class="progress">
<div class="progress-bar progress-bar-success progress-bar-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" style="width:0%;">
<div class="sr-only"></div>
</div>
</div>
<!-- The extended global progress information -->
<div class="progress-extended">&nbsp;</div>
<!-- Processing info container -->
<div class="progress-end" style="display:none;">{{ trans.processing }}</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
{% endverbatim %}
{% set title %}{% trans "Overwrite existing data?" %}{% endset %}
{% set helpText %}{% trans "Erase all content in this DataSet and overwrite it with the new content in this import." %}{% endset %}
{{ forms.checkbox("overwrite", title, "", helpText) }}
{% set title %}{% trans "Ignore first row?" %}{% endset %}
{% set helpText %}{% trans "Ignore the first row? Useful if the CSV has headings." %}{% endset %}
{{ forms.checkbox("ignorefirstrow", title, "", helpText) }}
{% set message %}{% trans "In the fields below please enter the column number in the CSV file that corresponds to the Column Heading listed. This should be done before Adding the file." %}{% endset %}
{{ forms.message(message) }}
{% verbatim %}
{{#each columns}}
<div class="form-group row">
<label class="col-sm-2 control-label" for="csvImport_{{dataSetColumnId}}">{{heading}}</label>
<div class="col-sm-10">
<input class="form-control" name="csvImport_{{dataSetColumnId}}" type="number" id="csvImport_{{dataSetColumnId}}" value="{{ index }}" />
</div>
</div>
{{/each}}
</div>
</div>
<!-- The table listing the files available for upload/download -->
<table role="presentation" class="table table-striped"><tbody class="files"></tbody></table>
</form>
</script>
<!-- The template to display files available for upload -->
<script id="template-dataset-upload" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
<tr class="template-upload">
<td>
<span class="fileupload-preview"></span>
</td>
<td class="title">
{% if (file.error) { %}
<div><span class="label label-danger">{%=file.error%}</span></div>
{% } %}
{% if (!file.error) { %}
<label for="name[]"><input name="name[]" type="text" id="name" value="" /></label>
{% } %}
</td>
<td>
<p class="size">{%=o.formatFileSize(file.size)%}</p>
{% if (!o.files.error) { %}
<div class="progress">
<div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" style="width:0%;">
<div class="sr-only"></div>
</div>
</div>
</div>
{% } %}
</td>
<td class="btn-group">
{% if (!o.files.error && !i && !o.options.autoUpload) { %}
<button class="btn btn-primary start">
<i class="fa fa-upload"></i>
</button>
{% } %}
{% if (!i) { %}
<button class="btn btn-warning cancel">
<i class="fa fa-ban"></i>
</button>
{% } %}
</td>
</tr>
{% } %}
</script>
<!-- The template to display files available for download -->
<script id="template-dataset-download" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
<tr class="template-download">
<td>
<p class="name" id="{%=file.storedas%}" status="{% if (file.error) { %}error{% } %}">
{%=file.name%}
</p>
{% if (file.error) { %}
<div><span class="label label-danger">{%=file.error%}</span></div>
{% } %}
</td>
<td>
<span class="size">{%=o.formatFileSize(file.size)%}</span>
</td>
</tr>
{% } %}
</script>
{% endverbatim %}
{% endblock %}

View File

@@ -0,0 +1,191 @@
{#
/**
* Copyright (C) 2018 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 RSS" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#dataSetRssAddForm").submit()
{% endblock %}
{% block callBack %}configureQueryBuilder{% endblock %}
{% block formFieldActions %}
[
{
"field": "useOrderingClause",
"trigger": "init",
"value": false,
"operation": "is:checked",
"actions": {
".order-clause-field": { "display": "none" },
"#orderClause": { "display": "" }
}
},{
"field": "useOrderingClause",
"trigger": "change",
"value": false,
"operation": "is:checked",
"actions": {
".order-clause-field": { "display": "none" },
"#orderClause": { "display": "" }
}
},{
"field": "useOrderingClause",
"trigger": "init",
"value": true,
"operation": "is:checked",
"actions": {
".order-clause-field": { "display": "" },
"#orderClause": { "display": "none" }
}
},{
"field": "useOrderingClause",
"trigger": "change",
"value": true,
"operation": "is:checked",
"actions": {
".order-clause-field": { "display": "" },
"#orderClause": { "display": "none" }
}
},{
"field": "useFilteringClause",
"trigger": "init",
"value": false,
"operation": "is:checked",
"actions": {
".filter-clause-field": { "display": "none" },
"#filterClause": { "display": "" }
}
},{
"field": "useFilteringClause",
"trigger": "change",
"value": false,
"operation": "is:checked",
"actions": {
".filter-clause-field": { "display": "none" },
"#filterClause": { "display": "" }
}
},{
"field": "useFilteringClause",
"trigger": "init",
"value": true,
"operation": "is:checked",
"actions": {
".filter-clause-field": { "display": "" },
"#filterClause": { "display": "none" }
}
},{
"field": "useFilteringClause",
"trigger": "change",
"value": true,
"operation": "is:checked",
"actions": {
".filter-clause-field": { "display": "" },
"#filterClause": { "display": "none" }
}
}
]
{% endblock %}
{% block extra %}{{ extra|json_encode|raw }}{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#general" role="tab" data-toggle="tab"><span>{% trans "General" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#orderCriteria" role="tab" data-toggle="tab"><span>{% trans "Order" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#filterCriteria" role="tab" data-toggle="tab"><span>{% trans "Filter" %}</span></a></li>
</ul>
<form id="dataSetRssAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("dataSet.rss.add", {id: dataSet.dataSetId}) }}">
<div class="tab-content">
<div class="tab-pane active" id="general">
{% set title %}{% trans "Title" %}{% endset %}
{% set helpText %}{% trans "The title for this Rss" %}{% endset %}
{{ forms.input("title", title, "", helpText, "", "required") }}
{% set title %}{% trans "Author" %}{% endset %}
{% set helpText %}{% trans "The author for this Rss" %}{% endset %}
{{ forms.input("author", title, "", helpText) }}
{% set title %}{% trans "Title Column" %}{% endset %}
{% set helpText %}{% trans "Please select a column to be the item title" %}{% endset %}
{{ forms.dropdown("titleColumnId", "single", title, titleColumnId, [{"dataSetColumnId": null, "heading": ""}]|merge(extra.columns), "dataSetColumnId", "heading", helpText) }}
{% set title %}{% trans "Summary Column" %}{% endset %}
{% set helpText %}{% trans "Please select a column to be the item summary" %}{% endset %}
{{ forms.dropdown("summaryColumnId", "single", title, summaryColumnId, [{"dataSetColumnId": null, "heading": ""}]|merge(extra.columns), "dataSetColumnId", "heading", helpText) }}
{% set title %}{% trans "Content Column" %}{% endset %}
{% set helpText %}{% trans "Please select a column to be the item content" %}{% endset %}
{{ forms.dropdown("contentColumnId", "single", title, contentColumnId, [{"dataSetColumnId": null, "heading": ""}]|merge(extra.columns), "dataSetColumnId", "heading", helpText) }}
{% set title %}{% trans "Published Date Column" %}{% endset %}
{% set helpText %}{% trans "Please select a column to be the item content. We will try to convert this to a date/time and if we can't we will use the current date/time." %}{% endset %}
{{ forms.dropdown("publishedDateColumn", "single", title, publishedDateColumn, [{"dataSetColumnId": null, "heading": ""}]|merge(extra.dateColumns), "dataSetColumnId", "heading", helpText) }}
</div>
<div class="tab-pane" id="orderCriteria">
{% set message %}{% trans "The DataSet results can be ordered by any column and set below. New fields can be added by selecting the plus icon at the end of the current row. Should a more complicated order be required the advanced checkbox can be selected to provide custom SQL syntax." %}{% endset %}
{{ forms.message(message) }}
<div id="orderClause">
</div>
{% set title %}{% trans "Use advanced order clause?" %}{% endset %}
{% set helpText %}{% trans "Provide a custom clause instead of using the clause builder above." %}{% endset %}
{{ forms.checkbox("useOrderingClause", title, module.getOption("useOrderingClause", 0), helpText) }}
{% set title %}{% trans "Order" %}{% endset %}
{% set helpText %}{% trans "Please enter a SQL clause for how this dataset should be ordered" %}{% endset %}
{{ forms.input("ordering", title, module.getOption("ordering"), helpText, "order-clause-field") }}
</div>
<div class="tab-pane" id="filterCriteria">
{% set message %}{% trans "The DataSet results can be filtered by any column and set below. New fields can be added by selecting the plus icon at the end of the current row. Should a more complicated filter be required the advanced checkbox can be selected to provide custom SQL syntax." %}{% endset %}
{{ forms.message(message) }}
<div id="filterClause">
</div>
{% set title %}{% trans "Use advanced filter clause?" %}{% endset %}
{% set helpText %}{% trans "Provide a custom clause instead of using the clause builder above." %}{% endset %}
{{ forms.checkbox("useFilteringClause", title, module.getOption("useFilteringClause", 0), helpText) }}
{% set title %}{% trans "Filter" %}{% endset %}
{% set helpText %}{% trans "Please enter a SQL clause to filter this DataSet." %}{% endset %}
{{ forms.input("filter", title, module.getOption("filter"), helpText, "filter-clause-field") }}
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Delete this RSS?" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Yes" %}, $("#dataSetColumnRssForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="dataSetColumnRssForm" class="XiboForm form-horizontal" method="delete" action="{{ url_for("dataSet.rss.delete", {id: dataSet.dataSetId, rssId: feed.id}) }}">
{% set message %}{% trans "Are you sure you want to delete?" %}{% endset %}
{{ forms.message(message) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,195 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Edit RSS" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#dataSetRssEditForm").submit()
{% endblock %}
{% block callBack %}configureQueryBuilder{% endblock %}
{% block formFieldActions %}
[
{
"field": "useOrderingClause",
"trigger": "init",
"value": false,
"operation": "is:checked",
"actions": {
".order-clause-field": { "display": "none" },
"#orderClause": { "display": "" }
}
},{
"field": "useOrderingClause",
"trigger": "change",
"value": false,
"operation": "is:checked",
"actions": {
".order-clause-field": { "display": "none" },
"#orderClause": { "display": "" }
}
},{
"field": "useOrderingClause",
"trigger": "init",
"value": true,
"operation": "is:checked",
"actions": {
".order-clause-field": { "display": "" },
"#orderClause": { "display": "none" }
}
},{
"field": "useOrderingClause",
"trigger": "change",
"value": true,
"operation": "is:checked",
"actions": {
".order-clause-field": { "display": "" },
"#orderClause": { "display": "none" }
}
},{
"field": "useFilteringClause",
"trigger": "init",
"value": false,
"operation": "is:checked",
"actions": {
".filter-clause-field": { "display": "none" },
"#filterClause": { "display": "" }
}
},{
"field": "useFilteringClause",
"trigger": "change",
"value": false,
"operation": "is:checked",
"actions": {
".filter-clause-field": { "display": "none" },
"#filterClause": { "display": "" }
}
},{
"field": "useFilteringClause",
"trigger": "init",
"value": true,
"operation": "is:checked",
"actions": {
".filter-clause-field": { "display": "" },
"#filterClause": { "display": "none" }
}
},{
"field": "useFilteringClause",
"trigger": "change",
"value": true,
"operation": "is:checked",
"actions": {
".filter-clause-field": { "display": "" },
"#filterClause": { "display": "none" }
}
}
]
{% endblock %}
{% block extra %}{{ extra|json_encode|raw }}{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#general" role="tab" data-toggle="tab"><span>{% trans "General" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#orderCriteria" role="tab" data-toggle="tab"><span>{% trans "Order" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#filterCriteria" role="tab" data-toggle="tab"><span>{% trans "Filter" %}</span></a></li>
</ul>
<form id="dataSetRssEditForm" class="XiboForm form-horizontal" method="put" action="{{ url_for("dataSet.rss.edit", {id: dataSet.dataSetId, rssId: feed.id}) }}">
<div class="tab-content">
<div class="tab-pane active" id="general">
{% set title %}{% trans "Title" %}{% endset %}
{% set helpText %}{% trans "The title for this Rss" %}{% endset %}
{{ forms.input("title", title, feed.title, helpText, "", "required") }}
{% set title %}{% trans "Author" %}{% endset %}
{% set helpText %}{% trans "The author for this Rss" %}{% endset %}
{{ forms.input("author", title, feed.author, helpText) }}
{% set title %}{% trans "Title Column" %}{% endset %}
{% set helpText %}{% trans "Please select a column to be the item title" %}{% endset %}
{{ forms.dropdown("titleColumnId", "single", title, feed.titleColumnId, [{"dataSetColumnId": null, "heading": ""}]|merge(extra.columns), "dataSetColumnId", "heading", helpText) }}
{% set title %}{% trans "Summary Column" %}{% endset %}
{% set helpText %}{% trans "Please select a column to be the item summary" %}{% endset %}
{{ forms.dropdown("summaryColumnId", "single", title, feed.summaryColumnId, [{"dataSetColumnId": null, "heading": ""}]|merge(extra.columns), "dataSetColumnId", "heading", helpText) }}
{% set title %}{% trans "Content Column" %}{% endset %}
{% set helpText %}{% trans "Please select a column to be the item content" %}{% endset %}
{{ forms.dropdown("contentColumnId", "single", title, feed.contentColumnId, [{"dataSetColumnId": null, "heading": ""}]|merge(extra.columns), "dataSetColumnId", "heading", helpText) }}
{% set title %}{% trans "Published Date Column" %}{% endset %}
{% set helpText %}{% trans "Please select a column to be the item content. We will try to convert this to a date/time and if we can't we will use the current date/time." %}{% endset %}
{{ forms.dropdown("publishedDateColumnId", "single", title, feed.publishedDateColumnId, [{"dataSetColumnId": null, "heading": ""}]|merge(extra.dateColumns), "dataSetColumnId", "heading", helpText) }}
{% set title %}{% trans "Generate new address?" %}{% endset %}
{% set helpText %}{% trans "Tick this box if you want to generate a new address for this RSS feed. You might want to do this if you think someone is accessing it unauthorised." %}{% endset %}
{{ forms.checkbox("regeneratePsk", title, 0, helpText) }}
</div>
<div class="tab-pane" id="orderCriteria">
{% set message %}{% trans "The DataSet results can be ordered by any column and set below. New fields can be added by selecting the plus icon at the end of the current row. Should a more complicated order be required the advanced checkbox can be selected to provide custom SQL syntax." %}{% endset %}
{{ forms.message(message) }}
<div id="orderClause">
</div>
{% set title %}{% trans "Use advanced order clause?" %}{% endset %}
{% set helpText %}{% trans "Provide a custom clause instead of using the clause builder above." %}{% endset %}
{{ forms.checkbox("useOrderingClause", title, extra.useOrderingClause, helpText) }}
{% set title %}{% trans "Order" %}{% endset %}
{% set helpText %}{% trans "Please enter a SQL clause for how this dataset should be ordered" %}{% endset %}
{{ forms.input("ordering", title, extra.sort, helpText, "order-clause-field") }}
</div>
<div class="tab-pane" id="filterCriteria">
{% set message %}{% trans "The DataSet results can be filtered by any column and set below. New fields can be added by selecting the plus icon at the end of the current row. Should a more complicated filter be required the advanced checkbox can be selected to provide custom SQL syntax." %}{% endset %}
{{ forms.message(message) }}
<div id="filterClause">
</div>
{% set title %}{% trans "Use advanced filter clause?" %}{% endset %}
{% set helpText %}{% trans "Provide a custom clause instead of using the clause builder above." %}{% endset %}
{{ forms.checkbox("useFilteringClause", title, extra.useFilteringClause, helpText) }}
{% set title %}{% trans "Filter" %}{% endset %}
{% set helpText %}{% trans "Please enter a SQL clause to filter this DataSet." %}{% endset %}
{{ forms.input("filter", title, extra.filter, helpText, "filter-clause-field") }}
</div>
</div>
</form>
</div>
</div>
{% endblock %}

360
views/dataset-rss-page.twig Normal file
View File

@@ -0,0 +1,360 @@
{#
/**
* 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 %}
{% set dataSetName = dataSet.dataSet %}
{% block title %}{% trans %}RSS Feeds for {{ dataSetName }}{% endtrans %} | {% endblock %}
{% block actionMenu %}
<div class="widget-action-menu pull-right">
<button class="btn btn-success XiboFormButton" title="{% trans "Add RSS" %}" href="{{ url_for("dataSet.rss.add.form", {id: dataSet.dataSetId}) }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add RSS" %}</button>
<button class="btn btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
</div>
{% endblock %}
{% block pageContent %}
{% set widgetTitle %}{% trans %}RSS Feeds for {{ dataSetName }}{% endtrans %}{% endset %}
<div class="widget">
<div class="widget-title">{{ widgetTitle }}</div>
<div class="widget-body">
<div class="XiboGrid" id="{{ random() }}">
<div class="XiboFilter">
<div class="FilterDiv card-body" id="Filter">
<form class="form-inline">
</form>
</div>
</div>
<div class="XiboData card pt-3">
<table id="datasets" class="table table-striped" data-state-preference-name="dataSetRssGrid">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Title" %}</th>
<th>{% trans "Author" %}</th>
<th>{% trans "URL" %}</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javaScript %}
<script type="text/javascript" nonce="{{ cspNonce }}">
var table = $("#datasets").DataTable({
"language": dataTablesLanguage,
dom: dataTablesTemplate,
serverSide: true,
stateSave: true,
stateDuration: 0,
responsive: true,
stateLoadCallback: dataTableStateLoadCallback,
stateSaveCallback: dataTableStateSaveCallback,
filter: false,
searchDelay: 3000,
"order": [[ 0, "asc"]],
ajax: {
"url": "{{ url_for("dataSet.rss.search", {id: dataSet.dataSetId}) }}",
"data": function(d) {
$.extend(d, $("#datasets").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
}
},
"columns": [
{ "data": "id" },
{ "data": "title" },
{ "data": "author" },
{
"orderable": false,
"data": function (data, type, row, meta) {
if (type !== "display")
return "";
var url = window.location.origin + "{{ url_for("dataSet.rss.feed", {psk: ':psk'}) }}".replace(':psk', data.psk);
return "<a href=\"" + url + "\" target=\"_blank\">" + url + "</a>";
}
},
{
"orderable": false,
responsivePriority: 1,
"data": dataTableButtonsColumn
}
]
});
table.on('draw', dataTableDraw);
table.on('processing.dt', dataTableProcessing);
dataTableAddButtons(table, $('#datasets_wrapper').find('.dataTables_buttons'));
$("#refreshGrid").click(function () {
table.ajax.reload();
});
/**
* Configure the Query Building on DataSetRss Add/Edit forms.
* @param dialog
*/
function configureQueryBuilder(dialog) {
// Order Clause
var orderClauseFields = $("#orderClause");
if (orderClauseFields.length === 0)
return;
var orderClauseTemplate = Handlebars.compile($("#dataSetViewOrderClauseTemplate").html());
var ascTitle = "{% trans "Ascending" %}";
var descTitle = "{% trans "Descending" %}";
if (dialog.data().extra.orderClauses.length === 0) {
// Add a template row
var context = {columns: dialog.data().extra.columns, title: "1", orderClause: "", orderClauseAsc: "", orderClauseDesc: "", buttonGlyph: "fa-plus", ascTitle: ascTitle, descTitle: descTitle};
orderClauseFields.append(orderClauseTemplate(context));
} else {
// For each of the existing codes, create form components
var i = 0;
$.each(dialog.data().extra.orderClauses, function (index, field) {
i++;
var direction = (field.orderClauseDirection === "ASC");
var context = {columns: dialog.data().extra.columns, title: i, orderClause: field.orderClause, orderClauseAsc: direction, orderClauseDesc: !direction, buttonGlyph: ((i == 1) ? "fa-plus" : "fa-minus"), ascTitle: ascTitle, descTitle: descTitle};
orderClauseFields.append(orderClauseTemplate(context));
});
}
// Nabble the resulting buttons
orderClauseFields.on("click", "button", function (e) {
e.preventDefault();
// find the gylph
if ($(this).find("i").hasClass("fa-plus")) {
var context = {columns: dialog.data().extra.columns, title: orderClauseFields.find('.form-group').length + 1, orderClause: "", orderClauseAsc: "", orderClauseDesc: "", buttonGlyph: "fa-minus", ascTitle: ascTitle, descTitle: descTitle};
orderClauseFields.append(orderClauseTemplate(context));
} else {
// Remove this row
$(this).closest(".form-group").remove();
}
});
//
// Filter Clause
//
var filterClauseFields = $("#filterClause");
var filterClauseTemplate = Handlebars.compile($("#dataSetViewFilterClauseTemplate").html());
var filterOptions = [
{
id: "starts-with",
value: "{% trans "starts with" %}"
},{
id: "ends-with",
value: "{% trans "ends with" %}"
},{
id: "contains",
value: "{% trans "contains" %}"
},{
id: "equals",
value: "{% trans "equals" %}"
},{
id: "not-starts-with",
value: "{% trans "does not start with" %}"
},{
id: "not-ends-with",
value: "{% trans "does not end with" %}"
},{
id: "not-contains",
value: "{% trans "does not contain" %}"
},{
id: "not-equals",
value: "{% trans "does not equal" %}"
},{
id: "greater-than",
value: "{% trans "greater than" %}"
},{
id: "less-than",
value: "{% trans "less than" %}"
},{
id: "is-empty",
value: "{% trans "is empty" %}"
},{
id: "is-not-empty",
value: "{% trans "is not empty" %}"
}
];
var filterOperatorOptions = [
{
id: "OR",
value: "{% trans "Or" %}"
},{
id: "AND",
value: "{% trans "And" %}"
}
];
if (dialog.data().extra.filterClauses.length == 0) {
// Add a template row
context = {
columns: dialog.data().extra.columns,
filterOptions: filterOptions,
filterOperatorOptions: filterOperatorOptions,
title: "1",
filterClause: "",
filterClauseOperator: "AND",
filterClauseCriteria: "",
filterClauseValue: "",
buttonGlyph: "fa-plus"
};
filterClauseFields.append(filterClauseTemplate(context));
} else {
// For each of the existing codes, create form components
var j = 0;
$.each(dialog.data().extra.filterClauses, function (index, field) {
j++;
var context = {
columns: dialog.data().extra.columns,
filterOptions: filterOptions,
filterOperatorOptions: filterOperatorOptions,
title: j,
filterClause: field.filterClause,
filterClauseOperator: field.filterClauseOperator,
filterClauseCriteria: field.filterClauseCriteria,
filterClauseValue: field.filterClauseValue,
buttonGlyph: ((j == 1) ? "fa-plus" : "fa-minus")
};
filterClauseFields.append(filterClauseTemplate(context));
});
}
// Nabble the resulting buttons
filterClauseFields.on("click", "button", function (e) {
e.preventDefault();
// find the gylph
if ($(this).find("i").hasClass("fa-plus")) {
var context = {
columns: dialog.data().extra.columns,
filterOptions: filterOptions,
filterOperatorOptions: filterOperatorOptions,
title: filterClauseFields.find('.form-group').length + 1,
filterClause: "",
filterClauseOperator: "AND",
filterClauseCriteria: "",
filterClauseValue: "",
buttonGlyph: "fa-minus"
};
filterClauseFields.append(filterClauseTemplate(context));
} else {
// Remove this row
$(this).closest(".form-group").remove();
}
});
}
// Equals helper for the templates below
Handlebars.registerHelper('eq', function(v1, v2, opts) {
if (v1 === v2) {
return opts.fn(this);
} else {
return opts.inverse(this);
}
});
</script>
{% verbatim %}
<script type="text/x-handlebars-template" id="dataSetViewOrderClauseTemplate">
<div class="form-group row">
<label class="col-sm-1 control-label" for="orderClause[]">{{ title }}</label>
<div class="col-sm-7">
<select class="form-control" name="orderClause[]">
<option value=""></option>
{{#each columns}}
<option value="{{ heading }}" {{#eq heading ../orderClause}}selected{{/eq}}>{{ heading }}</option>
{{/each}}
</select>
</div>
<div class="col-sm-3">
<label for="orderClauseDirection[]">
<select class="form-control" name="orderClauseDirection[]">
<option value="ASC" {{#if orderClauseAsc}}selected{{/if}}>{{ ascTitle }}</option>
<option value="DESC" {{#if orderClauseDesc}}selected{{/if}}>{{ descTitle }}</option>
</select>
</label>
</div>
<div class="col-sm-1">
<button class="btn btn-white"><i class="fa {{ buttonGlyph }}"></i></button>
</div>
</div>
</script>
<script type="text/x-handlebars-template" id="dataSetViewFilterClauseTemplate">
<div class="form-group row">
<label class="col-sm-1 control-label" for="filterClause[]">{{ title }}</label>
<div class="col-sm-2">
<label for="filterClauseOperator[]" {{#eq title "1"}}class="d-none"{{/eq}}>
<select class="form-control" name="filterClauseOperator[]">
{{#each filterOperatorOptions}}
<option value="{{ id }}" {{#eq id ../filterClauseOperator}}selected{{/eq}}>{{ value }}</option>
{{/each}}
</select>
</label>
</div>
<div class="col-sm-3">
<select class="form-control" name="filterClause[]">
<option value=""></option>
{{#each columns}}
<option value="{{ heading }}" {{#eq heading ../filterClause}}selected{{/eq}}>{{ heading }}</option>
{{/each}}
</select>
</div>
<div class="col-sm-3">
<label for="filterClauseCriteria[]">
<select class="form-control" name="filterClauseCriteria[]">
{{#each filterOptions}}
<option value="{{ id }}" {{#eq id ../filterClauseCriteria}}selected{{/eq}}>{{ value }}</option>
{{/each}}
</select>
</label>
</div>
<div class="col-sm-2">
<label for="filterClauseValue[]">
<input class="form-control" name="filterClauseValue[]" type="text" value="{{ filterClauseValue }}" />
</label>
</div>
<div class="col-sm-1">
<button class="btn btn-white"><i class="fa {{ buttonGlyph }}"></i></button>
</div>
</div>
</script>
{% endverbatim %}
{% endblock %}

View File

@@ -0,0 +1,83 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Add Daypart" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#dayPartAddForm").submit()
{% endblock %}
{% block extra %}{{ extra|json_encode|raw }}{% endblock %}
{% block callBack %}dayPartFormOpen{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#general" role="tab" data-toggle="tab"><span>{% trans "General" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#description" role="tab" data-toggle="tab"><span>{% trans "Description" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#exceptions" role="tab" data-toggle="tab"><span>{% trans "Exceptions" %}</span></a></li>
</ul>
<form id="dayPartAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("daypart.add") }}">
<div class="tab-content">
<div class="tab-pane active" id="general">
{% set title %}{% trans "Name" %}{% endset %}
{% set helpText %}{% trans "The Name for this Daypart" %}{% endset %}
{{ forms.input("name", title, "", helpText, "", "required") }}
{% set title %}{% trans "Retired" %}{% endset %}
{% set helpText %}{% trans "Retire? It will no longer be visible when scheduling" %}{% endset %}
{{ forms.checkbox("isRetired", title, dayPart.isRetired, helpText) }}
{% set title %}{% trans "Start Time" %}{% endset %}
{% set helpText %}{% trans "Enter the start time for this daypart" %}{% endset %}
{{ forms.time("startTime", title, "00:00", helpText) }}
{% set title %}{% trans "End Time" %}{% endset %}
{% set helpText %}{% trans "Enter the end time for this daypart. If the end time is before the start time, then the daypart will cross midnight." %}{% endset %}
{{ forms.time("endTime", title, "00:00", helpText) }}
</div>
<div class="tab-pane" id="description">
{% set title %}{% trans "Description" %}{% endset %}
{% set helpText %}{% trans "A Description of Daypart" %}{% endset %}
{{ forms.textarea("description", title, "", helpText) }}
</div>
<div class="tab-pane" id="exceptions">
{% set title %}{% trans "If there are any exceptions enter them below by selecting the Day from the list and entering a start/end time." %}{% endset %}
{{ forms.message(title) }}
<div id="dayPartExceptions">
</div>
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,50 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Delete Daypart" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Yes" %}, $("#dayPartDeleteForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="dayPartDeleteForm" class="XiboForm form-horizontal" method="delete" action="{{ url_for("daypart.delete", {"id": dayPart.dayPartId}) }}">
{% set message %}{% trans "Are you sure you want to delete this Daypart? This cannot be undone" %}{% endset %}
{{ forms.message(message) }}
{% if countSchedules > 0 %}
{% set message %}{% trans %}There are {{ countSchedules }} scheduled events that will also be deleted.{% endtrans %}{% endset %}
{{ forms.message(message) }}
{% endif %}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,85 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Edit Daypart" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#dayPartEditForm").submit()
{% endblock %}
{% block extra %}{{ extra|json_encode|raw }}{% endblock %}
{% block callBack %}dayPartFormOpen{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#general" role="tab" data-toggle="tab"><span>{% trans "General" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#description" role="tab" data-toggle="tab"><span>{% trans "Description" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#exceptions" role="tab" data-toggle="tab"><span>{% trans "Exceptions" %}</span></a></li>
</ul>
<form id="dayPartEditForm" class="XiboForm form-horizontal" method="put" action="{{ url_for("daypart.edit", {id: dayPart.dayPartId}) }}">
<div class="tab-content">
<div class="tab-pane active" id="general">
{% set title %}{% trans "Name" %}{% endset %}
{% set helpText %}{% trans "The Name for this Daypart" %}{% endset %}
{{ forms.input("name", title, dayPart.name, helpText, "", "required") }}
{% set title %}{% trans "Retired" %}{% endset %}
{% set helpText %}{% trans "Retire? It will no longer be visible when scheduling" %}{% endset %}
{{ forms.checkbox("isRetired", title, dayPart.isRetired, helpText) }}
{% set title %}{% trans "Start Time" %}{% endset %}
{% set helpText %}{% trans "Enter the start time for this daypart" %}{% endset %}
{{ forms.time("startTime", title, dayPart.startTime, helpText) }}
{% set title %}{% trans "End Time" %}{% endset %}
{% set helpText %}{% trans "Enter the end time for this daypart. If the end time is before the start time, then the daypart will cross midnight." %}{% endset %}
{{ forms.time("endTime", title, dayPart.endTime, helpText) }}
{% set title %}{% trans "If this daypart is already in use, the events will be adjusted to use the new times provided. If used on a recurring event and that event has already recurred. The event will be split in two and the future event time adjusted." %}{% endset %}
{{ forms.message(title) }}
</div>
<div class="tab-pane" id="description">
{% set title %}{% trans "Description" %}{% endset %}
{% set helpText %}{% trans "A Description of Daypart" %}{% endset %}
{{ forms.textarea("description", title, dayPart.description, helpText) }}
</div>
<div class="tab-pane" id="exceptions">
{% set title %}{% trans "If there are any exceptions enter them below by selecting the Day from the list and entering a start/end time." %}{% endset %}
{{ forms.message(title) }}
<div id="dayPartExceptions">
</div>
</div>
</div>
</form>
</div>
</div>
{% endblock %}

248
views/daypart-page.twig Normal file
View File

@@ -0,0 +1,248 @@
{#
/**
* 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 %}{{ "Dayparting"|trans }} | {% endblock %}
{% block actionMenu %}
<div class="widget-action-menu pull-right">
{% if currentUser.featureEnabled("daypart.add") %}
<button class="btn btn-success XiboFormButton" href="{{ url_for("daypart.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Daypart" %}</button>
{% endif %}
<button class="btn btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
</div>
{% endblock %}
{% block pageContent %}
<div class="widget">
<div class="widget-title">{% trans "Dayparting" %}</div>
<div class="widget-body">
<div class="XiboGrid" id="{{ random() }}">
<div class="XiboFilter card mb-3 bg-light">
<div class="FilterDiv card-body" id="Filter">
<form class="form-inline">
{% set title %}{% trans "Name" %}{% endset %}
{{ inline.inputNameGrid('name', title) }}
{% set title %}{% trans "Retired" %}{% endset %}
{% set option1 = "Yes"|trans %}
{% set option2 = "No"|trans %}
{% set values = [{id: 1, value: option1}, {id: 0, value: option2}] %}
{{ inline.dropdown("isRetired", "single", title, 0, values, "id", "value") }}
</form>
</div>
</div>
<div class="XiboData card pt-3">
<table id="dayparts" class="table table-striped" data-state-preference-name="daypartGrid">
<thead>
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Description" %}</th>
<th>{% trans "Start Time" %}</th>
<th>{% trans "End Time" %}</th>
<th class="rowMenu"></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javaScript %}
<script type="text/javascript" nonce="{{ cspNonce }}">
var table = $("#dayparts").DataTable({
"language": dataTablesLanguage,
dom: dataTablesTemplate,
serverSide: true,
stateSave: true,
stateDuration: 0,
responsive: true,
stateLoadCallback: dataTableStateLoadCallback,
stateSaveCallback: dataTableStateSaveCallback,
filter: false,
searchDelay: 3000,
"order": [[ 1, "asc"]],
ajax: {
"url": "{{ url_for("daypart.search") }}",
"data": function(d) {
$.extend(d, $("#dayparts").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
}
},
"columns": [
{ "data": "name", "render": dataTableSpacingPreformatted , responsivePriority: 2},
{ "data": "description" },
{ "data": "startTime" },
{ "data": "endTime" },
{
"orderable": false,
responsivePriority: 1,
"data": dataTableButtonsColumn
}
]
});
table.on('draw', dataTableDraw);
table.on('processing.dt', dataTableProcessing);
dataTableAddButtons(table, $('#dayparts_wrapper').find('.dataTables_buttons'));
$("#refreshGrid").click(function () {
table.ajax.reload();
});
function dayPartFormOpen(dialog) {
// Render a set of exceptions
$exceptions = $(dialog).find("#dayPartExceptions");
// Days of the week translations
var daysOfTheWeek = [
{ day: "Mon", title: "{% trans "Monday" %}" },
{ day: "Tue", title: "{% trans "Tuesday" %}" },
{ day: "Wed", title: "{% trans "Wednesday" %}" },
{ day: "Thu", title: "{% trans "Thursday" %}" },
{ day: "Fri", title: "{% trans "Friday" %}" },
{ day: "Sat", title: "{% trans "Saturday" %}" },
{ day: "Sun", title: "{% trans "Sunday" %}" }
];
// Compile the handlebars template
var exceptionsTemplate = Handlebars.compile($("#dayPartExceptionsTemplate").html());
if (dialog.data().extra.exceptions.length == 0) {
// Contexts for template
var context = {
daysOfWeek: daysOfTheWeek,
buttonGlyph: "fa-plus",
exceptionDay: "",
exceptionStart: "",
exceptionEnd: "",
fieldId: 0
};
// Append
$exceptions.append(exceptionsTemplate(context));
XiboInitialise("#" + $exceptions.prop("id"));
} else {
// For each of the existing exceptions, create form components
var i = 0;
$.each(dialog.data().extra.exceptions, function (index, field) {
i++;
// call the template
var context = {
daysOfWeek: daysOfTheWeek,
buttonGlyph: ((i == 1) ? "fa-plus" : "fa-minus"),
exceptionDay: field.day,
exceptionStart: field.start,
exceptionEnd: field.end,
fieldId: i
};
$exceptions.append(exceptionsTemplate(context));
XiboInitialise("#" + $exceptions.prop("id"));
});
}
// Nabble the resulting buttons
$exceptions.on("click", "button", function (e) {
e.preventDefault();
// find the gylph
if ($(this).find("i").hasClass("fa-plus")) {
var context = {
daysOfWeek: daysOfTheWeek,
buttonGlyph: "fa-minus",
exceptionDay: "",
exceptionStart: "",
exceptionEnd: "",
fieldId: $exceptions.find('.form-group').length + 1
};
$exceptions.append(exceptionsTemplate(context));
XiboInitialise("#" + $exceptions.prop("id"));
} else {
// Remove this row
$(this).closest(".form-group").remove();
}
});
// check if we already have this day in exceptions array, if so remove the row with a message.
$exceptions.on("change", "select", function() {
var selectedDays = [];
$('select').not('#' + $(this).attr('id')).each(function(i) {
selectedDays.push($(this).val());
});
if (selectedDays.includes(this.value)) {
toastr.error(translations.dayPartExceptionErrorMessage);
// Remove this row
$(this).closest(".form-group").remove();
}
})
}
// Equals helper for the templates below
Handlebars.registerHelper('eq', function(v1, v2, opts) {
if (v1 === v2) {
return opts.fn(this);
} else {
return opts.inverse(this);
}
});
</script>
{% verbatim %}
<script type="text/x-handlebars-template" id="dayPartExceptionsTemplate">
<div class="form-group row">
<div class="col-3">
<select class="form-control" name="exceptionDays[]" id="exceptionDays_{{fieldId}}">
<option value=""></option>
{{#each daysOfWeek}}
<option value="{{ day }}" {{#eq day ../exceptionDay}}selected{{/eq}}>{{ title }}</option>
{{/each}}
</select>
</div>
<div class="col-3">
{% endverbatim %}
{{ inline.time("exceptionStartTimes[]", "", "{{ exceptionStart }}" ) }}
{% verbatim %}
</div>
<div class="col-3">
{% endverbatim %}
{{ inline.time("exceptionEndTimes[]", "", "{{ exceptionEnd }}" ) }}
{% verbatim %}
</div>
<div class="col-1">
<button class="btn btn-white"><i class="fa {{ buttonGlyph }}"></i></button>
</div>
</div>
</script>
{% endverbatim %}
{% endblock %}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,88 @@
{#
/*
* Copyright (C) 2023 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 Module Template" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#form-module-template").submit();
{% endblock %}
{% block callBack %}moduleTemplateAddFormOpen{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="form-module-template"
class="XiboForm form-horizontal"
method="post"
action="{{ url_for("developer.templates.add") }}">
{% set title %}{% trans "ID" %}{% endset %}
{% set helpText %}{% trans "A unique ID for the module template" %}{% endset %}
{{ forms.input("templateId", title, "custom_", helpText) }}
{% set title %}{% trans "Title" %}{% endset %}
{% set helpText %}{% trans "A title for the module template" %}{% endset %}
{{ forms.input("title", title, "Custom Template", helpText) }}
{% set attributes = [
{ name: "data-search-url", value: url_for("developer.templates.datatypes.search") },
{ name: "data-search-term", value: "name" },
{ name: "data-id-property", value: "id" },
{ name: "data-text-property", value: "name" },
{ name: "data-hide-search", value: 1},
] %}
{% set title %}{% trans "Data Type" %}{% endset %}
{% set helpText %}{% trans "Which data type does this template need?" %}{% endset %}
{{ forms.dropdown("dataType", "single", title, null, null, "id", "id", helpText, "pagedSelect", "", "", "", attributes) }}
{% set attributes = [
{ name: "data-search-term", value: "title" },
{ name: "data-id-property", value: "templateId" },
{ name: "data-text-property", value: "title" },
{ name: "data-hide-search", value: 1},
{ name: "data-filter-options", value: '{"type":"static"}' },
] %}
{% set title %}{% trans "Template" %}{% endset %}
{% set helpText %}{% trans "Optionally select existing template to use as a base for this Template" %}{% endset %}
{{ forms.dropdown("copyTemplateId", "single", title, null, null, "templateId", "title", helpText, "d-none", "", "", "", attributes) }}
{% set title %}{% trans "Show In" %}{% endset %}
{% set helpText %}{% trans "Which Editor should this template be available in?" %}{% endset %}
{% set options = [
{ id: "none", name: "None"|trans },
{ id: "layout", name: "Layout Editor"|trans },
{ id: "playlist", name: "Playlist Editor"|trans },
{ id: "both", name: "Both"|trans },
] %}
{{ forms.dropdown("showIn", "single", title, "layout", options, "id", "name", helpText) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,48 @@
{#
/*
* 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 %}
{% set name = template.templateId %}
{% trans %}Copy {{ name }}{% endtrans %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Copy" %}, $("#form-module-template-copy").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="form-module-template-copy" class="XiboForm form-horizontal" method="post" action="{{ url_for("developer.templates.copy", {id: template.id}) }}")>
{% set title %}{% trans "ID" %}{% endset %}
{% set helpText %}{% trans "A unique ID for the module template" %}{% endset %}
{% set copyName %}{{ template.templateId }}_copy{% endset %}
{{ forms.input("templateId", title, copyName , helpText) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{#
/**
* 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 "Delete Module Template" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Yes" %}, $("#form-module-template-delete").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="form-module-template-delete" class="XiboForm form-horizontal" method="delete" action="{{ url_for("developer.templates.delete", {"id": template.id}) }}">
{% set message %}{% trans "Are you sure you want to delete this Template? This cannot be undone" %}{% endset %}
{{ forms.message(message) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,193 @@
{#
/*
* Copyright (C) 2023 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 %}{{ "Module Templates"|trans }} | {% endblock %}
{% block actionMenu %}
<div class="widget-action-menu pull-right">
<button class="btn btn-success XiboFormButton" title="{% trans "Add a new template" %}" href="{{ url_for("developer.templates.form.add") }}">
<i class="fa fa-plus" aria-hidden="true"></i> {% trans "Add Module Template" %}
</button>
<button class="btn btn-success" href="#" id="module-template-xml-import" title="{% trans "Add a new template by importing XML file" %}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Import XML" %}</button>
<button class="btn btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
</div>
{% endblock %}
{% block pageContent %}
<div class="widget">
<div class="widget-title">{% trans "Module Templates" %}</div>
<div class="widget-body">
<div class="XiboGrid" id="{{ random() }}" data-grid-name="moduleTemplatesView">
<div class="XiboFilter card mb-3 bg-light">
<div class="FilterDiv card-body" id="Filter">
<form class="form-inline">
{% set title %}{% trans "ID" %}{% endset %}
{{ inline.number('id', title) }}
{% set title %}{% trans "Title" %}{% endset %}
{{ inline.input('templateId', title) }}
{% set attributes = [
{ name: "data-placeholder--id", value: null },
{ name: "data-placeholder--value", value: "" },
{ name: "data-width", value: "200px" },
{ name: "data-search-url", value: url_for("developer.templates.datatypes.search") },
{ name: "data-search-term", value: "name" },
{ name: "data-id-property", value: "id" },
{ name: "data-text-property", value: "name" },
{ name: "data-initial-key", value: "dataType" },
{ name: "data-allow-clear", value: "true"},
{ name: "data-hide-search", value: 1}
] %}
{% set title %}{% trans "Data Type" %}{% endset %}
{% set helpText %}{% trans "Which data type does this template need?" %}{% endset %}
{{ inline.dropdown("dataType", "single", title, null, null, "id", "id", helpText, "pagedSelect", "", "", "", attributes) }}
</form>
</div>
</div>
<div class="XiboData card pt-3">
<table id="templates" class="table table-striped" data-state-preference-name="moduleTemplateGrid">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Template ID" %}</th>
<th>{% trans "Data Type" %}</th>
<th>{% trans "Title" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Sharing" %}</th>
<th class="rowMenu"></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javaScript %}
{# Initialise JS variables and translations #}
<script type="text/javascript" nonce="{{ cspNonce }}" defer>
{# JS variables #}
var developerTemplatesSearchURL = "{{ url_for('developer.templates.search') }}";
var developerTemplatesImportURL = "{{ url_for('developer.templates.import') }}";
var moduleTemplateSearchURL = "{{ url_for('module.template.search', {dataType : ':dataType'}) }}";
{# Custom translations #}
var developerTemplatePageTrans = {
importXML: "{% trans "Import XML" %}",
done: "{% trans "Done" %}",
templateOptions: {
addFiles: "{% trans "Add files" %}",
startUpload: "{% trans "Start upload" %}",
cancelUpload: "{% trans "Cancel upload" %}",
},
unknownError: "{% trans "Unknown error" %}",
};
</script>
{# Add page source code bundle ( JS ) #}
<script src="{{ theme.rootUri() }}dist/pages/developer-template-page.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
{% endblock %}
{% block javaScriptTemplates %}
{{ parent() }}
{% verbatim %}
<script type="text/x-handlebars-template" id="template-module-xml-upload">
<form class="form-horizontal" method="post" enctype="multipart/form-data">
<div class="row fileupload-buttonbar">
<div class="col-md-7">
<!-- The fileinput-button span is used to style the file input field as button -->
<span class="btn btn-success fileinput-button">
<i class="fa fa-plus"></i>
<span>{{ trans.addFiles }}</span>
<input type="file" name="files">
</span>
<button type="submit" class="btn btn-primary start">
<i class="fa fa-upload"></i>
<span>{{ trans.startUpload }}</span>
</button>
<button type="reset" class="btn btn-warning cancel">
<i class="fa fa-ban"></i>
<span>{{ trans.cancelUpload }}</span>
</button>
<!-- The loading indicator is shown during file processing -->
<span class="fileupload-loading"></span>
</div>
<!-- The global progress information -->
<div class="col-md-4 fileupload-progress fade">
<!-- The global progress bar -->
<div class="progress">
<div class="progress-bar progress-bar-success progress-bar-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" style="width:0%;">
<div class="sr-only"></div>
</div>
</div>
<!-- The extended global progress information -->
<div class="progress-extended">&nbsp;</div>
<!-- Processing info container -->
<div class="progress-end" style="display:none;">{{ trans.processing }}</div>
</div>
</div>
<!-- The table listing the files available for upload/download -->
<table role="presentation" class="table table-striped"><tbody class="files"></tbody></table>
</form>
</script>
<script id="template-module-xml-upload-files" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
<tr class="template-upload">
<td>
<p class="size">{%=o.formatFileSize(file.size)%}</p>
<div class="progress">
<div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" style="width:0%;">
<div class="sr-only"></div>
</div>
</div>
</div>
</td>
<td>
<div class="btn-group">
{% if (!i && !o.options.autoUpload) { %}
<button class="btn btn-primary start" disabled>
<i class="fa fa-upload"></i>
</button>
{% } %}
{% if (!i) { %}
<button class="btn btn-warning cancel">
<i class="fa fa-ban"></i>
</button>
{% } %}
</div>
</td>
</tr>
{% } %}
</script>
{% endverbatim %}
{% endblock %}

View File

@@ -0,0 +1,57 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Add Display via Code" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#displayAddViaCodeForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayAddViaCodeForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("display.addViaCode") }}">
<div class="alert alert-info">
{% set message = "After submitting this form with valid code, your CMS Address and Key will be sent and stored in the temporary storage in our Authentication Service."|trans %}
{{ forms.message(message) }}
{% set message2 = "The Player linked to the submitted code, will make regular calls to our Authentication Service to retrive the CMS details and configure itself with them.
Your details are removed from the temporary storage once the Player is configured"|trans %}
{{ forms.message(message2) }}
{% set message3 = "Please note that your CMS needs to make a successful call to our Authentication Service for this feature to work."|trans %}
{{ forms.message(message3) }}
</div>
{% set title %}{% trans "Code" %}{% endset %}
{% set helpText %}{% trans "Please provide the code displayed on the Player screen" %}{% endset %}
{{ forms.input("user_code", title, "", helpText) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,49 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Toggle Authorise" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Yes" %}, $("#displayAuthoriseForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayAuthoriseForm" class="XiboForm form-horizontal" method="put" action="{{ url_for("display.authorise", {id: display.displayId}) }}">
{% if display.licensed == 1 %}
{% set message %}{% trans "Are you sure you want to de-authorise this Display?" %}{% endset %}
{% else %}
{% set message %}{% trans "Are you sure you want to authorise this Display?" %}{% endset %}
{% endif %}
{{ forms.message(message) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,55 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Set Default Layout" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#displayDefaultLayoutForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayDefaultLayoutForm" class="XiboForm form-horizontal" method="put" action="{{ url_for("display.defaultlayout", {id: display.displayId}) }}">
{% set attributes = [
{ 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" },
{ name: "data-filter-options", value: '{"retired":"0"}' }
] %}
{% set title %}{% trans "Default Layout" %}{% endset %}
{% set helpText %}{% trans "The Default Layout to Display where there is no other content." %}{% endset %}
{{ forms.dropdown("layoutId", "single", title, display.defaultLayoutId, layouts, "layoutId", "layout", helpText, "pagedSelect", "", "", "", attributes) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Delete this Display?" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Yes" %}, $("#displayDeleteForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayDeleteForm" class="XiboForm form-horizontal" method="delete" action="{{ url_for("display.delete", {id: display.displayId }) }}">
{% set message %}{% trans "Are you sure you want to delete this display? This cannot be undone" %}{% endset %}
{{ forms.message(message) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,357 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Edit Display" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, displayEditFormSubmit()
{% endblock %}
{% block callBack %}displayEditFormOpen{% endblock %}
{% block extra %}{{ display.overrideConfig|json_encode|raw }}{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#general" role="tab" data-toggle="tab"><span>{% trans "General" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#location" role="tab" data-toggle="tab"><span>{% trans "Details" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#reference" role="tab" data-toggle="tab"><span>{% trans "Reference" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#maintenance" role="tab" data-toggle="tab"><span>{% trans "Maintenance" %}</span></a></li>
{% if not isWolDisabled %}
<li class="nav-item"><a class="nav-link" href="#wol" role="tab" data-toggle="tab"><span>{% trans "Wake on LAN" %}</span></a></li>
{% endif %}
<li class="nav-item"><a class="nav-link" href="#settings" role="tab" data-toggle="tab"><span>{% trans "Settings" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#remote" role="tab" data-toggle="tab"><span>{% trans "Remote" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#advanced" role="tab" data-toggle="tab"><span>{% trans "Advanced" %}</span></a></li>
</ul>
<form id="displayEditForm" class="DisplayForm XiboForm form-horizontal" method="put" action="{{ url_for("display.edit", {id: display.displayId}) }}" data-gettag="{{ url_for("tag.getByName") }}">
<div class="tab-content">
<div class="tab-pane active" id="general">
{% if currentUser.featureEnabled('folder.view') %}
<div class="form-group row">
<label class="col-sm-2 control-label">{% trans "Current Folder" %}</label>
<div class="col-sm-10" style="padding-top: 7px">
<span id="originalFormFolder"></span>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 control-label">{% trans "Move to Selected Folder:" %}</label>
<div class="col-sm-10">
<button type="button" class="btn btn-info" id="select-folder-button" data-toggle="modal" data-target="#folder-tree-form-modal">{% trans "Select Folder" %}</button>
<span id="selectedFormFolder"></span>
</div>
</div>
{{ forms.hidden('folderId', display.folderId) }}
{% endif %}
{% set title %}{% trans "Display" %}{% endset %}
{% if displayLockName %}
{% set helpText %}{% trans "The Name of the Display - your administrator has locked this to the device name" %}{% endset %}
{{ forms.disabled("display", title, display.display, helpText, "", "required") }}
{% else %}
{% set helpText %}{% trans "The Name of the Display - (1 - 50 characters)." %}{% endset %}
{{ forms.input("display", title, display.display, helpText, "", "required") }}
{% endif %}
{% set title %}{% trans "Display's Hardware Key" %}{% endset %}
{% set helpText %}{% trans "A unique identifier for this display." %}{% endset %}
{{ forms.input("license", title, display.license, helpText, "", "required", "", false) }}
{% set title %}{% trans "Description" %}{% endset %}
{% set helpText %}{% trans "A description - (1 - 254 characters)." %}{% endset %}
{{ forms.input("description", title, display.description, helpText) }}
{% if currentUser.featureEnabled("tag.tagging") %}
{% set title %}{% trans "Tags" %}{% endset %}
{% set helpText %}{% trans "Tags for this Display - Comma separated string of Tags or Tag|Value format. If you choose a Tag that has associated values, they will be shown for selection below." %}{% endset %}
{{ forms.inputWithTags("tags", title, display.getTagString(), helpText, 'tags-with-value') }}
<p id="loadingValues" style="margin-left: 17%"></p>
{% set title %}{% trans "Tag value" %}{% endset %}
{{ forms.dropdown("tagValue", "single", title, "", options, "key", "value") }}
<div id="tagValueContainer">
{% set title %}{% trans "Tag value" %}{% endset %}
{% set helpText %}{% trans "Please provide the value for this Tag and confirm by pressing enter on your keyboard." %}{% endset %}
{{ forms.input("tagValueInput", title, "", helpText) }}
</div>
<div id="tagValueRequired" class="alert alert-info">
<p>{% trans "This tag requires a set value, please select one from the Tag value dropdown or provide Tag value in the dedicated field." %}</p>
</div>
{% endif %}
{% set title %}{% trans "Authorise display?" %}{% endset %}
{% set helpText %}{% trans "Use one of the available slots for this display?" %}{% endset %}
{% set yes %}{% trans "Yes" %}{% endset %}
{% set no %}{% trans "No" %}{% endset %}
{% set options = [
{ licensedid: 1, licensed: yes },
{ licensedid: 0, licensed: no }
] %}
{{ forms.dropdown("licensed", "single", title, display.licensed, options, "licensedid", "licensed", helpText) }}
{% set title %}{% trans "Default Layout" %}{% endset %}
{% set helpText %}{% trans "Set the Default Layout to use when no other content is scheduled to this Display. This will override the global Default Layout as set in CMS Administrator Settings. If left blank a global Default Layout will be automatically set for this Display." %}{% endset %}
{% set attributes = [
{ 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" },
{ name: "data-filter-options", value: '{"retired":"0"}' }
] %}
{{ forms.dropdown("defaultLayoutId", "single", title, display.defaultLayoutId, layouts, "layoutId", "layout", helpText, "pagedSelect", "", "", "", attributes) }}
</div>
<div class="tab-pane" id="location">
{% set title %}{% trans "Latitude" %}{% endset %}
{% set helpText %}{% trans "The Latitude of this display" %}{% endset %}
{{ forms.number("latitude", title, display.latitude, helpText) }}
{% set title %}{% trans "Longitude" %}{% endset %}
{% set helpText %}{% trans "The Longitude of this Display" %}{% endset %}
{{ forms.number("longitude", title, display.longitude, helpText) }}
{% set attributes = [
{ name: "data-width", value: "100%" }
] %}
{% set title %}{% trans "Timezone" %}{% endset %}
{% set helpText %}{% trans "The timezone for this display, or empty to use the CMS timezone" %}{% endset %}
{{ forms.dropdown("timeZone", "single", title, display.timeZone, [{id:"", value:""}]|merge(timeZones), "id", "value", helpText, "selectPicker", "", "", "", attributes) }}
{{ forms.message("Configure further details for integration with 3rd parties such as DOOH providers:") }}
{% set title %}{% trans "Languages" %}{% endset %}
{% set helpText %}{% trans "The languages that the audience viewing this Display are likely to understand" %}{% endset %}
{{ forms.dropdown("languages[]", "dropdownmulti", title, display.getLanguages(), languages, "id", "value", helpText, "selectPicker") }}
{% set title %}{% trans "Display Type" %}{% endset %}
{% set helpText %}{% trans "The Type of this Display" %}{% endset %}
{{ forms.dropdown("displayTypeId", "single", title, display.displayTypeId, [{displayTypeId:null, displayType:""}]|merge(displayTypes), "displayTypeId", "displayType", helpText) }}
{% set title %}{% trans "Venue" %}{% endset %}
{% set helpText %}{% trans "The Location/Venue of this display" %}{% endset %}
{% set attributes = [
{ name: "data-width", value: "100%" },
{ name: "data-search-url", value: url_for("display.venue.search") },
{ name: "data-allow-clear", value: "true" },
{ name: "data-placeholder--id", value: null },
{ name: "data-placeholder--value", value: "" },
{ name: "data-venue-id", value: display.venueId },
] %}
{{ forms.dropdown("venueId", "single", title, "", null, "venueId", "venueName", helpText, "venue-select", "", "", "", attributes) }}
{% set title %}{% trans "Address" %}{% endset %}
{% set helpText %}{% trans "The Address of this Display" %}{% endset %}
{{ forms.input("address", title, display.address, helpText) }}
{% set title %}{% trans "Screen size" %}{% endset %}
{% set helpText %}{% trans "The Screen size of this Display" %}{% endset %}
{{ forms.number("screenSize", title, display.screenSize, helpText) }}
{% set title %}{% trans "Is mobile?" %}{% endset %}
{% set helpText %}{% trans "Is this display mobile?" %}{% endset %}
{{ forms.checkbox("isMobile", title, display.isMobile, helpText) }}
{% set title %}{% trans "Is outdoor?" %}{% endset %}
{% set helpText %}{% trans "Is your display located outdoors?" %}{% endset %}
{{ forms.checkbox("isOutdoor", title, display.isOutdoor, helpText) }}
{% set title %}{% trans "Cost per play" %}{% endset %}
{% set helpText %}{% trans "The cost per play" %}{% endset %}
{{ forms.number("costPerPlay", title, display.costPerPlay, helpText) }}
{% set title %}{% trans "Impressions per play" %}{% endset %}
{% set helpText %}{% trans "The impressions per play" %}{% endset %}
{{ forms.number("impressionsPerPlay", title, display.impressionsPerPlay, helpText) }}
</div>
<div class="tab-pane" id="reference">
{{ forms.message("Add reference fields if needed"|trans) }}
{% set title %}{% trans "Reference 1" %}{% endset %}
{{ forms.input("ref1", title, display.ref1) }}
{% set title %}{% trans "Reference 2" %}{% endset %}
{{ forms.input("ref2", title, display.ref2) }}
{% set title %}{% trans "Reference 3" %}{% endset %}
{{ forms.input("ref3", title, display.ref3) }}
{% set title %}{% trans "Reference 4" %}{% endset %}
{{ forms.input("ref4", title, display.ref4) }}
{% set title %}{% trans "Reference 5" %}{% endset %}
{{ forms.input("ref5", title, display.ref5) }}
{% set title %}{% trans "Custom ID" %}{% endset %}
{{ forms.input("customId", title, display.customId) }}
</div>
<div class="tab-pane" id="maintenance">
{% set title %}{% trans "Email Alerts" %}{% endset %}
{% set helpText %}{% trans "Do you want to be notified by email if there is a problem with this display?" %}{% endset %}
{% set yes %}{% trans "Yes" %}{% endset %}
{% set no %}{% trans "No" %}{% endset %}
{% set options = [
{ id: 0, value: no },
{ id: 1, value: yes }
] %}
{{ forms.dropdown("emailAlert", "single", title, display.emailAlert, options, "id", "value", helpText) }}
{% set title %}{% trans "Use the Global Timeout?" %}{% endset %}
{% set helpText %}{% trans "Should this display be tested against the global time out or the Player collection interval?" %}{% endset %}
{{ forms.checkbox("alertTimeout", title, display.alertTimeout, helpText) }}
</div>
{% if not isWolDisabled %}
<div class="tab-pane" id="wol">
{% set title %}{% trans "Enable Wake on LAN" %}{% endset %}
{% set helpText %}{% trans "Wake on Lan requires the correct network configuration to route the magic packet to the display PC" %}{% endset %}
{{ forms.checkbox("wakeOnLanEnabled", title, display.wakeOnLanEnabled, helpText) }}
{% set title %}{% trans "BroadCast Address" %}{% endset %}
{% set helpText %}{% trans "The IP address of the remote host\'s broadcast address (or gateway)" %}{% endset %}
{{ forms.input("broadCastAddress", title, display.broadCastAddress, helpText) }}
{% set title %}{% trans "Wake on LAN SecureOn" %}{% endset %}
{% set helpText %}{% trans "Enter a hexadecimal password of a SecureOn enabled Network Interface Card (NIC) of the remote host. Enter a value in this pattern: \'xx-xx-xx-xx-xx-xx\'. Leave the following field empty, if SecureOn is not used (for example, because the NIC of the remote host does not support SecureOn)." %}{% endset %}
{{ forms.input("secureOn", title, display.secureOn, helpText) }}
{% set title %}{% trans "Wake on LAN Time" %}{% endset %}
{% set helpText %}{% trans "The time this display should receive the WOL command, using the 24hr clock - e.g. 19:00. Maintenance must be enabled." %}{% endset %}
{{ forms.input("wakeOnLanTime", title, display.wakeOnLanTime, helpText) }}
{% set title %}{% trans "Wake on LAN CIDR" %}{% endset %}
{% set helpText %}{% trans "Enter a number within the range of 0 to 32 in the following field. Leave the following field empty, if no subnet mask should be used (CIDR = 0). If the remote host\'s broadcast address is unknown: Enter the host name or IP address of the remote host in Broad Cast Address and enter the CIDR subnet mask of the remote host in this field." %}{% endset %}
{{ forms.input("cidr", title, display.cidr, helpText) }}
</div>
{% endif %}
<div class="tab-pane" id="settings">
{% set title %}{% trans "Settings Profile?" %}{% endset %}
{% set helpText %}{% trans "What display profile should this display use? To use the default profile leave this empty." %}{% endset %}
{{ forms.dropdown("displayProfileId", "single", title, display.displayProfileId, [{displayProfileId: null, name: ""}]|merge(profiles), "displayProfileId", "name", helpText) }}
{% set message %}{% trans "The settings for this display are shown below. They are taken from the active Display Profile for this Display, which can be changed in Display Settings. If you have altered the Settings Profile above, you will need to save and re-show the form." %}{% endset %}
{{ forms.message(message) }}
<table id="settings-from-profile" class="table table-striped">
<thead>
<th>{{ "Setting"|trans }}</th>
<th>{{ "Profile"|trans }}</th>
<th>{{ "Override"|trans }}</th>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="tab-pane" id="advanced">
{% set title %}{% trans "Interleave Default" %}{% endset %}
{% set helpText %}{% trans "Whether to always put the default layout into the cycle." %}{% endset %}
{% set yes %}{% trans "Yes" %}{% endset %}
{% set no %}{% trans "No" %}{% endset %}
{% set options = [
{ id: 0, value: no },
{ id: 1, value: yes }
] %}
{{ forms.dropdown("incSchedule", "single", title, display.incSchedule, options, "id", "value", helpText) }}
{% set title %}{% trans "Auditing until" %}{% endset %}
{% set helpText %}{% trans "Collect auditing from this Player. Should only be used if there is a problem with the display." %}{% endset %}
{% if display.isAuditing() %}
{% set auditing = display.getUnmatchedProperty("auditingUntilIso") %}
{% else %}
{% set auditing = "" %}
{% endif %}
{{ forms.dateTime("auditingUntil", title, auditing, helpText) }}
{% set title %}{% trans "Bandwidth limit" %}{% endset %}
{% set helpText %}{% trans "The bandwidth limit that should be applied. Enter 0 for no limit." %}{% endset %}
<div class="form-group row">
<label class="col-sm-2 control-label" for="bandwidthLimit">{{ title }}</label>
<div class="col-sm-6">
<input class="form-control" name="bandwidthLimit" type="number" id="bandwidthLimit" min="0" value="{{ display.bandwidthLimit }}"/>
<span class="help-block">{{ helpText }}</span>
</div>
<div class="col-sm-4">
<select name="bandwidthLimitUnits" class="form-control">
<option value="kb">KiB</option>
<option value="mb">MiB</option>
<option value="gb">GiB</option>
</select>
</div>
</div>
{% set title %}{% trans "Clear Cached Data" %}{% endset %}
{% set helpText %}{% trans "Remove any cached data for this display." %}{% endset %}
{{ forms.checkbox("clearCachedData", title, 1, helpText) }}
{% set title %}{% trans "Reconfigure XMR" %}{% endset %}
{% set helpText %}{% trans "Remove the XMR configuration for this Player and send a rekey action." %}{% endset %}
{{ forms.checkbox("rekeyXmr", title, 0, helpText) }}
</div>
<div class="tab-pane" id="remote">
{% set title %}{% trans "TeamViewer Serial" %}{% endset %}
{% set helpText %}{% trans "If TeamViewer is installed on the device, enter the serial number here." %}{% endset %}
{{ forms.input("teamViewerSerial", title, display.teamViewerSerial, helpText) }}
{% set title %}{% trans "Webkey Serial" %}{% endset %}
{% set helpText %}{% trans "If Webkey is installed on the device, enter the serial number here." %}{% endset %}
{{ forms.input("webkeySerial", title, display.webkeySerial, helpText) }}
</div>
</div>
</form>
<div id="settings-from-display-profile" style="display:none">
{# Output the relevant form depending on the display profile player type #}
{% if displayProfile.getClientType() == "android" %}
{% include "displayprofile-form-edit-android.twig" %}
{% elseif displayProfile.getClientType() == "windows" %}
{% include "displayprofile-form-edit-windows.twig" %}
{% elseif displayProfile.getClientType() == "linux" %}
{% include "displayprofile-form-edit-linux.twig" %}
{% elseif displayProfile.getClientType() == "lg" or displayProfile.getClientType() == "sssp" %}
{% include "displayprofile-form-edit-soc.twig" %}
{% elseif displayProfile.getClientType() == "chromeOS" %}
{% include "displayprofile-form-edit-chromeos.twig" %}
{% elseif displayProfile.isCustom() %}
{{ include(displayProfile.getCustomEditTemplate()) }}
{% endif %}
{% include "displayprofile-form-edit-javascript.twig" %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,53 @@
{#
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Check Commercial Licence" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, displayFormLicenceCheckSubmit($("#displayLicenceCheckForm"))
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayLicenceCheckForm" class="XiboForm form-horizontal" method="put" action="{{ url_for("display.licencecheck", {id: display.displayId}) }}">
{% set message %}{% trans "Are you sure you want to ask this Player to check its Licence?" %}{% endset %}
{{ forms.message(message) }}
{% set message %}{% trans "The result of this check will be immediately actioned and the status reported in Commercial Licence column." %}{% endset %}
{{ forms.message(message) }}
{% if display.xmrChannel is empty %}
<div class="alert alert-info">
{% set message %}{% trans %}XMR is not working on this Player yet and therefore the licence check may not occur.{% endtrans %}{% endset %}
{{ forms.message(message) }}
</div>
{% endif %}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,83 @@
{#
/**
* Copyright (C) 2020-2023 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/>.
*/
#}
{% set randomId = random() %}
{% extends "form-base.twig" %}
{% import "forms.twig" as forms %}
{% import "inline.twig" as inline %}
{% block formTitle %}
{% set displayName = display.display %}
{% trans %}Manage Membership for {{ displayName }}{% endtrans %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, forms.membersFormSubmit("{{ randomId }}")
{% endblock %}
{% block callBack %}forms.membersFormOpen{% endblock %}
{% block extra %}{{ extra|json_encode|raw }}{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<div id="{{ randomId }}" class="div-horizontal controlDiv"
data-display-groups="true"
data-display-groups-get-url="{{ url_for("displayGroup.search") }}"
data-display-groups-param="displayGroupId"
data-display-groups-param-unassign="unassignDisplayGroupId"
data-display-groups-url="{{ url_for("display.assign.displayGroup", {id: display.displayId}) }}"
>
{% set helpText %}{% trans %}Check or un-check the options against each display group to control whether they are a member or not.{% endtrans %}{% endset %}
{{ forms.message(helpText) }}
<div class="XiboGrid" id="{{ random() }}">
<div class="XiboFilter card mb-3 bg-light">
<div class="FilterDiv card-body" id="Filter">
<form class="form-inline" id="displayGroupMembersFilter">
{% set title %}{% trans "Name" %}{% endset %}
{{ inline.input("displayGroup", title) }}
{{ forms.hidden("isDynamic", 0) }}
{{ inline.hidden("displayIdMember", display.displayId) }}
</form>
</div>
</div>
<table id="displaysGroupsMembersTable" class="table table-bordered membersTable" style="width: 100%">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Display Group" %}</th>
<th>{% trans "Member" %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,57 @@
{#
/*
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Transfer to another CMS" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#displayMoveCmsForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayMoveCmsForm" class="XiboForm form-horizontal" method="put" action="{{ url_for("display.moveCms", {id: display.displayId}) }}">
{% set message = "Please note: Once the CMS Address and Key are authenticated in this form the Display will attempt to register with the CMS Instance details entered. Once transferred the Display will stop communicating with this CMS Instance."|trans %}
{{ forms.message(message, "alert alert-info") }}
{% set title = "New CMS Address"|trans %}
{% set helpText = "Full URL to the new CMS, including https://"|trans %}
{{ forms.input("newCmsAddress", title, display.newCmsAddress, helpText) }}
{% set title = "New CMS Key"|trans %}
{% set helpText = "CMS Secret Key associated with the provided new CMS Address"|trans %}
{{ forms.input("newCmsKey", title, display.newCmsKey, helpText) }}
{% set title = "Two Factor Code"|trans %}
{% set helpText = "Please enter your Two Factor authentication code"|trans %}
{{ forms.input("twoFactorCode", title, "", helpText, "", "required") }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,44 @@
{#
/**
* Xibo - Digital Signage - http://www.xibo.org.uk
* Copyright (C) 2020 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 "Cancel Transfer?" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Yes" %}, $("#displayMoveCmsCancelForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayMoveCmsCancelForm" class="XiboForm form-horizontal" method="delete" action="{{ url_for("display.moveCmsCancel", {id: display.displayId }) }}">
{% set message %}{% trans "Are you sure you want to cancel this CMS transfer? This is only possible if the Display has not already transferred." %}{% endset %}
{{ forms.message(message) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,53 @@
{#
* Copyright (C) 2021 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 "Purge all Media files" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#displayPurgeAllForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayPurgeAllForm" class="XiboForm form-horizontal" method="put" action="{{ url_for("display.purge.all", {id: display.displayId}) }}">
{% set message %}{% trans "Caution! Trigerring this action will ask Player to remove every downloaded Media file from its storage." %}{% endset %}
{{ forms.alert(message, 'danger') }}
{% set message %}{% trans "This action will be immediately actioned. Player will remove all existing Media files from its local storage and request fresh copies of required files from the CMS" %}{% endset %}
{{ forms.alert(message, 'info') }}
{% if display.xmrChannel is empty %}
<div class="alert alert-info">
{% set message %}{% trans %}XMR is not working on this Player yet and therefore the licence check may not occur.{% endtrans %}{% endset %}
{{ forms.message(message) }}
</div>
{% endif %}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,58 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Request Screen Shot" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, displayRequestScreenshotFormSubmit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayRequestScreenshotForm" class="XiboForm form-horizontal" method="put" action="{{ url_for("display.requestscreenshot", {id: display.displayId}) }}">
{% set message %}{% trans "Are you sure you want to request a screenshot?" %}{% endset %}
{{ forms.message(message) }}
{% set message %}{% trans "If the Player is configured for push messaging, screenshots are requested immediately and should be seen when the form closed. In some circumstances it may be necessary to refresh the page after a few seconds." %}{% endset %}
{{ forms.message(message) }}
{% set message %}{% trans "Screenshots can be seen in the Display Grid by selecting Column Visibility and enabling the Screenshot column." %}{% endset %}
{{ forms.message(message) }}
{% if display.xmrChannel is empty %}
<div class="alert alert-info">
{% set message %}{% trans %}XMR is not working on this Player yet, the screenshot will be requested the next time the Player connects on its collection interval, expected {{ nextCollect }}.{% endtrans %}{% endset %}
{{ forms.message(message) }}
</div>
{% endif %}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,68 @@
{#
/**
* 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/>.
*/
#}
{% set randomId = random() %}
{% extends "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Bandwidth Limit" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#displayFormSetBandwidthLimit").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayFormSetBandwidthLimit" class="XiboForm form-horizontal" method="put" action="{{ url_for("display.setBandwidthLimitMultiple", {id: display.displayId}) }}">
{% set setBandwidthMultipleMessage %}
{% trans "Change Bandwidth Limit to all the selected displays." %}<br>
{% endset %}
{{ forms.message(setBandwidthMultipleMessage) }}
{{ forms.hidden('ids', ids) }}
{% set title %}{% trans "Bandwidth limit" %}{% endset %}
{% set helpText %}{% trans "The bandwidth limit that should be applied. Enter 0 for no limit." %}{% endset %}
<div class="form-group row">
<label class="col-sm-2 control-label" for="bandwidthLimit">{{ title }}</label>
<div class="col-sm-6">
<input class="form-control" name="bandwidthLimit" type="number" id="bandwidthLimit" min="0" value=""/>
<span class="help-block">{{ helpText }}</span>
</div>
<div class="col-sm-4">
<select name="bandwidthLimitUnits" class="form-control">
<option value="kb">KiB</option>
<option value="mb">MiB</option>
<option value="gb">GiB</option>
</select>
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Wake On Lan" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Yes" %}, $("#displayWakeOnLanForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayWakeOnLanForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("display.wol", {id: display.displayId}) }}">
{% set message %}{% trans "Are you sure you want to send a Wake On Lan message to this display?" %}{% endset %}
{{ forms.message(message) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,421 @@
{#
/**
* 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 "authed.twig" %}
{% import "inline.twig" as inline %}
{% set displayName = display.display %}
{% block title %}{% trans %}Manage {{ displayName }}{% endtrans %} | {% endblock %}
{% block pageContent %}
{% set displayLastAccessed = display.lastAccessed %}
{% if display.loggedIn == 1 %}
{% set title %}{% trans %}Display {{ displayName }} is currently logged-in, seen {{ timeAgo }}.{% endtrans %}{% endset %}
{% else %}
{% set title %}{% trans %}Display {{ displayName }} is not logged in at the moment and last accessed at <span class="unixDate">{{ displayLastAccessed }}</span>{% endtrans %}{% endset %}
{% endif %}
<h1 class="text-center">{{ title }}</h1>
{% if display.mediaInventoryStatus == 3 %}
<p class="text-center">{% trans "This Display hasn't connected since updates have been made in the CMS. The below information will be updated when it has." %}</p>
{% endif %}
<div class="row">
<div class="col-lg-6">
<div class="widget">
<div class="widget-title">{% trans "File Status - Count of Files" %}</div>
<div class="widget-body">
<canvas id="downloadedCountPie" style="clear:both; margin-top:25px;" width="230"></canvas>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="widget">
<div class="widget-title">{% trans "File Status - Size of Files" %}</div>
<div class="widget-body">
<canvas id="downloadedSizePie" style="clear:both; margin-top:25px;" width="230"></canvas>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="widget">
{% if display.clientCode < 300 %}
<div class="widget-body medium">
<p class="alert alert-info">
{{ "This player is too old to show faults. Please upgrade it to v3 or later."|trans }}
</p>
</div>
{% else %}
<div class="widget-title">
<button id="refreshLog" class="btn btn-sm pull-right"><span class="fa fa-refresh"></span></button>
{% trans "Reported Player Faults" %}
</div>
<div class="widget-body medium no-padding">
<table id="playerFaults" class="table table-striped" data-url="{{ url_for("display.faults.search", {"displayId": display.displayId}) }}">
<thead>
<tr>
<th>{% trans "Code" %}</th>
<th>{% trans "Reason" %}</th>
<th>{% trans "Date" %}</th>
<th>{% trans "Expires" %}</th>
<th>{% trans "Schedule ID" %}</th>
<th>{% trans "Layout ID" %}</th>
<th>{% trans "Region ID" %}</th>
<th>{% trans "Widget ID" %}</th>
<th>{% trans "Media ID" %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
{% endif %}
</div>
</div>
<div class="col-lg-6">
<div class="widget">
<div class="widget-title">{% trans "Dependencies" %}</div>
<div class="widget-body medium no-padding">
<table class="table table-striped">
<thead>
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Complete" %}</th>
<th>{% trans "Downloaded" %}</th>
</tr>
</thead>
<tbody>
{% for item in inventory.dependencies %}
<tr>
<td>{{ item.path }}</td>
<td>{{ item.fileType }}</td>
<td>
<span class="fa {% if item.complete == 1 %}fa-check{% else %}fa-download{% endif %}"></span>
</td>
<td>{{ item.bytesRequested|byteFormat }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="widget">
<div class="widget-title">{% trans "Layouts" %}</div>
<div class="widget-body medium no-padding">
<table class="table table-striped">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Name" %}</th>
<th>{% trans "Size" %}</th>
<th>{% trans "Complete" %}</th>
<th>{% trans "Downloaded" %}</th>
</tr>
</thead>
<tbody>
{% for item in inventory.layouts %}
<tr>
<td>{{ item.itemId }}</td>
<td>{{ item.layout }}</td>
<td>{{ item.size|byteFormat }}</td>
<td>
<span class="fa {% if item.complete == 1 %}fa-check{% else %}fa-download{% endif %}"></span>
</td>
<td>{{ item.bytesRequested|byteFormat }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="widget">
<div class="widget-title">
{% trans "Media" %}
</div>
<div class="widget-body medium no-padding">
<table class="table table-striped">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Name" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "File Name" %}</th>
<th>{% trans "Size" %}</th>
<th>{% trans "Complete" %}</th>
<th>{% trans "Downloaded" %}</th>
<th>{% trans "Released" %}</th>
</tr>
</thead>
<tbody>
{% for item in inventory.media %}
<tr>
<td>{{ item.itemId }}</td>
<td>{{ item.name }}</td>
<td>{{ item.type }}</td>
<td>{{ item.storedAs }}</td>
<td>{{ item.size|byteFormat }}</td>
<td title="{% if item.lastUsed == 0 %}{{ nonceNotUsed }}{% else %}{{ nonceUsed }}{% endif %}">
<span class="fa {% if item.complete == 1 %}fa-check{% else %}fa-download{% endif %}"></span>
</td>
<td>{{ item.bytesRequested|byteFormat }}</td>
<td>
<span class="fa {% if item.released == 1 %}fa-check{% elseif item.released == 0 %}fa-cogs{% elseif item.released == 2 %}fa-times{% endif %}"></span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="widget">
<div class="widget-title">{% trans "Widgets" %}</div>
<div class="widget-body medium no-padding">
<table class="table table-striped">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Name" %}</th>
<th>{% trans "Complete" %}</th>
<th>{% trans "Downloaded" %}</th>
</tr>
</thead>
<tbody>
{% for item in inventory.widgets %}
<tr>
<td>{{ item.itemId }}</td>
<td>{% if item.widgetName %}{{ item.widgetName }}{% else %}{{ item.widgetType }}{% endif %}</td>
<td title="{% if item.lastUsed == 0 %}{{ nonceNotUsed }}{% else %}{{ nonceUsed }}{% endif %}">
<span class="fa {% if item.complete == 1 %}fa-check{% else %}fa-download{% endif %}"></span>
</td>
<td>{{ item.bytesRequested|byteFormat }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="widget">
<div class="widget-title">{% trans "Widget Data" %}</div>
<div class="widget-body medium no-padding">
<table class="table table-striped">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Name" %}</th>
<th>{% trans "Downloaded" %}</th>
</tr>
</thead>
<tbody>
{% for item in inventory.widgetData %}
<tr>
<td>{{ item.widgetId }}</td>
<td>{% if item.widgetName %}{{ item.widgetName }}{% else %}{{ item.widgetType }}{% endif %}</td>
<td>{{ item.bytesRequested|byteFormat }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% if currentUser.featureEnabled("displays.reporting") %}
<div class="row">
<div class="col-lg-12">
<div class="widget">
<div class="widget-title">{% trans "Bandwidth" %}</div>
<div class="widget-body">
<div class="XiboGrid" id="{{ random() }}">
<div class="XiboFilter">
<div class="FilterDiv card-body" id="bandwidthFilter">
<form class="form-inline">
{{ inline.dateMonth("fromDt", "From Date", defaults.fromDate, "", "", "", "") }}
{{ inline.dateMonth("toDt", "To Date", defaults.toDate, "", "", "", "") }}
{{ inline.hidden("displayId", display.displayId) }}
</form>
</div>
</div>
<div class="XiboData">
<canvas id="bandwidthChart" style="clear:both; margin-top:25px;" height="330"></canvas>
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% endblock %}
{% block javaScript %}
<script type="text/javascript" nonce="{{ cspNonce }}">
$(document).ready(function() {
const $playerFaults = $('#playerFaults');
if ($playerFaults.length < 0) {
return;
}
var table = $playerFaults.DataTable({
language: dataTablesLanguage,
dom: dataTablesTemplate,
serverSide: true, stateSave: true, stateDuration: 0,
stateLoadCallback: dataTableStateLoadCallback,
stateSaveCallback: dataTableStateSaveCallback,
filter: false,
responsive: true,
searchDelay: 3000,
order: [[2, "desc"]],
ajax: {
url: $playerFaults.data().url,
},
"columns": [
{"data": "code", responsivePriority: 1, className: 'all'},
{"data": "reason", responsivePriority: 1, className: 'all'},
{"data": "incidentDt", responsivePriority: 1, className: 'all'},
{"data": "expires", responsivePriority: 1, className: 'all'},
{"data": "scheduleId", responsivePriority: 99, className: 'none'},
{"data": "layoutId", responsivePriority: 99, className: 'none'},
{"data": "regionId", responsivePriority: 99, className: 'none'},
{"data": "widgetId", responsivePriority: 99, className: 'none'},
{"data": "mediaId", responsivePriority: 99, className: 'none'}
]
});
table.on('draw', dataTableDraw);
table.on('processing.dt', dataTableProcessing);
$("#refreshLog").click(function () {
table.ajax.reload();
});
});
var bandwidthChart = null;
function setBandwidthChart() {
$.ajax({
type: "get",
url: "{{ url_for("stats.bandwidth.data") }}",
cache: false,
dataType: "json",
data: $("#bandwidthFilter").find("form").serialize(),
success: function(response) {
if (bandwidthChart !== undefined && bandwidthChart !== null) {
bandwidthChart.destroy();
}
if (response.extra.data.length <= 0) {
return;
}
// Create our chart
bandwidthChart = new Chart($("#bandwidthChart"), {
type: "bar",
data: {
labels: response.extra.labels,
datasets: [{
label: "{% trans "Bandwidth" %}",
backgroundColor: "rgb(11, 98, 164)",
data: response.extra.data
}]
},
options: {
scales: {
yAxes: [{
scaleLabel: {
display: true,
labelString: response.extra.postUnits,
}
}]
},
legend: {
display: false
},
maintainAspectRatio: false,
}
});
}
});
}
$(document).ready(function() {
{% if currentUser.featureEnabled("displays.reporting") %}
setBandwidthChart();
// Bind to form change
$("#bandwidthFilter input, #bandwidthFilter select").change(function() {
setBandwidthChart();
});
{% endif %}
// Find all Unix Dates and sort them out
$("span.unixDate").each(function() {
$(this).html(moment($(this).html(), "X").format(jsDateFormat));
});
// Find all ISO Dates and sort them out
$("span.isoDate").each(function() {
$(this).html(moment($(this).html()).format(jsDateFormat));
});
// Handle the Pie chart for download status
var downloadedCountPie = new Chart($("#downloadedCountPie"), {
type: 'pie',
data: {
datasets: [{
data: [{{ status.countComplete }}, {{ status.countRemaining }}],
backgroundColor: ["#00CC00", "#FF0000"]
}],
labels: [
"{% trans "Downloaded" %}", "{% trans "Pending" %}"
]
}
});
var downloadedSizePie = new Chart($("#downloadedSizePie"), {
type: 'pie',
data: {
datasets: [{
data: [{{ status.sizeComplete }}, {{ status.sizeRemaining }}],
backgroundColor: ["#00CC00", "#FF0000"]
}],
labels: [
"{% trans "Downloaded" %}" + " {{ status.units }}", "{% trans "Pending" %}" + " {{ status.units }}"
]
},
});
});
</script>
{% endblock %}

369
views/display-page.twig Normal file
View File

@@ -0,0 +1,369 @@
{#
/**
* Copyright (C) 2023 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 %}{{ "Displays"|trans }} | {% endblock %}
{% block actionMenu %}
<div class="widget-action-menu pull-right">
{% if currentUser.featureEnabled("displays.add") %}
<button class="btn btn-success XiboFormButton" title="{% trans "Add a Display via user_code displayed on the Player screen" %}" href="{{ url_for("display.addViaCode.form") }}"> <i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Display (Code)" %}</button>
{% endif %}
<button class="btn btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
</div>
{% endblock %}
{% block headContent %}
{# Add page source code bundle ( CSS ) #}
<link rel="stylesheet" href="{{ theme.rootUri() }}dist/pages/display-page.bundle.min.css?v={{ version }}&rev={{revision }}">
{% endblock %}
{% block pageContent %}
<div class="widget">
<div class="widget-title">{% trans "Displays" %}</div>
<div class="widget-body">
<div class="XiboGrid" id="{{ random() }}" data-grid-name="displayView">
<div class="XiboFilter card mb-3 bg-light">
<div class="FilterDiv card-body" id="Filter">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#filter-general" role="tab" data-toggle="tab">{% trans "General" %}</a></li>
<li class="nav-item"><a class="nav-link" href="#filter-advanced" role="tab" data-toggle="tab">{% trans "Advanced" %}</a></li>
</ul>
<form class="form-inline">
<div class="tab-content">
<div class="tab-pane active" id="filter-general">
{% set title %}{% trans "ID" %}{% endset %}
{{ inline.number("displayId", title) }}
{% set title %}{% trans "Name" %}{% endset %}
{{ inline.inputNameGrid('display', title) }}
{% set title %}{% trans "Status" %}{% endset %}
{% set check %}{% trans "Up to date" %}{% endset %}
{% set cross %}{% trans "Downloading" %}{% endset %}
{% set cloud %}{% trans "Out of date" %}{% endset %}
{% set options = [
{ optionid: "", option: "" },
{ optionid: "1", option: check},
{ optionid: "2", option: cross},
{ optionid: "3", option: cloud}
] %}
{{ inline.dropdown("mediaInventoryStatus", "single", title, "", options, "optionid", "option") }}
{% set title %}{% trans "Logged In?" %}{% endset %}
{% set yesOption %}{% trans "Yes" %}{% endset %}
{% set noOption %}{% trans "No" %}{% endset %}
{% set options = [
{ optionid: "", option: "" },
{ optionid: "1", option: yesOption},
{ optionid: "0", option: noOption}
] %}
{{ inline.dropdown("loggedIn", "single", title, "", options, "optionid", "option") }}
{% set title %}{% trans "Authorised?" %}{% endset %}
{% set yesOption %}{% trans "Yes" %}{% endset %}
{% set noOption %}{% trans "No" %}{% endset %}
{% set options = [
{ optionid: "", option: "" },
{ optionid: "1", option: yesOption },
{ optionid: "0", option: noOption},
] %}
{{ inline.dropdown("authorised", "single", title, "", options, "optionid", "option") }}
{% set title %}{% trans "XMR Registered?" %}{% endset %}
{% set yesOption %}{% trans "Yes" %}{% endset %}
{% set noOption %}{% trans "No" %}{% endset %}
{% set options = [
{ optionid: "", option: "" },
{ optionid: 1, option: yesOption},
{ optionid: 0, option: noOption},
] %}
{{ inline.dropdown("xmrRegistered", "single", title, "", options, "optionid", "option") }}
{% 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 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", exactTagTitle, logicalOperatorTitle) }}
{% endif %}
{% if currentUser.featureEnabled("displaygroup.view") %}
{% 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-filter-options", value: '{"isDisplaySpecific":0}' },
{ name: "data-search-term", value: "displayGroup" },
{ name: "data-id-property", value: "displayGroupId" },
{ name: "data-text-property", value: "displayGroup" },
{ name: "data-initial-key", value: "displayGroupId" },
] %}
{{ inline.dropdown("displayGroupId", "single", title, "", null, "displayGroupId", "displayGroup", helpText, "pagedSelect", "", "", "", attributes) }}
{% endif %}
{% if currentUser.featureEnabled("displayprofile.view") %}
{% set title %}{% trans "Display Profile" %}{% endset %}
{{ inline.dropdown("displayProfileId", "single", title, "", [{displayProfileId:null, name:""}]|merge(displayProfiles), "displayProfileId", "name") }}
{% endif %}
{{ inline.hidden("folderId") }}
</div>
<div class="tab-pane" id="filter-advanced">
{% set title %}{% trans "Last Accessed" %}{% endset %}
{{ inline.date("lastAccessed", title) }}
{% set title %}{% trans "Player Type" %}{% endset %}
{% set android %}{% trans "Android" %}{% endset %}
{% set chromeos %}{% trans "ChromeOS" %}{% endset %}
{% set windows %}{% trans "Windows" %}{% endset %}
{% set webos %}{% trans "webOS" %}{% endset %}
{% set sssp %}{% trans "Tizen" %}{% endset %}
{% set linux %}{% trans "Linux" %}{% endset %}
{% set options = [
{ optionid: "", option: "" },
{ optionid: "android", option: android},
{ optionid: "chromeos", option: chromeos},
{ optionid: "windows", option: windows},
{ optionid: "lg", option: webos},
{ optionid: "sssp", option: sssp},
{ optionid: "linux", option: linux},
] %}
{{ inline.dropdown("clientType", "single", title, "", options, "optionid", "option") }}
{% set title %}{% trans "Player Code" %}{% endset %}
{{ inline.input("clientCode", title) }}
{% set title %}{% trans "Custom ID" %}{% endset %}
{{ inline.input("customId", title) }}
{% set title %}{% trans "Mac Address" %}{% endset %}
{{ inline.input("macAddress", title) }}
{% set title %}{% trans "IP Address" %}{% endset %}
{{ inline.input("clientAddress", title) }}
{% set title %}{% trans "Orientation" %}{% endset %}
{% set landscape %}{% trans "Landscape" %}{% endset %}
{% set portrait %}{% trans "Portrait" %}{% endset %}
{% set options = [
{ optionid: "", option: "" },
{ optionid: "landscape", option: landscape},
{ optionid: "portrait", option: portrait}
] %}
{{ inline.dropdown("orientation", "single", title, "", options, "optionid", "option") }}
{% set title %}{% trans "Commercial Licence" %}{% endset %}
{% set licensed %}{% trans "Licensed fully" %}{% endset %}
{% set trial %}{% trans "Trial" %}{% endset %}
{% set notLinceced %}{% trans "Not licenced" %}{% endset %}
{% set notApplicable %}{% trans "Not applicable" %}{% endset %}
{% set options = [
{ optionid: "", option: "" },
{ optionid: "1", option: licensed},
{ optionid: "2", option: trial},
{ optionid: "0", option: notLinceced},
{ optionid: "3", option: notApplicable}
] %}
{{ inline.dropdown("commercialLicence", "single", title, "", options, "optionid", "option") }}
{% set title %}{% trans "Player supported?" %}{% endset %}
{% set yesOption %}{% trans "Yes" %}{% endset %}
{% set noOption %}{% trans "No" %}{% endset %}
{% set options = [
{ optionid: "", option: "" },
{ optionid: 1, option: yesOption},
{ optionid: 0, option: noOption},
] %}
{{ inline.dropdown("isPlayerSupported", "single", title, "", options, "optionid", "option") }}
</div>
</div>
</form>
</div>
</div>
<div class="grid-with-folders-container">
<div class="grid-folder-tree-container p-3" id="grid-folder-filter">
<input id="jstree-search" class="form-control" type="text" placeholder="{% trans "Search" %}">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="folder-tree-clear-selection-button">
<label class="form-check-label" for="folder-tree-clear-selection-button" title="{% trans "Search in all folders" %}">{% trans "All Folders" %}</label>
</div>
<div class="folder-search-no-results d-none">
<p>{% trans 'No Folders matching the search term' %}</p>
</div>
<div id="container-folder-tree"></div>
</div>
<div class="folder-controller d-none">
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{{ "Open / Close Folder Search options"|trans }}"><i class="fas fa-folder fa-1x"></i></button>
<div id="breadcrumbs" class="mt-2 pl-2"></div>
</div>
<div class="map-controller d-none pl-1">
<button type="button" id="map_button" class="btn btn-primary" title="{{ "Map"|trans }}"><i class="fa fa-map"></i></button>
</div>
<div class="list-controller d-none pl-1">
<button type="button" id="list_button" class="btn btn-primary" title="{{ "List"|trans }}"><i class="fa fa-list"></i></button>
</div>
<div id="datatable-container">
<div class="XiboData card py-3">
<table id="displays" class="table table-striped" data-content-type="display" data-content-id-name="displayId" data-state-preference-name="displayGrid" style="width: 100%;">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Display" %}</th>
<th>{% trans "Display Type" %}</th>
<th>{% trans "Address" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Authorised?" %}</th>
<th>{% trans "Current Layout" %}</th>
<th>{% trans "Storage Available" %}</th>
<th>{% trans "Storage Total" %}</th>
<th>{% trans "Storage Free %" %}</th>
<th>{% trans "Description" %}</th>
<th>{% trans "Orientation" %}</th>
<th>{% trans "Resolution" %}</th>
{% if currentUser.featureEnabled("tag.tagging") %}<th>{% trans "Tags" %}</th>{% endif %}
<th>{% trans "Default Layout" %}</th>
<th>{% trans "Interleave Default" %}</th>
<th>{% trans "Email Alert" %}</th>
<th>{% trans "Logged In" %}</th>
<th>{% trans "Last Accessed" %}</th>
<th>{% trans "Display Profile" %}</th>
<th>{% trans "Version" %}</th>
<th>{% trans "Supported?" %}</th>
<th>{% trans "Device Name" %}</th>
<th>{% trans "IP Address" %}</th>
<th>{% trans "Mac Address" %}</th>
<th>{% trans "Timezone" %}</th>
<th>{% trans "Languages" %}</th>
<th>{% trans "Latitude" %}</th>
<th>{% trans "Longitude" %}</th>
<th>{% trans "Screen shot?" %}</th>
<th>{% trans "Thumbnail" %}</th>
<th>{% trans "CMS Transfer?" %}</th>
<th>{% trans "Bandwidth Limit" %}</th>
<th>{% trans "Last Command" %}</th>
<th>{% trans "XMR Registered" %}</th>
<th>{% trans "Commercial Licence" %}</th>
<th>{% trans "Remote" %}</th>
<th>{% trans "Sharing" %}</th>
<th>{% trans "Screen Size" %}</th>
<th>{% trans "Is Mobile?" %}</th>
<th>{% trans "Outdoor?" %}</th>
<th>{% trans "Reference 1" %}</th>
<th>{% trans "Reference 2" %}</th>
<th>{% trans "Reference 3" %}</th>
<th>{% trans "Reference 4" %}</th>
<th>{% trans "Reference 5" %}</th>
<th>{% trans "Custom ID" %}</th>
<th>{% trans "Cost Per Play" %}</th>
<th>{% trans "Impressions Per Play" %}</th>
<th>{% trans "Created Date" %}</th>
<th>{% trans "Modified Date" %}</th>
<th>{% trans "Faults?" %}</th>
<th>{% trans "OS Version" %}</th>
<th>{% trans "OS SDK" %}</th>
<th>{% trans "Manufacturer" %}</th>
<th>{% trans "Brand" %}</th>
<th>{% trans "Model" %}</th>
<th class="rowMenu"></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<!-- Map -->
<div class="row">
<div class="col-sm-12">
<div class="map-legend" style="display:none; position: absolute; z-index: 500; right: 20px; top: 10px;">
<div class="display-map-legend" style="font-size: 12px;">
<div>Logged in</div>
<div><img style="width: 15%" src='{{ theme.rootUri() }}dist/assets/map-marker-green-check.png'/> - Up to date</div>
<div><img style="width: 15%" src='{{ theme.rootUri() }}dist/assets/map-marker-yellow-check.png'/> - Out of date</div>
<div><img style="width: 15%" src='{{ theme.rootUri() }}dist/assets/map-marker-red-check.png'/> - Downloading/Unknown</div>
</br>
<div>Logged out</div>
<div><img style="width: 15%" src='{{ theme.rootUri() }}dist/assets/map-marker-green-cross.png'/> - Up to date</div>
<div><img style="width: 15%" src='{{ theme.rootUri() }}dist/assets/map-marker-yellow-cross.png'/> - Out of date</div>
<div><img style="width: 15%" src='{{ theme.rootUri() }}dist/assets/map-marker-red-cross.png'/> - Downloading/Unknown</div>
</div>
</div>
<div id="display-map" data-displays-url="{{ url_for("display.map") }}">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javaScript %}
{# Initialise JS variables and translations #}
<script type="text/javascript" nonce="{{ cspNonce }}" defer>
{# JS variables #}
var publicPath = "{{ theme.rootUri() }}";
var displaySearchURL = "{{ url_for('display.search') }}";
var layoutSearchURL = "{{ url_for('layout.search') }}";
var mapConfig = {{ mapConfig| json_encode | raw }};
var playerVersionSupport = "{{playerVersion}}";
var folderViewEnabled = "{{ currentUser.featureEnabled('folder.view') }}";
var taggingEnabled = "{{ currentUser.featureEnabled('tag.tagging') }}";
var showThumbnailColumn = "{{ currentUser.getOptionValue('showThumbnailColumn', 1) }}";
var SHOW_DISPLAY_AS_VNCLINK = "{{ settings.SHOW_DISPLAY_AS_VNCLINK }}";
var SHOW_DISPLAY_AS_VNC_TGT = "{{ settings.SHOW_DISPLAY_AS_VNC_TGT }}";
{# Custom translations #}
var displayPageTrans = {
back: "{% trans "Back" %}",
yes: "{% trans "Yes" %}",
no: "{% trans "No" %}",
daysOfTheWeek: {
monday: "{% trans "Monday" %}",
tuesday: "{% trans "Tuesday" %}",
wednesday: "{% trans "Wednesday" %}",
thursday: "{% trans "Thursday" %}",
friday: "{% trans "Friday" %}",
saturday: "{% trans "Saturday" %}",
sunday: "{% trans "Sunday" %}",
},
playerStatusWindow: "{% trans "Player Status Window" %}",
VNCtoThisDisplay: "{% trans "VNC to this Display" %}",
TeamViewertoThisDisplay: "{% trans "TeamViewer to this Display" %}",
WebkeytoThisDisplay: "{% trans "Webkey to this Display" %}",
};
</script>
{# Add page source code bundle ( JS ) #}
<script src="{{ theme.rootUri() }}dist/leaflet.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
<script src="{{ theme.rootUri() }}dist/pages/display-page.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
{% endblock %}

View File

@@ -0,0 +1,189 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Add Display Group" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Next" %}, displayGroupAddFormNext()
{% trans "Save" %}, $("#displayGroupAddForm").submit();
{% endblock %}
{% block formFieldActions %}
[{
"field": "isDynamic",
"trigger": "init",
"value": false,
"operation": "is:checked",
"actions": {
".dynamic-fields": { "display": "none" }
}
},{
"field": "isDynamic",
"trigger": "change",
"value": false,
"operation": "is:checked",
"actions": {
".dynamic-fields": { "display": "none" }
}
},{
"field": "isDynamic",
"trigger": "init",
"value": true,
"operation": "is:checked",
"actions": {
".dynamic-fields": { "display": "" }
}
},{
"field": "isDynamic",
"trigger": "change",
"value": true,
"operation": "is:checked",
"actions": {
".dynamic-fields": { "display": "" }
}
}]
{% endblock %}
{% block callBack %}displayGroupFormOpen{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#general" role="tab" data-toggle="tab"><span>{% trans "General" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#reference" role="tab" data-toggle="tab"><span>{% trans "Reference" %}</span></a></li>
</ul>
<form id="displayGroupAddForm" class="XiboForm form-horizontal displayGroupForm" method="post" action="{{ url_for("displayGroup.add") }}" data-gettag="{{ url_for("tag.getByName") }}">
<div class="tab-content">
<div class="tab-pane active" id="general">
{% if currentUser.featureEnabled('folder.view') %}
<div class="form-group row">
<label class="col-sm-2 control-label">{% trans "Folder" %}</label>
<div class="col-sm-10">
<button type="button" class="btn btn-info" id="select-folder-button" data-toggle="modal" data-target="#folder-tree-form-modal">{% trans "Select Folder" %}</button>
<span id="selectedFormFolder"></span>
</div>
</div>
{{ forms.hidden('folderId') }}
{% endif %}
{% set title %}{% trans "Name" %}{% endset %}
{% set helpText %}{% trans "The Name for this Display Group" %}{% endset %}
{{ forms.input("displayGroup", title, "", helpText) }}
{% set title %}{% trans "Description" %}{% endset %}
{% set helpText %}{% trans "A short description of this Display Group" %}{% endset %}
{{ forms.input("description", title, "", helpText) }}
{% if currentUser.featureEnabled("tag.tagging") %}
{% set title %}{% trans "Tags" %}{% endset %}
{% set helpText %}{% trans "Tags for this Display Group - Comma separated string of Tags or Tag|Value format. If you choose a Tag that has associated values, they will be shown for selection below." %}{% endset %}
{{ forms.inputWithTags("tags", title, "", helpText, 'tags-with-value') }}
<p id="loadingValues" style="margin-left: 17%"></p>
{% set title %}{% trans "Tag value" %}{% endset %}
{{ forms.dropdown("tagValue", "single", title, "", options, "key", "value") }}
<div id="tagValueContainer">
{% set title %}{% trans "Tag value" %}{% endset %}
{% set helpText %}{% trans "Please provide the value for this Tag and confirm by pressing enter on your keyboard." %}{% endset %}
{{ forms.input("tagValueInput", title, "", helpText) }}
</div>
<div id="tagValueRequired" class="alert alert-info">
<p>{% trans "This tag requires a set value, please select one from the Tag value dropdown or provide Tag value in the dedicated field." %}</p>
</div>
{% endif %}
{% set title %}{% trans "Dynamic Group?" %}{% endset %}
{% set helpText %}{% trans "Are the members of this group dynamic?" %}{% endset %}
{{ forms.checkbox("isDynamic", title, 0, helpText) }}
<div class="widget dynamic-fields">
<div class="widget-title">{% trans "Displays" %}</div>
<div class="widget-body">
<div class="FilterDiv card-body" id="Filter">
{% set title %}{% trans "Criteria" %}{% endset %}
{% set helpText %}{% trans "A comma separated set of regular expressions run against the Display name to determine membership." %}{% endset %}
{{ forms.inputWithLogicalOperator("dynamicCriteria", title, "", helpText, "dynamic-fields") }}
{% if currentUser.featureEnabled("tag.tagging") %}
{% set title %}{% trans "Criteria 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 set of tags run against the Display tag to determine membership." %}{% endset %}
{{ forms.inputWithTags("dynamicCriteriaTags", title, "", helpText, "dynamic-fields", null, null, "exactTags", exactTagTitle, logicalOperatorTitle) }}
{% endif %}
{{ forms.hidden("useRegexForName", 1) }}
</div>
<div class="XiboData card pt-3">
<table id="displayGroupDisplays" class="table table-striped">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Display" %}</th>
<th>{% trans "Tags" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Licence" %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="reference">
{{ forms.message("Add reference fields if needed"|trans) }}
{% set title %}{% trans "Reference 1" %}{% endset %}
{{ forms.input("ref1", title, displayGroup.ref1) }}
{% set title %}{% trans "Reference 2" %}{% endset %}
{{ forms.input("ref2", title, displayGroup.ref2) }}
{% set title %}{% trans "Reference 3" %}{% endset %}
{{ forms.input("ref3", title, displayGroup.ref3) }}
{% set title %}{% trans "Reference 4" %}{% endset %}
{{ forms.input("ref4", title, displayGroup.ref4) }}
{% set title %}{% trans "Reference 5" %}{% endset %}
{{ forms.input("ref5", title, displayGroup.ref5) }}
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Collect Now" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#displayGroupCollectNow").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayGroupCollectNow" class="XiboForm form-horizontal" method="post" action="{{ url_for("displayGroup.action.collectNow", {id: displayGroup.displayGroupId}) }}">
{% set message %}{% trans "Are you sure you want to request a collection to occur?" %}{% endset %}
{{ forms.message(message) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,46 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Send Command" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#displayGroupCommandForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayGroupCommandForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("displayGroup.action.command", {"id": displayGroup.displayGroupId}) }}">
{% set title %}{% trans "Command" %}{% endset %}
{% set helpText %}{% trans "Pick a command to send to the Player. If the CMS has XMR enabled this will be sent immediately, otherwise it will show an error." %}{% endset %}
{{ forms.dropdown("commandId", "single", title, "", commands, "commandId", "command", helpText) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,67 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% set name = displayGroup.displayGroup %}
{% trans %}Copy {{ name }}{% endtrans %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#displayGroupCopyForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayGroupCopyForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("displayGroup.copy", {id: displayGroup.displayGroupId}) }}">
{% set title %}{% trans "Name" %}{% endset %}
{% set helpText %}{% trans "The Name for this Display Profile" %}{% endset %}
{% set name %}{{ displayGroup.displayGroup }} 2{% endset %}
{{ forms.input("displayGroup", title, name, helpText,"","required") }}
{% set title %}{% trans "Description" %}{% endset %}
{% set helpText %}{% trans "The description for this Display Profile" %}{% endset %}
{% set description %}{{ displayGroup.description }}{% endset %}
{{ forms.input("description", title, description, helpText) }}
{% if displayGroup.isDynamic == 0 %}
{% set title %}{% trans "Copy Members?" %}{% endset %}
{% set helpText %}{% trans "Should we copy all members to the new Display Group?" %}{% endset %}
{{ forms.checkbox("copyMembers", title, 0, helpText) }}
{% endif %}
{% set title %}{% trans "Copy Assignments?" %}{% endset %}
{% set helpText %}{% trans "Should we copy all file and layout assignments to the new Display Group?" %}{% endset %}
{{ forms.checkbox("copyAssignments", title, 0, helpText) }}
{% set title %}{% trans "Copy Tags?" %}{% endset %}
{% set helpText %}{% trans "Should we copy all tags to the new Display Group?" %}{% endset %}
{{ forms.checkbox("copyTags", title, 0, helpText) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Delete Display Group" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Yes" %}, $("#displayGroupDeleteForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayGroupDeleteForm" class="XiboForm form-horizontal" method="delete" action="{{ url_for("displayGroup.delete", {"id": displayGroup.displayGroupId}) }}">
{% set message %}{% trans "Are you sure you want to delete this display group? This cannot be undone" %}{% endset %}
{{ forms.message(message) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,194 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Edit Display Group" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#displayGroupEditForm").submit()
{% endblock %}
{% block formFieldActions %}
[{
"field": "isDynamic",
"trigger": "init",
"value": false,
"operation": "is:checked",
"actions": {
".dynamic-fields": { "display": "none" }
}
},{
"field": "isDynamic",
"trigger": "change",
"value": false,
"operation": "is:checked",
"actions": {
".dynamic-fields": { "display": "none" }
}
},{
"field": "isDynamic",
"trigger": "init",
"value": true,
"operation": "is:checked",
"actions": {
".dynamic-fields": { "display": "" }
}
},{
"field": "isDynamic",
"trigger": "change",
"value": true,
"operation": "is:checked",
"actions": {
".dynamic-fields": { "display": "" }
}
}]
{% endblock %}
{% block callBack %}displayGroupFormOpen{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#general" role="tab" data-toggle="tab"><span>{% trans "General" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#reference" role="tab" data-toggle="tab"><span>{% trans "Reference" %}</span></a></li>
</ul>
<form id="displayGroupEditForm" class="XiboForm form-horizontal displayGroupForm" method="put" action="{{ url_for("displayGroup.edit", {"id": displayGroup.displayGroupId}) }}" data-gettag="{{ url_for("tag.getByName") }}">
<div class="tab-content">
<div class="tab-pane active" id="general">
{% if currentUser.featureEnabled('folder.view') %}
<div class="form-group row">
<label class="col-sm-2 control-label">{% trans "Current Folder" %}</label>
<div class="col-sm-10" style="padding-top: 7px">
<span id="originalFormFolder"></span>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 control-label">{% trans "Move to Selected Folder:" %}</label>
<div class="col-sm-10">
<button type="button" class="btn btn-info" id="select-folder-button" data-toggle="modal" data-target="#folder-tree-form-modal">{% trans "Select Folder" %}</button>
<span id="selectedFormFolder"></span>
</div>
</div>
{{ forms.hidden('folderId', displayGroup.folderId) }}
{% endif %}
{% set title %}{% trans "Name" %}{% endset %}
{% set helpText %}{% trans "The Name for this Display Group" %}{% endset %}
{{ forms.input("displayGroup", title, displayGroup.displayGroup , helpText) }}
{% set title %}{% trans "Description" %}{% endset %}
{% set helpText %}{% trans "A short description of this Display Group" %}{% endset %}
{{ forms.input("description", title, displayGroup.description, helpText) }}
{% if currentUser.featureEnabled("tag.tagging") %}
{% set title %}{% trans "Tags" %}{% endset %}
{% set helpText %}{% trans "Tags for this Display Group - Comma separated string of Tags or Tag|Value format. If you choose a Tag that has associated values, they will be shown for selection below." %}{% endset %}
{{ forms.inputWithTags("tags", title, displayGroup.getTagString(), helpText, 'tags-with-value') }}
<p id="loadingValues" style="margin-left: 17%"></p>
{% set title %}{% trans "Tag value" %}{% endset %}
{{ forms.dropdown("tagValue", "single", title, "", options, "key", "value") }}
<div id="tagValueContainer">
{% set title %}{% trans "Tag value" %}{% endset %}
{% set helpText %}{% trans "Please provide the value for this Tag and confirm by pressing enter on your keyboard." %}{% endset %}
{{ forms.input("tagValueInput", title, "", helpText) }}
</div>
<div id="tagValueRequired" class="alert alert-info">
<p>{% trans "This tag requires a set value, please select one from the Tag value dropdown or provide Tag value in the dedicated field." %}</p>
</div>
{% endif %}
{% set title %}{% trans "Dynamic Group?" %}{% endset %}
{% set helpText %}{% trans "Are the members of this group dynamic?" %}{% endset %}
{{ forms.checkbox("isDynamic", title, displayGroup.isDynamic, helpText) }}
<div class="widget dynamic-fields">
<div class="widget-title">{% trans "Displays" %}</div>
<div class="widget-body">
<div class="FilterDiv card-body" id="Filter">
{% set title %}{% trans "Criteria" %}{% endset %}
{% set helpText %}{% trans "A comma separated set of regular expressions run against the Display name to determine membership." %}{% endset %}
{{ forms.inputWithLogicalOperator("dynamicCriteria", title, displayGroup.dynamicCriteria, helpText, "dynamic-fields", "", "", displayGroup.dynamicCriteriaLogicalOperator) }}
{% if currentUser.featureEnabled("tag.tagging") %}
{% set title %}{% trans "Criteria 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 set of tags run against the Display tag to determine membership." %}{% endset %}
{{ forms.inputWithTags("dynamicCriteriaTags", title, displayGroup.dynamicCriteriaTags, helpText, "dynamic-fields", null, null, "exactTags", exactTagTitle, logicalOperatorTitle, displayGroup.dynamicCriteriaExactTags, displayGroup.dynamicCriteriaTagsLogicalOperator) }}
{% endif %}
{{ forms.hidden("useRegexForName", 1) }}
</div>
<div class="XiboData card pt-3">
<table id="displayGroupDisplays" class="table table-striped">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Display" %}</th>
<th>{% trans "Tags" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Licence" %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="reference">
{% set title %}{% trans "Reference 1" %}{% endset %}
{{ forms.input("ref1", title, displayGroup.ref1) }}
{% set title %}{% trans "Reference 2" %}{% endset %}
{{ forms.input("ref2", title, displayGroup.ref2) }}
{% set title %}{% trans "Reference 3" %}{% endset %}
{{ forms.input("ref3", title, displayGroup.ref3) }}
{% set title %}{% trans "Reference 4" %}{% endset %}
{{ forms.input("ref4", title, displayGroup.ref4) }}
{% set title %}{% trans "Reference 5" %}{% endset %}
{{ forms.input("ref5", title, displayGroup.ref5) }}
</div>
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,80 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% import "inline.twig" as inline %}
{% block formTitle %}
{% trans "Assign a Layout" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, forms.layoutAssignSubmit()
{% endblock %}
{% block callBack %}forms.layoutFormCallBack{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<div class="alert alert-warning">
{% trans "Assigning a Layout to a Display/DisplayGroup does NOT schedule that Layout to be shown. Please use the Schedule to show Layouts." %}
</div>
<div id="FileAssociationsAssign" class="card p-3 mb-3 bg-light" data-url="{{ url_for("displayGroup.assign.layout", {id: displayGroup.displayGroupId}) }}">
<div>
<ul id="FileAssociationsSortable">
{% for item in layouts %}
<li data-layout-id="{{ item.layoutId }}" class="btn btn-sm btn-white">{{ item.layout }}<span class="fa fa-minus"></span></li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="XiboGrid" id="{{ random() }}">
<div class="FilterDiv card-body">
<form onsubmit="false">
</form>
</div>
<div class="XiboData card pt-3">
<table id="layoutAssignments" data-url="{{ url_for("layout.search") }}" class="table table-striped" data-state-preference-name="campaignLayoutAssignGrid">
<thead>
<tr>
<th>{% trans "Name" %}</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,82 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% import "inline.twig" as inline %}
{% block formTitle %}
{% trans "Associate an item from the Library" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, forms.mediaAssignSubmit()
{% endblock %}
{% block callBack %}forms.mediaDisplayGroupFormCallBack{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<div id="FileAssociationsAssign" class="card p-3 mb-3 bg-light" data-url="{{ url_for("displayGroup.assign.media", {id: displayGroup.displayGroupId}) }}">
<div>
<ul id="FileAssociationsSortable">
{% for item in media %}
<li data-media-id="{{ item.id }}" class="btn btn-sm btn-white">{{ item.name }}<span class="fa fa-minus"></span></li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="XiboGrid" id="{{ random() }}">
<div class="XiboFilter card mb-3 bg-light">
<div class="FilterDiv card-body" id="Filter">
<form class="form-inline" id="displayForm">
{% set title %}{% trans "Name" %}{% endset %}
{{ inline.input("media", title) }}
{{ inline.dropdown("type", "single", "Type", "", [{type: null, module: ""}]|merge(modules), "type", "name") }}
</form>
</div>
</div>
<div class="XiboData card pt-3">
<table id="mediaAssignments" data-url="{{ url_for("library.search") }}" class="table table-striped">
<thead>
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Type" %}</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,147 @@
{#
/**
* Copyright (C) 2020-2023 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/>.
*/
#}
{% set randomId = random() %}
{% extends "form-base.twig" %}
{% import "forms.twig" as forms %}
{% import "inline.twig" as inline %}
{% block formTitle %}
{% set displayGroupName = displayGroup.displayGroup %}
{% trans %}Manage Membership for {{ displayGroupName }}{% endtrans %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, forms.membersFormSubmit("{{ randomId }}")
{% endblock %}
{% block callBack %}forms.membersFormOpen{% endblock %}
{% block extra %}{{ extra|json_encode|raw }}{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<div id="{{ randomId }}" class="div-horizontal controlDiv"
data-display="true"
data-display-get-url="{{ url_for("display.search") }}"
data-display-param="displayId"
data-display-param-unassign="unassignDisplayId"
data-display-url="{{ url_for("displayGroup.assign.display", {id: displayGroup.displayGroupId}) }}"
data-display-groups="true"
data-display-groups-get-url="{{ url_for("displayGroup.search") }}"
data-display-groups-param="displayGroupId"
data-display-groups-param-unassign="unassignDisplayGroupId"
data-display-groups-url="{{ url_for("displayGroup.assign.displayGroup", {id: displayGroup.displayGroupId}) }}"
>
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#displayTab" role="tab" data-toggle="tab"><span>{% trans "Displays" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#displayGroupTab" role="tab" data-toggle="tab"><span>{% trans "Display Groups" %}</span></a></li>
<li class="nav-item"><a class="nav-link" href="#treeTab" role="tab" data-toggle="tab"><span>{% trans "Relationship Tree" %}</span></a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="displayTab">
{% set helpText %}{% trans %}Check or un-check the options against each display to control whether they are a member or not.{% endtrans %}{% endset %}
{{ forms.message(helpText) }}
<div class="XiboGrid" id="{{ random() }}">
<div class="XiboFilter card mb-3 bg-light">
<div class="FilterDiv card-body" id="Filter">
<form class="form-inline" id="displayForm">
{% set title %}{% trans "Name" %}{% endset %}
{{ inline.input("display", title) }}
{% set title %}{% trans "Authorised" %}{% endset %}
{% set values = [{id: -1, value: ""}, {id: 1, value: "Yes"}, {id: 0, value: "No"}] %}
{{ inline.dropdown("authorised", "single", title, -1, values, "id", "value") }}
{{ inline.hidden("displayGroupIdMembers", displayGroup.displayGroupId) }}
</form>
</div>
</div>
<table id="displaysMembersTable" class="table table-bordered membersTable">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Display" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Logged In" %}</th>
<th>{% trans "Version" %}</th>
<th>{% trans "Member" %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div class="tab-pane" id="displayGroupTab">
{% set helpText %}{% trans %}Check or un-check the options against each display group to control whether they are a member or not.{% endtrans %}{% endset %}
{{ forms.message(helpText) }}
<div class="XiboGrid" id="{{ random() }}">
<div class="XiboFilter card mb-3 bg-light">
<div class="FilterDiv card-body" id="Filter">
<form class="form-inline" id="displayGroupForm">
{% set title %}{% trans "Name" %}{% endset %}
{{ inline.input("displayGroup", title) }}
{{ inline.hidden("displayGroupIdMembers", displayGroup.displayGroupId) }}
{{ forms.hidden("isDynamic", 0) }}
</form>
</div>
</div>
<table id="displaysGroupsMembersTable" class="table table-bordered membersTable" style="width: 100%">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Display Group" %}</th>
<th>{% trans "Member" %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div class="tab-pane" id="treeTab">
{% set helpText %}{% trans %}Below is the family tree for this Display Group.{% endtrans %}{% endset %}
{{ forms.message(helpText) }}
{% set helpText %}{% trans %}The Display Group being edited is in bold. The list is ordered so that items above the current Display Group are its ancestors and items below are its descendants.{% endtrans %}{% endset %}
{{ forms.message(helpText) }}
<ul>
{% for group in tree %}
{% if group.displayGroup == displayGroup.displayGroup %}
<li><strong>{{ group.displayGroup }}</strong></li>
{% else %}
<li><a class="XiboFormButton" href="{{ url_for("displayGroup.members.form", {"id": group.displayGroupId}) }}">{{ group.displayGroup }}</a></li>
{% endif %}
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,59 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% set name = displayGroup.displayGroup %}
{% trans %} Display Group {{ name }} {% endtrans %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#displayGroupSelectFolderForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayGroupSelectFolderForm" class="XiboForm form-horizontal" method="put" action="{{ url_for("displayGroup.selectfolder", {id: displayGroup.displayGroupId}) }}">
<div class="form-group row">
<label class="col-sm-2 control-label">{% trans "Current Folder" %}</label>
<div class="col-sm-10" style="padding-top: 7px">
<span id="originalFormFolder"></span>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 control-label">{% trans "Move to Selected Folder:" %}</label>
<div class="col-sm-10">
<div class="card p-3 mb-3 bg-light" id="container-folder-form-tree"></div>
</div>
</div>
{{ forms.hidden('folderId', displayGroup.folderId) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,46 @@
{#
/**
* Copyright (C) 2021 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 "Trigger a web hook" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#displayGroupTriggerWebhookForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayGroupTriggerWebhookForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("displayGroup.action.trigger.webhook", {id: displayGroup.displayGroupId}) }}">
{% set title = "Trigger Code"|trans %}
{% set helpText = "Enter the code associated with the web hook you wish to trigger. Please note that for this action to work, the webhook trigger code has to be added to Interactive Actions in scheduled content for this Player. "|trans %}
{{ forms.input("triggerCode", title, "", helpText) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,366 @@
{#
/**
* Copyright (C) 2020-2023 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 %}{{ "Display Groups"|trans }} | {% endblock %}
{% block actionMenu %}
<div class="widget-action-menu pull-right">
{% if currentUser.featureEnabled("displaygroup.add") %}
<button class="btn btn-success XiboFormButton" title="{% trans "Add a new Display Group" %}" href="{{ url_for("displayGroup.add.form") }}"> <i class="fa fa-desktop" aria-hidden="true"></i> {% trans "Add Display Group" %}</button>
{% endif %}
<button class="btn btn-primary" id="refreshGrid" title="{% trans "Refresh the Table" %}" href="#"><i class="fa fa-refresh" aria-hidden="true"></i></button>
</div>
{% endblock %}
{% block pageContent %}
<div class="widget">
<div class="widget-title">{% trans "Display Groups" %}</div>
<div class="widget-body">
<div class="XiboGrid" id="{{ random() }}" data-grid-name="displayGroupGridView">
<div class="XiboFilter card mb-3 bg-light">
<div class="FilterDiv card-body" id="Filter">
<form class="form-inline">
{% set title %}{% trans "ID" %}{% endset %}
{{ inline.input("displayGroupId", title) }}
{% set title %}{% trans "Name" %}{% endset %}
{{ inline.inputNameGrid('displayGroup', 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" },
{ name: "data-initial-key", value: "displayId" },
] %}
{% set helpText %}{% trans "Return Display Groups that directly contain the selected Display." %}{% endset %}
{{ inline.dropdown("displayId", "single", title, "", null, "displayId", "display", helpText, "pagedSelect", "", "", "", attributes) }}
{% set title %}{% trans "Nested Display" %}{% endset %}
{% set helpText %}{% trans "Return Display Groups that contain the selected Display somewhere in the nested Display Group relationship tree." %}{% endset %}
{{ inline.dropdown("nestedDisplayId", "single", title, "", null, "displayId", "display", helpText, "pagedSelect", "", "", "", attributes) }}
{% set title %}{% trans "Dynamic Criteria" %}{% endset %}
{{ inline.input("dynamicCriteria", title) }}
{% 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 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", exactTagTitle, logicalOperatorTitle) }}
{% endif %}
{{ inline.hidden("folderId") }}
</form>
</div>
</div>
<div class="grid-with-folders-container">
<div class="grid-folder-tree-container p-3" id="grid-folder-filter">
<input id="jstree-search" class="form-control" type="text" placeholder="{% trans "Search" %}">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="folder-tree-clear-selection-button">
<label class="form-check-label" for="folder-tree-clear-selection-button" title="{% trans "Search in all folders" %}">{% trans "All Folders" %}</label>
</div>
<div class="folder-search-no-results d-none">
<p>{% trans 'No Folders matching the search term' %}</p>
</div>
<div id="container-folder-tree"></div>
</div>
<div class="folder-controller d-none">
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-folder fa-1x"></i></button>
<div id="breadcrumbs" class="mt-2 pl-2"></div>
</div>
<div id="datatable-container">
<div class="XiboData card py-3">
<table id="displaygroups" class="table table-striped" data-content-type="displayGroup" data-content-id-name="displayGroupId" data-state-preference-name="displayGroupGrid" style="width: 100%;">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Name" %}</th>
<th>{% trans "Description" %}</th>
<th>{% trans "Is Dynamic?" %}</th>
<th>{% trans "Criteria" %}</th>
{% if currentUser.featureEnabled("tag.tagging") %}
<th>{% trans "Criteria Tags" %}</th>
<th>{% trans "Tags" %}</th>
{% endif %}
<th>{% trans "Sharing" %}</th>
<th>{% trans "Reference 1" %}</th>
<th>{% trans "Reference 2" %}</th>
<th>{% trans "Reference 3" %}</th>
<th>{% trans "Reference 4" %}</th>
<th>{% trans "Reference 5" %}</th>
<th>{% trans "Created Date" %}</th>
<th>{% trans "Modified Date" %}</th>
<th class="rowMenu"></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javaScript %}
<script type="text/javascript" nonce="{{ cspNonce }}">
var displayGroupTable;
var displayTable;
var criteria;
var criteriaTag;
var useRegexForName;
var exactTags;
var logicalOperator;
var logicalOperatorName;
$(document).ready(function() {
{% if not currentUser.featureEnabled("folder.view") %}
disableFolders();
{% endif %}
displayGroupTable = $("#displaygroups").DataTable({
"language": dataTablesLanguage,
dom: dataTablesTemplate,
serverSide: true,
stateSave: true,
stateDuration: 0,
responsive: true,
stateLoadCallback: dataTableStateLoadCallback,
stateSaveCallback: dataTableStateSaveCallback,
"filter": false,
searchDelay: 3000,
"order": [[ 1, "asc"]],
ajax: {
"url": "{{ url_for("displayGroup.search") }}",
"data": function(d) {
$.extend(d, $("#displaygroups").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
}
},
"columns": [
{ "data": "displayGroupId", responsivePriority: 2},
{ "data": "displayGroup", "render": dataTableSpacingPreformatted, responsivePriority: 2 },
{ "data": "description", responsivePriority: 3 },
{ "data": "isDynamic", "render": dataTableTickCrossColumn, responsivePriority: 3 },
{ "data": "dynamicCriteria", responsivePriority: 4 },
{% if currentUser.featureEnabled("tag.tagging") %}
{ "data": "dynamicCriteriaTags", responsivePriority: 4},
{
"name": "tags",
"sortable": false,
responsivePriority: 3,
"data": dataTableCreateTags
},
{% endif %}
{
"data": "groupsWithPermissions",
visible: false,
responsivePriority: 10,
"render": dataTableCreatePermissions
},
{ "data": "ref1", "visible": false, responsivePriority: 5},
{ "data": "ref2", "visible": false, responsivePriority: 5},
{ "data": "ref3", "visible": false, responsivePriority: 5},
{ "data": "ref4", "visible": false, responsivePriority: 5},
{ "data": "ref5", "visible": false, responsivePriority: 5},
{ "data": "createdDt", "visible": false, responsivePriority: 5 },
{ "data": "modifiedDt", "visible": false, responsivePriority: 5 },
{
"orderable": false,
responsivePriority: 1,
"data": dataTableButtonsColumn
}
]
});
$("#refreshGrid").click(function () {
displayGroupTable.ajax.reload();
});
displayGroupTable.on('draw', dataTableDraw);
displayGroupTable.on('draw', { form: $("#displaygroups").closest(".XiboGrid").find(".FilterDiv form") }, dataTableCreateTagEvents);
displayGroupTable.on('processing.dt', dataTableProcessing);
dataTableAddButtons(displayGroupTable, $('#displaygroups_wrapper').find('.dataTables_buttons'));
$("#refreshGrid").click(function () {
displayGroupTable.ajax.reload();
});
});
function setDeleteMultiSelectFormOpen(dialog) {
$(dialog).find('.save-button').prop('disabled', false);
var template = Handlebars.compile($('#template-display-group-multi-delete-checkbox').html());
var $input = $(template());
$input.find('input').on('change', function() {
$(dialog).find('.save-button').prop('disabled', !$(this).is(':checked'));
});
$(dialog).find('.modal-body').append($input);
}
function displayGroupAddFormNext() {
// Get form
var $form = $("#displayGroupAddForm");
// Set apply and apply reset data
$form.data("apply", true);
$form.data("applyCallback", 'applyResetCallback');
// Submit form
$form.submit();
}
function applyResetCallback(form) {
// Reset form fields
$(form).find('#displayGroup').val("");
}
function displayGroupFormOpen(dialog) {
displayTable = null;
$(dialog).find("input[name=dynamicCriteria]").on("keyup", _.debounce(function() {
displayGroupQueryDynamicMembers(dialog);
}, 500));
$(dialog).find("input[name=dynamicCriteriaTags], input[name=exactTags], select[name=logicalOperator], select[name=logicalOperatorName]").change(function() {
displayGroupQueryDynamicMembers(dialog);
});
var $form = $('#displayGroupAddForm');
// First time in there
displayGroupQueryDynamicMembers(dialog);
}
function displayGroupQueryDynamicMembers(dialog) {
if ($(dialog).find("input[name=isDynamic]")[0].checked) {
criteria = $(dialog).find("input[name=dynamicCriteria]").val();
criteriaTag = $(dialog).find("input[name=dynamicCriteriaTags]").val();
useRegexForName = $(dialog).find("input[name=useRegexForName]").val();
exactTags = $(dialog).find("input[name=exactTags]").is(':checked');
logicalOperator = $(dialog).find("select[name=logicalOperator]").val();
logicalOperatorName = $(dialog).find("select[name=logicalOperatorName]").val();
if (criteria === "" && criteriaTag === "") {
if (displayTable != null) {
displayTable.destroy();
displayTable = null;
$("#displayGroupDisplays tbody").empty();
}
return;
}
if (displayTable != null) {
displayTable.ajax.reload();
} else {
displayTable = $("#displayGroupDisplays").DataTable({
"language": dataTablesLanguage,
serverSide: true,
stateSave: true, stateDuration: 0,
filter: false,
searchDelay: 3000,
"order": [[1, "asc"]],
ajax: {
"url": "{{ url_for("display.search") }}",
"data": function (d) {
$.extend(
d,
{
display: criteria,
tags: criteriaTag,
useRegexForName: useRegexForName,
exactTags: exactTags,
logicalOperator: logicalOperator,
logicalOperatorName: logicalOperatorName
}
);
}
},
"columns": [
{"data": "displayId"},
{"data": "display"},
{"data": dataTableCreateTags},
{
"data": "mediaInventoryStatus",
"render": function (data, type, row) {
if (type != "display")
return data;
var icon = "";
if (data == 1)
icon = "fa-check";
else if (data == 0)
icon = "fa-times";
else
icon = "fa-cloud-download";
return "<span class='fa " + icon + "'></span>";
}
},
{"data": "licensed", "render": dataTableTickCrossColumn}
]
});
displayTable.on('processing.dt', dataTableProcessing);
displayTable.on('draw', { form: $(".displayGroupForm") }, dataTableCreateTagEvents);
}
}
}
</script>
{% endblock %}
{% block javaScriptTemplates %}
{{ parent() }}
{% verbatim %}
<script type="text/x-handlebars-template" id="template-display-group-multi-delete-checkbox">
<div class="form-group row">
<div class="offset-sm-2 col-sm-10 mt-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="checkbox-confirmDelete" name="confirmDelete">
<label class="form-check-label" for="checkbox-confirmDelete">
{% endverbatim %}{{ "Are you sure you want to delete?"|trans }}{% verbatim %}
</label>
</div>
<small class="form-text text-muted">{% endverbatim %}{{ "Check to confirm deletion of the selected records."|trans }}{% verbatim %}</small>
</div>
</div>
</script>
{% endverbatim %}
{% endblock %}

View File

@@ -0,0 +1,55 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Add Profile" %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#displayProfileAddForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayProfileAddForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("displayProfile.add") }}"
data-next-form-url="{{ url_for("displayProfile.edit.form", {id:':id'}) }}">
{% set title %}{% trans "Name" %}{% endset %}
{% set helpText %}{% trans "The Name for this Display Profile" %}{% endset %}
{{ forms.input("name", title, "", helpText,"","required") }}
{% set title %}{% trans "Display Type" %}{% endset %}
{% set helpText %}{% trans "What type of display is this profile intended for?" %}{% endset %}
{{ forms.dropdown("type", "single", title, "", types, "typeId","type", helpText) }}
{% set title %}{% trans "Default Profile?" %}{% endset %}
{% set helpText %}{% trans "Is this the default profile for all Displays of this type? Only 1 profile can be the default." %}{% endset %}
{{ forms.checkbox("isDefault", title, isDefault, helpText) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,48 @@
{#
/**
* Copyright (C) 2019 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 %}
{% set name = displayProfile.name %}
{% trans %}Copy {{ name }}{% endtrans %}
{% endblock %}
{% block formButtons %}
{% trans "Cancel" %}, XiboDialogClose()
{% trans "Save" %}, $("#displayProfileCopyForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayProfileCopyForm" class="XiboForm form-horizontal" method="post" action="{{ url_for("displayProfile.copy", {id: displayProfile.displayProfileId}) }}">
{% set title %}{% trans "Name" %}{% endset %}
{% set helpText %}{% trans "The Name for this Display Profile" %}{% endset %}
{% set name %}{{ displayProfile.name }} 2{% endset %}
{{ forms.input("name", title, name, helpText,"","required") }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{#
/**
* 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 "form-base.twig" %}
{% import "forms.twig" as forms %}
{% block formTitle %}
{% trans "Delete Profile" %}
{% endblock %}
{% block formButtons %}
{% trans "No" %}, XiboDialogClose()
{% trans "Yes" %}, $("#displayProfileDeleteForm").submit()
{% endblock %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<form id="displayProfileDeleteForm" class="XiboForm form-horizontal" method="delete" action="{{ url_for("displayProfile.delete", {id: displayProfile.displayProfileId}) }}">
{% set message %}{% trans "Are you sure you want to delete this display profile?" %}{% endset %}
{{ forms.message(message) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,344 @@
{#
/**
* 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/>.
*/
#}
{% import "forms.twig" as forms %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#general" role="tab" data-toggle="tab">{% trans "General" %}</a></li>
<li class="nav-item"><a class="nav-link" href="#network" role="tab" data-toggle="tab">{% trans "Network" %}</a></li>
<li class="nav-item"><a class="nav-link" href="#location" role="tab" data-toggle="tab">{% trans "Location" %}</a></li>
<li class="nav-item"><a class="nav-link" href="#troubleshooting" role="tab" data-toggle="tab">{% trans "Troubleshooting" %}</a></li>
<li class="nav-item"><a class="nav-link" href="#advanced" role="tab" data-toggle="tab">{% trans "Advanced" %}</a></li>
{% if commands|length > 0 %}
<li class="nav-item"><a class="nav-link" href="#commands" role="tab" data-toggle="tab">{% trans "Commands" %}</a></li>
{% endif %}
</ul>
<form id="displayProfileForm" class="XiboForm form-horizontal" method="put" action="{{ url_for("displayProfile.edit", {id: displayProfile.displayProfileId}) }}">
<div class="tab-content">
<div class="tab-pane active" id="general">
{{ include('displayprofile-form-edit-common-fields.twig') }}
{% set title = "Licence Code"|trans %}
{% set helpText = "Provide the Licence Code (formerly Licence email address) to license Players using this Display Profile."|trans %}
{{ forms.email("emailAddress", title, displayProfile.getSetting("emailAddress"), helpText) }}
{% set title = "Password Protect Settings"|trans %}
{% set helpText = "Provide a Password which will be required to access settings"|trans %}
{{ forms.input("settingsPassword", title, displayProfile.getSetting("settingsPassword"), helpText) }}
{% set title = "Collect interval"|trans %}
{% set helpText = "How often should the Player check for new content."|trans %}
{% set options = [
{ id: 60, value: "1 minute"|trans },
{ id: 300, value: "5 minutes"|trans },
{ id: 600, value: "10 minutes"|trans },
{ id: 1800, value: "30 minutes"|trans },
{ id: 3600, value: "1 hour"|trans },
{ id: 5400, value: "1 hour 30 minutes"|trans },
{ id: 7200, value: "2 hours"|trans },
{ id: 9000, value: "2 hours 30 minutes"|trans },
{ id: 10800, value: "3 hours"|trans },
{ id: 12600, value: "3 hours 30 minutes"|trans },
{ id: 14400, value: "4 hours"|trans },
{ id: 18000, value: "5 hours"|trans },
{ id: 21600, value: "6 hours"|trans },
{ id: 25200, value: "7 hours"|trans },
{ id: 28800, value: "8 hours"|trans },
{ id: 32400, value: "9 hours"|trans },
{ id: 36000, value: "10 hours"|trans },
{ id: 39600, value: "11 hours"|trans },
{ id: 43200, value: "12 hours"|trans },
{ id: 86400, value: "24 hours"|trans }
] %}
{{ forms.dropdown("collectInterval", "single", title, displayProfile.getSetting("collectInterval"), options, "id", "value", helpText) }}
{% set title = "XMR WebSocket Address"|trans %}
{% set helpText = "Override the CMS WebSocket address for XMR."|trans %}
{{ forms.input("xmrWebSocketAddress", title, displayProfile.getSetting("xmrWebSocketAddress"), helpText) }}
{% set title = "XMR Public Address"|trans %}
{% set helpText = "Override the CMS public address for XMR."|trans %}
{{ forms.input("xmrNetworkAddress", title, displayProfile.getSetting("xmrNetworkAddress"), helpText) }}
{% set title = "Enable stats reporting?"|trans %}
{% set helpText = "Should the application send proof of play stats to the CMS."|trans %}
{{ forms.checkbox("statsEnabled", title, displayProfile.getSetting("statsEnabled"), helpText) }}
{% set title = "Aggregation level"|trans %}
{% set helpText = "Set the level of collection for Proof of Play Statistics to be applied to selected Layouts / Media and Widget items."|trans %}
{% set options = [
{ id: 'Individual', value: "Individual"|trans },
{ id: 'Hourly', value: "Hourly"|trans },
{ id: 'Daily', value: "Daily"|trans },
] %}
{{ forms.dropdown("aggregationLevel", "single", title, displayProfile.getSetting("aggregationLevel"), options, "id", "value", helpText, "aggregation-level") }}
{% set title = "Record geolocation on each Proof of Play?"|trans %}
{% set helpText = "If the geolocation of the Display is known, enable to record that location against each proof of play record."|trans %}
{{ forms.checkbox("isRecordGeoLocationOnProofOfPlay", title, displayProfile.getSetting("isRecordGeoLocationOnProofOfPlay"), helpText) }}
{% set title = "Player Version"|trans %}
{% set helpText = "Set the Player Version to install, making sure that the selected version is suitable for your device"|trans %}
{% set attributes = [
{ name: "data-width", value: "300px" },
{ name: "data-allow-clear", value: "true" },
{ name: "data-placeholder--id", value: null },
{ name: "data-placeholder--value", value: "" },
{ name: "data-search-url", value: url_for("playersoftware.search") },
{ name: "data-search-term", value: "playerShowVersion" },
{ name: "data-id-property", value: "versionId" },
{ name: "data-text-property", value: "playerShowVersion" },
{ name: "data-filter-options", value: '{"playerType":"android"}' }
] %}
{{ forms.dropdown("versionMediaId", "single", title, displayProfile.getSetting("versionMediaId"), [{versionId:null, playerShowVersion:""}]|merge(versions), "versionId", "playerShowVersion", helpText, "pagedSelect", "", "", "", attributes) }}
</div>
<div class="tab-pane" id="network">
{% set title = "Download Window Start Time"|trans %}
{% set helpText = "The start of the time window to connect to the CMS and download updates."|trans %}
{{ forms.time("downloadStartWindow", title, displayProfile.getSetting("downloadStartWindow"), helpText, "", "", "","HH:mm") }}
{% set title = "Download Window End Time"|trans %}
{% set helpText = "The end of the time window to connect to the CMS and download updates."|trans %}
{{ forms.time("downloadEndWindow", title, displayProfile.getSetting("downloadEndWindow"), helpText, "", "", "","HH:mm") }}
{% set title = "Update Window Start Time"|trans %}
{% set helpText = "The start of the time window to install application updates."|trans %}
{{ forms.time("updateStartWindow", title, displayProfile.getSetting("updateStartWindow"), helpText, "", "", "","HH:mm") }}
{% set title = "Update Window End Time"|trans %}
{% set helpText = "The end of the time window to install application updates."|trans %}
{{ forms.time("updateEndWindow", title, displayProfile.getSetting("updateEndWindow"), helpText, "", "", "","HH:mm") }}
{% set title = "Force HTTPS?"|trans %}
{% set helpText = "Should Displays be forced to use HTTPS connection to the CMS?"|trans %}
{{ forms.checkbox("forceHttps", title, displayProfile.getSetting("forceHttps"), helpText) }}
{% set title = "Operating Hours"|trans %}
{% set helpText = "Select a day part that should act as operating hours for this display - email alerts will not be sent outside of operating hours"|trans %}
{% set attributes = [
{ name: "data-width", value: "300px" },
{ name: "data-allow-clear", value: "true" },
{ name: "data-placeholder--id", value: null },
{ name: "data-placeholder--value", value: "" },
{ name: "data-search-url", value: url_for("daypart.search") },
{ name: "data-search-term", value: "name" },
{ name: "data-id-property", value: "dayPartId" },
{ name: "data-text-property", value: "name" },
{ name: "data-filter-options", value: '{"isAlways":"0", "isCustom":"0"}' }
] %}
{{ forms.dropdown("dayPartId", "single", title, displayProfile.getSetting("dayPartId"), [{dayPartId:null, name:""}]|merge(dayParts), "dayPartId", "name", helpText, "pagedSelect", "", "", "", attributes) }}
{% set title = "Restart Wifi on connection failure?"|trans %}
{% set helpText = "If an attempted connection to the CMS fails 10 times in a row, restart the Wifi adaptor."|trans %}
{{ forms.checkbox("restartWifiOnConnectionFailure", title, displayProfile.getSetting("restartWifiOnConnectionFailure"), helpText) }}
</div>
<div class="tab-pane" id="location">
{% set title = "Orientation"|trans %}
{% set helpText = "Set the orientation of the device (portrait mode will only work if supported by the hardware) Application Restart Required."|trans %}
{% set options = [
{ id: -1, value: "Device Default"|trans },
{ id: 0, value: "Landscape"|trans },
{ id: 1, value: "Portrait"|trans },
{ id: 8, value: "Reverse Landscape"|trans },
{ id: 9, value: "Reverse Portrait"|trans }
] %}
{{ forms.dropdown("orientation", "single", title, displayProfile.getSetting("orientation"), options, "id", "value", helpText) }}
{% set title = "Screen Dimensions"|trans %}
{% set helpText = "Set dimensions to be used for the Player window ensuring that they do not exceed the actual screen size. Enter the following values representing the pixel sizings for; Top,Left,Width,Height. This requires a Player Restart to action."|trans %}
{{ forms.input("screenDimensions", title, displayProfile.getSetting("screenDimensions"), helpText) }}
</div>
<div class="tab-pane" id="troubleshooting">
{% set title = "Blacklist Videos?"|trans %}
{% set helpText = "Should Videos we fail to play be blacklisted and no longer attempted?"|trans %}
{{ forms.checkbox("blacklistVideo", title, displayProfile.getSetting("blacklistVideo"), helpText) }}
{% set title = "Store HTML resources on the Internal Storage?"|trans %}
{% set helpText = "Store all HTML resources on the Internal Storage? Should be selected if the device cannot display text, ticker, dataset media."|trans %}
{{ forms.checkbox("storeHtmlOnInternal", title, displayProfile.getSetting("storeHtmlOnInternal"), helpText) }}
{% set title = "Use a SurfaceView for Video Rendering?"|trans %}
{% set helpText = "If the device is having trouble playing video, it may be useful to switch to a Surface View for Video Rendering."|trans %}
{{ forms.checkbox("useSurfaceVideoView", title, displayProfile.getSetting("useSurfaceVideoView"), helpText) }}
{% set title = "Log Level"|trans %}
{% set helpText = "The resting logging level that should be recorded by the Player."|trans %}
{% set options = [
{ id: 'emergency', value: "Emergency"|trans },
{ id: 'alert', value: "Alert"|trans },
{ id: 'critical', value: "Critical"|trans },
{ id: 'error', value: "Error"|trans },
{ id: 'off', value: "Off"|trans }
] %}
{{ forms.dropdown("logLevel", "single", title, displayProfile.getSetting("logLevel"), options, "id", "value", helpText) }}
{% set title %}{% trans "Elevate Logging until" %}{% endset %}
{% set helpText %}{% trans "Elevate log level for the specified time. Should only be used if there is a problem with the display." %}{% endset %}
{% if displayProfile.isElevatedLogging() %}
{% set elevatedLogs = displayProfile.getUnmatchedProperty("elevateLogsUntilIso") %}
{% else %}
{% set elevatedLogs = "" %}
{% endif %}
{{ forms.dateTime("elevateLogsUntil", title, elevatedLogs, helpText) }}
</div>
<div class="tab-pane" id="advanced">
{% set title = "Start during device start up?"|trans %}
{% set helpText = "When the device starts and Android finishes loading, should the Player start up and come to the foreground?"|trans %}
{{ forms.checkbox("startOnBoot", title, displayProfile.getSetting("startOnBoot"), helpText) }}
{% set title = "Action Bar Mode"|trans %}
{% set helpText = "How should the action bar behave?"|trans %}
{% set options = [
{ id: 0, value: "Hide"|trans },
{ id: 1, value: "Timed"|trans },
{ id: 2, value: "Run Intent"|trans }
] %}
{{ forms.dropdown("actionBarMode", "single", title, displayProfile.getSetting("actionBarMode"), options, "id", "value", helpText) }}
{% set title = "Action Bar Display Duration"|trans %}
{% set helpText = "How long should the Action Bar be shown for, in seconds?"|trans %}
{{ forms.number("actionBarDisplayDuration", title, displayProfile.getSetting("actionBarDisplayDuration"), helpText, "", "", "", "", "0") }}
{% set title = "Action Bar Intent"|trans %}
{% set helpText = "When set to Run Intent, which intent should be run. Format is: Action|ExtraKey,ExtraMsg"|trans %}
{{ forms.input("actionBarIntent", title, displayProfile.getSetting("actionBarIntent"), helpText) }}
{% set title = "Automatic Restart"|trans %}
{% set helpText = "Automatically Restart the application if we detect it is not visible."|trans %}
{{ forms.checkbox("autoRestart", title, displayProfile.getSetting("autoRestart"), helpText) }}
{% set title = "Start delay for device start up"|trans %}
{% set helpText = "The number of seconds to wait before starting the application after the device has started. Minimum 10."|trans %}
{{ forms.number("startOnBootDelay", title, displayProfile.getSetting("startOnBootDelay"), helpText, "", "", "", "", "10") }}
{% if theme.getSetting('DISPLAY_PROFILE_CURRENT_LAYOUT_STATUS_ENABLED', 0) == 1 %}
{% set title = "Notify current layout"|trans %}
{% set helpText = "When enabled the Player will send the current layout to the CMS each time it changes. Warning: This is bandwidth intensive and should be disabled unless on a LAN."|trans %}
{{ forms.checkbox("sendCurrentLayoutAsStatusUpdate", title, displayProfile.getSetting("sendCurrentLayoutAsStatusUpdate"), helpText) }}
{% endif %}
{% set title = "Expire Modified Layouts?"|trans %}
{% set helpText = "Expire Modified Layouts immediately on change. This means a layout can be cut during playback if it receives an update from the CMS"|trans %}
{{ forms.checkbox("expireModifiedLayouts", title, displayProfile.getSetting("expireModifiedLayouts"), helpText) }}
{% if theme.getSetting('DISPLAY_PROFILE_SCREENSHOT_INTERVAL_ENABLED', 0) == 1 %}
{% set title = "Screen shot interval"|trans %}
{% set helpText = "The duration between status screen shots in minutes. 0 to disable. Warning: This is bandwidth intensive."|trans %}
{{ forms.number("screenShotRequestInterval", title, displayProfile.getSetting("screenShotRequestInterval"), helpText, "", "", "", "", "0") }}
{% endif %}
{% set title = "Action for Screen Shot Intent"|trans %}
{% set helpText = "The Intent Action to use for requesting a screen shot. Leave empty to natively create an image from the player screen content."|trans %}
{{ forms.input("screenShotIntent", title, displayProfile.getSetting("screenShotIntent"), helpText) }}
{% set title = "Screen Shot Size"|trans %}
{% set helpText = "The size of the largest dimension. Empty or 0 means the screen size."|trans %}
{{ forms.number("screenShotSize", title, displayProfile.getSetting("screenShotSize"), helpText, "", "", "", "", "0") }}
{% set title = "WebView Plugin State"|trans %}
{% set helpText = "What plugin state should be used when starting a web view."|trans %}
{% set options = [
{ id: 'OFF', value: "Off"|trans },
{ id: 'DEMAND', value: "On Demand"|trans },
{ id: 'ON', value: "On"|trans }
] %}
{{ forms.dropdown("webViewPluginState", "single", title, displayProfile.getSetting("webViewPluginState"), options, "id", "value", helpText) }}
{% set title = "Hardware Accelerate Web Content"|trans %}
{% set helpText = "Mode for hardware acceleration of web based content."|trans %}
{% set options = [
{ id: '0', value: "Off"|trans },
{ id: '2', value: "Off when transparent"|trans },
{ id: '1', value: "On"|trans }
] %}
{{ forms.dropdown("hardwareAccelerateWebViewMode", "single", title, displayProfile.getSetting("hardwareAccelerateWebViewMode"), options, "id", "value", helpText) }}
{% set title = "Use CMS time?"|trans %}
{% set helpText = "Set the device time using the CMS. Only available on rooted devices or system signed players."|trans %}
{{ forms.checkbox("timeSyncFromCms", title, displayProfile.getSetting("timeSyncFromCms"), helpText) }}
{% set title = "Enable caching of Web Resources?"|trans %}
{% set helpText = "The standard browser cache will be used - we recommend this is switched off unless specifically required. Effects Web Page and Embedded."|trans %}
{{ forms.checkbox("webCacheEnabled", title, displayProfile.getSetting("webCacheEnabled"), helpText) }}
{% set title = "Embedded Web Server Port"|trans %}
{% set helpText = "The port number to use for the embedded web server on the Player. Only change this if there is a port conflict reported on the status screen."|trans %}
{{ forms.number("serverPort", title, displayProfile.getSetting("serverPort"), helpText, "", "", "", "", "0") }}
{% set title = "Embedded Web Server allow WAN?"|trans %}
{% set helpText = "Should we allow access to the Player Embedded Web Server from WAN? You may need to adjust the device firewall to allow external traffic"|trans %}
{{ forms.checkbox("embeddedServerAllowWan", title, displayProfile.getSetting("embeddedServerAllowWan"), helpText) }}
{% set title = "Load Link Libraries for APK Update"|trans %}
{% set helpText = "Should the update command include dynamic link libraries? Only change this if your updates are failing."|trans %}
{{ forms.checkbox("installWithLoadedLinkLibraries", title, displayProfile.getSetting("installWithLoadedLinkLibraries"), helpText) }}
{% set title = "Use Multiple Video Decoders"|trans %}
{% set helpText = "Should the Player try to use Multiple Video Decoders when preparing and showing Video content."|trans %}
{% set options = [
{ id: "default", value: "Device Default"|trans },
{ id: "on", value: "On"|trans },
{ id: "off", value: "Off"|trans }
] %}
{{ forms.dropdown("isUseMultipleVideoDecoders", "single", title, displayProfile.getSetting("isUseMultipleVideoDecoders"), options, "id", "value", helpText) }}
{% set title = "Maximum Region Count"|trans %}
{% set helpText = "This setting is a memory limit protection setting which will stop rendering regions beyond the limit set. Leave at 0 for no limit."|trans %}
{{ forms.number("maxRegionCount", title, displayProfile.getSetting("maxRegionCount"), helpText, "", "", "", "", "0") }}
{% set title = "Video Engine"|trans %}
{% set helpText = "Select which video engine should be used to playback video. ExoPlayer is usually better, but if you experience issues you can revert back to Android Media Player. HLS always uses ExoPlayer. Available from v3 R300."|trans %}
{% set options = [
{ id: 'default', value: "Device Default"|trans },
{ id: 'exoplayer', value: "ExoPlayer"|trans },
{ id: 'mediaplayer', value: "Android Media Player"|trans },
] %}
{{ forms.dropdown("videoEngine", "single", title, displayProfile.getSetting("videoEngine"), options, "id", "value", helpText) }}
{% set title = "Enable touch capabilities on the device?"|trans %}
{% set helpText = "If this device will be used as a touch screen check this option. Checking this option will cause a message to appear on the player which needs to be manually dismissed once. If this option is disabled, touching the screen will show the action bar according to the Action Bar Mode option. Available from v3 R300."|trans %}
{{ forms.checkbox("isTouchEnabled", title, displayProfile.getSetting("isTouchEnabled"), helpText) }}
</div>
{% if commands|length > 0 %}
<div class="tab-pane" id="commands">
{% include "displayprofile-form-edit-command-fields.twig" %}
</div>
{% endif %}
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,186 @@
{#
/**
* Copyright (C) 2024 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/>.
*/
#}
{% import "forms.twig" as forms %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#general" role="tab" data-toggle="tab">{% trans "General" %}</a></li>
<li class="nav-item"><a class="nav-link" href="#advanced" role="tab" data-toggle="tab">{% trans "Advanced" %}</a></li>
{% if commands|length > 0 %}
<li class="nav-item"><a class="nav-link" href="#commands" role="tab" data-toggle="tab">{% trans "Commands" %}</a></li>
{% endif %}
</ul>
<form id="displayProfileForm" class="XiboForm form-horizontal" method="put" action="{{ url_for("displayProfile.edit", {id: displayProfile.displayProfileId}) }}">
<div class="tab-content">
<div class="tab-pane active" id="general">
{% include "displayprofile-form-edit-common-fields.twig" %}
{% set title = "Licence Code"|trans %}
{% set helpText = "Provide the Licence Code to license Players using this Display Profile."|trans %}
{{ forms.email("licenceCode", title, displayProfile.getSetting("licenceCode"), helpText) }}
{% set title = "Collect interval"|trans %}
{% set helpText = "How often should the Player check for new content."|trans %}
{% set options = [
{ id: 60, value: "1 minute"|trans },
{ id: 300, value: "5 minutes"|trans },
{ id: 600, value: "10 minutes"|trans },
{ id: 1800, value: "30 minutes"|trans },
{ id: 3600, value: "1 hour"|trans },
{ id: 5400, value: "1 hour 30 minutes"|trans },
{ id: 7200, value: "2 hours"|trans },
{ id: 9000, value: "2 hours 30 minutes"|trans },
{ id: 10800, value: "3 hours"|trans },
{ id: 12600, value: "3 hours 30 minutes"|trans },
{ id: 14400, value: "4 hours"|trans },
{ id: 18000, value: "5 hours"|trans },
{ id: 21600, value: "6 hours"|trans },
{ id: 25200, value: "7 hours"|trans },
{ id: 28800, value: "8 hours"|trans },
{ id: 32400, value: "9 hours"|trans },
{ id: 36000, value: "10 hours"|trans },
{ id: 39600, value: "11 hours"|trans },
{ id: 43200, value: "12 hours"|trans },
{ id: 86400, value: "24 hours"|trans }
] %}
{{ forms.dropdown("collectInterval", "single", title, displayProfile.getSetting("collectInterval"), options, "id", "value", helpText) }}
{% set title = "XMR WebSocket Address"|trans %}
{% set helpText = "Override the CMS WebSocket address for XMR."|trans %}
{{ forms.input("xmrWebSocketAddress", title, displayProfile.getSetting("xmrWebSocketAddress"), helpText) }}
{% set title = "XMR Public Address"|trans %}
{% set helpText = "Override the CMS public address for XMR."|trans %}
{{ forms.input("xmrNetworkAddress", title, displayProfile.getSetting("xmrNetworkAddress"), helpText) }}
{% set title = "Enable stats reporting?"|trans %}
{% set helpText = "Should the application send proof of play stats to the CMS."|trans %}
{{ forms.checkbox("statsEnabled", title, displayProfile.getSetting("statsEnabled"), helpText) }}
{% set title = "Aggregation level"|trans %}
{% set helpText = "Set the level of collection for Proof of Play Statistics to be applied to selected Layouts / Media and Widget items."|trans %}
{% set options = [
{ id: 'Individual', value: "Individual"|trans },
{ id: 'Hourly', value: "Hourly"|trans },
{ id: 'Daily', value: "Daily"|trans },
] %}
{{ forms.dropdown("aggregationLevel", "single", title, displayProfile.getSetting("aggregationLevel"), options, "id", "value", helpText, "aggregation-level") }}
{% set title = "Player Version"|trans %}
{% set helpText = "Set the Player Version to install, making sure that the selected version is suitable for your device"|trans %}
{% set attributes = [
{ name: "data-width", value: "300px" },
{ name: "data-allow-clear", value: "true" },
{ name: "data-placeholder--id", value: null },
{ name: "data-placeholder--value", value: "" },
{ name: "data-search-url", value: url_for("playersoftware.search") },
{ name: "data-search-term", value: "playerShowVersion" },
{ name: "data-id-property", value: "versionId" },
{ name: "data-text-property", value: "playerShowVersion" },
{ name: "data-filter-options", value: '{"playerType":"chromeOS"}' }
] %}
{{ forms.dropdown("playerVersionId", "single", title, displayProfile.getSetting("playerVersionId"), [{versionId:null, playerShowVersion:""}]|merge(versions), "versionId", "playerShowVersion", helpText, "pagedSelect", "", "", "", attributes) }}
{% set title = "Operating Hours"|trans %}
{% set helpText = "Select a day part that should act as operating hours for this display - email alerts will not be sent outside of operating hours"|trans %}
{% set attributes = [
{ name: "data-width", value: "300px" },
{ name: "data-allow-clear", value: "true" },
{ name: "data-placeholder--id", value: null },
{ name: "data-placeholder--value", value: "" },
{ name: "data-search-url", value: url_for("daypart.search") },
{ name: "data-search-term", value: "name" },
{ name: "data-id-property", value: "dayPartId" },
{ name: "data-text-property", value: "name" },
{ name: "data-filter-options", value: '{"isAlways":"0", "isCustom":"0"}' }
] %}
{{ forms.dropdown("dayPartId", "single", title, displayProfile.getSetting("dayPartId"), [{dayPartId:null, name:""}]|merge(dayParts), "dayPartId", "name", helpText, "pagedSelect", "", "", "", attributes) }}
</div>
<div class="tab-pane" id="advanced">
{% set title = "Log Level"|trans %}
{% set helpText = "The resting logging level that should be recorded by the Player."|trans %}
{% set options = [
{ id: 'emergency', value: "Emergency"|trans },
{ id: 'alert', value: "Alert"|trans },
{ id: 'critical', value: "Critical"|trans },
{ id: 'error', value: "Error"|trans },
{ id: 'off', value: "Off"|trans }
] %}
{{ forms.dropdown("logLevel", "single", title, displayProfile.getSetting("logLevel"), options, "id", "value", helpText) }}
{% set title %}{% trans "Elevate Logging until" %}{% endset %}
{% set helpText %}{% trans "Elevate log level for the specified time. Should only be used if there is a problem with the display." %}{% endset %}
{% if displayProfile.isElevatedLogging() %}
{% set elevatedLogs = displayProfile.getUnmatchedProperty("elevateLogsUntilIso") %}
{% else %}
{% set elevatedLogs = "" %}
{% endif %}
{{ forms.dateTime("elevateLogsUntil", title, elevatedLogs, helpText) }}
{% if theme.getSetting('DISPLAY_PROFILE_CURRENT_LAYOUT_STATUS_ENABLED', 0) == 1 %}
{% set title = "Notify current layout"|trans %}
{% set helpText = "When enabled the Player will send the current layout to the CMS each time it changes. Warning: This is bandwidth intensive and should be disabled unless on a LAN."|trans %}
{{ forms.checkbox("sendCurrentLayoutAsStatusUpdate", title, displayProfile.getSetting("sendCurrentLayoutAsStatusUpdate"), helpText) }}
{% endif %}
{% if theme.getSetting('DISPLAY_PROFILE_SCREENSHOT_INTERVAL_ENABLED', 0) == 1 %}
{% set title %}
{% trans "Screen shot interval"%}
{{ forms.playerCompat("", "", "R204+", "R208+", "") }}
{% endset %}
{% set helpText = "The duration between status screen shots in minutes. 0 to disable. Warning: This is bandwidth intensive."|trans %}
{{ forms.number("screenShotRequestInterval", title, displayProfile.getSetting("screenShotRequestInterval"), helpText, "", "", "", "", "0") }}
{% endif %}
{% set title = "Screen Shot Size"|trans %}
{% set helpText = "The size of the screenshot to return when requested."|trans %}
{% if displayProfile.type == "lg" %}
{% set options = [
{ id: 1, value: "Thumbnail"|trans },
{ id: 2, value: "HD"|trans },
{ id: 3, value: "FHD"|trans }
] %}
{% else %}
{% set options = [
{ id: 1, value: "Thumbnail"|trans },
{ id: 2, value: "Standard"|trans }
] %}
{% endif %}
{{ forms.dropdown("screenShotSize", "single", title, displayProfile.getSetting("screenShotSize"), options, "id", "value", helpText) }}
</div>
{% if commands|length > 0 %}
<div class="tab-pane" id="commands">
{% include "displayprofile-form-edit-command-fields.twig" %}
</div>
{% endif %}
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,80 @@
{#
/**
* 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/>.
*/
#}
{% import "forms.twig" as forms %}
{% block formHtml %}
<div class="accordion" id="accordion" role="tablist" aria-multiselectable="true">
{% for field in commands %}
<div class="card">
<div class="card-header" role="tab" id="heading{{ field.commandId }}">
<h4 class="card-title">
<a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapse{{ field.commandId }}"
aria-expanded="true" aria-controls="collapse{{ field.commandId }}">
{{ field.command }} | {{ field.code }}
</a>
</h4>
</div>
<div id="collapse{{ field.commandId }}" class="collapse"
role="tabpanel" aria-labelledby="heading{{ field.commandId }}" aria-expanded="false">
<div class="card-body">
{% set title %}{% trans "Name" %}{% endset %}
{{ forms.disabled("command", title, field.command, field.description) }}
{% if field.commandString != "" %}
{{ forms.disabled("", "", "This Command has a default Command String."|trans, field.commandString) }}
{% endif %}
{% set fieldId = "commandString_" ~ field.commandId %}
{% set title %}{% trans "Command" %}{% endset %}
{% set helpText %}{% trans "The Command String for this Command on this display" %}{% endset %}
{{ forms.input(fieldId, title, field.commandStringDisplayProfile, helpText, "XiboCommand") }}
{% if field.validationString != "" %}
{{ forms.disabled("", "", "This Command has a default Valildation String."|trans, field.validationString) }}
{% endif %}
{% set fieldId = "validationString_" ~ field.commandId %}
{% set title %}{% trans "Validation" %}{% endset %}
{% set helpText %}{% trans "The Validation String for this Command on this display" %}{% endset %}
{{ forms.input(fieldId, title, field.validationStringDisplayProfile, helpText) }}
{% if field.createAlertOn != "" %}
{{ forms.disabled("", "", "This Command has a default setting for creating alerts."|trans, field.createAlertOn) }}
{% endif %}
{% set fieldId = "createAlertOn_" ~ field.commandId %}
{% set options = [
{ optionid: "", option: "" },
{ optionid: "never", option: "Never" },
{ optionid: "success", option: "Success" },
{ optionid: "failure", option: "Failure" },
{ optionid: "always", option: "Always" },
] %}
{% set title %}{% trans "Create Alert On" %}{% endset %}
{% set helpText %}{% trans "On command execution, when should a Display alert be created?" %}{% endset %}
{{ forms.dropdown(fieldId, "single", title, field.createAlertOnDisplayProfile, options, "optionid", "option", helpText) }}
</div>
</div>
</div>
{% endfor %}
{% endblock %}

View File

@@ -0,0 +1,33 @@
{#
/**
* 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/>.
*/
#}
{% import "forms.twig" as forms %}
{% block formHtml %}
{% set title %}{% trans "Name" %}{% endset %}
{% set helpText %}{% trans "The Name of the Profile - (1 - 50 characters)" %}{% endset %}
{{ forms.input("name", title, displayProfile.name, helpText) }}
{% set title %}{% trans "Default Profile?" %}{% endset %}
{% set helpText %}{% trans "Is this the default profile for all Displays of this type? Only 1 profile can be the default." %}{% endset %}
{{ forms.checkbox("isDefault", title, displayProfile.isDefault, helpText) }}
{% endblock %}

View File

@@ -0,0 +1,385 @@
{#
/**
* 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/>.
*/
#}
<script type="text/javascript" nonce="{{ cspNonce }}">
/**
* Setup Display Profile setting forms
*/
function displayProfileFormOpen() {
// TIMERS
setupFormFields(
$('#timers-container'),
Handlebars.compile($('#template-timers').html()),
$('#timers-container').data('values'),
$('#timers-container').data('options'),
timersFormInit,
timersFormInit
);
// PICTURE OPTIONS
setupFormFields(
$('#picture-options-container'),
Handlebars.compile($('#template-picture-options').html()),
$('#picture-options-container').data('values'),
$('#picture-options-container').data('options'),
pictureOptionsFormInit,
pictureOptionsFormUpdate
);
// Android location field
setAndroidDimensions($('.tab-pane#location'));
// Popovers
$('[data-toggle="popover"]').popover();
}
/**
* Android dimension field
* @param {jquery} container
*/
function setAndroidDimensions($container) {
const $inputField = $container.find('#screenDimensions');
const template = Handlebars.compile($('#android-dimension-fields').html());
// Prevent to run if screen dimensions does not exist
if($inputField.length <= 0){
return;
}
let fieldsTemplate;
// Hide input
$inputField.attr('type', 'hidden');
// Parse value if exists and assign it to the elements
if($inputField.val() != "") {
let values = $inputField.val().split(',');
fieldsTemplate = template({
top: values[0],
left: values[1],
width: values[2],
height: values[3]
});
} else {
fieldsTemplate = template();
}
// Add fields to parent container
$inputField.parent().prepend(fieldsTemplate);
// Initialise values
$container.on('change', '.androidDimensionInput', function() {
// Get all separated values
var valueLeft = $container.find('#dimensionLeft').val();
var valueTop = $container.find('#dimensionTop').val();
var valueWidth = $container.find('#dimensionWidth').val();
var valueHeight = $container.find('#dimensionHeight').val();
// Set default colour
$('.androidDimensionInput').css('background-color', '#fff');
// Set value to hidden input
if(valueLeft != '' && valueTop != '' && valueWidth != '' && valueWidth >= 0 && valueHeight != '' && valueHeight >= 0) {
$inputField.val([valueLeft, valueTop, valueWidth, valueHeight].toString());
} else {
// Invalid value
$inputField.val('');
// If some of the elements aren't empty and it's invalid, show a specific background
if(valueLeft != '' || valueTop != '' || valueWidth != '' || valueHeight != ''){
$('.androidDimensionInput').css('background-color', '#ffce9c');
}
}
});
}
/**
* Setup form dynamic fields
* @param {jquery} container
* @param {hbstemplate} template
* @param {Object} values - Defined values
* @param {Object} options - All the options
*/
function setupFormFields(container, template, values, options, callbackFuncInit, callbackFuncUpdate) {
// Prevent forms to setup inexisting fields
if(container.length === 0) {
return;
}
if(Object.keys(values).length == 0) {
// Add a template row
var context = {
options: options,
index: 1,
name: "",
values: {},
buttonGlyph: "fa-plus"
};
container.append(template(context));
} else {
// For each of the existing codes, create form components
var i = 0;
$.each(values, function(index, values) {
i++;
var context = {
options: options,
index: i,
name: index,
values: values,
buttonGlyph: ((i == 1) ? "fa-plus" : "fa-minus")
};
container.append(template(context));
});
}
// Button click handle
container.on("click", "button", function(e) {
e.preventDefault();
// find the gylph
if($(this).find("i").hasClass("fa-plus")) {
var context = {
options: options,
index: container.find('.multiSelect:last').data('index') + 1,
name: "",
values: {},
buttonGlyph: "fa-minus"
};
// Append new object to container
container.append(template(context));
// Initialize form fields
callbackFuncUpdate(container, options, values);
} else {
// Remove this row
$(this).closest(".form-group").remove();
}
});
// Initialize form fields
callbackFuncInit(container, options, values);
}
/**
* Init/Update timePicker fields
*/
function timersFormInit(container) {
var customTimeFormat = 'HH:mm';
container.find('.input-group:not(.timerInit) .timePickerDisplayEditForm').each(function() {
if(calendarType == 'Jalali') {
initDatePicker(
$(this),
customTimeFormat,
customTimeFormat,
{
onlyTimePicker: true,
format: customTimeFormat,
timePicker: {
second: {
enabled: false
}
},
altFieldFormatter: function(unixTime) {
var newDate = moment.unix(unixTime / 1000);
newDate.set('second', 0);
return newDate.format(customTimeFormat);
}
}
);
} else {
initDatePicker(
$(this),
customTimeFormat,
customTimeFormat,
{
enableTime: true,
noCalendar: true,
time_24hr: true,
altFormat: customTimeFormat
}
);
}
$(this).parent().addClass('timerInit');
});
}
/**
* Initialise all form fields
*/
function pictureOptionsFormInit(container, options, values) {
container.find('.multiSelect').each(function() {
pictureOptionsFormChangeInputs($(this).val(), $(this).closest('.row'), options[$(this).val()], values);
});
container.on('change', '.multiSelect', function() {
pictureOptionsFormChangeInputs($(this).val(), $(this).closest('.row'), options[$(this).val()], values);
});
}
/**
* Update the last added field
*/
function pictureOptionsFormUpdate(container, options, values) {
container.find('.multiSelect:last').each(function() {
pictureOptionsFormChangeInputs($(this).val(), $(this).closest('.row'), options[$(this).val()], values);
});
}
/**
* Populate and create the slider
*/
function pictureOptionsFormChangeInputs(selProperty, row, propertyOptions, values) {
if(selProperty == undefined || selProperty == '') {
row.find('.multiSelectInputs').html(Handlebars.compile($('#template-picture-options-none').html())());
return;
}
// Add empty template to the container
var template = Handlebars.compile($('#template-picture-options-slider').html());
row.find('.multiSelectInputs').html(template({
index: row.find('.multiSelect').data('index')
}));
var propertyValue = values[selProperty];
var sliderOptions = propertyOptions.sliderOptions;
// Find respective index
if(typeof(propertyValue) == "string") {
for (var index = 0; index < sliderOptions.ticks_labels.length; index++) {
var label = sliderOptions.ticks_labels[index];
if(label == propertyValue) {
propertyValue = index;
}
}
}
// Set value to the options
sliderOptions.value = ((propertyValue != undefined) ? propertyValue : 0);
// Init slider
row.find('.pictureControlsSlider').bootstrapSlider(sliderOptions);
// Refresh on tab switch to fix broken labels
$('a[data-toggle="tab"]').on('shown.bs.tab', function() {
row.find('.pictureControlsSlider').bootstrapSlider('refresh', { useCurrentValue: true });
});
}
// Equals helper for the templates below
Handlebars.registerHelper('eq', function(v1, v2, opts) {
if (v1 === v2) {
return opts.fn(this);
} else {
return opts.inverse(this);
}
});
</script>
<style>
/*
* Slider width fix
*/
.slider:not(.scaled-slider) {
width: 100% !important;
}
</style>
{% verbatim %}
<script type="text/x-handlebars-template" id="template-timers">
<div class="form-group col-sm-12 row form-group-timers">
<div class="col-sm-1">
<button type="button" class="btn btn-white"><i class="fa {{ buttonGlyph }}"></i></button>
</div>
<div class="col-sm-5">
<select class="form-control multiSelect" name="timers[{{ index }}][day]" data-index="{{ index }}">
<option value=""></option>
{{#each options}}
<option value="{{ id }}" {{#eq id ../name}} selected{{/eq}}>{{ name }}</option>
{{/each}}
</select>
</div>
<div class="col-sm-3 multiSelectInputs">
<div class="input-group">
<input class="form-control timePickerDisplayEditForm timersOn" type="text" name="timers[{{ index }}][on]" id="timersOn{{ index }}" value="{{ values.on }}" />
<span class="input-group-addon date-clear-button d-none" role="button"><i class="fa fa-times"></i></span>
</div>
</div>
<div class="col-sm-3 multiSelectInputs">
<div class="input-group">
<input class="form-control timePickerDisplayEditForm timersOff" type="text" name="timers[{{ index }}][off]" id="timersOff{{ index }}" value="{{ values.off }}" />
<span class="input-group-addon date-clear-button d-none" role="button"><i class="fa fa-times"></i></span>
</div>
</div>
</div>
</script>
<script type="text/x-handlebars-template" id="template-picture-options">
<div class="form-group col-sm-12 row form-group-picture-options">
<div class="col-sm-1">
<button type="button" class="btn btn-white"><i class="fa {{ buttonGlyph }}"></i></button>
</div>
<div class="col-sm-5">
<select class="form-control multiSelect" name="pictureControls[{{ index }}][property]" data-index="{{ index }}">
<option value=""></option>
{{#each options}}
<option value="{{@key}}" {{#eq @key ../name}} selected{{/eq}}>{{ name }}</option>
{{/each}}
</select>
</div>
<div class="col-sm-6 multiSelectInputs">
{% endverbatim %}
<p class="btn btn-block btn-primary" disabled>{% trans "Select a property to display inputs" %}</p>
{% verbatim %}
</div>
</div>
</script>
<script type="text/x-handlebars-template" id="template-picture-options-slider">
<input type="text" class="pictureControlsSlider" name="pictureControls[{{ index }}][value]"/>
</script>
<script type="text/x-handlebars-template" id="template-picture-options-none">
{% endverbatim %}
<p class="btn btn-block btn-primary" disabled>{% trans "Select a property to display inputs" %}</p>
{% verbatim %}
</script>
<script type="text/x-handlebars-template" id="android-dimension-fields">
<div class="row">
<input type="number" value="{{ top }}" class="col-3 form-control androidDimensionInput" name="dimensionTop" id="dimensionTop" placeholder="{% endverbatim %}{{ "Top"|trans }}{% verbatim %}"></input>
<input type="number" value="{{ left }}" class="col-3 form-control androidDimensionInput" name="dimensionLeft" id="dimensionLeft" placeholder="{% endverbatim %}{{ "Left"|trans }}{% verbatim %}"></input>
<input type="number" value="{{ width }}" class="col-3 form-control androidDimensionInput" min="0" name="dimensionWidth" id="dimensionWidth" placeholder="{% endverbatim %}{{ "Width"|trans }}{% verbatim %}"></input>
<input type="number" value="{{ height }}" class="col-3 form-control androidDimensionInput" min="0" name="dimensionHeight" id="dimensionHeight" placeholder="{% endverbatim %}{{ "Height"|trans }}{% verbatim %}"></input>
<div class="d-flex validation-error-container flex-column p-1 m-0 text-left"></div>
</div>
</script>
{% endverbatim %}

View File

@@ -0,0 +1,228 @@
{#
/**
* 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/>.
*/
#}
{% import "forms.twig" as forms %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#general" role="tab" data-toggle="tab">{% trans "General" %}</a></li>
<li class="nav-item"><a class="nav-link" href="#network" role="tab" data-toggle="tab">{% trans "Network" %}</a></li>
<li class="nav-item"><a class="nav-link" href="#location" role="tab" data-toggle="tab">{% trans "Location" %}</a></li>
<li class="nav-item"><a class="nav-link" href="#troubleshooting" role="tab" data-toggle="tab">{% trans "Troubleshooting" %}</a></li>
<li class="nav-item"><a class="nav-link" href="#advanced" role="tab" data-toggle="tab">{% trans "Advanced" %}</a></li>
{% if commands|length > 0 %}
<li class="nav-item"><a class="nav-link" href="#commands" role="tab" data-toggle="tab">{% trans "Commands" %}</a></li>
{% endif %}
</ul>
<form id="displayProfileForm" class="XiboForm form-horizontal" method="put" action="{{ url_for("displayProfile.edit", {id: displayProfile.displayProfileId}) }}">
<div class="tab-content">
<div class="tab-pane active" id="general">
{% include "displayprofile-form-edit-common-fields.twig" %}
{% set title = "Collect interval"|trans %}
{% set helpText = "How often should the Player check for new content."|trans %}
{% set options = [
{ id: 60, value: "1 minute"|trans },
{ id: 300, value: "5 minutes"|trans },
{ id: 600, value: "10 minutes"|trans },
{ id: 1800, value: "30 minutes"|trans },
{ id: 3600, value: "1 hour"|trans },
{ id: 5400, value: "1 hour 30 minutes"|trans },
{ id: 7200, value: "2 hours"|trans },
{ id: 9000, value: "2 hours 30 minutes"|trans },
{ id: 10800, value: "3 hours"|trans },
{ id: 12600, value: "3 hours 30 minutes"|trans },
{ id: 14400, value: "4 hours"|trans },
{ id: 18000, value: "5 hours"|trans },
{ id: 21600, value: "6 hours"|trans },
{ id: 25200, value: "7 hours"|trans },
{ id: 28800, value: "8 hours"|trans },
{ id: 32400, value: "9 hours"|trans },
{ id: 36000, value: "10 hours"|trans },
{ id: 39600, value: "11 hours"|trans },
{ id: 43200, value: "12 hours"|trans },
{ id: 86400, value: "24 hours"|trans }
] %}
{{ forms.dropdown("collectInterval", "single", title, displayProfile.getSetting("collectInterval"), options, "id", "value", helpText) }}
{% set title = "XMR WebSocket Address"|trans %}
{% set helpText = "Please enter the WebSocket address for XMR."|trans %}
{{ forms.input("xmrWebSocketAddress", title, displayProfile.getSetting("xmrWebSocketAddress"), helpText) }}
{% set title = "XMR Public Address"|trans %}
{% set helpText = "Please enter the public address for XMR."|trans %}
{{ forms.input("xmrNetworkAddress", title, displayProfile.getSetting("xmrNetworkAddress"), helpText) }}
{% set title = "Enable stats reporting?"|trans %}
{% set helpText = "Should the application send proof of play stats to the CMS."|trans %}
{{ forms.checkbox("statsEnabled", title, displayProfile.getSetting("statsEnabled"), helpText) }}
{% set title = "Aggregation level"|trans %}
{% set helpText = "Set the level of collection for Proof of Play Statistics to be applied to selected Layouts / Media and Widget items."|trans %}
{% set options = [
{ id: 'Individual', value: "Individual"|trans },
{ id: 'Hourly', value: "Hourly"|trans },
{ id: 'Daily', value: "Daily"|trans },
] %}
{{ forms.dropdown("aggregationLevel", "single", title, displayProfile.getSetting("aggregationLevel"), options, "id", "value", helpText, "aggregation-level") }}
</div>
<div class="tab-pane" id="network">
{% set title = "Download Window Start Time"|trans %}
{% set helpText = "The start of the time window to connect to the CMS and download updates."|trans %}
{{ forms.time("downloadStartWindow", title, displayProfile.getSetting("downloadStartWindow"), helpText, "", "", "","HH:mm") }}
{% set title = "Download Window End Time"|trans %}
{% set helpText = "The end of the time window to connect to the CMS and download updates."|trans %}
{{ forms.time("downloadEndWindow", title, displayProfile.getSetting("downloadEndWindow"), helpText, "", "", "","HH:mm") }}
{% set title = "Force HTTPS?"|trans %}
{% set helpText = "Should Displays be forced to use HTTPS connection to the CMS?"|trans %}
{{ forms.checkbox("forceHttps", title, displayProfile.getSetting("forceHttps"), helpText) }}
{% set title = "Operating Hours"|trans %}
{% set helpText = "Select a day part that should act as operating hours for this display - email alerts will not be sent outside of operating hours"|trans %}
{% set attributes = [
{ name: "data-width", value: "300px" },
{ name: "data-allow-clear", value: "true" },
{ name: "data-placeholder--id", value: null },
{ name: "data-placeholder--value", value: "" },
{ name: "data-search-url", value: url_for("daypart.search") },
{ name: "data-search-term", value: "name" },
{ name: "data-id-property", value: "dayPartId" },
{ name: "data-text-property", value: "name" },
{ name: "data-filter-options", value: '{"isAlways":"0", "isCustom":"0"}' }
] %}
{{ forms.dropdown("dayPartId", "single", title, displayProfile.getSetting("dayPartId"), [{dayPartId:null, name:""}]|merge(dayParts), "dayPartId", "name", helpText, "pagedSelect", "", "", "", attributes) }}
</div>
<div class="tab-pane" id="location">
{% set title = "Width"|trans %}
{% set helpText = "The Width of the Display Window. 0 means full width."|trans %}
{{ forms.number("sizeX", title, displayProfile.getSetting("sizeX"), helpText, "", "", "", "", "0") }}
{% set title = "Height"|trans %}
{% set helpText = "The Height of the Display Window. 0 means full height."|trans %}
{{ forms.number("sizeY", title, displayProfile.getSetting("sizeY"), helpText, "", "", "", "", "0") }}
{% set title = "Left Coordinate"|trans %}
{% set helpText = "The left pixel position the display window should be sized from."|trans %}
{{ forms.number("offsetX", title, displayProfile.getSetting("offsetX"), helpText) }}
{% set title = "Top Coordinate"|trans %}
{% set helpText = "The top pixel position the display window should be sized from."|trans %}
{{ forms.number("offsetY", title, displayProfile.getSetting("offsetY"), helpText) }}
</div>
<div class="tab-pane" id="troubleshooting">
{% set title = "Log Level"|trans %}
{% set helpText = "The resting logging level that should be recorded by the Player."|trans %}
{% set options = [
{ id: 'emergency', value: "Emergency"|trans },
{ id: 'alert', value: "Alert"|trans },
{ id: 'critical', value: "Critical"|trans },
{ id: 'error', value: "Error"|trans },
{ id: 'off', value: "Off"|trans }
] %}
{{ forms.dropdown("logLevel", "single", title, displayProfile.getSetting("logLevel"), options, "id", "value", helpText) }}
{% set title %}{% trans "Elevate Logging until" %}{% endset %}
{% set helpText %}{% trans "Elevate log level for the specified time. Should only be used if there is a problem with the display." %}{% endset %}
{% if displayProfile.isElevatedLogging() %}
{% set elevatedLogs = displayProfile.getUnmatchedProperty("elevateLogsUntilIso") %}
{% else %}
{% set elevatedLogs = "" %}
{% endif %}
{{ forms.dateTime("elevateLogsUntil", title, elevatedLogs, helpText) }}
</div>
<div class="tab-pane" id="advanced">
{% set title = "Enable Shell Commands"|trans %}
{% set helpText = "Enable the Shell Command module."|trans %}
{{ forms.checkbox("enableShellCommands", title, displayProfile.getSetting("enableShellCommands"), helpText) }}
{% if theme.getSetting('DISPLAY_PROFILE_CURRENT_LAYOUT_STATUS_ENABLED', 0) == 1 %}
{% set title = "Notify current layout"|trans %}
{% set helpText = "When enabled the Player will send the current layout to the CMS each time it changes. Warning: This is bandwidth intensive and should be disabled unless on a LAN."|trans %}
{{ forms.checkbox("sendCurrentLayoutAsStatusUpdate", title, displayProfile.getSetting("sendCurrentLayoutAsStatusUpdate"), helpText) }}
{% endif %}
{% set title = "Expire Modified Layouts?"|trans %}
{% set helpText = "Expire Modified Layouts immediately on change. This means a layout can be cut during playback if it receives an update from the CMS"|trans %}
{{ forms.checkbox("expireModifiedLayouts", title, displayProfile.getSetting("expireModifiedLayouts"), helpText) }}
{% set title = "Maximum concurrent downloads"|trans %}
{% set helpText = "The maximum number of concurrent downloads the Player will attempt."|trans %}
{{ forms.number("maxConcurrentDownloads", title, displayProfile.getSetting("maxConcurrentDownloads"), helpText, "", "", "", "", "0") }}
{% set title = "Shell Command Allow List"|trans %}
{% set helpText = "Which shell commands should the Player execute?"|trans %}
{{ forms.input("shellCommandAllowList", title, displayProfile.getSetting("shellCommandAllowList"), helpText) }}
{% if theme.getSetting('DISPLAY_PROFILE_SCREENSHOT_INTERVAL_ENABLED', 0) == 1 %}
{% set title = "Screen shot interval"|trans %}
{% set helpText = "The duration between status screen shots in minutes. 0 to disable. Warning: This is bandwidth intensive."|trans %}
{{ forms.number("screenShotRequestInterval", title, displayProfile.getSetting("screenShotRequestInterval"), helpText, "", "", "", "", "0") }}
{% endif %}
{% set title = "Screen Shot Size"|trans %}
{% set helpText = "The size of the largest dimension. Empty or 0 means the screen size."|trans %}
{{ forms.input("screenShotSize", title, displayProfile.getSetting("screenShotSize"), helpText) }}
{% set title = "Limit the number of log files uploaded concurrently"|trans %}
{% set helpText = "The number of log files to upload concurrently. The lower the number the longer it will take, but the better for memory usage."|trans %}
{{ forms.number("maxLogFileUploads", title, displayProfile.getSetting("maxLogFileUploads"), helpText, "", "", "", "", "0") }}
{% set title = "Embedded Web Server Port"|trans %}
{% set helpText = "The port number to use for the embedded web server on the Player. Only change this if there is a port conflict reported on the status screen."|trans %}
{{ forms.number("embeddedServerPort", title, displayProfile.getSetting("embeddedServerPort"), helpText, "", "", "", "", "0") }}
{% set title = "Embedded Web Server allow WAN?"|trans %}
{% set helpText = "Should we allow access to the Player Embedded Web Server from WAN? You may need to adjust the device firewall to allow external traffic"|trans %}
{{ forms.checkbox("embeddedServerAllowWan", title, displayProfile.getSetting("embeddedServerAllowWan"), helpText) }}
{% set title = "Prevent Sleep?"|trans %}
{% set helpText = "Stop the player PC power management from Sleeping the PC"|trans %}
{{ forms.checkbox("preventSleep", title, displayProfile.getSetting("preventSleep"), helpText) }}
</div>
{% if commands|length > 0 %}
<div class="tab-pane" id="commands">
{% include "displayprofile-form-edit-command-fields.twig" %}
</div>
{% endif %}
</div>
</form>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,426 @@
{#
/**
* 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/>.
*/
#}
{% import "forms.twig" as forms %}
{% block formHtml %}
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a class="nav-link active" href="#general" role="tab" data-toggle="tab">{% trans "General" %}</a></li>
<li class="nav-item"><a class="nav-link" href="#timers" role="tab" data-toggle="tab">{% trans "On/Off Timers" %}</a></li>
<li class="nav-item"><a class="nav-link" href="#pictureOptions" role="tab" data-toggle="tab">{% trans "Picture Settings" %}</a></li>
<li class="nav-item"><a class="nav-link" href="#lockOptions" role="tab" data-toggle="tab">{% trans "Lock Settings" %}</a></li>
<li class="nav-item"><a class="nav-link" href="#advanced" role="tab" data-toggle="tab">{% trans "Advanced" %}</a></li>
{% if commands|length > 0 %}
<li class="nav-item"><a class="nav-link" href="#commands" role="tab" data-toggle="tab">{% trans "Commands" %}</a></li>
{% endif %}
</ul>
<form id="displayProfileForm" class="XiboForm form-horizontal" method="put" action="{{ url_for("displayProfile.edit", {id: displayProfile.displayProfileId}) }}">
<div class="tab-content">
<div class="tab-pane active" id="general">
{% include "displayprofile-form-edit-common-fields.twig" %}
{% set title = "Licence Code"|trans %}
{% set helpText = "Provide the Licence Code (formerly Licence email address) to license Players using this Display Profile."|trans %}
{{ forms.email("emailAddress", title, displayProfile.getSetting("emailAddress"), helpText) }}
{% set title = "Collect interval"|trans %}
{% set helpText = "How often should the Player check for new content."|trans %}
{% set options = [
{ id: 60, value: "1 minute"|trans },
{ id: 300, value: "5 minutes"|trans },
{ id: 600, value: "10 minutes"|trans },
{ id: 1800, value: "30 minutes"|trans },
{ id: 3600, value: "1 hour"|trans },
{ id: 5400, value: "1 hour 30 minutes"|trans },
{ id: 7200, value: "2 hours"|trans },
{ id: 9000, value: "2 hours 30 minutes"|trans },
{ id: 10800, value: "3 hours"|trans },
{ id: 12600, value: "3 hours 30 minutes"|trans },
{ id: 14400, value: "4 hours"|trans },
{ id: 18000, value: "5 hours"|trans },
{ id: 21600, value: "6 hours"|trans },
{ id: 25200, value: "7 hours"|trans },
{ id: 28800, value: "8 hours"|trans },
{ id: 32400, value: "9 hours"|trans },
{ id: 36000, value: "10 hours"|trans },
{ id: 39600, value: "11 hours"|trans },
{ id: 43200, value: "12 hours"|trans },
{ id: 86400, value: "24 hours"|trans }
] %}
{{ forms.dropdown("collectInterval", "single", title, displayProfile.getSetting("collectInterval"), options, "id", "value", helpText) }}
{% set title = "XMR WebSocket Address"|trans %}
{% set helpText = "Override the CMS WebSocket address for XMR."|trans %}
{{ forms.input("xmrWebSocketAddress", title, displayProfile.getSetting("xmrWebSocketAddress"), helpText) }}
{% set title = "XMR Public Address"|trans %}
{% set helpText = "Override the CMS public address for XMR."|trans %}
{{ forms.input("xmrNetworkAddress", title, displayProfile.getSetting("xmrNetworkAddress"), helpText) }}
{% set title = "Enable stats reporting?"|trans %}
{% set helpText = "Should the application send proof of play stats to the CMS."|trans %}
{{ forms.checkbox("statsEnabled", title, displayProfile.getSetting("statsEnabled"), helpText) }}
{% set title = "Aggregation level"|trans %}
{% set helpText = "Set the level of collection for Proof of Play Statistics to be applied to selected Layouts / Media and Widget items."|trans %}
{% set options = [
{ id: 'Individual', value: "Individual"|trans },
{ id: 'Hourly', value: "Hourly"|trans },
{ id: 'Daily', value: "Daily"|trans },
] %}
{{ forms.dropdown("aggregationLevel", "single", title, displayProfile.getSetting("aggregationLevel"), options, "id", "value", helpText, "aggregation-level") }}
{% set title = "Player Version"|trans %}
{% set helpText = "Set the Player Version to install, making sure that the selected version is suitable for your device"|trans %}
{% if displayProfile.type == "lg" %}
{% set attributes = [
{ name: "data-width", value: "300px" },
{ name: "data-allow-clear", value: "true" },
{ name: "data-placeholder--id", value: null },
{ name: "data-placeholder--value", value: "" },
{ name: "data-search-url", value: url_for("playersoftware.search") },
{ name: "data-search-term", value: "playerShowVersion" },
{ name: "data-id-property", value: "versionId" },
{ name: "data-text-property", value: "playerShowVersion" },
{ name: "data-filter-options", value: '{"playerType":"lg"}' }
] %}
{% endif %}
{% if displayProfile.type == "sssp" %}
{% set attributes = [
{ name: "data-width", value: "300px" },
{ name: "data-allow-clear", value: "true" },
{ name: "data-placeholder--id", value: null },
{ name: "data-placeholder--value", value: "" },
{ name: "data-search-url", value: url_for("playersoftware.search") },
{ name: "data-search-term", value: "playerShowVersion" },
{ name: "data-id-property", value: "versionId" },
{ name: "data-text-property", value: "playerShowVersion" },
{ name: "data-filter-options", value: '{"playerType":"sssp"}' }
] %}
{% endif %}
{{ forms.dropdown("versionMediaId", "single", title, displayProfile.getSetting("versionMediaId"), [{versionId:null, playerShowVersion:""}]|merge(versions), "versionId", "playerShowVersion", helpText, "pagedSelect", "", "", "", attributes) }}
{% set title = "Orientation"|trans %}
{% set helpText = "Set the orientation of the device (portrait mode will only work if supported by the hardware) Application Restart Required."|trans %}
{% set options = [
{ id: 0, value: "Landscape"|trans },
{ id: 1, value: "Portrait"|trans },
{ id: 8, value: "Reverse Landscape"|trans },
{ id: 9, value: "Reverse Portrait"|trans }
] %}
{{ forms.dropdown("orientation", "single", title, displayProfile.getSetting("orientation"), options, "id", "value", helpText) }}
{% set title = "Download Window Start Time"|trans %}
{% set helpText = "The start of the time window to connect to the CMS and download updates."|trans %}
{{ forms.time("downloadStartWindow", title, displayProfile.getSetting("downloadStartWindow"), helpText, "", "", "","HH:mm") }}
{% set title = "Download Window End Time"|trans %}
{% set helpText = "The end of the time window to connect to the CMS and download updates."|trans %}
{{ forms.time("downloadEndWindow", title, displayProfile.getSetting("downloadEndWindow"), helpText, "", "", "","HH:mm") }}
{% set title = "Update Window Start Time"|trans %}
{% set helpText = "The start of the time window to install application updates."|trans %}
{{ forms.time("updateStartWindow", title, displayProfile.getSetting("updateStartWindow"), helpText, "", "", "","HH:mm") }}
{% set title = "Update Window End Time"|trans %}
{% set helpText = "The end of the time window to install application updates."|trans %}
{{ forms.time("updateEndWindow", title, displayProfile.getSetting("updateEndWindow"), helpText, "", "", "","HH:mm") }}
{% set title = "Force HTTPS?"|trans %}
{% set helpText = "Should Displays be forced to use HTTPS connection to the CMS?"|trans %}
{{ forms.checkbox("forceHttps", title, displayProfile.getSetting("forceHttps"), helpText) }}
{% set title = "Operating Hours"|trans %}
{% set helpText = "Select a day part that should act as operating hours for this display - email alerts will not be sent outside of operating hours"|trans %}
{% set attributes = [
{ name: "data-width", value: "300px" },
{ name: "data-allow-clear", value: "true" },
{ name: "data-placeholder--id", value: null },
{ name: "data-placeholder--value", value: "" },
{ name: "data-search-url", value: url_for("daypart.search") },
{ name: "data-search-term", value: "name" },
{ name: "data-id-property", value: "dayPartId" },
{ name: "data-text-property", value: "name" },
{ name: "data-filter-options", value: '{"isAlways":"0", "isCustom":"0"}' }
] %}
{{ forms.dropdown("dayPartId", "single", title, displayProfile.getSetting("dayPartId"), [{dayPartId:null, name:""}]|merge(dayParts), "dayPartId", "name", helpText, "pagedSelect", "", "", "", attributes) }}
</div>
<div class="tab-pane" id="advanced">
{% set title = "Log Level"|trans %}
{% set helpText = "The resting logging level that should be recorded by the Player."|trans %}
{% set options = [
{ id: 'emergency', value: "Emergency"|trans },
{ id: 'alert', value: "Alert"|trans },
{ id: 'critical', value: "Critical"|trans },
{ id: 'error', value: "Error"|trans },
{ id: 'off', value: "Off"|trans }
] %}
{{ forms.dropdown("logLevel", "single", title, displayProfile.getSetting("logLevel"), options, "id", "value", helpText) }}
{% set title %}{% trans "Elevate Logging until" %}{% endset %}
{% set helpText %}{% trans "Elevate log level for the specified time. Should only be used if there is a problem with the display." %}{% endset %}
{% if displayProfile.isElevatedLogging() %}
{% set elevatedLogs = displayProfile.getUnmatchedProperty("elevateLogsUntilIso") %}
{% else %}
{% set elevatedLogs = "" %}
{% endif %}
{{ forms.dateTime("elevateLogsUntil", title, elevatedLogs, helpText) }}
{% set title = "Action Bar Mode"|trans %}
{% set helpText = "How should the action bar behave?"|trans %}
{% set options = [
{ id: 0, value: "Hide"|trans },
{ id: 1, value: "Timed"|trans }
] %}
{{ forms.dropdown("actionBarMode", "single", title, displayProfile.getSetting("actionBarMode"), options, "id", "value", helpText) }}
{% set title = "Action Bar Display Duration"|trans %}
{% set helpText = "How long should the Action Bar be shown for, in seconds?"|trans %}
{{ forms.number("actionBarDisplayDuration", title, displayProfile.getSetting("actionBarDisplayDuration"), helpText, "", "", "", "", "0") }}
{% if theme.getSetting('DISPLAY_PROFILE_CURRENT_LAYOUT_STATUS_ENABLED', 0) == 1 %}
{% set title = "Notify current layout"|trans %}
{% set helpText = "When enabled the Player will send the current layout to the CMS each time it changes. Warning: This is bandwidth intensive and should be disabled unless on a LAN."|trans %}
{{ forms.checkbox("sendCurrentLayoutAsStatusUpdate", title, displayProfile.getSetting("sendCurrentLayoutAsStatusUpdate"), helpText) }}
{% endif %}
{% if theme.getSetting('DISPLAY_PROFILE_SCREENSHOT_INTERVAL_ENABLED', 0) == 1 %}
{% set title %}
{% trans "Screen shot interval"%}
{{ forms.playerCompat("", "", "R204+", "R208+", "") }}
{% endset %}
{% set helpText = "The duration between status screen shots in minutes. 0 to disable. Warning: This is bandwidth intensive."|trans %}
{{ forms.number("screenShotRequestInterval", title, displayProfile.getSetting("screenShotRequestInterval"), helpText, "", "", "", "", "0") }}
{% endif %}
{% set title = "Screen Shot Size"|trans %}
{% set helpText = "The size of the screenshot to return when requested."|trans %}
{% if displayProfile.type == "lg" %}
{% set options = [
{ id: 1, value: "Thumbnail"|trans },
{ id: 2, value: "HD"|trans },
{ id: 3, value: "FHD"|trans }
] %}
{% else %}
{% set options = [
{ id: 1, value: "Thumbnail"|trans },
{ id: 2, value: "Standard"|trans }
] %}
{% endif %}
{{ forms.dropdown("screenShotSize", "single", title, displayProfile.getSetting("screenShotSize"), options, "id", "value", helpText) }}
{% set title = "Send progress while downloading"|trans %}
{% set helpText = "How often, in minutes, should the Display send its download progress while it is downloading new content?"|trans %}
{{ forms.number("mediaInventoryTimer", title, displayProfile.getSetting("mediaInventoryTimer"), helpText, "", "", "", "", "0") }}
{% set title = "Embedded Web Server Port"|trans %}
{% set helpText = "The port number to use for the embedded web server on the Player. Only change this if there is a port conflict reported on the status screen."|trans %}
{{ forms.number("serverPort", title, displayProfile.getSetting("serverPort"), helpText, "", "", "", "", "0") }}
{% set title = "Embedded Web Server allow WAN?"|trans %}
{% set helpText = "Should we allow access to the Player Embedded Web Server from WAN? You may need to adjust the device firewall to allow external traffic"|trans %}
{{ forms.checkbox("embeddedServerAllowWan", title, displayProfile.getSetting("embeddedServerAllowWan"), helpText) }}
{% set title = "Use Multiple Video Decoders"|trans %}
{% set helpText = "Should the Player try to use Multiple Video Decoders when preparing and showing Video content."|trans %}
{% set options = [
{ id: "on", value: "On"|trans },
{ id: "off", value: "Off"|trans }
] %}
{{ forms.dropdown("isUseMultipleVideoDecoders", "single", title, displayProfile.getSetting("isUseMultipleVideoDecoders", "on"), options, "id", "value", helpText) }}
</div>
<div class="tab-pane" id="timers">
{% set settingsDesc1 %}
{% trans "Use the form fields to create On/Off timings for the monitor for specific days of the week as required." %}
{% endset %}
{% set settingsDesc2 %}
{% trans "Please note:" %}
{% endset %}
{% set settingsDesc3 %}
{% trans "When the monitor is 'Off' it will not be able to receive content updates. With the next timed 'On' the monitor will connect to the CMS and get content/schedule updates." %}
{% endset %}
<div class="alert alert-info">
<p>{{ settingsDesc1 }}<strong>{{ settingsDesc2 }}</strong>{{ settingsDesc3 }}</p>
</div>
{# Days translations #}
{% set monday %}{% trans "Monday" %}{% endset %}
{% set tuesday %}{% trans "Tuesday" %}{% endset %}
{% set wednesday %}{% trans "Wednesday" %}{% endset %}
{% set thursday %}{% trans "Thursday" %}{% endset %}
{% set friday %}{% trans "Friday" %}{% endset %}
{% set saturday %}{% trans "Saturday" %}{% endset %}
{% set sunday %}{% trans "Sunday" %}{% endset %}
{% set options = [
{ id: "monday", name: monday },
{ id: "tuesday", name: tuesday },
{ id: "wednesday", name: wednesday },
{ id: "thursday", name: thursday },
{ id: "friday", name: friday },
{ id: "saturday", name: saturday },
{ id: "sunday", name: sunday }
] %}
<div id="timers-container" class="container-fluid display-settings-container" data-values="{{ displayProfile.getSetting("timers") }}" data-options="{{ options|json_encode() }}"></div>
</div>
<div class="tab-pane" id="pictureOptions">
{% set settingsDesc %}
{% trans "Control picture settings using the fields below. Use the sliders to set the required range for each setting." %}
{% endset %}
<div class="alert alert-info">
<p>{{ settingsDesc }}</p>
</div>
{# Properties names translations #}
{% set backlight %}{% trans "Backlight" %}{% endset %}
{% set contrast %}{% trans "Contrast" %}{% endset %}
{% set brightness %}{% trans "Brightness" %}{% endset %}
{% set sharpness %}{% trans "Sharpness" %}{% endset %}
{% set hSharpness %}{% trans "Horizontal Sharpness" %}{% endset %}
{% set vSharpness %}{% trans "Vertical Sharpness" %}{% endset %}
{% set color %}{% trans "Color" %}{% endset %}
{% set tint %}{% trans "Tint" %}{% endset %}
{% set colorTemperature %}{% trans "Color Temperature" %}{% endset %}
{% set dynamicContrast %}{% trans "Dynamic Contrast" %}{% endset %}
{% set superResolution %}{% trans "Super Resolution" %}{% endset %}
{% set colorGamut %}{% trans "Color Gamut" %}{% endset %}
{% set dynamicColor %}{% trans "Dynamic Color" %}{% endset %}
{% set noiseReduction %}{% trans "Noise Reduction" %}{% endset %}
{% set mpegNoiseReduction %}{% trans "MPEG Noise Reduction" %}{% endset %}
{% set blackLevel %}{% trans "Black Level" %}{% endset %}
{% set gamma %}{% trans "Gamma" %}{% endset %}
{# Labels translations #}
{% set red %}{% trans "Red" %}{% endset %}
{% set green %}{% trans "Green" %}{% endset %}
{% set warm %}{% trans "Warm" %}{% endset %}
{% set cool %}{% trans "Cool" %}{% endset %}
{% set options = {
backlight: { name: backlight, type: "number", inputType: "slider", sliderOptions: { min: 0, max: 100, step: 1, ticks: [0, 100], ticks_labels: [0, 100] }},
contrast: { name: contrast, type: "number", inputType: "slider", sliderOptions: { min: 0, max: 100, step: 1, ticks: [0, 100], ticks_labels: [0, 100] }},
brightness: { name: brightness, type: "number", inputType: "slider", sliderOptions: { min: 0, max: 100, step: 1, ticks: [0, 100], ticks_labels: [0, 100] }},
sharpness: { name: sharpness, type: "number", inputType: "slider", sliderOptions: { min: 0, max: 50, step: 1, ticks: [0, 50], ticks_labels: [0, 50] }},
hSharpness: { name: hSharpness, type: "number", inputType: "slider", sliderOptions: { min: 0, max: 50, step: 1, ticks: [0, 50], ticks_labels: [0, 50] }},
vSharpness: { name: vSharpness, type: "number", inputType: "slider", sliderOptions: { min: 0, max: 50, step: 1, ticks: [0, 50], ticks_labels: [0, 50] }},
color: { name: color, type: "number", inputType: "slider", sliderOptions: { min: 0, max: 100, step: 1, ticks: [0, 100], ticks_labels: [0, 100] }},
tint: { name: tint, type: "number", inputType: "slider", sliderOptions: { min: 0, max: 100, step: 1, ticks: [0, 100], ticks_labels: [red, green] }},
colorTemperature: { name: colorTemperature, type: "number", inputType: "slider", sliderOptions: { min: 0, max: 100, step: 1, ticks: [0, 100], ticks_labels: [warm, cool] }},
dynamicContrast: { name: dynamicContrast, type: "string", inputType: "slider", sliderOptions: { min: 0, max: 3, step: 1, ticks: [0, 1, 2, 3], ticks_labels: ["off", "low", "medium", "high"], tooltip: "hide" }},
superResolution: { name: superResolution, type: "string", inputType: "slider", sliderOptions: { min: 0, max: 3, step: 1, ticks: [0, 1, 2, 3], ticks_labels: ["off", "low", "medium", "high"], tooltip: "hide" }},
colorGamut: { name: colorGamut, type: "string", inputType: "slider", sliderOptions: { min: 0, max: 1, step: 1, ticks: [0, 1], ticks_labels: ["normal", "extended"], tooltip: "hide" }},
dynamicColor: { name: dynamicColor, type: "string", inputType: "slider", sliderOptions: { min: 0, max: 3, step: 1, ticks: [0, 1, 2, 3], ticks_labels: ["off", "low", "medium", "high"], tooltip: "hide" }},
noiseReduction: { name: noiseReduction, type: "string", inputType: "slider", sliderOptions: { min: 0, max: 4, step: 1, ticks: [0, 1, 2, 3, 4], ticks_labels: ["auto", "off", "low", "medium", "high"], tooltip: "hide" }},
mpegNoiseReduction: { name: mpegNoiseReduction, type: "string", inputType: "slider", sliderOptions: { min: 0, max: 4, step: 1, ticks: [0, 1, 2, 3, 4], ticks_labels: ["auto", "off", "low", "medium", "high"], tooltip: "hide" }},
blackLevel: { name: blackLevel, type: "string", inputType: "slider", sliderOptions: { min: 0, max: 1, step: 1, ticks: [0, 1], ticks_labels: ["low", "high"], tooltip: "hide" }},
gamma: { name: gamma, type: "string", inputType: "slider", sliderOptions: { min: 0, max: 3, step: 1, ticks: [0, 1, 2, 3], ticks_labels: ["low", "medium", "high", "high2"], tooltip: "hide" }}
} %}
<div id="picture-options-container" class="container-fluid display-settings-container" data-values="{{ displayProfile.getSetting("pictureOptions") }}" data-options="{{ options|json_encode() }}"></div>
</div>
<div class="tab-pane" id="lockOptions">
<div id="lock-options-container" class="container-fluid display-settings-container" data-value="{{ displayProfile.getSetting("lockOptions") }}">
{# usblock and osdlock #}
{% set usblockTitle %}{% trans "usblock" %}{% endset %}
{% set usblockHelp %}{% trans "Set access to any device that uses the monitors USB port. Set to False the monitor will not accept input or read from USB ports." %}{% endset %}
{% set osdlockTitle %}{% trans "osdlock" %}{% endset %}
{% set osdlockHelp %}{% trans "Set access to the monitor settings via the remote control. Set To False the remote control will not change the volume, brightness etc of the monitor." %}{% endset %}
{% set falseText %}{% trans "False" %}{% endset %}
{% set trueText %}{% trans "True" %}{% endset %}
{% set options = [
{ val: 'empty', text: '' },
{ val: 'false', text: falseText },
{ val: 'true', text: trueText }
] %}
{% if lockOptions.usblock is defined and lockOptions.usblock is not null and displayProfile.type == "lg" %}
{% if lockOptions.usblock == true %}
{% set usblockValue = 'true' %}
{% else %}
{% set usblockValue = 'false' %}
{% endif %}
{% else %}
{% set usblockValue = 'empty' %}
{% endif %}
{% if lockOptions.osdlock is defined and lockOptions.osdlock is not null %}
{% if lockOptions.osdlock == true %}
{% set osdlockValue = 'true' %}
{% else %}
{% set osdlockValue = 'false' %}
{% endif %}
{% else %}
{% set osdlockValue = 'empty' %}
{% endif %}
{% if displayProfile.type == "lg" %}
{{ forms.dropdown("usblock", "single", usblockTitle, usblockValue, options, "val", "text", usblockHelp) }}
{% endif %}
{{ forms.dropdown("osdlock", "single", osdlockTitle, osdlockValue, options, "val", "text", osdlockHelp) }}
{# keylock (local and remote) #}
{% set localKeylockTitle %}{% trans "Keylock (local)" %}{% endset %}
{% set localKeylockHelp %}{% trans "Set the allowed key input for the monitor." %}{% endset %}
{% set remoteKeylockTitle %}{% trans "Keylock (remote)" %}{% endset %}
{% set remoteKeylockHelp %}{% trans "Set the allowed key input for the monitor." %}{% endset %}
{% set allowallText %}{% trans "Allow All" %}{% endset %}
{% set blockallText %}{% trans "Block All" %}{% endset %}
{% set poweronlyText %}{% trans "Power Only" %}{% endset %}
{% set options = [
{ val: "", text: "" },
{ val: "allowall", text: allowallText },
{ val: "blockall", text: blockallText }
] %}
{% if displayProfile.type == "lg" %}
{% set options = options|merge([{ val: "poweronly", text: poweronlyText }]) %}
{% endif %}
{{ forms.dropdown("keylockLocal", "single", localKeylockTitle, lockOptions.keylock.local, options, "val", "text", localKeylockHelp) }}
{{ forms.dropdown("keylockRemote", "single", remoteKeylockTitle, lockOptions.keylock.remote, options, "val", "text", remoteKeylockHelp) }}
</div>
</div>
{% if commands|length > 0 %}
<div class="tab-pane" id="commands">
{% include "displayprofile-form-edit-command-fields.twig" %}
</div>
{% endif %}
</div>
</form>
</div>
</div>
{% endblock %}

Some files were not shown because too many files have changed in this diff Show More