Merge branch 'master' into gitlab

This commit is contained in:
Joseph Schorr 2015-05-03 12:13:09 -04:00
commit e3aededcbc
70 changed files with 1000 additions and 265 deletions

View file

@ -0,0 +1,26 @@
/**
* Filter which displays numbers with suffixes.
*
* Based on: https://gist.github.com/pedrorocha-net/9aa21d5f34d9cc15d18f
*/
angular.module('quay').filter('abbreviated', function() {
return function(number) {
if (number >= 10000000) {
return (number / 1000000).toFixed(0) + 'M'
}
if (number >= 1000000) {
return (number / 1000000).toFixed(1) + 'M'
}
if (number >= 10000) {
return (number / 1000).toFixed(0) + 'K'
}
if (number >= 1000) {
return (number / 1000).toFixed(1) + 'K'
}
return number
}
});

View file

@ -0,0 +1,25 @@
/**
* Directive to transclude a template under an ng-repeat. From: http://stackoverflow.com/a/24512435
*/
angular.module('quay').directive('ngTranscope', function() {
return {
link: function( $scope, $element, $attrs, controller, $transclude ) {
if ( !$transclude ) {
throw minErr( 'ngTranscope' )( 'orphan',
'Illegal use of ngTransclude directive in the template! ' +
'No parent directive that requires a transclusion found. ' +
'Element: {0}',
startingTag( $element ));
}
var innerScope = $scope.$new();
$transclude( innerScope, function( clone ) {
$element.empty();
$element.append( clone );
$element.on( '$destroy', function() {
innerScope.$destroy();
});
});
}
};
});

View file

