var TEAM_PATTERN = '^[a-zA-Z][a-zA-Z0-9]+$'; var ROBOT_PATTERN = '^[a-zA-Z][a-zA-Z0-9]{3,29}$'; var USER_PATTERN = '^[a-z0-9_]{4,30}$'; quayDependencies = ['ngRoute', 'chieffancypants.loadingBar', 'angular-tour', 'restangular', 'angularMoment', 'mgcrea.ngStrap', 'ngCookies', 'ngSanitize', 'angular-md5', 'pasvaz.bindonce', 'ansiToHtml', 'ngAnimate', 'core-ui', 'core-config-setup']; if (window.__config && window.__config.MIXPANEL_KEY) { quayDependencies.push('angulartics'); quayDependencies.push('angulartics.mixpanel'); } // Define the application. quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoadingBarProvider) { cfpLoadingBarProvider.includeSpinner = false; }); // Configure the routes. quayApp.config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) { var title = window.__config['REGISTRY_TITLE'] || 'Quay.io'; $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, fixFooter: false, reloadOnSearch: false}). when('/repository/:namespace/:name/tag/:tag', {templateUrl: '/static/partials/view-repo.html', controller: RepoCtrl, fixFooter: false}). when('/repository/:namespace/:name/image/:image', {templateUrl: '/static/partials/image-view.html', controller: ImageViewCtrl, reloadOnSearch: false}). when('/repository/:namespace/:name/admin', {templateUrl: '/static/partials/repo-admin.html', controller:RepoAdminCtrl, reloadOnSearch: false}). when('/repository/:namespace/:name/build', {templateUrl: '/static/partials/repo-build.html', controller:RepoBuildCtrl, reloadOnSearch: false}). when('/repository/:namespace/:name/build/:buildid/buildpack', {templateUrl: '/static/partials/build-package.html', controller:BuildPackageCtrl, reloadOnSearch: false}). when('/repository/', {title: 'Repositories', description: 'Public and private docker repositories list', templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl, reloadOnSearch: false}). when('/user/', {title: 'Account Settings', description:'Account settings for ' + title, templateUrl: '/static/partials/user-admin.html', reloadOnSearch: false, controller: UserAdminCtrl}). when('/superuser/', {title: 'Enterprise Registry Management', description:'Admin panel for ' + title, templateUrl: '/static/partials/super-user.html', reloadOnSearch: false, controller: SuperUserAdminCtrl, newLayout: true}). when('/setup/', {title: 'Enterprise Registry Setup', description:'Setup for ' + title, templateUrl: '/static/partials/setup.html', reloadOnSearch: false, controller: SetupCtrl, newLayout: true}). when('/guide/', {title: 'Guide', description:'Guide to using private docker repositories on ' + title, templateUrl: '/static/partials/guide.html', controller: GuideCtrl}). when('/tutorial/', {title: 'Tutorial', description:'Interactive tutorial for using ' + title, templateUrl: '/static/partials/tutorial.html', controller: TutorialCtrl}). when('/contact/', {title: 'Contact Us', description:'Different ways for you to get a hold of us when you need us most.', templateUrl: '/static/partials/contact.html', controller: ContactCtrl}). when('/about/', {title: 'About Us', description:'Information about the Quay.io team and the company.', templateUrl: '/static/partials/about.html'}). 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'}). when('/signin/', {title: 'Sign In', description: 'Sign into ' + title, templateUrl: '/static/partials/signin.html', controller: SignInCtrl, reloadOnSearch: false}). 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 ' + title, 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, reloadOnSearch: false}). when('/organization/:orgname/teams/:teamname', {templateUrl: '/static/partials/team-view.html', controller: TeamViewCtrl}). when('/organization/:orgname/logs/:membername', {templateUrl: '/static/partials/org-member-logs.html', controller: OrgMemberLogsCtrl}). when('/organization/:orgname/application/:clientid', {templateUrl: '/static/partials/manage-application.html', controller: ManageApplicationCtrl, reloadOnSearch: false}). when('/v1/', {title: 'Activation information', templateUrl: '/static/partials/v1-page.html', controller: V1Ctrl}). when('/tour/', {title: title + ' Tour', templateUrl: '/static/partials/tour.html', controller: TourCtrl}). when('/tour/organizations', {title: 'Teams and Organizations Tour', templateUrl: '/static/partials/tour.html', controller: TourCtrl}). when('/tour/features', {title: title + ' Features', templateUrl: '/static/partials/tour.html', controller: TourCtrl}). when('/tour/enterprise', {title: 'Enterprise Edition', templateUrl: '/static/partials/tour.html', controller: TourCtrl}). when('/confirminvite', {title: 'Confirm Invite', templateUrl: '/static/partials/confirm-invite.html', controller: ConfirmInviteCtrl, reloadOnSearch: false}). when('/', {title: 'Hosted Private Docker Registry', templateUrl: '/static/partials/landing.html', controller: LandingCtrl, pageClass: 'landing-page'}). otherwise({redirectTo: '/'}); }]); // 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', function($location, $rootScope, Restangular, UserService, PlanService, $http, $timeout, CookieService, Features, $anchorScroll, UtilService) { // Handle session security. Restangular.setDefaultRequestParams(['post', 'put', 'remove', 'delete'], {'_csrf_token': window.__token || ''}); // Handle session expiration. Restangular.setErrorInterceptor(function(response) { if (response.status == 401 && response.data['error_type'] == 'invalid_token' && response.data['session_required'] !== false) { $('#sessionexpiredModal').modal({}); return false; } if (response.status == 503) { $('#cannotContactService').modal({}); return false; } if (response.status == 500) { document.location = '/500'; 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; } var changeTab = function(activeTab, opt_timeout) { var checkCount = 0; $timeout(function() { if (checkCount > 5) { return; } checkCount++; $('a[data-toggle="tab"]').each(function(index) { var tabName = this.getAttribute('data-target').substr(1); if (tabName != activeTab) { return; } if (this.clientWidth == 0) { changeTab(activeTab, 500); return; } UtilService.clickElement(this); }); }, opt_timeout); }; var resetDefaultTab = function() { $timeout(function() { $('a[data-toggle="tab"]').each(function(index) { if (index == 0) { UtilService.clickElement(this); } }); }); }; $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); }); $rootScope.$on('$routeUpdate', function(){ if ($location.search()['tab']) { changeTab($location.search()['tab']); } else { resetDefaultTab(); } }); $rootScope.$on('$routeChangeSuccess', function (event, current, previous) { $rootScope.pageClass = ''; $rootScope.current = current.$$route; if (!current.$$route) { return; } if (current.$$route.title) { $rootScope.title = current.$$route.title; } if (current.$$route.pageClass) { $rootScope.pageClass = current.$$route.pageClass; } $rootScope.newLayout = !!current.$$route.newLayout; if (current.$$route.description) { $rootScope.description = current.$$route.description; } else { $rootScope.description = ''; } $rootScope.fixFooter = !!current.$$route.fixFooter; $anchorScroll(); }); $rootScope.$on('$viewContentLoaded', function(event, current) { var activeTab = $location.search()['tab']; // Setup deep linking of tabs. This will change the search field of the URL whenever a tab // is changed in the UI. $timeout(function() { $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) { var tabName = e.target.getAttribute('data-target').substr(1); $rootScope.$apply(function() { var isDefaultTab = $('a[data-toggle="tab"]')[0] == e.target; var newSearch = $.extend($location.search(), {}); if (isDefaultTab) { delete newSearch['tab']; } else { newSearch['tab'] = tabName; } $location.search(newSearch); }); e.preventDefault(); }); if (activeTab) { changeTab(activeTab); } }, 400); // 400ms to make sure angular has rendered. }); var initallyChecked = false; window.__isLoading = function() { if (!initallyChecked) { initallyChecked = true; return true; } return $http.pendingRequests.length > 0; }; }]);