This repository has been archived on 2020-03-24. You can view files and clone it, but cannot push or open issues or pull requests.
quay/static/js/app.js

601 lines
18 KiB
JavaScript
Raw Normal View History

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'], function($provide) {
$provide.factory('UserService', ['Restangular', function(Restangular) {
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;
2013-10-03 19:46:22 +00:00
2013-10-03 20:16:10 +00:00
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()
})
2013-10-03 20:16:10 +00:00
}
});
};
userService.resetCurrentSubscription = function() {
currentSubscription = null;
};
userService.getCurrentSubscription = function(callback, failure) {
if (currentSubscription) { callback(currentSubscription); }
var getSubscription = Restangular.one('user/plan');
getSubscription.get().then(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) {
2013-11-05 19:40:45 +00:00
var i = 0;
for(i = 0; i < data.user.length; i++) {
planDict[data.user[i].stripeId] = data.user[i];
}
2013-11-05 19:40:45 +00:00
for(i = 0; i < data.business.length; i++) {
planDict[data.business[i].stripeId] = data.business[i];
}
plans = data;
callback(plans);
}, function() { callback([]); });
};
2013-11-05 19:40:45 +00:00
planService.getPlans = function(callback) {
planService.verifyLoaded(callback);
};
planService.getPlan = function(planId, callback) {
planService.verifyLoaded(function() {
callback(planDict[planId]);
});
};
2013-11-05 19:40:45 +00:00
planService.getMinimumPlan = function(privateCount, isBusiness, callback) {
planService.verifyLoaded(function() {
2013-11-05 19:40:45 +00:00
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.showSubscribeDialog = function($scope, planId, orgname, started, success, failed) {
var submitToken = function(token) {
$scope.$apply(function() {
started();
});
mixpanel.track('plan_subscribe');
var subscriptionDetails = {
token: token.id,
plan: planId,
};
var url = orgname ? getRestUrl('organization', orgname, 'plan') : 'user/plan';
var createSubscriptionRequest = Restangular.one(url);
$scope.$apply(function() {
createSubscriptionRequest.customPUT(subscriptionDetails).then(success, failed);
});
};
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);
});
};
}).
2013-10-03 19:46:22 +00:00
config(['$routeProvider', '$locationProvider', '$analyticsProvider',
function($routeProvider, $locationProvider, $analyticsProvider) {
2013-10-03 20:16:10 +00:00
$analyticsProvider.virtualPageviews(true);
2013-10-10 23:06:04 +00:00
$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
2013-10-03 20:16:10 +00:00
$routeProvider.
when('/repository/:namespace/:name', {templateUrl: '/static/partials/view-repo.html', controller: RepoCtrl, reloadOnSearch: false}).
2013-10-03 20:16:10 +00:00
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}).
2013-10-03 20:16:10 +00:00
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: 'User Admin', templateUrl: '/static/partials/user-admin.html', controller: UserAdminCtrl}).
when('/guide/', {title: 'User Guide', templateUrl: '/static/partials/guide.html', controller: GuideCtrl}).
2013-10-09 22:33:25 +00:00
when('/plans/', {title: 'Plans and Pricing', templateUrl: '/static/partials/plans.html', controller: PlansCtrl}).
when('/signin/', {title: 'Signin', templateUrl: '/static/partials/signin.html', controller: SigninCtrl}).
2013-10-24 21:41:55 +00:00
when('/new/', {title: 'Create new repository', templateUrl: '/static/partials/new-repo.html', controller: NewRepoCtrl}).
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}).
2013-10-09 22:33:25 +00:00
when('/', {title: 'Hosted Private Docker Registry', templateUrl: '/static/partials/landing.html', controller: LandingCtrl}).
2013-10-03 20:16:10 +00:00
otherwise({redirectTo: '/'});
}]).
2013-09-24 22:21:14 +00:00
config(function(RestangularProvider) {
RestangularProvider.setBaseUrl('/api/');
2013-09-26 21:59:20 +00:00
});
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('organizationHeader', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/organization-header.html',
replace: false,
2013-11-05 21:15:04 +00:00
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('namespaceSelector', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/namespace-selector.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'user': '=user',
'namespace': '=namespace'
},
controller: function($scope, $element) {
$scope.setNamespace = function(namespaceObj) {
if (!namespaceObj) {
namespaceObj = {'name': '', 'gravatar': ''};
}
$scope.namespaceObj = namespaceObj;
$scope.namespace = namespaceObj.name || namespaceObj.username;
};
$scope.setNamespace($scope.user);
$scope.$watch('user', function(user) {
$scope.setNamespace(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;
});
2013-11-05 22:20:43 +00:00
// 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);
});
};
});
2013-09-26 21:59:20 +00:00
quayApp.run(['$location', '$rootScope', function($location, $rootScope) {
2013-09-26 23:07:25 +00:00
$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
if (current.$$route.title) {
$rootScope.title = current.$$route.title;
}
});
}]);