471 lines
21 KiB
Twig
471 lines
21 KiB
Twig
{#
|
|
/**
|
|
* Copyright (C) 2021 Xibo Signage Ltd
|
|
*
|
|
* Xibo - Digital Signage - http://www.xibo.org.uk
|
|
*
|
|
* This file is part of Xibo.
|
|
*
|
|
* Xibo is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* any later version.
|
|
*
|
|
* Xibo is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#}
|
|
{% extends "authed.twig" %}
|
|
{% import "inline.twig" as inline %}
|
|
|
|
{% block title %}{{ "Users"|trans }} | {% endblock %}
|
|
|
|
{% block actionMenu %}
|
|
<div class="widget-action-menu pull-right">
|
|
{% if currentUser.isSuperAdmin() or (currentUser.isGroupAdmin() and currentUser.featureEnabled("users.add")) %}
|
|
{% if currentUser.getOptionValue("isAlwaysUseManualAddUserForm", 0) %}
|
|
{% set addUserFormUrl = url_for("user.add.form") %}
|
|
{% else %}
|
|
{% set addUserFormUrl = url_for("user.onboarding.form") %}
|
|
{% endif %}
|
|
<button id="user-add-button" class="btn btn-success XiboFormButton" title="{% trans "Add a new User" %}" href="{{ addUserFormUrl }}"><i class="fa fa-user-plus" aria-hidden="true"></i> {% trans "Add User" %}</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 "Users" %}</div>
|
|
<div class="widget-body">
|
|
<div class="XiboGrid" id="{{ random() }}" data-grid-name="usersView">
|
|
<div class="XiboFilter card mb-3 bg-light">
|
|
<div class="FilterDiv card-body" id="Filter">
|
|
<form class="form-inline">
|
|
|
|
{% set title %}{% trans "Username" %}{% endset %}
|
|
{{ inline.inputNameGrid('userName', title) }}
|
|
|
|
{% set title %}{% trans "User Type" %}{% endset %}
|
|
{{ inline.dropdown("userTypeId", "single", title, "", [{userTypeId:null, userType:""}]|merge(userTypes), "userTypeId", "userType") }}
|
|
|
|
{% set title %}{% trans "Retired" %}{% endset %}
|
|
{% set values = [{id: 1, value: "Yes"}, {id: 0, value: "No"}] %}
|
|
{{ inline.dropdown("retired", "single", title, 0, values, "id", "value") }}
|
|
|
|
{% set title %}{% trans "First Name" %}{% endset %}
|
|
{{ inline.input('firstName', title) }}
|
|
|
|
{% set title %}{% trans "Last Name" %}{% endset %}
|
|
{{ inline.input('lastName', title) }}
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="XiboData card pt-3">
|
|
<table id="users" class="table table-striped" data-state-preference-name="userGrid">
|
|
<thead>
|
|
<tr>
|
|
<th>{% trans "Username" %}</th>
|
|
<th>{% trans "Homepage" %}</th>
|
|
<th>{% trans "Home folder" %}</th>
|
|
<th>{% trans "Email" %}</th>
|
|
<th>{% trans "Library Quota" %}</th>
|
|
<th>{% trans "Last Login" %}</th>
|
|
<th>{% trans "Logged In?" %}</th>
|
|
<th>{% trans "Retired?" %}</th>
|
|
<th>{% trans "Two Factor" %}</th>
|
|
<th>{% trans "First Name" %}</th>
|
|
<th>{% trans "Last Name" %}</th>
|
|
<th>{% trans "Phone" %}</th>
|
|
<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 class="rowMenu">{% trans "Row Menu" %}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block javaScript %}
|
|
<script type="text/javascript" nonce="{{ cspNonce }}">
|
|
|
|
$(document).ready(function() {
|
|
var table = $("#users").DataTable({
|
|
"language": dataTablesLanguage,
|
|
dom: dataTablesTemplate,
|
|
serverSide: true,
|
|
stateSave: true,
|
|
responsive: true,
|
|
stateDuration: 0,
|
|
stateLoadCallback: dataTableStateLoadCallback,
|
|
stateSaveCallback: dataTableStateSaveCallback,
|
|
searchDelay: 3000,
|
|
"order": [[0, "asc"]],
|
|
"filter": false,
|
|
ajax: {
|
|
url: "{{ url_for("user.search") }}",
|
|
"data": function (d) {
|
|
$.extend(d, $("#users").closest(".XiboGrid").find(".FilterDiv form").serializeObject());
|
|
}
|
|
},
|
|
"columns": [
|
|
{"data": "userName", responsivePriority: 2},
|
|
{
|
|
"data": "homePage",
|
|
"sortable": false,
|
|
responsivePriority: 3
|
|
},
|
|
{
|
|
data: 'homeFolder',
|
|
responsivePriority: 4
|
|
},
|
|
{"data": "email", responsivePriority: 3},
|
|
{
|
|
"name": "libraryQuota",
|
|
responsivePriority: 3,
|
|
"data": null,
|
|
"render": {"_": "libraryQuota", "display": "libraryQuotaFormatted", "sort": "libraryQuota"}
|
|
},
|
|
{"data": "lastAccessed", "visible": false, responsivePriority: 4},
|
|
{
|
|
"data": "loggedIn",
|
|
responsivePriority: 3,
|
|
"render": dataTableTickCrossColumn,
|
|
"visible": false,
|
|
"sortable": false
|
|
},
|
|
{
|
|
"data": "retired",
|
|
responsivePriority: 3,
|
|
"render": dataTableTickCrossColumn
|
|
},
|
|
{
|
|
"data": "twoFactorTypeId",
|
|
responsivePriority: 5,
|
|
"visible": false,
|
|
"render": function (data, type, row) {
|
|
if (type != "display")
|
|
return data;
|
|
|
|
var icon = "";
|
|
if (data == 1)
|
|
icon = "fa-envelope";
|
|
else if (data == 2)
|
|
icon = "fa-google";
|
|
else
|
|
icon = "fa-times";
|
|
|
|
return '<span class="fa ' + icon + '" title="' + (row.twoFactorDescription) + '"></span>';
|
|
}
|
|
},
|
|
{"data": "firstName", "visible": false, responsivePriority: 5},
|
|
{"data": "lastName", "visible": false, responsivePriority: 5},
|
|
{"data": "phone", "visible": false, responsivePriority: 5},
|
|
{"data": "ref1", "visible": false, responsivePriority: 5},
|
|
{"data": "ref2", "visible": false, responsivePriority: 5},
|
|
{"data": "ref3", "visible": false, responsivePriority: 5},
|
|
{"data": "ref4", "visible": false, responsivePriority: 5},
|
|
{"data": "ref5", "visible": false, responsivePriority: 5},
|
|
{
|
|
"orderable": false,
|
|
responsivePriority: 1,
|
|
"data": dataTableButtonsColumn
|
|
}
|
|
]
|
|
});
|
|
|
|
table.on('draw', dataTableDraw);
|
|
table.on('processing.dt', dataTableProcessing)
|
|
dataTableAddButtons(table, $('#users_wrapper').find('.dataTables_buttons'));
|
|
|
|
$("#refreshGrid").click(function () {
|
|
table.ajax.reload();
|
|
});
|
|
});
|
|
|
|
function userFormOpen(dialog) {
|
|
// Make a select2 from the home page select
|
|
var $userForm = $(dialog).find("form.UserForm");
|
|
var $groupId = $(dialog).find("select[name=groupId]");
|
|
var $userTypeId = $(dialog).find("select[name=userTypeId]");
|
|
var $select = $(dialog).find(".homepage-select");
|
|
$select.select2({
|
|
minimumResultsForSearch: Infinity,
|
|
ajax: {
|
|
url: $select.data("searchUrl"),
|
|
dataType: "json",
|
|
delay: 250,
|
|
data: function (params) {
|
|
return {
|
|
q: params.term, // search term
|
|
page: params.page,
|
|
userId: $userForm.data().userId,
|
|
groupId: $groupId.val(),
|
|
userTypeId: $userTypeId.val(),
|
|
};
|
|
},
|
|
processResults: function (data) {
|
|
var results = [];
|
|
$.each(data.data, function(index, el) {
|
|
results.push({
|
|
"id": el.homepage,
|
|
"text": el.title,
|
|
"content": el.description
|
|
});
|
|
});
|
|
return {
|
|
results: results
|
|
};
|
|
}
|
|
},
|
|
templateResult: function(state) {
|
|
if (!state.content)
|
|
return state.text;
|
|
|
|
return $("<span>" + state.content + "</span>");
|
|
}
|
|
});
|
|
|
|
initFolderPanel(dialog, true);
|
|
|
|
// Validate form
|
|
var $userForm = $('.UserForm');
|
|
forms.validateForm(
|
|
$userForm, // form
|
|
$userForm.parents('.modal-body'), // container
|
|
{
|
|
submitHandler: function (form) {
|
|
// Grab and alter the value in the library quota field
|
|
var libraryQuotaField = $(form).find('input[name=libraryQuota]');
|
|
var libraryQuotaUnitsField = $(form).find('select[name=libraryQuotaUnits]');
|
|
var libraryQuota = libraryQuotaField.val();
|
|
|
|
if (libraryQuotaUnitsField.val() === 'mb') {
|
|
libraryQuota = libraryQuota * 1024;
|
|
} else if (libraryQuotaUnitsField.val() === 'gb') {
|
|
libraryQuota = libraryQuota * 1024 * 1024;
|
|
}
|
|
|
|
// Set the field
|
|
libraryQuotaField.prop('value', libraryQuota);
|
|
|
|
XiboFormSubmit(form);
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Callback when the onboarding form is opened.
|
|
*/
|
|
function onboardingFormOpen(dialog) {
|
|
$(dialog).find('[data-toggle="popover"]').popover();
|
|
|
|
// Init the folder panel
|
|
{% if currentUser.featureEnabled("folder.view") %}
|
|
initFolderPanel(dialog, false, true);
|
|
{% endif %}
|
|
|
|
var navListItems = $(dialog).find('div.setup-panel div a'),
|
|
allWells = $(dialog).find('.setup-content'),
|
|
stepWizard = $(dialog).find('.stepwizard');
|
|
|
|
navListItems.click(function (e) {
|
|
e.preventDefault();
|
|
var $target = $($(this).attr('href')),
|
|
$item = $(this);
|
|
|
|
if (!$item.attr('disabled')) {
|
|
// Set all step links to inactive
|
|
navListItems
|
|
.removeClass('btn-success')
|
|
.addClass('btn-default');
|
|
|
|
// Activate this specific one
|
|
$item.addClass('btn-success');
|
|
|
|
// Hide all the panels and show this specific one
|
|
allWells.hide();
|
|
$target.show();
|
|
$target.find('input:eq(0)').focus();
|
|
|
|
// Set the active panel on the links
|
|
stepWizard.data("active", $target.prop("id"))
|
|
|
|
// Is the next action to finish?
|
|
if ($target.data("next") === "finished") {
|
|
$(dialog).find("#onboarding-steper-next-button").html("{{ "Save"|trans }}");
|
|
} else {
|
|
$(dialog).find("#onboarding-steper-next-button").html("{{ "Next"|trans }}")
|
|
}
|
|
}
|
|
});
|
|
|
|
// Add some buttons.
|
|
$(dialog).find(".modal-footer")
|
|
.append($('<a class="btn btn-default">').html("{{ "Close"|trans }}")
|
|
.click(function(e) {
|
|
e.preventDefault();
|
|
XiboDialogClose();
|
|
}))
|
|
.append($('<a id="onboarding-steper-next-button" class="btn">').html("{{ "Next"|trans }}")
|
|
.addClass("btn-primary")
|
|
.click(function(e) {
|
|
e.preventDefault();
|
|
var steps = $(dialog).find(".stepwizard"),
|
|
curStep = $(dialog).find("#" + steps.data("active")),
|
|
curInputs = curStep.find("input[type='text'],input[type='url']"),
|
|
isValid = true;
|
|
|
|
// What is the next step?
|
|
if (curStep.data("next") === "finished") {
|
|
// Keep the form open
|
|
var $form = $(dialog).find("#userOnboardingForm");
|
|
$form.data("apply", true);
|
|
// Submit the form thereby creating the user
|
|
XiboFormSubmit($form, e, function(xhr) {
|
|
// Callback
|
|
if (xhr.success && xhr.id) {
|
|
{% if currentUser.featureEnabled("folder.view") %}
|
|
// Submit the folder ownerships
|
|
var selected = $(dialog).find("#container-form-folder-tree").jstree("get_selected");
|
|
|
|
// jsTree selects the root folder if all child folders are selected, we need to
|
|
// remove that.
|
|
var rootIndex = selected.indexOf('1');
|
|
if (rootIndex > -1) {
|
|
selected.splice(rootIndex, 1);
|
|
}
|
|
|
|
// View/edit for our group
|
|
var groupIds = {};
|
|
groupIds[xhr.data.groupId] = {
|
|
"view": 1,
|
|
"edit": 1
|
|
};
|
|
$.ajax(permissionsUrl.replace(":entity", "Folder"), {
|
|
"method": "POST",
|
|
"data": {
|
|
"ids": selected.join(","),
|
|
"groupIds": groupIds
|
|
},
|
|
"error": function() {
|
|
toastr.error("{{ "Problem saving folder sharing, please check the User created." }}");
|
|
}
|
|
});
|
|
{% endif %}
|
|
|
|
XiboDialogClose();
|
|
}
|
|
});
|
|
} else if (curStep.data("next") === "onboarding-step-2" && $("input[name='groupId']:checked").val() === "manual") {
|
|
// Load the user add form.
|
|
XiboDialogClose();
|
|
XiboFormRender("{{ url_for("user.add.form") }}");
|
|
} else {
|
|
var nextStepWizard = steps.find("a[href='#" + curStep.data("next") + "']");
|
|
|
|
$(dialog).find(".form-group").removeClass("has-error");
|
|
for (var i = 0; i < curInputs.length; i++) {
|
|
if (!curInputs[i].validity.valid) {
|
|
isValid = false;
|
|
$(curInputs[i]).closest(".form-group").addClass("has-error");
|
|
}
|
|
}
|
|
|
|
// Set the defaults.
|
|
if (curStep.data("next") === "onboarding-step-2") {
|
|
var $userGroupSelected = $("input[name='groupId']:checked");
|
|
$(dialog).find("input[name=homePageId]").val($userGroupSelected.data("defaultHomepageId"));
|
|
}
|
|
|
|
if (isValid) {
|
|
nextStepWizard.removeAttr('disabled').trigger('click');
|
|
}
|
|
}
|
|
}));
|
|
}
|
|
|
|
function userHomeFolderFormOpen(dialog) {
|
|
initFolderPanel(dialog, true);
|
|
}
|
|
|
|
function userHomeFolderMultiselectFormOpen(dialog) {
|
|
var $input = $('<div id="container-form-folder-tree" class="card card-body bg-light"></div>');
|
|
var $helpText = $('<span class="help-block">{{ "Set a home folder to use as the default folder for new content."|trans }}</span>');
|
|
|
|
$(dialog).find('.modal-body').append($input);
|
|
$(dialog).find('.modal-body').append($helpText);
|
|
|
|
initFolderPanel(dialog, true);
|
|
}
|
|
|
|
function initFolderPanel(dialog, isHomeOnSelect = false, isHomeContext = false) {
|
|
var plugins = [];
|
|
|
|
if (!isHomeOnSelect) {
|
|
plugins.push('checkbox');
|
|
}
|
|
|
|
initJsTreeAjax(
|
|
'#container-form-folder-tree',
|
|
'user-add_edit-form',
|
|
true,
|
|
600,
|
|
function(tree, $container) {
|
|
if (!isHomeOnSelect) {
|
|
tree.disable_checkbox(1);
|
|
tree.disable_node(1);
|
|
}
|
|
$container.jstree('open_all');
|
|
},
|
|
function(data) {
|
|
if (isHomeOnSelect && data.action === 'select_node') {
|
|
$(dialog).find('input[name=homeFolderId]').val(data.node.id);
|
|
|
|
// In case we're in a multi-select.
|
|
dialog.data().commitData = {homeFolderId: data.node.id};
|
|
}
|
|
},
|
|
function($node, items) {
|
|
if (isHomeContext) {
|
|
items['home'] = {
|
|
separator_before: false,
|
|
separator_after: false,
|
|
label: translations.folderTreeSetAsHome,
|
|
action: function () {
|
|
$(dialog).find('input[name=homeFolderId]').val($node.id);
|
|
}
|
|
}
|
|
}
|
|
return items;
|
|
},
|
|
plugins,
|
|
$(dialog).find('input[name=homeFolderId]').val()
|
|
);
|
|
|
|
$('.folder-tree-buttons').on('click', 'button', function(ev) {
|
|
const jsTree = $(dialog).find('#container-form-folder-tree').jstree(true);
|
|
if ($(ev.target).attr('id') === 'selectAllBtn') {
|
|
jsTree.select_all();
|
|
} else if ($(ev.target).attr('id') === 'selectNoneBtn') {
|
|
jsTree.deselect_all();
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock %} |