2013-12-10 06:38:05 +00:00
|
|
|
var TEAM_PATTERN = '^[a-zA-Z][a-zA-Z0-9]+$';
|
2014-05-20 21:58:39 +00:00
|
|
|
var ROBOT_PATTERN = '^[a-zA-Z][a-zA-Z0-9]{3,29}$';
|
2014-10-01 18:23:15 +00:00
|
|
|
var USER_PATTERN = '^[a-z0-9_]{4,30}$';
|
2013-12-10 06:38:05 +00:00
|
|
|
|
2015-02-20 23:15:48 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2014-04-09 01:10:33 +00:00
|
|
|
quayDependencies = ['ngRoute', 'chieffancypants.loadingBar', 'angular-tour', 'restangular', 'angularMoment',
|
|
|
|
'mgcrea.ngStrap', 'ngCookies', 'ngSanitize', 'angular-md5', 'pasvaz.bindonce', 'ansiToHtml',
|
2015-02-20 23:15:48 +00:00
|
|
|
'ngAnimate', 'core-ui', 'core-config-setup', 'quayPages'];
|
2014-04-09 01:10:33 +00:00
|
|
|
|
|
|
|
if (window.__config && window.__config.MIXPANEL_KEY) {
|
|
|
|
quayDependencies.push('angulartics');
|
|
|
|
quayDependencies.push('angulartics.mixpanel');
|
|
|
|
}
|
|
|
|
|
2015-02-19 21:21:54 +00:00
|
|
|
// Define the application.
|
2014-04-09 01:10:33 +00:00
|
|
|
quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoadingBarProvider) {
|
2015-02-19 21:21:54 +00:00
|
|
|
cfpLoadingBarProvider.includeSpinner = false;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Configure the routes.
|
2015-02-20 23:15:48 +00:00
|
|
|
quayApp.config(['$routeProvider', '$locationProvider', 'pages', function($routeProvider, $locationProvider, pages) {
|
2015-02-19 21:21:54 +00:00
|
|
|
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
|
2015-02-20 23:15:48 +00:00
|
|
|
|
2015-02-23 19:48:33 +00:00
|
|
|
var layoutProfile = 'old-layout';
|
|
|
|
|
|
|
|
// Check for the cookie for turning on the new layout.
|
|
|
|
if (document.cookie.toString().indexOf('quay.exp-new-layout=true') >= 0) {
|
|
|
|
layoutProfile = 'layout';
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for the override flag.
|
|
|
|
if (window.location.search.indexOf('old-ui=1') >= 0) {
|
|
|
|
layoutProfile = 'old-layout';
|
|
|
|
}
|
|
|
|
|
|
|
|
window.console.log('Using layout profile: ' + layoutProfile);
|
|
|
|
|
2015-02-20 23:15:48 +00:00
|
|
|
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);
|
|
|
|
|
|
|
|
routeBuilder
|
|
|
|
// Repository View
|
|
|
|
.route('/repository/:namespace/:name', 'repo-view')
|
2015-02-23 19:55:55 +00:00
|
|
|
.route('/repository/:namespace/:name/tag/:tag', 'repo-view')
|
2015-02-20 23:15:48 +00:00
|
|
|
|
|
|
|
// Image View
|
|
|
|
.route('/repository/:namespace/:name/image/:image', 'image-view')
|
|
|
|
|
|
|
|
// Repo Admin
|
|
|
|
.route('/repository/:namespace/:name/admin', 'repo-admin')
|
|
|
|
|
|
|
|
// Repo Builds
|
|
|
|
.route('/repository/:namespace/:name/build', 'repo-build')
|
|
|
|
|
|
|
|
// Repo Build Package
|
|
|
|
.route('/repository/:namespace/:name/build/:buildid/buildpack', 'build-package')
|
|
|
|
|
|
|
|
// Repo List
|
|
|
|
.route('/repository/', 'repo-list')
|
|
|
|
|
|
|
|
// Organizations
|
|
|
|
.route('/organizations/', 'organizations')
|
|
|
|
|
|
|
|
// New Organization
|
|
|
|
.route('/organizations/new/', 'new-organization')
|
|
|
|
|
|
|
|
// View Organization
|
|
|
|
.route('/organization/:orgname', 'org-view')
|
|
|
|
|
|
|
|
// Organization Admin
|
|
|
|
.route('/organization/:orgname/admin', 'org-admin')
|
|
|
|
|
|
|
|
// View Organization Team
|
|
|
|
.route('/organization/:orgname/teams/:teamname', 'team-view')
|
|
|
|
|
|
|
|
// Organization Member Logs
|
|
|
|
.route('/organization/:orgname/logs/:membername', 'org-member-logs')
|
|
|
|
|
|
|
|
// Organization View Application
|
|
|
|
.route('/organization/:orgname/application/:clientid', 'manage-application')
|
|
|
|
|
|
|
|
// User Admin
|
|
|
|
.route('/user/', 'user-admin')
|
|
|
|
|
|
|
|
// Sign In
|
|
|
|
.route('/signin/', 'signin')
|
|
|
|
|
|
|
|
// New Repository
|
|
|
|
.route('/new/', 'new-repo')
|
|
|
|
|
|
|
|
// ER Management
|
|
|
|
.route('/superuser/', 'superuser')
|
|
|
|
|
|
|
|
// ER Setup
|
|
|
|
.route('/setup/', 'setup')
|
|
|
|
|
|
|
|
// Plans
|
|
|
|
.route('/plans/', 'plans')
|
|
|
|
|
|
|
|
// Tutorial
|
|
|
|
.route('/tutorial/', 'tutorial')
|
|
|
|
|
|
|
|
// Contact
|
|
|
|
.route('/contact/', 'contact')
|
|
|
|
|
|
|
|
// About
|
2015-02-23 16:51:07 +00:00
|
|
|
.route('/about/', 'about')
|
2015-02-20 23:15:48 +00:00
|
|
|
|
|
|
|
// Security
|
2015-02-23 16:51:07 +00:00
|
|
|
.route('/security/', 'security')
|
2015-02-20 23:15:48 +00:00
|
|
|
|
|
|
|
// 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')
|
|
|
|
|
2015-02-23 19:48:33 +00:00
|
|
|
// Enable/disable experimental layout
|
|
|
|
.route('/__exp/newlayout', 'exp-new-layout')
|
|
|
|
|
2015-02-20 23:15:48 +00:00
|
|
|
// Default: Redirect to the landing page
|
|
|
|
.otherwise({redirectTo: '/'});
|
2015-02-19 21:21:54 +00:00
|
|
|
}]);
|
2014-01-14 21:01:37 +00:00
|
|
|
|
2015-02-19 21:21:54 +00:00
|
|
|
// 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):/);
|
|
|
|
});
|
2014-01-14 21:01:37 +00:00
|
|
|
|
2015-02-19 21:21:54 +00:00
|
|
|
// Configure the API provider.
|
|
|
|
quayApp.config(function(RestangularProvider) {
|
|
|
|
RestangularProvider.setBaseUrl('/api/v1/');
|
|
|
|
});
|
2014-04-05 03:26:10 +00:00
|
|
|
|
2015-02-19 21:21:54 +00:00
|
|
|
// Configure analytics.
|
|
|
|
if (window.__config && window.__config.MIXPANEL_KEY) {
|
|
|
|
quayApp.config(['$analyticsProvider', function($analyticsProvider) {
|
|
|
|
$analyticsProvider.virtualPageviews(true);
|
|
|
|
}]);
|
|
|
|
}
|
2014-04-05 03:26:10 +00:00
|
|
|
|
2015-02-19 21:21:54 +00:00
|
|
|
// 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}});
|
2014-04-05 03:26:10 +00:00
|
|
|
};
|
2013-11-05 22:20:43 +00:00
|
|
|
});
|
2015-02-19 21:21:54 +00:00
|
|
|
});
|
|
|
|
}
|
2014-10-22 18:49:33 +00:00
|
|
|
|
2015-02-19 21:21:54 +00:00
|
|
|
// 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) {
|
2013-12-28 19:07:44 +00:00
|
|
|
|
2015-02-23 19:48:33 +00:00
|
|
|
var title = window.__config['REGISTRY_TITLE'] || 'Quay.io';
|
|
|
|
|
2013-12-28 19:07:44 +00:00
|
|
|
// Handle session security.
|
2014-01-27 18:58:12 +00:00
|
|
|
Restangular.setDefaultRequestParams(['post', 'put', 'remove', 'delete'], {'_csrf_token': window.__token || ''});
|
2013-12-28 19:07:44 +00:00
|
|
|
|
2013-12-11 22:50:48 +00:00
|
|
|
// Handle session expiration.
|
2013-11-12 00:26:56 +00:00
|
|
|
Restangular.setErrorInterceptor(function(response) {
|
2014-09-04 18:24:20 +00:00
|
|
|
if (response.status == 401 && response.data['error_type'] == 'invalid_token' &&
|
|
|
|
response.data['session_required'] !== false) {
|
|
|
|
$('#sessionexpiredModal').modal({});
|
|
|
|
return false;
|
2013-11-12 00:03:18 +00:00
|
|
|
}
|
2013-11-12 00:26:56 +00:00
|
|
|
|
2014-08-25 19:30:29 +00:00
|
|
|
if (response.status == 503) {
|
|
|
|
$('#cannotContactService').modal({});
|
2014-05-28 19:22:36 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-03-10 21:01:36 +00:00
|
|
|
if (response.status == 500) {
|
|
|
|
document.location = '/500';
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-11-12 00:26:56 +00:00
|
|
|
return true;
|
2013-11-12 00:03:18 +00:00
|
|
|
});
|
|
|
|
|
2013-12-11 22:50:48 +00:00
|
|
|
// Check if we need to redirect based on a previously chosen plan.
|
2014-03-19 18:27:33 +00:00
|
|
|
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;
|
|
|
|
}
|
2013-12-11 22:50:48 +00:00
|
|
|
|
2013-12-21 03:38:53 +00:00
|
|
|
var changeTab = function(activeTab, opt_timeout) {
|
|
|
|
var checkCount = 0;
|
2013-12-10 04:33:28 +00:00
|
|
|
$timeout(function() {
|
2013-12-21 03:38:53 +00:00
|
|
|
if (checkCount > 5) { return; }
|
|
|
|
checkCount++;
|
|
|
|
|
2013-12-10 04:33:28 +00:00
|
|
|
$('a[data-toggle="tab"]').each(function(index) {
|
|
|
|
var tabName = this.getAttribute('data-target').substr(1);
|
2013-12-21 03:38:53 +00:00
|
|
|
if (tabName != activeTab) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.clientWidth == 0) {
|
|
|
|
changeTab(activeTab, 500);
|
|
|
|
return;
|
2013-12-10 04:33:28 +00:00
|
|
|
}
|
2013-12-21 03:38:53 +00:00
|
|
|
|
2015-02-19 21:21:54 +00:00
|
|
|
UtilService.clickElement(this);
|
2013-12-10 04:33:28 +00:00
|
|
|
});
|
2013-12-21 03:38:53 +00:00
|
|
|
}, opt_timeout);
|
2013-12-10 04:33:28 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
var resetDefaultTab = function() {
|
|
|
|
$timeout(function() {
|
|
|
|
$('a[data-toggle="tab"]').each(function(index) {
|
|
|
|
if (index == 0) {
|
2015-02-19 21:21:54 +00:00
|
|
|
UtilService.clickElement(this);
|
2013-12-10 04:33:28 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-04-11 21:15:03 +00:00
|
|
|
$rootScope.$watch('description', function(description) {
|
|
|
|
if (!description) {
|
|
|
|
description = 'Hosted private docker repositories. Includes full user management and history. Free for public repositories.';
|
|
|
|
}
|
2014-11-24 21:07:38 +00:00
|
|
|
|
2014-04-11 21:15:03 +00:00
|
|
|
// 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.
|
2014-11-24 21:07:38 +00:00
|
|
|
$('#descriptionTag').attr('content', description);
|
2014-04-11 21:15:03 +00:00
|
|
|
});
|
|
|
|
|
2013-12-10 04:33:28 +00:00
|
|
|
$rootScope.$on('$routeUpdate', function(){
|
|
|
|
if ($location.search()['tab']) {
|
|
|
|
changeTab($location.search()['tab']);
|
|
|
|
} else {
|
|
|
|
resetDefaultTab();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2013-09-26 23:07:25 +00:00
|
|
|
$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
|
2014-04-24 04:40:01 +00:00
|
|
|
$rootScope.pageClass = '';
|
2014-04-30 03:48:06 +00:00
|
|
|
$rootScope.current = current.$$route;
|
|
|
|
|
|
|
|
if (!current.$$route) { return; }
|
|
|
|
|
2013-09-26 23:07:25 +00:00
|
|
|
if (current.$$route.title) {
|
|
|
|
$rootScope.title = current.$$route.title;
|
2015-02-23 19:48:33 +00:00
|
|
|
} else {
|
|
|
|
$rootScope.title = title;
|
2013-09-26 23:07:25 +00:00
|
|
|
}
|
2013-11-19 00:03:35 +00:00
|
|
|
|
2014-04-24 04:40:01 +00:00
|
|
|
if (current.$$route.pageClass) {
|
|
|
|
$rootScope.pageClass = current.$$route.pageClass;
|
|
|
|
}
|
|
|
|
|
2014-12-22 20:13:23 +00:00
|
|
|
$rootScope.newLayout = !!current.$$route.newLayout;
|
|
|
|
|
2013-11-19 00:03:35 +00:00
|
|
|
if (current.$$route.description) {
|
|
|
|
$rootScope.description = current.$$route.description;
|
|
|
|
} else {
|
2014-04-11 21:15:03 +00:00
|
|
|
$rootScope.description = '';
|
2013-11-19 00:03:35 +00:00
|
|
|
}
|
2013-11-20 21:17:47 +00:00
|
|
|
|
2013-11-24 01:03:08 +00:00
|
|
|
$rootScope.fixFooter = !!current.$$route.fixFooter;
|
2014-05-21 19:28:38 +00:00
|
|
|
$anchorScroll();
|
2013-12-10 04:33:28 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
$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.
|
2014-12-22 20:13:23 +00:00
|
|
|
$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;
|
|
|
|
}
|
2014-02-20 23:57:49 +00:00
|
|
|
|
2014-12-22 20:13:23 +00:00
|
|
|
$location.search(newSearch);
|
|
|
|
});
|
2014-11-24 21:07:38 +00:00
|
|
|
|
2014-12-22 20:13:23 +00:00
|
|
|
e.preventDefault();
|
|
|
|
});
|
2013-12-10 04:33:28 +00:00
|
|
|
|
2014-12-22 20:13:23 +00:00
|
|
|
if (activeTab) {
|
|
|
|
changeTab(activeTab);
|
|
|
|
}
|
2015-01-23 22:19:15 +00:00
|
|
|
}, 400); // 400ms to make sure angular has rendered.
|
2013-09-26 23:07:25 +00:00
|
|
|
});
|
2013-11-18 22:11:06 +00:00
|
|
|
|
|
|
|
var initallyChecked = false;
|
|
|
|
window.__isLoading = function() {
|
|
|
|
if (!initallyChecked) {
|
|
|
|
initallyChecked = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return $http.pendingRequests.length > 0;
|
|
|
|
};
|
2013-09-27 20:12:51 +00:00
|
|
|
}]);
|