Get robots UI working
This commit is contained in:
parent
43f2dd80a0
commit
12eb932da1
11 changed files with 309 additions and 143 deletions
|
@ -297,7 +297,7 @@ def get_matching_entities(prefix):
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.is_org_member is not None:
|
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
|
return user_json
|
||||||
|
|
||||||
|
@ -924,8 +924,8 @@ def wrap_role_view_user(role_json, user):
|
||||||
return role_json
|
return role_json
|
||||||
|
|
||||||
|
|
||||||
def wrap_role_view_org(role_json, org_member):
|
def wrap_role_view_org(role_json, user, org_members):
|
||||||
role_json['is_org_member'] = org_member
|
role_json['is_org_member'] = user.robot or user.username in org_members
|
||||||
return role_json
|
return role_json
|
||||||
|
|
||||||
|
|
||||||
|
@ -1034,22 +1034,30 @@ def list_repo_team_permissions(namespace, repository):
|
||||||
def list_repo_user_permissions(namespace, repository):
|
def list_repo_user_permissions(namespace, repository):
|
||||||
permission = AdministerRepositoryPermission(namespace, repository)
|
permission = AdministerRepositoryPermission(namespace, repository)
|
||||||
if permission.can():
|
if permission.can():
|
||||||
# Determine how to wrap the permissions
|
# Lookup the organization (if any).
|
||||||
role_view_func = role_view
|
org = None
|
||||||
try:
|
try:
|
||||||
model.get_organization(namespace) # Will raise an error if not org
|
org = 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
|
|
||||||
|
|
||||||
except model.InvalidOrganizationException:
|
except model.InvalidOrganizationException:
|
||||||
# This repository isn't under an org
|
# This repository isn't under an org
|
||||||
pass
|
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)
|
repo_perms = model.get_all_repo_users(namespace, repository)
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'permissions': {perm.user.username: role_view_func(perm)
|
'permissions': {perm.user.username: role_view_func(perm)
|
||||||
|
@ -1074,8 +1082,7 @@ def get_user_permissions(namespace, repository, username):
|
||||||
try:
|
try:
|
||||||
model.get_organization(namespace)
|
model.get_organization(namespace)
|
||||||
org_members = model.get_organization_member_set(namespace)
|
org_members = model.get_organization_member_set(namespace)
|
||||||
perm_view = wrap_role_view_org(perm_view,
|
perm_view = wrap_role_view_org(perm_view, perm.user, org_members)
|
||||||
perm.user.username in org_members)
|
|
||||||
except model.InvalidOrganizationException:
|
except model.InvalidOrganizationException:
|
||||||
# This repository is not part of an organization
|
# This repository is not part of an organization
|
||||||
pass
|
pass
|
||||||
|
@ -1119,8 +1126,7 @@ def change_user_permissions(namespace, repository, username):
|
||||||
try:
|
try:
|
||||||
model.get_organization(namespace)
|
model.get_organization(namespace)
|
||||||
org_members = model.get_organization_member_set(namespace)
|
org_members = model.get_organization_member_set(namespace)
|
||||||
perm_view = wrap_role_view_org(perm_view,
|
perm_view = wrap_role_view_org(perm_view, perm.user, org_members)
|
||||||
perm.user.username in org_members)
|
|
||||||
except model.InvalidOrganizationException:
|
except model.InvalidOrganizationException:
|
||||||
# This repository is not part of an organization
|
# This repository is not part of an organization
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -3,6 +3,11 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#input-box {
|
||||||
|
padding: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +21,45 @@ html, body {
|
||||||
border-bottom: 1px dashed #aaa;
|
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 {
|
.content-container {
|
||||||
padding-bottom: 70px;
|
padding-bottom: 70px;
|
||||||
}
|
}
|
||||||
|
@ -44,8 +88,9 @@ html, body {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.robots-manager-element .robot {
|
.robots-manager-element .robot a {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.robots-manager-element .robot .prefix {
|
.robots-manager-element .robot .prefix {
|
||||||
|
@ -970,21 +1015,6 @@ p.editable:hover i {
|
||||||
width: 300px;
|
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 {
|
.repo-image-view .id-container {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
@ -1027,7 +1057,7 @@ p.editable:hover i {
|
||||||
margin-top: 28px;
|
margin-top: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo #clipboardCopied {
|
#clipboardCopied {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
@ -1038,7 +1068,7 @@ p.editable:hover i {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo #clipboardCopied.animated {
|
#clipboardCopied.animated {
|
||||||
-webkit-animation: fadeOut 4s ease-in-out 0s 1 forwards;
|
-webkit-animation: fadeOut 4s ease-in-out 0s 1 forwards;
|
||||||
-moz-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;
|
-ms-animation: fadeOut 4s ease-in-out 0s 1 forwards;
|
||||||
|
@ -1125,21 +1155,17 @@ p.editable:hover i {
|
||||||
width: 300px;
|
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 {
|
.repo-admin .panel {
|
||||||
display: inline-block;
|
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 {
|
.repo-admin .user i.fa-user {
|
||||||
|
@ -1147,6 +1173,11 @@ p.editable:hover i {
|
||||||
margin-right: 7px;
|
margin-right: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.repo-admin .user i.fa-wrench {
|
||||||
|
margin-left: 1px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.repo-admin .team i.fa-group {
|
.repo-admin .team i.fa-group {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
@ -1278,18 +1309,6 @@ p.editable:hover i {
|
||||||
white-space: nowrap;
|
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 {
|
.navbar-nav > li > .user-dropdown {
|
||||||
padding-top: 9px;
|
padding-top: 9px;
|
||||||
padding-bottom: 9px;
|
padding-bottom: 9px;
|
||||||
|
|
30
static/directives/docker-auth-dialog.html
Normal file
30
static/directives/docker-auth-dialog.html
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<!-- Modal message dialog -->
|
||||||
|
<div class="modal fade" id="dockerauthmodal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h4 class="modal-title">
|
||||||
|
<span ng-transclude></span>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body token-dialog-body">
|
||||||
|
<div class="alert alert-info">The docker <u>username</u> is <b>{{ username }}</b> and the <u>password</u> is the token below. You may use any value for email.</div>
|
||||||
|
<div class="well well-sm">
|
||||||
|
<input id="token-view" class="token-view" type="text" value="{{ token }}" onClick="this.select();" readonly>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<span class="download-cfg" ng-show="isDownloadSupported()">
|
||||||
|
<i class="fa fa-download"></i>
|
||||||
|
<a href="javascript:void(0)" ng-click="downloadCfg(shownRobot)">Download .dockercfg file</a>
|
||||||
|
</span>
|
||||||
|
<div id="clipboardCopied" style="display: none">
|
||||||
|
Copied to clipboard
|
||||||
|
</div>
|
||||||
|
<button id="copyClipboard" type="button" class="btn btn-primary" data-clipboard-target="token-view">Copy to clipboard</button>
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div><!-- /.modal-content -->
|
||||||
|
</div><!-- /.modal-dialog -->
|
||||||
|
</div><!-- /.modal -->
|
5
static/directives/popup-input-button.html
Normal file
5
static/directives/popup-input-button.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<button class="btn btn-success" data-trigger="click" bs-popover="'static/directives/popup-input-dialog.html'"
|
||||||
|
data-placement="bottom" ng-click="popupShown()">
|
||||||
|
<span ng-transclude></span>
|
||||||
|
</button>
|
||||||
|
|
4
static/directives/popup-input-dialog.html
Normal file
4
static/directives/popup-input-dialog.html
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<form name="popupinput" ng-submit="inputSubmit(); hide()" novalidate>
|
||||||
|
<input id="input-box" type="text form-control" placeholder="{{ placeholder }}" ng-blur="hide()"
|
||||||
|
ng-pattern="getRegexp(pattern)" ng-model="inputValue" ng-trim="false" ng-minlength="2" required>
|
||||||
|
</form>
|
|
@ -1,10 +1,13 @@
|
||||||
<div class="robots-manager-element">
|
<div class="robots-manager-element">
|
||||||
<i class="fa fa-spinner fa-spin fa-3x" ng-show="loading"></i>
|
<i class="fa fa-spinner fa-spin fa-3x" ng-show="loading"></i>
|
||||||
<div class="alert alert-info">Robot accounts allow for delegating access in multiple repositories to tokens</div>
|
<div class="alert alert-info">Robot accounts allow for delegating access in multiple repositories to role-based accounts that you manage</div>
|
||||||
|
|
||||||
<div class="container" ng-show="!loading">
|
<div class="container" ng-show="!loading">
|
||||||
<div class="side-controls">
|
<div class="side-controls">
|
||||||
<button class="btn btn-success"><i class="fa fa-wrench"></i> Create Robot Account</button>
|
<span class="popup-input-button" pattern="'^[a-zA-Z][a-zA-Z0-9]+$'" placeholder="'Robot Account Name'"
|
||||||
|
submitted="createRobot(value)">
|
||||||
|
<i class="fa fa-wrench"></i> Create Robot Account
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="table">
|
<table class="table">
|
||||||
|
@ -16,7 +19,9 @@
|
||||||
<tr ng-repeat="robotInfo in robots">
|
<tr ng-repeat="robotInfo in robots">
|
||||||
<td class="robot">
|
<td class="robot">
|
||||||
<i class="fa fa-wrench"></i>
|
<i class="fa fa-wrench"></i>
|
||||||
|
<a ng-click="showRobot(robotInfo)">
|
||||||
<span class="prefix">{{ getPrefix(robotInfo.name) }}+</span>{{ getShortenedName(robotInfo.name) }}
|
<span class="prefix">{{ getPrefix(robotInfo.name) }}+</span>{{ getShortenedName(robotInfo.name) }}
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="delete-ui" tabindex="0">
|
<span class="delete-ui" tabindex="0">
|
||||||
|
@ -26,6 +31,10 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="docker-auth-dialog" username="shownRobot.name" token="shownRobot.token"
|
||||||
|
shown="!!shownRobot" counter="showRobotCounter">
|
||||||
|
<i class="fa fa-wrench"></i> {{ shownRobot.name }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
144
static/js/app.js
144
static/js/app.js
|
@ -550,7 +550,7 @@ quayApp.directive('plansTable', function () {
|
||||||
priority: 0,
|
priority: 0,
|
||||||
templateUrl: '/static/directives/plans-table.html',
|
templateUrl: '/static/directives/plans-table.html',
|
||||||
replace: false,
|
replace: false,
|
||||||
transclude: true,
|
transclude: false,
|
||||||
restrict: 'C',
|
restrict: 'C',
|
||||||
scope: {
|
scope: {
|
||||||
'plans': '=plans',
|
'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 () {
|
quayApp.directive('robotsManager', function () {
|
||||||
var directiveDefinitionObject = {
|
var directiveDefinitionObject = {
|
||||||
priority: 0,
|
priority: 0,
|
||||||
templateUrl: '/static/directives/robots-manager.html',
|
templateUrl: '/static/directives/robots-manager.html',
|
||||||
replace: false,
|
replace: false,
|
||||||
transclude: true,
|
transclude: false,
|
||||||
restrict: 'C',
|
restrict: 'C',
|
||||||
scope: {
|
scope: {
|
||||||
'organization': '=organization',
|
'organization': '=organization',
|
||||||
|
@ -581,6 +633,13 @@ quayApp.directive('robotsManager', function () {
|
||||||
controller: function($scope, $element, Restangular) {
|
controller: function($scope, $element, Restangular) {
|
||||||
$scope.robots = null;
|
$scope.robots = null;
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
|
$scope.shownRobot = null;
|
||||||
|
$scope.showRobotCounter = 0;
|
||||||
|
|
||||||
|
$scope.showRobot = function(info) {
|
||||||
|
$scope.shownRobot = info;
|
||||||
|
$scope.showRobotCounter++;
|
||||||
|
};
|
||||||
|
|
||||||
$scope.getShortenedName = function(name) {
|
$scope.getShortenedName = function(name) {
|
||||||
var plus = name.indexOf('+');
|
var plus = name.indexOf('+');
|
||||||
|
@ -592,6 +651,28 @@ quayApp.directive('robotsManager', function () {
|
||||||
return name.substr(0, plus);
|
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) {
|
$scope.deleteRobot = function(info) {
|
||||||
var shortName = $scope.getShortenedName(info.name);
|
var shortName = $scope.getShortenedName(info.name);
|
||||||
var url = $scope.organization ? getRestUrl('organization', $scope.organization.name, 'robots', shortName) :
|
var url = $scope.organization ? getRestUrl('organization', $scope.organization.name, 'robots', shortName) :
|
||||||
|
@ -601,12 +682,21 @@ quayApp.directive('robotsManager', function () {
|
||||||
deleteRobot.customDELETE().then(function(resp) {
|
deleteRobot.customDELETE().then(function(resp) {
|
||||||
for (var i = 0; i < $scope.robots.length; ++i) {
|
for (var i = 0; i < $scope.robots.length; ++i) {
|
||||||
if ($scope.robots[i].name == info.name) {
|
if ($scope.robots[i].name == info.name) {
|
||||||
$scope.robots.slice(i, 1);
|
$scope.robots.splice(i, 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, function() {
|
}, 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 () {
|
quayApp.directive('organizationHeader', function () {
|
||||||
var directiveDefinitionObject = {
|
var directiveDefinitionObject = {
|
||||||
|
|
|
@ -422,30 +422,19 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
|
||||||
'html': true
|
'html': true
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#copyClipboard').clipboardCopy();
|
|
||||||
|
|
||||||
var namespace = $routeParams.namespace;
|
var namespace = $routeParams.namespace;
|
||||||
var name = $routeParams.name;
|
var name = $routeParams.name;
|
||||||
|
|
||||||
$scope.permissions = {'team': [], 'user': []};
|
$scope.permissions = {'team': [], 'user': []};
|
||||||
|
|
||||||
$scope.isDownloadSupported = function() {
|
$scope.getPrefix = function(name) {
|
||||||
try { return !!new Blob(); } catch(e){}
|
var plus = name.indexOf('+');
|
||||||
return false;
|
return name.substr(0, plus + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.downloadCfg = function(token) {
|
$scope.getShortenedName = function(name) {
|
||||||
var auth = $.base64.encode("$token:" + token.code);
|
var plus = name.indexOf('+');
|
||||||
config = {
|
return name.substr(plus + 1);
|
||||||
"https://quay.io/v1/": {
|
|
||||||
"auth": auth,
|
|
||||||
"email": ""
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var file = JSON.stringify(config, null, ' ');
|
|
||||||
var blob = new Blob([file]);
|
|
||||||
saveAs(blob, '.dockercfg');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.grantRole = function() {
|
$scope.grantRole = function() {
|
||||||
|
@ -554,9 +543,11 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.shownTokenCounter = 0;
|
||||||
|
|
||||||
$scope.showToken = function(tokenCode) {
|
$scope.showToken = function(tokenCode) {
|
||||||
$scope.shownToken = $scope.tokens[tokenCode];
|
$scope.shownToken = $scope.tokens[tokenCode];
|
||||||
$('#tokenmodal').modal({});
|
$scope.shownTokenCounter++;
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.askChangeAccess = function(newAccess) {
|
$scope.askChangeAccess = function(newAccess) {
|
||||||
|
@ -1103,17 +1094,7 @@ function OrgViewCtrl($rootScope, $scope, Restangular, $routeParams) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.createTeamShown = function() {
|
$scope.createTeam = function(teamname) {
|
||||||
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();
|
|
||||||
if (!teamname) {
|
if (!teamname) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
<form name="newteamform" ng-submit="createTeam(); hide()" novalidate>
|
|
||||||
<input id="create-team-box" type="text form-control" placeholder="Team Name" ng-blur="hide()" ng-pattern="/^[a-zA-Z][a-zA-Z0-9]+$/" ng-model="newTeamName" ng-trim="false" ng-minlength="2" required>
|
|
||||||
</form>
|
|
|
@ -9,7 +9,12 @@
|
||||||
<div class="org-view container" ng-show="!loading && organization">
|
<div class="org-view container" ng-show="!loading && organization">
|
||||||
<div class="organization-header" organization="organization">
|
<div class="organization-header" organization="organization">
|
||||||
<div class="header-buttons" ng-show="organization.is_admin">
|
<div class="header-buttons" ng-show="organization.is_admin">
|
||||||
<button class="btn btn-success" data-trigger="click" bs-popover="'static/partials/create-team-dialog.html'" data-placement="bottom" ng-click="createTeamShown()"><i class="fa fa-group"></i> Create Team</button>
|
|
||||||
|
<span class="popup-input-button" pattern="'^[a-zA-Z][a-zA-Z0-9]+$'" placeholder="'Team Name'"
|
||||||
|
submitted="createTeam(value)">
|
||||||
|
<i class="fa fa-group"></i> Create Team
|
||||||
|
</span>
|
||||||
|
|
||||||
<a class="btn btn-default" href="/organization/{{ organization.name }}/admin"><i class="fa fa-gear"></i> Settings</a>
|
<a class="btn btn-default" href="/organization/{{ organization.name }}/admin"><i class="fa fa-gear"></i> Settings</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -34,16 +34,16 @@
|
||||||
<div id="permissions" class="tab-pane active">
|
<div id="permissions" class="tab-pane active">
|
||||||
<!-- User Access Permissions -->
|
<!-- User Access Permissions -->
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">User <span ng-show="repo.is_organization">and Team</span> Access Permissions
|
<div class="panel-heading">Access Permissions
|
||||||
|
|
||||||
<i class="info-icon fa fa-info-circle" data-placement="left" data-content="Allow any number of users or teams to read, write or administer this repository"></i>
|
<i class="info-icon fa fa-info-circle" data-placement="left" data-content="Allow any number of users, robot accounts or teams to read, write or administer this repository"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
|
|
||||||
<table class="permissions">
|
<table class="permissions">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td>User<span ng-show="repo.is_organization">/Team</span></td>
|
<td style="min-width: 400px;">User<span ng-show="repo.is_organization">/Team</span>/Robot Account</td>
|
||||||
<td>Permissions</td>
|
<td>Permissions</td>
|
||||||
<td style="width: 95px;"></td>
|
<td style="width: 95px;"></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
<!-- Team Permissions -->
|
<!-- Team Permissions -->
|
||||||
<tr ng-repeat="(name, permission) in permissions['team']">
|
<tr ng-repeat="(name, permission) in permissions['team']">
|
||||||
<td class="team entity">
|
<td class="team entity">
|
||||||
<i class="fa fa-group"></i>
|
<i class="fa fa-group" title="Team" bs-tooltip="tooltip.title"></i>
|
||||||
<span><a href="/organization/{{ repo.namespace }}/teams/{{ name }}">{{name}}</a></span>
|
<span><a href="/organization/{{ repo.namespace }}/teams/{{ name }}">{{name}}</a></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="user-permissions">
|
<td class="user-permissions">
|
||||||
|
@ -69,9 +69,9 @@
|
||||||
<!-- User Permissions -->
|
<!-- User Permissions -->
|
||||||
<tr ng-repeat="(name, permission) in permissions['user']">
|
<tr ng-repeat="(name, permission) in permissions['user']">
|
||||||
<td class="{{ 'user entity ' + (permission.is_org_member ? '' : 'outside') }}">
|
<td class="{{ 'user entity ' + (permission.is_org_member ? '' : 'outside') }}">
|
||||||
<i class="fa fa-user" ng-show="!permission.is_robot"></i>
|
<i class="fa fa-user" ng-show="!permission.is_robot" title="User" bs-tooltip="tooltip.title"></i>
|
||||||
<i class="fa fa-wrench" ng-show="permission.is_robot"></i>
|
<i class="fa fa-wrench" ng-show="permission.is_robot" title="Robot Account" bs-tooltip="tooltip.title"></i>
|
||||||
<span>{{name}}</span>
|
<span class="prefix">{{getPrefix(name)}}</span><span>{{getShortenedName(name)}}</span>
|
||||||
<i class="fa fa-exclamation-triangle" ng-show="permission.is_org_member === false" data-trigger="hover" bs-popover="{'content': 'This user is not a member of the organization'}"></i>
|
<i class="fa fa-exclamation-triangle" ng-show="permission.is_org_member === false" data-trigger="hover" bs-popover="{'content': 'This user is not a member of the organization'}"></i>
|
||||||
</td>
|
</td>
|
||||||
<td class="user-permissions">
|
<td class="user-permissions">
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2" class="admin-search">
|
||||||
<span class="entity-search" namespace="repo.namespace" include-teams="true" input-title="'Add a ' + (repo.is_organization ? 'team or ' : '') + 'user...'" entity-selected="addNewPermission"></span>
|
<span class="entity-search" namespace="repo.namespace" include-teams="true" input-title="'Add a ' + (repo.is_organization ? 'team or ' : '') + 'user...'" entity-selected="addNewPermission"></span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -107,7 +107,7 @@
|
||||||
<table class="permissions">
|
<table class="permissions">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Token Description</td>
|
<td style="min-width: 400px;">Token Description</td>
|
||||||
<td>Permissions</td>
|
<td>Permissions</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -133,10 +133,10 @@
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td class="admin-search">
|
||||||
<input type="text" class="form-control" placeholder="New token description" ng-model="newToken.friendlyName"required>
|
<input type="text" class="form-control" placeholder="New token description" ng-model="newToken.friendlyName"required>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="admin-search">
|
||||||
<button type="submit" ng-disabled="createTokenForm.$invalid" class="btn btn-sm btn-default">Create</button>
|
<button type="submit" ng-disabled="createTokenForm.$invalid" class="btn btn-sm btn-default">Create</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -238,6 +238,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="docker-auth-dialog" username="shownToken.friendlyName" token="shownToken.code"
|
||||||
|
shown="!!shownToken" counter="shownTokenCounter">
|
||||||
|
<i class="fa fa-key"></i> {{ shownToken.friendlyName }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Modal message dialog -->
|
<!-- Modal message dialog -->
|
||||||
<div class="modal fade" id="cannotchangeModal">
|
<div class="modal fade" id="cannotchangeModal">
|
||||||
|
@ -257,36 +261,6 @@
|
||||||
</div><!-- /.modal-dialog -->
|
</div><!-- /.modal-dialog -->
|
||||||
</div><!-- /.modal -->
|
</div><!-- /.modal -->
|
||||||
|
|
||||||
<!-- Modal message dialog -->
|
|
||||||
<div class="modal fade" id="tokenmodal">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
|
||||||
<h4 class="modal-title"><i class="fa fa-key"></i> {{ shownToken.friendlyName }}</h4>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body token-dialog-body">
|
|
||||||
<div class="alert alert-info">The docker <u>username</u> is <b>$token</b> and the <u>password</u> is the token. You may use any value for email.</div>
|
|
||||||
<div class="well well-sm">
|
|
||||||
<input id="token-view" class="token-view" type="text" value="{{ shownToken.code }}" onClick="this.select();" readonly>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<span class="download-cfg" ng-show="isDownloadSupported()">
|
|
||||||
<i class="icon-download"></i>
|
|
||||||
<a href="javascript:void(0)" ng-click="downloadCfg(shownToken)">Download .dockercfg file</a>
|
|
||||||
</span>
|
|
||||||
<div id="clipboardCopied" style="display: none">
|
|
||||||
Copied to clipboard
|
|
||||||
</div>
|
|
||||||
<button id="copyClipboard" type="button" class="btn btn-primary" data-clipboard-target="token-view">Copy to clipboard</button>
|
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
|
||||||
</div>
|
|
||||||
</div><!-- /.modal-content -->
|
|
||||||
</div><!-- /.modal-dialog -->
|
|
||||||
</div><!-- /.modal -->
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Modal message dialog -->
|
<!-- Modal message dialog -->
|
||||||
<div class="modal fade" id="makepublicModal">
|
<div class="modal fade" id="makepublicModal">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
|
|
Reference in a new issue