Restructure
@@ -1,369 +0,0 @@
|
||||
{#
|
||||
/**
|
||||
* 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 %}
|
||||
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
@@ -1,173 +0,0 @@
|
||||
{#
|
||||
/**
|
||||
* 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 %}
|
||||
@@ -1 +0,0 @@
|
||||
404: Not Found
|
||||
@@ -1,584 +0,0 @@
|
||||
{#
|
||||
/**
|
||||
* 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"> </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 %}
|
||||
@@ -1,248 +0,0 @@
|
||||
{#
|
||||
/**
|
||||
* 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 %}
|
||||
@@ -1,523 +0,0 @@
|
||||
{% extends "authed.twig" %}
|
||||
{% import "inline.twig" as inline %}
|
||||
|
||||
{% block title %}{{ "Layouts"|trans }} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
{% if currentUser.featureEnabled("layout.add") %}
|
||||
<button class="btn btn-success layout-add-button"
|
||||
title="{% trans "Add a new Layout and jump to the layout editor." %}"
|
||||
href="{{ url_for("layout.add") }}">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Layout" %}
|
||||
</button>
|
||||
<button class="btn btn-info" id="layoutUploadForm" title="{% trans "Import a Layout from a ZIP file." %}" href="#"> <i class="fa fa-cloud-download" aria-hidden="true"></i> {% trans "Import" %}</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 "Layouts" %}</div>
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-grid-type="layout" data-grid-name="layoutView">
|
||||
<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="#general-filter" role="tab" data-toggle="tab" aria-selected="true"><span>{% trans "General" %}</span></a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="#advanced-filter" role="tab" data-toggle="tab" aria-selected="false"><span>{% trans "Advanced" %}</span></a></li>
|
||||
</ul>
|
||||
<form class="form-inline d-block">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="general-filter" role="tabpanel">
|
||||
{% set title %}{% trans "ID" %}{% endset %}
|
||||
{{ inline.number("campaignId", title) }}
|
||||
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{{ inline.inputNameGrid('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 %}
|
||||
|
||||
{% set title %}{% trans "Code" %}{% endset %}
|
||||
{{ inline.input('codeLike', title) }}
|
||||
|
||||
{% if currentUser.featureEnabled("displaygroup.view") %}
|
||||
{% set title %}{% trans "Display Group" %}{% endset %}
|
||||
{% set helpText %}{% trans "Show Layouts active on the selected Display / 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":-1}' },
|
||||
{ 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("activeDisplayGroupId", "single", title, "", null, "displayGroupId", "displayGroup", 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 "Owner User Group" %}{% endset %}
|
||||
{% set helpText %}{% trans "Show items owned by users in the selected User 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("group.search") },
|
||||
{ name: "data-search-term", value: "group" },
|
||||
{ name: "data-id-property", value: "groupId" },
|
||||
{ name: "data-text-property", value: "group" },
|
||||
{ name: "data-initial-key", value: "userGroupId" },
|
||||
] %}
|
||||
{{ inline.dropdown("ownerUserGroupId", "single", title, "", null, "groupId", "group", 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") }}
|
||||
|
||||
{{ inline.hidden("folderId") }}
|
||||
</div>
|
||||
<div class="tab-pane" id="advanced-filter" role="tabpanel">
|
||||
{% set title %}{% trans "Retired" %}{% endset %}
|
||||
{% set option1 = "No"|trans %}
|
||||
{% set option2 = "Yes"|trans %}
|
||||
{% set values = [{id: 0, value: option1}, {id: 1, value: option2}] %}
|
||||
{{ inline.dropdown("retired", "single", title, 0, values, "id", "value") }}
|
||||
|
||||
{% set title %}{% trans "Show" %}{% endset %}
|
||||
{% set option1 = "All"|trans %}
|
||||
{% set option2 = "Only Used"|trans %}
|
||||
{% set option3 = "Only Unused"|trans %}
|
||||
{% set values = [{id: 1, value: option1}, {id: 2, value: option2}, {id: 3, value: option3}] %}
|
||||
{{ inline.dropdown("layoutStatusId", "single", title, 1, values, "id", "value") }}
|
||||
|
||||
{% set title %}{% trans "Description" %}{% endset %}
|
||||
{% set option1 = "All"|trans %}
|
||||
{% set option2 = "1st line"|trans %}
|
||||
{% set option3 = "Widget List"|trans %}
|
||||
{% set values = [{id: 1, value: option1}, {id: 2, value: option2}, {id: 3, value: option3}] %}
|
||||
{{ inline.dropdown("showDescriptionId", "single", title, 2, values, "id", "value") }}
|
||||
|
||||
{% if currentUser.featureEnabled("library.view") %}
|
||||
{% set title %}{% trans "Media" %}{% endset %}
|
||||
{{ inline.input("mediaLike", title) }}
|
||||
{% endif %}
|
||||
|
||||
{% set title %}{% trans "Layout ID" %}{% endset %}
|
||||
{{ inline.number("layoutId", title) }}
|
||||
|
||||
{% set title %}{% trans "Modified Since" %}{% endset %}
|
||||
{{ inline.date("modifiedSinceDt", title) }}
|
||||
</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="{% 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="layouts" class="table table-striped responsive nowrap" data-content-type="layout" data-content-id-name="layoutId" data-state-preference-name="layoutGrid" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "ID" %}</th>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th>{% trans "Description" %}</th>
|
||||
<th>{% trans "Duration" %}</th>
|
||||
{% if currentUser.featureEnabled("tag.tagging") %}<th>{% trans "Tags" %}</th>{% endif %}
|
||||
<th>{% trans "Orientation" %}</th>
|
||||
<th>{% trans "Thumbnail" %}</th>
|
||||
<th>{% trans "Owner" %}</th>
|
||||
<th>{% trans "Sharing" %}</th>
|
||||
<th>{% trans "Valid?" %}</th>
|
||||
<th>{% trans "Stats?" %}</th>
|
||||
<th>{% trans "Created" %}</th>
|
||||
<th>{% trans "Modified" %}</th>
|
||||
<th>{% trans "Layout ID" %}</th>
|
||||
<th>{% trans "Code" %}</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 = $("#layouts").DataTable({
|
||||
language: dataTablesLanguage,
|
||||
lengthMenu: [10, 25, 50, 100, 250, 500],
|
||||
dom: dataTablesTemplate,
|
||||
serverSide: true,
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
responsive: true,
|
||||
stateLoadCallback: dataTableStateLoadCallback,
|
||||
stateSaveCallback: dataTableStateSaveCallback,
|
||||
filter: false,
|
||||
searchDelay: 3000,
|
||||
dataType: 'json',
|
||||
order: [[1, "asc"]],
|
||||
ajax: {
|
||||
url: "{{ url_for("layout.search") }}",
|
||||
data: function (d) {
|
||||
$.extend(d, $("#layouts").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
{"data": "campaignId", responsivePriority: 1},
|
||||
{
|
||||
"data": "layout",
|
||||
responsivePriority: 2,
|
||||
"render": dataTableSpacingPreformatted
|
||||
},
|
||||
{
|
||||
"name": "publishedStatus",
|
||||
responsivePriority: 2,
|
||||
"data": function (data, type) {
|
||||
if (data.publishedDate != null) {
|
||||
var now = moment();
|
||||
var published = moment(data.publishedDate);
|
||||
var differenceMinutes = published.diff(now, 'minutes');
|
||||
var momentDifference = moment(now).to(published);
|
||||
|
||||
if (differenceMinutes < -5) {
|
||||
return data.publishedStatus.concat(" - ", translations.publishedStatusFailed);
|
||||
} else {
|
||||
return data.publishedStatus.concat(" - ", translations.publishedStatusFuture + " " + momentDifference);
|
||||
}
|
||||
} else {
|
||||
return data.publishedStatus;
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"data": null,
|
||||
responsivePriority: 10,
|
||||
"render": {"_": "description", "display": "descriptionFormatted", "sort": "description"}
|
||||
},
|
||||
{
|
||||
"name": "duration",
|
||||
responsivePriority: 3,
|
||||
"data": function (data, type) {
|
||||
if (type != "display")
|
||||
return data.duration;
|
||||
|
||||
return dataTableTimeFromSeconds(data.duration, type);
|
||||
}
|
||||
},
|
||||
{% if currentUser.featureEnabled("tag.tagging") %}{
|
||||
"sortable": false,
|
||||
"visible": false,
|
||||
responsivePriority: 3,
|
||||
"data": dataTableCreateTags
|
||||
},{% endif %}
|
||||
{ data: 'orientation', responsivePriority: 10, visible: false},
|
||||
{
|
||||
responsivePriority: 5,
|
||||
data: 'thumbnail',
|
||||
render: function(data, type, row) {
|
||||
if (type !== 'display') {
|
||||
return row.layoutId;
|
||||
}
|
||||
if (data) {
|
||||
return '<a class="img-replace" data-toggle="lightbox" data-type="image" href="' + data + '">' +
|
||||
'<img class="img-fluid" src="' + data + '" alt="{{ "Thumbnail"|trans }}" />' +
|
||||
'</a>';
|
||||
} else {
|
||||
var addUrl = '{{ url_for("layout.thumbnail.add", {id: ":id"}) }}'.replace(':id', row.layoutId);
|
||||
return '<a class="img-replace generate-layout-thumbnail" data-type="image" href="' + addUrl + '">' +
|
||||
'<img class="img-fluid" src="{{ theme.uri("img/thumbs/placeholder.png") }}" alt="{{ "Add Thumbnail"|trans }}" />' +
|
||||
'</a>';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
sortable: false
|
||||
},
|
||||
{"data": "owner", responsivePriority: 4},
|
||||
{
|
||||
"data": "groupsWithPermissions",
|
||||
responsivePriority: 4,
|
||||
"render": dataTableCreatePermissions
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
responsivePriority: 3,
|
||||
"data": function (data, type) {
|
||||
if (type != "display")
|
||||
return data.status;
|
||||
|
||||
var icon = "";
|
||||
if (data.status == 1)
|
||||
icon = "fa-check";
|
||||
else if (data.status == 2)
|
||||
icon = "fa-exclamation";
|
||||
else if (data.status == 3)
|
||||
icon = "fa-cogs";
|
||||
else
|
||||
icon = "fa-times";
|
||||
|
||||
return '<span class="fa ' + icon + '" title="' + (data.statusDescription) + ((data.statusMessage == null) ? "" : " - " + (data.statusMessage)) + '"></span>';
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "enableStat",
|
||||
responsivePriority: 4,
|
||||
"data": function (data) {
|
||||
|
||||
var icon = "";
|
||||
if (data.enableStat == 1)
|
||||
icon = "fa-check";
|
||||
else
|
||||
icon = "fa-times";
|
||||
|
||||
return '<span class="fa ' + icon + '" title="' + (data.enableStatDescription) + '"></span>';
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": "createdDt",
|
||||
responsivePriority: 6,
|
||||
"render": dataTableDateFromIso,
|
||||
"visible": false
|
||||
},
|
||||
{
|
||||
data: "modifiedDt",
|
||||
responsivePriority: 6,
|
||||
render: dataTableDateFromIso,
|
||||
visible: true
|
||||
},
|
||||
{
|
||||
data: "layoutId",
|
||||
visible: false,
|
||||
responsivePriority: 4
|
||||
},
|
||||
{"data": "code", "visible":false, responsivePriority: 4},
|
||||
{
|
||||
"orderable": false,
|
||||
responsivePriority: 1,
|
||||
"data": dataTableButtonsColumn
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('draw', { form: $("#layouts").closest(".XiboGrid").find(".FilterDiv form") }, dataTableCreateTagEvents);
|
||||
table.on('draw', function(e, settings) {
|
||||
$('#' + e.target.id + ' .generate-layout-thumbnail').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
var $anchor = $(this);
|
||||
$.ajax({
|
||||
url: $anchor.attr('href'),
|
||||
method: 'POST',
|
||||
success: function() {
|
||||
$anchor.find('img').attr('src', $anchor.attr('href'));
|
||||
$anchor.removeClass('generate-layout-thumbnail').attr('data-toggle', 'lightbox');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
dataTableAddButtons(table, $('#layouts_wrapper').find('.dataTables_buttons'));
|
||||
|
||||
$("#refreshGrid").click(function() {
|
||||
table.ajax.reload();
|
||||
});
|
||||
|
||||
// Bind to the layout add button
|
||||
$('button.layout-add-button').on('click', function() {
|
||||
let currentWorkingFolderId =
|
||||
$("#layouts")
|
||||
.closest(".XiboGrid")
|
||||
.find(".FilterDiv form")
|
||||
.find('#folderId').val()
|
||||
// Submit the URL provided as a POST request.
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: $(this).attr('href'),
|
||||
cache: false,
|
||||
data : {folderId : currentWorkingFolderId},
|
||||
dataType: 'json',
|
||||
success: function(response, textStatus, error) {
|
||||
if (response.success && response.id) {
|
||||
XiboRedirect('{{ url_for("layout.designer", {id: ':id'}) }}'.replace(':id', response.id));
|
||||
} else {
|
||||
if (response.login) {
|
||||
LoginBox(response.message);
|
||||
} else {
|
||||
SystemMessage(response.message ?? '{{ "Unknown Error"|trans }}', false);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function(xhr, textStatus, errorThrown) {
|
||||
SystemMessage(xhr.responseText, false);
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$("#layoutUploadForm").click(function(e) {
|
||||
e.preventDefault();
|
||||
var currentWorkingFolderId = $('#folderId').val();
|
||||
|
||||
// Open the upload dialog with our options.
|
||||
openUploadForm({
|
||||
url: "{{ url_for("layout.import") }}",
|
||||
title: "{{ "Upload Layout"|trans }}",
|
||||
videoImageCovers: false,
|
||||
buttons: {
|
||||
main: {
|
||||
label: "{{ "Done"|trans }}",
|
||||
className: "btn-primary btn-bb-main",
|
||||
callback: function () {
|
||||
table.ajax.reload();
|
||||
XiboDialogClose();
|
||||
}
|
||||
}
|
||||
},
|
||||
templateOptions: {
|
||||
layoutImport: true,
|
||||
updateInAllChecked: {% if settings.LIBRARY_MEDIA_UPDATEINALL_CHECKB == 1 %}true{% else %}false{% endif %},
|
||||
deleteOldRevisionsChecked: {% if settings.LIBRARY_MEDIA_DELETEOLDVER_CHECKB == 1 %}true{% else %}false{% endif %},
|
||||
trans: {
|
||||
addFiles: "{{ "Add Layout Export ZIP Files"|trans }}",
|
||||
startUpload: "{{ "Start Import"|trans }}",
|
||||
cancelUpload: "{{ "Cancel Import"|trans }}",
|
||||
replaceExistingMediaMessage: "{{ "Replace Existing Media?"|trans }}",
|
||||
importTagsMessage: "{{ "Import Tags?"|trans }}",
|
||||
useExistingDataSetsMessage: "{{ "Use existing DataSets matched by name?"|trans }}",
|
||||
dataSetDataMessage: "{{ "Import DataSet Data?"|trans }}",
|
||||
fallbackMessage: "{{ "Import Widget Fallback Data?"|trans }}",
|
||||
selectFolder: "{{ "Select Folder"|trans }}",
|
||||
selectFolderTitle: "{{ "Change Current Folder location"|trans }}",
|
||||
selectedFolder: "{{ "Current Folder"|trans }}:",
|
||||
selectedFolderTitle: "{{ "Upload files to this Folder"|trans }}"
|
||||
},
|
||||
upload: {
|
||||
maxSize: {{ libraryUpload.maxSize }},
|
||||
maxSizeMessage: "{{ libraryUpload.maxSizeMessage }}",
|
||||
validExt: "zip"
|
||||
},
|
||||
currentWorkingFolderId: currentWorkingFolderId,
|
||||
folderSelector: true
|
||||
},
|
||||
formOpenedEvent: function () {
|
||||
// Configure the active behaviour of the checkboxes
|
||||
$("#useExistingDataSets").on("click", function () {
|
||||
$("#importDataSetData").prop("disabled", ($(this).is(":checked")));
|
||||
});
|
||||
},
|
||||
uploadDoneEvent: function (data) {
|
||||
XiboDialogClose();
|
||||
table.ajax.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function layoutExportFormSubmit() {
|
||||
var $form = $("#layoutExportForm");
|
||||
window.location = $form.attr("action") + "?" + $form.serialize();
|
||||
|
||||
setTimeout(function() {
|
||||
XiboDialogClose();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function assignLayoutToCampaignFormSubmit() {
|
||||
var form = $("#layoutAssignCampaignForm");
|
||||
|
||||
var url = form.prop("action").replace(":id", form.find("#campaignId").val());
|
||||
|
||||
$.ajax({
|
||||
type: form.attr("method"),
|
||||
url: url,
|
||||
data: {layoutId: form.data().layoutId},
|
||||
cache: false,
|
||||
dataType:"json",
|
||||
success: XiboSubmitResponse
|
||||
});
|
||||
}
|
||||
|
||||
function setEnableStatMultiSelectFormOpen(dialog) {
|
||||
var $input = $('<input type=checkbox id="enableStat" name="enableStat"> {{ "Enable Stats Collection?"|trans }} </input>');
|
||||
var $helpText = $('<span class="help-block">{{ "Check to enable the collection of Proof of Play statistics for the selected items."|trans }}</span>');
|
||||
|
||||
$input.on('change', function() {
|
||||
dialog.data().commitData = {enableStat: $(this).val()};
|
||||
});
|
||||
|
||||
$(dialog).find('.modal-body').append($input);
|
||||
$(dialog).find('.modal-body').append($helpText);
|
||||
}
|
||||
|
||||
function layoutPublishFormOpen() {
|
||||
// Nothing to do here, but we use the same form on the layout designer and have a callback registered there
|
||||
}
|
||||
|
||||
function layoutEditFormSaved() {
|
||||
// Nothing to do here.
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,194 +0,0 @@
|
||||
{#
|
||||
/**
|
||||
* 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 %}{{ "Menu Boards"|trans }} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
{% if currentUser.featureEnabled("menuBoard.add") %}
|
||||
<button class="btn btn-success XiboFormButton" title="{% trans "Add a new Menu Board" %}" href="{{ url_for("menuBoard.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Menu Board" %}</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 "Menu Boards" %}</div>
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-grid-type="menuBoard" data-grid-name="menuBoardView">
|
||||
<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("menuId", title) }}
|
||||
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{{ inline.inputNameGrid('name', title) }}
|
||||
|
||||
{% set title %}{% trans "Code" %}{% endset %}
|
||||
{{ inline.input('code', title) }}
|
||||
|
||||
{% 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="row">
|
||||
<div class="col-sm-1 form-group" style="padding: 0">
|
||||
<button type="button" id="folder-tree-select-folder-button" class="btn btn-outline-secondary btn-sm" title="{% trans "Open / Close Folder Search options" %}"><i class="fas fa-bars fa-1x"></i></button>
|
||||
</div>
|
||||
<div class="form-group col-sm-11" style="padding: 0">
|
||||
<div id="breadcrumbs" style="margin-top: 5px;"></div>
|
||||
</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 id="datatable-container">
|
||||
<div class="XiboData card py-3">
|
||||
<table id="menuBoards" class="table table-striped responsive nowrap" data-content-type="menuBoard" data-content-id-name="menuId" data-state-preference-name="menuBoardGrid" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "ID" %}</th>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Description" %}</th>
|
||||
<th>{% trans "Code" %}</th>
|
||||
<th>{% trans "Modified Date" %}</th>
|
||||
<th>{% trans "Owner" %}</th>
|
||||
<th>{% trans "Permissions" %}</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 = $("#menuBoards").DataTable({
|
||||
"language": dataTablesLanguage,
|
||||
"lengthMenu": [10, 25, 50, 100, 250, 500],
|
||||
serverSide: true,
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
responsive: true,
|
||||
stateLoadCallback: dataTableStateLoadCallback,
|
||||
stateSaveCallback: dataTableStateSaveCallback,
|
||||
filter: false,
|
||||
searchDelay: 3000,
|
||||
dataType: 'json',
|
||||
"order": [[1, "asc"]],
|
||||
ajax: {
|
||||
url: "{{ url_for("menuBoard.search") }}",
|
||||
"data": function (d) {
|
||||
$.extend(d, $("#menuBoards").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
|
||||
}
|
||||
},
|
||||
"columns": [
|
||||
{"data": "menuId", responsivePriority: 2},
|
||||
{
|
||||
"data": "name",
|
||||
responsivePriority: 2,
|
||||
"render": dataTableSpacingPreformatted
|
||||
},
|
||||
{
|
||||
"data": "description",
|
||||
responsivePriority: 2,
|
||||
"render": dataTableSpacingPreformatted
|
||||
},
|
||||
{
|
||||
"data": "code", responsivePriority: 3
|
||||
},
|
||||
{
|
||||
"name": "modifiedDt",
|
||||
"data": function (data) {
|
||||
return moment.unix(data.modifiedDt).format(jsDateFormat);
|
||||
}
|
||||
},
|
||||
{"data": "owner", responsivePriority: 4},
|
||||
{
|
||||
"data": "groupsWithPermissions",
|
||||
responsivePriority: 4,
|
||||
"render": dataTableCreatePermissions
|
||||
},
|
||||
{
|
||||
"orderable": false,
|
||||
responsivePriority: 1,
|
||||
"data": dataTableButtonsColumn
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
dataTableAddButtons(table, $('#menuBoards_wrapper').find('.col-md-6').eq(1));
|
||||
|
||||
$("#refreshGrid").click(function () {
|
||||
table.ajax.reload();
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,120 +0,0 @@
|
||||
{#
|
||||
/**
|
||||
* 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 %}{{ "Resolutions"|trans }} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
{% if currentUser.featureEnabled("resolution.add") %}
|
||||
<button class="btn btn-success XiboFormButton btns" title="{% trans "Add a new resolution for use on layouts" %}" href="{{ url_for("resolution.add.form") }}"><span class="fa fa-plus"></span> {% trans "Add Resolution" %}</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 "Resolution" %}</div>
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-grid-name="resolutionView">
|
||||
<div class="XiboFilter card mb-3 bg-light">
|
||||
<div class="FilterDiv card-body" id="Filter">
|
||||
<form class="form-inline">
|
||||
{% set title %}{% trans "Enabled" %}{% endset %}
|
||||
{% set option1 %}{% trans "Yes" %}{% endset %}
|
||||
{% set option2 %}{% trans "No" %}{% endset %}
|
||||
{% set values = [{id: 1, value: option1}, {id: 0, value: option2}] %}
|
||||
{{ inline.dropdown("enabled", "single", title, 1, values, "id", "value") }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="XiboData card pt-3">
|
||||
<table id="resolutions" class="table table-striped" data-state-preference-name="resolutionGrid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "ID" %}</th>
|
||||
<th>{% trans "Resolution" %}</th>
|
||||
<th>{% trans "Width" %}</th>
|
||||
<th>{% trans "Height" %}</th>
|
||||
<th>{% trans "Enabled?" %}</th>
|
||||
<th class="rowMenu"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
$(document).ready(function() {
|
||||
var table = $("#resolutions").DataTable({
|
||||
"language": dataTablesLanguage,
|
||||
dom: dataTablesTemplate,
|
||||
serverSide: true,
|
||||
stateSave: true,
|
||||
responsive: true,
|
||||
stateDuration: 0,
|
||||
stateLoadCallback: dataTableStateLoadCallback,
|
||||
stateSaveCallback: dataTableStateSaveCallback,
|
||||
filter: false,
|
||||
searchDelay: 3000,
|
||||
"order": [[1, "asc"]],
|
||||
ajax: {
|
||||
url: "{{ url_for("resolution.search") }}",
|
||||
data: function (d) {
|
||||
$.extend(d, $("#resolutions").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
|
||||
}
|
||||
},
|
||||
"columns": [
|
||||
{"data": "resolutionId", responsivePriority: 2},
|
||||
{"data": "resolution"},
|
||||
{"data": "width"},
|
||||
{"data": "height"},
|
||||
{"data": "enabled"},
|
||||
{
|
||||
"orderable": false,
|
||||
responsivePriority: 1,
|
||||
"data": dataTableButtonsColumn
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
dataTableAddButtons(table, $('#resolutions_wrapper').find('.dataTables_buttons'));
|
||||
|
||||
$("#refreshGrid").click(function () {
|
||||
table.ajax.reload();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,342 +0,0 @@
|
||||
{#
|
||||
/**
|
||||
* 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 %}
|
||||
{% import "forms.twig" as forms %}
|
||||
|
||||
{% block title %}{{ "Schedule"|trans }} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
{% if currentUser.featureEnabled("schedule.add") %}
|
||||
<button class="btn btn-success XiboFormButton btns" title="{% trans "Add a new Scheduled event" %}"
|
||||
href="{{ url_for("schedule.add.form") }}"><span class="fa fa-plus"></span> {% trans "Add Event" %}
|
||||
</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 "Schedule" %}</div>
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-grid-name="scheduleGridView">
|
||||
<div class="XiboFilter card mb-3 bg-light">
|
||||
<div class="FilterDiv card-body" id="schedule-filter">
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li class="nav-item"><a class="nav-link active" href="#general-filter" role="tab" data-toggle="tab" aria-selected="true"><span>{% trans "General" %}</span></a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="#advanced-filter" role="tab" data-toggle="tab" aria-selected="false"><span>{% trans "Advanced" %}</span></a></li>
|
||||
</ul>
|
||||
<form class="form-inline">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="general-filter" role="tabpanel">
|
||||
{% set title %}{% trans "Range" %}{% endset %}
|
||||
{% set range %}{% trans "Custom" %}{% endset %}
|
||||
{% set day %}{% trans "Day" %}{% endset %}
|
||||
{% set week %}{% trans "Week" %}{% endset %}
|
||||
{% set month %}{% trans "Month" %}{% endset %}
|
||||
{% set year %}{% trans "Year" %}{% endset %}
|
||||
{% set options = [
|
||||
{ name: "custom", range: range },
|
||||
{ name: "day", range: day },
|
||||
{ name: "week", range: week },
|
||||
{ name: "month", range: month },
|
||||
{ name: "year", range: year },
|
||||
] %}
|
||||
{{ inline.dropdown("range", "single", title, "month", options, "name", "range", "", "date-range-input") }}
|
||||
|
||||
{% set title %}{% trans 'From Date' %}{% endset %}
|
||||
{{ inline.dateTime("fromDt", title, "", "", "custom-date-range d-none", "", "") }}
|
||||
|
||||
{% set title %}{% trans 'To Date' %}{% endset %}
|
||||
{{ inline.dateTime("toDt", title, "", "", "custom-date-range d-none", "", "") }}
|
||||
|
||||
{% set title %}{% trans "Date Controls" %}{% endset %}
|
||||
<div class="form-group mr-1 mb-1 controls-date-range">
|
||||
<div class="control-label mr-1" title=""
|
||||
accesskey="">{{ title }}</div>
|
||||
<div class="controls-date-inputs">
|
||||
<div class="inputgroup date" id="dateInput">
|
||||
<span class="btn btn-outline-primary date-open-button" role="button">
|
||||
<i class="fa fa-calendar"></i>
|
||||
</span>
|
||||
<input type="text" class="form-control" id="dateInputLink" data-input style="display:none;"/>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-secondary" data-calendar-nav="prev"><span class="fa fa-caret-left"></span> {% trans "Prev" %}</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-calendar-nav="today">{% trans "Today" %}</button>
|
||||
<button type="button" class="btn btn-secondary" data-calendar-nav="next">{% trans "Next" %} <span class="fa fa-caret-right"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% set title %}{% trans "Name" %}{% endset %}
|
||||
{{ inline.inputNameGrid('name', title) }}
|
||||
|
||||
{% set title %}{% trans 'Event Type' %}{% endset %}
|
||||
{{ inline.dropdown("eventTypeId", "single", title, "", [{eventTypeId: null, eventTypeName: "All"}]|merge(eventTypes), "eventTypeId", "eventTypeName") }}
|
||||
|
||||
{% set title %}{% trans "Layout / Campaign" %}{% endset %}
|
||||
{% set helpText %}{% trans "Please select a Layout or Campaign for this Event to show" %}{% endset %}
|
||||
|
||||
<div class="form-group mr-1 mb-1">
|
||||
<label class="control-label mr-1" for="campaignId" title=""
|
||||
accesskey="">{{ title }}</label>
|
||||
<select name="campaignId" id="campaignIdFilter" class="form-control"
|
||||
data-search-url="{{ url_for("campaign.search") }}"
|
||||
data-trans-campaigns="{% trans "Campaigns" %}"
|
||||
data-trans-layouts="{% trans "Layouts" %}"
|
||||
data-allow-clear="true"
|
||||
data-width="100%"
|
||||
title="{% trans "Layout / Campaign" %}"
|
||||
data-placeholder="{% trans "Layout / Campaign" %}"
|
||||
data-dropdownAutoWidth
|
||||
>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{% set title %}{% trans "Displays" %}{% endset %}
|
||||
<div class="form-group mr-1 mb-1 pagedSelect" style="min-width: 200px">
|
||||
<label class="control-label mr-1" for="DisplayList" title=""
|
||||
accesskey="">{{ title }}</label>
|
||||
<select id="DisplayList" class="form-control" name="displaySpecificGroupIds[]"
|
||||
data-width="100%"
|
||||
data-placeholder="{% trans "Displays" %}"
|
||||
data-search-url="{{ url_for("display.search") }}"
|
||||
data-search-term="display"
|
||||
data-id-property="displayGroupId"
|
||||
data-text-property="display"
|
||||
data-additional-property="displayGroupId"
|
||||
data-allow-clear="true"
|
||||
data-initial-key="displayGroupIds[]"
|
||||
multiple>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{% set title %}{% trans "Display Groups" %}{% endset %}
|
||||
<div class="form-group mr-2 mb-1 pagedSelect" style="min-width: 200px">
|
||||
<label class="control-label mr-1" for="DisplayGroupList" title=""
|
||||
accesskey="">{{ title }}</label>
|
||||
<select id="DisplayGroupList" class="form-control" name="displayGroupIds[]"
|
||||
data-width="100%"
|
||||
data-placeholder="{% trans "Display Groups" %}"
|
||||
data-search-url="{{ url_for("displayGroup.search") }}"
|
||||
data-search-term="displayGroup"
|
||||
data-id-property="displayGroupId"
|
||||
data-text-property="displayGroup"
|
||||
data-allow-clear="true"
|
||||
data-initial-key="displayGroupIds[]"
|
||||
multiple>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="advanced-filter" role="tabpanel">
|
||||
{% set label %}{% trans "Direct Schedule?" %}{% endset %}
|
||||
{% set title %}{% trans "Show only events scheduled directly on selected Displays/Groups" %}{% endset %}
|
||||
<div class="form-group ml-2 mr-3 mb-1">
|
||||
<div class="form-check">
|
||||
<input title="{{ title }}" class="form-check-input" type="checkbox" id="directSchedule" name="directSchedule">
|
||||
<label class="form-check-label" title="{{ title }}" for="directSchedule" accesskey="">{{ label }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% set title %}{% trans "Only show schedules which appear on all filtered displays/groups?" %}{% endset %}
|
||||
{% set label %}{% trans "Shared Schedule?" %}{% endset %}
|
||||
<div class="form-group ml-2 mr-3 mb-1">
|
||||
<div class="form-check">
|
||||
<input title="{{ title }}" class="form-check-input" type="checkbox" id="sharedSchedule" name="sharedSchedule">
|
||||
<label class="form-check-label" title="{{ title }}" for="sharedSchedule" accesskey="">{{ label }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% set title %}{% trans 'Geo Aware?' %}{% endset %}
|
||||
{% set options = [
|
||||
{ id: null, name: "Both"|trans },
|
||||
{ id: 0, name: "No"|trans },
|
||||
{ id: 1, name: "Yes"|trans }
|
||||
] %}
|
||||
{{ inline.dropdown("geoAware", "single", title, "both", options, "id", "name") }}
|
||||
|
||||
{% set title %}{% trans 'Recurring?' %}{% endset %}
|
||||
{% set options = [
|
||||
{ id: null, name: "Both" },
|
||||
{ id: 0, name: "No"|trans },
|
||||
{ id: 1, name: "Yes"|trans }
|
||||
] %}
|
||||
{{ inline.dropdown("recurring", "single", title, "both", options, "id", "name") }}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="XiboSchedule card">
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-tabs card-header-tabs">
|
||||
<li class="nav-item">
|
||||
<a class="schedule-nav grid-nav nav-link active" id="grid-tab" href="#grid-view"
|
||||
data-schedule-view="grid"
|
||||
role="tab"
|
||||
data-toggle="tab"><span>{% trans "Grid" %}</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="schedule-nav calendar-nav nav-link" id="calendar-tab" href="#calendar-view"
|
||||
data-schedule-view="calendar"
|
||||
data-calendar-view="month"
|
||||
role="tab"
|
||||
data-toggle="tab"><span>{% trans "Calendar" %}</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="xibo-calendar-header-container col-xl-12 d-inline-flex justify-content-between">
|
||||
<div class="xibo-calendar-header text-center d-inline-flex">
|
||||
<h1 class="page-header"></h1>
|
||||
</div>
|
||||
|
||||
<div class="calendar-loading">
|
||||
<span id="calendar-progress-table" class="fa fa-spin fa-cog"></span>
|
||||
<span id="calendar-progress" class="fa fa-spin fa-cog"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="grid-view">
|
||||
<div class="XiboData pt-3">
|
||||
<table id="schedule-grid" class="table table-striped w-100"
|
||||
data-state-preference-name="scheduleGrid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans 'ID' %}</th>
|
||||
<th></th>
|
||||
<th>{% trans 'Event Type' %}</th>
|
||||
<th>{% trans 'Name' %}</th>
|
||||
<th>{% trans 'Start' %}</th>
|
||||
<th>{% trans 'End' %}</th>
|
||||
<th>{% trans 'Event' %}</th>
|
||||
<th>{% trans 'Campaign ID' %}</th>
|
||||
<th>{% trans 'Display Groups' %}</th>
|
||||
<th>{% trans 'SoV' %}</th>
|
||||
<th>{% trans 'Max Plays per Hour' %}</th>
|
||||
<th>{% trans 'Geo Aware?' %}</th>
|
||||
<th>{% trans 'Recurring?' %}</th>
|
||||
<th>{% trans 'Recurrence Description' %}</th>
|
||||
<th>{% trans 'Recurrence Type' %}</th>
|
||||
<th>{% trans 'Recurrence Interval' %}</th>
|
||||
<th>{% trans 'Recurrence Repeats On' %}</th>
|
||||
<th>{% trans 'Recurrence End' %}</th>
|
||||
<th>{% trans 'Priority?' %}</th>
|
||||
<th>{% trans 'Criteria?' %}</th>
|
||||
<th>{% trans 'Created On' %}</th>
|
||||
<th>{% trans 'Updated On' %}</th>
|
||||
<th>{% trans 'Modified By' %}</th>
|
||||
<th class="rowMenu"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="calendar-view">
|
||||
<div class="row">
|
||||
<div id="CalendarContainer"
|
||||
data-agenda-link="{{ url_for("schedule.events", {id: ':id'}) }}"
|
||||
data-calendar-type="{{ settings.CALENDAR_TYPE }}" class="col-sm-12"
|
||||
data-default-lat="{{ defaultLat }}"
|
||||
data-default-long="{{ defaultLong }}">
|
||||
<div class="calendar-view" id="Calendar"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="cal-legend">
|
||||
<ul>
|
||||
<li class="event-always"><span
|
||||
class="fa fa-retweet"></span> {% trans "Always showing" %}</li>
|
||||
<li class="event-info"><span
|
||||
class="fa fa-desktop"></span> {% trans "Single Display" %}</li>
|
||||
<li class="event-success"><span
|
||||
class="fa fa-desktop"></span> {% trans "Multi Display" %}</li>
|
||||
<li class="event-important"><span
|
||||
class="fa fa-bullseye"></span> {% trans "Priority" %}</li>
|
||||
<li class="event-special"><span
|
||||
class="fa fa-repeat"></span> {% trans "Recurring" %}</li>
|
||||
<li class="event-inverse"><span
|
||||
class="fa fa-lock"></span> {% trans "View Only" %}</li>
|
||||
<li class="event-command"><span
|
||||
class="fa fa-wrench"></span> {% trans "Command" %}</li>
|
||||
<li class="event-interrupt"><span
|
||||
class="fa fa-hand-paper"></span> {% trans "Interrupt" %}</li>
|
||||
<li class="event-geo-location"><span
|
||||
class="fa fa-map-marker"></span> {% trans "Geo Location" %}</li>
|
||||
<li class="event-action"><span
|
||||
class="fa fa-paper-plane "></span> {% trans "Interactive Action" %}
|
||||
</li>
|
||||
<li class="event-sync"><span
|
||||
class="fa fa-refresh"></span> {% trans "Synchronised" %}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
{# Initialise JS variables #}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
{# JS variables #}
|
||||
var scheduleRecurrenceDeleteUrl = "{{ url_for("schedule.recurrence.delete.form", {id:':id'}) }}";
|
||||
var layoutPreviewUrl = "{{ theme.rootUri() }}preview/layout/preview/:id";
|
||||
var scheduleSearchUrl = "{{ url_for("schedule.search") }}";
|
||||
var userAgendaViewEnabled = "{{ currentUser.featureEnabled('schedule.agenda') }}";
|
||||
|
||||
{# Custom translations #}
|
||||
var schedulePageTrans = {
|
||||
always: "{% trans "Always" %}",
|
||||
adjustTimesofTimer: "{% trans "Adjust the times of this timer. To add or remove a day, use the Display Profile." %}",
|
||||
daysOfTheWeek: {
|
||||
monday: "{% trans "Monday" %}",
|
||||
tuesday: "{% trans "Tuesday" %}",
|
||||
wednesday: "{% trans "Wednesday" %}",
|
||||
thursday: "{% trans "Thursday" %}",
|
||||
friday: "{% trans "Friday" %}",
|
||||
saturday: "{% trans "Saturday" %}",
|
||||
sunday: "{% trans "Sunday" %}",
|
||||
},
|
||||
};
|
||||
</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/schedule-page.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
|
||||
{% endblock %}
|
||||
@@ -1 +0,0 @@
|
||||
404: Not Found
|
||||
@@ -1,277 +0,0 @@
|
||||
{#
|
||||
/**
|
||||
* 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 %}{{ "Templates"|trans }} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
{% if currentUser.featureEnabled("template.add") %}
|
||||
<button class="btn btn-success XiboFormButton btns" title="{% trans "Add a new Template and jump to the layout editor." %}" href="{{ url_for("template.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Template" %}</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 "Templates" %}</div>
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-grid-name="templateView">
|
||||
<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('template', 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="templates" class="table table-striped" data-content-type="layout" data-content-id-name="layoutId" data-state-preference-name="templateGrid" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th>{% trans "Owner" %}</th>
|
||||
<th>{% trans "Description" %}</th>
|
||||
{% if currentUser.featureEnabled("tag.tagging") %}<th>{% trans "Tags" %}</th>{% endif %}
|
||||
<th>{% trans "Orientation" %}</th>
|
||||
<th>{% trans "Thumbnail" %}</th>
|
||||
<th>{% trans "Sharing" %}</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 }}">
|
||||
{% if not currentUser.featureEnabled("folder.view") %}
|
||||
disableFolders();
|
||||
{% endif %}
|
||||
var table = $("#templates").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("template.search") }}",
|
||||
"data": function(d) {
|
||||
$.extend(d, $("#templates").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
|
||||
}
|
||||
},
|
||||
"columns": [
|
||||
{ "data": "layout", responsivePriority: 2},
|
||||
{
|
||||
"name": "publishedStatus",
|
||||
responsivePriority: 2,
|
||||
"data": function (data, type) {
|
||||
if (data.publishedDate != null) {
|
||||
var now = moment();
|
||||
var published = moment(data.publishedDate);
|
||||
var differenceMinutes = published.diff(now, 'minutes');
|
||||
var momentDifference = moment(now).to(published);
|
||||
|
||||
if (differenceMinutes < -5) {
|
||||
return data.publishedStatus.concat(" - ", translations.publishedStatusFailed);
|
||||
} else {
|
||||
return data.publishedStatus.concat(" - ", translations.publishedStatusFuture + " " + momentDifference);
|
||||
}
|
||||
} else {
|
||||
return data.publishedStatus;
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
{ "data": "owner", responsivePriority: 3},
|
||||
{
|
||||
"name": "description",
|
||||
"data": null,
|
||||
responsivePriority: 3,
|
||||
"render": {"_": "description", "display": "descriptionWithMarkup", "sort": "description"}
|
||||
},
|
||||
{% if currentUser.featureEnabled("tag.tagging") %}{
|
||||
"sortable": false,
|
||||
"visible": false,
|
||||
"data": dataTableCreateTags,
|
||||
responsivePriority: 3
|
||||
},{% endif %}
|
||||
{ data: 'orientation', responsivePriority: 10, visible: false},
|
||||
{
|
||||
responsivePriority: 3,
|
||||
data: 'thumbnail',
|
||||
render: function (data, type, row) {
|
||||
if (type !== 'display') {
|
||||
return row.layoutId;
|
||||
}
|
||||
if (data) {
|
||||
return '<a class="img-replace" data-toggle="lightbox" data-type="image" href="' + data + '">' +
|
||||
'<img class="img-fluid" src="' + data + '" alt="{{ "Thumbnail"|trans }}" />' +
|
||||
'</a>';
|
||||
} else {
|
||||
var addUrl = '{{ url_for("layout.thumbnail.add", {id: ":id"}) }}'.replace(':id', row.layoutId);
|
||||
return '<a class="img-replace generate-layout-thumbnail" href="' + addUrl + '">' +
|
||||
'<img class="img-fluid" src="{{ theme.uri("img/thumbs/placeholder.png") }}" alt="{{ "Add Thumbnail"|trans }}" />' +
|
||||
'</a>';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
"data": "groupsWithPermissions",
|
||||
responsivePriority: 4,
|
||||
"render": dataTableCreatePermissions
|
||||
},
|
||||
{
|
||||
"orderable": false,
|
||||
responsivePriority: 1,
|
||||
"data": dataTableButtonsColumn
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('draw', { form: $("#templates").closest(".XiboGrid").find(".FilterDiv form") } ,dataTableCreateTagEvents);
|
||||
table.on('draw', function(e, settings) {
|
||||
$('#' + e.target.id + ' .generate-layout-thumbnail').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
var $anchor = $(this);
|
||||
$.ajax({
|
||||
url: $anchor.attr('href'),
|
||||
method: 'POST',
|
||||
success: function() {
|
||||
$anchor.find('img').attr('src', $anchor.attr('href'));
|
||||
$anchor.removeClass('generate-layout-thumbnail').attr('data-toggle', 'lightbox');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
dataTableAddButtons(table, $('#templates_wrapper').find('.dataTables_buttons'));
|
||||
|
||||
$("#refreshGrid").click(function () {
|
||||
table.ajax.reload();
|
||||
});
|
||||
|
||||
function templateFormOpen() {
|
||||
if ($('#folder-tree-form-modal').length === 0) {
|
||||
// compile tree folder modal and append it to Form
|
||||
var folderTreeModal = templates['folder-tree'];
|
||||
var treeConfig = {"container": "container-folder-form-tree", "modal": "folder-tree-form-modal"};
|
||||
treeConfig.trans = translations.folderTree;
|
||||
$("body").append(folderTreeModal(treeConfig));
|
||||
|
||||
$("#folder-tree-form-modal").on('hidden.bs.modal', function () {
|
||||
// Fix for 2nd/overlay modal
|
||||
$('.modal:visible').length && $(document.body).addClass('modal-open');
|
||||
|
||||
$(this).data('bs.modal', null);
|
||||
});
|
||||
}
|
||||
|
||||
// select current working folder if one is selected in the grid
|
||||
if ($('#container-folder-tree').jstree("get_selected", true)[0] !== undefined) {
|
||||
$('#templateAddForm' + ' #folderId').val($('#container-folder-tree').jstree("get_selected", true)[0].id);
|
||||
}
|
||||
|
||||
initJsTreeAjax($('#folder-tree-form-modal').find('#container-folder-form-tree'), 'templateAddForm', true, 600);
|
||||
|
||||
$("#templateAddForm").submit(function(e) {
|
||||
e.preventDefault();
|
||||
var form = $(this);
|
||||
|
||||
var url = $(this).data().redirect;
|
||||
|
||||
$.ajax({
|
||||
type: $(this).attr("method"),
|
||||
url: $(this).attr("action"),
|
||||
data: $(this).serialize(),
|
||||
cache: false,
|
||||
dataType:"json",
|
||||
success: function(xhr, textStatus, error) {
|
||||
|
||||
XiboSubmitResponse(xhr, form);
|
||||
|
||||
if (xhr.success) {
|
||||
// Reload the designer
|
||||
XiboRedirect(url.replace(":id", xhr.id));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function layoutPublishFormOpen() {
|
||||
// Nothing to do here, but we use the same form on the layout designer and have a callback registered there
|
||||
}
|
||||
|
||||
function layoutEditFormSaved() {
|
||||
// Nothing to do here.
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,277 +0,0 @@
|
||||
{#
|
||||
/**
|
||||
* 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 %}{{ "Templates"|trans }} | {% endblock %}
|
||||
|
||||
{% block actionMenu %}
|
||||
<div class="widget-action-menu pull-right">
|
||||
{% if currentUser.featureEnabled("template.add") %}
|
||||
<button class="btn btn-success XiboFormButton btns" title="{% trans "Add a new Template and jump to the layout editor." %}" href="{{ url_for("template.add.form") }}"><i class="fa fa-plus-circle" aria-hidden="true"></i> {% trans "Add Template" %}</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 "Templates" %}</div>
|
||||
<div class="widget-body">
|
||||
<div class="XiboGrid" id="{{ random() }}" data-grid-name="templateView">
|
||||
<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('template', 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="templates" class="table table-striped" data-content-type="layout" data-content-id-name="layoutId" data-state-preference-name="templateGrid" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th>{% trans "Owner" %}</th>
|
||||
<th>{% trans "Description" %}</th>
|
||||
{% if currentUser.featureEnabled("tag.tagging") %}<th>{% trans "Tags" %}</th>{% endif %}
|
||||
<th>{% trans "Orientation" %}</th>
|
||||
<th>{% trans "Thumbnail" %}</th>
|
||||
<th>{% trans "Sharing" %}</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 }}">
|
||||
{% if not currentUser.featureEnabled("folder.view") %}
|
||||
disableFolders();
|
||||
{% endif %}
|
||||
var table = $("#templates").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("template.search") }}",
|
||||
"data": function(d) {
|
||||
$.extend(d, $("#templates").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
|
||||
}
|
||||
},
|
||||
"columns": [
|
||||
{ "data": "layout", responsivePriority: 2},
|
||||
{
|
||||
"name": "publishedStatus",
|
||||
responsivePriority: 2,
|
||||
"data": function (data, type) {
|
||||
if (data.publishedDate != null) {
|
||||
var now = moment();
|
||||
var published = moment(data.publishedDate);
|
||||
var differenceMinutes = published.diff(now, 'minutes');
|
||||
var momentDifference = moment(now).to(published);
|
||||
|
||||
if (differenceMinutes < -5) {
|
||||
return data.publishedStatus.concat(" - ", translations.publishedStatusFailed);
|
||||
} else {
|
||||
return data.publishedStatus.concat(" - ", translations.publishedStatusFuture + " " + momentDifference);
|
||||
}
|
||||
} else {
|
||||
return data.publishedStatus;
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
{ "data": "owner", responsivePriority: 3},
|
||||
{
|
||||
"name": "description",
|
||||
"data": null,
|
||||
responsivePriority: 3,
|
||||
"render": {"_": "description", "display": "descriptionWithMarkup", "sort": "description"}
|
||||
},
|
||||
{% if currentUser.featureEnabled("tag.tagging") %}{
|
||||
"sortable": false,
|
||||
"visible": false,
|
||||
"data": dataTableCreateTags,
|
||||
responsivePriority: 3
|
||||
},{% endif %}
|
||||
{ data: 'orientation', responsivePriority: 10, visible: false},
|
||||
{
|
||||
responsivePriority: 3,
|
||||
data: 'thumbnail',
|
||||
render: function (data, type, row) {
|
||||
if (type !== 'display') {
|
||||
return row.layoutId;
|
||||
}
|
||||
if (data) {
|
||||
return '<a class="img-replace" data-toggle="lightbox" data-type="image" href="' + data + '">' +
|
||||
'<img class="img-fluid" src="' + data + '" alt="{{ "Thumbnail"|trans }}" />' +
|
||||
'</a>';
|
||||
} else {
|
||||
var addUrl = '{{ url_for("layout.thumbnail.add", {id: ":id"}) }}'.replace(':id', row.layoutId);
|
||||
return '<a class="img-replace generate-layout-thumbnail" href="' + addUrl + '">' +
|
||||
'<img class="img-fluid" src="{{ theme.uri("img/thumbs/placeholder.png") }}" alt="{{ "Add Thumbnail"|trans }}" />' +
|
||||
'</a>';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
"data": "groupsWithPermissions",
|
||||
responsivePriority: 4,
|
||||
"render": dataTableCreatePermissions
|
||||
},
|
||||
{
|
||||
"orderable": false,
|
||||
responsivePriority: 1,
|
||||
"data": dataTableButtonsColumn
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
table.on('draw', dataTableDraw);
|
||||
table.on('draw', { form: $("#templates").closest(".XiboGrid").find(".FilterDiv form") } ,dataTableCreateTagEvents);
|
||||
table.on('draw', function(e, settings) {
|
||||
$('#' + e.target.id + ' .generate-layout-thumbnail').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
var $anchor = $(this);
|
||||
$.ajax({
|
||||
url: $anchor.attr('href'),
|
||||
method: 'POST',
|
||||
success: function() {
|
||||
$anchor.find('img').attr('src', $anchor.attr('href'));
|
||||
$anchor.removeClass('generate-layout-thumbnail').attr('data-toggle', 'lightbox');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
table.on('processing.dt', dataTableProcessing);
|
||||
dataTableAddButtons(table, $('#templates_wrapper').find('.dataTables_buttons'));
|
||||
|
||||
$("#refreshGrid").click(function () {
|
||||
table.ajax.reload();
|
||||
});
|
||||
|
||||
function templateFormOpen() {
|
||||
if ($('#folder-tree-form-modal').length === 0) {
|
||||
// compile tree folder modal and append it to Form
|
||||
var folderTreeModal = templates['folder-tree'];
|
||||
var treeConfig = {"container": "container-folder-form-tree", "modal": "folder-tree-form-modal"};
|
||||
treeConfig.trans = translations.folderTree;
|
||||
$("body").append(folderTreeModal(treeConfig));
|
||||
|
||||
$("#folder-tree-form-modal").on('hidden.bs.modal', function () {
|
||||
// Fix for 2nd/overlay modal
|
||||
$('.modal:visible').length && $(document.body).addClass('modal-open');
|
||||
|
||||
$(this).data('bs.modal', null);
|
||||
});
|
||||
}
|
||||
|
||||
// select current working folder if one is selected in the grid
|
||||
if ($('#container-folder-tree').jstree("get_selected", true)[0] !== undefined) {
|
||||
$('#templateAddForm' + ' #folderId').val($('#container-folder-tree').jstree("get_selected", true)[0].id);
|
||||
}
|
||||
|
||||
initJsTreeAjax($('#folder-tree-form-modal').find('#container-folder-form-tree'), 'templateAddForm', true, 600);
|
||||
|
||||
$("#templateAddForm").submit(function(e) {
|
||||
e.preventDefault();
|
||||
var form = $(this);
|
||||
|
||||
var url = $(this).data().redirect;
|
||||
|
||||
$.ajax({
|
||||
type: $(this).attr("method"),
|
||||
url: $(this).attr("action"),
|
||||
data: $(this).serialize(),
|
||||
cache: false,
|
||||
dataType:"json",
|
||||
success: function(xhr, textStatus, error) {
|
||||
|
||||
XiboSubmitResponse(xhr, form);
|
||||
|
||||
if (xhr.success) {
|
||||
// Reload the designer
|
||||
XiboRedirect(url.replace(":id", xhr.id));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function layoutPublishFormOpen() {
|
||||
// Nothing to do here, but we use the same form on the layout designer and have a callback registered there
|
||||
}
|
||||
|
||||
function layoutEditFormSaved() {
|
||||
// Nothing to do here.
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||