Merge branch 'master' into git

This commit is contained in:
Jimmy Zelinskie 2015-03-19 12:10:34 -04:00
commit 5a29218c5c
173 changed files with 151322 additions and 527 deletions

View file

@ -162,6 +162,51 @@ angular.module("core-ui", [])
return directiveDefinitionObject;
})
.directive('corConfirmDialog', function() {
var directiveDefinitionObject = {
priority: 1,
templateUrl: '/static/directives/cor-confirm-dialog.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'dialogTitle': '@dialogTitle',
'dialogActionTitle': '@dialogActionTitle',
'dialogContext': '=dialogContext',
'dialogAction': '&dialogAction'
},
controller: function($rootScope, $scope, $element) {
$scope.working = false;
$scope.$watch('dialogContext', function(dc) {
if (!dc) { return; }
$scope.show();
});
$scope.performAction = function() {
$scope.working = true;
$scope.dialogAction({
'info': $scope.dialogContext,
'callback': function(result) {
$scope.hide();
}
});
};
$scope.show = function() {
$scope.working = false;
$element.find('.modal').modal({});
};
$scope.hide = function() {
$element.find('.modal').modal('hide');
};
}
};
return directiveDefinitionObject;
})
.directive('corTabPanel', function() {
var directiveDefinitionObject = {
priority: 1,
@ -581,4 +626,86 @@ angular.module("core-ui", [])
}
};
return directiveDefinitionObject;
})
.directive('corCheckableMenu', function() {
var directiveDefinitionObject = {
priority: 1,
templateUrl: '/static/directives/cor-checkable-menu.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'controller': '=controller'
},
controller: function($rootScope, $scope, $element) {
$scope.getClass = function(items, checked) {
if (checked.length == 0) {
return 'none';
}
if (checked.length == items.length) {
return 'all';
}
return 'some';
};
$scope.toggleItems = function($event) {
$event.stopPropagation();
$scope.controller.toggleItems();
};
this.checkByFilter = function(filter) {
$scope.controller.checkByFilter(function(tag) {
return filter({'tag': tag});
});
};
}
};
return directiveDefinitionObject;
})
.directive('corCheckableMenuItem', function() {
var directiveDefinitionObject = {
priority: 1,
templateUrl: '/static/directives/cor-checkable-menu-item.html',
replace: true,
transclude: true,
restrict: 'C',
require: '^corCheckableMenu',
scope: {
'itemFilter': '&itemFilter'
},
link: function($scope, $element, $attr, parent) {
$scope.parent = parent;
},
controller: function($rootScope, $scope, $element) {
$scope.selected = function() {
$scope.parent.checkByFilter(this.itemFilter);
};
}
};
return directiveDefinitionObject;
})
.directive('corCheckableItem', function() {
var directiveDefinitionObject = {
priority: 1,
templateUrl: '/static/directives/cor-checkable-item.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'item': '=item',
'controller': '=controller'
},
controller: function($rootScope, $scope, $element) {
$scope.toggleItem = function() {
$scope.controller.toggleItem($scope.item);
};
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,233 @@
/**
* An element which displays the builds panel for a repository view.
*/
angular.module('quay').directive('repoPanelBuilds', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/repo-view/repo-panel-builds.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'repository': '=repository',
'builds': '=builds'
},
controller: function($scope, $element, $filter, $routeParams, ApiService, TriggerService) {
var orderBy = $filter('orderBy');
$scope.TriggerService = TriggerService;
$scope.options = {
'filter': 'recent',
'reverse': false,
'predicate': 'started_datetime'
};
$scope.currentFilter = null;
$scope.currentStartTrigger = null;
$scope.currentSetupTrigger = null;
$scope.showBuildDialogCounter = 0;
$scope.showTriggerStartDialogCounter = 0;
$scope.showTriggerSetupCounter = 0;
var updateBuilds = function() {
if (!$scope.allBuilds) { return; }
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;
}
return $.extend(build_info, {
'started_datetime': (new Date(build_info.started)).valueOf() * (-1),
'building_tags': job_config.docker_tags || [],
'commit_sha': commit_sha
});
});
$scope.fullBuilds = orderBy(unordered, $scope.options.predicate, $scope.options.reverse);
};
var loadBuilds = function() {
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; }
var since = null;
if ($scope.options.filter == '48hour') {
since = Math.floor(moment().subtract(2, 'days').valueOf() / 1000);
} else if ($scope.options.filter == '30day') {
since = Math.floor(moment().subtract(30, 'days').valueOf() / 1000);
} else {
since = null;
}
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'limit': 100,
'since': since
};
$scope.buildsResource = ApiService.getRepoBuildsAsResource(params).get(function(resp) {
$scope.allBuilds = resp.builds;
$scope.currentFilter = filter;
updateBuilds();
});
};
var buildsChanged = function() {
if (!$scope.allBuilds) {
loadBuilds();
return;
}
if (!$scope.builds || !$scope.repository) {
return;
}
// Replace any build records with updated records from the server.
$scope.builds.map(function(build) {
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
}
}
});
updateBuilds();
};
var loadBuildTriggers = function() {
if (!$scope.repository || !$scope.repository.can_admin) { return; }
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name
};
$scope.triggersResource = ApiService.listBuildTriggersAsResource(params).get(function(resp) {
$scope.triggers = resp.triggers;
// Check to see if we need to setup any trigger.
var newTriggerId = $routeParams.newtrigger;
if (newTriggerId) {
$scope.triggers.map(function(trigger) {
if (trigger['id'] == newTriggerId && !trigger['is_active']) {
$scope.setupTrigger(trigger);
}
});
}
});
};
$scope.$watch('repository', loadBuildTriggers);
$scope.$watch('repository', loadBuilds);
$scope.$watch('builds', buildsChanged);
$scope.$watch('options.filter', loadBuilds);
$scope.$watch('options.predicate', updateBuilds);
$scope.$watch('options.reverse', updateBuilds);
$scope.tablePredicateClass = function(name, predicate, reverse) {
if (name != predicate) {
return '';
}
return 'current ' + (reverse ? 'reversed' : '');
};
$scope.orderBy = function(predicate) {
if (predicate == $scope.options.predicate) {
$scope.options.reverse = !$scope.options.reverse;
return;
}
$scope.options.reverse = false;
$scope.options.predicate = predicate;
};
$scope.askDeleteTrigger = function(trigger) {
$scope.deleteTriggerInfo = {
'trigger': trigger
};
};
$scope.askRunTrigger = function(trigger) {
$scope.currentStartTrigger = trigger;
$scope.showTriggerStartDialogCounter++;
};
$scope.cancelSetupTrigger = function(trigger) {
if ($scope.currentSetupTrigger != trigger) { return; }
$scope.currentSetupTrigger = null;
$scope.deleteTrigger(trigger);
};
$scope.setupTrigger = function(trigger) {
$scope.currentSetupTrigger = trigger;
$scope.showTriggerSetupCounter++;
};
$scope.startTrigger = function(trigger, opt_custom) {
var parameters = TriggerService.getRunParameters(trigger.service);
if (parameters.length && !opt_custom) {
$scope.currentStartTrigger = trigger;
$scope.showTriggerStartDialogCounter++;
return;
}
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'trigger_uuid': trigger.id
};
ApiService.manuallyStartBuildTrigger(opt_custom || {}, params).then(function(resp) {
$scope.allBuilds.push(resp);
updateBuilds();
}, ApiService.errorDisplay('Could not start build'));
};
$scope.deleteTrigger = function(trigger, opt_callback) {
if (!trigger) { return; }
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'trigger_uuid': trigger.id
};
var errorHandler = ApiService.errorDisplay('Could not delete build trigger', function() {
opt_callback && opt_callback(false);
});
ApiService.deleteBuildTrigger(null, params).then(function(resp) {
$scope.triggers.splice($scope.triggers.indexOf(trigger), 1);
opt_callback && opt_callback(true);
}, errorHandler);
};
$scope.showNewBuildDialog = function() {
$scope.showBuildDialogCounter++;
};
$scope.handleBuildStarted = function(build) {
$scope.allBuilds.push(build);
updateBuilds();
};
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,204 @@
/**
* An element which displays the changes visualization panel for a repository view.
*/
angular.module('quay').directive('repoPanelChanges', function () {
var RepositoryImageTracker = function(repository, images) {
this.repository = repository;
this.images = images;
// Build a map of image ID -> image.
var imageIDMap = {};
this.images.map(function(image) {
imageIDMap[image.id] = image;
});
this.imageMap_ = imageIDMap;
};
RepositoryImageTracker.prototype.imageLink = function(image) {
return '/repository/' + this.repository.namespace + '/' +
this.repository.name + '/image/' + image;
};
RepositoryImageTracker.prototype.getImageForTag = function(tag) {
var tagData = this.lookupTag(tag);
if (!tagData) { return null; }
return this.imageMap_[tagData.image_id];
};
RepositoryImageTracker.prototype.lookupTag = function(tag) {
return this.repository.tags[tag];
};
RepositoryImageTracker.prototype.lookupImage = function(image) {
return this.imageMap_[image];
};
RepositoryImageTracker.prototype.forAllTagImages = function(tag, callback) {
var tagData = this.lookupTag(tag);
if (!tagData) { return; }
var tagImage = this.imageMap_[tagData.image_id];
if (!tagImage) { return; }
// Callback the tag's image itself.
callback(tagImage);
// Callback any parent images.
if (!tagImage.ancestors) { return; }
var ancestors = tagImage.ancestors.split('/');
for (var i = 0; i < ancestors.length; ++i) {
var image = this.imageMap_[ancestors[i]];
if (image) {
callback(image);
}
}
};
RepositoryImageTracker.prototype.getTotalSize = function(tag) {
var size = 0;
this.forAllTagImages(tag, function(image) {
size += image.size;
});
return size;
};
RepositoryImageTracker.prototype.getImagesForTagBySize = function(tag) {
var images = [];
this.forAllTagImages(tag, function(image) {
images.push(image);
});
images.sort(function(a, b) {
return b.size - a.size;
});
return images;
};
///////////////////////////////////////////////////////////////////////////////////////
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/repo-view/repo-panel-changes.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'repository': '=repository',
'selectedTags': '=selectedTags',
'imagesResource': '=imagesResource',
'images': '=images',
'isEnabled': '=isEnabled'
},
controller: function($scope, $element, $timeout, ApiService, UtilService, ImageMetadataService) {
var update = function() {
if (!$scope.repository || !$scope.selectedTags) { return; }
$scope.currentImage = null;
$scope.currentTag = null;
if (!$scope.tracker) {
updateImages();
}
};
var updateImages = function() {
if (!$scope.repository || !$scope.images) { return; }
$scope.tracker = new RepositoryImageTracker($scope.repository, $scope.images);
if ($scope.selectedTags && $scope.selectedTags.length) {
refreshTree();
}
};
$scope.$watch('selectedTags', update)
$scope.$watch('repository', update);
$scope.$watch('images', updateImages);
$scope.$watch('isEnabled', function(isEnabled) {
if (isEnabled) {
refreshTree();
}
});
var refreshTree = function() {
if (!$scope.repository || !$scope.images) { return; }
$('#image-history-container').empty();
var tree = new ImageHistoryTree(
$scope.repository.namespace,
$scope.repository.name,
$scope.images,
UtilService.getFirstMarkdownLineAsText,
$scope.getTimeSince,
ImageMetadataService.getEscapedFormattedCommand,
function(tag) {
return $.inArray(tag, $scope.selectedTags) >= 0;
});
$scope.tree = tree.draw('image-history-container');
if ($scope.tree) {
// Give enough time for the UI to be drawn before we resize the tree.
$timeout(function() {
$scope.tree.notifyResized();
}, 100);
// Listen for changes to the selected tag and image in the tree.
$($scope.tree).bind('tagChanged', function(e) {
$scope.$apply(function() { $scope.setTag(e.tag); });
});
$($scope.tree).bind('imageChanged', function(e) {
$scope.$apply(function() { $scope.setImage(e.image.id); });
});
}
};
$scope.setImage = function(image_id) {
$scope.currentTag = null;
$scope.currentImage = image_id;
$scope.tree.setImage(image_id);
};
$scope.setTag = function(tag) {
$scope.currentTag = tag;
$scope.currentImage = null;
$scope.tree.setTag(tag);
};
$scope.parseDate = function(dateString) {
return Date.parse(dateString);
};
$scope.getTimeSince = function(createdTime) {
return moment($scope.parseDate(createdTime)).fromNow();
};
$scope.handleTagChanged = function(data) {
$scope.tracker = new RepositoryImageTracker($scope.repository, $scope.images);
data.removed.map(function(tag) {
$scope.currentImage = null;
$scope.currentTag = null;
});
data.added.map(function(tag) {
$scope.selectedTags.push(tag);
$scope.currentTag = tag;
});
refreshTree();
};
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,24 @@
/**
* An element which displays the information panel for a repository view.
*/
angular.module('quay').directive('repoPanelInfo', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/repo-view/repo-panel-info.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'repository': '=repository',
'builds': '=builds'
},
controller: function($scope, $element, ApiService) {
$scope.updateDescription = function(content) {
$scope.repository.description = content;
$scope.repository.put();
};
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,84 @@
/**
* An element which displays the settings panel for a repository view.
*/
angular.module('quay').directive('repoPanelSettings', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/repo-view/repo-panel-settings.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'repository': '=repository'
},
controller: function($scope, $element, ApiService, Config) {
$scope.getBadgeFormat = function(format, repository) {
if (!repository) { return ''; }
var imageUrl = Config.getUrl('/repository/' + repository.namespace + '/' + repository.name + '/status');
if (!$scope.repository.is_public) {
imageUrl += '?token=' + repository.status_token;
}
var linkUrl = Config.getUrl('/repository/' + repository.namespace + '/' + repository.name);
switch (format) {
case 'svg':
return imageUrl;
case 'md':
return '[![Docker Repository on ' + Config.REGISTRY_TITLE_SHORT + '](' + imageUrl +
' "Docker Repository on ' + Config.REGISTRY_TITLE_SHORT + '")](' + linkUrl + ')';
case 'asciidoc':
return 'image:' + imageUrl + '["Docker Repository on ' + Config.REGISTRY_TITLE_SHORT + '", link="' + linkUrl + '"]';
}
return '';
};
$scope.askDelete = function() {
bootbox.confirm('Are you sure you want delete this repository?', function(r) {
if (!r) { return; }
$scope.deleteRepo();
});
};
$scope.deleteRepo = function() {
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name
};
ApiService.deleteRepository(null, params).then(function() {
setTimeout(function() {
document.location = '/repository/';
}, 100);
}, ApiService.errorDisplay('Could not delete repository'));
};
$scope.askChangeAccess = function(newAccess) {
bootbox.confirm('Are you sure you want to make this repository ' + newAccess + '?', function(r) {
if (!r) { return; }
$scope.changeAccess(newAccess);
});
};
$scope.changeAccess = function(newAccess) {
var visibility = {
'visibility': newAccess
};
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name
};
ApiService.changeRepoVisibility(visibility, params).then(function() {
$scope.repository.is_public = newAccess == 'public';
}, ApiService.errorDisplay('Could not change repository visibility'));
};
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,209 @@
/**
* An element which displays the tags panel for a repository view.
*/
angular.module('quay').directive('repoPanelTags', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/repo-view/repo-panel-tags.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'repository': '=repository',
'selectedTags': '=selectedTags',
'imagesResource': '=imagesResource',
'images': '=images',
},
controller: function($scope, $element, $filter, $location, ApiService, UIService) {
var orderBy = $filter('orderBy');
$scope.checkedTags = UIService.createCheckStateController([]);
$scope.options = {
'predicate': 'last_modified_datetime',
'reverse': false
};
$scope.iterationState = {};
$scope.tagActionHandler = null;
var setTagState = function() {
if (!$scope.repository || !$scope.selectedTags) { return; }
var tags = [];
var allTags = [];
var imageMap = {};
var imageTracks = [];
// Build a list of tags and filtered tags.
for (var tag in $scope.repository.tags) {
if (!$scope.repository.tags.hasOwnProperty(tag)) { continue; }
var tagData = $scope.repository.tags[tag];
var tagInfo = $.extend(tagData, {
'name': tag,
'last_modified_datetime': (new Date(tagData.last_modified || 0)).valueOf() * (-1)
});
allTags.push(tagInfo);
if (!$scope.options.tagFilter || tag.indexOf($scope.options.tagFilter) >= 0 ||
tagInfo.image_id.indexOf($scope.options.tagFilter) >= 0) {
tags.push(tagInfo);
}
}
// Sort the tags by the predicate and the reverse, and map the information.
var imageIDs = [];
var ordered = orderBy(tags, $scope.options.predicate, $scope.options.reverse);
var checked = [];
for (var i = 0; i < ordered.length; ++i) {
var tagInfo = ordered[i];
if (!imageMap[tagInfo.image_id]) {
imageMap[tagInfo.image_id] = [];
imageIDs.push(tagInfo.image_id)
}
imageMap[tagInfo.image_id].push(tagInfo);
if ($.inArray(tagInfo.name, $scope.selectedTags) >= 0) {
checked.push(tagInfo);
}
};
// Calculate the image tracks.
var colors = d3.scale.category10();
var index = 0;
imageIDs.sort().map(function(image_id) {
if (imageMap[image_id].length >= 2){
imageTracks.push({
'image_id': image_id,
'color': colors(index),
'count': imageMap[image_id].length,
'tags': imageMap[image_id]
});
++index;
}
});
$scope.imageMap = imageMap;
$scope.imageTracks = imageTracks;
$scope.tags = ordered;
$scope.allTags = allTags;
$scope.checkedTags = UIService.createCheckStateController(ordered, checked);
$scope.checkedTags.listen(function(checked) {
$scope.selectedTags = checked.map(function(tag_info) {
return tag_info.name;
});
});
}
$scope.$watch('options.predicate', setTagState);
$scope.$watch('options.reverse', setTagState);
$scope.$watch('options.tagFilter', setTagState);
$scope.$watch('selectedTags', function(selectedTags) {
if (!selectedTags || !$scope.repository || !$scope.imageMap) { return; }
$scope.checkedTags.checked = selectedTags.map(function(tag) {
return $scope.repository.tags[tag];
});
}, true);
$scope.$watch('repository', function(repository) {
if (!repository) { return; }
// Process each of the tags.
setTagState();
});
$scope.trackLineClass = function(index, track_info) {
var startIndex = $.inArray(track_info.tags[0], $scope.tags);
var endIndex = $.inArray(track_info.tags[track_info.tags.length - 1], $scope.tags);
if (index == startIndex) {
return 'start';
}
if (index == endIndex) {
return 'end';
}
if (index > startIndex && index < endIndex) {
return 'middle';
}
if (index < startIndex) {
return 'before';
}
if (index > endIndex) {
return 'after';
}
};
$scope.tablePredicateClass = function(name, predicate, reverse) {
if (name != predicate) {
return '';
}
return 'current ' + (reverse ? 'reversed' : '');
};
$scope.askDeleteTag = function(tag) {
$scope.tagActionHandler.askDeleteTag(tag);
};
$scope.askDeleteMultipleTags = function(tags) {
if (tags.length == 1) {
$scope.askDeleteTag(tags[0].name);
return;
}
$scope.tagActionHandler.askDeleteMultipleTags(tags);
};
$scope.orderBy = function(predicate) {
if (predicate == $scope.options.predicate) {
$scope.options.reverse = !$scope.options.reverse;
return;
}
$scope.options.reverse = false;
$scope.options.predicate = predicate;
};
$scope.commitTagFilter = function(tag) {
var r = new RegExp('^[0-9a-f]{7}$');
return tag.name.match(r);
};
$scope.allTagFilter = function(tag) {
return true;
};
$scope.noTagFilter = function(tag) {
return false;
};
$scope.imageIDFilter = function(image_id, tag) {
return tag.image_id == image_id;
};
$scope.setTab = function(tab) {
$location.search('tab', tab);
};
$scope.selectTrack = function(it) {
$scope.checkedTags.checkByFilter(function(tag) {
return $scope.imageIDFilter(it.image_id, tag);
});
};
}
};
return directiveDefinitionObject;
});

