/*
* Copyright 2023 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @private
* Tool that the display all the permissions of a user.
*/
Ext.define('Ametys.plugins.coreui.profiles.UserPermissionsTool', {
extend: "Ametys.tool.Tool",
statics : {
/**
* @private
* Renderer for the assignment cells, which draws an icon representing the assignment
* @param {Object} value The data value for the current cell
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record for the current row
* @return {String} The HTML string to be rendered
*/
_renderAssignment: function(value, metaData, record)
{
var value = value || {};
var glyph = Ametys.plugins.coreui.profiles.UserPermissionsTool._computeGlyph(value.accessResult);
var tooltip = Ametys.plugins.coreui.profiles.ProfilesGridHelper.computeExplanation(value.accessExplanations);
metaData.tdAttr = `data-qtip="${ Ext.String.htmlEncode(tooltip)}" data-qclass="uitool-user-permissions-tooltip"`; // surround with quote to avoid breaking rendering
return '<span class="a-grid-access-result-glyph ' + glyph + '"/>';
},
/**
* @private
* Gets a list of css classes based on an access result.
* @param accessResult a string representing an access result
* @return {String} The HTML string to be rendered
*/
_computeGlyph: function (accessResult)
{
switch (accessResult)
{
case Ametys.plugins.coreui.rights.AccessResult.ANONYMOUS_ALLOWED:
case Ametys.plugins.coreui.rights.AccessResult.ANY_CONNECTED_ALLOWED:
return "ametysicon-check allowed";
case Ametys.plugins.coreui.rights.AccessResult.ANONYMOUS_DENIED:
case Ametys.plugins.coreui.rights.AccessResult.ANY_CONNECTED_DENIED:
return "ametysicon-cross denied"
case Ametys.plugins.coreui.rights.AccessResult.USER_ALLOWED:
case Ametys.plugins.coreui.rights.AccessResult.GROUP_ALLOWED:
return "ametysicon-check-1 allowed";
case Ametys.plugins.coreui.rights.AccessResult.USER_DENIED:
case Ametys.plugins.coreui.rights.AccessResult.GROUP_DENIED:
return "ametysicon-cross-1 denied";
case Ametys.plugins.coreui.rights.AccessResult.UNKNOWN:
default:
// Undetermined
return "ametysicon-minus-symbol unknown disabled";
}
}
},
createPanel: function() {
return this._createAssignmentsGrid();
},
setParams: function(params)
{
this.callParent(arguments);
this._login = params.login;
this._populationId = params.populationId;
// Call the server to retrieve the user full name and update the tool title
// (or close the tool if the user doesn't exists)
this.serverCall('getUser', [this._login, this._populationId], this._getUserCb, {scope: this, ignoreCallbackOnError: false});
},
/**
* @private
* The callback for getuser
* @param {Object} user the user's properties
*/
_getUserCb: function(user)
{
if (user)
{
this._fullName = user.fullname || user.login + " / " + user.populationLabel;
this.setTitle(new Ext.Template("{{i18n PLUGINS_CORE_UI_TOOL_USER_PROFILES_LABEL}}").apply([user.fullname]));
this.setDescription(new Ext.Template("{{i18n PLUGINS_CORE_UI_TOOL_USER_PROFILES_DESCRIPTION}}").apply([user.fullname, user.login, user.populationLabel]))
// mark the tool out of date to start the process or retrieving the permissions
this.showOutOfDate();
}
else
{
this.close();
Ametys.notify({
title: "{{i18n PLUGINS_CORE_UI_TOOL_USER_PROFILES_AUTOCLOSE_TITLE}}",
description: "{{i18n PLUGINS_CORE_UI_TOOL_USER_PROFILES_AUTOCLOSE_DESCRIPTION}}",
type: "warn"
});
this.getLogger().error("Unknown user " + this._login + "#" + this._populationId);
}
},
refresh: function (manual)
{
this.showRefreshing();
var me = this
this._store.load(function() {me.showRefreshed()});
},
/**
* @private
* Create a grid displaying all the assignments of a user
* @param {Object[]} a list of object representing the profiles
* @returns {Ext.grid.Panel} the assignment grid panel
*/
_createAssignmentsGrid: function() {
this._store = Ext.create('Ext.data.Store', this._getStoreCfg());
this._assignmentsGrid = Ext.create("Ext.grid.Panel", {
dockedItems: this._getGridDockedItemsCfg(),
itemId: 'user-permissions-grid',
store: this._store,
cls: 'uitool-user-permissions',
// First the context then all the profiles
columns: this._getFirstColumn(),
enableColumnHide: false, // disable column hiding by the user. Column visibility is handled by the right filter
scrollable: true,
stateful: true,
stateId: this.self.getName() + "$assignment-grid",
features: [{
ftype:'grouping',
enableGroupingMenu: false,
groupHeaderTpl: '{name} ({children.length})',
}]
});
// reset column visibility
// column are stateful, but the visibility is linked to the right filter and not the user interaction
Ext.Array.forEach(this._assignmentsGrid.getColumns(), function(column, index) {
column.setVisible(true);
}, this);
return this._assignmentsGrid;
},
/**
* @private
* Get the store configuration for the assignments grid
* @param {Object[]} a list of object representing all the existing profiles
* @returns {Object} the store configuration
*/
_getStoreCfg: function()
{
var me = this;
return {
model: 'Ametys.plugins.coreui.profiles.UserPermissionsTool.Entry',
proxy: {
type: 'ametys',
role: this.getServerRole(),
serverId : this.getServerId(),
methodName: 'getUserPermissions',
methodArguments: ['login', 'population'],
reader: {
type: 'json',
rootProperty: 'data'
}
},
groupField: 'category',
sorters: [{property: 'order', direction:'DESC'}, {property: 'label', direction:'ASC'}],
listeners: {
scope: this,
'beforeload': function(store, operation)
{
// Get the parameters, or create empty object if needed
var parameters = operation.getParams() || {};
// Add the method arguments
parameters = Ext.apply(parameters, {
login: this._login,
population: this._populationId,
});
// Set the parameters
operation.setParams(parameters);
},
'metachange': function(store, meta) {
let columns = Ametys.plugins.coreui.profiles.ProfilesGridHelper.getColumnsFromPermissions(meta.permissions, Ametys.plugins.coreui.profiles.UserPermissionsTool._renderAssignment);
this._assignmentsGrid.reconfigure(store, this._getFirstColumn().concat(columns));
}
}
}
},
/**
* @private
* Get the definition of the components to dock on the assignments grid
* @returns {Object/Object[]} the component definition
*/
_getGridDockedItemsCfg: function()
{
return {
dock: 'top',
xtype: 'toolbar',
layout: {
type: 'hbox',
align: 'stretch'
},
border: false,
defaultType: 'textfield',
items: [{
xtype: 'component',
html: '{{i18n PLUGINS_CORE_UI_TOOL_USER_PROFILES_FILTERS}}'
}, {
xtype: 'edition.right',
itemId: 'profile-filter',
name: 'profile-filter',
cls: 'ametys',
allowBlank: true,
multiple: false,
stacked: "false",
width: 400,
emptyText: "{{i18n PLUGINS_CORE_UI_TOOL_USER_PROFILES_RIGHT_FILTER}}",
listeners: {change: Ext.bind(this._filterByRight, this)}
}, {
itemId: 'context-filter',
name: 'context-filter',
cls: 'ametys',
width: 400,
emptyText: "{{i18n PLUGINS_CORE_UI_TOOL_USER_PROFILES_CONTEXT_FILTER}}",
listeners: {change: Ext.Function.createBuffered(this._filterByContext, 500, this)}
}, {
xtype: 'button',
// Clear filter
tooltip: "{{i18n PLUGINS_CORE_UI_TOOL_USER_PROFILES_CLEAR_CONTEXT_FILTER}}",
handler: Ext.bind (this._clearFilter, this, ['context-filter'], false),
iconCls: 'a-btn-glyph ametysicon-eraser11 size-16',
cls: 'a-btn-light'
}
]
};
},
_getFirstColumn: function()
{
return [
{
stateId: 'grid-context-label',
text: "",
locked: true,
lockable: false,
dataIndex: "label",
width: 300,
minWidth: 300,
sortable: false,
hideable: false,
}
];
},
/**
* @private
* Filters the columns by right
* @param {Ametys.form.widget.Right} field The right field
*/
_filterByRight: function(field)
{
Ext.suspendLayouts();
var rightId = field.getValue();
if (Ext.isEmpty(rightId))
{
Ext.Array.forEach(this._assignmentsGrid.getColumns(), function(column, index) {
column.setVisible(true);
}, this);
this._store.removeFilter('emptyProfile');
}
else
{
// Computes the columns to let visible
var matchingColumns = [];
// Hide the others (except the first column)
Ext.Array.forEach(this._assignmentsGrid.getColumns(), function(column, index) {
let rights = column.getInitialConfig("rights");
if (index == 0)
{
// Do nothing on label column
}
else if (rights == undefined || Ext.Array.contains(rights, rightId))
{
column.setVisible(true);
matchingColumns.push(column.dataIndex);
}
else
{
column.setVisible(false);
}
}, this);
this._store.addFilter([{
id: 'emptyProfile',
filterFn: function(record)
{
for (var dataIndex of matchingColumns)
{
var assignment = record.get(dataIndex)
if (assignment != undefined && assignment.accessResult != Ametys.plugins.coreui.rights.AccessResult.UNKNOWN)
{
return true;
}
}
return false;
}
}]);
}
Ext.resumeLayouts(true);
},
/**
* @private
* Filters the row by the context label
* @param {Ext.form.field.Text} field The context field
*/
_filterByContext: function(field)
{
Ext.suspendLayouts();
var query = field.getValue();
if (Ext.isEmpty(query))
{
this._store.removeFilter('context');
}
else
{
this._store.addFilter([{
id: 'context',
value: query,
filterFn: function(item) {return item.data.label != null && item.data.label.toLowerCase().includes(this.getValue().toLowerCase())}
}])
}
Ext.resumeLayouts(true);
},
/**
* @private
* Clear the current text filter
* @param {String} id The input id
*/
_clearFilter: function(id)
{
this.getContentPanel().down("#"+id).reset();
},
getMBSelectionInteraction: function() {
return Ametys.tool.Tool.MB_TYPE_NOSELECTION;
}
});
/**
* This class is the data model for user permissions grid entries.
* @private
*/
Ext.define('Ametys.plugins.coreui.profiles.UserPermissionsTool.Entry', {
extend: 'Ext.data.Model',
fields: [
{name: 'label', mapping: 'label'},
{name: 'group', type: 'string', mapping: 'group'},
{name: 'order', type: 'int', mapping: 'order'}
]
});