Merge branch 'master' into looksirdroids
This commit is contained in:
commit
43f2dd80a0
38 changed files with 752 additions and 400 deletions
|
@ -1,5 +1,35 @@
|
|||
* {
|
||||
font-family: 'Droid Sans', sans-serif;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
word-break: normal !important;
|
||||
word-wrap: normal !important;
|
||||
}
|
||||
|
||||
.code-info {
|
||||
border-bottom: 1px dashed #aaa;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
padding-bottom: 70px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
min-height: 100%;
|
||||
height: auto !important;
|
||||
height: 100%;
|
||||
margin: 0 auto -136px;
|
||||
}
|
||||
|
||||
.footer-container, .push {
|
||||
height: 110px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.button-hidden {
|
||||
|
@ -512,6 +542,15 @@
|
|||
padding: 20px;
|
||||
}
|
||||
|
||||
.jumbotron .disclaimer-link {
|
||||
font-size: .3em;
|
||||
vertical-align: 23px;
|
||||
}
|
||||
|
||||
.jumbotron .disclaimer-link:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.landing .popover {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
@ -644,57 +683,61 @@ form input.ng-valid.ng-dirty,
|
|||
}
|
||||
}
|
||||
|
||||
.landing-footer {
|
||||
.page-footer {
|
||||
background-color: white;
|
||||
background-image: none;
|
||||
padding: 10px;
|
||||
padding-bottom: 40px;
|
||||
margin-top: 10px;
|
||||
margin-top: 52px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.landing-footer .row {
|
||||
.page-footer .row {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.landing-footer .copyright-container {
|
||||
.page-footer .logo-container {
|
||||
font-size: 11px;
|
||||
color: black;
|
||||
text-align: right;
|
||||
padding-right: 30px;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.landing-footer .copyright-container a {
|
||||
.page-footer .logo-container a {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.landing-footer .dt-logo {
|
||||
.page-footer .dt-logo {
|
||||
vertical-align: center;
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.landing-footer .copyright {
|
||||
font-size: 11px;
|
||||
.page-footer .copyright {
|
||||
font-size: 12px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.landing-footer ul {
|
||||
.page-footer ul {
|
||||
list-style-type: none;
|
||||
margin: 0px;
|
||||
padding-left: 0px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.landing-footer li {
|
||||
.page-footer li {
|
||||
margin: 0px;
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.user-welcome {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#repoSearch {
|
||||
width: 300px;
|
||||
.repo-search-box {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.repo-mini-listing {
|
||||
|
@ -718,7 +761,6 @@ form input.ng-valid.ng-dirty,
|
|||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
|
||||
.entity-mini-listing {
|
||||
margin: 2px;
|
||||
}
|
||||
|
@ -827,7 +869,13 @@ p.editable:hover i {
|
|||
.repo .empty-message {
|
||||
padding: 6px;
|
||||
font-size: 1.8em;
|
||||
color: #ccc;
|
||||
color: #666;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.repo .empty-description {
|
||||
max-width: 600px;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.repo dl.dl-horizontal dt {
|
||||
|
@ -1067,6 +1115,12 @@ p.editable:hover i {
|
|||
padding-left: 44px;
|
||||
}
|
||||
|
||||
.repo-admin .right-info {
|
||||
font-size: 11px;
|
||||
margin-top: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.repo-admin .entity-search input {
|
||||
width: 300px;
|
||||
}
|
||||
|
|
|
@ -36,4 +36,22 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="cannotchangecardModal">
|
||||
<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">Cannot change credit card</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Your credit card could not be changed
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
</div>
|
||||
|
|
|
@ -22,15 +22,17 @@
|
|||
|
||||
|
||||
<ul class="nav navbar-nav navbar-right" ng-switch on="user.anonymous">
|
||||
<form class="navbar-form navbar-left" role="search">
|
||||
<div class="form-group">
|
||||
<input id="repoSearch" type="text" class="form-control" placeholder="Find Repo">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<span class="navbar-left user-tools" ng-show="!user.anonymous">
|
||||
<a href="/new/"><i class="fa fa-upload user-tool" bs-tooltip="tooltip.title" data-placement="bottom" title="Create new repository"></i></a>
|
||||
</span>
|
||||
<li>
|
||||
<form class="navbar-form navbar-left" role="search">
|
||||
<div class="form-group">
|
||||
<span class="repo-search"></span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<span class="navbar-left user-tools" ng-show="!user.anonymous">
|
||||
<a href="/new/"><i class="fa fa-upload user-tool" bs-tooltip="tooltip.title" data-placement="bottom" title="Create new repository"></i></a>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li class="dropdown" ng-switch-when="false">
|
||||
<a href="javascript:void(0)" class="dropdown-toggle user-dropdown" data-toggle="dropdown">
|
|
@ -1 +1 @@
|
|||
<span class="markdown-view-content" ng-bind-html-unsafe="getMarkedDown(content, firstLineOnly)"></span>
|
||||
<span class="markdown-view-content" ng-bind-html="getMarkedDown(content, firstLineOnly)"></span>
|
||||
|
|
1
static/directives/repo-search.html
Normal file
1
static/directives/repo-search.html
Normal file
|
@ -0,0 +1 @@
|
|||
<input type="text" class="form-control repo-search-box" placeholder="Find Repo">
|
207
static/js/app.js
207
static/js/app.js
|
@ -47,7 +47,7 @@ function getMarkedDown(string) {
|
|||
}
|
||||
|
||||
// Start the application code itself.
|
||||
quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics', 'angulartics.mixpanel', '$strap.directives', 'ngCookies'], function($provide) {
|
||||
quayApp = angular.module('quay', ['ngRoute', 'restangular', 'angularMoment', 'angulartics', 'angulartics.mixpanel', '$strap.directives', 'ngCookies'], function($provide) {
|
||||
$provide.factory('UserService', ['Restangular', function(Restangular) {
|
||||
var userResponse = {
|
||||
verified: false,
|
||||
|
@ -137,7 +137,26 @@ quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics',
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
planService.handleCardError = function(resp) {
|
||||
if (!planService.isCardError(resp)) { return; }
|
||||
|
||||
bootbox.dialog({
|
||||
"message": resp.data.carderror,
|
||||
"title": "Credit card issue",
|
||||
"buttons": {
|
||||
"close": {
|
||||
"label": "Close",
|
||||
"className": "btn-primary"
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
planService.isCardError = function(resp) {
|
||||
return resp && resp.data && resp.data.carderror;
|
||||
};
|
||||
|
||||
planService.verifyLoaded = function(callback) {
|
||||
if (plans) {
|
||||
callback(plans);
|
||||
|
@ -259,7 +278,10 @@ quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics',
|
|||
return;
|
||||
}
|
||||
|
||||
planService.setSubscription(orgname, planId, callbacks['success'], callbacks['failure']);
|
||||
planService.setSubscription(orgname, planId, callbacks['success'], function(resp) {
|
||||
planService.handleCardError(resp);
|
||||
callbacks['failure'](resp);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -269,7 +291,10 @@ quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics',
|
|||
callbacks['opening']();
|
||||
}
|
||||
|
||||
var submitted = false;
|
||||
var submitToken = function(token) {
|
||||
if (submitted) { return; }
|
||||
submitted = true;
|
||||
$scope.$apply(function() {
|
||||
if (callbacks['started']) {
|
||||
callbacks['started']();
|
||||
|
@ -281,7 +306,10 @@ quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics',
|
|||
|
||||
var url = orgname ? getRestUrl('organization', orgname, 'card') : 'user/card';
|
||||
var changeCardRequest = Restangular.one(url);
|
||||
changeCardRequest.customPOST(cardInfo).then(callbacks['success'], callbacks['failure']);
|
||||
changeCardRequest.customPOST(cardInfo).then(callbacks['success'], function(resp) {
|
||||
planService.handleCardError(resp);
|
||||
callbacks['failure'](resp);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -321,7 +349,11 @@ quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics',
|
|||
callbacks['opening']();
|
||||
}
|
||||
|
||||
var submitted = false;
|
||||
var submitToken = function(token) {
|
||||
if (submitted) { return; }
|
||||
submitted = true;
|
||||
|
||||
mixpanel.track('plan_subscribe');
|
||||
|
||||
$scope.$apply(function() {
|
||||
|
@ -395,26 +427,31 @@ quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics',
|
|||
// index rule to make sure that deep links directly deep into the app continue to work.
|
||||
// WARNING WARNING WARNING
|
||||
$routeProvider.
|
||||
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', {templateUrl: '/static/partials/view-repo.html', controller: RepoCtrl,
|
||||
hideFooter: true, reloadOnSearch: false}).
|
||||
when('/repository/:namespace/:name/tag/:tag', {templateUrl: '/static/partials/view-repo.html', controller: RepoCtrl,
|
||||
hideFooter: true}).
|
||||
when('/repository/:namespace/:name/image/:image', {templateUrl: '/static/partials/image-view.html', controller: ImageViewCtrl}).
|
||||
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('/user/', {title: 'Account Settings', templateUrl: '/static/partials/user-admin.html', controller: UserAdminCtrl}).
|
||||
when('/guide/', {title: 'Guide', templateUrl: '/static/partials/guide.html', controller: GuideCtrl}).
|
||||
when('/plans/', {title: 'Plans and Pricing', templateUrl: '/static/partials/plans.html', controller: PlansCtrl}).
|
||||
when('/signin/', {title: 'Sign In', templateUrl: '/static/partials/signin.html', controller: SigninCtrl}).
|
||||
when('/new/', {title: 'Create new repository', templateUrl: '/static/partials/new-repo.html', controller: NewRepoCtrl}).
|
||||
|
||||
when('/organizations/', {title: 'Organizations', templateUrl: '/static/partials/organizations.html', controller: OrgsCtrl}).
|
||||
when('/organizations/new/', {title: 'New Organization', templateUrl: '/static/partials/new-organization.html', controller: NewOrgCtrl}).
|
||||
|
||||
when('/repository/', {title: 'Repositories', description: 'Public and private docker repositories list',
|
||||
templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl}).
|
||||
when('/user/', {title: 'Account Settings', description:'Account settings for Quay.io', templateUrl: '/static/partials/user-admin.html', controller: UserAdminCtrl}).
|
||||
when('/guide/', {title: 'Guide', description:'Guide to using private docker repositories on Quay.io', templateUrl: '/static/partials/guide.html', controller: GuideCtrl}).
|
||||
when('/plans/', {title: 'Plans and Pricing', description: 'Plans and pricing for private docker repositories on Quay.io',
|
||||
templateUrl: '/static/partials/plans.html', controller: PlansCtrl}).
|
||||
when('/security/', {title: 'Security', description: 'Security features used when transmitting and storing data',
|
||||
templateUrl: '/static/partials/security.html', controller: SecurityCtrl}).
|
||||
when('/signin/', {title: 'Sign In', description: 'Sign into Quay.io', templateUrl: '/static/partials/signin.html', controller: SigninCtrl}).
|
||||
when('/new/', {title: 'Create new repository', description: 'Create a new public or private docker repository, optionally constructing from a dockerfile',
|
||||
templateUrl: '/static/partials/new-repo.html', controller: NewRepoCtrl}).
|
||||
when('/organizations/', {title: 'Organizations', description: 'Private docker repository hosting for businesses and organizations',
|
||||
templateUrl: '/static/partials/organizations.html', controller: OrgsCtrl}).
|
||||
when('/organizations/new/', {title: 'New Organization', description: 'Create a new organization on Quay.io',
|
||||
templateUrl: '/static/partials/new-organization.html', controller: NewOrgCtrl}).
|
||||
when('/organization/:orgname', {templateUrl: '/static/partials/org-view.html', controller: OrgViewCtrl}).
|
||||
when('/organization/:orgname/admin', {templateUrl: '/static/partials/org-admin.html', controller: OrgAdminCtrl}).
|
||||
when('/organization/:orgname/teams/:teamname', {templateUrl: '/static/partials/team-view.html', controller: TeamViewCtrl}).
|
||||
|
||||
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}).
|
||||
otherwise({redirectTo: '/'});
|
||||
}]).
|
||||
|
@ -434,12 +471,12 @@ quayApp.directive('markdownView', function () {
|
|||
'content': '=content',
|
||||
'firstLineOnly': '=firstLineOnly'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
controller: function($scope, $element, $sce) {
|
||||
$scope.getMarkedDown = function(content, firstLineOnly) {
|
||||
if (firstLineOnly) {
|
||||
content = getFirstTextLine(content);
|
||||
}
|
||||
return getMarkedDown(content);
|
||||
return $sce.trustAsHtml(getMarkedDown(content));
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -661,6 +698,103 @@ quayApp.directive('markdownInput', function () {
|
|||
});
|
||||
|
||||
|
||||
quayApp.directive('repoSearch', function () {
|
||||
var number = 0;
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/repo-search.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
},
|
||||
controller: function($scope, $element, $location, UserService, Restangular) {
|
||||
var searchToken = 0;
|
||||
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
||||
++searchToken;
|
||||
}, true);
|
||||
|
||||
var element = $($element[0].childNodes[0]);
|
||||
element.typeahead({
|
||||
name: 'repositories',
|
||||
remote: {
|
||||
url: '/api/find/repository?query=%QUERY',
|
||||
replace: function (url, uriEncodedQuery) {
|
||||
url = url.replace('%QUERY', uriEncodedQuery);
|
||||
url += '&cb=' + searchToken;
|
||||
return url;
|
||||
},
|
||||
filter: function(data) {
|
||||
var datums = [];
|
||||
for (var i = 0; i < data.repositories.length; ++i) {
|
||||
var repo = data.repositories[i];
|
||||
datums.push({
|
||||
'value': repo.name,
|
||||
'tokens': [repo.name, repo.namespace],
|
||||
'repo': repo
|
||||
});
|
||||
}
|
||||
return datums;
|
||||
}
|
||||
},
|
||||
template: function (datum) {
|
||||
template = '<div class="repo-mini-listing">';
|
||||
template += '<i class="fa fa-hdd fa-lg"></i>'
|
||||
template += '<span class="name">' + datum.repo.namespace +'/' + datum.repo.name + '</span>'
|
||||
if (datum.repo.description) {
|
||||
template += '<span class="description">' + getFirstTextLine(datum.repo.description) + '</span>'
|
||||
}
|
||||
|
||||
template += '</div>'
|
||||
return template;
|
||||
}
|
||||
});
|
||||
|
||||
element.on('typeahead:selected', function (e, datum) {
|
||||
element.typeahead('setQuery', '');
|
||||
document.location = '/repository/' + datum.repo.namespace + '/' + datum.repo.name;
|
||||
});
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
||||
|
||||
quayApp.directive('headerBar', function () {
|
||||
var number = 0;
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/header-bar.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
},
|
||||
controller: function($scope, $element, $location, UserService, Restangular) {
|
||||
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
||||
$scope.user = currentUser;
|
||||
}, true);
|
||||
|
||||
$scope.signout = function() {
|
||||
var signoutPost = Restangular.one('signout');
|
||||
signoutPost.customPOST().then(function() {
|
||||
UserService.load();
|
||||
$location.path('/');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.appLinkTarget = function() {
|
||||
if ($("div[ng-view]").length === 0) {
|
||||
return "_self";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
||||
|
||||
quayApp.directive('entitySearch', function () {
|
||||
var number = 0;
|
||||
var directiveDefinitionObject = {
|
||||
|
@ -795,6 +929,7 @@ quayApp.directive('billingOptions', function () {
|
|||
});
|
||||
|
||||
$scope.changeCard = function() {
|
||||
var previousCard = $scope.currentCard;
|
||||
$scope.changingCard = true;
|
||||
var callbacks = {
|
||||
'opened': function() { $scope.changingCard = true; },
|
||||
|
@ -804,9 +939,13 @@ quayApp.directive('billingOptions', function () {
|
|||
$scope.currentCard = resp.card;
|
||||
$scope.changingCard = false;
|
||||
},
|
||||
'failure': function() {
|
||||
$('#couldnotchangecardModal').modal({});
|
||||
'failure': function(resp) {
|
||||
$scope.changingCard = false;
|
||||
$scope.currentCard = previousCard;
|
||||
|
||||
if (!PlanService.isCardError(resp)) {
|
||||
$('#cannotchangecardModal').modal({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -897,7 +1036,9 @@ quayApp.directive('planManager', function () {
|
|||
'opened': function() { $scope.planChanging = true; },
|
||||
'closed': function() { $scope.planChanging = false; },
|
||||
'success': subscribedToPlan,
|
||||
'failure': function() { $scope.planChanging = false; }
|
||||
'failure': function(resp) {
|
||||
$scope.planChanging = false;
|
||||
}
|
||||
};
|
||||
|
||||
PlanService.changePlan($scope, $scope.organization, planId, callbacks);
|
||||
|
@ -1123,7 +1264,8 @@ quayApp.directive('ngBlur', function() {
|
|||
};
|
||||
});
|
||||
|
||||
quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', function($location, $rootScope, Restangular, UserService) {
|
||||
quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', '$http',
|
||||
function($location, $rootScope, Restangular, UserService, $http) {
|
||||
Restangular.setErrorInterceptor(function(response) {
|
||||
if (response.status == 401) {
|
||||
$('#sessionexpiredModal').modal({});
|
||||
|
@ -1137,5 +1279,22 @@ quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', function($
|
|||
if (current.$$route.title) {
|
||||
$rootScope.title = current.$$route.title;
|
||||
}
|
||||
|
||||
if (current.$$route.description) {
|
||||
$rootScope.description = current.$$route.description;
|
||||
} else {
|
||||
$rootScope.description = 'Hosted private docker repositories. Includes full user management and history. Free for public repositories.';
|
||||
}
|
||||
|
||||
$rootScope.hideFooter = !!current.$$route.hideFooter;
|
||||
});
|
||||
|
||||
var initallyChecked = false;
|
||||
window.__isLoading = function() {
|
||||
if (!initallyChecked) {
|
||||
initallyChecked = true;
|
||||
return true;
|
||||
}
|
||||
return $http.pendingRequests.length > 0;
|
||||
};
|
||||
}]);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
$.fn.clipboardCopy = function() {
|
||||
var clip = new ZeroClipboard($(this), { 'moviePath': 'static/lib/ZeroClipboard.swf' });
|
||||
var clip = new ZeroClipboard($(this), { 'moviePath': 'static/lib/ZeroClipboard.swf' });
|
||||
|
||||
clip.on('complete', function() {
|
||||
// Resets the animation.
|
||||
var elem = $('#clipboardCopied')[0];
|
||||
|
@ -14,74 +15,6 @@ $.fn.clipboardCopy = function() {
|
|||
});
|
||||
};
|
||||
|
||||
function HeaderCtrl($scope, $location, UserService, Restangular) {
|
||||
var searchToken = 0;
|
||||
|
||||
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
||||
$scope.user = currentUser;
|
||||
++searchToken;
|
||||
}, true);
|
||||
|
||||
$scope.signout = function() {
|
||||
var signoutPost = Restangular.one('signout');
|
||||
signoutPost.customPOST().then(function() {
|
||||
UserService.load();
|
||||
$location.path('/');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.appLinkTarget = function() {
|
||||
if ($("div[ng-view]").length === 0) {
|
||||
return "_self";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
$scope.$on('$includeContentLoaded', function() {
|
||||
// THIS IS BAD, MOVE THIS TO A DIRECTIVE
|
||||
$('#repoSearch').typeahead({
|
||||
name: 'repositories',
|
||||
remote: {
|
||||
url: '/api/find/repository?query=%QUERY',
|
||||
replace: function (url, uriEncodedQuery) {
|
||||
url = url.replace('%QUERY', uriEncodedQuery);
|
||||
url += '&cb=' + searchToken;
|
||||
return url;
|
||||
},
|
||||
filter: function(data) {
|
||||
var datums = [];
|
||||
for (var i = 0; i < data.repositories.length; ++i) {
|
||||
var repo = data.repositories[i];
|
||||
datums.push({
|
||||
'value': repo.name,
|
||||
'tokens': [repo.name, repo.namespace],
|
||||
'repo': repo
|
||||
});
|
||||
}
|
||||
return datums;
|
||||
}
|
||||
},
|
||||
template: function (datum) {
|
||||
template = '<div class="repo-mini-listing">';
|
||||
template += '<i class="fa fa-hdd fa-lg"></i>'
|
||||
template += '<span class="name">' + datum.repo.namespace +'/' + datum.repo.name + '</span>'
|
||||
if (datum.repo.description) {
|
||||
template += '<span class="description">' + getFirstTextLine(datum.repo.description) + '</span>'
|
||||
}
|
||||
|
||||
template += '</div>'
|
||||
return template;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
$('#repoSearch').on('typeahead:selected', function (e, datum) {
|
||||
$('#repoSearch').typeahead('setQuery', '');
|
||||
document.location = '/repository/' + datum.repo.namespace + '/' + datum.repo.name
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function SigninCtrl($scope, $location, $timeout, Restangular, KeyService, UserService) {
|
||||
$scope.sendRecovery = function() {
|
||||
var signinPost = Restangular.one('recovery');
|
||||
|
@ -93,15 +26,12 @@ function SigninCtrl($scope, $location, $timeout, Restangular, KeyService, UserSe
|
|||
$scope.sent = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.status = 'ready';
|
||||
};
|
||||
|
||||
function PlansCtrl($scope, $location, UserService, PlanService) {
|
||||
// Load the list of plans.
|
||||
PlanService.getPlans(function(plans) {
|
||||
$scope.plans = plans;
|
||||
$scope.status = 'ready';
|
||||
});
|
||||
|
||||
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
||||
|
@ -126,7 +56,9 @@ function PlansCtrl($scope, $location, UserService, PlanService) {
|
|||
}
|
||||
|
||||
function GuideCtrl($scope) {
|
||||
$scope.status = 'ready';
|
||||
}
|
||||
|
||||
function SecurityCtrl($scope) {
|
||||
}
|
||||
|
||||
function RepoListCtrl($scope, Restangular, UserService) {
|
||||
|
@ -257,14 +189,18 @@ function LandingCtrl($scope, $timeout, $location, Restangular, UserService, KeyS
|
|||
});
|
||||
};
|
||||
|
||||
$scope.status = 'ready';
|
||||
|
||||
browserchrome.update();
|
||||
}
|
||||
|
||||
function RepoCtrl($scope, Restangular, $routeParams, $rootScope, $location, $timeout) {
|
||||
$rootScope.title = 'Loading...';
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
if ($scope.tree) {
|
||||
$scope.tree.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
// Watch for changes to the tag parameter.
|
||||
$scope.$on('$routeUpdate', function(){
|
||||
$scope.setTag($location.search().tag, false);
|
||||
|
@ -307,6 +243,11 @@ function RepoCtrl($scope, Restangular, $routeParams, $rootScope, $location, $tim
|
|||
var repositoryFetch = Restangular.one('repository/' + namespace + '/' + name);
|
||||
repositoryFetch.get().then(function(repo) {
|
||||
$rootScope.title = namespace + '/' + name;
|
||||
|
||||
var kind = repo.is_public ? 'public' : 'private';
|
||||
$rootScope.description = jQuery(getFirstTextLine(repo.description)).text() ||
|
||||
'View of a ' + kind + ' docker repository on Quay';
|
||||
|
||||
$scope.repo = repo;
|
||||
|
||||
$scope.setTag($routeParams.tag);
|
||||
|
@ -376,7 +317,7 @@ function RepoCtrl($scope, Restangular, $routeParams, $rootScope, $location, $tim
|
|||
|
||||
// Dispose of any existing tree.
|
||||
if ($scope.tree) {
|
||||
$scope.tree.dispose();
|
||||
$scope.tree.dispose();
|
||||
}
|
||||
|
||||
// Create the new tree.
|
||||
|
@ -665,6 +606,8 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
|
|||
var permissionsFetch = Restangular.one('repository/' + namespace + '/' + name + '/permissions/' + kind + '/');
|
||||
permissionsFetch.get().then(function(resp) {
|
||||
$rootScope.title = 'Settings - ' + namespace + '/' + name;
|
||||
$rootScope.description = 'Administrator settings for ' + namespace + '/' + name +
|
||||
': Permissions, web hooks and other settings';
|
||||
$scope.permissions[kind] = resp.permissions;
|
||||
checkLoading();
|
||||
}, function() {
|
||||
|
@ -889,6 +832,8 @@ function ImageViewCtrl($scope, $routeParams, $rootScope, Restangular) {
|
|||
};
|
||||
$scope.image = image;
|
||||
$rootScope.title = 'View Image - ' + image.id;
|
||||
$rootScope.description = 'Viewing docker image ' + image.id + ' under repository ' + namespace + '/' + name +
|
||||
': Image changes tree and list view';
|
||||
}, function() {
|
||||
$rootScope.title = 'Unknown Image';
|
||||
$scope.loading = false;
|
||||
|
@ -1067,13 +1012,17 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, Restangula
|
|||
|
||||
$scope.upgradePlan = function() {
|
||||
var callbacks = {
|
||||
'started': function() { $scope.planChanging = true; },
|
||||
'opened': function() { $scope.planChanging = true; },
|
||||
'closed': function() { $scope.planChanging = false; },
|
||||
'success': subscribedToPlan,
|
||||
'failure': function() { $('#couldnotsubscribeModal').modal(); $scope.planChanging = false; }
|
||||
'failure': function(resp) {
|
||||
$('#couldnotsubscribeModal').modal();
|
||||
$scope.planChanging = false;
|
||||
}
|
||||
};
|
||||
|
||||
PlanService.changePlan($scope, null, $scope.planRequired.stripeId, null, callbacks);
|
||||
PlanService.changePlan($scope, null, $scope.planRequired.stripeId, callbacks);
|
||||
};
|
||||
|
||||
// Watch the namespace on the repo. If it changes, we update the plan and the public/private
|
||||
|
@ -1127,6 +1076,7 @@ function OrgViewCtrl($rootScope, $scope, Restangular, $routeParams) {
|
|||
$scope.loading = false;
|
||||
|
||||
$rootScope.title = orgname;
|
||||
$rootScope.description = 'Viewing organization ' + orgname;
|
||||
}, function() {
|
||||
$scope.loading = false;
|
||||
});
|
||||
|
@ -1278,6 +1228,7 @@ function OrgAdminCtrl($rootScope, $scope, Restangular, $routeParams, UserService
|
|||
if (resp && resp.is_admin) {
|
||||
$scope.organization = resp;
|
||||
$rootScope.title = orgname + ' (Admin)';
|
||||
$rootScope.description = 'Administration page for organization ' + orgname;
|
||||
}
|
||||
|
||||
$scope.loading = false;
|
||||
|
@ -1355,6 +1306,7 @@ function TeamViewCtrl($rootScope, $scope, Restangular, $routeParams) {
|
|||
$scope.canEditMembers = resp.can_edit;
|
||||
$scope.loading = !$scope.organization || !$scope.members;
|
||||
$rootScope.title = teamname + ' (' + $scope.orgname + ')';
|
||||
$rootScope.description = 'Team management page for team ' + teamname + ' under organization ' + orgname;
|
||||
}, function() {
|
||||
$scope.organization = null;
|
||||
$scope.members = null;
|
||||
|
|
15
static/lib/ZeroClipboard.min.js
vendored
15
static/lib/ZeroClipboard.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,26 +1,38 @@
|
|||
<div class="container ready-indicator" data-status="{{ status }}">
|
||||
<div class="container content-container">
|
||||
<div class="alert alert-warning">Warning: Quay requires docker version 0.6.2 or higher to work</div>
|
||||
|
||||
<h2>User Guide</h2>
|
||||
<div class="user-guide container">
|
||||
|
||||
<h3>Pulling a repository from Quay</h3>
|
||||
<h3>Signing into Quay <span class="label label-default">Setup</span></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:
|
||||
To setup your Docker client for pushing to Quay, login with your credentials:
|
||||
<br><br>
|
||||
<pre>docker pull quay.io/<i>username/repo_name</i></pre>
|
||||
</div>
|
||||
<br>
|
||||
<pre>$ sudo docker login quay.io
|
||||
|
||||
Login against server at https://quay.io/v1/
|
||||
Username: myusername
|
||||
Password: mypassword
|
||||
Email: my@email.com</pre>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<h3>Pushing a repository to Quay <span class="label label-success">Requires Write Access</span></h3>
|
||||
<div class="container">
|
||||
First, tag the image with your repository name:<br><br>
|
||||
<pre>docker tag <i>0u123imageid</i> quay.io/<i>username/repo_name</i></pre>
|
||||
In order to push a repository to Quay, it must be <b>tagged</b> with the <b>quay.io</b> domain and the namespace under which it will live:
|
||||
<br><br>
|
||||
<pre>sudo docker tag <i>0u123imageid</i> quay.io/<i>username/repo_name</i></pre>
|
||||
<br>
|
||||
Second, push the repository to Quay:<br><br>
|
||||
<pre>docker push quay.io/<i>username/repo_name</i></pre>
|
||||
Once tagged, the repository can be pushed to Quay:<br><br>
|
||||
<pre>sudo docker push quay.io/<i>username/repo_name</i></pre>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<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 above 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>sudo docker pull quay.io/<i>username/repo_name</i></pre>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
|
@ -58,15 +70,28 @@
|
|||
<dt>Email</dt><dd>This value is ignored, any value may be used.</dd>
|
||||
</dl>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<h3>Signing into Quay <span class="label label-default">Optional</span></h3>
|
||||
<a name="#post-hook"></a>
|
||||
<h3>Using push web hooks <span class="label label-info">Requires Admin Access</span></h3>
|
||||
<div class="container">
|
||||
If you have never pushed a repository to Quay and wish to pull a <b>private</b> repository, you can sign into Quay by running the following command:
|
||||
<br><br>
|
||||
<pre>docker login quay.io</pre>
|
||||
A repository can have one or more <b>push web hooks</b> setup, which will be invoked whenever <u>a successful push occurs</u>. Web hooks can be managed from the repository's admin interface.
|
||||
<br><br> A web hook will be invoked
|
||||
as an HTTP <b>POST</b> to the specified URL, with a JSON body describing the push:<br><br>
|
||||
<pre>
|
||||
{
|
||||
<span class="code-info" title="The number of images pushed" bs-tooltip="tooltip.title">"pushed_image_count"</span>: 2,
|
||||
<span class="code-info" title="The name of the repository (without its namespace)" bs-tooltip="tooltip.title">"name"</span>: "ubuntu",
|
||||
<span class="code-info" title="The full name of the repository" bs-tooltip="tooltip.title">"repository"</span>:"devtable/ubuntu",
|
||||
<span class="code-info" title="The URL at which the repository can be pulled by Docker" bs-tooltip="tooltip.title">"docker_url"</span>: "quay.io/devtable/ubuntu",
|
||||
<span class="code-info" title="Map of updated tag names to their latest image IDs" bs-tooltip="tooltip.title">"updated_tags"</span>: {
|
||||
"latest": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc"
|
||||
},
|
||||
<span class="code-info" title="The namespace of the repository" bs-tooltip="tooltip.title">"namespace"</span>: "devtable",
|
||||
<span class="code-info" title="Whether the repository is public or private" bs-tooltip="tooltip.title">"visibility"</span>: "private",
|
||||
<span class="code-info" title="The Quay URL for the repository" bs-tooltip="tooltip.title">"homepage"</span>: "https://quay.io/repository/devtable/ubuntu"
|
||||
}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<div class="jumbotron landing ready-indicator" data-status="{{ status }}">
|
||||
<div class="jumbotron landing">
|
||||
<div class="container">
|
||||
<div class="row messages">
|
||||
<div class="col-md-7">
|
||||
<div ng-show="user.anonymous">
|
||||
<h1>Secure hosting for <b>private</b> docker repositories</h1>
|
||||
<h3>Use the docker images <b>your team</b> needs with the safety of <b>private</b> repositories</h3>
|
||||
<div class="sellcall"><a href="/plans">Private repository plans starting at $7/mo</a></div>
|
||||
<h1>Secure hosting for <b>private</b> Docker<a class="disclaimer-link" href="/disclaimer" target="_self">*</a> repositories</h1>
|
||||
<h3>Use the Docker images <b>your team</b> needs with the safety of <b>private</b> repositories</h3>
|
||||
<div class="sellcall"><a href="/plans/">Private repository plans starting at $7/mo</a></div>
|
||||
</div>
|
||||
|
||||
<div ng-show="!user.anonymous">
|
||||
|
@ -72,17 +72,16 @@
|
|||
<i class="fa fa-lock"></i>
|
||||
<b>Secure</b>
|
||||
<span class="shoutout-expand">
|
||||
Store your private docker containers where only you and your team
|
||||
can access it, with communication secured by <strong>SSL at all times</strong>
|
||||
</span>
|
||||
Your data is transferred using <strong>SSL at all times</strong> and <strong>encrypted</strong> when at rest. More information available in our <a href="/security/">security guide</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 shoutout">
|
||||
<i class="fa fa-user"></i>
|
||||
<i class="fa fa-group"></i>
|
||||
<b>Shareable</b>
|
||||
<span class="shoutout-expand">
|
||||
Have to share a repository? No problem! Share with anyone you choose
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 shoutout">
|
||||
|
@ -90,7 +89,7 @@
|
|||
<b>Cloud Hosted</b>
|
||||
<span class="shoutout-expand">
|
||||
Accessible from anywhere, anytime
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div> <!-- row -->
|
||||
</div> <!-- container -->
|
||||
|
@ -147,36 +146,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="landing-footer">
|
||||
<div class="row">
|
||||
<div class="col-md-2 col-md-offset-1">
|
||||
<h4>About</h4>
|
||||
<ul>
|
||||
<li><a href="http://blog.devtable.com/">Blog</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<h4>Legal</h4>
|
||||
<ul>
|
||||
<li><a href="/tos" target="_self">Terms of Service</a></li>
|
||||
<li><a href="/privacy" target="_self">Privacy Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<h4>Support</h4>
|
||||
<ul>
|
||||
<li><a href="mailto:support@quay.io">Contact Support</a></li>
|
||||
<li><a href="/guide/">User Guide</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 col-md-offset-2 copyright-container">
|
||||
<a href="https://devtable.com"><img class="dt-logo" src="/static/img/dt-logo.png"></a>
|
||||
<span class="copyright">©2013 DevTable, LLC</span>
|
||||
</div>
|
||||
</div> <!-- row -->
|
||||
|
||||
</nav>
|
||||
|
|
|
@ -55,12 +55,13 @@
|
|||
<th>Billing Date/Time</th>
|
||||
<th>Amount Due</th>
|
||||
<th>Status</th>
|
||||
<th></th>
|
||||
</thead>
|
||||
|
||||
<tbody class="invoice" ng-repeat="invoice in invoices">
|
||||
<tr class="invoice-title" ng-click="toggleInvoice(invoice.id)">
|
||||
<td><span class="invoice-datetime">{{ invoice.date * 1000 | date:'medium' }}</span></td>
|
||||
<td><span class="invoice-amount">{{ invoice.amount_due / 100 }}</span></td>
|
||||
<tr class="invoice-title">
|
||||
<td ng-click="toggleInvoice(invoice.id)"><span class="invoice-datetime">{{ invoice.date * 1000 | date:'medium' }}</span></td>
|
||||
<td ng-click="toggleInvoice(invoice.id)"><span class="invoice-amount">{{ invoice.amount_due / 100 }}</span></td>
|
||||
<td>
|
||||
<span class="invoice-status">
|
||||
<span class="success" ng-show="invoice.paid">Paid - Thank you!</span>
|
||||
|
@ -69,6 +70,11 @@
|
|||
<span class="pending" ng-show="!invoice.paid && !invoice.attempted">Payment pending</span>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a ng-show="invoice.paid" href="/receipt?id={{ invoice.id }}" download="receipt.pdf" target="_new">
|
||||
<i class="fa fa-download" title="Download Receipt" bs-tooltip="tooltip.title"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr ng-class="invoiceExpanded[invoice.id] ? 'in' : 'out'" class="invoice-details panel-collapse collapse">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="container org-list">
|
||||
<div class="container org-list conntent-container">
|
||||
<div class="loading" ng-show="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="container plans ready-indicator" data-status="{{ status }}">
|
||||
<div class="container plans content-container">
|
||||
<div class="callout">
|
||||
Plans & Pricing
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
<script src="static/lib/Blob.js"></script>
|
||||
<script src="static/lib/FileSaver.js"></script>
|
||||
<script src="static/lib/jquery.base64.min.js"></script>
|
||||
|
||||
<div class="loading" ng-show="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
|
@ -23,7 +20,7 @@
|
|||
<div class="col-md-2">
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li class="active"><a href="javascript:void(0)" data-toggle="tab" data-target="#permissions">Permissions</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#webhook" ng-click="loadWebhooks()">Webhooks</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#webhook" ng-click="loadWebhooks()">Web Hooks</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#publicprivate">Public/Private</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#delete">Delete</a></li>
|
||||
</ul>
|
||||
|
@ -152,19 +149,19 @@
|
|||
<!-- Webhook tab -->
|
||||
<div id="webhook" class="tab-pane">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Push Webhooks
|
||||
<i class="info-icon fa fa-info-circle" data-placement="left" data-content="URLs which will be invoked when a successful push to the repository occurs."></i>
|
||||
<div class="panel-heading">Push Web Hooks
|
||||
<i class="info-icon fa fa-info-circle" data-placement="left" data-content="URLs which will be invoked with an HTTP POST and JSON payload when a successful push to the repository occurs."></i>
|
||||
</div>
|
||||
|
||||
<div class="panel-body" ng-show="webhooksLoading">
|
||||
Loading webhooks: <i class="fa fa-spinner fa-spin fa-2x" style="vertical-align: middle; margin-left: 4px"></i>
|
||||
Loading web hooks: <i class="fa fa-spinner fa-spin fa-2x" style="vertical-align: middle; margin-left: 4px"></i>
|
||||
</div>
|
||||
|
||||
<div class="panel-body" ng-show="!webhooksLoading">
|
||||
<table class="permissions" ng-form="newWebhookForm">
|
||||
<thead>
|
||||
<tr>
|
||||
<td style="width: 500px;">Webhook URL</td>
|
||||
<td style="width: 500px;">Web Hook URL</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -175,13 +172,13 @@
|
|||
<td>
|
||||
<span class="delete-ui" tabindex="0">
|
||||
<span class="delete-ui-button" ng-click="deleteWebhook(webhook)"><button class="btn btn-danger">Delete</button></span>
|
||||
<i class="fa fa-times" bs-tooltip="tooltip.title" data-placement="right" title="Delete Webhook"></i>
|
||||
<i class="fa fa-times" bs-tooltip="tooltip.title" data-placement="right" title="Delete Web Hook"></i>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="url" class="form-control" placeholder="New webhook url..." ng-model="newWebhook.url" required>
|
||||
<input type="url" class="form-control" placeholder="New web hook url..." ng-model="newWebhook.url" required>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-primary" type="submit" ng-click="createWebhook()">Create</button>
|
||||
|
@ -189,6 +186,10 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="right-info">
|
||||
Quay will <b>POST</b> to these web hooks whenever a push occurs. See the <a href="/guide">User Guide</a> for more information.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
||||
<div class="container ready-indicator" ng-show="!loading" data-status="{{ loading ? '' : 'ready' }}">
|
||||
<div class="container" ng-show="!loading">
|
||||
<div class="repo-list" ng-show="!user.anonymous">
|
||||
<div ng-class="user.organizations.length ? 'section-header' : ''">
|
||||
<div class="button-bar-right">
|
||||
|
|
44
static/partials/security.html
Normal file
44
static/partials/security.html
Normal file
|
@ -0,0 +1,44 @@
|
|||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1>Quay.io Security</h1>
|
||||
<p>We understand that when you upload one of your repositories to Quay.io that you are trusting us with some potentially very sensitive data. On this page we will lay out our security features and practices to help you make an informed decision about whether you can trust us with your data.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>SSL Everwhere</h3>
|
||||
<p>We expressly forbid connections to Quay.io using unencrypted HTTP traffic. This helps keep your data and account information safe on the wire. Our SSL traffic is decrypted on our application servers, so your traffic is encrypted even within the datacenter. We use a 4096-bit RSA key, and after the key exchange is complete, traffic is transferred using 256-bit AES, for the maximum encryption strength.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Encryption</h3>
|
||||
<p>Our binary data is currently stored in Amazon's <a href="http://aws.amazon.com/s3/">S3</a> service. We use HTTPS when transferring your data internally between our application servers and S3, so your data is never exposed in plain text on any wire. We use their <a href="http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingServerSideEncryption.html">server side encryption</a> to protect your data while stored at rest in their data centers.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Passwords</h3>
|
||||
<p>There have been a number of high profile leaks recently where companies have been storing their customers' passwords in plain text, an unsalted hash, or a <a href="http://en.wikipedia.org/wiki/Salt_(cryptography)">salted hash</a> where every salt is the same. At Quay.io we use the <a href="http://en.wikipedia.org/wiki/Bcrypt">bcrypt</a> algorithm to generate a salted hash from your password, using a unique salt for each password. This method of storage is safe against <a href="http://en.wikipedia.org/wiki/Rainbow_table">rainbow attacks</a> and is obviously superior to plain-text storage. Your credentials are also never written in plain text to our application logs, a leak that is commonly overlooked.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Access Controls</h3>
|
||||
<p>Repositories will only ever be shared with people to whom you delegate access. Repositories created from the Docker command line are private by default and repositories must subsequently made public with an explicit action in the Quay.io UI. We have a test suite which is run before every code push which tests all methods which expose private data with all levels of access to ensure nothing is accidentally leaked.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Firewalls</h3>
|
||||
<p>Our application servers and database servers are all protected with firewall settings that only allow communication with known hosts and host groups on sensitive ports (e.g. SSH). None of our servers have SSH password authentication enabled, preventing brute force password attacks.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Data Resilience</h3>
|
||||
<p>While not related directly to security, many of you are probably worried about whether you can depend on the data you store in Quay.io. All binary data that we store is stored in Amazon S3 at the highest redundancy level, which Amazon claims provides <a href="http://aws.amazon.com/s3/faqs/#How_is_Amazon_S3_designed_to_achieve_99.999999999%_durability">11-nines of durability</a>. Our service metadata (e.g. logins, tags, teams) is stored in a database which is backed up nightly, and backups are preserved for 7 days.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,4 +1,4 @@
|
|||
<div class="container signin-container ready-indicator" data-status="{{ loading ? '' : 'ready' }}">
|
||||
<div class="container signin-container">
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-sm-offset-3">
|
||||
<div class="panel-group" id="accordion">
|
||||
|
|
|
@ -57,7 +57,15 @@
|
|||
|
||||
<!-- Empty message -->
|
||||
<div class="repo-content" ng-show="!currentTag.image && !repo.is_building">
|
||||
<div class="empty-message">(This repository is empty)</div>
|
||||
<div class="empty-message">
|
||||
This repository is empty
|
||||
</div>
|
||||
|
||||
<div class="empty-description" ng-show="repo.can_write">
|
||||
To push images to this repository:<br><br>
|
||||
<pre>sudo docker tag <i>0u123imageidgoeshere</i> quay.io/{{repo.namespace}}/{{repo.name}}
|
||||
sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="repo-content" ng-show="!currentTag.image && repo.is_building">
|
||||
|
@ -67,7 +75,7 @@
|
|||
<!-- Content view -->
|
||||
<div class="repo-content" ng-show="currentTag.image">
|
||||
<!-- Image History -->
|
||||
<div id="image-history">
|
||||
<div id="image-history" style="max-height: 10px;">
|
||||
<div class="row">
|
||||
<!-- Tree View container -->
|
||||
<div class="col-md-8">
|
||||
|
|
34
static/sitemap.xml
Normal file
34
static/sitemap.xml
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://quay.io/</loc>
|
||||
<changefreq>hourly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://quay.io/plans/</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://quay.io/organizations/</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://quay.io/repository/</loc>
|
||||
<changefreq>always</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://quay.io/guide/</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://quay.io/tos</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.4</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://quay.io/privacy</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.4</priority>
|
||||
</url>
|
||||
</urlset>
|
Reference in a new issue