View file

@ -10,7 +10,8 @@ angular.module('quay').directive('buildInfoBar', function () {
restrict: 'C',
scope: {
'build': '=build',
'showTime': '=showTime'
'showTime': '=showTime',
'hideId': '=hideId'
},
controller: function($scope, $element) {
}

View file

@ -0,0 +1,22 @@
/**
* An element which displays the status of a build as a mini-bar.
*/
angular.module('quay').directive('buildMiniStatus', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/build-mini-status.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'build': '=build'
},
controller: function($scope, $element) {
$scope.isBuilding = function(build) {
if (!build) { return true; }
return build.phase != 'complete' && build.phase != 'error';
};
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,22 @@
/**
* An element which displays an icon representing the state of the build.
*/
angular.module('quay').directive('buildStateIcon', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/build-state-icon.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'build': '=build'
},
controller: function($scope, $element) {
$scope.isBuilding = function(build) {
if (!build) { return true; }
return build.phase != 'complete' && build.phase != 'error';
};
}
};
return directiveDefinitionObject;
});

View file

@ -1,5 +1,5 @@
/**
* An element which displays the status of a build.
* DEPRECATED: An element which displays the status of a build.
*/
angular.module('quay').directive('buildStatus', function () {
var directiveDefinitionObject = {

View file

@ -1,5 +1,5 @@
/**
* An element which displays controls and information about a defined external notification on
* DEPRECATED: An element which displays controls and information about a defined external notification on
* a repository.
*/
angular.module('quay').directive('externalNotificationView', function () {

View file

@ -0,0 +1,22 @@
/**
* An element which displays a link to change a lookup filter, and shows whether it is selected.
*/
angular.module('quay').directive('filterControl', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/filter-control.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'filter': '=filter',
'value': '@value'
},
controller: function($scope, $element) {
$scope.setFilter = function() {
$scope.filter = $scope.value;
};
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,34 @@
/**
* An element which loads and displays UI representing the changes made in an image.
*/
angular.module('quay').directive('imageChangesView', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/image-changes-view.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'repository': '=repository',
'image': '=image',
'hasChanges': '=hasChanges'
},
controller: function($scope, $element, ApiService) {
$scope.$watch('image', function() {
if (!$scope.image || !$scope.repository) { return; }
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'image_id': $scope.image
};
$scope.hasChanges = true;
$scope.imageChangesResource = ApiService.getImageChangesAsResource(params).get(function(resp) {
$scope.changeData = resp;
$scope.hasChanges = resp.added.length || resp.removed.length || resp.changed.length;
});
});
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,32 @@
/**
* An element which displays sidebar information for a image. Primarily used in the repo view.
*/
angular.module('quay').directive('imageInfoSidebar', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/image-info-sidebar.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'tracker': '=tracker',
'image': '=image',
'tagSelected': '&tagSelected',
'addTagRequested': '&addTagRequested'
},
controller: function($scope, $element, ImageMetadataService) {
$scope.$watch('image', function(image) {
if (!image || !$scope.tracker) { return; }
$scope.imageData = $scope.tracker.lookupImage(image);
});
$scope.parseDate = function(dateString) {
return Date.parse(dateString);
};
$scope.getFormattedCommand = ImageMetadataService.getFormattedCommand;
}
};
return directiveDefinitionObject;
});

View file

@ -14,7 +14,8 @@ angular.module('quay').directive('quayServiceStatusBar', function () {
StatusService.getStatus(function(data) {
$scope.indicator = data['status']['indicator'];
$scope.incidents = data['incidents'];
$scope.incidents = data['incidents'] || [];
$scope.scheduled_maintenances = data['scheduled_maintenances'] || [];
});
}
};

View file

@ -13,7 +13,7 @@ angular.module('quay').directive('repoListGrid', function () {
starred: '=starred',
user: "=user",
namespace: '=namespace',
toggleStar: '&toggleStar'
starToggled: '&starToggled'
},
controller: function($scope, $element, UserService) {
$scope.isOrganization = function(namespace) {

View file

@ -0,0 +1,53 @@
/**
* An element that displays the star status of a repository and allows it to be toggled.
*/
angular.module('quay').directive('repoStar', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/repo-star.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
repository: '=repository',
starToggled: '&starToggled'
},
controller: function($scope, $element, UserService, ApiService) {
// Star a repository or unstar a repository.
$scope.toggleStar = function() {
if ($scope.repository.is_starred) {
unstarRepo();
} else {
starRepo();
}
};
// Star a repository and update the UI.
var starRepo = function() {
var data = {
'namespace': $scope.repository.namespace,
'repository': $scope.repository.name
};
ApiService.createStar(data).then(function(result) {
$scope.repository.is_starred = true;
$scope.starToggled({'repository': $scope.repository});
}, ApiService.errorDisplay('Could not star repository'));
};
// Unstar a repository and update the UI.
var unstarRepo = function(repo) {
var data = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name
};
ApiService.deleteStar(null, data).then(function(result) {
$scope.repository.is_starred = false;
$scope.starToggled({'repository': $scope.repository});
}, ApiService.errorDisplay('Could not unstar repository'));
};
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,91 @@
/**
* An element which displays a table of events on a repository and allows them to be
* edited.
*/
angular.module('quay').directive('repositoryEventsTable', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/repository-events-table.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'repository': '=repository'
},
controller: function($scope, $element, ApiService, Restangular, UtilService, ExternalNotificationData) {
$scope.showNewNotificationCounter = 0;
var loadNotifications = function() {
if (!$scope.repository || $scope.notificationsResource) { return; }
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name
};
$scope.notificationsResource = ApiService.listRepoNotificationsAsResource(params).get(
function(resp) {
$scope.notifications = resp.notifications;
return $scope.notifications;
});
};
$scope.$watch('repository', loadNotifications);
loadNotifications();
$scope.handleNotificationCreated = function(notification) {
$scope.notifications.push(notification);
};
$scope.askCreateNotification = function() {
$scope.showNewNotificationCounter++;
};
$scope.getEventInfo = function(notification) {
return ExternalNotificationData.getEventInfo(notification.event);
};
$scope.getMethodInfo = function(notification) {
return ExternalNotificationData.getMethodInfo(notification.method);
};
$scope.deleteNotification = function(notification) {
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'uuid': notification.uuid
};
ApiService.deleteRepoNotification(null, params).then(function() {
var index = $.inArray(notification, $scope.notifications);
if (index < 0) { return; }
$scope.notifications.splice(index, 1);
}, ApiService.errorDisplay('Cannot delete notification'));
};
$scope.showWebhookInfo = function(notification) {
var eventId = notification.event;
document.location = 'http://docs.quay.io/guides/notifications.html#webhook_' + eventId;
};
$scope.testNotification = function(notification) {
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'uuid': notification.uuid
};
ApiService.testRepoNotification(null, params).then(function() {
bootbox.dialog({
"title": "Test Notification Queued",
"message": "A test version of this notification has been queued and should appear shortly",
"buttons": {
"close": {
"label": "Close",
"className": "btn-primary"
}
}
});
}, ApiService.errorDisplay('Could not issue test notification'));
};
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,147 @@
/**
* An element which displays a table of permissions on a repository and allows them to be
* edited.
*/
angular.module('quay').directive('repositoryPermissionsTable', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/repository-permissions-table.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'repository': '=repository'
},
controller: function($scope, $element, ApiService, Restangular, UtilService) {
$scope.roles = [
{ 'id': 'read', 'title': 'Read', 'kind': 'success' },
{ 'id': 'write', 'title': 'Write', 'kind': 'success' },
{ 'id': 'admin', 'title': 'Admin', 'kind': 'primary' }
];
$scope.permissionResources = {'team': {}, 'user': {}};
$scope.permissionCache = {};
$scope.permissions = {};
$scope.addPermissionInfo = {};
var loadAllPermissions = function() {
if (!$scope.repository) { return; }
fetchPermissions('user');
fetchPermissions('team');
};
var fetchPermissions = function(kind) {
if ($scope.permissionResources[kind]['loading'] != null) {
return;
}
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name
};
var Kind = kind[0].toUpperCase() + kind.substring(1);
var r = ApiService['listRepo' + Kind + 'PermissionsAsResource'](params).get(function(resp) {
$scope.permissions[kind] = resp.permissions;
return resp.permissions;
});
$scope.permissionResources[kind] = r;
};
$scope.$watch('repository', loadAllPermissions);
loadAllPermissions();
var getPermissionEndpoint = function(entityName, kind) {
var namespace = $scope.repository.namespace;
var name = $scope.repository.name;
var url = UtilService.getRestUrl('repository', namespace, name, 'permissions', kind, entityName);
return Restangular.one(url);
};
$scope.buildEntityForPermission = function(name, permission, kind) {
var key = name + ':' + kind;
if ($scope.permissionCache[key]) {
return $scope.permissionCache[key];
}
return $scope.permissionCache[key] = {
'kind': kind,
'name': name,
'is_robot': permission.is_robot,
'is_org_member': permission.is_org_member
};
};
$scope.addPermission = function() {
$scope.addPermissionInfo['working'] = true;
$scope.addNewPermission($scope.addPermissionInfo.entity, $scope.addPermissionInfo.role)
};
$scope.grantPermission = function(entity, callback) {
$scope.addRole(entity.name, 'read', entity.kind, callback);
};
$scope.addNewPermission = function(entity, opt_role) {
// Don't allow duplicates.
if (!entity || !entity.kind || $scope.permissions[entity.kind][entity.name]) {
$scope.addPermissionInfo = {};
return;
}
if (entity.is_org_member === false) {
$scope.grantPermissionInfo = {
'entity': entity
};
return;
}
$scope.addRole(entity.name, opt_role || 'read', entity.kind);
};
$scope.deleteRole = function(entityName, kind) {
var errorHandler = ApiService.errorDisplay('Cannot change permission', function(resp) {
if (resp.status == 409) {
return 'Cannot change permission as you do not have the authority';
}
});
var endpoint = getPermissionEndpoint(entityName, kind);
endpoint.customDELETE().then(function() {
delete $scope.permissions[kind][entityName];
}, errorHandler);
};
$scope.addRole = function(entityName, role, kind, opt_callback) {
var permission = {
'role': role,
};
var errorHandler = ApiService.errorDisplay('Cannot change permission', function() {
opt_callback && opt_callback(false);
$scope.addPermissionInfo = {};
});
var endpoint = getPermissionEndpoint(entityName, kind);
endpoint.customPUT(permission).then(function(result) {
$scope.permissions[kind][entityName] = result;
$scope.addPermissionInfo = {};
opt_callback && opt_callback(true)
}, errorHandler);
};
$scope.setRole = function(role, entityName, kind) {
var errorDisplay = ApiService.errorDisplay(function(resp) {
$scope.permissions[kind][entityName] = {'role': currentRole};
});
var permission = $scope.permissions[kind][entityName];
var currentRole = permission.role;
permission.role = role;
var endpoint = getPermissionEndpoint(entityName, kind);
endpoint.customPUT(permission).then(function() {}, errorDisplay);
};
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,68 @@
/**
* An element which displays a table of tokens on a repository and allows them to be
* edited.
*/
angular.module('quay').directive('repositoryTokensTable', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/repository-tokens-table.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'repository': '=repository',
'hasTokens': '=hasTokens'
},
controller: function($scope, $element, ApiService, Restangular, UtilService) {
$scope.roles = [
{ 'id': 'read', 'title': 'Read', 'kind': 'success' },
{ 'id': 'write', 'title': 'Write', 'kind': 'success' },
{ 'id': 'admin', 'title': 'Admin', 'kind': 'primary' }
];
$scope.hasTokens = false;
var loadTokens = function() {
if (!$scope.repository || $scope.tokensResource) { return; }
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name
};
$scope.tokensResource = ApiService.listRepoTokensAsResource(params).get(function(resp) {
$scope.tokens = resp.tokens;
$scope.hasTokens = Object.keys($scope.tokens).length >= 1;
}, ApiService.errorDisplay('Could not load access tokens'));
};
$scope.$watch('repository', loadTokens);
loadTokens();
$scope.deleteToken = function(tokenCode) {
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'code': tokenCode
};
ApiService.deleteToken(null, params).then(function() {
delete $scope.tokens[tokenCode];
});
};
$scope.changeTokenAccess = function(tokenCode, newAccess) {
var role = {
'role': newAccess
};
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'code': tokenCode
};
ApiService.changeToken(role, params).then(function(updated) {
$scope.tokens[updated.code] = updated;
});
};
}
};
return directiveDefinitionObject;
});

