diff --git a/static/css/core-ui.css b/static/css/core-ui.css index 86a3eed90..3352c7a49 100644 --- a/static/css/core-ui.css +++ b/static/css/core-ui.css @@ -1527,13 +1527,13 @@ a:focus { -webkit-filter: grayscale(100%); } - .co-dialog .co-tab-content { padding: 16px; padding-bottom: 30px; } .co-dialog .co-tab-content h3 { + margin-top: 0px; margin-bottom: 0px; } @@ -1574,3 +1574,9 @@ a:focus { height: auto; } } + +.co-modal-body-scrollable { + overflow-y: auto; + overflow-x: hidden; + max-height: 400px; +} \ No newline at end of file diff --git a/static/css/directives/ui/create-entity-dialog.css b/static/css/directives/ui/create-entity-dialog.css new file mode 100644 index 000000000..f7eb3fc9f --- /dev/null +++ b/static/css/directives/ui/create-entity-dialog.css @@ -0,0 +1,53 @@ +.create-entity-dialog-element .modal-body { + min-height: 300px; +} + +.create-entity-dialog-element form { + padding: 10px; +} + +.create-entity-dialog-element .help-text { + color: #aaa; + margin-top: 10px; +} + +.create-entity-dialog-element h4 .fa { + margin-left: 4px; + margin-right: 4px; +} + +.create-entity-dialog-element label { + margin-top: 4px; +} + +.create-entity-dialog-element .co-table { + margin-top: 20px; +} + +.create-entity-dialog-element .fa-hdd-o { + margin-right: 4px; + vertical-align: middle; +} + +.create-entity-dialog-element .co-filter-box { + display: block; + float: right; + margin-bottom: 20px; +} + +.create-entity-dialog-element .co-filter-box .filter-message { + left: -180px; + top: 4px; +} + +.create-entity-dialog-element .co-filter-box input { + width: 100%; + padding-top: 2px; + padding-bottom: 2px; + height: 28px; +} + +.create-entity-dialog-element label .avatar { + vertical-align: text-bottom; + margin-left: 4px; +} \ No newline at end of file diff --git a/static/css/directives/ui/feedback-bar.css b/static/css/directives/ui/feedback-bar.css index b8fce1285..3f72cbc70 100644 --- a/static/css/directives/ui/feedback-bar.css +++ b/static/css/directives/ui/feedback-bar.css @@ -7,6 +7,7 @@ overflow: hidden; text-align: center; height: 42px; + pointer-events: none; } @keyframes flow-down-up { diff --git a/static/css/directives/ui/role-group.css b/static/css/directives/ui/role-group.css index 119a0db16..327a4665e 100644 --- a/static/css/directives/ui/role-group.css +++ b/static/css/directives/ui/role-group.css @@ -2,14 +2,26 @@ width: 90px; position: relative; text-align: left; + + padding: 4px; + padding-left: 10px; +} + +.role-group.small .btn { + padding: 2px; + padding-left: 10px; } .new-role-group .btn .caret { position: absolute; - top: 15px; + top: 13px; right: 7px; } +.role-group.small .btn .caret { + top: 11px; +} + .new-role-group .role-help-text { font-size: 12px; color: #ccc; diff --git a/static/directives/create-entity-dialog.html b/static/directives/create-entity-dialog.html new file mode 100644 index 000000000..eff7fa581 --- /dev/null +++ b/static/directives/create-entity-dialog.html @@ -0,0 +1,98 @@ +
+ +
\ No newline at end of file diff --git a/static/directives/create-robot-dialog.html b/static/directives/create-robot-dialog.html new file mode 100644 index 000000000..c631f7880 --- /dev/null +++ b/static/directives/create-robot-dialog.html @@ -0,0 +1,9 @@ +
+
+
+
+
\ No newline at end of file diff --git a/static/directives/create-team-dialog.html b/static/directives/create-team-dialog.html new file mode 100644 index 000000000..0aafffa9f --- /dev/null +++ b/static/directives/create-team-dialog.html @@ -0,0 +1,9 @@ +
+
+
+
+
\ No newline at end of file diff --git a/static/directives/entity-search.html b/static/directives/entity-search.html index dfb8b99db..6baa3db77 100644 --- a/static/directives/entity-search.html +++ b/static/directives/entity-search.html @@ -14,12 +14,12 @@
  • - + Create team
  • - + Create robot account @@ -70,4 +70,8 @@
  • +
    +
    diff --git a/static/directives/fetch-tag-dialog.html b/static/directives/fetch-tag-dialog.html index b75c7c2ab..0cb45f2d1 100644 --- a/static/directives/fetch-tag-dialog.html +++ b/static/directives/fetch-tag-dialog.html @@ -38,6 +38,7 @@ Pull Credentials: +
    +
    + +
    +
    +
    - + diff --git a/static/directives/repo-view/repo-panel-tags.html b/static/directives/repo-view/repo-panel-tags.html index 41194cdae..5c4c5c8c2 100644 --- a/static/directives/repo-view/repo-panel-tags.html +++ b/static/directives/repo-view/repo-panel-tags.html @@ -61,7 +61,7 @@ - +
    diff --git a/static/directives/repository-permissions-table.html b/static/directives/repository-permissions-table.html index eb0f114c7..a839617b1 100644 --- a/static/directives/repository-permissions-table.html +++ b/static/directives/repository-permissions-table.html @@ -106,6 +106,8 @@ @@ -125,6 +127,8 @@
    diff --git a/static/directives/robots-manager.html b/static/directives/robots-manager.html index d6f4ad755..22186560f 100644 --- a/static/directives/robots-manager.html +++ b/static/directives/robots-manager.html @@ -4,12 +4,9 @@
    - - Create Robot Account - +
    @@ -134,5 +131,6 @@
    +
    diff --git a/static/directives/teams-manager.html b/static/directives/teams-manager.html index 72b459257..e17a938a8 100644 --- a/static/directives/teams-manager.html +++ b/static/directives/teams-manager.html @@ -13,23 +13,17 @@
    - - - Create New Team -
    - +
    +
    +
    = 0 || - tagInfo.image_id.indexOf($scope.options.tagFilter) >= 0) { - tags.push(tagInfo); - } } // Sort the tags by the predicate and the reverse, and map the information. var imageIDs = []; - var ordered = orderBy(tags, $scope.options.predicate, $scope.options.reverse); - var checked = []; + var ordered = TableService.buildOrderedItems(allTags, $scope.options, + ['name'], ['last_modified_datetime', 'size']).entries; + var checked = []; var imageMap = {}; var imageIndexMap = {}; for (var i = 0; i < ordered.length; ++i) { @@ -175,7 +167,7 @@ angular.module('quay').directive('repoPanelTags', function () { $scope.$watch('options.predicate', setTagState); $scope.$watch('options.reverse', setTagState); - $scope.$watch('options.tagFilter', setTagState); + $scope.$watch('options.filter', setTagState); $scope.$watch('options.page', function(page) { if (page != null && $scope.checkedTags) { diff --git a/static/js/directives/ui/create-entity-dialog.js b/static/js/directives/ui/create-entity-dialog.js new file mode 100644 index 000000000..781e07747 --- /dev/null +++ b/static/js/directives/ui/create-entity-dialog.js @@ -0,0 +1,202 @@ +/** + * An element which displays a create entity dialog. + */ +angular.module('quay').directive('createEntityDialog', function () { + var directiveDefinitionObject = { + priority: 0, + templateUrl: '/static/directives/create-entity-dialog.html', + replace: false, + transclude: true, + restrict: 'C', + scope: { + 'info': '=info', + + 'entityKind': '@entityKind', + 'entityTitle': '@entityTitle', + 'entityIcon': '@entityIcon', + 'entityNameRegex': '@entityNameRegex', + + 'entityCreateRequested': '&entityCreateRequested', + 'entityCreateCompleted': '&entityCreateCompleted' + }, + + controller: function($scope, $element, ApiService, UIService, TableService, RolesService, UserService) { + $scope.TableService = TableService; + + $scope.options = { + 'predicate': 'last_modified_datetime', + 'reverse': false, + 'filter': '' + }; + + var handleRepoCheckChange = function() { + $scope.repositories.forEach(function(repo) { + if ($scope.checkedRepos.isChecked(repo)) { + if (repo['permission'] == 'none') { + repo['permission'] = 'read'; + } + } else { + repo['permission'] = 'none'; + } + }); + }; + + $scope.$on('$destroy', function() { + if ($scope.inBody) { + document.body.removeChild($element[0]); + } + }); + + $scope.setRole = function(role, repo) { + repo['permission'] = role; + + if (role == 'none') { + $scope.checkedRepos.uncheckItem(repo); + } else { + $scope.checkedRepos.checkItem(repo); + } + }; + + $scope.hide = function() { + $element.find('.modal').modal('hide'); + if ($scope.entity) { + $scope.entityCreateCompleted({'entity': $scope.entity}); + $scope.entity = null; + } + }; + + $scope.show = function() { + $scope.entityName = null; + $scope.entity = null; + $scope.creating = false; + $scope.view = 'enterName'; + $scope.enterNameForm.$setPristine(true); + + // Move the dialog to the body to prevent it from nesting if called + // from within another dialog. + $element.find('.modal').modal({}); + $scope.inBody = true; + document.body.appendChild($element[0]); + }; + + var setRepoState = function() { + if (!$scope.repositories) { + return; + } + + $scope.orderedRepositories = TableService.buildOrderedItems($scope.repositories, $scope.options, + ['name', 'permission'], + ['last_modified_datetime']); + }; + + var entityCreateCallback = function(entity) { + if (!entity || $scope.info.skip_permissions) { + $scope.entity = entity; + $scope.hide(); + return; + } + + // Load the repositories under the entity's namespace. + var params = { + 'namespace': $scope.info.namespace, + 'last_modified': true + }; + + ApiService.listRepos(null, params).then(function(resp) { + $scope.view = 'addperms'; + $scope.entity = entity; + + var repos = []; + resp['repositories'].forEach(function(repo) { + repos.push({ + 'namespace': repo.namespace, + 'name': repo.name, + 'last_modified': repo.last_modified, + 'last_modified_datetime': TableService.getReversedTimestamp(repo.last_modified), + 'permission': 'none' + }); + }); + + if (repos.length == 0) { + $scope.hide(); + return; + } + + $scope.repositories = repos; + $scope.checkedRepos = UIService.createCheckStateController($scope.repositories, 'name'); + $scope.checkedRepos.listen(handleRepoCheckChange); + + if ($scope.info.repository) { + repos.forEach(function(repo) { + if (repo['namespace'] == $scope.info.repository.namespace && + repo['name'] == $scope.info.repository.name) { + $scope.checkedRepos.checkItem(repo); + $scope.options.filter = $scope.info.repository.name; + } + }); + } + + setRepoState(); + }, ApiService.errorDisplay('Could not load repositories')); + }; + + $scope.addPermissions = function() { + $scope.view = 'addingperms'; + + var repos = $scope.checkedRepos.checked; + var counter = 0; + + var addPerm = function() { + if (counter >= repos.length) { + $scope.hide(); + return; + } + + var repo = repos[counter]; + RolesService.setRepositoryRole(repo, repo.permission, $scope.entityKind, $scope.entity.name, + function(status) { + if (status) { + counter++; + addPerm(); + } else { + $scope.hide(); + } + }); + }; + + addPerm(); + }; + + $scope.createEntity = function() { + $scope.view = 'creating'; + $scope.entityCreateRequested({ + 'name': $scope.entityName, + 'callback': entityCreateCallback + }); + }; + + $scope.$watch('options.predicate', setRepoState); + $scope.$watch('options.reverse', setRepoState); + $scope.$watch('options.filter', setRepoState); + + $scope.$watch('entityNameRegex', function(r) { + if (r) { + $scope.entityNameRegexObj = new RegExp(r); + } + }); + + $scope.$watch('info', function(info) { + if (!info || !info.namespace) { + $scope.hide(); + return; + } + + $scope.namespace = UserService.getNamespace(info.namespace); + if ($scope.namespace) { + $scope.show(); + } + }); + } + }; + return directiveDefinitionObject; +}); \ No newline at end of file diff --git a/static/js/directives/ui/create-robot-dialog.js b/static/js/directives/ui/create-robot-dialog.js new file mode 100644 index 000000000..59beecc90 --- /dev/null +++ b/static/js/directives/ui/create-robot-dialog.js @@ -0,0 +1,43 @@ +/** + * An element which displays a dialog for creating a robot account. + */ +angular.module('quay').directive('createRobotDialog', function () { + var directiveDefinitionObject = { + priority: 0, + templateUrl: '/static/directives/create-robot-dialog.html', + replace: false, + transclude: true, + restrict: 'C', + scope: { + 'info': '=info', + 'robotCreated': '&robotCreated' + }, + controller: function($scope, $element, ApiService, UserService) { + $scope.ROBOT_PATTERN = ROBOT_PATTERN; + + $scope.robotFinished = function(robot) { + $scope.robotCreated({'robot': robot}); + }; + + $scope.createRobot = function(name, callback) { + var organization = $scope.info.namespace; + if (!UserService.isOrganization(organization)) { + organization = null; + } + + var params = { + 'robot_shortname': name + }; + + var errorDisplay = ApiService.errorDisplay('Cannot create robot account', function() { + callback(null); + }); + + ApiService.createRobot(organization, null, params).then(function(resp) { + callback(resp); + }, errorDisplay); + }; + } + }; + return directiveDefinitionObject; +}); \ No newline at end of file diff --git a/static/js/directives/ui/create-team-dialog.js b/static/js/directives/ui/create-team-dialog.js new file mode 100644 index 000000000..4d63e57b8 --- /dev/null +++ b/static/js/directives/ui/create-team-dialog.js @@ -0,0 +1,44 @@ +/** + * An element which displays a dialog for creating a team. + */ +angular.module('quay').directive('createTeamDialog', function () { + var directiveDefinitionObject = { + priority: 0, + templateUrl: '/static/directives/create-team-dialog.html', + replace: false, + transclude: true, + restrict: 'C', + scope: { + 'info': '=info', + 'teamCreated': '&teamCreated' + }, + controller: function($scope, $element, ApiService, UserService) { + $scope.TEAM_PATTERN = TEAM_PATTERN; + + $scope.teamFinished = function(team) { + $scope.teamCreated({'team': team}); + }; + + $scope.createTeam = function(name, callback) { + var data = { + 'name': name, + 'role': 'member' + }; + + var params = { + 'orgname': $scope.info.namespace, + 'teamname': name + }; + + var errorDisplay = ApiService.errorDisplay('Cannot create team', function() { + callback(null); + }); + + ApiService.updateOrganizationTeam(data, params).then(function(resp) { + callback(resp); + }, errorDisplay); + }; + } + }; + return directiveDefinitionObject; +}); \ No newline at end of file diff --git a/static/js/directives/ui/credentials-dialog.js b/static/js/directives/ui/credentials-dialog.js index d20a4624c..52e1aec48 100644 --- a/static/js/directives/ui/credentials-dialog.js +++ b/static/js/directives/ui/credentials-dialog.js @@ -22,6 +22,12 @@ angular.module('quay').directive('credentialsDialog', function () { $scope.rkt = {}; $scope.docker = {}; + $scope.$on('$destroy', function() { + if ($scope.inBody) { + document.body.removeChild($element[0]); + } + }); + // Generate a unique ID for the dialog. if (!$rootScope.credentialsDialogCounter) { $rootScope.credentialsDialogCounter = 0; @@ -36,6 +42,11 @@ angular.module('quay').directive('credentialsDialog', function () { $scope.show = function() { $element.find('.modal').modal({}); + + // Move the dialog to the body to prevent it from being affected + // by being placed inside other tables. + $scope.inBody = true; + document.body.appendChild($element[0]); }; $scope.$watch('credentials', function(credentials) { diff --git a/static/js/directives/ui/entity-search.js b/static/js/directives/ui/entity-search.js index 565103ca0..874908848 100644 --- a/static/js/directives/ui/entity-search.js +++ b/static/js/directives/ui/entity-search.js @@ -18,6 +18,8 @@ angular.module('quay').directive('entitySearch', function () { scope: { 'namespace': '=namespace', 'placeholder': '=placeholder', + 'forRepository': '=forRepository', + 'skipPermissions': '=skipPermissions', // Default: ['user', 'team', 'robot'] 'allowedEntities': '=allowedEntities', @@ -41,7 +43,7 @@ angular.module('quay').directive('entitySearch', function () { // True if the menu should pull right. 'pullRight': '@pullRight' }, - controller: function($rootScope, $scope, $element, Restangular, UserService, ApiService, UtilService, Config, CreateService) { + controller: function($rootScope, $scope, $element, Restangular, UserService, ApiService, UtilService, Config) { $scope.lazyLoading = true; $scope.teams = null; @@ -55,6 +57,8 @@ angular.module('quay').directive('entitySearch', function () { $scope.includeOrgs = false; $scope.currentEntityInternal = $scope.currentEntity; + $scope.createRobotInfo = null; + $scope.createTeamInfo = null; $scope.Config = Config; @@ -91,18 +95,30 @@ angular.module('quay').directive('entitySearch', function () { } }; - $scope.createTeam = function() { - CreateService.askCreateTeam($scope.namespace, function(created) { - $scope.setEntity(created.name, 'team', false, created.avatar); - $scope.teams[created.name] = created; - }); + $scope.askCreateTeam = function() { + $scope.createTeamInfo = { + 'namespace': $scope.namespace, + 'repository': $scope.forRepository, + 'skip_permissions': $scope.skipPermissions + }; }; - $scope.createRobot = function() { - CreateService.askCreateRobot($scope.namespace, function(created) { - $scope.setEntity(created.name, 'user', true, created.avatar); - $scope.robots.push(created); - }); + $scope.askCreateRobot = function() { + $scope.createRobotInfo = { + 'namespace': $scope.namespace, + 'repository': $scope.forRepository, + 'skip_permissions': $scope.skipPermissions + }; + }; + + $scope.handleTeamCreated = function(created) { + $scope.setEntity(created.name, 'team', false, created.avatar); + $scope.teams[created.name] = created; + }; + + $scope.handleRobotCreated = function(created) { + $scope.setEntity(created.name, 'user', true, created.avatar); + $scope.robots.push(created); }; $scope.setEntity = function(name, kind, is_robot, avatar) { diff --git a/static/js/directives/ui/header-bar.js b/static/js/directives/ui/header-bar.js index 3617fdcdd..8b88d886f 100644 --- a/static/js/directives/ui/header-bar.js +++ b/static/js/directives/ui/header-bar.js @@ -13,7 +13,7 @@ angular.module('quay').directive('headerBar', function () { scope: { }, controller: function($rootScope, $scope, $element, $location, $timeout, hotkeys, UserService, - PlanService, ApiService, NotificationService, Config, CreateService, Features, + PlanService, ApiService, NotificationService, Config, Features, DocumentationService, ExternalLoginService) { $scope.externalSigninUrl = ExternalLoginService.getSingleSigninUrl(); @@ -268,24 +268,36 @@ angular.module('quay').directive('headerBar', function () { $location.url('/repository/' + context.repository.namespace + '/' + context.repository.name + '/build/' + build.id); }; - $scope.createRobot = function(context) { + $scope.handleRobotCreated = function(created, context) { var namespace = $scope.getNamespace(context); - CreateService.askCreateRobot(namespace, function(created) { - if (UserService.isOrganization(namespace)) { - $location.url('/organization/' + namespace + '?tab=robots&showRobot=' + created.name); - } else { - $location.url('/user/' + namespace + '?tab=robots&showRobot=' + created.name); - } - }); + if (UserService.isOrganization(namespace)) { + $location.url('/organization/' + namespace + '?tab=robots&showRobot=' + created.name); + } else { + $location.url('/user/' + namespace + '?tab=robots&showRobot=' + created.name); + } }; - $scope.createTeam = function(context) { + $scope.handleTeamCreated = function(created, context) { + var namespace = $scope.getNamespace(context); + $location.url('/organization/' + namespace + '/teams/' + created.name); + }; + + $scope.askCreateRobot = function(context) { var namespace = $scope.getNamespace(context); if (!namespace || !UserService.isNamespaceAdmin(namespace)) { return; } - CreateService.askCreateTeam(namespace, function(created) { - $location.url('/organization/' + namespace + '/teams/' + created.name); - }); + $scope.createRobotInfo = { + 'namespace': namespace + }; + }; + + $scope.askCreateTeam = function(context) { + var namespace = $scope.getNamespace(context); + if (!namespace || !UserService.isNamespaceAdmin(namespace)) { return; } + + $scope.createTeamInfo = { + 'namespace': namespace + }; }; } }; diff --git a/static/js/directives/ui/repo-list-table.js b/static/js/directives/ui/repo-list-table.js index 7a5e98e53..4b9b59fdd 100644 --- a/static/js/directives/ui/repo-list-table.js +++ b/static/js/directives/ui/repo-list-table.js @@ -13,30 +13,22 @@ angular.module('quay').directive('repoListTable', function () { 'namespaces': '=namespaces', 'starToggled': '&starToggled' }, - controller: function($scope, $element, $filter) { - var orderBy = $filter('orderBy'); - + controller: function($scope, $element, $filter, TableService) { $scope.repositories = null; $scope.orderedRepositories = []; $scope.maxPopularity = 0; $scope.options = { 'predicate': 'popularity', - 'reverse': true + 'reverse': false, + 'filter': null }; var buildOrderedRepositories = function() { if (!$scope.repositories) { return; } - var modifier = $scope.options.reverse ? '-' : ''; - var fields = [modifier + $scope.options.predicate]; - // Secondary ordering by full name. - if ($scope.options.predicate != 'full_name') { - fields.push('full_name'); - } - - var ordered = orderBy($scope.repositories, fields, false); - $scope.orderedRepositories = ordered; + $scope.orderedRepositories = TableService.buildOrderedItems($scope.repositories, $scope.options, + [], ['last_modified_datetime', 'popularity']) }; $scope.tablePredicateClass = function(name, predicate, reverse) { @@ -92,7 +84,7 @@ angular.module('quay').directive('repoListTable', function () { (resource.value || []).forEach(function(repository) { var repositoryInfo = $.extend(repository, { 'full_name': repository.namespace + '/' + repository.name, - 'last_modified_datetime': (new Date(repository.last_modified || 0)).valueOf() * (-1) + 'last_modified_datetime': TableService.getReversedTimestamp(repository.last_modified), }); $scope.repositories.push(repositoryInfo); diff --git a/static/js/directives/ui/repository-permissions-table.js b/static/js/directives/ui/repository-permissions-table.js index a4e98b361..a832ac207 100644 --- a/static/js/directives/ui/repository-permissions-table.js +++ b/static/js/directives/ui/repository-permissions-table.js @@ -28,7 +28,7 @@ angular.module('quay').directive('repositoryPermissionsTable', function () { 'repository': '=repository', 'isEnabled': '=isEnabled' }, - controller: function($scope, $element, ApiService, Restangular, UtilService, RolesService) { + controller: function($scope, $element, ApiService, RolesService) { $scope.permissionResources = {'team': {}, 'user': {}}; $scope.permissionCache = {}; $scope.permissions = {}; @@ -69,13 +69,6 @@ angular.module('quay').directive('repositoryPermissionsTable', function () { loadAllPermissions(); - var getPermissionEndpoint = function(entityName, kind) { - var namespace = $scope.repository.namespace; - var name = $scope.repository.name; - var url = UtilService.getRestUrl('repository', namespace, name, 'permissions', kind, entityName); - return Restangular.one(url); - }; - $scope.buildEntityForPermission = function(permission, kind) { var key = permission.name + ':' + kind; if ($scope.permissionCache[key]) { @@ -146,51 +139,36 @@ angular.module('quay').directive('repositoryPermissionsTable', function () { }; $scope.deleteRole = function(entityName, kind) { - var errorHandler = ApiService.errorDisplay('Cannot change permission', function(resp) { - if (resp.status == 409) { - return 'Cannot change permission as you do not have the authority'; + RolesService.deleteRepositoryRole($scope.repository, kind, entityName, function(status) { + if (status) { + delete $scope.permissions[kind][entityName]; } }); - - var endpoint = getPermissionEndpoint(entityName, kind); - endpoint.customDELETE().then(function() { - delete $scope.permissions[kind][entityName]; - }, errorHandler); }; $scope.addRole = function(entityName, role, kind, opt_callback) { - var permission = { - 'role': role, - }; - - var errorHandler = ApiService.errorDisplay('Cannot change permission', function() { - opt_callback && opt_callback(false); + RolesService.setRepositoryRole($scope.repository, role, kind, entityName, function(status, result) { $scope.addPermissionInfo = { 'role': readRole }; + + if (status) { + $scope.permissions[kind][entityName] = result; + } + + opt_callback && opt_callback(status); }); - - var endpoint = getPermissionEndpoint(entityName, kind); - endpoint.customPUT(permission).then(function(result) { - $scope.permissions[kind][entityName] = result; - $scope.addPermissionInfo = { - 'role': readRole - }; - opt_callback && opt_callback(true) - }, errorHandler); }; $scope.setRole = function(role, entityName, kind) { - var errorDisplay = ApiService.errorDisplay(function(resp) { - $scope.permissions[kind][entityName] = {'role': currentRole}; + var currentRole = $scope.permissions[kind][entityName].role; + RolesService.setRepositoryRole($scope.repository, role, kind, entityName, function(status) { + if (status) { + $scope.permissions[kind][entityName]['role'] = role; + } else { + $scope.permissions[kind][entityName]['role'] = currentRole; + } }); - - var permission = $scope.permissions[kind][entityName]; - var currentRole = permission.role; - permission.role = role; - - var endpoint = getPermissionEndpoint(entityName, kind); - endpoint.customPUT(permission).then(function() {}, errorDisplay); }; } }; diff --git a/static/js/directives/ui/robots-manager.js b/static/js/directives/ui/robots-manager.js index 0f1e9ded1..d34833f69 100644 --- a/static/js/directives/ui/robots-manager.js +++ b/static/js/directives/ui/robots-manager.js @@ -13,15 +13,14 @@ angular.module('quay').directive('robotsManager', function () { 'user': '=user', 'isEnabled': '=isEnabled' }, - controller: function($scope, $element, ApiService, $routeParams, $location, CreateService, - Config, $rootScope) { - $scope.ROBOT_PATTERN = ROBOT_PATTERN; - + controller: function($scope, $element, ApiService, $routeParams, $location, Config, $rootScope) { $scope.robots = null; $scope.loading = false; $scope.Config = Config; $scope.feedback = null; + $scope.robotDisplayInfo = null; + $scope.createRobotInfo = null; // Listen for route changes and update the tabs accordingly. var locationListener = $rootScope.$on('$routeUpdate', function(){ @@ -96,22 +95,10 @@ angular.module('quay').directive('robotsManager', function () { return name.substr(0, plus); }; - $scope.createRobot = function(name) { - if (!name) { return; } - - CreateService.createRobotAccount(ApiService, !!$scope.organization, $scope.organization ? $scope.organization.name : '', name, - function(created) { - created.teams = []; - created.repositories = []; - $scope.robots.push(created); - $scope.feedback = { - 'kind': 'success', - 'message': 'Robot account {robot} was created', - 'data': { - 'robot': name - } - }; - }); + $scope.askCreateRobot = function() { + $scope.createRobotInfo = { + 'namespace': $scope.organization ? $scope.organization.name : $scope.user.username + }; }; $scope.deleteRobot = function(info) { @@ -131,7 +118,6 @@ angular.module('quay').directive('robotsManager', function () { }, ApiService.errorDisplay('Cannot delete robot account')); }; - $scope.askDeleteRobot = function(info) { bootbox.confirm('Are you sure you want to delete robot ' + info.name + '?', function(resp) { if (resp) { @@ -140,6 +126,10 @@ angular.module('quay').directive('robotsManager', function () { }); }; + $scope.robotCreated = function() { + update(); + }; + var update = function() { if (!$scope.user && !$scope.organization) { return; } if ($scope.loading || !$scope.isEnabled) { return; } diff --git a/static/js/directives/ui/teams-manager.js b/static/js/directives/ui/teams-manager.js index 9e67ca853..80a09efe1 100644 --- a/static/js/directives/ui/teams-manager.js +++ b/static/js/directives/ui/teams-manager.js @@ -12,8 +12,7 @@ angular.module('quay').directive('teamsManager', function () { 'organization': '=organization', 'isEnabled': '=isEnabled' }, - controller: function($scope, $element, ApiService, CreateService, $timeout, UserService) { - $scope.TEAM_PATTERN = TEAM_PATTERN; + controller: function($scope, $element, ApiService, $timeout, UserService) { $scope.teamRoles = [ { 'id': 'member', 'title': 'Member', 'kind': 'default' }, { 'id': 'creator', 'title': 'Creator', 'kind': 'success' }, @@ -27,6 +26,7 @@ angular.module('quay').directive('teamsManager', function () { $scope.showingMembers = false; $scope.fullMemberList = null; $scope.feedback = null; + $scope.createTeamInfo = null; var loadTeamMembers = function() { if (!$scope.organization || !$scope.isEnabled) { return; } @@ -107,35 +107,27 @@ angular.module('quay').directive('teamsManager', function () { }, errorHandler); }; - $scope.createTeam = function(teamname) { - if (!teamname) { - return; - } + $scope.askCreateTeam = function(teamname) { + $scope.createTeamInfo = { + 'namespace': $scope.organization.name + }; + }; - if ($scope.organization.teams[teamname]) { - $('#team-' + teamname).removeClass('highlight'); - setTimeout(function() { - $('#team-' + teamname).addClass('highlight'); - }, 10); - return; - } + $scope.handleTeamCreated = function(created) { + var teamname = created.name; + $scope.organization.teams[teamname] = created; + $scope.members[teamname] = {}; + $scope.members[teamname].members = []; + $scope.organization.ordered_teams.push(teamname); + $scope.orderedTeams.push(created); - var orgname = $scope.organization.name; - CreateService.createOrganizationTeam(ApiService, orgname, teamname, function(created) { - $scope.organization.teams[teamname] = created; - $scope.members[teamname] = {}; - $scope.members[teamname].members = []; - $scope.organization.ordered_teams.push(teamname); - $scope.orderedTeams.push(created); - - $scope.feedback = { - 'kind': 'success', - 'message': 'Team {team} created', - 'data': { - 'team': teamname - } - }; - }); + $scope.feedback = { + 'kind': 'success', + 'message': 'Team {team} created', + 'data': { + 'team': teamname + } + }; }; $scope.askDeleteTeam = function(teamname) { diff --git a/static/js/pages/user-view.js b/static/js/pages/user-view.js index 92efb21d6..30cc4d719 100644 --- a/static/js/pages/user-view.js +++ b/static/js/pages/user-view.js @@ -41,6 +41,7 @@ var loadUser = function() { $scope.userResource = ApiService.getUserInformationAsResource({'username': username}).get(function(user) { $scope.context.viewuser = user; + $scope.viewuser = user; // Load the repositories. $timeout(function() { diff --git a/static/js/services/create-service.js b/static/js/services/create-service.js deleted file mode 100644 index d52d3cf92..000000000 --- a/static/js/services/create-service.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Service which exposes various methods for creating entities on the backend. - */ -angular.module('quay').factory('CreateService', ['ApiService', 'UserService', function(ApiService, UserService) { - var createService = {}; - - createService.createRobotAccount = function(ApiService, is_org, orgname, name, callback) { - ApiService.createRobot(is_org ? orgname : null, null, {'robot_shortname': name}) - .then(callback, ApiService.errorDisplay('Cannot create robot account')); - }; - - createService.createOrganizationTeam = function(ApiService, orgname, teamname, callback) { - var data = { - 'name': teamname, - 'role': 'member' - }; - - var params = { - 'orgname': orgname, - 'teamname': teamname - }; - - ApiService.updateOrganizationTeam(data, params) - .then(callback, ApiService.errorDisplay('Cannot create team')); - }; - - createService.askCreateRobot = function(namespace, callback) { - if (!namespace || !UserService.isNamespaceAdmin(namespace)) { return; } - - var isorg = UserService.isOrganization(namespace); - 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; - } - - createService.createRobotAccount(ApiService, isorg, namespace, robotname, callback); - }); - }; - - createService.askCreateTeam = function(namespace, callback) { - if (!namespace || !UserService.isNamespaceAdmin(namespace)) { return; } - - 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; - } - - createService.createOrganizationTeam(ApiService, namespace, teamname, callback); - }); - }; - - return createService; -}]); diff --git a/static/js/services/roles-service.js b/static/js/services/roles-service.js index bf83e0f77..2f9854d51 100644 --- a/static/js/services/roles-service.js +++ b/static/js/services/roles-service.js @@ -1,20 +1,57 @@ /** * Service which defines the various role groups. */ -angular.module('quay').factory('RolesService', [function() { +angular.module('quay').factory('RolesService', ['UtilService', 'Restangular', 'ApiService', function(UtilService, Restangular, ApiService) { var roleService = {}; - roleService.repoRoles = [ + roleService.repoRolesOrNone = [ + { 'id': 'none', 'title': 'None', 'kind': 'default', 'description': 'No permissions on the repository' }, + { 'id': 'read', 'title': 'Read', 'kind': 'success', 'description': 'Can view and pull from the repository' }, { 'id': 'write', 'title': 'Write', 'kind': 'success', 'description': 'Can view, pull and push to the repository' }, { 'id': 'admin', 'title': 'Admin', 'kind': 'primary', 'description': 'Full admin access, pull and push on the repository' } ]; + roleService.repoRoles = roleService.repoRolesOrNone.slice(1); + roleService.teamRoles = [ { 'id': 'member', 'title': 'Member', 'kind': 'default', 'description': 'Inherits all permissions of the team' }, { 'id': 'creator', 'title': 'Creator', 'kind': 'success', 'description': 'Member and can create new repositories' }, { 'id': 'admin', 'title': 'Admin', 'kind': 'primary', 'description': 'Full admin access to the organization' } ]; + var getPermissionEndpoint = function(repository, entityName, kind) { + var namespace = repository.namespace; + var name = repository.name; + var url = UtilService.getRestUrl('repository', namespace, name, 'permissions', kind, entityName); + return Restangular.one(url); + }; + + roleService.deleteRepositoryRole = function(repository, entityKind, entityName, callback) { + var errorDisplay = ApiService.errorDisplay('Cannot change permission', function(resp) { + callback(false); + }); + + var endpoint = getPermissionEndpoint(repository, entityName, kind); + endpoint.customDELETE().then(function() { + callback(true); + }, errorHandler); + }; + + roleService.setRepositoryRole = function(repository, role, entityKind, entityName, callback) { + var errorDisplay = ApiService.errorDisplay('Cannot change permission', function(resp) { + callback(false); + }); + + var permission = { + 'role': role + }; + + var endpoint = getPermissionEndpoint(repository, entityName, entityKind); + endpoint.customPUT(permission).then(function(resp) { + callback(true, resp); + }, errorDisplay); + }; + return roleService; }]); diff --git a/static/js/services/table-service.js b/static/js/services/table-service.js index 08fbf9d8d..37a2a4e8e 100644 --- a/static/js/services/table-service.js +++ b/static/js/services/table-service.js @@ -22,6 +22,14 @@ angular.module('quay').factory('TableService', ['AngularViewArray', function(Ang options.predicate = predicate; }; + tableService.getReversedTimestamp = function(datetime) { + if (!datetime) { + return -Number.MAX_VALUE; + } + + return (new Date(datetime)).valueOf() * (-1); + }; + tableService.buildOrderedItems = function(items, options, filterFields, numericFields, opt_extrafilter) { var orderedItems = AngularViewArray.create(); diff --git a/static/js/services/ui-service.js b/static/js/services/ui-service.js index 5bf0726dc..126a1c216 100644 --- a/static/js/services/ui-service.js +++ b/static/js/services/ui-service.js @@ -82,7 +82,7 @@ angular.module('quay').factory('UIService', ['$timeout', '$rootScope', '$locatio CheckStateController.prototype.rebuildCheckedList_ = function() { var that = this; this.checked = []; - this.items.forEach(function(item) { + this.allItems_.forEach(function(item) { if (that.allCheckedMap_[item[that.itemKey_]]) { that.checked.push(item); } diff --git a/static/js/services/user-service.js b/static/js/services/user-service.js index ca693b74a..85612b188 100644 --- a/static/js/services/user-service.js +++ b/static/js/services/user-service.js @@ -134,6 +134,19 @@ function(ApiService, CookieService, $rootScope, Config) { return !!org; }; + userService.getNamespace = function(namespace) { + var org = userService.getOrganization(namespace); + if (org) { + return org; + } + + if (namespace == userResponse.username) { + return userResponse; + } + + return null; + }; + userService.currentUser = function() { return userResponse; };