Files
Cloud-CMS/views/user-page.twig

471 lines
21 KiB
Twig
Raw Permalink Normal View History

2025-12-02 10:32:59 -05:00
{#
/**
* 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 %}