@ -42,15 +42,14 @@ angular.module('quay').directive('repoPanelBuilds', function () {
var unordered = $scope.allBuilds.map(function(build_info) {
var commit_sha = null;
var job_config = build_info.job_config || {};
if (job_config.trigger_metadata) {
commit_sha = job_config.trigger_metadata.commit_sha;
if (build_info.trigger_metadata) {
commit_sha = build_info.trigger_metadata.commit_sha;
}
return $.extend(build_info, {
'started_datetime': (new Date(build_info.started)).valueOf() * (-1),
'building_tags': job_config.docker_tags || [],
'building_tags': build_info.tags || [],
'commit_sha': commit_sha
});
});
@ -58,14 +57,16 @@ angular.module('quay').directive('repoPanelBuilds', function () {
$scope.fullBuilds = orderBy(unordered, $scope.options.predicate, $scope.options.reverse);
};
var loadBuilds = function() {
var loadBuilds = function(opt_forcerefresh) {
if (!$scope.builds || !$scope.repository || !$scope.options.filter) {
return;
}
// Note: We only refresh if the filter has changed.
var filter = $scope.options.filter;
if ($scope.buildsResource && filter == $scope.currentFilter) { return; }
if ($scope.buildsResource && filter == $scope.currentFilter && !opt_forcerefresh) {
return;
}
var since = null;
var limit = 10;
@ -105,17 +106,30 @@ angular.module('quay').directive('repoPanelBuilds', function () {
}
// Replace any build records with updated records from the server.
var requireReload = false;
$scope.builds.map(function(build) {
var found = false;
for (var i = 0; i < $scope.allBuilds.length; ++i) {
var current = $scope.allBuilds[i];
if (current.id == build.id && current.phase != build.phase) {
$scope.allBuilds[i] = build;
break
found = true;
break;
}
}
// If the build was not found, then a new build has started. Reload
// the builds list.
if (!found) {
requireReload = true;
}
});
updateBuilds();
if (requireReload) {
loadBuilds(/* force refresh */true);
} else {
updateBuilds();
}
};
var loadBuildTriggers = function() {

View file

@ -96,20 +96,24 @@ angular.module('quay').directive('repoPanelChanges', function () {
'isEnabled': '=isEnabled'
},
controller: function($scope, $element, $timeout, ApiService, UtilService, ImageMetadataService) {
$scope.tagNames = [];
var update = function() {
if (!$scope.repository || !$scope.selectedTags) { return; }
if (!$scope.repository || !$scope.isEnabled) { return; }
$scope.tagNames = Object.keys($scope.repository.tags);
$scope.currentImage = null;
$scope.currentTag = null;
if (!$scope.tracker) {
if ($scope.tracker) {
refreshTree();
} else {
updateImages();
}
};
var updateImages = function() {
if (!$scope.repository || !$scope.images) { return; }
if (!$scope.repository || !$scope.images || !$scope.isEnabled) { return; }
$scope.tracker = new RepositoryImageTracker($scope.repository, $scope.images);
@ -120,16 +124,17 @@ angular.module('quay').directive('repoPanelChanges', function () {
$scope.$watch('selectedTags', update)
$scope.$watch('repository', update);
$scope.$watch('isEnabled', update);
$scope.$watch('images', updateImages);
$scope.$watch('isEnabled', function(isEnabled) {
if (isEnabled) {
refreshTree();
}
});
$scope.updateState = function() {
update();
};
var refreshTree = function() {
if (!$scope.repository || !$scope.images) { return; }
if (!$scope.repository || !$scope.images || !$scope.isEnabled) { return; }
if ($scope.selectedTags.length < 1) { return; }
$('#image-history-container').empty();
@ -149,6 +154,7 @@ angular.module('quay').directive('repoPanelChanges', function () {
// Give enough time for the UI to be drawn before we resize the tree.
$timeout(function() {
$scope.tree.notifyResized();
$scope.setTag($scope.selectedTags[0]);
}, 100);
// Listen for changes to the selected tag and image in the tree.

View file

@ -10,6 +10,7 @@ angular.module('quay').directive('anchor', function () {
restrict: 'C',
scope: {
'href': '@href',
'target': '@target',
'isOnlyText': '=isOnlyText'
},
controller: function($scope, $element) {

View file

@ -0,0 +1,35 @@
/**
* An element which displays a dropdown for selecting multiple elements.
*/
angular.module('quay').directive('multiselectDropdown', function ($compile) {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/multiselect-dropdown.html',
transclude: true,
replace: false,
restrict: 'C',
scope: {
'items': '=items',
'selectedItems': '=selectedItems',
'itemName': '@itemName',
'itemChecked': '&itemChecked'
},
controller: function($scope, $element) {
$scope.isChecked = function(checked, item) {
return checked.indexOf(item) >= 0;
};
$scope.toggleItem = function(item) {
var isChecked = $scope.isChecked($scope.selectedItems, item);
if (!isChecked) {
$scope.selectedItems.push(item);
} else {
var index = $scope.selectedItems.indexOf(item);
$scope.selectedItems.splice(index, 1);
}
$scope.itemChecked({'item': item, 'checked': !isChecked});
};
}
};
return directiveDefinitionObject;
});

View file

@ -128,6 +128,15 @@ angular.module('quay').directive('robotsManager', function () {
}, ApiService.errorDisplay('Cannot delete robot account'));
};
$scope.askDeleteRobot = function(info) {
bootbox.confirm('Are you sure you want to delete robot ' + info.name + '?', function(resp) {
if (resp) {
$scope.deleteRobot(info);
}
});
};
var update = function() {
if (!$scope.user && !$scope.organization) { return; }
if ($scope.loading || !$scope.isEnabled) { return; }

View file

@ -6,11 +6,12 @@ angular.module('quay').directive('sourceCommitLink', function () {
priority: 0,
templateUrl: '/static/directives/source-commit-link.html',
replace: false,
transclude: false,
transclude: true,
restrict: 'C',
scope: {
'commitSha': '=commitSha',
'urlTemplate': '=urlTemplate'
'urlTemplate': '=urlTemplate',
'showTransclude': '=showTransclude'
},
controller: function($scope, $element) {
$scope.getUrl = function(sha, template) {

View file

@ -16,26 +16,38 @@ angular.module('quay').directive('stepView', function ($compile) {
'stepsCompleted': '&stepsCompleted'
},
controller: function($scope, $element, $rootScope) {
this.currentStepIndex = -1;
this.steps = [];
this.watcher = null;
var currentStepIndex = -1;
var steps = [];
var watcher = null;
this.getCurrentStep = function() {
return this.steps[this.currentStepIndex];
// Members on 'this' are accessed by the individual steps.
this.register = function(scope, element) {
element.hide();
steps.push({
'scope': scope,
'element': element
});
nextStep();
};
this.reset = function() {
this.currentStepIndex = -1;
for (var i = 0; i < this.steps.length; ++i) {
this.steps[i].element.hide();
var getCurrentStep = function() {
return steps[currentStepIndex];
};
var reset = function() {
currentStepIndex = -1;
for (var i = 0; i < steps.length; ++i) {
steps[i].element.hide();
}
$scope.currentStepValid = false;
};
this.next = function() {
if (this.currentStepIndex >= 0) {
var currentStep = this.getCurrentStep();
var next = function() {
if (currentStepIndex >= 0) {
var currentStep = getCurrentStep();
if (!currentStep || !currentStep.scope) { return; }
if (!currentStep.scope.completeCondition) {
@ -44,20 +56,20 @@ angular.module('quay').directive('stepView', function ($compile) {
currentStep.element.hide();
if (this.unwatch) {
this.unwatch();
this.unwatch = null;
if (unwatch) {
unwatch();
unwatch = null;
}
}
this.currentStepIndex++;
currentStepIndex++;
if (this.currentStepIndex < this.steps.length) {
var currentStep = this.getCurrentStep();
if (currentStepIndex < steps.length) {
var currentStep = getCurrentStep();
currentStep.element.show();
currentStep.scope.load()
this.unwatch = currentStep.scope.$watch('completeCondition', function(cc) {
unwatch = currentStep.scope.$watch('completeCondition', function(cc) {
$scope.currentStepValid = !!cc;
});
} else {
@ -65,23 +77,17 @@ angular.module('quay').directive('stepView', function ($compile) {
}
};
this.register = function(scope, element) {
element.hide();
var nextStep = function() {
if (!steps || !steps.length) { return; }
this.steps.push({
'scope': scope,
'element': element
});
if ($scope.nextStepCounter >= 0) {
next();
} else {
reset();
}
};
var that = this;
$scope.$watch('nextStepCounter', function(nsc) {
if (nsc >= 0) {
that.next();
} else {
that.reset();
}
});
$scope.$watch('nextStepCounter', nextStep);
}
};
return directiveDefinitionObject;

View file

@ -12,29 +12,30 @@ angular.module('quay').directive('triggeredBuildDescription', function () {
'build': '=build'
},
controller: function($scope, $element, KeyService, TriggerService) {
$scope.TriggerService = TriggerService;
$scope.$watch('build', function(build) {
if (!build) { return; }
var jobConfig = build.job_config || {};
var triggerMetadata = jobConfig.trigger_metadata || {};
var triggerMetadata = build.trigger_metadata || {};
if (!build.trigger && !jobConfig.manual_user) {
if (!build.trigger && !build.manual_user) {
$scope.infoDisplay = 'manual';
return;
}
if (!build.trigger && jobConfig.manual_user) {
if (!build.trigger && build.manual_user) {
$scope.infoDisplay = 'manual+user';
return;
}
if (triggerMetadata.commit_info) {
if (build.trigger && triggerMetadata.commit_info) {
$scope.infoDisplay = 'fullcommit';
return;
}
if (triggerMetadata.commit_sha) {
if (build.trigger && build.trigger.build_source && triggerMetadata.commit_sha) {
$scope.infoDisplay = 'commitsha';
return;
}
$scope.infoDisplay = 'source';

View file

@ -30,16 +30,14 @@
var determineDockerfilePath = function() {
var dockerfilePath = 'Dockerfile';
if ($scope.repobuild['job_config']) {
var dockerfileFolder = ($scope.repobuild['job_config']['build_subdir'] || '');
if (dockerfileFolder[0] == '/') {
dockerfileFolder = dockerfileFolder.substr(1);
}
if (dockerfileFolder && dockerfileFolder[dockerfileFolder.length - 1] != '/') {
dockerfileFolder += '/';
}
dockerfilePath = dockerfileFolder + 'Dockerfile';
var dockerfileFolder = ($scope.repobuild['subdirectory'] || '');
if (dockerfileFolder[0] == '/') {
dockerfileFolder = dockerfileFolder.substr(1);
}
if (dockerfileFolder && dockerfileFolder[dockerfileFolder.length - 1] != '/') {
dockerfileFolder += '/';
}
dockerfilePath = dockerfileFolder + 'Dockerfile';
return dockerfilePath;
};

View file

@ -70,15 +70,12 @@
$scope.restartBuild = function(build) {
$('#confirmRestartBuildModal').modal('hide');
var subdirectory = '';
if (build['job_config']) {
subdirectory = build['job_config']['build_subdir'] || '';
}
var subdirectory = build['subdirectory'] || '';
var data = {
'file_id': build['resource_key'],
'subdirectory': subdirectory,
'docker_tags': build['job_config']['docker_tags']
'docker_tags': build['tags']
};
if (build['pull_robot']) {

View file

@ -56,6 +56,16 @@ function($rootScope, $interval, UserService, ApiService, StringBuilderService, P
'<br><br>Please upgrade your plan to avoid disruptions in service.',
'page': function(metadata) {
var organization = UserService.getOrganization(metadata['namespace']);
// TODO(jschorr): Remove once the new layout is in prod.
if (Config.isNewLayout()) {
if (organization) {
return '/organization/' + metadata['namespace'] + '?tab=billing';
} else {
return '/user/' + metadata['namespace'] + '?tab=billing';
}
}
if (organization) {
return '/organization/' + metadata['namespace'] + '/admin';
} else {

View file

@ -54,6 +54,14 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K
'templates': {
'credentials': '/static/directives/trigger/githost/credentials.html',
'trigger-description': '/static/directives/trigger/github/trigger-description.html'
},
'repository_url': function(build) {
return KeyService['githubTriggerEndpoint'] + build.trigger.build_source;
},
'link_templates': {
'commit': '/commit/{sha}',
'branch': '/tree/{branch}',
'tag': '/releases/tag/{tag}',
}
},
@ -85,6 +93,14 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K
'templates': {
'credentials': '/static/directives/trigger/githost/credentials.html',
'trigger-description': '/static/directives/trigger/bitbucket/trigger-description.html'
},
'repository_url': function(build) {
return 'https://bitbucket.org/' + build.trigger.build_source;
},
'link_templates': {
'commit': '/commits/{sha}',
'branch': '/branch/{branch}',
'tag': '/commits/tag/{tag}',
}
},
@ -160,6 +176,26 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K
});
};
triggerService.getFullLinkTemplate = function(build, templateName) {
var name = build.trigger.service;
var type = triggerTypes[name];
if (!type) {
return null;
}
var repositoryUrl = type.repository_url;
if (!repositoryUrl) {
return null;
}
var linkTemplate = type.link_templates;
if (!linkTemplate || !linkTemplate[templateName]) {
return null;
}
return repositoryUrl(build) + linkTemplate[templateName];
};
triggerService.supportsFullListing = function(name) {
var type = triggerTypes[name];
if (!type) {