diff --git a/endpoints/api/team.py b/endpoints/api/team.py index 1c633780f..125d82c35 100644 --- a/endpoints/api/team.py +++ b/endpoints/api/team.py @@ -231,6 +231,32 @@ class TeamMember(ApiResource): raise Unauthorized() +@resource('/v1/organization//team//invite/') +class InviteTeamMember(ApiResource): + """ Resource for inviting a team member via email address. """ + @require_scope(scopes.ORG_ADMIN) + @nickname('inviteTeamMemberEmail') + def put(self, orgname, teamname, email): + """ Invites an email address to an existing team. """ + permission = AdministerOrganizationPermission(orgname) + if permission.can(): + team = None + + # Find the team. + try: + team = model.get_organization_team(orgname, teamname) + except model.InvalidTeamException: + raise NotFound() + + # Invite the email to the team. + inviter = get_authenticated_user() + invite = handle_addinvite_team(inviter, team, email=email) + log_action('org_invite_team_member', orgname, {'email': email, 'team': teamname}) + return invite_view(invite) + + raise Unauthorized() + + @resource('/v1/teaminvite/') @internal_only class TeamMemberInvite(ApiResource): diff --git a/static/css/quay.css b/static/css/quay.css index 16789b8a5..c33ed1c85 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -3544,6 +3544,12 @@ p.editable:hover i { white-space: nowrap; } +.tt-message { + padding: 10px; + font-size: 12px; + white-space: nowrap; +} + .tt-suggestion p { margin: 0; } diff --git a/static/directives/team-view-add.html b/static/directives/team-view-add.html index 5c9280076..c06f46a22 100644 --- a/static/directives/team-view-add.html +++ b/static/directives/team-view-add.html @@ -2,10 +2,13 @@
diff --git a/static/js/app.js b/static/js/app.js index 78f543e5a..6ebadb5fd 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -399,6 +399,11 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading $provide.factory('UtilService', ['$sanitize', function($sanitize) { var utilService = {}; + utilService.isEmailAddress = function(val) { + var emailRegex = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/; + return emailRegex.test(val); + }; + utilService.escapeHtmlString = function(text) { var adjusted = text.replace(/&/g, "&") .replace(/ 0) { - return '
A ' + Config.REGISTRY_TITLE_SHORT + ' username (not an e-mail address) must be specified
'; + if (UtilService.isEmailAddress(val)) { + if ($scope.allowEmails) { + return '
' + $scope.emailMessage + '
'; + } else { + return '
A ' + Config.REGISTRY_TITLE_SHORT + ' username (not an e-mail address) must be specified
'; + } } var classes = []; @@ -3743,6 +3759,16 @@ quayApp.directive('entitySearch', function () { }} }); + $(input).on('keypress', function(e) { + var val = $(input).val(); + var code = e.keyCode || e.which; + if (code == 13 && $scope.allowEmails && UtilService.isEmailAddress(val)) { + $scope.$apply(function() { + $scope.emailSelected({'email': val}); + }); + } + }); + $(input).on('input', function(e) { $scope.$apply(function() { $scope.clearEntityInternal(); diff --git a/static/js/controllers.js b/static/js/controllers.js index 82ed5c684..fad0d2cbe 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -2371,6 +2371,28 @@ function TeamViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams) }; }; + $scope.inviteEmail = function(email) { + if (!email || $scope.memberMap[email]) { return; } + + $scope.addingMember = true; + + var params = { + 'orgname': orgname, + 'teamname': teamname, + 'email': email + }; + + var errorHandler = ApiService.errorDisplay('Cannot invite team member', function() { + $scope.addingMember = false; + }); + + ApiService.inviteTeamMemberEmail(null, params).then(function(resp) { + $scope.members.push(resp); + $scope.memberMap[resp.name] = resp; + $scope.addingMember = false; + }, errorHandler); + }; + $scope.addNewMember = function(member) { if (!member || $scope.memberMap[member.name]) { return; } @@ -2380,15 +2402,16 @@ function TeamViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams) 'membername': member.name }; + var errorHandler = ApiService.errorDisplay('Cannot add team member', function() { + $scope.addingMember = false; + }); + $scope.addingMember = true; ApiService.updateOrganizationTeamMember(null, params).then(function(resp) { $scope.members.push(resp); $scope.memberMap[resp.name] = resp; $scope.addingMember = false; - }, function() { - $('#cannotChangeMembersModal').modal({}); - $scope.addingMember = false; - }); + }, errorHandler); }; $scope.removeMember = function(username) {