Merge remote-tracking branch 'origin/master' into tagyourit
Conflicts: static/css/quay.css static/js/graphing.js static/partials/view-repo.html test/data/test.db
This commit is contained in:
commit
3f42d15335
132 changed files with 4266 additions and 1924 deletions
631
static/js/app.js
631
static/js/app.js
|
@ -102,7 +102,17 @@ function getMarkedDown(string) {
|
|||
return Markdown.getSanitizingConverter().makeHtml(string || '');
|
||||
}
|
||||
|
||||
quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angular-tour', 'restangular', 'angularMoment', 'angulartics', /*'angulartics.google.analytics',*/ 'angulartics.mixpanel', 'mgcrea.ngStrap', 'ngCookies', 'ngSanitize', 'angular-md5', 'pasvaz.bindonce', 'ansiToHtml', 'ngAnimate'], function($provide, cfpLoadingBarProvider) {
|
||||
|
||||
quayDependencies = ['ngRoute', 'chieffancypants.loadingBar', 'angular-tour', 'restangular', 'angularMoment',
|
||||
'mgcrea.ngStrap', 'ngCookies', 'ngSanitize', 'angular-md5', 'pasvaz.bindonce', 'ansiToHtml',
|
||||
'ngAnimate'];
|
||||
|
||||
if (window.__config && window.__config.MIXPANEL_KEY) {
|
||||
quayDependencies.push('angulartics');
|
||||
quayDependencies.push('angulartics.mixpanel');
|
||||
}
|
||||
|
||||
quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoadingBarProvider) {
|
||||
cfpLoadingBarProvider.includeSpinner = false;
|
||||
|
||||
/**
|
||||
|
@ -325,6 +335,42 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
}]);
|
||||
|
||||
|
||||
$provide.factory('UIService', [function() {
|
||||
var uiService = {};
|
||||
|
||||
uiService.hidePopover = function(elem) {
|
||||
var popover = $('#signupButton').data('bs.popover');
|
||||
if (popover) {
|
||||
popover.hide();
|
||||
}
|
||||
};
|
||||
|
||||
uiService.showPopover = function(elem, content) {
|
||||
var popover = $(elem).data('bs.popover');
|
||||
if (!popover) {
|
||||
$(elem).popover({'content': '-', 'placement': 'left'});
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
var popover = $(elem).data('bs.popover');
|
||||
popover.options.content = content;
|
||||
popover.show();
|
||||
}, 500);
|
||||
};
|
||||
|
||||
uiService.showFormError = function(elem, result) {
|
||||
var message = result.data['message'] || result.data['error_description'] || '';
|
||||
if (message) {
|
||||
uiService.showPopover(elem, message);
|
||||
} else {
|
||||
uiService.hidePopover(elem);
|
||||
}
|
||||
};
|
||||
|
||||
return uiService;
|
||||
}]);
|
||||
|
||||
|
||||
$provide.factory('UtilService', ['$sanitize', function($sanitize) {
|
||||
var utilService = {};
|
||||
|
||||
|
@ -439,6 +485,63 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
return metadataService;
|
||||
}]);
|
||||
|
||||
$provide.factory('Features', [function() {
|
||||
if (!window.__features) {
|
||||
return {};
|
||||
}
|
||||
|
||||
var features = window.__features;
|
||||
features.getFeature = function(name, opt_defaultValue) {
|
||||
var value = features[name];
|
||||
if (value == null) {
|
||||
return opt_defaultValue;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
features.hasFeature = function(name) {
|
||||
return !!features.getFeature(name);
|
||||
};
|
||||
|
||||
features.matchesFeatures = function(list) {
|
||||
for (var i = 0; i < list.length; ++i) {
|
||||
var value = features.getFeature(list[i]);
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
return features;
|
||||
}]);
|
||||
|
||||
$provide.factory('Config', [function() {
|
||||
if (!window.__config) {
|
||||
return {};
|
||||
}
|
||||
|
||||
var config = window.__config;
|
||||
config.getDomain = function() {
|
||||
return config['SERVER_HOSTNAME'];
|
||||
};
|
||||
|
||||
config.getUrl = function(opt_path) {
|
||||
var path = opt_path || '';
|
||||
return config['PREFERRED_URL_SCHEME'] + '://' + config['SERVER_HOSTNAME'] + path;
|
||||
};
|
||||
|
||||
config.getValue = function(name, opt_defaultValue) {
|
||||
var value = config[name];
|
||||
if (value == null) {
|
||||
return opt_defaultValue;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
return config;
|
||||
}]);
|
||||
|
||||
$provide.factory('ApiService', ['Restangular', function(Restangular) {
|
||||
var apiService = {};
|
||||
|
||||
|
@ -622,8 +725,8 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
return cookieService;
|
||||
}]);
|
||||
|
||||
$provide.factory('UserService', ['ApiService', 'CookieService', '$rootScope',
|
||||
function(ApiService, CookieService, $rootScope) {
|
||||
$provide.factory('UserService', ['ApiService', 'CookieService', '$rootScope', 'Config',
|
||||
function(ApiService, CookieService, $rootScope, Config) {
|
||||
var userResponse = {
|
||||
verified: false,
|
||||
anonymous: true,
|
||||
|
@ -653,15 +756,17 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
userResponse = loadedUser;
|
||||
|
||||
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()
|
||||
})
|
||||
if (Config.MIXPANEL_KEY) {
|
||||
mixpanel.identify(userResponse.username);
|
||||
mixpanel.people.set({
|
||||
'$email': userResponse.email,
|
||||
'$username': userResponse.username,
|
||||
'verified': userResponse.verified
|
||||
});
|
||||
mixpanel.people.set_once({
|
||||
'$created': new Date()
|
||||
})
|
||||
}
|
||||
|
||||
if (window.olark !== undefined) {
|
||||
olark('api.visitor.getDetails', function(details) {
|
||||
|
@ -735,8 +840,8 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
return userService;
|
||||
}]);
|
||||
|
||||
$provide.factory('NotificationService', ['$rootScope', '$interval', 'UserService', 'ApiService', 'StringBuilderService', 'PlanService', 'UserService',
|
||||
function($rootScope, $interval, UserService, ApiService, StringBuilderService, PlanService, UserService) {
|
||||
$provide.factory('NotificationService', ['$rootScope', '$interval', 'UserService', 'ApiService', 'StringBuilderService', 'PlanService', 'UserService', 'Config',
|
||||
function($rootScope, $interval, UserService, ApiService, StringBuilderService, PlanService, UserService, Config) {
|
||||
var notificationService = {
|
||||
'user': null,
|
||||
'notifications': [],
|
||||
|
@ -830,28 +935,18 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
return notificationService;
|
||||
}]);
|
||||
|
||||
$provide.factory('KeyService', ['$location', function($location) {
|
||||
$provide.factory('KeyService', ['$location', 'Config', function($location, Config) {
|
||||
var keyService = {}
|
||||
|
||||
if ($location.host() === 'quay.io') {
|
||||
keyService['stripePublishableKey'] = 'pk_live_P5wLU0vGdHnZGyKnXlFG4oiu';
|
||||
keyService['githubClientId'] = '5a8c08b06c48d89d4d1e';
|
||||
keyService['githubRedirectUri'] = 'https://quay.io/oauth2/github/callback';
|
||||
} else if($location.host() === 'staging.quay.io') {
|
||||
keyService['stripePublishableKey'] = 'pk_live_P5wLU0vGdHnZGyKnXlFG4oiu';
|
||||
keyService['githubClientId'] = '4886304accbc444f0471';
|
||||
keyService['githubRedirectUri'] = 'https://staging.quay.io/oauth2/github/callback';
|
||||
} else {
|
||||
keyService['stripePublishableKey'] = 'pk_test_uEDHANKm9CHCvVa2DLcipGRh';
|
||||
keyService['githubClientId'] = 'cfbc4aca88e5c1b40679';
|
||||
keyService['githubRedirectUri'] = 'http://localhost:5000/oauth2/github/callback';
|
||||
}
|
||||
|
||||
keyService['stripePublishableKey'] = Config['STRIPE_PUBLISHABLE_KEY'];
|
||||
keyService['githubClientId'] = Config['GITHUB_CLIENT_ID'];
|
||||
keyService['githubLoginClientId'] = Config['GITHUB_LOGIN_CLIENT_ID'];
|
||||
keyService['githubRedirectUri'] = Config.getUrl('/oauth2/github/callback');
|
||||
return keyService;
|
||||
}]);
|
||||
|
||||
$provide.factory('PlanService', ['KeyService', 'UserService', 'CookieService', 'ApiService',
|
||||
function(KeyService, UserService, CookieService, ApiService) {
|
||||
|
||||
$provide.factory('PlanService', ['KeyService', 'UserService', 'CookieService', 'ApiService', 'Features', 'Config',
|
||||
function(KeyService, UserService, CookieService, ApiService, Features, Config) {
|
||||
var plans = null;
|
||||
var planDict = {};
|
||||
var planService = {};
|
||||
|
@ -877,7 +972,9 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
};
|
||||
|
||||
planService.notePlan = function(planId) {
|
||||
CookieService.putSession('quay.notedplan', planId);
|
||||
if (Features.BILLING) {
|
||||
CookieService.putSession('quay.notedplan', planId);
|
||||
}
|
||||
};
|
||||
|
||||
planService.isOrgCompatible = function(plan) {
|
||||
|
@ -903,7 +1000,7 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
|
||||
planService.handleNotedPlan = function() {
|
||||
var planId = planService.getAndResetNotedPlan();
|
||||
if (!planId) { return false; }
|
||||
if (!planId || !Features.BILLING) { return false; }
|
||||
|
||||
UserService.load(function() {
|
||||
if (UserService.currentUser().anonymous) {
|
||||
|
@ -948,6 +1045,8 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
};
|
||||
|
||||
planService.verifyLoaded = function(callback) {
|
||||
if (!Features.BILLING) { return; }
|
||||
|
||||
if (plans) {
|
||||
callback(plans);
|
||||
return;
|
||||
|
@ -1007,10 +1106,14 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
};
|
||||
|
||||
planService.getSubscription = function(orgname, success, failure) {
|
||||
ApiService.getSubscription(orgname).then(success, failure);
|
||||
if (!Features.BILLING) { return; }
|
||||
|
||||
ApiService.getSubscription(orgname).then(success, failure);
|
||||
};
|
||||
|
||||
planService.setSubscription = function(orgname, planId, success, failure, opt_token) {
|
||||
if (!Features.BILLING) { return; }
|
||||
|
||||
var subscriptionDetails = {
|
||||
plan: planId
|
||||
};
|
||||
|
@ -1030,6 +1133,8 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
};
|
||||
|
||||
planService.getCardInfo = function(orgname, callback) {
|
||||
if (!Features.BILLING) { return; }
|
||||
|
||||
ApiService.getCard(orgname).then(function(resp) {
|
||||
callback(resp.card);
|
||||
}, function() {
|
||||
|
@ -1038,6 +1143,8 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
};
|
||||
|
||||
planService.changePlan = function($scope, orgname, planId, callbacks) {
|
||||
if (!Features.BILLING) { return; }
|
||||
|
||||
if (callbacks['started']) {
|
||||
callbacks['started']();
|
||||
}
|
||||
|
@ -1063,6 +1170,8 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
};
|
||||
|
||||
planService.changeCreditCard = function($scope, orgname, callbacks) {
|
||||
if (!Features.BILLING) { return; }
|
||||
|
||||
if (callbacks['opening']) {
|
||||
callbacks['opening']();
|
||||
}
|
||||
|
@ -1119,6 +1228,8 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
};
|
||||
|
||||
planService.showSubscribeDialog = function($scope, orgname, planId, callbacks) {
|
||||
if (!Features.BILLING) { return; }
|
||||
|
||||
if (callbacks['opening']) {
|
||||
callbacks['opening']();
|
||||
}
|
||||
|
@ -1128,7 +1239,9 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
if (submitted) { return; }
|
||||
submitted = true;
|
||||
|
||||
mixpanel.track('plan_subscribe');
|
||||
if (Config.MIXPANEL_KEY) {
|
||||
mixpanel.track('plan_subscribe');
|
||||
}
|
||||
|
||||
$scope.$apply(function() {
|
||||
if (callbacks['started']) {
|
||||
|
@ -1146,7 +1259,7 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
email: email,
|
||||
amount: planDetails.price,
|
||||
currency: 'usd',
|
||||
name: 'Quay ' + planDetails.title + ' Subscription',
|
||||
name: 'Quay.io ' + planDetails.title + ' Subscription',
|
||||
description: 'Up to ' + planDetails.privateRepos + ' private repositories',
|
||||
panelLabel: 'Subscribe',
|
||||
token: submitToken,
|
||||
|
@ -1189,10 +1302,8 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
});
|
||||
};
|
||||
}).
|
||||
config(['$routeProvider', '$locationProvider', '$analyticsProvider',
|
||||
function($routeProvider, $locationProvider, $analyticsProvider) {
|
||||
|
||||
$analyticsProvider.virtualPageviews(true);
|
||||
config(['$routeProvider', '$locationProvider',
|
||||
function($routeProvider, $locationProvider) {
|
||||
|
||||
$locationProvider.html5Mode(true);
|
||||
|
||||
|
@ -1213,6 +1324,8 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl}).
|
||||
when('/user/', {title: 'Account Settings', description:'Account settings for Quay.io', templateUrl: '/static/partials/user-admin.html',
|
||||
reloadOnSearch: false, controller: UserAdminCtrl}).
|
||||
when('/superuser/', {title: 'Superuser Admin Panel', description:'Admin panel for Quay.io', templateUrl: '/static/partials/super-user.html',
|
||||
reloadOnSearch: false, controller: SuperUserAdminCtrl}).
|
||||
when('/guide/', {title: 'Guide', description:'Guide to using private docker repositories on Quay.io', templateUrl: '/static/partials/guide.html',
|
||||
controller: GuideCtrl}).
|
||||
when('/tutorial/', {title: 'Tutorial', description:'Interactive tutorial for using Quay.io', templateUrl: '/static/partials/tutorial.html',
|
||||
|
@ -1245,6 +1358,178 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu
|
|||
RestangularProvider.setBaseUrl('/api/v1/');
|
||||
});
|
||||
|
||||
if (window.__config && window.__config.MIXPANEL_KEY) {
|
||||
quayApp.config(['$analyticsProvider', function($analyticsProvider) {
|
||||
$analyticsProvider.virtualPageviews(true);
|
||||
}]);
|
||||
}
|
||||
|
||||
|
||||
function buildConditionalLinker($animate, name, evaluator) {
|
||||
// Based off of a solution found here: http://stackoverflow.com/questions/20325480/angularjs-whats-the-best-practice-to-add-ngif-to-a-directive-programmatically
|
||||
return function ($scope, $element, $attr, ctrl, $transclude) {
|
||||
var block;
|
||||
var childScope;
|
||||
var roles;
|
||||
|
||||
$attr.$observe(name, function (value) {
|
||||
if (evaluator($scope.$eval(value))) {
|
||||
if (!childScope) {
|
||||
childScope = $scope.$new();
|
||||
$transclude(childScope, function (clone) {
|
||||
block = {
|
||||
startNode: clone[0],
|
||||
endNode: clone[clone.length++] = document.createComment(' end ' + name + ': ' + $attr[name] + ' ')
|
||||
};
|
||||
$animate.enter(clone, $element.parent(), $element);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (childScope) {
|
||||
childScope.$destroy();
|
||||
childScope = null;
|
||||
}
|
||||
|
||||
if (block) {
|
||||
$animate.leave(getBlockElements(block));
|
||||
block = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
quayApp.directive('quayRequire', function ($animate, Features) {
|
||||
return {
|
||||
transclude: 'element',
|
||||
priority: 600,
|
||||
terminal: true,
|
||||
restrict: 'A',
|
||||
link: buildConditionalLinker($animate, 'quayRequire', function(value) {
|
||||
return Features.matchesFeatures(value);
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
quayApp.directive('quayShow', function($animate, Features, Config) {
|
||||
return {
|
||||
priority: 590,
|
||||
restrict: 'A',
|
||||
link: function($scope, $element, $attr, ctrl, $transclude) {
|
||||
$scope.Features = Features;
|
||||
$scope.Config = Config;
|
||||
$scope.$watch($attr.quayShow, function(result) {
|
||||
$animate[!!result ? 'removeClass' : 'addClass']($element, 'ng-hide');
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
quayApp.directive('quayClasses', function(Features, Config) {
|
||||
return {
|
||||
priority: 580,
|
||||
restrict: 'A',
|
||||
link: function($scope, $element, $attr, ctrl, $transclude) {
|
||||
|
||||
// Borrowed from ngClass.
|
||||
function flattenClasses(classVal) {
|
||||
if(angular.isArray(classVal)) {
|
||||
return classVal.join(' ');
|
||||
} else if (angular.isObject(classVal)) {
|
||||
var classes = [], i = 0;
|
||||
angular.forEach(classVal, function(v, k) {
|
||||
if (v) {
|
||||
classes.push(k);
|
||||
}
|
||||
});
|
||||
return classes.join(' ');
|
||||
}
|
||||
|
||||
return classVal;
|
||||
}
|
||||
|
||||
function removeClass(classVal) {
|
||||
$attr.$removeClass(flattenClasses(classVal));
|
||||
}
|
||||
|
||||
|
||||
function addClass(classVal) {
|
||||
$attr.$addClass(flattenClasses(classVal));
|
||||
}
|
||||
|
||||
$scope.$watch($attr.quayClasses, function(result) {
|
||||
var scopeVals = {
|
||||
'Features': Features,
|
||||
'Config': Config
|
||||
};
|
||||
|
||||
for (var expr in result) {
|
||||
if (!result.hasOwnProperty(expr)) { continue; }
|
||||
|
||||
// Evaluate the expression with the entire features list added.
|
||||
var value = $scope.$eval(expr, scopeVals);
|
||||
if (value) {
|
||||
addClass(result[expr]);
|
||||
} else {
|
||||
removeClass(result[expr]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
quayApp.directive('quayInclude', function($compile, $templateCache, $http, Features, Config) {
|
||||
return {
|
||||
priority: 595,
|
||||
restrict: 'A',
|
||||
link: function($scope, $element, $attr, ctrl) {
|
||||
var getTemplate = function(templateName) {
|
||||
var templateUrl = '/static/partials/' + templateName;
|
||||
return $http.get(templateUrl, {cache: $templateCache});
|
||||
};
|
||||
|
||||
var result = $scope.$eval($attr.quayInclude);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
var scopeVals = {
|
||||
'Features': Features,
|
||||
'Config': Config
|
||||
};
|
||||
|
||||
var templatePath = null;
|
||||
for (var expr in result) {
|
||||
if (!result.hasOwnProperty(expr)) { continue; }
|
||||
|
||||
// Evaluate the expression with the entire features list added.
|
||||
var value = $scope.$eval(expr, scopeVals);
|
||||
if (value) {
|
||||
templatePath = result[expr];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!templatePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
var promise = getTemplate(templatePath).success(function(html) {
|
||||
$element.html(html);
|
||||
}).then(function (response) {
|
||||
$element.replaceWith($compile($element.html())($scope));
|
||||
if ($attr.onload) {
|
||||
$scope.$eval($attr.onload);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
quayApp.directive('entityReference', function () {
|
||||
var directiveDefinitionObject = {
|
||||
|
@ -1516,12 +1801,14 @@ quayApp.directive('signinForm', function () {
|
|||
'signInStarted': '&signInStarted',
|
||||
'signedIn': '&signedIn'
|
||||
},
|
||||
controller: function($scope, $location, $timeout, ApiService, KeyService, UserService, CookieService) {
|
||||
controller: function($scope, $location, $timeout, ApiService, KeyService, UserService, CookieService, Features, Config) {
|
||||
$scope.showGithub = function() {
|
||||
if (!Features.GITHUB_LOGIN) { return; }
|
||||
|
||||
$scope.markStarted();
|
||||
|
||||
var mixpanelDistinctIdClause = '';
|
||||
if (mixpanel.get_distinct_id !== undefined) {
|
||||
if (Config.MIXPANEL_KEY && mixpanel.get_distinct_id !== undefined) {
|
||||
$scope.mixpanelDistinctIdClause = "&state=" + encodeURIComponent(mixpanel.get_distinct_id());
|
||||
}
|
||||
|
||||
|
@ -1587,34 +1874,35 @@ quayApp.directive('signupForm', function () {
|
|||
scope: {
|
||||
|
||||
},
|
||||
controller: function($scope, $location, $timeout, ApiService, KeyService, UserService) {
|
||||
controller: function($scope, $location, $timeout, ApiService, KeyService, UserService, Config, UIService) {
|
||||
$('.form-signup').popover();
|
||||
|
||||
angulartics.waitForVendorApi(mixpanel, 500, function(loadedMixpanel) {
|
||||
var mixpanelId = loadedMixpanel.get_distinct_id();
|
||||
$scope.github_state_clause = '&state=' + mixpanelId;
|
||||
});
|
||||
if (Config.MIXPANEL_KEY) {
|
||||
angulartics.waitForVendorApi(mixpanel, 500, function(loadedMixpanel) {
|
||||
var mixpanelId = loadedMixpanel.get_distinct_id();
|
||||
$scope.github_state_clause = '&state=' + mixpanelId;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.githubClientId = KeyService.githubClientId;
|
||||
|
||||
$scope.awaitingConfirmation = false;
|
||||
$scope.registering = false;
|
||||
|
||||
|
||||
$scope.register = function() {
|
||||
$('.form-signup').popover('hide');
|
||||
UIService.hidePopover('#signupButton');
|
||||
$scope.registering = true;
|
||||
|
||||
ApiService.createNewUser($scope.newUser).then(function() {
|
||||
$scope.awaitingConfirmation = true;
|
||||
$scope.registering = false;
|
||||
|
||||
mixpanel.alias($scope.newUser.username);
|
||||
$scope.awaitingConfirmation = true;
|
||||
|
||||
if (Config.MIXPANEL_KEY) {
|
||||
mixpanel.alias($scope.newUser.username);
|
||||
}
|
||||
}, function(result) {
|
||||
$scope.registering = false;
|
||||
$scope.registerError = result.data.message;
|
||||
$timeout(function() {
|
||||
$('.form-signup').popover('show');
|
||||
});
|
||||
UIService.showFormError('#signupButton', result);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -1644,7 +1932,7 @@ quayApp.directive('plansTable', function () {
|
|||
});
|
||||
|
||||
|
||||
quayApp.directive('dockerAuthDialog', function () {
|
||||
quayApp.directive('dockerAuthDialog', function (Config) {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/docker-auth-dialog.html',
|
||||
|
@ -1665,11 +1953,10 @@ quayApp.directive('dockerAuthDialog', function () {
|
|||
|
||||
$scope.downloadCfg = function() {
|
||||
var auth = $.base64.encode($scope.username + ":" + $scope.token);
|
||||
config = {
|
||||
"https://quay.io/v1/": {
|
||||
"auth": auth,
|
||||
"email": ""
|
||||
}
|
||||
config = {}
|
||||
config[Config.getUrl('/v1/')] = {
|
||||
"auth": auth,
|
||||
"email": ""
|
||||
};
|
||||
|
||||
var file = JSON.stringify(config, null, ' ');
|
||||
|
@ -2653,11 +2940,13 @@ quayApp.directive('entitySearch', function () {
|
|||
'isOrganization': '=isOrganization',
|
||||
'isPersistent': '=isPersistent',
|
||||
'currentEntity': '=currentEntity',
|
||||
'clearNow': '=clearNow'
|
||||
'clearNow': '=clearNow',
|
||||
'filter': '=filter',
|
||||
},
|
||||
controller: function($scope, $element, Restangular, UserService, ApiService) {
|
||||
$scope.lazyLoading = true;
|
||||
$scope.isAdmin = false;
|
||||
$scope.currentEntityInternal = $scope.currentEntity;
|
||||
|
||||
$scope.lazyLoad = function() {
|
||||
if (!$scope.namespace || !$scope.lazyLoading) { return; }
|
||||
|
@ -2736,20 +3025,27 @@ quayApp.directive('entitySearch', function () {
|
|||
entity['is_org_member'] = true;
|
||||
}
|
||||
|
||||
$scope.setEntityInternal(entity);
|
||||
$scope.setEntityInternal(entity, false);
|
||||
};
|
||||
|
||||
$scope.clearEntityInternal = function() {
|
||||
$scope.currentEntityInternal = null;
|
||||
$scope.currentEntity = null;
|
||||
|
||||
if ($scope.entitySelected) {
|
||||
$scope.entitySelected(null);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.setEntityInternal = function(entity) {
|
||||
$(input).typeahead('val', $scope.isPersistent ? entity.name : '');
|
||||
$scope.setEntityInternal = function(entity, updateTypeahead) {
|
||||
if (updateTypeahead) {
|
||||
$(input).typeahead('val', $scope.isPersistent ? entity.name : '');
|
||||
} else {
|
||||
$(input).val($scope.isPersistent ? entity.name : '');
|
||||
}
|
||||
|
||||
if ($scope.isPersistent) {
|
||||
$scope.currentEntityInternal = entity;
|
||||
$scope.currentEntity = entity;
|
||||
}
|
||||
|
||||
|
@ -2777,6 +3073,19 @@ quayApp.directive('entitySearch', function () {
|
|||
var datums = [];
|
||||
for (var i = 0; i < data.results.length; ++i) {
|
||||
var entity = data.results[i];
|
||||
if ($scope.filter) {
|
||||
var allowed = $scope.filter;
|
||||
var found = 'user';
|
||||
if (entity.kind == 'user') {
|
||||
found = entity.is_robot ? 'robot' : 'user';
|
||||
} else if (entity.kind == 'team') {
|
||||
found = 'team';
|
||||
}
|
||||
if (allowed.indexOf(found)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
datums.push({
|
||||
'value': entity.name,
|
||||
'tokens': [entity.name],
|
||||
|
@ -2849,7 +3158,7 @@ quayApp.directive('entitySearch', function () {
|
|||
|
||||
$(input).on('typeahead:selected', function(e, datum) {
|
||||
$scope.$apply(function() {
|
||||
$scope.setEntityInternal(datum.entity);
|
||||
$scope.setEntityInternal(datum.entity, true);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -2861,6 +3170,16 @@ quayApp.directive('entitySearch', function () {
|
|||
$scope.$watch('inputTitle', function(title) {
|
||||
input.setAttribute('placeholder', title);
|
||||
});
|
||||
|
||||
$scope.$watch('currentEntity', function(entity) {
|
||||
if ($scope.currentEntityInternal != entity) {
|
||||
if (entity) {
|
||||
$scope.setEntityInternal(entity, false);
|
||||
} else {
|
||||
$scope.clearEntityInternal();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
|
@ -3072,7 +3391,7 @@ quayApp.directive('planManager', function () {
|
|||
}
|
||||
|
||||
if (!$scope.chart) {
|
||||
$scope.chart = new RepositoryUsageChart();
|
||||
$scope.chart = new UsageChart();
|
||||
$scope.chart.draw('repository-usage-chart');
|
||||
}
|
||||
|
||||
|
@ -3398,6 +3717,145 @@ quayApp.directive('dropdownSelectMenu', function () {
|
|||
});
|
||||
|
||||
|
||||
quayApp.directive('setupTriggerDialog', function () {
|
||||
var directiveDefinitionObject = {
|
||||
templateUrl: '/static/directives/setup-trigger-dialog.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'trigger': '=trigger',
|
||||
'counter': '=counter',
|
||||
'canceled': '&canceled',
|
||||
'activated': '&activated'
|
||||
},
|
||||
controller: function($scope, $element, ApiService, UserService) {
|
||||
$scope.show = function() {
|
||||
$scope.pullEntity = null;
|
||||
$scope.publicPull = true;
|
||||
$scope.showPullRequirements = false;
|
||||
|
||||
$('#setupTriggerModal').modal({});
|
||||
$('#setupTriggerModal').on('hidden.bs.modal', function () {
|
||||
$scope.$apply(function() {
|
||||
$scope.cancelSetupTrigger();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.isNamespaceAdmin = function(namespace) {
|
||||
return UserService.isNamespaceAdmin(namespace);
|
||||
};
|
||||
|
||||
$scope.cancelSetupTrigger = function() {
|
||||
$scope.canceled({'trigger': $scope.trigger});
|
||||
};
|
||||
|
||||
$scope.hide = function() {
|
||||
$('#setupTriggerModal').modal('hide');
|
||||
};
|
||||
|
||||
$scope.setPublicPull = function(value) {
|
||||
$scope.publicPull = value;
|
||||
};
|
||||
|
||||
$scope.checkAnalyze = function(isValid) {
|
||||
if (!isValid) {
|
||||
$scope.publicPull = true;
|
||||
$scope.pullEntity = null;
|
||||
$scope.showPullRequirements = false;
|
||||
$scope.checkingPullRequirements = false;
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.checkingPullRequirements = true;
|
||||
$scope.showPullRequirements = true;
|
||||
$scope.pullRequirements = null;
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'trigger_uuid': $scope.trigger.id
|
||||
};
|
||||
|
||||
var data = {
|
||||
'config': $scope.trigger.config
|
||||
};
|
||||
|
||||
ApiService.analyzeBuildTrigger(data, params).then(function(resp) {
|
||||
$scope.pullRequirements = resp;
|
||||
|
||||
if (resp['status'] == 'publicbase') {
|
||||
$scope.publicPull = true;
|
||||
$scope.pullEntity = null;
|
||||
} else if (resp['namespace']) {
|
||||
$scope.publicPull = false;
|
||||
|
||||
if (resp['robots'] && resp['robots'].length > 0) {
|
||||
$scope.pullEntity = resp['robots'][0];
|
||||
} else {
|
||||
$scope.pullEntity = null;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.checkingPullRequirements = false;
|
||||
}, function(resp) {
|
||||
$scope.pullRequirements = resp;
|
||||
$scope.checkingPullRequirements = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.activate = function() {
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'trigger_uuid': $scope.trigger.id
|
||||
};
|
||||
|
||||
var data = {
|
||||
'config': $scope.trigger['config']
|
||||
};
|
||||
|
||||
if ($scope.pullEntity) {
|
||||
data['pull_robot'] = $scope.pullEntity['name'];
|
||||
}
|
||||
|
||||
ApiService.activateBuildTrigger(data, params).then(function(resp) {
|
||||
trigger['is_active'] = true;
|
||||
trigger['pull_robot'] = resp['pull_robot'];
|
||||
$scope.activated({'trigger': $scope.trigger});
|
||||
}, function(resp) {
|
||||
$scope.hide();
|
||||
$scope.canceled({'trigger': $scope.trigger});
|
||||
|
||||
bootbox.dialog({
|
||||
"message": resp['data']['message'] || 'The build trigger setup could not be completed',
|
||||
"title": "Could not activate build trigger",
|
||||
"buttons": {
|
||||
"close": {
|
||||
"label": "Close",
|
||||
"className": "btn-primary"
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var check = function() {
|
||||
if ($scope.counter && $scope.trigger && $scope.repository) {
|
||||
$scope.show();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.$watch('trigger', check);
|
||||
$scope.$watch('counter', check);
|
||||
$scope.$watch('repository', check);
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
||||
|
||||
|
||||
quayApp.directive('triggerSetupGithub', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
|
@ -3407,15 +3865,18 @@ quayApp.directive('triggerSetupGithub', function () {
|
|||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'trigger': '=trigger'
|
||||
'trigger': '=trigger',
|
||||
'analyze': '&analyze'
|
||||
},
|
||||
controller: function($scope, $element, ApiService) {
|
||||
$scope.analyzeCounter = 0;
|
||||
$scope.setupReady = false;
|
||||
$scope.loading = true;
|
||||
|
||||
$scope.handleLocationInput = function(location) {
|
||||
$scope.trigger['config']['subdir'] = location || '';
|
||||
$scope.isInvalidLocation = $scope.locations.indexOf(location) < 0;
|
||||
$scope.analyze({'isValid': !$scope.isInvalidLocation});
|
||||
};
|
||||
|
||||
$scope.handleLocationSelected = function(datum) {
|
||||
|
@ -3426,6 +3887,7 @@ quayApp.directive('triggerSetupGithub', function () {
|
|||
$scope.currentLocation = location;
|
||||
$scope.trigger['config']['subdir'] = location || '';
|
||||
$scope.isInvalidLocation = false;
|
||||
$scope.analyze({'isValid': true});
|
||||
};
|
||||
|
||||
$scope.selectRepo = function(repo, org) {
|
||||
|
@ -3464,6 +3926,7 @@ quayApp.directive('triggerSetupGithub', function () {
|
|||
$scope.locations = null;
|
||||
$scope.trigger.$ready = false;
|
||||
$scope.isInvalidLocation = false;
|
||||
$scope.analyze({'isValid': false});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -3476,12 +3939,14 @@ quayApp.directive('triggerSetupGithub', function () {
|
|||
} else {
|
||||
$scope.currentLocation = null;
|
||||
$scope.isInvalidLocation = resp['subdir'].indexOf('') < 0;
|
||||
$scope.analyze({'isValid': !$scope.isInvalidLocation});
|
||||
}
|
||||
}, function(resp) {
|
||||
$scope.locationError = resp['message'] || 'Could not load Dockerfile locations';
|
||||
$scope.locations = null;
|
||||
$scope.trigger.$ready = false;
|
||||
$scope.isInvalidLocation = false;
|
||||
$scope.analyze({'isValid': false});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -3526,7 +3991,14 @@ quayApp.directive('triggerSetupGithub', function () {
|
|||
});
|
||||
};
|
||||
|
||||
loadSources();
|
||||
var check = function() {
|
||||
if ($scope.repository && $scope.trigger) {
|
||||
loadSources();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.$watch('repository', check);
|
||||
$scope.$watch('trigger', check);
|
||||
|
||||
$scope.$watch('currentRepo', function(repo) {
|
||||
$scope.selectRepoInternal(repo);
|
||||
|
@ -3572,7 +4044,7 @@ quayApp.directive('dockerfileCommand', function () {
|
|||
scope: {
|
||||
'command': '=command'
|
||||
},
|
||||
controller: function($scope, $element, $sanitize) {
|
||||
controller: function($scope, $element, $sanitize, Config) {
|
||||
var registryHandlers = {
|
||||
'quay.io': function(pieces) {
|
||||
var rnamespace = pieces[pieces.length - 2];
|
||||
|
@ -3587,6 +4059,8 @@ quayApp.directive('dockerfileCommand', function () {
|
|||
}
|
||||
};
|
||||
|
||||
registryHandlers[Config.getDomain()] = registryHandlers['quay.io'];
|
||||
|
||||
var kindHandlers = {
|
||||
'FROM': function(title) {
|
||||
var pieces = title.split('/');
|
||||
|
@ -4259,6 +4733,17 @@ quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', 'PlanServi
|
|||
});
|
||||
};
|
||||
|
||||
$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);
|
||||
});
|
||||
|
||||
$rootScope.$on('$routeUpdate', function(){
|
||||
if ($location.search()['tab']) {
|
||||
changeTab($location.search()['tab']);
|
||||
|
@ -4275,7 +4760,7 @@ quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', 'PlanServi
|
|||
if (current.$$route.description) {
|
||||
$rootScope.description = current.$$route.description;
|
||||
} else {
|
||||
$rootScope.description = 'Hosted private docker repositories. Includes full user management and history. Free for public repositories.';
|
||||
$rootScope.description = '';
|
||||
}
|
||||
|
||||
$rootScope.fixFooter = !!current.$$route.fixFooter;
|
||||
|
|
Reference in a new issue