fe69ba5ec1
- Have conversion to organization update its plan to a business plan - Fix bug in the repo donut usage graph thingy where it had zero size when not in the default tab
854 lines
26 KiB
JavaScript
854 lines
26 KiB
JavaScript
function getFirstTextLine(commentString) {
|
|
if (!commentString) { return ''; }
|
|
|
|
var lines = commentString.split('\n');
|
|
var MARKDOWN_CHARS = {
|
|
'#': true,
|
|
'-': true,
|
|
'>': true,
|
|
'`': true
|
|
};
|
|
|
|
for (var i = 0; i < lines.length; ++i) {
|
|
// Skip code lines.
|
|
if (lines[i].indexOf(' ') == 0) {
|
|
continue;
|
|
}
|
|
|
|
// Skip empty lines.
|
|
if ($.trim(lines[i]).length == 0) {
|
|
continue;
|
|
}
|
|
|
|
// Skip control lines.
|
|
if (MARKDOWN_CHARS[$.trim(lines[i])[0]]) {
|
|
continue;
|
|
}
|
|
|
|
return getMarkedDown(lines[i]);
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
function getRestUrl(args) {
|
|
var url = '';
|
|
for (var i = 0; i < arguments.length; ++i) {
|
|
if (i > 0) {
|
|
url += '/';
|
|
}
|
|
url += encodeURI(arguments[i])
|
|
}
|
|
return url;
|
|
}
|
|
|
|
function getMarkedDown(string) {
|
|
return Markdown.getSanitizingConverter().makeHtml(string || '');
|
|
}
|
|
|
|
// Start the application code itself.
|
|
quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics', 'angulartics.mixpanel', '$strap.directives', 'ngCookies'], function($provide) {
|
|
$provide.factory('UserService', ['Restangular', 'PlanService', function(Restangular, PlanService) {
|
|
var userResponse = {
|
|
verified: false,
|
|
anonymous: true,
|
|
username: null,
|
|
email: null,
|
|
askForPassword: false,
|
|
}
|
|
|
|
var userService = {}
|
|
var currentSubscription = null;
|
|
|
|
userService.load = function() {
|
|
var userFetch = Restangular.one('user/');
|
|
userFetch.get().then(function(loadedUser) {
|
|
userResponse = loadedUser;
|
|
|
|
if (!userResponse.anonymous) {
|
|
mixpanel.identify(userResponse.username);
|
|
mixpanel.people.set({
|
|
'$email': userResponse.email,
|
|
'$username': userResponse.username,
|
|
'verified': userResponse.verified
|
|
});
|
|
mixpanel.people.set_once({
|
|
'$created': new Date()
|
|
})
|
|
}
|
|
});
|
|
};
|
|
|
|
userService.resetCurrentSubscription = function() {
|
|
currentSubscription = null;
|
|
};
|
|
|
|
userService.getCurrentSubscription = function(callback, failure) {
|
|
if (currentSubscription) { callback(currentSubscription); }
|
|
PlanService.getSubscription(null, function(sub) {
|
|
currentSubscription = sub;
|
|
callback(sub);
|
|
}, failure);
|
|
};
|
|
|
|
userService.currentUser = function() {
|
|
return userResponse;
|
|
}
|
|
|
|
// Load the user the first time.
|
|
userService.load();
|
|
|
|
return userService;
|
|
}]);
|
|
|
|
$provide.factory('KeyService', ['$location', function($location) {
|
|
var keyService = {}
|
|
|
|
if ($location.host() === 'quay.io') {
|
|
keyService['stripePublishableKey'] = 'pk_live_P5wLU0vGdHnZGyKnXlFG4oiu';
|
|
keyService['githubClientId'] = '5a8c08b06c48d89d4d1e';
|
|
} else {
|
|
keyService['stripePublishableKey'] = 'pk_test_uEDHANKm9CHCvVa2DLcipGRh';
|
|
keyService['githubClientId'] = 'cfbc4aca88e5c1b40679';
|
|
}
|
|
|
|
return keyService;
|
|
}]);
|
|
|
|
$provide.factory('PlanService', ['Restangular', 'KeyService', function(Restangular, KeyService) {
|
|
var plans = null;
|
|
var planDict = {};
|
|
var planService = {}
|
|
|
|
planService.verifyLoaded = function(callback) {
|
|
if (plans) {
|
|
callback(plans);
|
|
return;
|
|
}
|
|
|
|
var getPlans = Restangular.one('plans');
|
|
getPlans.get().then(function(data) {
|
|
var i = 0;
|
|
for(i = 0; i < data.user.length; i++) {
|
|
planDict[data.user[i].stripeId] = data.user[i];
|
|
}
|
|
for(i = 0; i < data.business.length; i++) {
|
|
planDict[data.business[i].stripeId] = data.business[i];
|
|
}
|
|
plans = data;
|
|
callback(plans);
|
|
}, function() { callback([]); });
|
|
};
|
|
|
|
planService.getMatchingBusinessPlan = function(callback) {
|
|
planService.getPlans(function() {
|
|
planService.getSubscription(null, function(sub) {
|
|
var plan = planDict[sub.plan];
|
|
if (!plan) {
|
|
planService.getMinimumPlan(0, true, callback);
|
|
return;
|
|
}
|
|
|
|
var count = Math.max(sub.usedPrivateRepos, plan.privateRepos);
|
|
planService.getMinimumPlan(count, true, callback);
|
|
}, function() {
|
|
planService.getMinimumPlan(0, true, callback);
|
|
});
|
|
});
|
|
};
|
|
|
|
planService.getPlans = function(callback) {
|
|
planService.verifyLoaded(callback);
|
|
};
|
|
|
|
planService.getPlan = function(planId, callback) {
|
|
planService.verifyLoaded(function() {
|
|
if (planDict[planId]) {
|
|
callback(planDict[planId]);
|
|
}
|
|
});
|
|
};
|
|
|
|
planService.getMinimumPlan = function(privateCount, isBusiness, callback) {
|
|
planService.verifyLoaded(function() {
|
|
var planSource = plans.user;
|
|
if (isBusiness) {
|
|
planSource = plans.business;
|
|
}
|
|
|
|
for (var i = 0; i < planSource.length; i++) {
|
|
var plan = planSource[i];
|
|
if (plan.privateRepos >= privateCount) {
|
|
callback(plan);
|
|
return;
|
|
}
|
|
}
|
|
|
|
callback(null);
|
|
});
|
|
};
|
|
|
|
planService.getSubscription = function(organization, success, failure) {
|
|
var url = planService.getSubscriptionUrl(organization);
|
|
var getSubscription = Restangular.one(url);
|
|
getSubscription.get().then(success, failure);
|
|
};
|
|
|
|
planService.getSubscriptionUrl = function(orgname) {
|
|
return orgname ? getRestUrl('organization', orgname, 'plan') : 'user/plan';
|
|
};
|
|
|
|
planService.setSubscription = function(orgname, planId, success, failure, opt_token) {
|
|
var subscriptionDetails = {
|
|
plan: planId
|
|
};
|
|
|
|
if (opt_token) {
|
|
subscriptionDetails['token'] = opt_token.id;
|
|
}
|
|
|
|
var url = planService.getSubscriptionUrl(orgname);
|
|
var createSubscriptionRequest = Restangular.one(url);
|
|
createSubscriptionRequest.customPUT(subscriptionDetails).then(success, failure);
|
|
};
|
|
|
|
planService.changePlan = function($scope, orgname, planId, hasExistingSubscription, started, success, failure) {
|
|
if (!hasExistingSubscription) {
|
|
planService.showSubscribeDialog($scope, orgname, planId, started, success, failure);
|
|
return;
|
|
}
|
|
|
|
started();
|
|
planService.setSubscription(orgname, planId, success, failure);
|
|
};
|
|
|
|
planService.showSubscribeDialog = function($scope, orgname, planId, started, success, failure) {
|
|
var submitToken = function(token) {
|
|
mixpanel.track('plan_subscribe');
|
|
|
|
$scope.$apply(function() {
|
|
started();
|
|
planService.setSubscription(orgname, planId, success, failure);
|
|
});
|
|
};
|
|
|
|
planService.getPlan(planId, function(planDetails) {
|
|
StripeCheckout.open({
|
|
key: KeyService.stripePublishableKey,
|
|
address: false,
|
|
amount: planDetails.price,
|
|
currency: 'usd',
|
|
name: 'Quay ' + planDetails.title + ' Subscription',
|
|
description: 'Up to ' + planDetails.privateRepos + ' private repositories',
|
|
panelLabel: 'Subscribe',
|
|
token: submitToken,
|
|
image: 'static/img/quay-icon-stripe.png'
|
|
});
|
|
});
|
|
};
|
|
|
|
return planService;
|
|
}]);
|
|
}).
|
|
directive('match', function($parse) {
|
|
return {
|
|
require: 'ngModel',
|
|
link: function(scope, elem, attrs, ctrl) {
|
|
scope.$watch(function() {
|
|
return $parse(attrs.match)(scope) === ctrl.$modelValue;
|
|
}, function(currentValue) {
|
|
ctrl.$setValidity('mismatch', currentValue);
|
|
});
|
|
}
|
|
};
|
|
}).
|
|
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',
|
|
function($routeProvider, $locationProvider, $analyticsProvider) {
|
|
|
|
$analyticsProvider.virtualPageviews(true);
|
|
|
|
$locationProvider.html5Mode(true);
|
|
|
|
// WARNING WARNING WARNING
|
|
// If you add a route here, you must add a corresponding route in thr endpoints/web.py
|
|
// 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/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('/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: '/'});
|
|
}]).
|
|
config(function(RestangularProvider) {
|
|
RestangularProvider.setBaseUrl('/api/');
|
|
});
|
|
|
|
|
|
quayApp.directive('markdownView', function () {
|
|
var directiveDefinitionObject = {
|
|
priority: 0,
|
|
templateUrl: '/static/directives/markdown-view.html',
|
|
replace: false,
|
|
transclude: false,
|
|
restrict: 'C',
|
|
scope: {
|
|
'content': '=content',
|
|
'firstLineOnly': '=firstLineOnly'
|
|
},
|
|
controller: function($scope, $element) {
|
|
$scope.getMarkedDown = function(content, firstLineOnly) {
|
|
if (firstLineOnly) {
|
|
content = getFirstTextLine(content);
|
|
}
|
|
return getMarkedDown(content);
|
|
};
|
|
}
|
|
};
|
|
return directiveDefinitionObject;
|
|
});
|
|
|
|
|
|
quayApp.directive('repoCircle', function () {
|
|
var directiveDefinitionObject = {
|
|
priority: 0,
|
|
templateUrl: '/static/directives/repo-circle.html',
|
|
replace: false,
|
|
transclude: false,
|
|
restrict: 'C',
|
|
scope: {
|
|
'repo': '=repo'
|
|
},
|
|
controller: function($scope, $element) {
|
|
}
|
|
};
|
|
return directiveDefinitionObject;
|
|
});
|
|
|
|
|
|
quayApp.directive('signinForm', function () {
|
|
var directiveDefinitionObject = {
|
|
priority: 0,
|
|
templateUrl: '/static/directives/signin-form.html',
|
|
replace: false,
|
|
transclude: true,
|
|
restrict: 'C',
|
|
scope: {
|
|
'redirectUrl': '=redirectUrl'
|
|
},
|
|
controller: function($scope, $location, $timeout, Restangular, KeyService, UserService) {
|
|
$scope.githubClientId = KeyService.githubClientId;
|
|
|
|
var appendMixpanelId = function() {
|
|
if (mixpanel.get_distinct_id !== undefined) {
|
|
$scope.mixpanelDistinctIdClause = "&state=" + mixpanel.get_distinct_id();
|
|
} else {
|
|
// Mixpanel not yet loaded, try again later
|
|
$timeout(appendMixpanelId, 200);
|
|
}
|
|
};
|
|
|
|
appendMixpanelId();
|
|
|
|
$scope.signin = function() {
|
|
var signinPost = Restangular.one('signin');
|
|
signinPost.customPOST($scope.user).then(function() {
|
|
$scope.needsEmailVerification = false;
|
|
$scope.invalidCredentials = false;
|
|
|
|
// Redirect to the specified page or the landing page
|
|
UserService.load();
|
|
$location.path($scope.redirectUrl ? $scope.redirectUrl : '/');
|
|
}, function(result) {
|
|
$scope.needsEmailVerification = result.data.needsEmailVerification;
|
|
$scope.invalidCredentials = result.data.invalidCredentials;
|
|
});
|
|
};
|
|
}
|
|
};
|
|
return directiveDefinitionObject;
|
|
});
|
|
|
|
|
|
quayApp.directive('plansTable', function () {
|
|
var directiveDefinitionObject = {
|
|
priority: 0,
|
|
templateUrl: '/static/directives/plans-table.html',
|
|
replace: false,
|
|
transclude: true,
|
|
restrict: 'C',
|
|
scope: {
|
|
'plans': '=plans',
|
|
'currentPlan': '=currentPlan'
|
|
},
|
|
controller: function($scope, $element) {
|
|
$scope.setPlan = function(plan) {
|
|
$scope.currentPlan = plan;
|
|
};
|
|
}
|
|
};
|
|
return directiveDefinitionObject;
|
|
});
|
|
|
|
|
|
quayApp.directive('organizationHeader', function () {
|
|
var directiveDefinitionObject = {
|
|
priority: 0,
|
|
templateUrl: '/static/directives/organization-header.html',
|
|
replace: false,
|
|
transclude: true,
|
|
restrict: 'C',
|
|
scope: {
|
|
'organization': '=organization',
|
|
'teamName': '=teamName'
|
|
},
|
|
controller: function($scope, $element) {
|
|
}
|
|
};
|
|
return directiveDefinitionObject;
|
|
});
|
|
|
|
|
|
quayApp.directive('markdownInput', function () {
|
|
var counter = 0;
|
|
|
|
var directiveDefinitionObject = {
|
|
priority: 0,
|
|
templateUrl: '/static/directives/markdown-input.html',
|
|
replace: false,
|
|
transclude: false,
|
|
restrict: 'C',
|
|
scope: {
|
|
'content': '=content',
|
|
'canWrite': '=canWrite',
|
|
'contentChanged': '=contentChanged',
|
|
'fieldTitle': '=fieldTitle'
|
|
},
|
|
controller: function($scope, $element) {
|
|
var elm = $element[0];
|
|
|
|
$scope.id = (counter++);
|
|
|
|
$scope.editContent = function() {
|
|
if (!$scope.canWrite) { return; }
|
|
|
|
if (!$scope.markdownDescriptionEditor) {
|
|
var converter = Markdown.getSanitizingConverter();
|
|
var editor = new Markdown.Editor(converter, '-description-' + $scope.id);
|
|
editor.run();
|
|
$scope.markdownDescriptionEditor = editor;
|
|
}
|
|
|
|
$('#wmd-input-description-' + $scope.id)[0].value = $scope.content;
|
|
$(elm).find('.modal').modal({});
|
|
};
|
|
|
|
$scope.saveContent = function() {
|
|
$scope.content = $('#wmd-input-description-' + $scope.id)[0].value;
|
|
$(elm).find('.modal').modal('hide');
|
|
|
|
if ($scope.contentChanged) {
|
|
$scope.contentChanged($scope.content);
|
|
}
|
|
};
|
|
}
|
|
};
|
|
return directiveDefinitionObject;
|
|
});
|
|
|
|
|
|
quayApp.directive('entitySearch', function () {
|
|
var number = 0;
|
|
var directiveDefinitionObject = {
|
|
priority: 0,
|
|
templateUrl: '/static/directives/entity-search.html',
|
|
replace: false,
|
|
transclude: false,
|
|
restrict: 'C',
|
|
scope: {
|
|
'organization': '=organization',
|
|
'inputTitle': '=inputTitle',
|
|
'entitySelected': '=entitySelected'
|
|
},
|
|
controller: function($scope, $element) {
|
|
if (!$scope.entitySelected) { return; }
|
|
|
|
number++;
|
|
|
|
var input = $element[0].firstChild;
|
|
$scope.organization = $scope.organization || '';
|
|
$(input).typeahead({
|
|
name: 'entities' + number,
|
|
remote: {
|
|
url: '/api/entities/%QUERY',
|
|
replace: function (url, uriEncodedQuery) {
|
|
url = url.replace('%QUERY', uriEncodedQuery);
|
|
if ($scope.organization) {
|
|
url += '?organization=' + encodeURIComponent($scope.organization);
|
|
}
|
|
return url;
|
|
},
|
|
filter: function(data) {
|
|
var datums = [];
|
|
for (var i = 0; i < data.results.length; ++i) {
|
|
var entity = data.results[i];
|
|
datums.push({
|
|
'value': entity.name,
|
|
'tokens': [entity.name],
|
|
'entity': entity
|
|
});
|
|
}
|
|
return datums;
|
|
}
|
|
},
|
|
template: function (datum) {
|
|
template = '<div class="entity-mini-listing">';
|
|
if (datum.entity.kind == 'user') {
|
|
template += '<i class="fa fa-user fa-lg"></i>';
|
|
} else if (datum.entity.kind == 'team') {
|
|
template += '<i class="fa fa-group fa-lg"></i>';
|
|
}
|
|
template += '<span class="name">' + datum.value + '</span>';
|
|
|
|
if (datum.entity.is_org_member !== undefined && !datum.entity.is_org_member) {
|
|
template += '<div class="alert-warning warning">This user is outside your organization</div>';
|
|
}
|
|
|
|
template += '</div>';
|
|
return template;
|
|
},
|
|
});
|
|
|
|
$(input).on('typeahead:selected', function(e, datum) {
|
|
$(input).typeahead('setQuery', '');
|
|
$scope.entitySelected(datum.entity);
|
|
});
|
|
|
|
$scope.$watch('inputTitle', function(title) {
|
|
input.setAttribute('placeholder', title);
|
|
});
|
|
}
|
|
};
|
|
return directiveDefinitionObject;
|
|
});
|
|
|
|
|
|
quayApp.directive('roleGroup', function () {
|
|
var directiveDefinitionObject = {
|
|
priority: 0,
|
|
templateUrl: '/static/directives/role-group.html',
|
|
replace: false,
|
|
transclude: false,
|
|
restrict: 'C',
|
|
scope: {
|
|
'roles': '=roles',
|
|
'currentRole': '=currentRole',
|
|
'roleChanged': '&roleChanged'
|
|
},
|
|
controller: function($scope, $element) {
|
|
$scope.setRole = function(role) {
|
|
$scope.currentRole = role;
|
|
if ($scope.roleChanged) {
|
|
if ($scope.changeParams) {
|
|
$scope.changeParams();
|
|
}
|
|
$scope.roleChanged({'role': role});
|
|
}
|
|
};
|
|
}
|
|
};
|
|
return directiveDefinitionObject;
|
|
});
|
|
|
|
|
|
quayApp.directive('planManager', function () {
|
|
var directiveDefinitionObject = {
|
|
priority: 0,
|
|
templateUrl: '/static/directives/plan-manager.html',
|
|
replace: false,
|
|
transclude: false,
|
|
restrict: 'C',
|
|
scope: {
|
|
'user': '=user',
|
|
'organization': '=organization',
|
|
'readyForPlan': '&readyForPlan'
|
|
},
|
|
controller: function($scope, $element, PlanService, Restangular) {
|
|
var hasSubscription = false;
|
|
|
|
$scope.getActiveSubClass = function() {
|
|
return 'active';
|
|
};
|
|
|
|
$scope.changeSubscription = function(planId) {
|
|
if ($scope.planChanging) { return; }
|
|
|
|
PlanService.changePlan($scope, $scope.organization, planId, hasSubscription, function() {
|
|
// Started.
|
|
$scope.planChanging = true;
|
|
}, function(sub) {
|
|
// Success.
|
|
subscribedToPlan(sub);
|
|
}, function() {
|
|
// Failure.
|
|
$scope.planChanging = false;
|
|
});
|
|
};
|
|
|
|
$scope.cancelSubscription = function() {
|
|
$scope.changeSubscription(getFreePlan());
|
|
};
|
|
|
|
var subscribedToPlan = function(sub) {
|
|
$scope.subscription = sub;
|
|
|
|
if (sub.plan != getFreePlan()) {
|
|
hasSubscription = true;
|
|
}
|
|
|
|
PlanService.getPlan(sub.plan, function(subscribedPlan) {
|
|
$scope.subscribedPlan = subscribedPlan;
|
|
$scope.planUsagePercent = sub.usedPrivateRepos * 100 / $scope.subscribedPlan.privateRepos;
|
|
|
|
if (sub.usedPrivateRepos > $scope.subscribedPlan.privateRepos) {
|
|
$scope.limit = 'over';
|
|
} else if (sub.usedPrivateRepos == $scope.subscribedPlan.privateRepos) {
|
|
$scope.limit = 'at';
|
|
} else if (sub.usedPrivateRepos >= $scope.subscribedPlan.privateRepos * 0.7) {
|
|
$scope.limit = 'near';
|
|
} else {
|
|
$scope.limit = 'none';
|
|
}
|
|
|
|
if (!$scope.chart) {
|
|
$scope.chart = new RepositoryUsageChart();
|
|
$scope.chart.draw('repository-usage-chart');
|
|
}
|
|
|
|
$scope.chart.update(sub.usedPrivateRepos || 0, $scope.subscribedPlan.privateRepos || 0);
|
|
|
|
$scope.planChanging = false;
|
|
$scope.planLoading = false;
|
|
});
|
|
};
|
|
|
|
var getFreePlan = function() {
|
|
for (var i = 0; i < $scope.plans.length; ++i) {
|
|
if ($scope.plans[i].price == 0) {
|
|
return $scope.plans[i].stripeId;
|
|
}
|
|
}
|
|
return 'free';
|
|
};
|
|
|
|
var update = function() {
|
|
$scope.planLoading = true;
|
|
if (!$scope.plans) { return; }
|
|
|
|
PlanService.getSubscription($scope.organization, subscribedToPlan, function() {
|
|
// User/Organization has no subscription.
|
|
subscribedToPlan({ 'plan': getFreePlan() });
|
|
});
|
|
};
|
|
|
|
var loadPlans = function() {
|
|
if ($scope.plans || $scope.loadingPlans) { return; }
|
|
if (!$scope.user && !$scope.organization) { return; }
|
|
|
|
$scope.loadingPlans = true;
|
|
PlanService.getPlans(function(plans) {
|
|
$scope.plans = plans[$scope.organization ? 'business' : 'user'];
|
|
update();
|
|
|
|
if ($scope.readyForPlan) {
|
|
var planRequested = $scope.readyForPlan();
|
|
if (planRequested && planRequested != getFreePlan()) {
|
|
$scope.changeSubscription(planRequested);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
// Start the initial download.
|
|
$scope.planLoading = true;
|
|
loadPlans();
|
|
|
|
$scope.$watch('organization', loadPlans);
|
|
$scope.$watch('user', loadPlans);
|
|
}
|
|
};
|
|
return directiveDefinitionObject;
|
|
});
|
|
|
|
|
|
|
|
quayApp.directive('namespaceSelector', function () {
|
|
var directiveDefinitionObject = {
|
|
priority: 0,
|
|
templateUrl: '/static/directives/namespace-selector.html',
|
|
replace: false,
|
|
transclude: false,
|
|
restrict: 'C',
|
|
scope: {
|
|
'user': '=user',
|
|
'namespace': '=namespace',
|
|
'requireCreate': '=requireCreate'
|
|
},
|
|
controller: function($scope, $element, $cookieStore) {
|
|
$scope.namespaces = {};
|
|
|
|
$scope.initialize = function(user) {
|
|
var namespaces = {};
|
|
namespaces[user.username] = user;
|
|
if (user.organizations) {
|
|
for (var i = 0; i < user.organizations.length; ++i) {
|
|
namespaces[user.organizations[i].name] = user.organizations[i];
|
|
}
|
|
}
|
|
|
|
var initialNamespace = $cookieStore.get('quay.currentnamespace') || $scope.user.username;
|
|
$scope.namespaces = namespaces;
|
|
$scope.setNamespace($scope.namespaces[initialNamespace]);
|
|
};
|
|
|
|
$scope.setNamespace = function(namespaceObj) {
|
|
if (!namespaceObj) {
|
|
namespaceObj = $scope.namespaces[$scope.user.username];
|
|
}
|
|
|
|
if ($scope.requireCreate && !namespaceObj.can_create_repo) {
|
|
namespaceObj = $scope.namespaces[$scope.user.username];
|
|
}
|
|
|
|
var newNamespace = namespaceObj.name || namespaceObj.username;
|
|
$scope.namespaceObj = namespaceObj;
|
|
$scope.namespace = newNamespace;
|
|
$cookieStore.put('quay.currentnamespace', newNamespace);
|
|
};
|
|
|
|
$scope.$watch('user', function(user) {
|
|
$scope.user = user;
|
|
$scope.initialize(user);
|
|
});
|
|
}
|
|
};
|
|
return directiveDefinitionObject;
|
|
});
|
|
|
|
|
|
quayApp.directive('buildStatus', function () {
|
|
var directiveDefinitionObject = {
|
|
priority: 0,
|
|
templateUrl: '/static/directives/build-status.html',
|
|
replace: false,
|
|
transclude: false,
|
|
restrict: 'C',
|
|
scope: {
|
|
'build': '=build'
|
|
},
|
|
controller: function($scope, $element) {
|
|
$scope.getBuildProgress = function(buildInfo) {
|
|
switch (buildInfo.status) {
|
|
case 'building':
|
|
return (buildInfo.current_command / buildInfo.total_commands) * 100;
|
|
break;
|
|
|
|
case 'pushing':
|
|
var imagePercentDecimal = (buildInfo.image_completion_percent / 100);
|
|
return ((buildInfo.current_image + imagePercentDecimal) / buildInfo.total_images) * 100;
|
|
break;
|
|
|
|
case 'complete':
|
|
return 100;
|
|
break;
|
|
|
|
case 'initializing':
|
|
case 'starting':
|
|
case 'waiting':
|
|
return 0;
|
|
break;
|
|
}
|
|
|
|
return -1;
|
|
};
|
|
|
|
$scope.getBuildMessage = function(buildInfo) {
|
|
switch (buildInfo.status) {
|
|
case 'initializing':
|
|
return 'Starting Dockerfile build';
|
|
break;
|
|
|
|
case 'starting':
|
|
case 'waiting':
|
|
case 'building':
|
|
return 'Building image from Dockerfile';
|
|
break;
|
|
|
|
case 'pushing':
|
|
return 'Pushing image built from Dockerfile';
|
|
break;
|
|
|
|
case 'complete':
|
|
return 'Dockerfile build completed and pushed';
|
|
break;
|
|
|
|
case 'error':
|
|
return 'Dockerfile build failed: ' + buildInfo.message;
|
|
break;
|
|
}
|
|
};
|
|
}
|
|
};
|
|
return directiveDefinitionObject;
|
|
});
|
|
|
|
// Note: ngBlur is not yet in Angular stable, so we add it manaully here.
|
|
quayApp.directive('ngBlur', function() {
|
|
return function( scope, elem, attrs ) {
|
|
elem.bind('blur', function() {
|
|
scope.$apply(attrs.ngBlur);
|
|
});
|
|
};
|
|
});
|
|
|
|
quayApp.run(['$location', '$rootScope', function($location, $rootScope) {
|
|
$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
|
|
if (current.$$route.title) {
|
|
$rootScope.title = current.$$route.title;
|
|
}
|
|
});
|
|
}]);
|