diff --git a/endpoints/api.py b/endpoints/api.py index 07c3fd7f8..22300cb52 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -297,7 +297,7 @@ def get_matching_entities(prefix): } if user.is_org_member is not None: - user_json['is_org_member'] = user.is_org_member + user_json['is_org_member'] = user.is_robot or user.is_org_member return user_json @@ -924,8 +924,8 @@ def wrap_role_view_user(role_json, user): return role_json -def wrap_role_view_org(role_json, org_member): - role_json['is_org_member'] = org_member +def wrap_role_view_org(role_json, user, org_members): + role_json['is_org_member'] = user.robot or user.username in org_members return role_json @@ -1034,22 +1034,30 @@ def list_repo_team_permissions(namespace, repository): def list_repo_user_permissions(namespace, repository): permission = AdministerRepositoryPermission(namespace, repository) if permission.can(): - # Determine how to wrap the permissions - role_view_func = role_view + # Lookup the organization (if any). + org = None try: - model.get_organization(namespace) # Will raise an error if not org - org_members = model.get_organization_member_set(namespace) - def wrapped_role_view(repo_perm): - unwrapped = wrap_role_view_user(role_view(repo_perm), repo_perm.user) - return wrap_role_view_org(unwrapped, - repo_perm.user.username in org_members) - - role_view_func = wrapped_role_view - + org = model.get_organization(namespace) # Will raise an error if not org except model.InvalidOrganizationException: # This repository isn't under an org pass + # Determine how to wrap the role(s). + def wrapped_role_view(repo_perm): + return wrap_role_view_user(role_view(repo_perm), repo_perm.user) + + role_view_func = wrapped_role_view + + if org: + org_members = model.get_organization_member_set(namespace) + current_func = role_view_func + + def wrapped_role_org_view(repo_perm): + return wrap_role_view_org(current_func(repo_perm), repo_perm.user, org_members) + + role_view_func = wrapped_role_org_view + + # Load and return the permissions. repo_perms = model.get_all_repo_users(namespace, repository) return jsonify({ 'permissions': {perm.user.username: role_view_func(perm) @@ -1074,8 +1082,7 @@ def get_user_permissions(namespace, repository, username): try: model.get_organization(namespace) org_members = model.get_organization_member_set(namespace) - perm_view = wrap_role_view_org(perm_view, - perm.user.username in org_members) + perm_view = wrap_role_view_org(perm_view, perm.user, org_members) except model.InvalidOrganizationException: # This repository is not part of an organization pass @@ -1119,8 +1126,7 @@ def change_user_permissions(namespace, repository, username): try: model.get_organization(namespace) org_members = model.get_organization_member_set(namespace) - perm_view = wrap_role_view_org(perm_view, - perm.user.username in org_members) + perm_view = wrap_role_view_org(perm_view, perm.user, org_members) except model.InvalidOrganizationException: # This repository is not part of an organization pass diff --git a/static/css/quay.css b/static/css/quay.css index b7d63acc3..62e7cb7a8 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -3,6 +3,11 @@ margin: 0; } +#input-box { + padding: 4px; + font-size: 14px; +} + html, body { height: 100%; } @@ -16,6 +21,45 @@ html, body { border-bottom: 1px dashed #aaa; } +.docker-auth-dialog .token-dialog-body .well { + margin-bottom: 0px; +} + +.docker-auth-dialog .token-view { + background: transparent; + display: block; + border: 0px transparent; + font-size: 12px; + width: 100%; +} + +.docker-auth-dialog .download-cfg { + float: left; + padding-top: 6px; + font-size: 16px; +} + +.docker-auth-dialog .download-cfg .fa-download { + margin-right: 10px; + font-size: 25px; + vertical-align: middle; +} + +#copyClipboard { + cursor: pointer; +} + +#copyClipboard.zeroclipboard-is-hover { + background: #428bca; + color: white; +} + +#clipboardCopied.hovering { + position: absolute; + right: 0px; + top: 40px; +} + .content-container { padding-bottom: 70px; } @@ -44,8 +88,9 @@ html, body { margin-bottom: 20px; } -.robots-manager-element .robot { +.robots-manager-element .robot a { font-size: 16px; + cursor: pointer; } .robots-manager-element .robot .prefix { @@ -970,21 +1015,6 @@ p.editable:hover i { width: 300px; } -.repo #copyClipboard { - cursor: pointer; -} - -.repo #copyClipboard.zeroclipboard-is-hover { - background: #428bca; - color: white; -} - -.repo #clipboardCopied.hovering { - position: absolute; - right: 0px; - top: 40px; -} - .repo-image-view .id-container { display: inline-block; margin-top: 10px; @@ -1027,7 +1057,7 @@ p.editable:hover i { margin-top: 28px; } -.repo #clipboardCopied { +#clipboardCopied { font-size: 0.8em; display: inline-block; margin-right: 10px; @@ -1038,7 +1068,7 @@ p.editable:hover i { border-radius: 4px; } -.repo #clipboardCopied.animated { +#clipboardCopied.animated { -webkit-animation: fadeOut 4s ease-in-out 0s 1 forwards; -moz-animation: fadeOut 4s ease-in-out 0s 1 forwards; -ms-animation: fadeOut 4s ease-in-out 0s 1 forwards; @@ -1125,21 +1155,17 @@ p.editable:hover i { width: 300px; } -.repo-admin .token-dialog-body .well { - margin-bottom: 0px; -} - -.repo-admin .token-view { - background: transparent; - display: block; - border: 0px transparent; - font-size: 12px; - width: 100%; -} - .repo-admin .panel { display: inline-block; - width: 620px; + width: 720px; +} + +.repo-admin .prefix { + color: #aaa; +} + +.repo-admin .admin-search { + padding-top: 20px; } .repo-admin .user i.fa-user { @@ -1147,6 +1173,11 @@ p.editable:hover i { margin-right: 7px; } +.repo-admin .user i.fa-wrench { + margin-left: 1px; + margin-right: 5px; +} + .repo-admin .team i.fa-group { margin-right: 4px; } @@ -1278,18 +1309,6 @@ p.editable:hover i { white-space: nowrap; } -.repo .download-cfg { - float: left; - padding-top: 6px; - font-size: 16px; -} - -.repo .download-cfg .icon-download { - margin-right: 10px; - font-size: 25px; - vertical-align: middle; -} - .navbar-nav > li > .user-dropdown { padding-top: 9px; padding-bottom: 9px; diff --git a/static/directives/docker-auth-dialog.html b/static/directives/docker-auth-dialog.html new file mode 100644 index 000000000..dcb71a25b --- /dev/null +++ b/static/directives/docker-auth-dialog.html @@ -0,0 +1,30 @@ + + diff --git a/static/directives/popup-input-button.html b/static/directives/popup-input-button.html new file mode 100644 index 000000000..005c037bc --- /dev/null +++ b/static/directives/popup-input-button.html @@ -0,0 +1,5 @@ + + diff --git a/static/directives/popup-input-dialog.html b/static/directives/popup-input-dialog.html new file mode 100644 index 000000000..6632b1999 --- /dev/null +++ b/static/directives/popup-input-dialog.html @@ -0,0 +1,4 @@ +
+ +
diff --git a/static/directives/robots-manager.html b/static/directives/robots-manager.html index 21e916296..ef787a3dd 100644 --- a/static/directives/robots-manager.html +++ b/static/directives/robots-manager.html @@ -1,10 +1,13 @@
-
Robot accounts allow for delegating access in multiple repositories to tokens
+
Robot accounts allow for delegating access in multiple repositories to role-based accounts that you manage
-
- +
+ + Create Robot Account +
@@ -16,7 +19,9 @@
- {{ getPrefix(robotInfo.name) }}+{{ getShortenedName(robotInfo.name) }} + + {{ getPrefix(robotInfo.name) }}+{{ getShortenedName(robotInfo.name) }} + @@ -26,6 +31,10 @@
+
+
+ {{ shownRobot.name }}
diff --git a/static/js/app.js b/static/js/app.js index 7561fd2e6..a2d30a5fb 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -550,7 +550,7 @@ quayApp.directive('plansTable', function () { priority: 0, templateUrl: '/static/directives/plans-table.html', replace: false, - transclude: true, + transclude: false, restrict: 'C', scope: { 'plans': '=plans', @@ -566,13 +566,65 @@ quayApp.directive('plansTable', function () { }); +quayApp.directive('dockerAuthDialog', function () { + var directiveDefinitionObject = { + priority: 0, + templateUrl: '/static/directives/docker-auth-dialog.html', + replace: false, + transclude: true, + restrict: 'C', + scope: { + 'username': '=username', + 'token': '=token', + 'shown': '=shown', + 'counter': '=counter' + }, + controller: function($scope, $element, Restangular) { + $scope.isDownloadSupported = function() { + try { return !!new Blob(); } catch(e){} + return false; + }; + + $scope.downloadCfg = function() { + var auth = $.base64.encode($scope.username + ":" + $scope.token); + config = { + "https://quay.io/v1/": { + "auth": auth, + "email": "" + } + }; + + var file = JSON.stringify(config, null, ' '); + var blob = new Blob([file]); + saveAs(blob, '.dockercfg'); + }; + + var show = function(r) { + if (!$scope.shown || !$scope.username || !$scope.token) { + $('#dockerauthmodal').modal('hide'); + return; + } + + $('#copyClipboard').clipboardCopy(); + $('#dockerauthmodal').modal({}); + }; + + $scope.$watch('counter', show); + $scope.$watch('shown', show); + $scope.$watch('username', show); + $scope.$watch('token', show); + } + }; + return directiveDefinitionObject; +}); + quayApp.directive('robotsManager', function () { var directiveDefinitionObject = { priority: 0, templateUrl: '/static/directives/robots-manager.html', replace: false, - transclude: true, + transclude: false, restrict: 'C', scope: { 'organization': '=organization', @@ -581,6 +633,13 @@ quayApp.directive('robotsManager', function () { controller: function($scope, $element, Restangular) { $scope.robots = null; $scope.loading = false; + $scope.shownRobot = null; + $scope.showRobotCounter = 0; + + $scope.showRobot = function(info) { + $scope.shownRobot = info; + $scope.showRobotCounter++; + }; $scope.getShortenedName = function(name) { var plus = name.indexOf('+'); @@ -592,6 +651,28 @@ quayApp.directive('robotsManager', function () { return name.substr(0, plus); }; + $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" + } + } + }); + }); + }; + $scope.deleteRobot = function(info) { var shortName = $scope.getShortenedName(info.name); var url = $scope.organization ? getRestUrl('organization', $scope.organization.name, 'robots', shortName) : @@ -601,12 +682,21 @@ quayApp.directive('robotsManager', function () { deleteRobot.customDELETE().then(function(resp) { for (var i = 0; i < $scope.robots.length; ++i) { if ($scope.robots[i].name == info.name) { - $scope.robots.slice(i, 1); + $scope.robots.splice(i, 1); return; } } }, function() { - + bootbox.dialog({ + "message": 'The selected robot account could not be deleted', + "title": "Cannot delete robot account", + "buttons": { + "close": { + "label": "Close", + "className": "btn-primary" + } + } + }); }); }; @@ -631,6 +721,52 @@ quayApp.directive('robotsManager', function () { }); +quayApp.directive('popupInputButton', function () { + var directiveDefinitionObject = { + priority: 0, + templateUrl: '/static/directives/popup-input-button.html', + replace: false, + transclude: true, + restrict: 'C', + scope: { + 'placeholder': '=placeholder', + 'pattern': '=pattern', + 'submitted': '&submitted' + }, + controller: function($scope, $element) { + $scope.popupShown = function() { + setTimeout(function() { + var box = $('#input-box'); + box[0].value = ''; + box.focus(); + }, 10); + }; + + $scope.getRegexp = function(pattern) { + if (!pattern) { + pattern = '.*'; + } + return new RegExp(pattern); + }; + + $scope.inputSubmit = function() { + var box = $('#input-box'); + if (box.hasClass('ng-invalid')) { return; } + + var entered = box[0].value; + if (!entered) { + return; + } + + if ($scope.submitted) { + $scope.submitted({'value': entered}); + } + }; + } + }; + return directiveDefinitionObject; +}); + quayApp.directive('organizationHeader', function () { var directiveDefinitionObject = { diff --git a/static/js/controllers.js b/static/js/controllers.js index 2d6dd6ccc..c3edc3733 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -422,30 +422,19 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) { 'html': true }); - $('#copyClipboard').clipboardCopy(); - var namespace = $routeParams.namespace; var name = $routeParams.name; $scope.permissions = {'team': [], 'user': []}; - $scope.isDownloadSupported = function() { - try { return !!new Blob(); } catch(e){} - return false; + $scope.getPrefix = function(name) { + var plus = name.indexOf('+'); + return name.substr(0, plus + 1); }; - $scope.downloadCfg = function(token) { - var auth = $.base64.encode("$token:" + token.code); - config = { - "https://quay.io/v1/": { - "auth": auth, - "email": "" - } - }; - - var file = JSON.stringify(config, null, ' '); - var blob = new Blob([file]); - saveAs(blob, '.dockercfg'); + $scope.getShortenedName = function(name) { + var plus = name.indexOf('+'); + return name.substr(plus + 1); }; $scope.grantRole = function() { @@ -554,9 +543,11 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) { }); }; + $scope.shownTokenCounter = 0; + $scope.showToken = function(tokenCode) { $scope.shownToken = $scope.tokens[tokenCode]; - $('#tokenmodal').modal({}); + $scope.shownTokenCounter++; }; $scope.askChangeAccess = function(newAccess) { @@ -1103,17 +1094,7 @@ function OrgViewCtrl($rootScope, $scope, Restangular, $routeParams) { }); }; - $scope.createTeamShown = function() { - setTimeout(function() { - $('#create-team-box').focus(); - }, 10); - }; - - $scope.createTeam = function() { - var box = $('#create-team-box'); - if (box.hasClass('ng-invalid')) { return; } - - var teamname = box[0].value.toLowerCase(); + $scope.createTeam = function(teamname) { if (!teamname) { return; } diff --git a/static/partials/create-team-dialog.html b/static/partials/create-team-dialog.html deleted file mode 100644 index 41a599189..000000000 --- a/static/partials/create-team-dialog.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/static/partials/org-view.html b/static/partials/org-view.html index c1011d74f..2d05e1426 100644 --- a/static/partials/org-view.html +++ b/static/partials/org-view.html @@ -9,7 +9,12 @@
- + + + Create Team + + Settings
diff --git a/static/partials/repo-admin.html b/static/partials/repo-admin.html index 34f378cb5..df7ff652c 100644 --- a/static/partials/repo-admin.html +++ b/static/partials/repo-admin.html @@ -34,16 +34,16 @@
-
User and Team Access Permissions +
Access Permissions - +
- + @@ -52,7 +52,7 @@ - @@ -107,7 +107,7 @@
User/TeamUser/Team/Robot Account Permissions
- + {{name}} @@ -69,9 +69,9 @@
- - - {{name}} + + + {{getPrefix(name)}}{{getShortenedName(name)}} @@ -88,7 +88,7 @@
+
- + @@ -133,10 +133,10 @@ - - @@ -238,6 +238,10 @@ +
+ {{ shownToken.friendlyName }} +
- - - -
Token DescriptionToken Description Permissions
+ +