- Add an entity-search directive for adding a nice search box for users or teams
- Add support for team-based permissions to the repos
This commit is contained in:
parent
09afe0753f
commit
100ec563fa
7 changed files with 362 additions and 93 deletions
|
@ -207,8 +207,13 @@ def get_user(username):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_matching_teams(team_prefix, organization):
|
||||||
|
query = Team.select().where(Team.name ** (team_prefix + '%'), Team.organization == organization)
|
||||||
|
return list(query.limit(10))
|
||||||
|
|
||||||
|
|
||||||
def get_matching_users(username_prefix):
|
def get_matching_users(username_prefix):
|
||||||
query = User.select().where(User.username ** (username_prefix + '%'))
|
query = User.select().where(User.username ** (username_prefix + '%'), User.organization == False)
|
||||||
return list(query.limit(10))
|
return list(query.limit(10))
|
||||||
|
|
||||||
|
|
||||||
|
@ -328,6 +333,15 @@ def get_all_user_permissions(user):
|
||||||
return with_role.where(User.username == user.username)
|
return with_role.where(User.username == user.username)
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_repo_teams(namespace_name, repository_name):
|
||||||
|
select = RepositoryPermission.select(Team.name.alias('team_name'), Role.name,
|
||||||
|
RepositoryPermission)
|
||||||
|
with_team = select.join(Team)
|
||||||
|
with_role = with_team.switch(RepositoryPermission).join(Role)
|
||||||
|
with_repo = with_role.switch(RepositoryPermission).join(Repository)
|
||||||
|
return with_repo.where(Repository.namespace == namespace_name,
|
||||||
|
Repository.name == repository_name)
|
||||||
|
|
||||||
def get_all_repo_users(namespace_name, repository_name):
|
def get_all_repo_users(namespace_name, repository_name):
|
||||||
select = RepositoryPermission.select(User.username, Role.name,
|
select = RepositoryPermission.select(User.username, Role.name,
|
||||||
RepositoryPermission)
|
RepositoryPermission)
|
||||||
|
|
159
endpoints/api.py
159
endpoints/api.py
|
@ -188,6 +188,46 @@ def get_matching_users(prefix):
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/entities/<prefix>', methods=['GET'])
|
||||||
|
@api_login_required
|
||||||
|
def get_matching_entities(prefix):
|
||||||
|
users = model.get_matching_users(prefix)
|
||||||
|
teams = []
|
||||||
|
|
||||||
|
organization_name = request.args.get('organization', None)
|
||||||
|
organization = None
|
||||||
|
if organization_name:
|
||||||
|
try:
|
||||||
|
organization = model.get_organization(organization_name)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if organization:
|
||||||
|
# TODO: ensure that the user has access to the organization
|
||||||
|
teams = model.get_matching_teams(prefix, organization)
|
||||||
|
|
||||||
|
def team_view(team):
|
||||||
|
return {
|
||||||
|
'name': team.name,
|
||||||
|
'kind': 'team'
|
||||||
|
}
|
||||||
|
|
||||||
|
def user_view(user):
|
||||||
|
# TODO: Return whether the user is outside the organization (if one is
|
||||||
|
# specified)
|
||||||
|
return {
|
||||||
|
'name': user.username,
|
||||||
|
'kind': 'user',
|
||||||
|
'outside_org': True
|
||||||
|
}
|
||||||
|
|
||||||
|
team_data = [team_view(team) for team in teams]
|
||||||
|
user_data = [user_view(user) for user in users]
|
||||||
|
return jsonify({
|
||||||
|
'results': team_data + user_data
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
user_files = UserRequestFiles(app.config['AWS_ACCESS_KEY'],
|
user_files = UserRequestFiles(app.config['AWS_ACCESS_KEY'],
|
||||||
app.config['AWS_SECRET_KEY'],
|
app.config['AWS_SECRET_KEY'],
|
||||||
app.config['REGISTRY_S3_BUCKET'])
|
app.config['REGISTRY_S3_BUCKET'])
|
||||||
|
@ -225,8 +265,10 @@ def get_organization_private_allowed(orgname):
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
user = current_user.db_user()
|
user = current_user.db_user()
|
||||||
organization = model.lookup_organization(orgname, username = user.username)
|
|
||||||
if not organization:
|
try:
|
||||||
|
organization = model.get_organization(orgname, username = user.username)
|
||||||
|
except:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
private_repos = model.get_private_repo_count(organization.username)
|
private_repos = model.get_private_repo_count(organization.username)
|
||||||
|
@ -405,6 +447,12 @@ def get_repo_api(namespace, repository):
|
||||||
'image': image_view(image),
|
'image': image_view(image),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
organization = None
|
||||||
|
try:
|
||||||
|
organization = model.get_organization(namespace)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
permission = ReadRepositoryPermission(namespace, repository)
|
permission = ReadRepositoryPermission(namespace, repository)
|
||||||
is_public = model.repository_is_public(namespace, repository)
|
is_public = model.repository_is_public(namespace, repository)
|
||||||
if permission.can() or is_public:
|
if permission.can() or is_public:
|
||||||
|
@ -426,6 +474,7 @@ def get_repo_api(namespace, repository):
|
||||||
'can_admin': can_admin,
|
'can_admin': can_admin,
|
||||||
'is_public': is_public,
|
'is_public': is_public,
|
||||||
'is_building': len(active_builds) > 0,
|
'is_building': len(active_builds) > 0,
|
||||||
|
'is_organization': bool(organization)
|
||||||
})
|
})
|
||||||
|
|
||||||
abort(404) # Not fount
|
abort(404) # Not fount
|
||||||
|
@ -501,11 +550,11 @@ def request_repo_build(namespace, repository):
|
||||||
abort(403) # Permissions denied
|
abort(403) # Permissions denied
|
||||||
|
|
||||||
|
|
||||||
def user_role_view(repo_perm_obj, username):
|
def role_view(repo_perm_obj, username=None):
|
||||||
# TODO: Determine whether the user is outside of the organization.
|
# TODO: Determine whether the user (if given) is outside of the organization.
|
||||||
return {
|
return {
|
||||||
'role': repo_perm_obj.role.name,
|
'role': repo_perm_obj.role.name,
|
||||||
'outside_org': False
|
'outside_org': username != 'devtable'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -586,42 +635,73 @@ def list_tag_images(namespace, repository, tag):
|
||||||
abort(403) # Permission denied
|
abort(403) # Permission denied
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/repository/<path:repository>/permissions/', methods=['GET'])
|
@app.route('/api/repository/<path:repository>/permissions/team/', methods=['GET'])
|
||||||
@api_login_required
|
@api_login_required
|
||||||
@parse_repository_name
|
@parse_repository_name
|
||||||
def list_repo_permissions(namespace, repository):
|
def list_repo_team_permissions(namespace, repository):
|
||||||
permission = AdministerRepositoryPermission(namespace, repository)
|
permission = AdministerRepositoryPermission(namespace, repository)
|
||||||
if permission.can():
|
if permission.can():
|
||||||
repo_perms = model.get_all_repo_users(namespace, repository)
|
repo_perms = model.get_all_repo_teams(namespace, repository)
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'permissions': {repo_perm.user.username: user_role_view(repo_perm, repo_perm.user.username)
|
'permissions': {repo_perm.team.name: role_view(repo_perm)
|
||||||
for repo_perm in repo_perms}
|
for repo_perm in repo_perms}
|
||||||
})
|
})
|
||||||
|
|
||||||
abort(403) # Permission denied
|
abort(403) # Permission denied
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/repository/<path:repository>/permissions/<username>',
|
@app.route('/api/repository/<path:repository>/permissions/user/', methods=['GET'])
|
||||||
|
@api_login_required
|
||||||
|
@parse_repository_name
|
||||||
|
def list_repo_user_permissions(namespace, repository):
|
||||||
|
permission = AdministerRepositoryPermission(namespace, repository)
|
||||||
|
if permission.can():
|
||||||
|
repo_perms = model.get_all_repo_users(namespace, repository)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'permissions': {repo_perm.user.username: role_view(repo_perm, username=repo_perm.user.username)
|
||||||
|
for repo_perm in repo_perms}
|
||||||
|
})
|
||||||
|
|
||||||
|
abort(403) # Permission denied
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/repository/<path:repository>/permissions/user/<username>',
|
||||||
methods=['GET'])
|
methods=['GET'])
|
||||||
@api_login_required
|
@api_login_required
|
||||||
@parse_repository_name
|
@parse_repository_name
|
||||||
def get_permissions(namespace, repository, username):
|
def get_user_permissions(namespace, repository, username):
|
||||||
logger.debug('Get repo: %s/%s permissions for user %s' %
|
logger.debug('Get repo: %s/%s permissions for user %s' %
|
||||||
(namespace, repository, username))
|
(namespace, repository, username))
|
||||||
permission = AdministerRepositoryPermission(namespace, repository)
|
permission = AdministerRepositoryPermission(namespace, repository)
|
||||||
if permission.can():
|
if permission.can():
|
||||||
perm = model.get_user_reponame_permission(username, namespace, repository)
|
perm = model.get_user_reponame_permission(username, namespace, repository)
|
||||||
return jsonify(user_role_view(perm, username))
|
return jsonify(role_view(perm, username=username))
|
||||||
|
|
||||||
abort(403) # Permission denied
|
abort(403) # Permission denied
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/repository/<path:repository>/permissions/<username>',
|
@app.route('/api/repository/<path:repository>/permissions/team/<teamname>',
|
||||||
|
methods=['GET'])
|
||||||
|
@api_login_required
|
||||||
|
@parse_repository_name
|
||||||
|
def get_team_permissions(namespace, repository, teamname):
|
||||||
|
logger.debug('Get repo: %s/%s permissions for team %s' %
|
||||||
|
(namespace, repository, teamname))
|
||||||
|
permission = AdministerRepositoryPermission(namespace, repository)
|
||||||
|
if permission.can():
|
||||||
|
perm = model.get_team_reponame_permission(username, namespace, repository)
|
||||||
|
return jsonify(role_view(perm))
|
||||||
|
|
||||||
|
abort(403) # Permission denied
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/repository/<path:repository>/permissions/user/<username>',
|
||||||
methods=['PUT', 'POST'])
|
methods=['PUT', 'POST'])
|
||||||
@api_login_required
|
@api_login_required
|
||||||
@parse_repository_name
|
@parse_repository_name
|
||||||
def change_permissions(namespace, repository, username):
|
def change_user_permissions(namespace, repository, username):
|
||||||
permission = AdministerRepositoryPermission(namespace, repository)
|
permission = AdministerRepositoryPermission(namespace, repository)
|
||||||
if permission.can():
|
if permission.can():
|
||||||
new_permission = request.get_json()
|
new_permission = request.get_json()
|
||||||
|
@ -636,7 +716,7 @@ def change_permissions(namespace, repository, username):
|
||||||
logger.warning('User tried to remove themselves as admin.')
|
logger.warning('User tried to remove themselves as admin.')
|
||||||
abort(409)
|
abort(409)
|
||||||
|
|
||||||
resp = jsonify(user_role_view(perm, username))
|
resp = jsonify(role_view(perm, username=username))
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
resp.status_code = 201
|
resp.status_code = 201
|
||||||
return resp
|
return resp
|
||||||
|
@ -644,11 +724,38 @@ def change_permissions(namespace, repository, username):
|
||||||
abort(403) # Permission denied
|
abort(403) # Permission denied
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/repository/<path:repository>/permissions/<username>',
|
@app.route('/api/repository/<path:repository>/permissions/team/<teamname>',
|
||||||
|
methods=['PUT', 'POST'])
|
||||||
|
@api_login_required
|
||||||
|
@parse_repository_name
|
||||||
|
def change_team_permissions(namespace, repository, teamname):
|
||||||
|
permission = AdministerRepositoryPermission(namespace, repository)
|
||||||
|
if permission.can():
|
||||||
|
new_permission = request.get_json()
|
||||||
|
|
||||||
|
logger.debug('Setting permission to: %s for team %s' %
|
||||||
|
(new_permission['role'], teamname))
|
||||||
|
|
||||||
|
try:
|
||||||
|
perm = model.set_team_repo_permission(teamname, namespace, repository,
|
||||||
|
new_permission['role'])
|
||||||
|
except model.DataModelException:
|
||||||
|
logger.warning('User tried to remove themselves as admin.')
|
||||||
|
abort(409)
|
||||||
|
|
||||||
|
resp = jsonify(role_view(perm))
|
||||||
|
if request.method == 'POST':
|
||||||
|
resp.status_code = 201
|
||||||
|
return resp
|
||||||
|
|
||||||
|
abort(403) # Permission denied
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/repository/<path:repository>/permissions/user/<username>',
|
||||||
methods=['DELETE'])
|
methods=['DELETE'])
|
||||||
@api_login_required
|
@api_login_required
|
||||||
@parse_repository_name
|
@parse_repository_name
|
||||||
def delete_permissions(namespace, repository, username):
|
def delete_user_permissions(namespace, repository, username):
|
||||||
permission = AdministerRepositoryPermission(namespace, repository)
|
permission = AdministerRepositoryPermission(namespace, repository)
|
||||||
if permission.can():
|
if permission.can():
|
||||||
try:
|
try:
|
||||||
|
@ -662,6 +769,24 @@ def delete_permissions(namespace, repository, username):
|
||||||
abort(403) # Permission denied
|
abort(403) # Permission denied
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/repository/<path:repository>/permissions/team/<teamname>',
|
||||||
|
methods=['DELETE'])
|
||||||
|
@api_login_required
|
||||||
|
@parse_repository_name
|
||||||
|
def delete_team_permissions(namespace, repository, teamname):
|
||||||
|
permission = AdministerRepositoryPermission(namespace, repository)
|
||||||
|
if permission.can():
|
||||||
|
try:
|
||||||
|
model.delete_team_permission(teamname, namespace, repository)
|
||||||
|
except model.DataModelException:
|
||||||
|
logger.warning('User tried to remove themselves as admin.')
|
||||||
|
abort(409)
|
||||||
|
|
||||||
|
return make_response('Deleted', 204)
|
||||||
|
|
||||||
|
abort(403) # Permission denied
|
||||||
|
|
||||||
|
|
||||||
def token_view(token_obj):
|
def token_view(token_obj):
|
||||||
return {
|
return {
|
||||||
'friendlyName': token_obj.friendly_name,
|
'friendlyName': token_obj.friendly_name,
|
||||||
|
|
|
@ -574,14 +574,20 @@ form input.ng-valid.ng-dirty {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.user-mini-listing {
|
.entity-mini-listing {
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-mini-listing i {
|
.entity-mini-listing i {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.entity-mini-listing .warning {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.editable {
|
.editable {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -898,6 +904,10 @@ p.editable:hover i {
|
||||||
padding-left: 44px;
|
padding-left: 44px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.repo-admin .entity-search input {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
.repo-admin .token-dialog-body .well {
|
.repo-admin .token-dialog-body .well {
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
@ -916,10 +926,21 @@ p.editable:hover i {
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo-admin .user i {
|
.repo-admin .user i {
|
||||||
margin-right: 6px;
|
margin-left: 2px;
|
||||||
|
margin-right: 7px;
|
||||||
|
color: rgb(79, 195, 79);
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo-admin .user {
|
.repo-admin .user.outside i {
|
||||||
|
color: rgb(224, 173, 41);
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-admin .team i {
|
||||||
|
margin-right: 4px;
|
||||||
|
color: rgb(79, 195, 79);
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-admin .entity {
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
1
static/directives/entity-search.html
Normal file
1
static/directives/entity-search.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<input class="entity-search-control form-control">
|
|
@ -233,6 +233,82 @@ quayApp.directive('repoCircle', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
quayApp.directive('entitySearch', function () {
|
||||||
|
var number = 0;
|
||||||
|
var directiveDefinitionObject = {
|
||||||
|
priority: 0,
|
||||||
|
templateUrl: '/static/directives/entity-search.html',
|
||||||
|
replace: false,
|
||||||
|
transclude: false,
|
||||||
|
restrict: 'C',
|
||||||
|
scope: {
|
||||||
|
'organization': '=organization',
|
||||||
|
'inputTitle': '=inputTitle',
|
||||||
|
'entitySelected': '=entitySelected'
|
||||||
|
},
|
||||||
|
controller: function($scope, $element) {
|
||||||
|
if (!$scope.entitySelected) { return; }
|
||||||
|
|
||||||
|
number++;
|
||||||
|
|
||||||
|
var input = $element[0].firstChild;
|
||||||
|
$scope.organization = $scope.organization || '';
|
||||||
|
$(input).typeahead({
|
||||||
|
name: 'entities' + number,
|
||||||
|
remote: {
|
||||||
|
url: '/api/entities/%QUERY',
|
||||||
|
replace: function (url, uriEncodedQuery) {
|
||||||
|
url = url.replace('%QUERY', uriEncodedQuery);
|
||||||
|
if ($scope.organization) {
|
||||||
|
url += '?organization=' + encodeURIComponent($scope.organization);
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
},
|
||||||
|
filter: function(data) {
|
||||||
|
var datums = [];
|
||||||
|
for (var i = 0; i < data.results.length; ++i) {
|
||||||
|
var entity = data.results[i];
|
||||||
|
datums.push({
|
||||||
|
'value': entity.name,
|
||||||
|
'tokens': [entity.name],
|
||||||
|
'entity': entity
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return datums;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: function (datum) {
|
||||||
|
template = '<div class="entity-mini-listing">';
|
||||||
|
if (datum.entity.kind == 'user') {
|
||||||
|
template += '<i class="fa fa-user fa-lg"></i>';
|
||||||
|
} else if (datum.entity.kind == 'team') {
|
||||||
|
template += '<i class="fa fa-group fa-lg"></i>';
|
||||||
|
}
|
||||||
|
template += '<span class="name">' + datum.value + '</span>';
|
||||||
|
|
||||||
|
if (datum.entity.outside_org) {
|
||||||
|
template += '<div class="alert-warning warning">This user is outside your organization</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
template += '</div>';
|
||||||
|
return template;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
$(input).on('typeahead:selected', function(e, datum) {
|
||||||
|
$(input).typeahead('setQuery', '');
|
||||||
|
$scope.entitySelected(datum.entity);
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.$watch('inputTitle', function(title) {
|
||||||
|
input.setAttribute('placeholder', title);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return directiveDefinitionObject;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
quayApp.directive('namespaceSelector', function () {
|
quayApp.directive('namespaceSelector', function () {
|
||||||
var directiveDefinitionObject = {
|
var directiveDefinitionObject = {
|
||||||
priority: 0,
|
priority: 0,
|
||||||
|
|
|
@ -527,39 +527,7 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
|
||||||
var namespace = $routeParams.namespace;
|
var namespace = $routeParams.namespace;
|
||||||
var name = $routeParams.name;
|
var name = $routeParams.name;
|
||||||
|
|
||||||
$scope.$on('$viewContentLoaded', function() {
|
$scope.permissions = {'team': [], 'user': []};
|
||||||
// THIS IS BAD, MOVE THIS TO A DIRECTIVE
|
|
||||||
$('#userSearch').typeahead({
|
|
||||||
name: 'users',
|
|
||||||
remote: {
|
|
||||||
url: '/api/users/%QUERY',
|
|
||||||
filter: function(data) {
|
|
||||||
var datums = [];
|
|
||||||
for (var i = 0; i < data.users.length; ++i) {
|
|
||||||
var user = data.users[i];
|
|
||||||
datums.push({
|
|
||||||
'value': user,
|
|
||||||
'tokens': [user],
|
|
||||||
'username': user
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return datums;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
template: function (datum) {
|
|
||||||
template = '<div class="user-mini-listing">';
|
|
||||||
template += '<i class="fa fa-user fa-lg"></i>'
|
|
||||||
template += '<span class="name">' + datum.username + '</span>'
|
|
||||||
template += '</div>'
|
|
||||||
return template;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#userSearch').on('typeahead:selected', function(e, datum) {
|
|
||||||
$('#userSearch').typeahead('setQuery', '');
|
|
||||||
$scope.addNewPermission(datum.username);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.isDownloadSupported = function() {
|
$scope.isDownloadSupported = function() {
|
||||||
try { return !!new Blob(); } catch(e){}
|
try { return !!new Blob(); } catch(e){}
|
||||||
|
@ -580,21 +548,34 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
|
||||||
saveAs(blob, '.dockercfg');
|
saveAs(blob, '.dockercfg');
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.addNewPermission = function(username) {
|
$scope.grantRole = function() {
|
||||||
|
$('#confirmaddoutsideModal').modal('hide');
|
||||||
|
var entity = $scope.currentAddEntity;
|
||||||
|
$scope.addRole(entity.name, 'read', entity.kind, entity.outside_org)
|
||||||
|
$scope.currentAddEntity = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.addNewPermission = function(entity) {
|
||||||
// Don't allow duplicates.
|
// Don't allow duplicates.
|
||||||
if ($scope.permissions[username]) { return; }
|
if ($scope.permissions[entity.kind][entity.name]) { return; }
|
||||||
|
|
||||||
|
if (entity.outside_org) {
|
||||||
|
$scope.currentAddEntity = entity;
|
||||||
|
$('#confirmaddoutsideModal').modal('show');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Need the $scope.apply for both the permission stuff to change and for
|
// Need the $scope.apply for both the permission stuff to change and for
|
||||||
// the XHR call to be made.
|
// the XHR call to be made.
|
||||||
$scope.$apply(function() {
|
$scope.$apply(function() {
|
||||||
$scope.addRole(username, 'read')
|
$scope.addRole(entity.name, 'read', entity.kind, entity.outside_org)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.deleteRole = function(username) {
|
$scope.deleteRole = function(entityName, kind) {
|
||||||
var permissionDelete = Restangular.one('repository/' + namespace + '/' + name + '/permissions/' + username);
|
var permissionDelete = Restangular.one('repository/' + namespace + '/' + name + '/permissions/' + kind + '/' + entityName);
|
||||||
permissionDelete.customDELETE().then(function() {
|
permissionDelete.customDELETE().then(function() {
|
||||||
delete $scope.permissions[username];
|
delete $scope.permissions[kind][entityName];
|
||||||
}, function(result) {
|
}, function(result) {
|
||||||
if (result.status == 409) {
|
if (result.status == 409) {
|
||||||
$('#onlyadminModal').modal({});
|
$('#onlyadminModal').modal({});
|
||||||
|
@ -604,26 +585,26 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.addRole = function(username, role) {
|
$scope.addRole = function(entityName, role, kind, outside_org) {
|
||||||
var permission = {
|
var permission = {
|
||||||
'role': role
|
'role': role,
|
||||||
|
'outside_org': !!outside_org
|
||||||
};
|
};
|
||||||
|
|
||||||
var permissionPost = Restangular.one('repository/' + namespace + '/' + name + '/permissions/' + username);
|
var permissionPost = Restangular.one('repository/' + namespace + '/' + name + '/permissions/' + kind + '/' + entityName);
|
||||||
permissionPost.customPOST(permission).then(function() {
|
permissionPost.customPOST(permission).then(function() {
|
||||||
$scope.permissions[username] = permission;
|
$scope.permissions[kind][entityName] = permission;
|
||||||
$scope.permissions = $scope.permissions;
|
|
||||||
}, function(result) {
|
}, function(result) {
|
||||||
$('#cannotchangeModal').modal({});
|
$('#cannotchangeModal').modal({});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.setRole = function(username, role) {
|
$scope.setRole = function(entityName, role, kind) {
|
||||||
var permission = $scope.permissions[username];
|
var permission = $scope.permissions[kind][entityName];
|
||||||
var currentRole = permission.role;
|
var currentRole = permission.role;
|
||||||
permission.role = role;
|
permission.role = role;
|
||||||
|
|
||||||
var permissionPut = Restangular.one('repository/' + namespace + '/' + name + '/permissions/' + username);
|
var permissionPut = Restangular.one('repository/' + namespace + '/' + name + '/permissions/' + kind + '/' + entityName);
|
||||||
permissionPut.customPUT(permission).then(function() {}, function(result) {
|
permissionPut.customPUT(permission).then(function() {}, function(result) {
|
||||||
if (result.status == 409) {
|
if (result.status == 409) {
|
||||||
permission.role = currentRole;
|
permission.role = currentRole;
|
||||||
|
@ -709,6 +690,23 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
|
||||||
|
|
||||||
$scope.loading = true;
|
$scope.loading = true;
|
||||||
|
|
||||||
|
var checkLoading = function() {
|
||||||
|
$scope.loading = !($scope.permissions['user'] && $scope.permissions['team'] && $scope.repo && $scope.tokens);
|
||||||
|
};
|
||||||
|
|
||||||
|
var fetchPermissions = function(kind) {
|
||||||
|
var permissionsFetch = Restangular.one('repository/' + namespace + '/' + name + '/permissions/' + kind + '/');
|
||||||
|
permissionsFetch.get().then(function(resp) {
|
||||||
|
$rootScope.title = 'Settings - ' + namespace + '/' + name;
|
||||||
|
$scope.permissions[kind] = resp.permissions;
|
||||||
|
checkLoading();
|
||||||
|
}, function() {
|
||||||
|
$scope.permissions[kind] = null;
|
||||||
|
$rootScope.title = 'Unknown Repository';
|
||||||
|
$scope.loading = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Fetch the repository information.
|
// Fetch the repository information.
|
||||||
var repositoryFetch = Restangular.one('repository/' + namespace + '/' + name);
|
var repositoryFetch = Restangular.one('repository/' + namespace + '/' + name);
|
||||||
repositoryFetch.get().then(function(repo) {
|
repositoryFetch.get().then(function(repo) {
|
||||||
|
@ -720,23 +718,15 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch the permissions.
|
// Fetch the user and team permissions.
|
||||||
var permissionsFetch = Restangular.one('repository/' + namespace + '/' + name + '/permissions');
|
fetchPermissions('user');
|
||||||
permissionsFetch.get().then(function(resp) {
|
fetchPermissions('team');
|
||||||
$rootScope.title = 'Settings - ' + namespace + '/' + name;
|
|
||||||
$scope.permissions = resp.permissions;
|
|
||||||
$scope.loading = !($scope.permissions && $scope.repo && $scope.tokens);
|
|
||||||
}, function() {
|
|
||||||
$scope.permissions = null;
|
|
||||||
$rootScope.title = 'Unknown Repository';
|
|
||||||
$scope.loading = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fetch the tokens.
|
// Fetch the tokens.
|
||||||
var tokensFetch = Restangular.one('repository/' + namespace + '/' + name + '/tokens/');
|
var tokensFetch = Restangular.one('repository/' + namespace + '/' + name + '/tokens/');
|
||||||
tokensFetch.get().then(function(resp) {
|
tokensFetch.get().then(function(resp) {
|
||||||
$scope.tokens = resp.tokens;
|
$scope.tokens = resp.tokens;
|
||||||
$scope.loading = !($scope.permissions && $scope.repo && $scope.tokens);
|
checkLoading();
|
||||||
}, function() {
|
}, function() {
|
||||||
$scope.tokens = null;
|
$scope.tokens = null;
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
|
|
|
@ -20,36 +20,58 @@
|
||||||
|
|
||||||
<!-- User Access Permissions -->
|
<!-- User Access Permissions -->
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">User Access Permissions
|
<div class="panel-heading">User <span ng-show="repo.is_organization">and Team</span> Access Permissions
|
||||||
|
|
||||||
<i class="info-icon fa fa-info-circle" data-placement="left" data-content="Allow any number of users to read, write or administer this repository"></i>
|
<i class="info-icon fa fa-info-circle" data-placement="left" data-content="Allow any number of users or teams to read, write or administer this repository"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
|
|
||||||
<table class="permissions">
|
<table class="permissions">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td>User</td>
|
<td>User<span ng-show="repo.is_organization">/Team</span></td>
|
||||||
<td>Permissions</td>
|
<td>Permissions</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tr ng-repeat="(username, permission) in permissions">
|
<!-- Team Permissions -->
|
||||||
<td class="user">
|
<tr ng-repeat="(name, permission) in permissions['team']">
|
||||||
<i class="fa fa-user"></i>
|
<td class="team entity">
|
||||||
<span>{{username}}</span>
|
<i class="fa fa-group"></i>
|
||||||
|
<span>{{name}}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="user-permissions">
|
<td class="user-permissions">
|
||||||
<div class="btn-group btn-group-sm">
|
<div class="btn-group btn-group-sm">
|
||||||
<button type="button" class="btn btn-default" ng-click="setRole(username, 'read')" ng-class="{read: 'active', write: '', admin: ''}[permission.role]">Read only</button>
|
<button type="button" class="btn btn-default" ng-click="setRole(name, 'read', 'team')" ng-class="{read: 'active', write: '', admin: ''}[permission.role]">Read only</button>
|
||||||
<button type="button" class="btn btn-default" ng-click="setRole(username, 'write')" ng-class="{read: '', write: 'active', admin: ''}[permission.role]">Write</button>
|
<button type="button" class="btn btn-default" ng-click="setRole(name, 'write', 'team')" ng-class="{read: '', write: 'active', admin: ''}[permission.role]">Write</button>
|
||||||
<button type="button" class="btn btn-default" ng-click="setRole(username, 'admin')" ng-class="{read: '', write: '', admin: 'active'}[permission.role]">Admin</button>
|
<button type="button" class="btn btn-default" ng-click="setRole(name, 'admin', 'team')" ng-class="{read: '', write: '', admin: 'active'}[permission.role]">Admin</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="delete-ui" tabindex="0" title="Delete Permission">
|
<span class="delete-ui" tabindex="0" title="Delete Permission">
|
||||||
<span class="delete-ui-button" ng-click="deleteRole(username)"><button class="btn btn-danger">Delete</button></span>
|
<span class="delete-ui-button" ng-click="deleteRole(name, 'team')"><button class="btn btn-danger">Delete</button></span>
|
||||||
|
<i class="fa fa-remove"></i>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- User Permissions -->
|
||||||
|
<tr ng-repeat="(name, permission) in permissions['user']">
|
||||||
|
<td class="{{ 'user entity ' + (permission.outside_org ? 'outside' : '') }}">
|
||||||
|
<i class="fa fa-user"></i>
|
||||||
|
<span>{{name}}</span>
|
||||||
|
</td>
|
||||||
|
<td class="user-permissions">
|
||||||
|
<div class="btn-group btn-group-sm">
|
||||||
|
<button type="button" class="btn btn-default" ng-click="setRole(name, 'read', 'user')" ng-class="{read: 'active', write: '', admin: ''}[permission.role]">Read only</button>
|
||||||
|
<button type="button" class="btn btn-default" ng-click="setRole(name, 'write', 'user')" ng-class="{read: '', write: 'active', admin: ''}[permission.role]">Write</button>
|
||||||
|
<button type="button" class="btn btn-default" ng-click="setRole(name, 'admin', 'user')" ng-class="{read: '', write: '', admin: 'active'}[permission.role]">Admin</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="delete-ui" tabindex="0" title="Delete Permission">
|
||||||
|
<span class="delete-ui-button" ng-click="deleteRole(name, 'user')"><button class="btn btn-danger">Delete</button></span>
|
||||||
<i class="fa fa-remove"></i>
|
<i class="fa fa-remove"></i>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
@ -57,7 +79,7 @@
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<input id="userSearch" class="form-control" placeholder="Add new user...">
|
<span class="entity-search" organization="repo.namespace" input-title="'Add a ' + (repo.is_organization ? 'team or ' : '') + 'user...'" entity-selected="addNewPermission"></span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -283,4 +305,24 @@
|
||||||
</div><!-- /.modal-dialog -->
|
</div><!-- /.modal-dialog -->
|
||||||
</div><!-- /.modal -->
|
</div><!-- /.modal -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Modal message dialog -->
|
||||||
|
<div class="modal fade" id="confirmaddoutsideModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h4 class="modal-title">Add User?</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
The selected user is outside of your organization. Are you sure you want to grant the user access to this repository?
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-primary" ng-click="grantRole()">Yes, I'm sure</button>
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div><!-- /.modal-content -->
|
||||||
|
</div><!-- /.modal-dialog -->
|
||||||
|
</div><!-- /.modal -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
Reference in a new issue