Merge branch 'looksirdroids'

This commit is contained in:
Joseph Schorr 2013-11-24 22:43:19 -05:00
commit 72acc8769b
22 changed files with 728 additions and 208 deletions

View file

@ -3,6 +3,11 @@
margin: 0;
}
#input-box {
padding: 4px;
font-size: 14px;
}
html, body {
height: 100%;
}
@ -16,6 +21,45 @@ html, body {
border-bottom: 1px dashed #aaa;
}
.docker-auth-dialog .token-dialog-body .well {
margin-bottom: 0px;
}
.docker-auth-dialog .token-view {
background: transparent;
display: block;
border: 0px transparent;
font-size: 12px;
width: 100%;
}
.docker-auth-dialog .download-cfg {
float: left;
padding-top: 6px;
font-size: 16px;
}
.docker-auth-dialog .download-cfg .fa-download {
margin-right: 10px;
font-size: 25px;
vertical-align: middle;
}
#copyClipboard {
cursor: pointer;
}
#copyClipboard.zeroclipboard-is-hover {
background: #428bca;
color: white;
}
#clipboardCopied.hovering {
position: absolute;
right: 0px;
top: 40px;
}
.content-container {
padding-bottom: 70px;
}
@ -43,6 +87,27 @@ html, body {
visibility: hidden;
}
.robots-manager-element {
max-width: 800px;
}
.robots-manager-element .alert {
margin-bottom: 20px;
}
.robots-manager-element .robot a {
font-size: 16px;
cursor: pointer;
}
.robots-manager-element .robot .prefix {
color: #aaa;
}
.robots-manager-element .robot i {
margin-right: 10px;
}
.billing-options-element .current-card {
font-size: 16px;
margin-bottom: 20px;
@ -957,21 +1022,6 @@ p.editable:hover i {
width: 300px;
}
.repo #copyClipboard {
cursor: pointer;
}
.repo #copyClipboard.zeroclipboard-is-hover {
background: #428bca;
color: white;
}
.repo #clipboardCopied.hovering {
position: absolute;
right: 0px;
top: 40px;
}
.repo-image-view .id-container {
display: inline-block;
margin-top: 10px;
@ -1014,7 +1064,7 @@ p.editable:hover i {
margin-top: 28px;
}
.repo #clipboardCopied {
#clipboardCopied {
font-size: 0.8em;
display: inline-block;
margin-right: 10px;
@ -1025,7 +1075,7 @@ p.editable:hover i {
border-radius: 4px;
}
.repo #clipboardCopied.animated {
#clipboardCopied.animated {
-webkit-animation: fadeOut 4s ease-in-out 0s 1 forwards;
-moz-animation: fadeOut 4s ease-in-out 0s 1 forwards;
-ms-animation: fadeOut 4s ease-in-out 0s 1 forwards;
@ -1112,21 +1162,17 @@ p.editable:hover i {
width: 300px;
}
.repo-admin .token-dialog-body .well {
margin-bottom: 0px;
}
.repo-admin .token-view {
background: transparent;
display: block;
border: 0px transparent;
font-size: 12px;
width: 100%;
}
.repo-admin .panel {
display: inline-block;
width: 620px;
width: 720px;
}
.repo-admin .prefix {
color: #aaa;
}
.repo-admin .admin-search {
padding-top: 20px;
}
.repo-admin .user i.fa-user {
@ -1134,6 +1180,11 @@ p.editable:hover i {
margin-right: 7px;
}
.repo-admin .user i.fa-wrench {
margin-left: 1px;
margin-right: 5px;
}
.repo-admin .team i.fa-group {
margin-right: 4px;
}
@ -1265,18 +1316,6 @@ p.editable:hover i {
white-space: nowrap;
}
.repo .download-cfg {
float: left;
padding-top: 6px;
font-size: 16px;
}
.repo .download-cfg .icon-download {
margin-right: 10px;
font-size: 25px;
vertical-align: middle;
}
.navbar-nav > li > .user-dropdown {
padding-top: 9px;
padding-bottom: 9px;
@ -1732,16 +1771,16 @@ p.editable:hover i {
margin-right: 4px;
}
.org-admin #members .side-controls {
.side-controls {
float: right;
}
.org-admin #members .result-count {
.side-controls .result-count {
display: inline-block;
margin-right: 10px;
}
.org-admin #members .filter-input {
.side-controls .filter-input {
display: inline-block;
}

View 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">&times;</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 -->

View 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>

View 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>

View file

@ -0,0 +1,40 @@
<div class="robots-manager-element">
<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 role-based accounts that you manage</div>
<div class="container" ng-show="!loading">
<div class="side-controls">
<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>
<table class="table">
<thead>
<th>Robot Account Name</th>
<th style="width: 150px"></th>
</thead>
<tr ng-repeat="robotInfo in robots">
<td class="robot">
<i class="fa fa-wrench"></i>
<a ng-click="showRobot(robotInfo)">
<span class="prefix">{{ getPrefix(robotInfo.name) }}+</span>{{ getShortenedName(robotInfo.name) }}
</a>
</td>
<td>
<span class="delete-ui" tabindex="0">
<span class="delete-ui-button" ng-click="deleteRobot(robotInfo)"><button class="btn btn-danger">Delete</button></span>
<i class="fa fa-times" bs-tooltip="tooltip.title" data-placement="right" title="Delete Robot Account"></i>
</span>
</td>
</tr>
</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>

View file

@ -550,7 +550,7 @@ quayApp.directive('plansTable', function () {
priority: 0,
templateUrl: '/static/directives/plans-table.html',
replace: false,
transclude: true,
transclude: false,
restrict: 'C',
scope: {
'plans': '=plans',
@ -566,6 +566,208 @@ quayApp.directive('plansTable', function () {
});
quayApp.directive('dockerAuthDialog', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/docker-auth-dialog.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'username': '=username',
'token': '=token',
'shown': '=shown',
'counter': '=counter'
},
controller: function($scope, $element, Restangular) {
$scope.isDownloadSupported = function() {
try { return !!new Blob(); } catch(e){}
return false;
};
$scope.downloadCfg = function() {
var auth = $.base64.encode($scope.username + ":" + $scope.token);
config = {
"https://quay.io/v1/": {
"auth": auth,
"email": ""
}
};
var file = JSON.stringify(config, null, ' ');
var blob = new Blob([file]);
saveAs(blob, '.dockercfg');
};
var show = function(r) {
if (!$scope.shown || !$scope.username || !$scope.token) {
$('#dockerauthmodal').modal('hide');
return;
}
$('#copyClipboard').clipboardCopy();
$('#dockerauthmodal').modal({});
};
$scope.$watch('counter', show);
$scope.$watch('shown', show);
$scope.$watch('username', show);
$scope.$watch('token', show);
}
};
return directiveDefinitionObject;
});
quayApp.directive('robotsManager', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/robots-manager.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'organization': '=organization',
'user': '=user'
},
controller: function($scope, $element, Restangular) {
$scope.robots = null;
$scope.loading = false;
$scope.shownRobot = null;
$scope.showRobotCounter = 0;
$scope.showRobot = function(info) {
$scope.shownRobot = info;
$scope.showRobotCounter++;
};
$scope.getShortenedName = function(name) {
var plus = name.indexOf('+');
return name.substr(plus + 1);
};
$scope.getPrefix = function(name) {
var plus = name.indexOf('+');
return name.substr(0, plus);
};
$scope.createRobot = function(name) {
if (!name) { return; }
var url = $scope.organization ? getRestUrl('organization', $scope.organization.name, 'robots', name) :
getRestUrl('user/robots', name);
var createRobot = Restangular.one(url);
createRobot.customPUT().then(function(resp) {
$scope.robots.push(resp);
}, function(resp) {
bootbox.dialog({
"message": resp.data ? resp.data : 'The robot account could not be created',
"title": "Cannot create robot account",
"buttons": {
"close": {
"label": "Close",
"className": "btn-primary"
}
}
});
});
};
$scope.deleteRobot = function(info) {
var shortName = $scope.getShortenedName(info.name);
var url = $scope.organization ? getRestUrl('organization', $scope.organization.name, 'robots', shortName) :
getRestUrl('user/robots', shortName);
var deleteRobot = Restangular.one(url);
deleteRobot.customDELETE().then(function(resp) {
for (var i = 0; i < $scope.robots.length; ++i) {
if ($scope.robots[i].name == info.name) {
$scope.robots.splice(i, 1);
return;
}
}
}, function() {
bootbox.dialog({
"message": 'The selected robot account could not be deleted',
"title": "Cannot delete robot account",
"buttons": {
"close": {
"label": "Close",
"className": "btn-primary"
}
}
});
});
};
var update = function() {
if (!$scope.user && !$scope.organization) { return; }
if ($scope.loading) { return; }
$scope.loading = true;
var url = $scope.organization ? getRestUrl('organization', $scope.organization.name, 'robots') : 'user/robots';
var getRobots = Restangular.one(url);
getRobots.customGET($scope.obj).then(function(resp) {
$scope.robots = resp.robots;
$scope.loading = false;
});
};
$scope.$watch('organization', update);
$scope.$watch('user', update);
}
};
return directiveDefinitionObject;
});
quayApp.directive('popupInputButton', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/popup-input-button.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'placeholder': '=placeholder',
'pattern': '=pattern',
'submitted': '&submitted'
},
controller: function($scope, $element) {
$scope.popupShown = function() {
setTimeout(function() {
var box = $('#input-box');
box[0].value = '';
box.focus();
}, 10);
};
$scope.getRegexp = function(pattern) {
if (!pattern) {
pattern = '.*';
}
return new RegExp(pattern);
};
$scope.inputSubmit = function() {
var box = $('#input-box');
if (box.hasClass('ng-invalid')) { return; }
var entered = box[0].value;
if (!entered) {
return;
}
if ($scope.submitted) {
$scope.submitted({'value': entered});
}
};
}
};
return directiveDefinitionObject;
});
quayApp.directive('organizationHeader', function () {
var directiveDefinitionObject = {
priority: 0,
@ -738,9 +940,10 @@ quayApp.directive('entitySearch', function () {
transclude: false,
restrict: 'C',
scope: {
'organization': '=organization',
'namespace': '=namespace',
'inputTitle': '=inputTitle',
'entitySelected': '=entitySelected'
'entitySelected': '=entitySelected',
'includeTeams': '=includeTeams'
},
controller: function($scope, $element) {
if (!$scope.entitySelected) { return; }
@ -748,15 +951,16 @@ quayApp.directive('entitySearch', function () {
number++;
var input = $element[0].firstChild;
$scope.organization = $scope.organization || '';
$scope.namespace = $scope.namespace || '';
$(input).typeahead({
name: 'entities' + number,
remote: {
url: '/api/entities/%QUERY',
replace: function (url, uriEncodedQuery) {
url = url.replace('%QUERY', uriEncodedQuery);
if ($scope.organization) {
url += '?organization=' + encodeURIComponent($scope.organization);
url += '?namespace=' + encodeURIComponent($scope.namespace);
if ($scope.includeTeams) {
url += '&includeTeams=true'
}
return url;
},
@ -775,14 +979,16 @@ quayApp.directive('entitySearch', function () {
},
template: function (datum) {
template = '<div class="entity-mini-listing">';
if (datum.entity.kind == 'user') {
if (datum.entity.kind == 'user' && !datum.entity.is_robot) {
template += '<i class="fa fa-user fa-lg"></i>';
} else if (datum.entity.kind == 'user' && datum.entity.is_robot) {
template += '<i class="fa fa-wrench fa-lg"></i>';
} else if (datum.entity.kind == 'team') {
template += '<i class="fa fa-group fa-lg"></i>';
}
template += '<span class="name">' + datum.value + '</span>';
if (datum.entity.is_org_member !== undefined && !datum.entity.is_org_member) {
if (datum.entity.is_org_member !== undefined && !datum.entity.is_org_member && datum.kind == 'user') {
template += '<div class="alert-warning warning">This user is outside your organization</div>';
}

View file

@ -422,30 +422,19 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
'html': true
});
$('#copyClipboard').clipboardCopy();
var namespace = $routeParams.namespace;
var name = $routeParams.name;
$scope.permissions = {'team': [], 'user': []};
$scope.isDownloadSupported = function() {
try { return !!new Blob(); } catch(e){}
return false;
$scope.getPrefix = function(name) {
var plus = name.indexOf('+');
return name.substr(0, plus + 1);
};
$scope.downloadCfg = function(token) {
var auth = $.base64.encode("$token:" + token.code);
config = {
"https://quay.io/v1/": {
"auth": auth,
"email": ""
}
};
var file = JSON.stringify(config, null, ' ');
var blob = new Blob([file]);
saveAs(blob, '.dockercfg');
$scope.getShortenedName = function(name) {
var plus = name.indexOf('+');
return name.substr(plus + 1);
};
$scope.grantRole = function() {
@ -468,7 +457,7 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
// Need the $scope.apply for both the permission stuff to change and for
// the XHR call to be made.
$scope.$apply(function() {
$scope.addRole(entity.name, 'read', entity.kind, entity.is_org_member)
$scope.addRole(entity.name, 'read', entity.kind);
});
};
@ -486,15 +475,14 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
});
};
$scope.addRole = function(entityName, role, kind, is_org_member) {
$scope.addRole = function(entityName, role, kind) {
var permission = {
'role': role,
'is_org_member': is_org_member
};
var permissionPost = Restangular.one(getRestUrl('repository', namespace, name, 'permissions', kind, entityName));
permissionPost.customPOST(permission).then(function() {
$scope.permissions[kind][entityName] = permission;
permissionPost.customPOST(permission).then(function(result) {
$scope.permissions[kind][entityName] = result;
}, function(result) {
$('#cannotchangeModal').modal({});
});
@ -555,9 +543,11 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
});
};
$scope.shownTokenCounter = 0;
$scope.showToken = function(tokenCode) {
$scope.shownToken = $scope.tokens[tokenCode];
$('#tokenmodal').modal({});
$scope.shownTokenCounter++;
};
$scope.askChangeAccess = function(newAccess) {
@ -1104,17 +1094,7 @@ function OrgViewCtrl($rootScope, $scope, Restangular, $routeParams) {
});
};
$scope.createTeamShown = function() {
setTimeout(function() {
$('#create-team-box').focus();
}, 10);
};
$scope.createTeam = function() {
var box = $('#create-team-box');
if (box.hasClass('ng-invalid')) { return; }
var teamname = box[0].value.toLowerCase();
$scope.createTeam = function(teamname) {
if (!teamname) {
return;
}
@ -1247,7 +1227,7 @@ function TeamViewCtrl($rootScope, $scope, Restangular, $routeParams) {
'html': true
});
var orgname = $routeParams.orgname;
$scope.orgname = $routeParams.orgname;
var teamname = $routeParams.teamname;
$rootScope.title = 'Loading...';
@ -1258,7 +1238,7 @@ function TeamViewCtrl($rootScope, $scope, Restangular, $routeParams) {
if ($scope.members[member.name]) { return; }
$scope.$apply(function() {
var addMember = Restangular.one(getRestUrl('organization', orgname, 'team', teamname, 'members', member.name));
var addMember = Restangular.one(getRestUrl('organization', $scope.orgname, 'team', teamname, 'members', member.name));
addMember.customPOST().then(function(resp) {
$scope.members[member.name] = resp;
}, function() {
@ -1268,7 +1248,7 @@ function TeamViewCtrl($rootScope, $scope, Restangular, $routeParams) {
};
$scope.removeMember = function(username) {
var removeMember = Restangular.one(getRestUrl('organization', orgname, 'team', teamname, 'members', username));
var removeMember = Restangular.one(getRestUrl('organization', $scope.orgname, 'team', teamname, 'members', username));
removeMember.customDELETE().then(function(resp) {
delete $scope.members[username];
}, function() {
@ -1279,7 +1259,7 @@ function TeamViewCtrl($rootScope, $scope, Restangular, $routeParams) {
$scope.updateForDescription = function(content) {
$scope.organization.teams[teamname].description = content;
var updateTeam = Restangular.one(getRestUrl('organization', orgname, 'team', teamname));
var updateTeam = Restangular.one(getRestUrl('organization', $scope.orgname, 'team', teamname));
var data = $scope.organization.teams[teamname];
updateTeam.customPUT(data).then(function(resp) {
}, function() {
@ -1288,7 +1268,7 @@ function TeamViewCtrl($rootScope, $scope, Restangular, $routeParams) {
};
var loadOrganization = function() {
var getOrganization = Restangular.one(getRestUrl('organization', orgname))
var getOrganization = Restangular.one(getRestUrl('organization', $scope.orgname))
getOrganization.get().then(function(resp) {
$scope.organization = resp;
$scope.team = $scope.organization.teams[teamname];
@ -1301,12 +1281,12 @@ function TeamViewCtrl($rootScope, $scope, Restangular, $routeParams) {
};
var loadMembers = function() {
var getMembers = Restangular.one(getRestUrl('organization', orgname, 'team', teamname, 'members'));
var getMembers = Restangular.one(getRestUrl('organization', $scope.orgname, 'team', teamname, 'members'));
getMembers.get().then(function(resp) {
$scope.members = resp.members;
$scope.canEditMembers = resp.can_edit;
$scope.loading = !$scope.organization || !$scope.members;
$rootScope.title = teamname + ' (' + orgname + ')';
$rootScope.title = teamname + ' (' + $scope.orgname + ')';
$rootScope.description = 'Team management page for team ' + teamname + ' under organization ' + orgname;
}, function() {
$scope.organization = null;

View file

@ -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>

View file

@ -50,11 +50,32 @@ Email: my@email.com</pre>
</div>
<br>
<h3>Using robot accounts <span class="label label-info">Requires Admin Access</span></h3>
<div class="container">
<div class="description-overview">
There are many circumstances where permissions for repositories need to be shared across those repositories (continuous integration, etc).
To support this case, Quay allows the use of <b>robot accounts</b> which can be created in the user/organization's admin view and can be
shared by multiple repositories that are owned by that user or organization.
</div>
<ul class="description-list">
<li>Robot accounts can be managed in the user or organization admin's interface
<li><b>Adding a robot account:</b> Click "Create Robot Account" and enter a name for the account. The username will become <b>namespace+accountname</b> where "namespace" is the name of the user or organiaztion.
<li><b>Setting permissions:</b> Permissions can be granted to a robot account in a repository by adding that account like any other user or team.
<li><b>Deleting a robot account:</b> A robot account can be deleted by clicking the <b>X</b> and then clicking <b>Delete</b>
<li><b>Using a robot account:</b> To use the robot account, the following credentials can be used:
<dl class="dl-horizontal">
<dt>Username</dt><dd>namespace+accountname (Example: mycompany+deploy)</dd>
<dt>Password</dt><dd>(token value can be found by clicking on the robot account in the admin panel)</dd>
<dt>Email</dt><dd>This value is ignored, any value may be used.</dd>
</dl>
</ul>
</div>
<h3>Using access tokens in place of users <span class="label label-info">Requires Admin Access</span></h3>
<div class="container">
<div class="description-overview">
There are many circumstances where it makes sense to <b>not</b> use a user's username and password (deployment scripts, etc).
To support this case, Quay allows the use of <b>access tokens</b> which can be created on a repository and have read and/or write
For per-repository token authentication, Quay allows the use of <b>access tokens</b> which can be created on a repository and have read and/or write
permissions, without any passwords.
</div>

View file

@ -15,6 +15,7 @@
<ul class="nav nav-pills nav-stacked">
<li class="active"><a href="javascript:void(0)" data-toggle="tab" data-target="#plan">Plan and Usage</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#members" ng-click="loadMembers()">Members</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#robots">Robot Accounts</a></li>
<li ng-show="hasPaidPlan"><a href="javascript:void(0)" data-toggle="tab" data-target="#billingoptions">Billing</a></li>
<li ng-show="hasPaidPlan"><a href="javascript:void(0)" data-toggle="tab" data-target="#billing" ng-click="loadInvoices()">Billing History</a></li>
</ul>
@ -28,6 +29,11 @@
<div class="plan-manager" organization="orgname" plan-changed="planChanged(plan)"></div>
</div>
<!-- Robot accounts tab -->
<div id="robots" class="tab-pane">
<div class="robots-manager" organization="organization"></div>
</div>
<!-- Billing Options tab -->
<div id="billingoptions" class="tab-pane">
<div class="billing-options" organization="organization"></div>

View file

@ -9,7 +9,12 @@
<div class="org-view container" ng-show="!loading && organization">
<div class="organization-header" organization="organization">
<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>
</div>
</div>

View file

@ -34,16 +34,16 @@
<div id="permissions" class="tab-pane active">
<!-- User Access Permissions -->
<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 class="panel-body">
<table class="permissions">
<thead>
<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 style="width: 95px;"></td>
</tr>
@ -52,7 +52,7 @@
<!-- Team Permissions -->
<tr ng-repeat="(name, permission) in permissions['team']">
<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>
</td>
<td class="user-permissions">
@ -68,9 +68,10 @@
<!-- User Permissions -->
<tr ng-repeat="(name, permission) in permissions['user']">
<td class="{{ 'user entity ' + (permission.is_org_member? '' : 'outside') }}">
<i class="fa fa-user"></i>
<span>{{name}}</span>
<td class="{{ 'user entity ' + (permission.is_org_member ? '' : 'outside') }}">
<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" title="Robot Account" bs-tooltip="tooltip.title"></i>
<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>
</td>
<td class="user-permissions">
@ -87,8 +88,8 @@
</tr>
<tr>
<td colspan="2">
<span class="entity-search" organization="repo.namespace" input-title="'Add a ' + (repo.is_organization ? 'team or ' : '') + 'user...'" entity-selected="addNewPermission"></span>
<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>
</td>
</tr>
</table>
@ -106,7 +107,7 @@
<table class="permissions">
<thead>
<tr>
<td>Token Description</td>
<td style="min-width: 400px;">Token Description</td>
<td>Permissions</td>
<td></td>
</tr>
@ -132,10 +133,10 @@
</tr>
<tr>
<td>
<td class="admin-search">
<input type="text" class="form-control" placeholder="New token description" ng-model="newToken.friendlyName"required>
</td>
<td>
<td class="admin-search">
<button type="submit" ng-disabled="createTokenForm.$invalid" class="btn btn-sm btn-default">Create</button>
</td>
</tr>
@ -237,6 +238,10 @@
</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 -->
<div class="modal fade" id="cannotchangeModal">
@ -256,36 +261,6 @@
</div><!-- /.modal-dialog -->
</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">&times;</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 -->
<div class="modal fade" id="makepublicModal">
<div class="modal-dialog">

View file

@ -20,7 +20,8 @@
<table class="permissions">
<tr ng-repeat="(name, member) in members">
<td class="user entity">
<i class="fa fa-user"></i>
<i class="fa fa-user" ng-show="!member.is_robot"></i>
<i class="fa fa-wrench" ng-show="member.is_robot"></i>
<span>{{ member.username }}</span>
</td>
<td>
@ -33,7 +34,7 @@
<tr ng-show="canEditMembers">
<td colspan="2">
<span class="entity-search" organization="''" input-title="'Add a user...'" entity-selected="addNewMember"></span>
<span class="entity-search" namespace="orgname" include-teams="false" input-title="'Add a user...'" entity-selected="addNewMember"></span>
</td>
</tr>
</table>

View file

@ -28,6 +28,7 @@
<ul class="nav nav-pills nav-stacked">
<li class="active"><a href="javascript:void(0)" data-toggle="tab" data-target="#plan">Plan and Usage</a></li>
<li ng-show="hasPaidPlan"><a href="javascript:void(0)" data-toggle="tab" data-target="#billing">Billing Options</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#robots">Robot Accounts</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#password">Set Password</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#migrate" id="migrateTab">Convert to Organization</a></li>
</ul>
@ -59,6 +60,11 @@
</div>
</div>
<!-- Robot accounts tab -->
<div id="robots" class="tab-pane">
<div class="robots-manager" user="user"></div>
</div>
<!-- Billing options tab -->
<div id="billing" class="tab-pane">
<div class="billing-options" user="user"></div>