mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
Merge pull request #257 from YunoHost/enh-permissions
Add permission and group management
This commit is contained in:
commit
7397504d81
12 changed files with 493 additions and 283 deletions
|
@ -5,7 +5,7 @@
|
|||
"private": true,
|
||||
"dependencies": {
|
||||
"bootstrap": "3.3.6",
|
||||
"font-awesome": "4.5.0",
|
||||
"fork-awesome": "1.1.7",
|
||||
"handlebars-helper-intl": "1.1.2",
|
||||
"handlebars": "4.0.11",
|
||||
"sammy": "0.7.6",
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
/*
|
||||
* FontAwesome
|
||||
*/
|
||||
@import "../bower_components/font-awesome/less/font-awesome.less";
|
||||
@import "../bower_components/fork-awesome/less/fork-awesome.less";
|
||||
|
||||
|
||||
// Fixes
|
||||
|
@ -101,6 +101,10 @@ button {
|
|||
color: transparent;
|
||||
}
|
||||
|
||||
.label {
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
/*
|
||||
* The top heading of the doc
|
||||
*
|
||||
|
@ -698,6 +702,62 @@ input[type='radio'].nice-radio {
|
|||
}
|
||||
|
||||
|
||||
/** Groups View **/
|
||||
#view-groups {
|
||||
.panel-heading a {
|
||||
text-decoration: none;
|
||||
&.group-delete {
|
||||
float:right;
|
||||
color:lighten(@label-danger-bg, 20%);
|
||||
:hover {
|
||||
color:@label-danger-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
.panel-body {
|
||||
h3 {
|
||||
margin-top:0;
|
||||
}
|
||||
button.dropdown-toggle {
|
||||
line-height: 15.666px;
|
||||
top: -1.666px;
|
||||
}
|
||||
.dropdown-menu {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.label-removable {
|
||||
// The following match properties from regular btn's
|
||||
display:inline-block;
|
||||
font-size:14px;
|
||||
color:#333;
|
||||
background-color:#f8f8f8;
|
||||
border: #ccc 1px solid;
|
||||
font-weight: normal;
|
||||
margin-bottom:0;
|
||||
position: relative;
|
||||
top: -1.666px;
|
||||
height: 29.666px;
|
||||
vertical-align: middle;
|
||||
padding: 6px 12px;
|
||||
|
||||
margin-right:7px; // Spacing between labels
|
||||
|
||||
> a {
|
||||
margin-left:6px;
|
||||
padding-left:6px;
|
||||
border-left: #ccc 1px solid;
|
||||
color:lighten(@label-info-bg,20);
|
||||
|
||||
text-decoration: none;
|
||||
}
|
||||
> a:hover {
|
||||
color:@label-info-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Flash messages **/
|
||||
#flashMessage {
|
||||
max-height: 120px;
|
||||
|
|
|
@ -71,7 +71,7 @@ gulp.task('js-lint', function() {
|
|||
// Fonts
|
||||
gulp.task('fonts', function() {
|
||||
return gulp.src([
|
||||
'bower_components/font-awesome/fonts/*',
|
||||
'bower_components/fork-awesome/fonts/*',
|
||||
'bower_components/source-code-pro/EOT/*.eot',
|
||||
'bower_components/source-code-pro/OTF/*.otf',
|
||||
'bower_components/source-code-pro/TTF/*.ttf',
|
||||
|
|
|
@ -202,20 +202,23 @@
|
|||
// Get app information
|
||||
app.get('#/apps/:app', function (c) {
|
||||
c.api('/apps/'+c.params['app']+'?raw', function(data) { // http://api.yunohost.org/#!/app/app_info_get_9
|
||||
// Presentation
|
||||
data.settings.allowed_users = (data.settings.allowed_users) ? data.settings.allowed_users.replace(',', ', ')+"." : y18n.t('everyone_has_access');
|
||||
c.api('/users/permissions', function(data_permissions) {
|
||||
|
||||
// Multilingual description
|
||||
data.description = (typeof data.manifest.description[y18n.locale] !== 'undefined') ?
|
||||
data.manifest.description[y18n.locale] :
|
||||
data.manifest.description['en']
|
||||
;
|
||||
// Permissions
|
||||
data.permissions = data_permissions.permissions[c.params['app']+".main"]["allowed"];
|
||||
|
||||
// Multi Instance settings
|
||||
data.manifest.multi_instance = data.manifest.multi_instance ? y18n.t('yes') : y18n.t('no');
|
||||
data.install_time = new Date(data.settings.install_time * 1000);
|
||||
// Multilingual description
|
||||
data.description = (typeof data.manifest.description[y18n.locale] !== 'undefined') ?
|
||||
data.manifest.description[y18n.locale] :
|
||||
data.manifest.description['en']
|
||||
;
|
||||
|
||||
c.view('app/app_info', data);
|
||||
// Multi Instance settings
|
||||
data.manifest.multi_instance = data.manifest.multi_instance ? y18n.t('yes') : y18n.t('no');
|
||||
data.install_time = new Date(data.settings.install_time * 1000);
|
||||
|
||||
c.view('app/app_info', data);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -619,150 +622,6 @@
|
|||
);
|
||||
});
|
||||
|
||||
// Manage app access
|
||||
app.get('#/apps/:app/access', function (c) {
|
||||
c.api('/apps/'+c.params['app']+'?raw', function(data) { // http://api.yunohost.org/#!/app/app_info_get_9
|
||||
c.api('/users', function(dataUsers) {
|
||||
|
||||
// allowed_users as array
|
||||
if (typeof data.settings.allowed_users !== 'undefined') {
|
||||
if (data.settings.allowed_users.length === 0) {
|
||||
// Force empty array, means no user has access
|
||||
data.settings.allowed_users = [];
|
||||
}
|
||||
else {
|
||||
data.settings.allowed_users = data.settings.allowed_users.split(',');
|
||||
}
|
||||
} else {
|
||||
data.settings.allowed_users = []; // Force array
|
||||
// if 'allowed_users' is undefined, everyone has access
|
||||
// that means that undefined is different from empty array
|
||||
data.settings.allow_everyone = true;
|
||||
}
|
||||
|
||||
// Available users
|
||||
data.users = [];
|
||||
$.each(dataUsers.users, function(username, user){
|
||||
// Do not list allowed_users in select list
|
||||
if ( data.settings.allowed_users.indexOf(username) === -1 ) {
|
||||
data.users.push({
|
||||
value: username,
|
||||
label: user.fullname+' ('+user.mail+')'
|
||||
});
|
||||
} else {
|
||||
// Complete allowed_users data
|
||||
data.settings.allowed_users[data.settings.allowed_users.indexOf(username)] = {
|
||||
username: username,
|
||||
fullname: user.fullname,
|
||||
mail: user.mail,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
c.view('app/app_access', data);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Remove all access
|
||||
app.get('#/apps/:app/access/remove', function (c) {
|
||||
c.confirm(
|
||||
y18n.t('applications'),
|
||||
y18n.t('confirm_access_remove_all', [c.params['app']]),
|
||||
function() {
|
||||
var params = {
|
||||
apps: c.params['app'],
|
||||
users: []
|
||||
};
|
||||
c.api('/access?'+c.serialize(params), function(data) { // http://api.yunohost.org/#!/app/app_removeaccess_delete_12
|
||||
store.clear('slide');
|
||||
c.redirect('#/apps/'+ c.params['app']+ '/access');
|
||||
}, 'DELETE', params);
|
||||
},
|
||||
function() {
|
||||
store.clear('slide');
|
||||
c.redirect('#/apps/'+ c.params['app']+ '/access');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Remove access to a specific user
|
||||
app.get('#/apps/:app/access/remove/:user', function (c) {
|
||||
c.confirm(
|
||||
y18n.t('applications'),
|
||||
y18n.t('confirm_access_remove_user', [c.params['app'], c.params['user']]),
|
||||
function() {
|
||||
var params = {
|
||||
apps: c.params['app'],
|
||||
users: c.params['user']
|
||||
};
|
||||
c.api('/access?'+c.serialize(params), function(data) { // http://api.yunohost.org/#!/app/app_removeaccess_delete_12
|
||||
store.clear('slide');
|
||||
c.redirect('#/apps/'+ c.params['app']+ '/access');
|
||||
}, 'DELETE', params); // passing 'params' here is useless because jQuery doesn't handle ajax datas for DELETE requests. Passing parameters through uri.
|
||||
},
|
||||
function() {
|
||||
store.clear('slide');
|
||||
c.redirect('#/apps/'+ c.params['app']+ '/access');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Grant all access
|
||||
app.get('#/apps/:app/access/add', function (c) {
|
||||
c.confirm(
|
||||
y18n.t('applications'),
|
||||
y18n.t('confirm_access_add', [c.params['app']]),
|
||||
function() {
|
||||
var params = {
|
||||
apps: c.params['app'],
|
||||
users: null
|
||||
};
|
||||
c.api('/access', function() { // http://api.yunohost.org/#!/app/app_addaccess_put_13
|
||||
store.clear('slide');
|
||||
c.redirect('#/apps/'+ c.params['app'] +'/access');
|
||||
}, 'PUT', params);
|
||||
},
|
||||
function() {
|
||||
store.clear('slide');
|
||||
c.redirect('#/apps/'+ c.params['app']+ '/access');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Grant access for a specific user
|
||||
app.post('#/apps/:app/access/add', function (c) {
|
||||
var params = {
|
||||
users: c.params['user'],
|
||||
apps: c.params['app']
|
||||
};
|
||||
c.api('/access', function() { // http://api.yunohost.org/#!/app/app_addaccess_put_13
|
||||
store.clear('slide');
|
||||
c.redirect('#/apps/'+ c.params['app'] +'/access');
|
||||
}, 'PUT', params);
|
||||
});
|
||||
|
||||
// Clear access (reset)
|
||||
app.get('#/apps/:app/access/clear', function (c) {
|
||||
c.confirm(
|
||||
y18n.t('applications'),
|
||||
y18n.t('confirm_access_clear', [c.params['app']]),
|
||||
function() {
|
||||
var params = {
|
||||
apps: c.params['app']
|
||||
};
|
||||
c.api('/access', function() { //
|
||||
store.clear('slide');
|
||||
c.redirect('#/apps/'+ c.params['app'] +'/access');
|
||||
}, 'POST', params);
|
||||
},
|
||||
function() {
|
||||
store.clear('slide');
|
||||
c.redirect('#/apps/'+ c.params['app']+ '/access');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Make app default
|
||||
app.get('#/apps/:app/default', function (c) {
|
||||
c.confirm(
|
||||
|
|
|
@ -5,6 +5,196 @@
|
|||
|
||||
var PASSWORD_MIN_LENGTH = 4;
|
||||
|
||||
// A small utility to convert a string to title case
|
||||
// e.g. "hAvE a NicE dAy" --> "Have A Nice Day"
|
||||
// Savagely stolen from https://stackoverflow.com/a/196991
|
||||
function toTitleCase(str) {
|
||||
return str.replace(
|
||||
/\w\S*/g,
|
||||
function(txt) {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups and permissions
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Update group or permissions
|
||||
*
|
||||
* @model data organize in the same way than /users/groups?full&include_primary_groups
|
||||
* @params.operation "add"|"remove"
|
||||
* @params.type "members"|"permissions"
|
||||
* @param.item Name of the user or the permission to add or remove
|
||||
* @param.group Name of the group affected
|
||||
*
|
||||
* This function is built to be apply with params generated by the use of
|
||||
* HTML dataset attributes (e.g. link in the partial inline view "label" in group_list.ms)
|
||||
*
|
||||
* @return void
|
||||
**/
|
||||
function updateGroup(model, params) {
|
||||
var type = params.type;
|
||||
var operation = params.operation;
|
||||
var item = params.item;
|
||||
var groupname = params.group;
|
||||
var group = data.groups[groupname];
|
||||
var to = (operation == 'add')?group[type]:group[type + 'Inv'];
|
||||
var from = (operation == 'add')?group[type+'Inv']:group[type];
|
||||
// Do nothing, if array of destination already contains the item
|
||||
if (from.indexOf(item) === -1) return;
|
||||
|
||||
// Hack to disable pacman loader if any
|
||||
if ($('div.loader').length === 0) {
|
||||
$('#main').append('<div class="loader loader-content" style="display: none"></div>');
|
||||
}
|
||||
$('div.loader').css('display', 'none');
|
||||
|
||||
// Update group
|
||||
var params = {}; var url;
|
||||
if (type == 'members') {
|
||||
url = '/users/groups/' + groupname;
|
||||
params[operation] = [item];
|
||||
}
|
||||
else {
|
||||
url = '/users/permissions/' + item;
|
||||
params[operation] = [groupname];
|
||||
}
|
||||
c.api(url, function(data_update) {
|
||||
to.push(item);
|
||||
from.splice(from.indexOf(item), 1);
|
||||
updateView(data);
|
||||
}, 'PUT', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the view with the new model
|
||||
*
|
||||
* @model data organize in the same way than /users/groups?full&include_primary_groups
|
||||
*
|
||||
* @return void
|
||||
**/
|
||||
function updateView(model) {
|
||||
// Sort in aphanumerical order to improve user experience
|
||||
for (var group in model.groups) {
|
||||
model.groups[group].permissions.sort();
|
||||
model.groups[group].permissionsInv.sort();
|
||||
model.groups[group].members.sort();
|
||||
model.groups[group].membersInv.sort();
|
||||
}
|
||||
|
||||
// Manual render, we don't use c.view to avoid scrollTop and other
|
||||
// uneeded behaviour
|
||||
var rendered = c.render('views/user/group_list.ms', model);
|
||||
rendered.swap(function () {
|
||||
// Add click event to get a nice "reactive" interface
|
||||
jQuery(".group-update").on('click', function (e) {
|
||||
updateGroup(model, jQuery(this)[0].dataset);
|
||||
return false;
|
||||
});
|
||||
jQuery(".group-add-user").on('click', function (e) {
|
||||
data.groups[$(this)[0].dataset.user].display = true;
|
||||
updateView(data);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
app.get('#/groups', function (c) {
|
||||
c.api('/users/groups?full&include_primary_groups', function(data_groups) {
|
||||
c.api('/users', function(data_users) {
|
||||
c.api('/users/permissions?short', function(data_permissions) {
|
||||
//var perms = data_permissions.permissions;
|
||||
var specific_perms = {};
|
||||
var all_perms = data_permissions.permissions;
|
||||
var users = Object.keys(data_users.users);
|
||||
|
||||
// Enrich groups data with primary group indicator and inversed items list
|
||||
for (var group in data_groups.groups) {
|
||||
data_groups.groups[group].primary = users.indexOf(group) !== -1;
|
||||
data_groups.groups[group].permissionsInv = all_perms.filter(function(item) {
|
||||
return data_groups.groups[group].permissions.indexOf(item) === -1;
|
||||
}).filter(function(item) {
|
||||
return group != "visitors" || (item != "mail.main" && item != "xmpp.main"); // Remove 'email' and 'xmpp' in visitors's permission choice list
|
||||
});
|
||||
data_groups.groups[group].membersInv = users.filter(function(item) {
|
||||
return data_groups.groups[group].members.indexOf(item) === -1;
|
||||
});
|
||||
}
|
||||
|
||||
// Declare all_users and visitors has special
|
||||
data_groups.groups['all_users'].special = true;
|
||||
data_groups.groups['visitors'].special = true;
|
||||
|
||||
// Data given to the view with 2 functions to convert technical
|
||||
// permission id to display names
|
||||
data = {
|
||||
'groups':data_groups.groups,
|
||||
'displayPermission': function (text) {
|
||||
// Display a permission correctly for a human
|
||||
text = text.replace('.main', '');
|
||||
if (text.indexOf('.') > -1)
|
||||
text = text.replace('.', ' (') + ')';
|
||||
|
||||
if (text == "mail")
|
||||
text = "E-mail";
|
||||
else if (text == "xmpp")
|
||||
text = "XMPP";
|
||||
else
|
||||
text = toTitleCase(text);
|
||||
|
||||
return text;
|
||||
},
|
||||
'displayUser': function (text) {
|
||||
return text;
|
||||
},
|
||||
};
|
||||
updateView(data);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Create a new group
|
||||
app.get('#/groups/create', function (c) {
|
||||
c.view('user/group_create', {});
|
||||
});
|
||||
|
||||
app.post('#/groups/create', function (c) {
|
||||
c.params['groupname'] = c.params['groupname'].replace(' ', '_').toLowerCase();
|
||||
c.api('/users/groups', function(data) {
|
||||
c.redirect('#/groups');
|
||||
}, 'POST', c.params.toHash());
|
||||
});
|
||||
|
||||
app.get('#/groups/:group/delete', function (c) {
|
||||
|
||||
var params = {};
|
||||
|
||||
// make confirm content
|
||||
var confirmModalContent = $('<div>'+ y18n.t('confirm_delete', [c.params['group']]) +'</div>');
|
||||
|
||||
// display confirm modal
|
||||
c.confirm(
|
||||
y18n.t('groups'),
|
||||
confirmModalContent,
|
||||
function(){
|
||||
c.api('/users/groups/'+ c.params['group'], function(data) {
|
||||
c.redirect('#/groups');
|
||||
}, 'DELETE', params);
|
||||
},
|
||||
function(){
|
||||
//store.clear('slide');
|
||||
c.redirect('#/groups');
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Users
|
||||
*
|
||||
|
@ -234,5 +424,8 @@
|
|||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
})();
|
||||
|
|
|
@ -101,12 +101,43 @@
|
|||
|
||||
// equality stuff because mustache/Handlebars is lame
|
||||
// source https://stackoverflow.com/a/31632215
|
||||
Handlebars.registerHelper('eq', function(a, b) {
|
||||
return a === b;
|
||||
Handlebars.registerHelper({
|
||||
eq: function(a, b) {
|
||||
return a === b;
|
||||
},
|
||||
neq: function(a, b) {
|
||||
return a !== b;
|
||||
},
|
||||
lt: function (v1, v2) {
|
||||
return v1 < v2;
|
||||
},
|
||||
gt: function (v1, v2) {
|
||||
return v1 > v2;
|
||||
},
|
||||
lte: function (v1, v2) {
|
||||
return v1 <= v2;
|
||||
},
|
||||
gte: function (v1, v2) {
|
||||
return v1 >= v2;
|
||||
},
|
||||
and: function () {
|
||||
return Array.prototype.slice.call(arguments).every(function (arg) {
|
||||
return (Array.isArray(arg))?arg.length !== 0:arg;
|
||||
});
|
||||
},
|
||||
or: function () {
|
||||
return Array.prototype.slice.call(arguments, 0, -1).some(function (arg) {
|
||||
return (Array.isArray(arg))?arg.length !== 0:arg;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('neq', function(a, b) {
|
||||
return a !== b;
|
||||
// Be able to call a function given in context
|
||||
Handlebars.registerHelper('call', function () {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var func = args.shift();
|
||||
args.pop();
|
||||
return func.apply(null, args);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('in', function(a) {
|
||||
|
|
|
@ -5,22 +5,13 @@
|
|||
"advanced": "Advanced",
|
||||
"remove": "Remove",
|
||||
"administration_password": "Administration password",
|
||||
"allowed_users": "Allowed users",
|
||||
"all_apps": "All apps",
|
||||
"all_apps": "All apps",
|
||||
"api_not_responding": "The YunoHost API is not responding. Maybe 'yunohost-api' is down or got restarted?",
|
||||
"app_access": "Access",
|
||||
"app_access_addall_btn": "Enable access to all",
|
||||
"app_access_addall_desc": "All existing users will have access to %s.",
|
||||
"app_access_clearall_btn": "Clear all access",
|
||||
"app_access_clearall_desc": "Every user will have access to %s.",
|
||||
"app_access_removeall_btn": "Remove all access",
|
||||
"app_access_removeall_desc": "No users will have access to %s.",
|
||||
"app_access_title": "%s access",
|
||||
"app_change_label": "Change Label",
|
||||
"app_change_url": "Change URL",
|
||||
"app_debug_no_logs": "Application's logs are not available",
|
||||
"app_debug_tab": "Display debug information",
|
||||
"app_info_access_desc": "Manage user access. Allowed users: %s",
|
||||
"app_info_access_desc": "Groups / users currently allowed to access this app:",
|
||||
"app_info_changelabel_desc": "Change app label in the portal.",
|
||||
"app_info_debug_desc": "Display debugging information for this application.",
|
||||
"app_info_default_desc": "Redirect domain root to this application (%s).",
|
||||
|
@ -69,10 +60,6 @@
|
|||
"check_mx": "MX record",
|
||||
"check_stmp": "port 25 access",
|
||||
"close": "Close",
|
||||
"confirm_access_add": "Are you sure you want to add access to %s for all users?",
|
||||
"confirm_access_clear": "Are you sure you want to clear all access to %s?",
|
||||
"confirm_access_remove_all": "Are you sure you want to remove all access to %s?",
|
||||
"confirm_access_remove_user": "Are you sure you want to remove access to %s for %s?",
|
||||
"confirm_app_change_url": "Are you sure you want to change the app access URL?",
|
||||
"confirm_app_default": "Are you sure you want to make this app default?",
|
||||
"confirm_change_maindomain": "Are you sure you want to change the main domain?",
|
||||
|
@ -146,7 +133,6 @@
|
|||
"error_server": "Server error",
|
||||
"error_server_unexpected": "Unexpected server error (%s)",
|
||||
"error_connection_interrupted": "The server closed the connection instead of answering it. Has nginx or the yunohost-api been restarted or stoppted for some reason? (Error code/message: %s)",
|
||||
"everyone_has_access": "Everyone has access.",
|
||||
"experimental_warning": "Warning: this feature is experimental and not consider stable, you shouldn't be using it except if you know what you are doing.",
|
||||
"filesystem": "Filesystem",
|
||||
"firewall": "Firewall",
|
||||
|
@ -158,6 +144,20 @@
|
|||
"gateway": "Gateway: ",
|
||||
"good_practices_about_admin_password": "You are now about to define a new admin password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).",
|
||||
"good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).",
|
||||
"group": "Group",
|
||||
"group_name": "Group name",
|
||||
"group_all_users": "All users",
|
||||
"group_visitors": "Visitors",
|
||||
"group_format_name_help": "You can use alpha-numeric chars and space",
|
||||
"group_add_member": "Add a user",
|
||||
"group_add_permission": "Add a permission",
|
||||
"group_new": "New group",
|
||||
"group_explain_all_users": "This is a special group containing all users accounts on the server",
|
||||
"group_explain_visitors": "This is a special group representing anonymous visitors",
|
||||
"group_specific_permissions": "User specific permissions",
|
||||
"groups_and_permissions": "Groups and permissions",
|
||||
"groups_and_permissions_manage": "Manage groups and permissions",
|
||||
"permissions": "Permissions",
|
||||
"home": "Home",
|
||||
"hook_adminjs_group_configuration": "System configurations",
|
||||
"hook_conf_cron": "Automatic tasks",
|
||||
|
@ -227,10 +227,9 @@
|
|||
"network": "Network",
|
||||
"next": "Next",
|
||||
"no": "No",
|
||||
"no_allowed_users": "No allowed users.",
|
||||
"no_installed_apps": "No installed apps.",
|
||||
"no_log": "No log.",
|
||||
"no_user_to_add": "No more users to add.",
|
||||
"nobody": "Nobody",
|
||||
"non_compatible_api": "Non-compatible API",
|
||||
"ok": "OK",
|
||||
"only_highquality_apps": "Only high-quality apps",
|
||||
|
@ -288,7 +287,6 @@
|
|||
"read_more": "Read more",
|
||||
"reception": "Reception",
|
||||
"refresh_app_list": "Refresh list",
|
||||
"remove_access": "Remove access",
|
||||
"request_adoption": "waiting adoption",
|
||||
"request_adoption_details": "The current maintainer would like to stop maintaining this app. Feel free to propose yourself as the new maintainer!",
|
||||
"request_help": "need help",
|
||||
|
@ -298,7 +296,6 @@
|
|||
"running": "Running",
|
||||
"save": "Save",
|
||||
"search_for_apps": "Search for apps...",
|
||||
"select_user": "Select user",
|
||||
"select_all": "Select all",
|
||||
"select_none": "Select none",
|
||||
"service_description": "Description:",
|
||||
|
@ -385,7 +382,6 @@
|
|||
"users_no": "No users.",
|
||||
"versions": "Versions",
|
||||
"version": "Version",
|
||||
"view_user_profile": "View %s's profile",
|
||||
"warning_first_user": "You probably need to <a href='#/users/create' class='alert-link'>create a user</a> first.",
|
||||
"write": "Write",
|
||||
"wrong_password": "Wrong password",
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
<div class="btn-breadcrumb">
|
||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
||||
<a href="#/apps" class="hidden-xs">{{t 'applications'}}</a>
|
||||
<a href="#/apps" class="visible-xs">…</a>
|
||||
<a href="#/apps/{{settings.id}}">{{settings.label}}</a>
|
||||
<a href="#/apps/{{settings.id}}/access">{{t 'app_access'}}</a>
|
||||
</div>
|
||||
|
||||
<div class="separator"></div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h2 class="panel-title"><span class="fa-fw fa-users"></span> {{t 'allowed_users'}}</h2>
|
||||
</div>
|
||||
{{#if settings.allowed_users}}
|
||||
<div class="list-group">
|
||||
{{#each settings.allowed_users}}
|
||||
<div class="list-group-item">
|
||||
<a role="button" href="#/apps/{{settings.id}}/access/remove/{{username}}" class="btn btn-danger slide back pull-right">
|
||||
<span class="fa-trash-o"></span> {{t 'remove_access'}}
|
||||
</a>
|
||||
<h3 class="list-group-item-heading">{{fullname}} ({{mail}})</h3>
|
||||
<a href="#/users/{{username}}">{{t 'view_user_profile' username}}</a>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="panel-body">
|
||||
{{#if settings.allow_everyone}}
|
||||
<p class="text-success">{{t 'everyone_has_access'}}</p>
|
||||
{{else}}
|
||||
<p class="text-warning">{{t 'no_allowed_users'}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="panel-footer">
|
||||
{{#if users}}
|
||||
<form method="POST" action="#/apps/{{settings.id}}/access/add" class="row">
|
||||
<input type="hidden" name="app" value="{{settings.id}}">
|
||||
<div class="col-sm-6">
|
||||
<select name="user" required class="form-control">
|
||||
<option value="" default disabled selected>{{t 'select_user'}}</option>
|
||||
{{#users}}<option value="{{value}}">{{label}}</option>{{/users}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<br class="visible-xs" />
|
||||
<input type="submit" class="btn btn-success slide back" value="{{t 'add'}}">
|
||||
</div>
|
||||
</form>
|
||||
{{else}}
|
||||
<p class="text-warning">{{t 'no_user_to_add'}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-group" id="accordion">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h2 class="panel-title">
|
||||
<a data-toggle="collapse" data-parent="#accordion" href="#collapseOne">
|
||||
<span class="fa-fw fa-wrench"></span> {{t 'operations'}}
|
||||
</a>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="collapseOne" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
{{#if users}}
|
||||
<div class="container">
|
||||
<p>{{t 'app_access_addall_desc' settings.label}}</p>
|
||||
<a role="button" href="#/apps/{{settings.id}}/access/add" class="btn btn-success slide back">
|
||||
<span class="fa-plus"></span> {{t 'app_access_addall_btn'}}
|
||||
</a>
|
||||
</div>
|
||||
<hr>
|
||||
{{/if}}
|
||||
{{#if settings.allowed_users}}
|
||||
<div class="container">
|
||||
<p>{{t 'app_access_removeall_desc' settings.label}}</p>
|
||||
<a role="button" href="#/apps/{{settings.id}}/access/remove" class="btn btn-danger slide back">
|
||||
<span class="fa-trash-o"></span> {{t 'app_access_removeall_btn'}}
|
||||
</a>
|
||||
</div>
|
||||
<hr>
|
||||
{{/if}}
|
||||
{{#unless settings.allow_everyone}}
|
||||
<div class="container">
|
||||
<p>{{t 'app_access_clearall_desc' settings.label}}</p>
|
||||
<a role="button" href="#/apps/{{settings.id}}/access/clear" class="btn btn-primary slide back">
|
||||
<span class="fa-unlock-alt"></span> {{t 'app_access_clearall_btn'}}
|
||||
</a>
|
||||
</div>
|
||||
{{/unless}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -48,9 +48,10 @@
|
|||
</div>
|
||||
<hr>
|
||||
<div class="container">
|
||||
<p>{{t 'app_info_access_desc' settings.allowed_users}}</p>
|
||||
<a role="button" href="#/apps/{{settings.id}}/access" class="btn btn-info slide">
|
||||
<span class="fa-lock"></span> {{t 'app_access'}}
|
||||
<p>{{t 'app_info_access_desc'}} {{#each permissions}} {{ucwords .}}{{#unless @last}}, {{/unless}} {{ else }} {{t 'nobody'}} {{/each}}
|
||||
</p>
|
||||
<a role="button" href="#/groups" class="btn btn-info slide">
|
||||
<span class="fa-key-modern"></span> {{t 'groups_and_permissions_manage'}}
|
||||
</a>
|
||||
</div>
|
||||
<hr>
|
||||
|
|
30
src/views/user/group_create.ms
Normal file
30
src/views/user/group_create.ms
Normal file
|
@ -0,0 +1,30 @@
|
|||
<div class="btn-breadcrumb">
|
||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
||||
<a href="#/users" class="visible-xs">…</a>
|
||||
<a href="#/users" class="hidden-xs">{{t 'users'}}</a>
|
||||
<a href="#/groups" class="visible-xs">…</a>
|
||||
<a href="#/groups" class="hidden-xs">{{t 'group_permissions'}}</a>
|
||||
<a href="#/groups/create">{{t 'group_new'}}</a>
|
||||
</div>
|
||||
|
||||
<div class="separator"></div>
|
||||
|
||||
<form action="#/groups/create" method="POST" class="form-horizontal">
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<div class="form-group">
|
||||
<label for="groupname" class="col-sm-3 control-label">{{t 'group_name'}}</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" id="groupname" name="groupname" class="form-control" placeholder="my group name" required pattern="[A-Za-z0-9_ ]+">
|
||||
<div class="help-block">{{t 'group_format_name_help'}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<input type="submit" role="button" class="btn btn-success slide back" value="{{t 'save'}}">
|
||||
</div>
|
||||
|
||||
</form>
|
134
src/views/user/group_list.ms
Normal file
134
src/views/user/group_list.ms
Normal file
|
@ -0,0 +1,134 @@
|
|||
<div class="btn-breadcrumb">
|
||||
<a href="#/"><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
||||
<a href="#/users">{{t 'users'}}</a>
|
||||
<a href="#/groups">{{t 'groups_and_permissions'}}</a>
|
||||
</div>
|
||||
|
||||
<div class="actions-group">
|
||||
<a role="button" href="#/groups/create" class="btn btn-success slide">
|
||||
<span class="fa-plus"></span> {{t 'group_new'}}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="separator"></div>
|
||||
|
||||
{{!-- ======================== Partial inline view ======================= --}}
|
||||
{{#*inline "label"}}
|
||||
<span class="label label-default label-removable">
|
||||
<span class="fa-fw fa-{{icon}}"></span>
|
||||
{{text}}
|
||||
<a role="button" data-type="{{type}}s" data-operation="remove" data-item="{{value}}" data-group="{{group}}" class="group-update">
|
||||
<span class="fa-close" style="margin-left:5px"></span>
|
||||
<span class="sr-only">{{t 'delete'}}</span>
|
||||
</a>
|
||||
</span>
|
||||
{{/inline}}
|
||||
|
||||
{{#*inline "labelsLine"}}
|
||||
{{#each items}}
|
||||
{{> label text=(call ../display .) value=. icon=../icon type=../type item=. group=../group}}
|
||||
{{/each}}
|
||||
{{#if inv}}
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="fa-plus"></span> {{t (concat 'group_add_' type)}}
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{{#each inv}}
|
||||
<li><a href="#" data-type="{{../type}}s" data-operation="add" data-item="{{.}}" data-group="{{../group}}" class="group-update">{{call ../display .}}</a></li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/inline}}
|
||||
<div id="view-groups">
|
||||
|
||||
{{!-- ======================== Groups ======================= --}}
|
||||
{{#each groups}}
|
||||
{{#unless primary}}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" role="tab" id="heading-context-group-{{@key}}">
|
||||
<h2 class="panel-title">
|
||||
<a role="button" data-toggle="collapse" href="#collapse-group-{{@key}}" aria-expanded="false" aria-controls="collapse-group-{{@key}}">
|
||||
<span class="fa-fw fa-group"></span> {{#if special}}{{t (concat 'group_' @key)}}{{else}}{{t 'group'}} "{{ucwords @key}}"{{/if}}
|
||||
</a>
|
||||
{{#unless special}}
|
||||
<a href="#/groups/{{@key}}/delete" role="button" class="group-delete">
|
||||
<span class="fa-close"></span>
|
||||
<span class="sr-only">{{t 'delete'}}</span>
|
||||
</a>
|
||||
{{/unless}}
|
||||
</h2>
|
||||
</div>
|
||||
<div id="collapse-group-{{@key}}" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="heading-context-group-{{@key}}">
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-2">
|
||||
<h3>{{t 'users'}}</h3>
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
{{#if special}}
|
||||
<div style="font-style:italic"><span class="fa-info-circle"></span> {{t (concat 'group_explain_' @key)}}</div>
|
||||
{{else}}
|
||||
{{> labelsLine display=../displayUser icon="user" type="member" items=members inv=membersInv group=@key}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div class="col-sm-2">
|
||||
<h3>{{t 'permissions'}}</h3>
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
{{> labelsLine display=../displayPermission icon="key-modern" type="permission" items=permissions inv=permissionsInv group=@key}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
|
||||
|
||||
{{!-- ====================== User specific permissions ==================== --}}
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading" role="tab" id="heading-context-specific">
|
||||
<h2 class="panel-title">
|
||||
<a role="button" data-toggle="collapse" href="#collapse-specific" aria-expanded="false" aria-controls="collapse-specific">
|
||||
<span class="fa-fw fa-group"></span> {{t 'group_specific_permissions'}}
|
||||
</a>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="collapse-specific" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="heading-context-specific">
|
||||
<div class="panel-body">
|
||||
{{#each groups}}
|
||||
{{#if (or (and primary permissions) display)}}
|
||||
<div class="row">
|
||||
<div class="col-sm-2">
|
||||
<h3><span class="fa-fw fa-user"></span> {{@key}}</h3>
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
{{> labelsLine display=../displayPermission icon="key-modern" type="permission" items=permissions inv=permissionsInv group=@key}}
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="fa-plus"></span> {{t 'group_add_member'}}
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{{#each groups}}
|
||||
{{#if primary}}
|
||||
{{#unless (or permissions display)}}
|
||||
<li><a href="#" data-user="{{@key}}" class="group-add-user">{{@key}}</a></li>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -4,6 +4,9 @@
|
|||
</div>
|
||||
|
||||
<div class="actions-group">
|
||||
<a href="#/groups" class="btn btn-info">
|
||||
<span class="fa-key-modern"></span> {{t 'groups_and_permissions_manage'}}
|
||||
</a>
|
||||
<a role="button" href="#/users/create" class="btn btn-success slide">
|
||||
<span class="fa-plus"></span> {{t 'users_new'}}
|
||||
</a>
|
||||
|
|
Loading…
Add table
Reference in a new issue