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'); } // 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', function($routeProvider, $locationProvider, pages) { $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 AngularRouteBuilder($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 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; }; }]);