Merge branch 'master' into git
This commit is contained in:
commit
5a29218c5c
173 changed files with 151322 additions and 527 deletions
|
@ -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;
|
||||
});
|
233
static/js/directives/repo-view/repo-panel-builds.js
Normal file
233
static/js/directives/repo-view/repo-panel-builds.js
Normal 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;
|
||||
});
|
||||
|
204
static/js/directives/repo-view/repo-panel-changes.js
Normal file
204
static/js/directives/repo-view/repo-panel-changes.js
Normal 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;
|
||||
});
|
||||
|
24
static/js/directives/repo-view/repo-panel-info.js
Normal file
24
static/js/directives/repo-view/repo-panel-info.js
Normal 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;
|
||||
});
|
||||
|
84
static/js/directives/repo-view/repo-panel-settings.js
Normal file
84
static/js/directives/repo-view/repo-panel-settings.js
Normal 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 '[](' + 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;
|
||||
});
|
||||
|
209
static/js/directives/repo-view/repo-panel-tags.js
Normal file
209
static/js/directives/repo-view/repo-panel-tags.js
Normal 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;
|
||||
});
|
||||
|
|
@ -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) {
|
||||
}
|
||||
|
|
22
static/js/directives/ui/build-mini-status.js
Normal file
22
static/js/directives/ui/build-mini-status.js
Normal 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;
|
||||
});
|
22
static/js/directives/ui/build-state-icon.js
Normal file
22
static/js/directives/ui/build-state-icon.js
Normal 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;
|
||||
});
|
|
@ -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 = {
|
||||
|
|
|
@ -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 () {
|
||||
|
|
22
static/js/directives/ui/filter-control.js
Normal file
22
static/js/directives/ui/filter-control.js
Normal 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;
|
||||
});
|
34
static/js/directives/ui/image-changes-view.js
Normal file
34
static/js/directives/ui/image-changes-view.js
Normal 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;
|
||||
});
|
32
static/js/directives/ui/image-info-sidebar.js
Normal file
32
static/js/directives/ui/image-info-sidebar.js
Normal 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;
|
||||
});
|
|
@ -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'] || [];
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
53
static/js/directives/ui/repo-star.js
Normal file
53
static/js/directives/ui/repo-star.js
Normal 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;
|
||||
});
|
91
static/js/directives/ui/repository-events-table.js
Normal file
91
static/js/directives/ui/repository-events-table.js
Normal 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;
|
||||
});
|
147
static/js/directives/ui/repository-permissions-table.js
Normal file
147
static/js/directives/ui/repository-permissions-table.js
Normal 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;
|
||||
});
|
68
static/js/directives/ui/repository-tokens-table.js
Normal file
68
static/js/directives/ui/repository-tokens-table.js
Normal 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;
|
||||
});
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
28
static/js/directives/ui/tag-info-sidebar.js
Normal file
28
static/js/directives/ui/tag-info-sidebar.js
Normal 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;
|
||||
});
|
148
static/js/directives/ui/tag-operations-dialog.js
Normal file
148
static/js/directives/ui/tag-operations-dialog.js
Normal 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;
|
||||
});
|
|
@ -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];
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}]);
|
||||
|
|
Reference in a new issue