Merge branch 'master' into looksirdroids

This commit is contained in:
Joseph Schorr 2013-11-22 18:22:29 -05:00
commit 43f2dd80a0
38 changed files with 752 additions and 400 deletions

View file

@ -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;
};
}]);

View file

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