From 580814f7120e6fcf94cc4e3f066106fd8da62a2f Mon Sep 17 00:00:00 2001 From: Joseph Schorr <josephschorr@users.noreply.github.com> Date: Thu, 12 May 2016 13:42:11 -0400 Subject: [PATCH 1/4] Move the credentials dialog to always be under the body Fixes a CSS issue with display:table-cell and nesting --- static/js/directives/ui/credentials-dialog.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/static/js/directives/ui/credentials-dialog.js b/static/js/directives/ui/credentials-dialog.js index c574a478e..3f83606b1 100644 --- a/static/js/directives/ui/credentials-dialog.js +++ b/static/js/directives/ui/credentials-dialog.js @@ -22,6 +22,10 @@ angular.module('quay').directive('credentialsDialog', function () { $scope.rkt = {}; $scope.docker = {}; + $scope.$on('$destroy', function() { + document.body.removeChild($element[0]); + }); + // Generate a unique ID for the dialog. if (!$rootScope.credentialsDialogCounter) { $rootScope.credentialsDialogCounter = 0; @@ -36,6 +40,10 @@ 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. + document.body.appendChild($element[0]); }; $scope.$watch('credentials', function(credentials) { From 2d4916c6eccf8b4b5b0e1323619c5877cdb48c3a Mon Sep 17 00:00:00 2001 From: Joseph Schorr <josephschorr@users.noreply.github.com> Date: Thu, 12 May 2016 13:42:18 -0400 Subject: [PATCH 2/4] Make role group thinner --- static/css/directives/ui/role-group.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/static/css/directives/ui/role-group.css b/static/css/directives/ui/role-group.css index 119a0db16..829209d33 100644 --- a/static/css/directives/ui/role-group.css +++ b/static/css/directives/ui/role-group.css @@ -2,11 +2,14 @@ width: 90px; position: relative; text-align: left; + + padding: 4px; + padding-left: 10px; } .new-role-group .btn .caret { position: absolute; - top: 15px; + top: 13px; right: 7px; } From 2274d6ff84c0a5b12361d796a199988d64aee2ae Mon Sep 17 00:00:00 2001 From: Joseph Schorr <josephschorr@users.noreply.github.com> Date: Thu, 12 May 2016 16:54:05 -0400 Subject: [PATCH 3/4] Fix error --- static/js/directives/ui/credentials-dialog.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/static/js/directives/ui/credentials-dialog.js b/static/js/directives/ui/credentials-dialog.js index 3f83606b1..646197bac 100644 --- a/static/js/directives/ui/credentials-dialog.js +++ b/static/js/directives/ui/credentials-dialog.js @@ -23,7 +23,9 @@ angular.module('quay').directive('credentialsDialog', function () { $scope.docker = {}; $scope.$on('$destroy', function() { - document.body.removeChild($element[0]); + if ($scope.inBody) { + document.body.removeChild($element[0]); + } }); // Generate a unique ID for the dialog. @@ -43,6 +45,7 @@ angular.module('quay').directive('credentialsDialog', function () { // 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]); }; From 4a543be7a7be5ffd2d3ec141c9f56b19dd50b47a Mon Sep 17 00:00:00 2001 From: Joseph Schorr <josephschorr@users.noreply.github.com> Date: Thu, 12 May 2016 17:59:49 -0400 Subject: [PATCH 4/4] New create entity dialogs (team and robot) Fixes https://github.com/coreos-inc/design/issues/230 --- static/css/core-ui.css | 8 +- .../directives/ui/create-entity-dialog.css | 53 +++++ static/css/directives/ui/feedback-bar.css | 1 + static/css/directives/ui/role-group.css | 9 + static/directives/create-entity-dialog.html | 98 +++++++++ static/directives/create-robot-dialog.html | 9 + static/directives/create-team-dialog.html | 9 + static/directives/entity-search.html | 8 +- static/directives/fetch-tag-dialog.html | 1 + static/directives/header-bar.html | 12 +- static/directives/repo-list-table.html | 2 +- .../directives/repo-view/repo-panel-tags.html | 2 +- .../repository-permissions-table.html | 4 + static/directives/robots-manager.html | 10 +- static/directives/teams-manager.html | 20 +- .../directives/repo-view/repo-panel-tags.js | 22 +- .../js/directives/ui/create-entity-dialog.js | 202 ++++++++++++++++++ .../js/directives/ui/create-robot-dialog.js | 43 ++++ static/js/directives/ui/create-team-dialog.js | 44 ++++ static/js/directives/ui/entity-search.js | 38 +++- static/js/directives/ui/header-bar.js | 38 ++-- static/js/directives/ui/repo-list-table.js | 20 +- .../ui/repository-permissions-table.js | 58 ++--- static/js/directives/ui/robots-manager.js | 32 +-- static/js/directives/ui/teams-manager.js | 50 ++--- static/js/pages/user-view.js | 1 + static/js/services/create-service.js | 61 ------ static/js/services/roles-service.js | 41 +++- static/js/services/table-service.js | 8 + static/js/services/ui-service.js | 2 +- static/js/services/user-service.js | 13 ++ 31 files changed, 687 insertions(+), 232 deletions(-) create mode 100644 static/css/directives/ui/create-entity-dialog.css create mode 100644 static/directives/create-entity-dialog.html create mode 100644 static/directives/create-robot-dialog.html create mode 100644 static/directives/create-team-dialog.html create mode 100644 static/js/directives/ui/create-entity-dialog.js create mode 100644 static/js/directives/ui/create-robot-dialog.js create mode 100644 static/js/directives/ui/create-team-dialog.js delete mode 100644 static/js/services/create-service.js 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 829209d33..327a4665e 100644 --- a/static/css/directives/ui/role-group.css +++ b/static/css/directives/ui/role-group.css @@ -7,12 +7,21 @@ padding-left: 10px; } +.role-group.small .btn { + padding: 2px; + padding-left: 10px; +} + .new-role-group .btn .caret { position: absolute; 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 @@ +<div class="create-entity-dialog-element"> + <div class="modal fade co-dialog wider"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" ng-click="hide()" aria-hidden="true">×</button> + <h4 class="modal-title" ng-show="view == 'enterName' || view == 'creating'"> + <i class="fa {{ entityIcon }}"></i> + Create {{ entityTitle }} + </h4> + <h4 class="modal-title" ng-show="view == 'addperms' || view == 'addingperms'"> + Add permissions for <i class="fa {{ entityIcon }}"></i> {{ entity.name }} + </h4> + </div> <!-- /.model-header --> + <div class="modal-body" ng-show="view == 'creating' || view == 'addingperms'"> + <div class="cor-loader"></div> + </div> + <div class="modal-body co-modal-body-scrollable" ng-show="view == 'addperms'"> + <span class="co-filter-box"> + <span class="filter-message" ng-if="options.filter"> + Showing {{ orderedRepositories.entries.length }} of {{ repositories.length }} repositories + </span> + <input class="form-control" type="text" ng-model="options.filter" placeholder="Filter Repositories..."> + </span> + + <label> + Select repositories in + <span class="avatar" size="16" data="namespace.avatar"></span> + {{ info.namespace }}: + </label> + + <table class="co-table" style="margin-bottom: 210px;"> + <thead> + <td class="checkbox-col"></td> + <td ng-class="TableService.tablePredicateClass('name', options.predicate, options.reverse)"> + <a ng-click="TableService.orderBy('name', options)">Repository Name</a> + </td> + <td>Permission</td> + <td ng-class="TableService.tablePredicateClass('last_modified_datetime', options.predicate, options.reverse)"> + <a ng-click="TableService.orderBy('last_modified_datetime', options)">Last Updated</a> + </td> + </thead> + + <tr class="co-checkable-row" + ng-repeat="repo in orderedRepositories.visibleEntries" + ng-class="checkedRepos.isChecked(repo, checkedRepos.checked) ? 'checked' : ''" + bindonce> + <td> + <span class="cor-checkable-item" controller="checkedRepos" item="repo"></span> + </td> + <td> + <i class="fa fa-hdd-o"></i> + <span bo-text="repo.name"></span> + </td> + <td> + <span class="role-group small" current-role="repo.permission" + roles="repoRolesOrNone" + role-changed="setRole(role, repo)"></span> + </td> + <td> + <span ng-if="repo.last_modified"> + {{ repo.last_modified * 1000 | amCalendar }} + </span> + <span class="empty" ng-if="!repo.last_modified">(Empty Repository)</span> + </td> + </tr> + </table> + + <div class="empty" ng-if="!orderedRepositories.entries.length" + style="margin-top: 20px;"> + <div class="empty-primary-msg">No matching repositories found.</div> + <div class="empty-secondary-msg">Try expanding your filtering terms.</div> + </div> + </div> + <div class="modal-body" ng-show="view == 'enterName'"> + <form name="enterNameForm" ng-submit="createEntity()"> + <label>Provide a name for your new {{ entityTitle }}:</label> + <input type="text" class="form-control" ng-model="entityName" ng-pattern="entityNameRegexObj" required> + <div class="help-text"> + Choose a name to inform your teammates + about this {{ entityTitle }}. Must match {{ entityNameRegex }}. + </div> + </form> + </div> <!-- /.modal-body --> + <div class="modal-footer" ng-show="view == 'addperms'"> + <button type="button" class="btn btn-primary" ng-click="addPermissions()" + ng-show="checkedRepos.checked.length">Add permissions</button> + <button type="button" class="btn btn-default" ng-click="hide()">Close</button> + </div> <!-- /.footer-body --> + <div class="modal-footer" ng-show="view == 'enterName'"> + <button type="button" class="btn btn-primary" ng-click="createEntity()" + ng-disabled="enterNameForm.$invalid">Create {{ entityTitle }}</button> + <button type="button" class="btn btn-default" ng-click="hide()">Cancel</button> + </div> <!-- /.footer-body --> + </div> + </div> + </div> +</div> \ 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 @@ +<div class="create-robot-dialog-element"> + <div ng-if="info"> + <div class="create-entity-dialog" info="info" entity-title="robot account" + entity-kind="user" + entity-icon="ci-robot" entity-name-regex="{{ ROBOT_PATTERN }}" + entity-create-requested="createRobot(name, callback)" + entity-create-completed="robotFinished(entity)"></div> + </div> +</div> \ 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 @@ +<div class="create-team-dialog-element"> + <div ng-if="info"> + <div class="create-entity-dialog" info="info" entity-title="team" + entity-kind="team" + entity-icon="fa-group" entity-name-regex="{{ TEAM_PATTERN }}" + entity-create-requested="createTeam(name, callback)" + entity-create-completed="teamFinished(entity)"></div> + </div> +</div> \ 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 @@ </li> <li role="presentation" ng-show="includeTeams && isOrganization && !lazyLoading && isAdmin"> - <a role="menuitem" class="new-action" tabindex="-1" ng-click="createTeam()"> + <a role="menuitem" class="new-action" tabindex="-1" ng-click="askCreateTeam()"> <i class="fa fa-group"></i> Create team </a> </li> <li role="presentation" ng-show="includeRobots && !lazyLoading && isAdmin"> - <a role="menuitem" class="new-action" tabindex="-1" ng-click="createRobot()"> + <a role="menuitem" class="new-action" tabindex="-1" ng-click="askCreateRobot()"> <i class="fa ci-robot"></i> Create robot account </a> @@ -70,4 +70,8 @@ </li> </ul> </div> + <div class="create-team-dialog" info="createTeamInfo" + team-created="handleTeamCreated(team)"></div> + <div class="create-robot-dialog" info="createRobotInfo" + robot-created="handleRobotCreated(robot)"></div> </span> 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 @@ <td class="first-col">Pull Credentials:</td> <td> <div class="entity-search" namespace="repository.namespace" + for-repository="repository" placeholder="'Choose Pull Credentials'" allowed-entities="['robot']" clear-value="clearCounter" diff --git a/static/directives/header-bar.html b/static/directives/header-bar.html index 1346bdbe9..74fe68c93 100644 --- a/static/directives/header-bar.html +++ b/static/directives/header-bar.html @@ -85,12 +85,12 @@ Namespace {{ getNamespace(currentPageContext) }} </li> <li ng-if="isOrganization(getNamespace(currentPageContext)) && canAdmin(getNamespace(currentPageContext))"> - <a ng-click="createTeam(currentPageContext)"> + <a ng-click="askCreateTeam(currentPageContext)"> <i class="fa fa-group"></i> New Team </a> </li> <li ng-if="canAdmin(getNamespace(currentPageContext))"> - <a ng-click="createRobot(currentPageContext)"> + <a ng-click="askCreateRobot(currentPageContext)"> <i class="fa ci-robot"></i> New Robot Account </a> </li> @@ -207,6 +207,14 @@ </ul> </div> + <div class="create-robot-dialog" info="createRobotInfo" + robot-created="handleRobotCreated(robot, currentPageContext)"> + </div> + + <div class="create-team-dialog" info="createTeamInfo" + team-created="handleTeamCreated(team, currentPageContext)"> + </div> + <div class="dockerfile-build-dialog" show-now="showBuildDialogCounter" repository="currentPageContext.repository" diff --git a/static/directives/repo-list-table.html b/static/directives/repo-list-table.html index c79215a72..98d19af40 100644 --- a/static/directives/repo-list-table.html +++ b/static/directives/repo-list-table.html @@ -30,7 +30,7 @@ </thead> <tbody> - <tr ng-repeat="repository in orderedRepositories"> + <tr ng-repeat="repository in orderedRepositories.entries"> <td class="repo-name-icon"> <span class="avatar" size="24" data="getAvatarData(repository.namespace)"></span> <a href="/repository/{{ repository.namespace }}/{{ repository.name }}"> diff --git a/static/directives/repo-view/repo-panel-tags.html b/static/directives/repo-view/repo-panel-tags.html index 4c5ddba14..387a2aff1 100644 --- a/static/directives/repo-view/repo-panel-tags.html +++ b/static/directives/repo-view/repo-panel-tags.html @@ -61,7 +61,7 @@ <span class="co-filter-box"> <span class="page-controls" total-count="tags.length" current-page="options.page" page-size="tagsPerPage"></span> - <input class="form-control" type="text" ng-model="options.tagFilter" placeholder="Filter Tags..."> + <input class="form-control" type="text" ng-model="options.filter" placeholder="Filter Tags..."> </span> </div> 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 @@ <tr class="add-row" ng-if-media="'(min-width: 768px)'"> <td id="add-entity-permission" class="admin-search"> <span class="entity-search" namespace="repository.namespace" + for-repository="repository" + skip-permissions="true" placeholder="'Select a ' + (repository.is_organization ? 'team or ' : '') + 'user...'" current-entity="addPermissionInfo.entity"></span> </td> @@ -125,6 +127,8 @@ <!-- Mobile add permissions --> <div class="mobile-add-row" ng-if-media="'(max-width: 767px)'"> <span class="entity-search" namespace="repository.namespace" + for-repository="repository" + skip-permissions="true" placeholder="'Select a ' + (repository.is_organization ? 'team or ' : '') + 'user...'" current-entity="addPermissionInfo.entity" pull-right="true"></span> 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 @@ <div ng-show="!loading"> <div class="manager-header" header-title="Robot Accounts"> - <span class="popup-input-button" pattern="ROBOT_PATTERN" - placeholder="'Robot Account Name'" - submitted="createRobot(value)" - ng-show="isEnabled"> - <i class="fa fa-plus"></i> Create Robot Account - </span> + <button class="btn btn-primary" ng-click="askCreateRobot()" ng-show="isEnabled"> + <i class="fa fa-plus" style="margin-right: 4px;"></i> Create Robot Account + </button> </div> <div class="section-description-header"> @@ -134,5 +131,6 @@ </table> </div> + <div class="create-robot-dialog" info="createRobotInfo" robot-created="robotCreated()"></div> <div class="robot-credentials-dialog" info="robotDisplayInfo"></div> </div> 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 @@ </button> </div> </div> - - <span class="popup-input-button visible-xs" ng-if="!showingMembers" - pattern="TEAM_PATTERN" placeholder="'Team Name'" - submitted="createTeam(value)" ng-show="organization.is_admin"> - <i class="fa fa-plus" style="margin-right: 6px;"></i> Create New Team - </span> </div> <!-- Teams List --> <div ng-show="!showingMembers"> <div class="row" style="margin-left: 0px; margin-right: 0px;"> - <span class="popup-input-button hidden-xs" - pattern="TEAM_PATTERN" placeholder="'Team Name'" - submitted="createTeam(value)" ng-show="organization.is_admin" - style="margin-bottom: 10px;"> - <i class="fa fa-plus" style="margin-right: 6px;"></i> Create New Team - </span> + <button class="btn btn-primary hidden-xs" + ng-show="organization.is_admin" + style="margin-bottom: 10px; float: right;" + ng-click="askCreateTeam()"> + <i class="fa fa-plus" style="margin-right: 4px;"></i> Create New Team + </button> </div> <div class="row hidden-xs"> @@ -155,6 +149,8 @@ </table> </div> + <div class="create-team-dialog" info="createTeamInfo" team-created="handleTeamCreated(team)"></div> + <!-- Remove member confirm --> <div class="cor-confirm-dialog" dialog-context="removeMemberInfo" diff --git a/static/js/directives/repo-view/repo-panel-tags.js b/static/js/directives/repo-view/repo-panel-tags.js index 6bbff1984..71568b11c 100644 --- a/static/js/directives/repo-view/repo-panel-tags.js +++ b/static/js/directives/repo-view/repo-panel-tags.js @@ -18,9 +18,7 @@ angular.module('quay').directive('repoPanelTags', function () { 'getImages': '&getImages' }, - controller: function($scope, $element, $filter, $location, ApiService, UIService, VulnerabilityService) { - var orderBy = $filter('orderBy'); - + controller: function($scope, $element, $filter, $location, ApiService, UIService, VulnerabilityService, TableService) { $scope.maxTrackCount = 5; $scope.checkedTags = UIService.createCheckStateController([], 'name'); @@ -45,32 +43,26 @@ angular.module('quay').directive('repoPanelTags', function () { var setTagState = function() { if (!$scope.repository || !$scope.selectedTags) { return; } - var tags = []; + // Build a list of all the tags, with extending information. var allTags = []; - - // Build a list of tags and filtered tags. for (var tag in $scope.repository.tags) { if (!$scope.repository.tags.hasOwnProperty(tag)) { continue; } var tagData = $scope.repository.tags[tag]; var tagInfo = $.extend(tagData, { 'name': tag, - 'last_modified_datetime': (new Date(tagData.last_modified || 0)).valueOf() * (-1) + 'last_modified_datetime': TableService.getReversedTimestamp(tagData.last_modified) }); allTags.push(tagInfo); - - if (!$scope.options.tagFilter || tag.indexOf($scope.options.tagFilter) >= 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/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 d6bce37cc..aa3d8f424 100644 --- a/static/js/services/user-service.js +++ b/static/js/services/user-service.js @@ -122,6 +122,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; };