- 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:
Joseph Schorr 2013-11-01 21:48:10 -04:00
parent 09afe0753f
commit 100ec563fa
7 changed files with 362 additions and 93 deletions

View file

@ -574,14 +574,20 @@ form input.ng-valid.ng-dirty {
}
.user-mini-listing {
.entity-mini-listing {
margin: 2px;
}
.user-mini-listing i {
.entity-mini-listing i {
margin-right: 8px;
}
.entity-mini-listing .warning {
margin-top: 6px;
font-size: 10px;
padding: 4px;
}
.editable {
position: relative;
}
@ -898,6 +904,10 @@ p.editable:hover i {
padding-left: 44px;
}
.repo-admin .entity-search input {
width: 300px;
}
.repo-admin .token-dialog-body .well {
margin-bottom: 0px;
}
@ -916,10 +926,21 @@ p.editable:hover 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;
min-width: 300px;
}

View file

@ -0,0 +1 @@
<input class="entity-search-control form-control">

View file

@ -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 () {
var directiveDefinitionObject = {
priority: 0,

View file

@ -527,39 +527,7 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
var namespace = $routeParams.namespace;
var name = $routeParams.name;
$scope.$on('$viewContentLoaded', function() {
// 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.permissions = {'team': [], 'user': []};
$scope.isDownloadSupported = function() {
try { return !!new Blob(); } catch(e){}
@ -580,21 +548,34 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
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.
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
// the XHR call to be made.
$scope.$apply(function() {
$scope.addRole(username, 'read')
$scope.addRole(entity.name, 'read', entity.kind, entity.outside_org)
});
};
$scope.deleteRole = function(username) {
var permissionDelete = Restangular.one('repository/' + namespace + '/' + name + '/permissions/' + username);
$scope.deleteRole = function(entityName, kind) {
var permissionDelete = Restangular.one('repository/' + namespace + '/' + name + '/permissions/' + kind + '/' + entityName);
permissionDelete.customDELETE().then(function() {
delete $scope.permissions[username];
delete $scope.permissions[kind][entityName];
}, function(result) {
if (result.status == 409) {
$('#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 = {
'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() {
$scope.permissions[username] = permission;
$scope.permissions = $scope.permissions;
$scope.permissions[kind][entityName] = permission;
}, function(result) {
$('#cannotchangeModal').modal({});
});
};
$scope.setRole = function(username, role) {
var permission = $scope.permissions[username];
$scope.setRole = function(entityName, role, kind) {
var permission = $scope.permissions[kind][entityName];
var currentRole = permission.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) {
if (result.status == 409) {
permission.role = currentRole;
@ -709,6 +690,23 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
$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.
var repositoryFetch = Restangular.one('repository/' + namespace + '/' + name);
repositoryFetch.get().then(function(repo) {
@ -720,23 +718,15 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
$scope.loading = false;
});
// Fetch the permissions.
var permissionsFetch = Restangular.one('repository/' + namespace + '/' + name + '/permissions');
permissionsFetch.get().then(function(resp) {
$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 user and team permissions.
fetchPermissions('user');
fetchPermissions('team');
// Fetch the tokens.
var tokensFetch = Restangular.one('repository/' + namespace + '/' + name + '/tokens/');
tokensFetch.get().then(function(resp) {
$scope.tokens = resp.tokens;
$scope.loading = !($scope.permissions && $scope.repo && $scope.tokens);
checkLoading();
}, function() {
$scope.tokens = null;
$scope.loading = false;

View file

@ -20,36 +20,58 @@
<!-- User Access Permissions -->
<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 class="panel-body">
<table class="permissions">
<thead>
<tr>
<td>User</td>
<td>User<span ng-show="repo.is_organization">/Team</span></td>
<td>Permissions</td>
<td></td>
</tr>
</thead>
<tr ng-repeat="(username, permission) in permissions">
<td class="user">
<i class="fa fa-user"></i>
<span>{{username}}</span>
<!-- Team Permissions -->
<tr ng-repeat="(name, permission) in permissions['team']">
<td class="team entity">
<i class="fa fa-group"></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(username, 'read')" 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(username, 'admin')" ng-class="{read: '', write: '', admin: 'active'}[permission.role]">Admin</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(name, 'write', 'team')" ng-class="{read: '', write: 'active', admin: ''}[permission.role]">Write</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>
</td>
<td>
<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>
</span>
</td>
@ -57,7 +79,7 @@
<tr>
<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>
</tr>
</table>
@ -283,4 +305,24 @@
</div><!-- /.modal-dialog -->
</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">&times;</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>