Merge branch 'master' of ssh://bitbucket.org/yackob03/quay into diffs
This commit is contained in:
commit
5e43cba0f5
12 changed files with 448 additions and 146 deletions
|
@ -71,6 +71,10 @@ def signin():
|
||||||
def repository():
|
def repository():
|
||||||
return index('')
|
return index('')
|
||||||
|
|
||||||
|
@app.route('/v1')
|
||||||
|
@app.route('/v1/')
|
||||||
|
def v1():
|
||||||
|
return index('')
|
||||||
|
|
||||||
@app.route('/status', methods=['GET'])
|
@app.route('/status', methods=['GET'])
|
||||||
def status():
|
def status():
|
||||||
|
|
|
@ -52,7 +52,8 @@ def create_subtree(repo, structure, parent):
|
||||||
create_subtree(repo, subtree, new_image)
|
create_subtree(repo, subtree, new_image)
|
||||||
|
|
||||||
|
|
||||||
def __generate_repository(user, name, description, is_public, permissions, structure):
|
def __generate_repository(user, name, description, is_public, permissions,
|
||||||
|
structure):
|
||||||
repo = model.create_repository(user.username, name, user)
|
repo = model.create_repository(user.username, name, user)
|
||||||
|
|
||||||
if is_public:
|
if is_public:
|
||||||
|
@ -92,7 +93,7 @@ if __name__ == '__main__':
|
||||||
'Complex repository with many branches and tags.',
|
'Complex repository with many branches and tags.',
|
||||||
False, [(new_user_2, 'read')],
|
False, [(new_user_2, 'read')],
|
||||||
(2, [(3, [], 'v2.0'),
|
(2, [(3, [], 'v2.0'),
|
||||||
(1, [(1, [(1, [], ['latest', 'prod'])],
|
(1, [(1, [(1, [], ['prod'])],
|
||||||
'staging'),
|
'staging'),
|
||||||
(1, [], None)], None)], None))
|
(1, [], None)], None)], None))
|
||||||
|
|
||||||
|
@ -113,3 +114,7 @@ if __name__ == '__main__':
|
||||||
__generate_repository(new_user_1, 'shared',
|
__generate_repository(new_user_1, 'shared',
|
||||||
'Shared repository, another user can write.', False,
|
'Shared repository, another user can write.', False,
|
||||||
[(new_user_2, 'write')], (5, [], 'latest'))
|
[(new_user_2, 'write')], (5, [], 'latest'))
|
||||||
|
|
||||||
|
__generate_repository(new_user_1, 'empty',
|
||||||
|
'Empty repository with no images or tags.', False,
|
||||||
|
[], (0, [], None))
|
|
@ -2,11 +2,48 @@
|
||||||
font-family: 'Droid Sans', sans-serif;
|
font-family: 'Droid Sans', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.description-overview {
|
||||||
|
padding: 4px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-list {
|
||||||
|
margin: 10px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-list li:before {
|
||||||
|
content: "\00BB";
|
||||||
|
margin-right: 6px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-list li {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-icon {
|
||||||
|
display: inline-block;
|
||||||
|
float: right;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.accordion-toggle {
|
.accordion-toggle {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-guide h3 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-guide h3 .label {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
.plans .callout {
|
.plans .callout {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -441,7 +478,8 @@ p.editable:hover i {
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo dl.dl-horizontal dt {
|
.repo dl.dl-horizontal dt {
|
||||||
width: 60px;
|
width: 80px;
|
||||||
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo dl.dl-horizontal dd {
|
.repo dl.dl-horizontal dd {
|
||||||
|
@ -485,18 +523,21 @@ p.editable:hover i {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo #clipboardCopied {
|
.repo #clipboardCopied.hovering {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
top: 40px;
|
top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo #clipboardCopied {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 10px;
|
||||||
|
|
||||||
background: black;
|
background: black;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
|
|
||||||
-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;
|
||||||
|
@ -557,6 +598,18 @@ p.editable:hover i {
|
||||||
padding-left: 36px;
|
padding-left: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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: 620px;
|
||||||
|
@ -571,6 +624,10 @@ p.editable:hover i {
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.repo-admin .token a {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.repo .description p {
|
.repo .description p {
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,6 +117,23 @@ quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}).
|
}).
|
||||||
|
directive('onresize', function ($window, $parse) {
|
||||||
|
return function (scope, element, attr) {
|
||||||
|
var fn = $parse(attr.onresize);
|
||||||
|
|
||||||
|
var notifyResized = function() {
|
||||||
|
scope.$apply(function () {
|
||||||
|
fn(scope);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
angular.element($window).on('resize', null, notifyResized);
|
||||||
|
|
||||||
|
scope.$on('$destroy', function() {
|
||||||
|
angular.element($window).off('resize', null, notifyResized);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}).
|
||||||
config(['$routeProvider', '$locationProvider', '$analyticsProvider',
|
config(['$routeProvider', '$locationProvider', '$analyticsProvider',
|
||||||
function($routeProvider, $locationProvider, $analyticsProvider) {
|
function($routeProvider, $locationProvider, $analyticsProvider) {
|
||||||
|
|
||||||
|
@ -129,14 +146,17 @@ quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics',
|
||||||
// index rule to make sure that deep links directly deep into the app continue to work.
|
// index rule to make sure that deep links directly deep into the app continue to work.
|
||||||
// WARNING WARNING WARNING
|
// WARNING WARNING WARNING
|
||||||
$routeProvider.
|
$routeProvider.
|
||||||
when('/repository/:namespace/:name', {templateUrl: '/static/partials/view-repo.html', controller: RepoCtrl}).
|
when('/repository/:namespace/:name', {templateUrl: '/static/partials/view-repo.html', controller: RepoCtrl, reloadOnSearch: false}).
|
||||||
when('/repository/:namespace/:name/tag/:tag', {templateUrl: '/static/partials/view-repo.html', controller: RepoCtrl}).
|
when('/repository/:namespace/:name/tag/:tag', {templateUrl: '/static/partials/view-repo.html', controller: RepoCtrl}).
|
||||||
when('/repository/:namespace/:name/admin', {templateUrl: '/static/partials/repo-admin.html', controller:RepoAdminCtrl}).
|
when('/repository/:namespace/:name/admin', {templateUrl: '/static/partials/repo-admin.html', controller:RepoAdminCtrl}).
|
||||||
when('/repository/', {title: 'Repositories', templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl}).
|
when('/repository/', {title: 'Repositories', templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl}).
|
||||||
when('/user/', {title: 'User Admin', templateUrl: '/static/partials/user-admin.html', controller: UserAdminCtrl}).
|
when('/user/', {title: 'User Admin', templateUrl: '/static/partials/user-admin.html', controller: UserAdminCtrl}).
|
||||||
when('/guide/', {title: 'Getting Started Guide', templateUrl: '/static/partials/guide.html', controller: GuideCtrl}).
|
when('/guide/', {title: 'User Guide', templateUrl: '/static/partials/guide.html', controller: GuideCtrl}).
|
||||||
when('/plans/', {title: 'Plans and Pricing', templateUrl: '/static/partials/plans.html', controller: PlansCtrl}).
|
when('/plans/', {title: 'Plans and Pricing', templateUrl: '/static/partials/plans.html', controller: PlansCtrl}).
|
||||||
when('/signin/', {title: 'Signin', templateUrl: '/static/partials/signin.html', controller: SigninCtrl}).
|
when('/signin/', {title: 'Signin', templateUrl: '/static/partials/signin.html', controller: SigninCtrl}).
|
||||||
|
|
||||||
|
when('/v1/', {title: 'Activation information', templateUrl: '/static/partials/v1-page.html', controller: V1Ctrl}).
|
||||||
|
|
||||||
when('/', {title: 'Hosted Private Docker Registry', templateUrl: '/static/partials/landing.html', controller: LandingCtrl}).
|
when('/', {title: 'Hosted Private Docker Registry', templateUrl: '/static/partials/landing.html', controller: LandingCtrl}).
|
||||||
otherwise({redirectTo: '/'});
|
otherwise({redirectTo: '/'});
|
||||||
}]).
|
}]).
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
$.fn.clipboardCopy = function() {
|
||||||
|
var clip = new ZeroClipboard($(this), { 'moviePath': 'static/lib/ZeroClipboard.swf' });
|
||||||
|
clip.on('complete', function() {
|
||||||
|
// Resets the animation.
|
||||||
|
var elem = $('#clipboardCopied')[0];
|
||||||
|
elem.style.display = 'none';
|
||||||
|
|
||||||
|
// Show the notification.
|
||||||
|
setTimeout(function() {
|
||||||
|
elem.style.display = 'inline-block';
|
||||||
|
}, 1);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
function getFirstTextLine(commentString) {
|
function getFirstTextLine(commentString) {
|
||||||
if (!commentString) { return; }
|
if (!commentString) { return; }
|
||||||
|
|
||||||
|
@ -36,7 +50,7 @@ function getMarkedDown(string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function HeaderCtrl($scope, $location, UserService, Restangular) {
|
function HeaderCtrl($scope, $location, UserService, Restangular) {
|
||||||
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
||||||
$scope.user = currentUser;
|
$scope.user = currentUser;
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
@ -256,9 +270,14 @@ function LandingCtrl($scope, $timeout, Restangular, UserService, KeyService) {
|
||||||
browserchrome.update();
|
browserchrome.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
function RepoCtrl($scope, Restangular, $routeParams, $rootScope) {
|
function RepoCtrl($scope, Restangular, $routeParams, $rootScope, $location, $timeout) {
|
||||||
$rootScope.title = 'Loading...';
|
$rootScope.title = 'Loading...';
|
||||||
|
|
||||||
|
// Watch for changes to the tag parameter.
|
||||||
|
$scope.$on('$routeUpdate', function(){
|
||||||
|
$scope.setTag($location.search().tag, false);
|
||||||
|
});
|
||||||
|
|
||||||
$scope.editDescription = function() {
|
$scope.editDescription = function() {
|
||||||
if (!$scope.repo.can_write) { return; }
|
if (!$scope.repo.can_write) { return; }
|
||||||
|
|
||||||
|
@ -295,19 +314,45 @@ function RepoCtrl($scope, Restangular, $routeParams, $rootScope) {
|
||||||
if (!string) { return ''; }
|
if (!string) { return ''; }
|
||||||
return getMarkedDown(string);
|
return getMarkedDown(string);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.listImages = function() {
|
var getDefaultTag = function() {
|
||||||
|
if ($scope.repo === undefined) {
|
||||||
|
return undefined;
|
||||||
|
} else if ($scope.repo.tags.hasOwnProperty('latest')) {
|
||||||
|
return $scope.repo.tags['latest'];
|
||||||
|
} else {
|
||||||
|
for (key in $scope.repo.tags) {
|
||||||
|
return $scope.repo.tags[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.$watch('repo', function() {
|
||||||
|
if ($scope.tree) {
|
||||||
|
$timeout(function() {
|
||||||
|
$scope.tree.notifyResized();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var listImages = function() {
|
||||||
if ($scope.imageHistory) { return; }
|
if ($scope.imageHistory) { return; }
|
||||||
|
|
||||||
var imageFetch = Restangular.one('repository/' + namespace + '/' + name + '/image');
|
var imageFetch = Restangular.one('repository/' + namespace + '/' + name + '/image/');
|
||||||
imageFetch.get().then(function(resp) {
|
imageFetch.get().then(function(resp) {
|
||||||
$scope.imageHistory = resp.images;
|
$scope.imageHistory = resp.images;
|
||||||
$scope.tree = new ImageHistoryTree(namespace, name, resp.images, $scope.currentTag,
|
$scope.tree = new ImageHistoryTree(namespace, name, resp.images,
|
||||||
$scope.getCommentFirstLine, $scope.getTimeSince);
|
$scope.getCommentFirstLine, $scope.getTimeSince);
|
||||||
|
|
||||||
$scope.tree.draw('image-history-container');
|
$scope.tree.draw('image-history-container');
|
||||||
|
|
||||||
|
// If we already have a tag, use it
|
||||||
|
if ($scope.currentTag) {
|
||||||
|
$scope.tree.setTag($scope.currentTag.name);
|
||||||
|
}
|
||||||
|
|
||||||
$($scope.tree).bind('tagChanged', function(e) {
|
$($scope.tree).bind('tagChanged', function(e) {
|
||||||
$scope.$apply(function() { $scope.setTag(e.tag); });
|
$scope.$apply(function() { $scope.setTag(e.tag, true); });
|
||||||
});
|
});
|
||||||
$($scope.tree).bind('imageChanged', function(e) {
|
$($scope.tree).bind('imageChanged', function(e) {
|
||||||
$scope.$apply(function() { $scope.setImage(e.image); });
|
$scope.$apply(function() { $scope.setImage(e.image); });
|
||||||
|
@ -322,12 +367,28 @@ function RepoCtrl($scope, Restangular, $routeParams, $rootScope) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.setTag = function(tagName) {
|
$scope.setTag = function(tagName, opt_updateURL) {
|
||||||
var repo = $scope.repo;
|
var repo = $scope.repo;
|
||||||
$scope.currentTag = repo.tags[tagName] || repo.tags['latest'];
|
var proposedTag = repo.tags[tagName];
|
||||||
$scope.currentImage = $scope.currentTag.image;
|
if (!proposedTag) {
|
||||||
if ($scope.tree) {
|
// We must find a good default
|
||||||
$scope.tree.setTag($scope.currentTag.name);
|
for (tagName in repo.tags) {
|
||||||
|
if (!proposedTag || tagName == 'latest') {
|
||||||
|
proposedTag = repo.tags[tagName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proposedTag) {
|
||||||
|
$scope.currentTag = proposedTag;
|
||||||
|
$scope.currentImage = $scope.currentTag.image;
|
||||||
|
if ($scope.tree) {
|
||||||
|
$scope.tree.setTag($scope.currentTag.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt_updateURL) {
|
||||||
|
$location.search('tag', $scope.currentTag.name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -342,7 +403,6 @@ function RepoCtrl($scope, Restangular, $routeParams, $rootScope) {
|
||||||
|
|
||||||
var namespace = $routeParams.namespace;
|
var namespace = $routeParams.namespace;
|
||||||
var name = $routeParams.name;
|
var name = $routeParams.name;
|
||||||
var tag = $routeParams.tag || 'latest';
|
|
||||||
|
|
||||||
$scope.loading = true;
|
$scope.loading = true;
|
||||||
|
|
||||||
|
@ -351,21 +411,10 @@ function RepoCtrl($scope, Restangular, $routeParams, $rootScope) {
|
||||||
repositoryFetch.get().then(function(repo) {
|
repositoryFetch.get().then(function(repo) {
|
||||||
$rootScope.title = namespace + '/' + name;
|
$rootScope.title = namespace + '/' + name;
|
||||||
$scope.repo = repo;
|
$scope.repo = repo;
|
||||||
$scope.currentTag = repo.tags[tag] || repo.tags['latest'];
|
|
||||||
$scope.setImage($scope.currentTag.image);
|
|
||||||
|
|
||||||
var clip = new ZeroClipboard($('#copyClipboard'), { 'moviePath': 'static/lib/ZeroClipboard.swf' });
|
$scope.setTag($routeParams.tag);
|
||||||
clip.on('complete', function() {
|
|
||||||
// Resets the animation.
|
|
||||||
var elem = $('#clipboardCopied')[0];
|
|
||||||
elem.style.display = 'none';
|
|
||||||
|
|
||||||
// Show the notification.
|
|
||||||
setTimeout(function() {
|
|
||||||
elem.style.display = 'block';
|
|
||||||
}, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
$('#copyClipboard').clipboardCopy();
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
}, function() {
|
}, function() {
|
||||||
$scope.repo = null;
|
$scope.repo = null;
|
||||||
|
@ -374,10 +423,17 @@ function RepoCtrl($scope, Restangular, $routeParams, $rootScope) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch the image history.
|
// Fetch the image history.
|
||||||
$scope.listImages();
|
listImages();
|
||||||
}
|
}
|
||||||
|
|
||||||
function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
|
function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
|
||||||
|
$('.info-icon').popover({
|
||||||
|
'trigger': 'hover',
|
||||||
|
'html': true
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#copyClipboard').clipboardCopy();
|
||||||
|
|
||||||
var namespace = $routeParams.namespace;
|
var namespace = $routeParams.namespace;
|
||||||
var name = $routeParams.name;
|
var name = $routeParams.name;
|
||||||
|
|
||||||
|
@ -476,6 +532,8 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
|
||||||
|
|
||||||
var permissionPost = Restangular.one('repository/' + namespace + '/' + name + '/tokens/');
|
var permissionPost = Restangular.one('repository/' + namespace + '/' + name + '/tokens/');
|
||||||
permissionPost.customPOST(friendlyName).then(function(newToken) {
|
permissionPost.customPOST(friendlyName).then(function(newToken) {
|
||||||
|
$scope.newToken.friendlyName = '';
|
||||||
|
$scope.createTokenForm.$setPristine();
|
||||||
$scope.tokens[newToken.code] = newToken;
|
$scope.tokens[newToken.code] = newToken;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -699,4 +757,14 @@ function UserAdminCtrl($scope, $timeout, Restangular, PlanService, UserService,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function V1Ctrl($scope, UserService) {
|
||||||
|
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
||||||
|
$scope.user = currentUser;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
$scope.browseRepos = function() {
|
||||||
|
document.location = '/repository/';
|
||||||
|
};
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@ var DEPTH_WIDTH = 132;
|
||||||
/**
|
/**
|
||||||
* Based off of http://mbostock.github.io/d3/talk/20111018/tree.html by Mike Bostock (@mbostock)
|
* Based off of http://mbostock.github.io/d3/talk/20111018/tree.html by Mike Bostock (@mbostock)
|
||||||
*/
|
*/
|
||||||
function ImageHistoryTree(namespace, name, images, current, formatComment, formatTime) {
|
function ImageHistoryTree(namespace, name, images, formatComment, formatTime) {
|
||||||
/**
|
/**
|
||||||
* The namespace of the repo.
|
* The namespace of the repo.
|
||||||
*/
|
*/
|
||||||
|
@ -20,21 +20,6 @@ function ImageHistoryTree(namespace, name, images, current, formatComment, forma
|
||||||
*/
|
*/
|
||||||
this.images_ = images;
|
this.images_ = images;
|
||||||
|
|
||||||
/**
|
|
||||||
* The current tag.
|
|
||||||
*/
|
|
||||||
this.currentTag_ = current.name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current image.
|
|
||||||
*/
|
|
||||||
this.currentImage_ = current.image;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Counter for creating unique IDs.
|
|
||||||
*/
|
|
||||||
this.idCounter_ = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to invoke to format a comment for an image.
|
* Method to invoke to format a comment for an image.
|
||||||
*/
|
*/
|
||||||
|
@ -44,9 +29,82 @@ function ImageHistoryTree(namespace, name, images, current, formatComment, forma
|
||||||
* Method to invoke to format the time for an image.
|
* Method to invoke to format the time for an image.
|
||||||
*/
|
*/
|
||||||
this.formatTime_ = formatTime;
|
this.formatTime_ = formatTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current tag (if any).
|
||||||
|
*/
|
||||||
|
this.currentTag_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current image (if any).
|
||||||
|
*/
|
||||||
|
this.currentImage_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counter for creating unique IDs.
|
||||||
|
*/
|
||||||
|
this.idCounter_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the dimensions of the tree.
|
||||||
|
*/
|
||||||
|
ImageHistoryTree.prototype.calculateDimensions_ = function(container) {
|
||||||
|
var cw = Math.max(document.getElementById(container).clientWidth, this.maxWidth_ * DEPTH_WIDTH);
|
||||||
|
var ch = this.maxHeight_ * (DEPTH_HEIGHT + 10);
|
||||||
|
|
||||||
|
var margin = { top: 40, right: 20, bottom: 20, left: 40 };
|
||||||
|
var m = [margin.top, margin.right, margin.bottom, margin.left];
|
||||||
|
var w = cw - m[1] - m[3];
|
||||||
|
var h = ch - m[0] - m[2];
|
||||||
|
|
||||||
|
return {
|
||||||
|
'w': w,
|
||||||
|
'h': h,
|
||||||
|
'm': m,
|
||||||
|
'cw': cw,
|
||||||
|
'ch': ch
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the dimensions of the tree.
|
||||||
|
*/
|
||||||
|
ImageHistoryTree.prototype.updateDimensions_ = function() {
|
||||||
|
var container = this.container_;
|
||||||
|
var dimensions = this.calculateDimensions_(container);
|
||||||
|
|
||||||
|
var m = dimensions.m;
|
||||||
|
var w = dimensions.w;
|
||||||
|
var h = dimensions.h;
|
||||||
|
var cw = dimensions.cw;
|
||||||
|
var ch = dimensions.ch;
|
||||||
|
|
||||||
|
// Set the height of the container so that it never goes offscreen.
|
||||||
|
$('#' + container).removeOverscroll();
|
||||||
|
var viewportHeight = $(window).height();
|
||||||
|
var boundingBox = document.getElementById(container).getBoundingClientRect();
|
||||||
|
document.getElementById(container).style.maxHeight = (viewportHeight - boundingBox.top - 30) + 'px';
|
||||||
|
$('#' + container).overscroll();
|
||||||
|
|
||||||
|
// Update the tree.
|
||||||
|
var rootSvg = this.rootSvg_;
|
||||||
|
var tree = this.tree_;
|
||||||
|
var vis = this.vis_;
|
||||||
|
|
||||||
|
rootSvg
|
||||||
|
.attr("width", w + m[1] + m[3])
|
||||||
|
.attr("height", h + m[0] + m[2]);
|
||||||
|
|
||||||
|
tree.size([w, h]);
|
||||||
|
vis.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
|
||||||
|
|
||||||
|
return dimensions;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws the tree.
|
* Draws the tree.
|
||||||
*/
|
*/
|
||||||
|
@ -56,34 +114,20 @@ ImageHistoryTree.prototype.draw = function(container) {
|
||||||
this.maxWidth_ = result['maxWidth'];
|
this.maxWidth_ = result['maxWidth'];
|
||||||
this.maxHeight_ = result['maxHeight'];
|
this.maxHeight_ = result['maxHeight'];
|
||||||
|
|
||||||
// Determine the size of the SVG container.
|
// Save the container.
|
||||||
var width = Math.max(document.getElementById(container).clientWidth, this.maxWidth_ * DEPTH_WIDTH);
|
this.container_ = container;
|
||||||
var height = this.maxHeight_ * (DEPTH_HEIGHT + 10);
|
|
||||||
|
|
||||||
// Set the height of the container so that it never goes offscreen.
|
|
||||||
var viewportHeight = $(window).height();
|
|
||||||
var boundingBox = document.getElementById(container).getBoundingClientRect();
|
|
||||||
document.getElementById(container).style.maxHeight = (viewportHeight - boundingBox.top) + 'px';
|
|
||||||
|
|
||||||
var margin = { top: 40, right: 20, bottom: 20, left: 40 };
|
|
||||||
var m = [margin.top, margin.right, margin.bottom, margin.left];
|
|
||||||
var w = width - m[1] - m[3];
|
|
||||||
var h = height - m[0] - m[2];
|
|
||||||
|
|
||||||
// Create the tree and all its components.
|
// Create the tree and all its components.
|
||||||
var tree = d3.layout.tree()
|
var tree = d3.layout.tree()
|
||||||
.separation(function() { return 2; })
|
.separation(function() { return 2; });
|
||||||
.size([w, h]);
|
|
||||||
|
|
||||||
var diagonal = d3.svg.diagonal()
|
var diagonal = d3.svg.diagonal()
|
||||||
.projection(function(d) { return [d.x, d.y]; });
|
.projection(function(d) { return [d.x, d.y]; });
|
||||||
|
|
||||||
var vis = d3.select("#" + container).append("svg:svg")
|
var rootSvg = d3.select("#" + container).append("svg:svg")
|
||||||
.attr("width", w + m[1] + m[3])
|
.attr("class", "image-tree");
|
||||||
.attr("height", h + m[0] + m[2])
|
|
||||||
.attr("class", "image-tree")
|
var vis = rootSvg.append("svg:g");
|
||||||
.append("svg:g")
|
|
||||||
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
|
|
||||||
|
|
||||||
var formatComment = this.formatComment_;
|
var formatComment = this.formatComment_;
|
||||||
var formatTime = this.formatTime_;
|
var formatTime = this.formatTime_;
|
||||||
|
@ -101,6 +145,10 @@ ImageHistoryTree.prototype.draw = function(container) {
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!d.image) {
|
||||||
|
return '(This repository is empty)';
|
||||||
|
}
|
||||||
|
|
||||||
if (d.image.comment) {
|
if (d.image.comment) {
|
||||||
html += '<span class="comment">' + formatComment(d.image.comment) + '</span>';
|
html += '<span class="comment">' + formatComment(d.image.comment) + '</span>';
|
||||||
}
|
}
|
||||||
|
@ -112,26 +160,34 @@ ImageHistoryTree.prototype.draw = function(container) {
|
||||||
vis.call(tip);
|
vis.call(tip);
|
||||||
|
|
||||||
// Save all the state created.
|
// Save all the state created.
|
||||||
this.fullWidth_ = width;
|
|
||||||
|
|
||||||
this.width_ = w;
|
|
||||||
this.height_ = h;
|
|
||||||
|
|
||||||
this.diagonal_ = diagonal;
|
this.diagonal_ = diagonal;
|
||||||
this.vis_ = vis;
|
this.vis_ = vis;
|
||||||
|
this.rootSvg_ = rootSvg;
|
||||||
this.tip_ = tip;
|
this.tip_ = tip;
|
||||||
|
|
||||||
this.tree_ = tree;
|
this.tree_ = tree;
|
||||||
|
|
||||||
|
// Update the dimensions of the tree.
|
||||||
|
var dimensions = this.updateDimensions_();
|
||||||
|
|
||||||
// Populate the tree.
|
// Populate the tree.
|
||||||
this.root_.x0 = this.fullWidth_ / 2;
|
this.root_.x0 = dimensions.cw / 2;
|
||||||
this.root_.y0 = 0;
|
this.root_.y0 = 0;
|
||||||
|
|
||||||
this.setTag_(this.currentTag_);
|
this.setTag_(this.currentTag_);
|
||||||
|
|
||||||
$('#' + container).overscroll();
|
$('#' + container).overscroll();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redraws the image history to fit the new size.
|
||||||
|
*/
|
||||||
|
ImageHistoryTree.prototype.notifyResized = function() {
|
||||||
|
this.updateDimensions_();
|
||||||
|
this.update_(this.root_);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the current tag displayed in the tree.
|
* Sets the current tag displayed in the tree.
|
||||||
*/
|
*/
|
||||||
|
@ -198,7 +254,7 @@ ImageHistoryTree.prototype.buildRoot_ = function() {
|
||||||
// "name": "...",
|
// "name": "...",
|
||||||
// "children": [...]
|
// "children": [...]
|
||||||
// }
|
// }
|
||||||
var formatted = {};
|
var formatted = {"name": "No images found"};
|
||||||
|
|
||||||
// Build a node for each image.
|
// Build a node for each image.
|
||||||
var imageByDBID = {};
|
var imageByDBID = {};
|
||||||
|
@ -266,8 +322,10 @@ ImageHistoryTree.prototype.buildRoot_ = function() {
|
||||||
*/
|
*/
|
||||||
ImageHistoryTree.prototype.determineMaximumHeight_ = function(node) {
|
ImageHistoryTree.prototype.determineMaximumHeight_ = function(node) {
|
||||||
var maxHeight = 0;
|
var maxHeight = 0;
|
||||||
for (var i = 0; i < node.children.length; ++i) {
|
if (node.children) {
|
||||||
maxHeight = Math.max(this.determineMaximumHeight_(node.children[i]), maxHeight);
|
for (var i = 0; i < node.children.length; ++i) {
|
||||||
|
maxHeight = Math.max(this.determineMaximumHeight_(node.children[i]), maxHeight);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return maxHeight + 1;
|
return maxHeight + 1;
|
||||||
};
|
};
|
||||||
|
@ -292,7 +350,7 @@ ImageHistoryTree.prototype.collapseNodes_ = function(node) {
|
||||||
if (encountered.length >= 3) {
|
if (encountered.length >= 3) {
|
||||||
// Collapse the node.
|
// Collapse the node.
|
||||||
var collapsed = {
|
var collapsed = {
|
||||||
"name": '(' + encountered.length + ' images)',
|
"name": '(' + (encountered.length - 1) + ' images)',
|
||||||
"children": [current],
|
"children": [current],
|
||||||
"collapsed": true,
|
"collapsed": true,
|
||||||
"encountered": encountered
|
"encountered": encountered
|
||||||
|
@ -361,21 +419,36 @@ ImageHistoryTree.prototype.markPath_ = function(startingNode, isHighlighted) {
|
||||||
* Sets the current tag displayed in the tree.
|
* Sets the current tag displayed in the tree.
|
||||||
*/
|
*/
|
||||||
ImageHistoryTree.prototype.setTag_ = function(tagName) {
|
ImageHistoryTree.prototype.setTag_ = function(tagName) {
|
||||||
|
if (tagName == this.currentTag_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var imageByDBID = this.imageByDBID_;
|
||||||
|
|
||||||
|
// Save the current tag.
|
||||||
|
var previousTagName = this.currentTag_;
|
||||||
this.currentTag_ = tagName;
|
this.currentTag_ = tagName;
|
||||||
|
|
||||||
// Update the state of each existing node to no longer be highlighted.
|
// Update the state of each existing node to no longer be highlighted.
|
||||||
var imageByDBID = this.imageByDBID_;
|
var previousImage = this.findImage_(function(image) {
|
||||||
var currentNode = imageByDBID[this.currentImage_.dbid];
|
return image.tags.indexOf(previousTagName || '(no tag specified)') >= 0;
|
||||||
this.markPath_(currentNode, false);
|
});
|
||||||
|
|
||||||
// Find the new current image.
|
if (previousImage) {
|
||||||
|
var currentNode = imageByDBID[previousImage.dbid];
|
||||||
|
this.markPath_(currentNode, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the new current image (if any).
|
||||||
this.currentImage_ = this.findImage_(function(image) {
|
this.currentImage_ = this.findImage_(function(image) {
|
||||||
return image.tags.indexOf(tagName) >= 0;
|
return image.tags.indexOf(tagName || '(no tag specified)') >= 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update the state of the new node path.
|
// Update the state of the new node path.
|
||||||
currentNode = imageByDBID[this.currentImage_.dbid];
|
if (this.currentImage_) {
|
||||||
this.markPath_(currentNode, true);
|
var currentNode = imageByDBID[this.currentImage_.dbid];
|
||||||
|
this.markPath_(currentNode, true);
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure that the children are in the correct order.
|
// Ensure that the children are in the correct order.
|
||||||
for (var i = 0; i < this.images_.length; ++i) {
|
for (var i = 0; i < this.images_.length; ++i) {
|
||||||
|
@ -432,6 +505,7 @@ ImageHistoryTree.prototype.update_ = function(source) {
|
||||||
var diagonal = this.diagonal_;
|
var diagonal = this.diagonal_;
|
||||||
var tip = this.tip_;
|
var tip = this.tip_;
|
||||||
var currentTag = this.currentTag_;
|
var currentTag = this.currentTag_;
|
||||||
|
var currentImage = this.currentImage_;
|
||||||
var repoNamespace = this.repoNamespace_;
|
var repoNamespace = this.repoNamespace_;
|
||||||
var repoName = this.repoName_;
|
var repoName = this.repoName_;
|
||||||
var maxHeight = this.maxHeight_;
|
var maxHeight = this.maxHeight_;
|
||||||
|
@ -469,7 +543,7 @@ ImageHistoryTree.prototype.update_ = function(source) {
|
||||||
.attr("dy", ".35em")
|
.attr("dy", ".35em")
|
||||||
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
|
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
|
||||||
.text(function(d) { return d.name; })
|
.text(function(d) { return d.name; })
|
||||||
.on("click", function(d) { that.changeImage_(d.image.id); })
|
.on("click", function(d) { if (d.image) { that.changeImage_(d.image.id); } })
|
||||||
.on('mouseover', tip.show)
|
.on('mouseover', tip.show)
|
||||||
.on('mouseout', tip.hide);
|
.on('mouseout', tip.hide);
|
||||||
|
|
||||||
|
@ -520,7 +594,10 @@ ImageHistoryTree.prototype.update_ = function(source) {
|
||||||
if (d.collapsed) {
|
if (d.collapsed) {
|
||||||
return 'collapsed';
|
return 'collapsed';
|
||||||
}
|
}
|
||||||
return d.image.id == that.currentImage_.id ? 'current' : '';
|
if (!currentImage) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return d.image.id == currentImage.id ? 'current' : '';
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure that the node is visible.
|
// Ensure that the node is visible.
|
||||||
|
|
|
@ -1,10 +1,20 @@
|
||||||
<div class="container ready-indicator" data-status="{{ status }}">
|
<div class="container ready-indicator" data-status="{{ status }}">
|
||||||
<div class="alert alert-warning">Warning: Quay requires docker version 0.6.2 or higher to work</div>
|
<div class="alert alert-warning">Warning: Quay requires docker version 0.6.2 or higher to work</div>
|
||||||
|
|
||||||
<h2>Getting started guide</h2>
|
<h2>User guide</h2>
|
||||||
<div class="container">
|
<div class="user-guide container">
|
||||||
|
|
||||||
<h3>Pushing a repository to Quay</h3>
|
<h3>Pulling a repository from Quay</h3>
|
||||||
|
<div class="container">
|
||||||
|
<div class="alert alert-info">Note: <b>Private</b> repositories require you to be <b>logged in</b> or the pull will fail. See below for how to sign into Quay if you have never done so before. </div>
|
||||||
|
To pull a repository from Quay, run the following command:
|
||||||
|
<br><br>
|
||||||
|
<pre>docker pull quay.io/<i>username/repo_name</i></pre>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
|
||||||
|
<h3>Pushing a repository to Quay <span class="label label-success">Requires Write Access</span></h3>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
First, tag the image with your repository name:<br><br>
|
First, tag the image with your repository name:<br><br>
|
||||||
<pre>docker tag <i>0u123imageid</i> quay.io/<i>username/repo_name</i></pre>
|
<pre>docker tag <i>0u123imageid</i> quay.io/<i>username/repo_name</i></pre>
|
||||||
|
@ -14,12 +24,41 @@
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<h3>Pulling a repository from Quay</h3>
|
<h3>Granting and managing permissions to users <span class="label label-info">Requires Admin Access</span></h3>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="alert alert-info">Note: <b>Private</b> repositories require you to be <b>logged in</b> or the pull will fail. See below for how to sign into Quay if you have never done so before. </div>
|
<div class="description-overview">Quay allows a repository to be shared any number of users and to grant those users any level of permissions for a repository</div>
|
||||||
To pull a repository from Quay, run the following command:
|
|
||||||
<br><br>
|
<ul class="description-list">
|
||||||
<pre>docker pull quay.io/<i>username/repo_name</i></pre>
|
<li>Permissions for a repository can be granted and managed in the repository's admin interface
|
||||||
|
<li><b>Adding a user:</b> Type that user's username in the "Add New User..." field, and select the user
|
||||||
|
<li><b>Changing permissions:</b> A user's permissions (read, read/write or admin) can be changed by clicking the field to the right of the user
|
||||||
|
<li><b>Removing a user:</b> A user can be removed from the list by clicking the <b>X</b> and then clicking <b>Delete</b>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<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
|
||||||
|
permissions, without any passwords.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="description-list">
|
||||||
|
<li>Tokens can be managed in the repository's admin interface
|
||||||
|
<li><b>Adding a token:</b> Enter a user-readable description in the "New token description" field
|
||||||
|
<li><b>Changing permissions:</b> A token's permissions (read or read/write) can be changed by clicking the field to the right of the token
|
||||||
|
<li><b>Deleting a token:</b> A token can be deleted by clicking the <b>X</b> and then clicking <b>Delete</b>
|
||||||
|
<li><b>Using a token:</b> To use the token, the following credentials can be used:
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
<dt>Username</dt><dd>$token</dd>
|
||||||
|
<dt>Password</dt><dd>(token value can be found by clicking on the token)</dd>
|
||||||
|
<dt>Email</dt><dd>This value is ignored, any value may be used.</dd>
|
||||||
|
</dl>
|
||||||
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<div class="collapse navbar-collapse navbar-ex1-collapse">
|
<div class="collapse navbar-collapse navbar-ex1-collapse">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
<li><a ng-href="/repository/">Repositories</a></li>
|
<li><a ng-href="/repository/">Repositories</a></li>
|
||||||
<li><a ng-href="/guide/">Getting Started</a></li>
|
<li><a ng-href="/guide/">User Guide</a></li>
|
||||||
<li><a ng-href="/plans/">Plans & Pricing</a></li>
|
<li><a ng-href="/plans/">Plans & Pricing</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@ -50,4 +50,4 @@
|
||||||
<a href="/signin/">Sign in</a>
|
<a href="/signin/">Sign in</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div><!-- /.navbar-collapse -->
|
</div><!-- /.navbar-collapse -->
|
||||||
|
|
|
@ -16,7 +16,10 @@
|
||||||
|
|
||||||
<!-- User Access Permissions -->
|
<!-- User Access Permissions -->
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">User Access Permissions</div>
|
<div class="panel-heading">User Access Permissions
|
||||||
|
|
||||||
|
<i class="info-icon icon-info-sign" data-placement="left" data-content="Allow any number of users to read, write or administer this repository"></i>
|
||||||
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
|
|
||||||
<table class="permissions">
|
<table class="permissions">
|
||||||
|
@ -59,48 +62,50 @@
|
||||||
|
|
||||||
<!-- Token Permissions -->
|
<!-- Token Permissions -->
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">Access Token Permissions</div>
|
<div class="panel-heading">Access Token Permissions
|
||||||
<div class="panel-body">
|
|
||||||
|
|
||||||
<table class="permissions">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td>Token</td>
|
|
||||||
<td>Permissions</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tr ng-repeat="(code, token) in tokens">
|
|
||||||
<td class="user">
|
|
||||||
<i class="icon-key"></i>
|
|
||||||
<a ng-click="showToken(token.code)">{{ token.friendlyName }}</a>
|
|
||||||
</td>
|
|
||||||
<td class="user-permissions">
|
|
||||||
<div class="btn-group btn-group-sm">
|
|
||||||
<button type="button" class="btn btn-default" ng-click="changeTokenAccess(token.code, 'read')" ng-class="{read: 'active', write: ''}[token.role]">Read only</button>
|
|
||||||
<button type="button" class="btn btn-default" ng-click="changeTokenAccess(token.code, 'write')" ng-class="{read: '', write: 'active'}[token.role]">Write</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span class="delete-ui" tabindex="0" title="Delete Token">
|
|
||||||
<span class="delete-ui-button" ng-click="deleteToken(token.code)"><button class="btn btn-danger">Delete</button></span>
|
|
||||||
<i class="icon-remove"></i>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
<i class="info-icon icon-info-sign" data-placement="left" data-content="Grant permissions to this repository by creating unique tokens that can be used without entering account passwords<br><br>To use in docker:<br><dl class='dl-horizontal'><dt>Username</dt><dd>$token</dd><dt>Password</dt><dd>(token value)</dd><dt>Email</dt><dd>(any value)</dd></dl>"></i>
|
||||||
<form name="createTokenForm" ng-submit="createToken()">
|
</div>
|
||||||
<td>
|
<div class="panel-body">
|
||||||
<input class="form-control" placeholder="New token friendly name..." ng-model="newToken.friendlyName">
|
<form name="createTokenForm" ng-submit="createToken()">
|
||||||
|
<table class="permissions">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Token Description</td>
|
||||||
|
<td>Permissions</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tr ng-repeat="(code, token) in tokens">
|
||||||
|
<td class="user token">
|
||||||
|
<i class="icon-key"></i>
|
||||||
|
<a ng-click="showToken(token.code)">{{ token.friendlyName }}</a>
|
||||||
|
</td>
|
||||||
|
<td class="user-permissions">
|
||||||
|
<div class="btn-group btn-group-sm">
|
||||||
|
<button type="button" class="btn btn-default" ng-click="changeTokenAccess(token.code, 'read')" ng-class="{read: 'active', write: ''}[token.role]">Read only</button>
|
||||||
|
<button type="button" class="btn btn-default" ng-click="changeTokenAccess(token.code, 'write')" ng-class="{read: '', write: 'active'}[token.role]">Write</button>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button type="submit" class="btn btn-sm btn-default">Create</button>
|
<span class="delete-ui" tabindex="0" title="Delete Token">
|
||||||
|
<span class="delete-ui-button" ng-click="deleteToken(token.code)"><button class="btn btn-danger" type="button">Delete</button></span>
|
||||||
|
<i class="icon-remove"></i>
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</form>
|
</tr>
|
||||||
</tr>
|
|
||||||
</table>
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="text" class="form-control" placeholder="New token description" ng-model="newToken.friendlyName"required>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button type="submit" ng-disabled="createTokenForm.$invalid" class="btn btn-sm btn-default">Create</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -166,12 +171,19 @@
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
<h4 class="modal-title">Token code for {{ shownToken.friendlyName }}</h4>
|
<h4 class="modal-title"><i class="icon-key"></i> {{ shownToken.friendlyName }}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body token-dialog-body">
|
||||||
{{ shownToken.code }}
|
<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>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
<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>
|
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- /.modal-content -->
|
</div><!-- /.modal-content -->
|
||||||
|
|
20
static/partials/v1-page.html
Normal file
20
static/partials/v1-page.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<div class="container">
|
||||||
|
<div ng-show="!user.anonymous && user.verified">
|
||||||
|
<h3>Welcome <b>{{ user.username }}</b>. Your account is fully activated!</h3>
|
||||||
|
|
||||||
|
<div style="margin-top: 20px;">
|
||||||
|
<button class="btn btn-lg btn-primary" ng-click="browseRepos()">Browse all repositories</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ng-show="!user.anonymous && !user.verified">
|
||||||
|
<h3>Welcome <b>{{ user.username }}</b>. Your account is pending email confirmation.</h3>
|
||||||
|
<p>Please check your inbox (and potentially your spam folder) for an email from <a href="mailto:support@quay.io">support@quay.io</a></p>
|
||||||
|
</div>
|
||||||
|
<div ng-show="user.anonymous">
|
||||||
|
<h3>Docker sent me here. What do I need to do?</h3>
|
||||||
|
<p>
|
||||||
|
<a href="/signin">Sign In</a> to check the activation status of your account. If you have already activated
|
||||||
|
your account previously, then you do not need to do anything further.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -37,7 +37,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="clipboardCopied" style="display: none">
|
<div id="clipboardCopied" class="hovering" style="display: none">
|
||||||
Copied to clipboard
|
Copied to clipboard
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -83,7 +83,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tree View itself -->
|
<!-- Tree View itself -->
|
||||||
<div id="image-history-container"></div>
|
<div id="image-history-container" onresize="tree.notifyResized()"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
BIN
test.db
BIN
test.db
Binary file not shown.
Reference in a new issue