9b65b37011
Add support for recaptcha during the create account flow
356 lines
11 KiB
JavaScript
356 lines
11 KiB
JavaScript
var TEAM_PATTERN = '^[a-z][a-z0-9]+$';
|
|
var ROBOT_PATTERN = '^[a-z][a-z0-9_]{3,29}$';
|
|
var USERNAME_PATTERN = '^([a-z0-9]+(?:[._-][a-z0-9]+)*){4,30}$';
|
|
|
|
// Define the pages module.
|
|
quayPages = angular.module('quayPages', [], function(){});
|
|
|
|
// Define a constant for creating pages.
|
|
quayPages.constant('pages', {
|
|
'_pages': {},
|
|
|
|
'create': function(pageName, templateName, opt_controller, opt_flags, opt_profiles) {
|
|
var profiles = opt_profiles || ['old-layout', 'layout'];
|
|
for (var i = 0; i < profiles.length; ++i) {
|
|
this._pages[profiles[i] + ':' + pageName] = {
|
|
'name': pageName,
|
|
'controller': opt_controller,
|
|
'templateName': templateName,
|
|
'flags': opt_flags || {}
|
|
};
|
|
}
|
|
},
|
|
|
|
'get': function(pageName, profiles) {
|
|
for (var i = 0; i < profiles.length; ++i) {
|
|
var current = profiles[i];
|
|
var key = current.id + ':' + pageName;
|
|
var page = this._pages[key];
|
|
if (page) {
|
|
return [current, page];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
});
|
|
|
|
quayDependencies = ['ngRoute', 'chieffancypants.loadingBar', 'cfp.hotkeys', 'angular-tour', 'restangular', 'angularMoment',
|
|
'mgcrea.ngStrap', 'ngCookies', 'ngSanitize', 'angular-md5', 'pasvaz.bindonce', 'ansiToHtml',
|
|
'core-ui', 'core-config-setup', 'quayPages', 'infinite-scroll', 'react'];
|
|
|
|
if (window.__config && (window.__config.MIXPANEL_KEY || window.__config.MUNCHKIN_KEY || window.__config.GOOGLE_ANALYTICS_KEY)) {
|
|
quayDependencies.push('angulartics');
|
|
}
|
|
|
|
if (window.__config && window.__config.MIXPANEL_KEY) {
|
|
quayDependencies.push('angulartics.mixpanel');
|
|
}
|
|
|
|
if (window.__config && window.__config.MUNCHKIN_KEY) {
|
|
quayDependencies.push('angulartics.marketo');
|
|
}
|
|
|
|
if (window.__config && window.__config.GOOGLE_ANALYTICS_KEY) {
|
|
quayDependencies.push('angulartics.google.analytics');
|
|
}
|
|
|
|
if (window.__config && window.__config.RECAPTCHA_SITE_KEY) {
|
|
quayDependencies.push('vcRecaptcha');
|
|
}
|
|
|
|
// Define the application.
|
|
quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoadingBarProvider) {
|
|
cfpLoadingBarProvider.includeSpinner = false;
|
|
});
|
|
|
|
// Disable tooltips on touch devices.
|
|
quayApp.config(['$tooltipProvider', function ($tooltipProvider) {
|
|
var tooltipFactory = $tooltipProvider.$get[$tooltipProvider.$get.length - 1];
|
|
|
|
// decorate the tooltip getter
|
|
$tooltipProvider.$get[$tooltipProvider.$get.length - 1] = function($window) {
|
|
if ('ontouchstart' in $window) {
|
|
var existing = tooltipFactory.apply(this, arguments);
|
|
return function(element) {
|
|
// Note: We only disable bs-tooltip's themselves. $tooltip is used for other things
|
|
// (such as the datepicker), so we need to be specific when canceling it.
|
|
if (element.attr('bs-tooltip') == null) {
|
|
return existing.apply(this, arguments);
|
|
} else {
|
|
return null;
|
|
}
|
|
};
|
|
}
|
|
|
|
return tooltipFactory.apply(this, arguments);
|
|
};
|
|
}]);
|
|
|
|
quayApp.config(['$compileProvider', function ($compileProvider) {
|
|
if (!window.__config['DEBUG']) {
|
|
$compileProvider.debugInfoEnabled(false);
|
|
}
|
|
}]);
|
|
|
|
// Configure the routes.
|
|
quayApp.config(['$routeProvider', '$locationProvider', 'pages', 'RouteBuilderProvider',
|
|
function($routeProvider, $locationProvider, pages, RouteBuilderProvider) {
|
|
$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
|
|
|
|
var layoutProfile = 'layout';
|
|
window.console.log('Using layout profile: ' + layoutProfile);
|
|
|
|
var routeBuilder = new RouteBuilderProvider.$get()($routeProvider, pages, [
|
|
// Start with the old pages (if we asked for it).
|
|
{id: 'old-layout', templatePath: '/static/partials/'},
|
|
|
|
// Fallback back combined new/existing pages.
|
|
{id: 'layout', templatePath: '/static/partials/'}
|
|
], layoutProfile);
|
|
|
|
if (window.__features.SUPER_USERS) {
|
|
// QE Management
|
|
routeBuilder.route('/superuser/', 'superuser')
|
|
|
|
// QE Setup
|
|
.route('/setup/', 'setup');
|
|
}
|
|
|
|
routeBuilder
|
|
// Repository View
|
|
.route('/repository/:namespace/:name', 'repo-view')
|
|
.route('/repository/:namespace/:name/tag/:tag', 'repo-view')
|
|
|
|
// Image View
|
|
.route('/repository/:namespace/:name/image/:image', 'image-view')
|
|
|
|
// Repo Build View
|
|
.route('/repository/:namespace/:name/build/:buildid', 'build-view')
|
|
|
|
// Create repository notification
|
|
.route('/repository/:namespace/:name/create-notification', 'create-repository-notification')
|
|
|
|
// Repo List
|
|
.route('/repository/', 'repo-list')
|
|
|
|
// Organizations
|
|
.route('/organizations/', 'organizations')
|
|
|
|
// New Organization
|
|
.route('/organizations/new/', 'new-organization')
|
|
|
|
// View Organization
|
|
.route('/organization/:orgname', 'org-view')
|
|
|
|
// View Organization Team
|
|
.route('/organization/:orgname/teams/:teamname', 'team-view')
|
|
|
|
// Organization View Application
|
|
.route('/organization/:orgname/application/:clientid', 'manage-application')
|
|
|
|
// View Organization Billing
|
|
.route('/organization/:orgname/billing', 'billing')
|
|
|
|
// View Organization Billing Invoices
|
|
.route('/organization/:orgname/billing/invoices', 'invoices')
|
|
|
|
// View User
|
|
.route('/user/:username', 'user-view')
|
|
|
|
// View User Billing
|
|
.route('/user/:username/billing', 'billing')
|
|
|
|
// View User Billing Invoices
|
|
.route('/user/:username/billing/invoices', 'invoices')
|
|
|
|
// Sign In
|
|
.route('/signin/', 'signin')
|
|
|
|
// New Repository
|
|
.route('/new/', 'new-repo')
|
|
|
|
// Plans
|
|
.route('/plans/', 'plans')
|
|
|
|
// Tutorial
|
|
.route('/tutorial/', 'tutorial')
|
|
|
|
// Contact
|
|
.route('/contact/', 'contact')
|
|
|
|
// About
|
|
.route('/about/', 'about')
|
|
|
|
// Security
|
|
.route('/security/', 'security')
|
|
|
|
// TOS
|
|
.route('/tos', 'tos')
|
|
|
|
// Privacy
|
|
.route('/privacy', 'privacy')
|
|
|
|
// Change username
|
|
.route('/updateuser', 'update-user')
|
|
|
|
// Landing Page
|
|
.route('/', 'landing')
|
|
|
|
// Tour
|
|
.route('/tour/', 'tour')
|
|
.route('/tour/features', 'tour')
|
|
.route('/tour/organizations', 'tour')
|
|
.route('/tour/enterprise', 'tour')
|
|
|
|
// Confirm Invite
|
|
.route('/confirminvite', 'confirm-invite')
|
|
|
|
// Enterprise marketing page
|
|
.route('/enterprise', 'enterprise')
|
|
|
|
// Public Repo Experiments
|
|
.route('/__exp/publicRepo', 'public-repo-exp')
|
|
|
|
// 404/403
|
|
.route('/:catchall', 'error-view')
|
|
.route('/:catch/:all', 'error-view')
|
|
.route('/:catch/:all/:things', 'error-view')
|
|
.route('/:catch/:all/:things/:here', 'error-view');
|
|
}]);
|
|
|
|
// Configure compile provider to add additional URL prefixes to the sanitization list. We use
|
|
// these on the Contact page.
|
|
quayApp.config(function($compileProvider) {
|
|
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|irc):/);
|
|
});
|
|
|
|
// Configure the API provider.
|
|
quayApp.config(function(RestangularProvider) {
|
|
RestangularProvider.setBaseUrl('/api/v1/');
|
|
});
|
|
|
|
// Configure analytics.
|
|
if (window.__config && window.__config.MIXPANEL_KEY) {
|
|
quayApp.config(['$analyticsProvider', function($analyticsProvider) {
|
|
$analyticsProvider.virtualPageviews(true);
|
|
}]);
|
|
}
|
|
|
|
// Configure sentry.
|
|
if (window.__config && window.__config.SENTRY_PUBLIC_DSN) {
|
|
quayApp.config(function($provide) {
|
|
$provide.decorator("$exceptionHandler", function($delegate) {
|
|
return function(ex, cause) {
|
|
$delegate(ex, cause);
|
|
Raven.captureException(ex, {extra: {cause: cause}});
|
|
};
|
|
});
|
|
});
|
|
}
|
|
|
|
// Run the application.
|
|
quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', 'PlanService', '$http', '$timeout', 'CookieService', 'Features', '$anchorScroll', 'UtilService', 'MetaService', 'UIService',
|
|
function($location, $rootScope, Restangular, UserService, PlanService, $http, $timeout, CookieService, Features, $anchorScroll, UtilService, MetaService, UIService) {
|
|
|
|
var defaultTitle = window.__config['REGISTRY_TITLE'] || 'Quay Container Registry';
|
|
|
|
// Handle session security.
|
|
Restangular.setDefaultRequestParams(['post', 'put', 'remove', 'delete'], {'_csrf_token': window.__token || ''});
|
|
|
|
// Handle session expiration.
|
|
Restangular.setErrorInterceptor(function(response) {
|
|
if (response.status == 503) {
|
|
$('#cannotContactService').modal({});
|
|
return false;
|
|
}
|
|
|
|
if (response.status == 500) {
|
|
document.location = '/500';
|
|
return false;
|
|
}
|
|
|
|
if (!response.data) {
|
|
return true;
|
|
}
|
|
|
|
var invalid_token = response.data['title'] == 'invalid_token' || response.data['error_type'] == 'invalid_token';
|
|
if (response.status == 401 && invalid_token && response.data['session_required'] !== false) {
|
|
$('#sessionexpiredModal').modal({});
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
// Check if we need to redirect based on a previously chosen plan.
|
|
var result = PlanService.handleNotedPlan();
|
|
|
|
// Check to see if we need to show a redirection page.
|
|
var redirectUrl = CookieService.get('quay.redirectAfterLoad');
|
|
CookieService.clear('quay.redirectAfterLoad');
|
|
|
|
if (!result && redirectUrl && redirectUrl.indexOf(window.location.href) == 0) {
|
|
window.location = redirectUrl;
|
|
return;
|
|
}
|
|
|
|
$rootScope.$watch('description', function(description) {
|
|
if (!description) {
|
|
description = 'Hosted private docker repositories. Includes full user management and history. Free for public repositories.';
|
|
}
|
|
|
|
// Note: We set the content of the description tag manually here rather than using Angular binding
|
|
// because we need the <meta> tag to have a default description that is not of the form "{{ description }}",
|
|
// we read by tools that do not properly invoke the Angular code.
|
|
$('#descriptionTag').attr('content', description);
|
|
});
|
|
|
|
// Listen for scope changes and update the title and description accordingly.
|
|
$rootScope.$watch(function() {
|
|
var title = MetaService.getTitle($rootScope.currentPage) || defaultTitle;
|
|
if ($rootScope.title != title) {
|
|
$rootScope.title = title;
|
|
}
|
|
|
|
var description = MetaService.getDescription($rootScope.currentPage) || '';
|
|
if ($rootScope.description != description) {
|
|
$rootScope.description = description;
|
|
}
|
|
});
|
|
|
|
$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
|
|
$rootScope.current = current.$$route;
|
|
$rootScope.currentPage = current;
|
|
|
|
$rootScope.pageClass = '';
|
|
|
|
if (!current.$$route) { return; }
|
|
|
|
var pageClass = current.$$route.pageClass || '';
|
|
if (typeof pageClass != 'string') {
|
|
pageClass = pageClass(Features);
|
|
}
|
|
|
|
|
|
$rootScope.pageClass = pageClass;
|
|
$rootScope.newLayout = !!current.$$route.newLayout;
|
|
$rootScope.fixFooter = !!current.$$route.fixFooter;
|
|
|
|
$anchorScroll();
|
|
});
|
|
|
|
var initallyChecked = false;
|
|
window.__isLoading = function() {
|
|
if (!initallyChecked) {
|
|
initallyChecked = true;
|
|
return true;
|
|
}
|
|
return $http.pendingRequests.length > 0;
|
|
};
|
|
}]);
|