View file

@ -21,13 +21,17 @@ angular.module('quay').directive('resourceView', function () {
}
var resources = $scope.resources || [$scope.resource];
if (!resources.length) {
return 'loading';
}
for (var i = 0; i < resources.length; ++i) {
var current = resources[i];
if (current.loading) {
return 'loading';
}
if (current.error) {
if (current.hasError) {
return 'error';
}
}

View file

@ -0,0 +1,28 @@
/**
* An element which displays sidebar information for a tag. Primarily used in the repo view.
*/
angular.module('quay').directive('tagInfoSidebar', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/tag-info-sidebar.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'tracker': '=tracker',
'tag': '=tag',
'imageSelected': '&imageSelected',
'deleteTagRequested': '&deleteTagRequested'
},
controller: function($scope, $element) {
$scope.$watch('tag', function(tag) {
if (!tag || !$scope.tracker) { return; }
$scope.tagImage = $scope.tracker.getImageForTag(tag);
$scope.tagData = $scope.tracker.lookupTag(tag);
});
}
};
return directiveDefinitionObject;
});

View file

@ -0,0 +1,148 @@
/**
* An element which adds a series of dialogs for performing operations for tags (adding, moving
* deleting).
*/
angular.module('quay').directive('tagOperationsDialog', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/tag-operations-dialog.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'repository': '=repository',
'images': '=images',
'actionHandler': '=actionHandler',
'tagChanged': '&tagChanged'
},
controller: function($scope, $element, $timeout, ApiService) {
$scope.addingTag = false;
var markChanged = function(added, removed) {
// Reload the repository and the images.
$scope.repository.get().then(function(resp) {
$scope.repository = resp;
var params = {
'repository': resp.namespace + '/' + resp.name
};
ApiService.listRepositoryImages(null, params).then(function(resp) {
$scope.images = resp.images;
// Note: We need the timeout here so that Angular can $digest the images change
// on the parent scope before the tagChanged callback occurs.
$timeout(function() {
$scope.tagChanged({
'data': { 'added': added, 'removed': removed }
});
}, 1);
})
});
};
$scope.isAnotherImageTag = function(image, tag) {
if (!$scope.repository || !$scope.images) { return; }
var found = $scope.repository.tags[tag];
if (found == null) { return false; }
return found.image_id != image;
};
$scope.isOwnedTag = function(image, tag) {
if (!$scope.repository || !$scope.images) { return; }
var found = $scope.repository.tags[tag];
if (found == null) { return false; }
return found.image_id == image;
};
$scope.createOrMoveTag = function(image, tag) {
if (!$scope.repository.can_write) { return; }
$scope.addingTag = true;
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'tag': tag
};
var data = {
'image': image
};
var errorHandler = ApiService.errorDisplay('Cannot create or move tag', function(resp) {
$element.find('#createOrMoveTagModal').modal('hide');
});
ApiService.changeTagImage(data, params).then(function(resp) {
$element.find('#createOrMoveTagModal').modal('hide');
$scope.addingTag = false;
markChanged([tag], []);
}, errorHandler);
};
$scope.deleteMultipleTags = function(tags, callback) {
var count = tags.length;
var perform = function(index) {
if (index >= count) {
callback(true);
markChanged([], tags);
return;
}
var tag_info = tags[index];
$scope.deleteTag(tag_info.name, function(result) {
if (!result) {
callback(false);
return;
}
perform(index + 1);
}, true);
};
perform(0);
};
$scope.deleteTag = function(tag, callback, opt_skipmarking) {
if (!$scope.repository.can_write) { return; }
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'tag': tag
};
var errorHandler = ApiService.errorDisplay('Cannot delete tag', callback);
ApiService.deleteFullTag(null, params).then(function() {
callback(true);
!opt_skipmarking && markChanged([], [tag]);
}, errorHandler);
};
$scope.actionHandler = {
'askDeleteTag': function(tag) {
$scope.deleteTagInfo = {
'tag': tag
};
},
'askDeleteMultipleTags': function(tags) {
$scope.deleteMultipleTagsInfo = {
'tags': tags
};
},
'askAddTag': function(image) {
$scope.tagToCreate = '';
$scope.toTagImage = image;
$scope.addingTag = false;
$scope.addTagForm.$setPristine();
$element.find('#createOrMoveTagModal').modal('show');
}
};
}
};
return directiveDefinitionObject;
});

