Merge pull request #1754 from coreos-inc/team-add-perms

Better UI and permissions handling for robots and teams
This commit is contained in:
josephschorr 2016-09-06 17:21:19 -04:00 committed by GitHub
commit cd8b45e25b
21 changed files with 895 additions and 458 deletions

View file

@ -20,25 +20,9 @@ angular.module('quay').directive('createEntityDialog', function () {
'entityCreateCompleted': '&entityCreateCompleted'
},
controller: function($scope, $element, ApiService, UIService, TableService, RolesService, UserService) {
$scope.TableService = TableService;
$scope.options = {
'predicate': 'last_modified_datetime',
'reverse': false,
'filter': ''
};
var handleRepoCheckChange = function() {
$scope.repositories.forEach(function(repo) {
if ($scope.checkedRepos.isChecked(repo)) {
if (repo['permission'] == 'none') {
repo['permission'] = 'read';
}
} else {
repo['permission'] = 'none';
}
});
controller: function($scope, $element, ApiService, UIService, UserService) {
$scope.context = {
'setPermissionsCounter': 0
};
$scope.$on('$destroy', function() {
@ -47,16 +31,6 @@ angular.module('quay').directive('createEntityDialog', function () {
}
});
$scope.setRole = function(role, repo) {
repo['permission'] = role;
if (role == 'none') {
$scope.checkedRepos.uncheckItem(repo);
} else {
$scope.checkedRepos.checkItem(repo);
}
};
$scope.hide = function() {
$element.find('.modal').modal('hide');
if ($scope.entity) {
@ -68,6 +42,7 @@ angular.module('quay').directive('createEntityDialog', function () {
$scope.show = function() {
$scope.entityName = null;
$scope.entity = null;
$scope.entityForPermissions = null;
$scope.creating = false;
$scope.view = 'enterName';
$scope.enterNameForm.$setPristine(true);
@ -79,92 +54,13 @@ angular.module('quay').directive('createEntityDialog', function () {
document.body.appendChild($element[0]);
};
var setRepoState = function() {
if (!$scope.repositories) {
return;
}
$scope.orderedRepositories = TableService.buildOrderedItems($scope.repositories, $scope.options,
['name', 'permission'],
['last_modified_datetime']);
};
var entityCreateCallback = function(entity) {
$scope.entity = entity;
if (!entity || $scope.info.skip_permissions) {
$scope.entity = entity;
$scope.hide();
return;
}
// Load the repositories under the entity's namespace.
var params = {
'namespace': $scope.info.namespace,
'last_modified': true
};
ApiService.listRepos(null, params).then(function(resp) {
$scope.view = 'addperms';
$scope.entity = entity;
var repos = [];
resp['repositories'].forEach(function(repo) {
repos.push({
'namespace': repo.namespace,
'name': repo.name,
'last_modified': repo.last_modified,
'last_modified_datetime': TableService.getReversedTimestamp(repo.last_modified),
'permission': 'none'
});
});
if (repos.length == 0) {
$scope.hide();
return;
}
$scope.repositories = repos;
$scope.checkedRepos = UIService.createCheckStateController($scope.repositories, 'name');
$scope.checkedRepos.listen(handleRepoCheckChange);
if ($scope.info.repository) {
repos.forEach(function(repo) {
if (repo['namespace'] == $scope.info.repository.namespace &&
repo['name'] == $scope.info.repository.name) {
$scope.checkedRepos.checkItem(repo);
$scope.options.filter = $scope.info.repository.name;
}
});
}
setRepoState();
}, ApiService.errorDisplay('Could not load repositories'));
};
$scope.addPermissions = function() {
$scope.view = 'addingperms';
var repos = $scope.checkedRepos.checked;
var counter = 0;
var addPerm = function() {
if (counter >= repos.length) {
$scope.hide();
return;
}
var repo = repos[counter];
RolesService.setRepositoryRole(repo, repo.permission, $scope.entityKind, $scope.entity.name,
function(status) {
if (status) {
counter++;
addPerm();
} else {
$scope.hide();
}
});
};
addPerm();
};
$scope.createEntity = function() {
@ -175,21 +71,27 @@ angular.module('quay').directive('createEntityDialog', function () {
});
};
$scope.allRepositoriesFilter = function(item) {
return true;
$scope.permissionsSet = function(repositories) {
$scope.entity['repo_count'] = repositories.length;
$scope.hide();
};
$scope.noRepositoriesFilter = function(item) {
return false;
$scope.settingPermissions = function() {
$scope.view = 'settingperms';
};
$scope.missingPermsRepositoriesFilter = function(item) {
return !item.perm;
$scope.setPermissions = function() {
$scope.context.setPermissionsCounter++;
};
$scope.$watch('options.predicate', setRepoState);
$scope.$watch('options.reverse', setRepoState);
$scope.$watch('options.filter', setRepoState);
$scope.repositoriesLoaded = function(repositories) {
if (repositories && !repositories.length) {
$scope.hide();
return;
}
$scope.view = 'setperms';
};
$scope.$watch('entityNameRegex', function(r) {
if (r) {

View file

@ -33,15 +33,6 @@ angular.module('quay').directive('robotsManager', function () {
locationListener && locationListener();
});
var loadRobotPermissions = function(info) {
var shortName = $scope.getShortenedName(info.name);
info.loading_permissions = true;
ApiService.getRobotPermissions($scope.organization, null, {'robot_shortname': shortName}).then(function(resp) {
info.permissions = resp.permissions;
info.loading_permissions = false;
}, ApiService.errorDisplay('Could not load robot permissions'));
};
$scope.filterToRobot = function(robotName) {
if ($scope.robotFilter == robotName) {
return;
@ -56,14 +47,6 @@ angular.module('quay').directive('robotsManager', function () {
$scope.robotFilter = robotName;
};
$scope.showPermissions = function(robotInfo) {
robotInfo.showing_permissions = !robotInfo.showing_permissions;
if (robotInfo.showing_permissions) {
loadRobotPermissions(robotInfo);
}
};
$scope.showRobot = function(info) {
$scope.robotDisplayInfo = {
'name': info.name
@ -126,6 +109,21 @@ angular.module('quay').directive('robotsManager', function () {
});
};
$scope.setPermissions = function(info) {
var namespace = $scope.organization ? $scope.organization.name : $scope.user.username;
$scope.setRepoPermissionsInfo = {
'namespace': namespace,
'entityName': info.name,
'entityKind': 'robot',
'entityIcon': 'ci-robot'
};
};
$scope.handlePermissionsSet = function(info, repositories) {
var index = $scope.findRobotIndexByName(info.entityName);
$scope.robots[index]['repositories'] = repositories;
};
$scope.robotCreated = function() {
update();
};

View file

@ -0,0 +1,60 @@
/**
* An element which displays a dialog for setting permissions for an entity to repositories under
* a namespace.
*/
angular.module('quay').directive('setRepoPermissionsDialog', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/set-repo-permissions-dialog.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'info': '=info',
'permissionsSet': '&permissionsSet',
},
controller: function($scope, $element) {
$scope.setPermissionsCounter = 0;
$scope.loading = false;
$scope.context = {};
$scope.setPermissions = function() {
$scope.setPermissionsCounter++;
};
$scope.settingPermissions = function() {
$scope.working = true;
};
$scope.show = function() {
$scope.setPermissionsCounter = 0;
$scope.working = false;
$element.find('.modal').modal({});
};
$scope.hide = function() {
$scope.working = false;
$scope.context.info = null;
$scope.context.hasChangedRepositories = false;
$scope.context.hasCheckedRepositories = false;
$element.find('.modal').modal('hide');
};
$scope.permissionsSetComplete = function(repositories) {
$scope.hide();
$scope.permissionsSet({'repositories': repositories, 'info': $scope.info});
};
$scope.$watch('info', function(info) {
if (info) {
$scope.context.info = info;
$scope.show();
}
});
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,235 @@
/**
* An element which displays a table for setting permissions for an entity to repositories under
* a namespace.
*/
angular.module('quay').directive('setRepoPermissions', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/set-repo-permissions.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'namespace': '=namespace',
'entityName': '=entityName',
'entityKind': '=entityKind',
'setPermissions': '=setPermissions',
'hasCheckedRepositories': '=hasCheckedRepositories',
'hasChangedRepositories': '=hasChangedRepositories',
'repositoriesLoaded': '&repositoriesLoaded',
'settingPermissions': '&settingPermissions',
'permissionsSet': '&permissionsSet',
},
controller: function($scope, $element, ApiService, UIService, TableService, RolesService, UserService) {
$scope.TableService = TableService;
$scope.options = {
'predicate': 'last_modified_datetime',
'reverse': false,
'filter': ''
};
$scope.repositories = null;
$scope.currentNamespace = null;
$scope.currentEntityName = null;
var checkForChanges = function() {
var hasChanges = false;
$scope.repositories.forEach(function(repo) {
if (repo['permission'] != repo['original_permission']) {
hasChanges = true;
}
});
$scope.hasCheckedRepositories = !!$scope.checkedRepos.checked.length;
$scope.hasChangedRepositories = hasChanges;
};
var handleRepoCheckChange = function() {
$scope.repositories.forEach(function(repo) {
if ($scope.checkedRepos.isChecked(repo)) {
if (repo['permission'] == 'none') {
repo['permission'] = 'read';
}
} else {
repo['permission'] = 'none';
}
});
checkForChanges();
};
var setRepoState = function() {
if (!$scope.repositories) {
return;
}
$scope.orderedRepositories = TableService.buildOrderedItems(
$scope.repositories, $scope.options,
['name', 'permission'],
['last_modified_datetime']);
};
var loadRepositoriesAndPermissions = function() {
if (!$scope.namespace || !$scope.entityName || !$scope.entityKind) {
return;
}
if (($scope.entityName == $scope.currentEntityName) &&
($scope.namespace == $scope.currentNamespace)) {
return;
}
$scope.currentNamespace = $scope.namespace;
$scope.currentEntityName = $scope.entityName;
// Load the repository permissions for the entity first. We then load the full repo list
// and compare.
RolesService.getRepoPermissions($scope.namespace, $scope.entityKind, $scope.entityName,
function(permissions) {
if (permissions == null) {
$scope.currentNamespace = null;
$scope.currentEntityName = null;
return;
}
var existingPermissionsMap = {};
permissions.forEach(function(existingPermission) {
existingPermissionsMap[existingPermission.repository.name] = existingPermission.role;
});
loadRepositories(existingPermissionsMap);
});
};
var loadRepositories = function(existingPermissionsMap) {
$scope.namespaceInfo = UserService.getNamespace($scope.namespace);
// Load the repositories under the entity's namespace, along with the current repo
// permissions for the entity.
var params = {
'namespace': $scope.namespace,
'last_modified': true
};
ApiService.listRepos(null, params).then(function(resp) {
$scope.currentNamespace = $scope.namespace;
var repos = [];
resp['repositories'].forEach(function(repo) {
var existingPermission = existingPermissionsMap[repo.name] || 'none';
repos.push({
'namespace': repo.namespace,
'name': repo.name,
'last_modified': repo.last_modified,
'last_modified_datetime': TableService.getReversedTimestamp(repo.last_modified),
'permission': existingPermission,
'original_permission': existingPermission
});
});
if (repos.length == 0) {
$scope.repositoriesLoaded({'repositories': repos});
return;
}
$scope.repositories = repos;
$scope.checkedRepos = UIService.createCheckStateController($scope.repositories, 'name');
repos.forEach(function(repo) {
if (repo.permission != 'none') {
$scope.checkedRepos.checkItem(repo);
}
});
$scope.checkedRepos.listen(handleRepoCheckChange);
setRepoState();
$scope.repositoriesLoaded({'repositories': repos});
}, ApiService.errorDisplay('Could not load repositories'));
};
var setPermissions = function() {
if (!$scope.checkedRepos || !$scope.namespace || !$scope.repositories) {
return;
}
$scope.settingPermissions();
var repos = $scope.repositories;
var counter = 0;
var setPerm = function() {
if (counter >= repos.length) {
$scope.permissionsSet({'repositories': $scope.checkedRepos.checked});
$scope.checkedRepos.setChecked([]);
return;
}
var repo = repos[counter];
if (repo['permission'] == repo['original_permission']) {
// Skip changing it.
counter++;
setPerm();
return;
}
RolesService.setRepositoryRole(repo, repo.permission, $scope.entityKind,
$scope.entityName, function(status) {
if (status) {
counter++;
setPerm();
}
});
};
setPerm();
};
$scope.setRole = function(role, repo) {
repo['permission'] = role;
if (role == 'none') {
$scope.checkedRepos.uncheckItem(repo);
} else {
$scope.checkedRepos.checkItem(repo);
}
checkForChanges();
};
$scope.allRepositoriesFilter = function(item) {
return true;
};
$scope.noRepositoriesFilter = function(item) {
return false;
};
$scope.missingPermsRepositoriesFilter = function(item) {
return !item.perm;
};
$scope.$watch('options.predicate', setRepoState);
$scope.$watch('options.reverse', setRepoState);
$scope.$watch('options.filter', setRepoState);
$scope.$watch('namespace', loadRepositoriesAndPermissions);
$scope.$watch('entityName', loadRepositoriesAndPermissions);
$scope.$watch('entityKind', loadRepositoriesAndPermissions);
$scope.$watch('setPermissions', function(value) {
if (value) {
setPermissions();
}
});
}
};
return directiveDefinitionObject;
});

View file

@ -12,7 +12,15 @@ angular.module('quay').directive('teamsManager', function () {
'organization': '=organization',
'isEnabled': '=isEnabled'
},
controller: function($scope, $element, ApiService, $timeout, UserService) {
controller: function($scope, $element, ApiService, $timeout, UserService, TableService, UIService) {
$scope.TableService = TableService;
$scope.options = {
'predicate': 'ordered_team_index',
'reverse': false,
'filter': ''
};
$scope.teamRoles = [
{ 'id': 'member', 'title': 'Member', 'kind': 'default' },
{ 'id': 'creator', 'title': 'Creator', 'kind': 'success' },
@ -21,65 +29,42 @@ angular.module('quay').directive('teamsManager', function () {
UserService.updateUserIn($scope);
$scope.members = {};
$scope.orderedTeams = [];
$scope.teams = null;
$scope.orderedTeams = null;
$scope.showingMembers = false;
$scope.fullMemberList = null;
$scope.feedback = null;
$scope.createTeamInfo = null;
var loadTeamMembers = function() {
if (!$scope.organization || !$scope.isEnabled) { return; }
// Skip loading team members on mobile.
if (!window.matchMedia('(min-width: 768px)').matches) {
return;
var getRoleIndex = function(name) {
for (var i = 0; i < $scope.teamRoles.length; ++i) {
if ($scope.teamRoles[i]['id'] == name) {
return i;
}
}
for (var name in $scope.organization.teams) {
if (!$scope.organization.teams.hasOwnProperty(name) || $scope.members[name]) { continue; }
// Load fully async to prevent it from blocking the UI.
(function(teamname) {
$timeout(function() {
loadMembersOfTeam(teamname);
}, 1);
})(name);
}
return -1;
};
var loadMembersOfTeam = function(name) {
var params = {
'orgname': $scope.organization.name,
'teamname': name
};
$scope.members[name] = {};
ApiService.getOrganizationTeamMembers(null, params).then(function(resp) {
$scope.members[name].members = resp.members;
}, function() {
delete $scope.members[name];
});
};
var loadOrderedTeams = function() {
var setTeamsState = function() {
if (!$scope.organization || !$scope.organization.ordered_teams || !$scope.isEnabled) {
return;
}
$scope.orderedTeams = [];
$scope.organization.ordered_teams.map(function(name) {
$scope.orderedTeams.push($scope.organization.teams[name]);
$scope.teams = [];
$scope.organization.ordered_teams.map(function(name, index) {
var team = $scope.organization.teams[name];
team['ordered_team_index'] = $scope.organization.ordered_teams.length - index;
team['role_index'] = getRoleIndex(team['role']);
$scope.teams.push(team);
});
$scope.orderedTeams = TableService.buildOrderedItems(
$scope.teams, $scope.options,
['name'],
['ordered_team_index', 'member_count', 'repo_count', 'role_index']);
};
$scope.$watch('organization', loadOrderedTeams);
$scope.$watch('organization', loadTeamMembers);
$scope.$watch('isEnabled', loadOrderedTeams);
$scope.$watch('isEnabled', loadTeamMembers);
$scope.setRole = function(role, teamname) {
var previousRole = $scope.organization.teams[teamname].role;
$scope.organization.teams[teamname].role = role;
@ -115,9 +100,9 @@ angular.module('quay').directive('teamsManager', function () {
$scope.handleTeamCreated = function(created) {
var teamname = created.name;
created['member_count'] = 0;
$scope.organization.teams[teamname] = created;
$scope.members[teamname] = {};
$scope.members[teamname].members = [];
$scope.organization.ordered_teams.push(teamname);
$scope.orderedTeams.push(created);
@ -150,8 +135,8 @@ angular.module('quay').directive('teamsManager', function () {
$scope.organization.ordered_teams.splice(index, 1);
}
loadOrderedTeams();
delete $scope.organization.teams[teamname];
setTeamsState();
$scope.feedback = {
'kind': 'success',
@ -192,12 +177,7 @@ angular.module('quay').directive('teamsManager', function () {
ApiService.removeOrganizationMember(null, params).then(function(resp) {
// Reset the state of the directive.
$scope.members = {};
$scope.orderedTeams = [];
$scope.fullMemberList = null;
loadOrderedTeams();
loadTeamMembers();
$scope.showMembers(true);
callback(true);
@ -215,6 +195,27 @@ angular.module('quay').directive('teamsManager', function () {
$scope.askRemoveMember = function(memberInfo) {
$scope.removeMemberInfo = $.extend({}, memberInfo);
};
$scope.setRepoPermissions = function(teamName) {
$scope.setRepoPermissionsInfo = {
'namespace': $scope.organization.name,
'entityName': teamName,
'entityKind': 'team',
'entityIcon': 'fa-group'
};
};
$scope.handlePermissionsSet = function(info, repositories) {
var team = $scope.organization.teams[info.entityName];
team['repo_count'] = repositories.length;
};
$scope.$watch('organization', setTeamsState);
$scope.$watch('isEnabled', setTeamsState);
$scope.$watch('options.predicate', setTeamsState);
$scope.$watch('options.reverse', setTeamsState);
$scope.$watch('options.filter', setTeamsState);
}
};

View file

@ -1,7 +1,8 @@
/**
* Service which defines the various role groups.
*/
angular.module('quay').factory('RolesService', ['UtilService', 'Restangular', 'ApiService', function(UtilService, Restangular, ApiService) {
angular.module('quay').factory('RolesService', ['UtilService', 'Restangular', 'ApiService', 'UserService',
function(UtilService, Restangular, ApiService, UserService) {
var roleService = {};
roleService.repoRolesOrNone = [
@ -20,14 +21,22 @@ angular.module('quay').factory('RolesService', ['UtilService', 'Restangular', 'A
{ 'id': 'admin', 'title': 'Admin', 'kind': 'primary', 'description': 'Full admin access to the organization' }
];
var getPermissionEndpoint = function(repository, entityName, kind) {
var getPermissionEndpoint = function(repository, entityName, entityKind) {
if (entityKind == 'robot') {
entityKind = 'user';
}
var namespace = repository.namespace;
var name = repository.name;
var url = UtilService.getRestUrl('repository', namespace, name, 'permissions', kind, entityName);
var url = UtilService.getRestUrl('repository', namespace, name, 'permissions', entityKind, entityName);
return Restangular.one(url);
};
roleService.deleteRepositoryRole = function(repository, entityKind, entityName, callback) {
if (entityKind == 'robot') {
entityKind = 'user';
}
var errorDisplay = ApiService.errorDisplay('Cannot change permission', function(resp) {
callback(false);
});
@ -39,6 +48,15 @@ angular.module('quay').factory('RolesService', ['UtilService', 'Restangular', 'A
};
roleService.setRepositoryRole = function(repository, role, entityKind, entityName, callback) {
if (role == 'none') {
roleService.deleteRepositoryRole(repository, entityKind, entityName, callback);
return;
}
if (entityKind == 'robot') {
entityKind = 'user';
}
var errorDisplay = ApiService.errorDisplay('Cannot change permission', function(resp) {
callback(false);
});
@ -53,5 +71,30 @@ angular.module('quay').factory('RolesService', ['UtilService', 'Restangular', 'A
}, errorDisplay);
};
roleService.getRepoPermissions = function(namespace, entityKind, entityName, callback) {
var errorHandler = ApiService.errorDisplay('Could not load permissions', callback);
if (entityKind == 'team') {
var params = {
'orgname': namespace,
'teamname': entityName
};
ApiService.getTeamPermissions(null, params).then(function(resp) {
callback(resp.permissions);
}, errorHandler);
} else if (entityKind == 'robot') {
var parts = entityName.split('+');
var shortName = parts[1];
var orgname = UserService.isOrganization(namespace) ? namespace : null;
ApiService.getRobotPermissions(orgname, null, {'robot_shortname': shortName}).then(function(resp) {
callback(resp.permissions);
}, errorHandler);
} else {
throw Error('Unknown entity kind ' + entityKind);
}
};
return roleService;
}]);