diff --git a/endpoints/api.py b/endpoints/api.py index 786393810..2cd7e208d 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -828,7 +828,8 @@ def get_repo_api(namespace, repository): 'can_admin': can_admin, 'is_public': is_public, 'is_building': len(active_builds) > 0, - 'is_organization': bool(organization) + 'is_organization': bool(organization), + 'is_org_admin': bool(organization) and AdministerOrganizationPermission(namespace).can() }) abort(404) # Not found diff --git a/static/css/quay.css b/static/css/quay.css index 3b13de3b8..ad2472baa 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -3,6 +3,39 @@ margin: 0; } +.entity-search-element input { + vertical-align: middle; +} + +.entity-search-element .twitter-typeahead { + vertical-align: middle; +} + +.entity-search-element .dropdown { + vertical-align: middle; + display: inline-block; + margin-top: 0px; +} + +.dropdown-menu i.fa { + margin-right: 6px; + position: relative; +} + +.dropdown-menu .new-action i.fa { + atext-shadow: 2px 2px 7px #5cb85c, 2px 2px 7px #5cb85c; +} + +.dropdown-menu .new-action i.fa:after { + content: "+"; + color: #5cb85c; + position: absolute; + left: -14px; + top: -2px; + font-weight: bold; + font-size: 115%; +} + #input-box { padding: 4px; font-size: 14px; diff --git a/static/directives/entity-reference.html b/static/directives/entity-reference.html index 9b82b457e..e1380800c 100644 --- a/static/directives/entity-reference.html +++ b/static/directives/entity-reference.html @@ -2,6 +2,7 @@ - {{team}} + {{team}} + {{team}} {{getPrefix(name)}}{{getShortenedName(name)}} diff --git a/static/directives/entity-search.html b/static/directives/entity-search.html index 37e2d1a3f..eddd44916 100644 --- a/static/directives/entity-search.html +++ b/static/directives/entity-search.html @@ -1 +1,39 @@ - + + + diff --git a/static/directives/robots-manager.html b/static/directives/robots-manager.html index ef787a3dd..c478fbb02 100644 --- a/static/directives/robots-manager.html +++ b/static/directives/robots-manager.html @@ -4,7 +4,7 @@
- Create Robot Account diff --git a/static/js/app.js b/static/js/app.js index cdc3ddabf..719bb9953 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -1,3 +1,6 @@ +var TEAM_PATTERN = '^[a-zA-Z][a-zA-Z0-9]+$'; +var ROBOT_PATTERN = '^[a-zA-Z][a-zA-Z0-9]+$'; + function getFirstTextLine(commentString) { if (!commentString) { return ''; } @@ -31,6 +34,45 @@ function getFirstTextLine(commentString) { return ''; } +function createRobotAccount(Restangular, is_org, orgname, name, callback) { + var url = is_org ? getRestUrl('organization', orgname, 'robots', name) : + getRestUrl('user/robots', name); + var createRobot = Restangular.one(url); + createRobot.customPUT().then(callback, function(resp) { + bootbox.dialog({ + "message": resp.data ? resp.data : 'The robot account could not be created', + "title": "Cannot create robot account", + "buttons": { + "close": { + "label": "Close", + "className": "btn-primary" + } + } + }); + }); +} + +function createOrganizationTeam(Restangular, orgname, teamname, callback) { + var createTeam = Restangular.one(getRestUrl('organization', orgname, 'team', teamname)); + var data = { + 'name': teamname, + 'role': 'member' + }; + + createTeam.customPOST(data).then(callback, function() { + bootbox.dialog({ + "message": resp.data ? resp.data : 'The team could not be created', + "title": "Cannot create team", + "buttons": { + "close": { + "label": "Close", + "className": "btn-primary" + } + } + }); + }); +} + function getRestUrl(args) { var url = ''; for (var i = 0; i < arguments.length; ++i) { @@ -474,7 +516,8 @@ quayApp.directive('entityReference', function () { 'name': '=name', 'orgname': '=orgname', 'team': '=team', - 'isrobot': '=isrobot' + 'isrobot': '=isrobot', + 'isorgadmin': '=isorgadmin' }, controller: function($scope, $element) { $scope.getPrefix = function(name) { @@ -905,6 +948,7 @@ quayApp.directive('robotsManager', function () { 'user': '=user' }, controller: function($scope, $element, Restangular) { + $scope.ROBOT_PATTERN = ROBOT_PATTERN; $scope.robots = null; $scope.loading = false; $scope.shownRobot = null; @@ -928,23 +972,10 @@ quayApp.directive('robotsManager', function () { $scope.createRobot = function(name) { if (!name) { return; } - var url = $scope.organization ? getRestUrl('organization', $scope.organization.name, 'robots', name) : - getRestUrl('user/robots', name); - var createRobot = Restangular.one(url); - createRobot.customPUT().then(function(resp) { - $scope.robots.push(resp); - }, function(resp) { - bootbox.dialog({ - "message": resp.data ? resp.data : 'The robot account could not be created', - "title": "Cannot create robot account", - "buttons": { - "close": { - "label": "Close", - "className": "btn-primary" - } - } - }); - }); + createRobotAccount(Restangular, !!$scope.organization, $scope.organization ? $scope.organization.name : '', name, + function(created) { + $scope.robots.push(created); + }); }; $scope.deleteRobot = function(info) { @@ -1218,14 +1249,89 @@ quayApp.directive('entitySearch', function () { 'namespace': '=namespace', 'inputTitle': '=inputTitle', 'entitySelected': '=entitySelected', - 'includeTeams': '=includeTeams' + 'includeTeams': '=includeTeams', + 'isOrganization': '=isOrganization' }, - controller: function($scope, $element) { + controller: function($scope, $element, Restangular) { + $scope.lazyLoading = true; + $scope.isAdmin = false; + + $scope.lazyLoad = function() { + if (!$scope.namespace || !$scope.lazyLoading) { return; } + + if ($scope.isOrganization && $scope.includeTeams) { + var url = getRestUrl('organization', $scope.namespace); + var getOrganization = Restangular.one(url); + getOrganization.customGET().then(function(resp) { + $scope.teams = resp.teams; + }); + } + + var url = $scope.isOrganization ? getRestUrl('organization', $scope.namespace, 'robots') : 'user/robots'; + var getRobots = Restangular.one(url); + getRobots.customGET().then(function(resp) { + $scope.robots = resp.robots; + $scope.isAdmin = true; + $scope.lazyLoading = false; + }, function() { + $scope.isAdmin = false; + $scope.lazyLoading = false; + }); + }; + + $scope.createTeam = function() { + bootbox.prompt('Enter the name of the new team', function(teamname) { + if (!teamname) { return; } + + var regex = new RegExp(TEAM_PATTERN); + if (!regex.test(teamname)) { + bootbox.alert('Invalid team name'); + return; + } + + createOrganizationTeam(Restangular, $scope.namespace, teamname, function(created) { + $scope.setEntity(created.name, 'team', false); + $scope.teams[teamname] = created; + }); + }); + }; + + $scope.createRobot = function() { + bootbox.prompt('Enter the name of the new robot account', function(robotname) { + if (!robotname) { return; } + + var regex = new RegExp(ROBOT_PATTERN); + if (!regex.test(robotname)) { + bootbox.alert('Invalid robot account name'); + return; + } + + createRobotAccount(Restangular, $scope.isOrganization, $scope.namespace, robotname, function(created) { + $scope.setEntity(created.name, 'user', true); + $scope.robots.push(created); + }); + }); + }; + + $scope.setEntity = function(name, kind, is_robot) { + var entity = { + 'name': name, + 'kind': kind, + 'is_robot': is_robot + }; + + if ($scope.is_organization) { + entity['is_org_member'] = true; + } + + $scope.entitySelected(entity); + }; + if (!$scope.entitySelected) { return; } number++; - var input = $element[0].firstChild; + var input = $element[0].firstChild.firstChild; $scope.namespace = $scope.namespace || ''; $(input).typeahead({ name: 'entities' + number, @@ -1274,7 +1380,9 @@ quayApp.directive('entitySearch', function () { $(input).on('typeahead:selected', function(e, datum) { $(input).typeahead('setQuery', ''); - $scope.entitySelected(datum.entity); + $scope.$apply(function() { + $scope.entitySelected(datum.entity); + }); }); $scope.$watch('inputTitle', function(title) { diff --git a/static/js/controllers.js b/static/js/controllers.js index b3f8a5b90..00ffe5c85 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -449,11 +449,7 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) { 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(entity.name, 'read', entity.kind); - }); + $scope.addRole(entity.name, 'read', entity.kind); }; $scope.deleteRole = function(entityName, kind) { @@ -1051,6 +1047,7 @@ function OrgViewCtrl($rootScope, $scope, Restangular, $routeParams) { 'html': true }); + $scope.TEAM_PATTERN = TEAM_PATTERN; $rootScope.title = 'Loading...'; var orgname = $routeParams.orgname; @@ -1102,15 +1099,8 @@ function OrgViewCtrl($rootScope, $scope, Restangular, $routeParams) { return; } - var createTeam = Restangular.one(getRestUrl('organization', orgname, 'team', teamname)); - var data = { - 'name': teamname, - 'role': 'member' - }; - createTeam.customPOST(data).then(function(resp) { - $scope.organization.teams[teamname] = resp; - }, function() { - $('#cannotChangeTeamModal').modal({}); + createOrganizationTeam(Restangular, orgname, teamname, function(created) { + $scope.organization.teams[teamname] = created; }); }; @@ -1237,13 +1227,11 @@ function TeamViewCtrl($rootScope, $scope, Restangular, $routeParams) { $scope.addNewMember = function(member) { if ($scope.members[member.name]) { return; } - $scope.$apply(function() { - var addMember = Restangular.one(getRestUrl('organization', $scope.orgname, 'team', teamname, 'members', member.name)); - addMember.customPOST().then(function(resp) { - $scope.members[member.name] = resp; - }, function() { - $('#cannotChangeMembersModal').modal({}); - }); + var addMember = Restangular.one(getRestUrl('organization', $scope.orgname, 'team', teamname, 'members', member.name)); + addMember.customPOST().then(function(resp) { + $scope.members[member.name] = resp; + }, function() { + $('#cannotChangeMembersModal').modal({}); }); }; diff --git a/static/partials/org-admin.html b/static/partials/org-admin.html index 7a8bcdb3e..17bdf56df 100644 --- a/static/partials/org-admin.html +++ b/static/partials/org-admin.html @@ -126,7 +126,7 @@ - + diff --git a/static/partials/org-view.html b/static/partials/org-view.html index 2d05e1426..2a6deeb52 100644 --- a/static/partials/org-view.html +++ b/static/partials/org-view.html @@ -10,7 +10,7 @@
- Create Team diff --git a/static/partials/repo-admin.html b/static/partials/repo-admin.html index b48938b2f..9ca4e8686 100644 --- a/static/partials/repo-admin.html +++ b/static/partials/repo-admin.html @@ -58,7 +58,7 @@ - + @@ -74,7 +74,7 @@ - + @@ -92,7 +92,7 @@ - + diff --git a/static/partials/team-view.html b/static/partials/team-view.html index 9e97d21f5..1c52dfb35 100644 --- a/static/partials/team-view.html +++ b/static/partials/team-view.html @@ -32,7 +32,8 @@ - +