View file

@ -31,7 +31,9 @@ var DEPTH_WIDTH = 140;
/**
* Based off of http://mbostock.github.io/d3/talk/20111018/tree.html by Mike Bostock (@mbostock)
*/
function ImageHistoryTree(namespace, name, images, formatComment, formatTime, formatCommand) {
function ImageHistoryTree(namespace, name, images, formatComment, formatTime, formatCommand,
opt_tagFilter) {
/**
* The namespace of the repo.
*/
@ -62,6 +64,11 @@ function ImageHistoryTree(namespace, name, images, formatComment, formatTime, fo
*/
this.formatCommand_ = formatCommand;
/**
* Method for filtering the tags and image paths displayed in the tree.
*/
this.tagFilter_ = opt_tagFilter || function() { return true; };
/**
* The current tag (if any).
*/
@ -153,7 +160,7 @@ ImageHistoryTree.prototype.updateDimensions_ = function() {
$('#' + container).removeOverscroll();
var viewportHeight = $(window).height();
var boundingBox = document.getElementById(container).getBoundingClientRect();
document.getElementById(container).style.maxHeight = (viewportHeight - boundingBox.top - 150) + 'px';
document.getElementById(container).style.maxHeight = (viewportHeight - boundingBox.top - 100) + 'px';
this.setupOverscroll_();
@ -526,7 +533,14 @@ ImageHistoryTree.prototype.pruneUnreferenced_ = function(node) {
return node.children.length == 0;
}
return (node.children.length == 0 && node.tags.length == 0);
var tags = [];
for (var i = 0; i < node.tags.length; ++i) {
if (this.tagFilter_(node.tags[i])) {
tags.push(node.tags[i]);
}
}
return (node.children.length == 0 && tags.length == 0);
};
@ -549,12 +563,12 @@ ImageHistoryTree.prototype.determineMaximumHeight_ = function(node) {
* compact.
*/
ImageHistoryTree.prototype.collapseNodes_ = function(node) {
if (node.children.length == 1) {
if (node.children && node.children.length == 1) {
// Keep searching downward until we find a node with more than a single child.
var current = node;
var previous = node;
var encountered = [];
while (current.children.length == 1) {
while (current.children.length == 1 && current.tags.length == 0) {
encountered.push(current);
previous = current;
current = current.children[0];

View file

@ -1,6 +1,6 @@
(function() {
/**
* Repository admin/settings page.
* DEPRECATED: Repository admin/settings page.
*/
angular.module('quayPages').config(['pages', function(pages) {
pages.create('repo-admin', 'repo-admin.html', RepoAdminCtrl);

View file

@ -52,38 +52,14 @@
return !!UserService.getOrganization(namespace);
};
// Star a repository or unstar a repository.
$scope.toggleStar = function(repo) {
$scope.starToggled = function(repo) {
if (repo.is_starred) {
unstarRepo(repo);
} else {
starRepo(repo);
}
}
// Star a repository and update the UI.
var starRepo = function(repo) {
var data = {
'namespace': repo.namespace,
'repository': repo.name
};
ApiService.createStar(data).then(function(result) {
repo.is_starred = true;
$scope.starred_repositories.value.push(repo);
}, ApiService.errorDisplay('Could not star repository'));
};
// Unstar a repository and update the UI.
var unstarRepo = function(repo) {
var data = {
'repository': repo.namespace + '/' + repo.name
};
ApiService.deleteStar(null, data).then(function(result) {
repo.is_starred = false;
} else {
$scope.starred_repositories.value = $scope.starred_repositories.value.filter(function(repo) {
return repo.is_starred;
});
}, ApiService.errorDisplay('Could not unstar repository'));
}
};
// Finds a duplicate repo if it exists. If it doesn't, inserts the repo.

View file

@ -3,10 +3,124 @@
* Repository view page.
*/
angular.module('quayPages').config(['pages', function(pages) {
pages.create('repo-view', 'repo-view.html', RepoCtrl);
pages.create('repo-view', 'repo-view.html', RepoViewCtrl, {
'newLayout': true,
'title': '{{ namespace }}/{{ name }}',
'description': 'Repository {{ namespace }}/{{ name }}'
}, ['layout'])
pages.create('repo-view', 'old-repo-view.html', OldRepoViewCtrl, {
}, ['old-layout']);
}]);
function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiService, $routeParams, $rootScope, $location, $timeout, Config, UtilService) {
function RepoViewCtrl($scope, $routeParams, $location, ApiService, UserService, AngularPollChannel) {
$scope.namespace = $routeParams.namespace;
$scope.name = $routeParams.name;
$scope.logsShown = 0;
$scope.viewScope = {
'selectedTags': [],
'repository': null,
'images': null,
'imagesResource': null,
'builds': null,
'changesVisible': false
};
var buildPollChannel = null;
// Make sure we track the current user.
UserService.updateUserIn($scope);
// Watch the selected tags and update the URL accordingly.
$scope.$watch('viewScope.selectedTags', function(selectedTags) {
if (!selectedTags || !$scope.viewScope.repository) { return; }
var tags = filterTags(selectedTags);
if (!tags.length) {
$location.search('tag', null);
return;
}
$location.search('tag', tags.join(','));
}, true);
// Watch the repository to filter any tags removed.
$scope.$watch('viewScope.repository', function(repository) {
if (!repository) { return; }
$scope.viewScope.selectedTags = filterTags($scope.viewScope.selectedTags);
});
var filterTags = function(tags) {
return (tags || []).filter(function(tag) {
return !!$scope.viewScope.repository.tags[tag];
});
};
var loadRepository = function() {
var params = {
'repository': $scope.namespace + '/' + $scope.name
};
$scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) {
$scope.viewScope.repository = repo;
$scope.setTags($routeParams.tag);
// Track builds.
buildPollChannel = AngularPollChannel.create($scope, loadRepositoryBuilds, 5000 /* 5s */);
buildPollChannel.start();
});
};
var loadImages = function() {
var params = {
'repository': $scope.namespace + '/' + $scope.name
};
$scope.viewScope.imagesResource = ApiService.listRepositoryImagesAsResource(params).get(function(resp) {
$scope.viewScope.images = resp.images;
});
};
var loadRepositoryBuilds = function(callback) {
var params = {
'repository': $scope.namespace + '/' + $scope.name,
'limit': 3
};
var errorHandler = function() {
callback(false);
};
$scope.repositoryBuildsResource = ApiService.getRepoBuildsAsResource(params, /* background */true).get(function(resp) {
$scope.viewScope.builds = resp.builds;
callback(true);
}, errorHandler);
};
// Load the repository and images.
loadRepository();
loadImages();
$scope.setTags = function(tagNames) {
if (!tagNames) {
$scope.viewScope.selectedTags = [];
return;
}
$scope.viewScope.selectedTags = $.unique(tagNames.split(','));
};
$scope.showLogs = function() {
$scope.logsShown++;
};
$scope.handleChangesState = function(value) {
$scope.viewScope.changesVisible = value;
};
}
function OldRepoViewCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiService, $routeParams, $rootScope, $location, $timeout, Config, UtilService) {
$scope.Config = Config;
var namespace = $routeParams.namespace;

View file

@ -2,8 +2,8 @@
* Helper service for defining the various kinds of build triggers and retrieving information
* about them.
*/
angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'KeyService',
function(UtilService, $sanitize, KeyService) {
angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'KeyService', 'Features', 'CookieService',
function(UtilService, $sanitize, KeyService, Features, CookieService) {
var triggerService = {};
var triggerTypes = {
@ -53,15 +53,46 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K
var redirect_uri = KeyService['githubRedirectUri'] + '/trigger/' +
namespace + '/' + repository;
// TODO(jschorr): Remove once the new layout is in place.
if (CookieService.get('quay.exp-new-layout') == 'true') {
redirect_uri += '/__new';
}
var authorize_url = KeyService['githubTriggerAuthorizeUrl'];
var client_id = KeyService['githubTriggerClientId'];
return authorize_url + 'client_id=' + client_id +
'&scope=repo,user:email&redirect_uri=' + redirect_uri;
},
'is_enabled': function() {
return Features.GITHUB_BUILD;
},
'icon': 'fa-github',
'title': function() {
var isEnterprise = KeyService.isEnterprise('github-trigger');
if (isEnterprise) {
return 'GitHub Enterprise Repository Push';
}
return 'GitHub Repository Push';
}
}
}
triggerService.getTypes = function() {
var types = [];
for (var key in triggerTypes) {
if (!triggerTypes.hasOwnProperty(key)) {
continue;
}
types.push(key);
}
return types;
};
triggerService.getRedirectUrl = function(name, namespace, repository) {
var type = triggerTypes[name];
if (!type) {
@ -70,6 +101,14 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K
return type['get_redirect_url'](namespace, repository);
};
triggerService.getTitle = function(name) {
var type = triggerTypes[name];
if (!type) {
return 'Unknown';
}
return type['title']();
};
triggerService.getDescription = function(name, config) {
var type = triggerTypes[name];
if (!type) {
@ -78,6 +117,10 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K
return type['description'](config);
};
triggerService.getMetadata = function(name) {
return triggerTypes[name];
};
triggerService.getRunParameters = function(name, config) {
var type = triggerTypes[name];
if (!type) {

View file

@ -2,6 +2,61 @@
* Service which provides helper methods for performing some simple UI operations.
*/
angular.module('quay').factory('UIService', [function() {
var CheckStateController = function(items, opt_checked) {
this.items = items;
this.checked = opt_checked || [];
this.listeners_ = [];
};
CheckStateController.prototype.listen = function(callback) {
this.listeners_.push(callback);
};
CheckStateController.prototype.isChecked = function(item) {
return $.inArray(item, this.checked) >= 0;
};
CheckStateController.prototype.toggleItem = function(item) {
if (this.isChecked(item)) {
this.uncheckItem(item);
} else {
this.checkItem(item);
}
};
CheckStateController.prototype.toggleItems = function() {
if (this.checked.length) {
this.checked = [];
} else {
this.checked = this.items.slice();
}
this.callListeners_();
};
CheckStateController.prototype.checkByFilter = function(filter) {
this.checked = $.grep(this.items, filter);
this.callListeners_();
};
CheckStateController.prototype.checkItem = function(item) {
this.checked.push(item);
this.callListeners_();
};
CheckStateController.prototype.uncheckItem = function(item) {
this.checked = $.grep(this.checked, function(cItem) {
return cItem != item;
});
this.callListeners_();
};
CheckStateController.prototype.callListeners_ = function() {
var checked = this.checked;
this.listeners_.map(function(listener) {
listener(checked);
});
};
var uiService = {};
uiService.hidePopover = function(elem) {
@ -33,5 +88,9 @@ angular.module('quay').factory('UIService', [function() {
}
};
uiService.createCheckStateController = function(items, opt_checked) {
return new CheckStateController(items, opt_checked);
};
return uiService;
}]);