8a94e38028
- Convert the build view page over to use the new class - Add code so that if the builds logs returned by the API start in the set we already have, we only add the new ones
2674 lines
74 KiB
JavaScript
2674 lines
74 KiB
JavaScript
function GuideCtrl() {
|
|
}
|
|
|
|
function SecurityCtrl($scope) {
|
|
}
|
|
|
|
function ContactCtrl($scope) {
|
|
}
|
|
|
|
function PlansCtrl($scope, $location, UserService, PlanService, $routeParams) {
|
|
// Monitor any user changes and place the current user into the scope.
|
|
UserService.updateUserIn($scope);
|
|
|
|
$scope.signedIn = function() {
|
|
$('#signinModal').modal('hide');
|
|
PlanService.handleNotedPlan();
|
|
};
|
|
|
|
$scope.buyNow = function(plan) {
|
|
PlanService.notePlan(plan);
|
|
if ($scope.user && !$scope.user.anonymous) {
|
|
PlanService.handleNotedPlan();
|
|
} else {
|
|
$('#signinModal').modal({});
|
|
}
|
|
};
|
|
|
|
// Load the list of plans.
|
|
PlanService.getPlans(function(plans) {
|
|
$scope.plans = plans;
|
|
|
|
if ($scope && $routeParams['trial-plan']) {
|
|
$scope.buyNow($routeParams['trial-plan']);
|
|
}
|
|
}, /* include the personal plan */ true);
|
|
}
|
|
|
|
function TutorialCtrl($scope, AngularTour, AngularTourSignals, UserService, Config) {
|
|
// Default to showing sudo on all commands if on linux.
|
|
var showSudo = navigator.appVersion.indexOf("Linux") != -1;
|
|
|
|
$scope.tour = {
|
|
'title': Config.REGISTRY_TITLE_SHORT + ' Tutorial',
|
|
'initialScope': {
|
|
'showSudo': showSudo,
|
|
'domainName': Config.getDomain()
|
|
},
|
|
'steps': [
|
|
{
|
|
'title': 'Welcome to the ' + Config.REGISTRY_TITLE_SHORT + ' tutorial!',
|
|
'templateUrl': '/static/tutorial/welcome.html'
|
|
},
|
|
{
|
|
'title': 'Sign in to get started',
|
|
'templateUrl': '/static/tutorial/signup.html',
|
|
'signal': function($tourScope) {
|
|
var user = UserService.currentUser();
|
|
$tourScope.username = user.username;
|
|
$tourScope.email = user.email;
|
|
$tourScope.inOrganization = user.organizations && user.organizations.length > 0;
|
|
return !user.anonymous;
|
|
}
|
|
},
|
|
{
|
|
'title': 'Step 1: Login to ' + Config.REGISTRY_TITLE,
|
|
'templateUrl': '/static/tutorial/docker-login.html',
|
|
'signal': AngularTourSignals.serverEvent('/realtime/user/subscribe?events=docker-cli',
|
|
function(message) {
|
|
return message['data']['action'] == 'login';
|
|
}),
|
|
'waitMessage': "Waiting for docker login",
|
|
'skipTitle': "I'm already logged in",
|
|
'mixpanelEvent': 'tutorial_start'
|
|
},
|
|
{
|
|
'title': 'Step 2: Create a new container',
|
|
'templateUrl': '/static/tutorial/create-container.html'
|
|
},
|
|
{
|
|
'title': 'Step 3: Create a new image',
|
|
'templateUrl': '/static/tutorial/create-image.html'
|
|
},
|
|
{
|
|
'title': 'Step 4: Push the image to ' + Config.REGISTRY_TITLE,
|
|
'templateUrl': '/static/tutorial/push-image.html',
|
|
'signal': AngularTourSignals.serverEvent('/realtime/user/subscribe?events=docker-cli',
|
|
function(message, tourScope) {
|
|
var pushing = message['data']['action'] == 'push_repo';
|
|
if (pushing) {
|
|
tourScope.repoName = message['data']['repository'];
|
|
}
|
|
return pushing;
|
|
}),
|
|
'waitMessage': "Waiting for repository push to begin",
|
|
'mixpanelEvent': 'tutorial_wait_for_push'
|
|
},
|
|
{
|
|
'title': 'Push in progress',
|
|
'templateUrl': '/static/tutorial/pushing.html',
|
|
'signal': AngularTourSignals.serverEvent('/realtime/user/subscribe?events=docker-cli',
|
|
function(message, tourScope) {
|
|
return message['data']['action'] == 'pushed_repo';
|
|
}),
|
|
'waitMessage': "Waiting for repository push to complete"
|
|
},
|
|
{
|
|
'title': 'Step 5: View the repository on ' + Config.REGISTRY_TITLE,
|
|
'templateUrl': '/static/tutorial/view-repo.html',
|
|
'signal': AngularTourSignals.matchesLocation('/repository/'),
|
|
'overlayable': true,
|
|
'mixpanelEvent': 'tutorial_push_complete'
|
|
},
|
|
{
|
|
'templateUrl': '/static/tutorial/view-repo.html',
|
|
'signal': AngularTourSignals.matchesLocation('/repository/'),
|
|
'overlayable': true
|
|
},
|
|
{
|
|
'templateUrl': '/static/tutorial/waiting-repo-list.html',
|
|
'signal': AngularTourSignals.elementAvaliable('*[data-repo="{{username}}/{{repoName}}"]'),
|
|
'overlayable': true
|
|
},
|
|
{
|
|
'templateUrl': '/static/tutorial/repo-list.html',
|
|
'signal': AngularTourSignals.matchesLocation('/repository/{{username}}/{{repoName}}'),
|
|
'element': '*[data-repo="{{username}}/{{repoName}}"]',
|
|
'overlayable': true
|
|
},
|
|
{
|
|
'title': 'Repository View',
|
|
'content': 'This is the repository view page. It displays all the primary information about your repository.',
|
|
'overlayable': true,
|
|
'mixpanelEvent': 'tutorial_view_repo'
|
|
},
|
|
{
|
|
'title': 'Image History',
|
|
'content': 'The tree displays the full history of your repository, including all its tag. ' +
|
|
'You can click on a tag or image to see its information.',
|
|
'element': '#image-history-container',
|
|
'overlayable': true
|
|
},
|
|
{
|
|
'title': 'Tag/Image Information',
|
|
'content': 'This panel displays information about the currently selected tag or image',
|
|
'element': '#side-panel',
|
|
'overlayable': true
|
|
},
|
|
{
|
|
'title': 'Select tag or image',
|
|
'content': 'You can select a tag or image by clicking on this dropdown',
|
|
'element': '#side-panel-dropdown',
|
|
'overlayable': true
|
|
},
|
|
{
|
|
'content': 'To view the admin settings for the repository, click on the gear',
|
|
'element': '#admin-cog',
|
|
'signal': AngularTourSignals.matchesLocation('/repository/{{username}}/{{repoName}}/admin'),
|
|
'overlayable': true
|
|
},
|
|
{
|
|
'title': 'Repository Admin',
|
|
'content': "The repository admin panel allows for modification of a repository's permissions, notifications, visibility and other settings",
|
|
'overlayable': true,
|
|
'mixpanelEvent': 'tutorial_view_admin'
|
|
},
|
|
{
|
|
'title': 'Permissions',
|
|
'templateUrl': '/static/tutorial/permissions.html',
|
|
'overlayable': true,
|
|
'element': '#permissions'
|
|
},
|
|
{
|
|
'title': 'Adding a permission',
|
|
'content': 'To add an <b>additional</b> permission, enter a username or robot account name into the autocomplete ' +
|
|
'or hit the dropdown arrow to manage robot accounts',
|
|
'overlayable': true,
|
|
'element': '#add-entity-permission'
|
|
},
|
|
{
|
|
'templateUrl': '/static/tutorial/done.html',
|
|
'overlayable': true,
|
|
'mixpanelEvent': 'tutorial_complete'
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
function RepoListCtrl($scope, $sanitize, Restangular, UserService, ApiService) {
|
|
$scope.namespace = null;
|
|
$scope.page = 1;
|
|
$scope.publicPageCount = null;
|
|
|
|
// Monitor changes in the user.
|
|
UserService.updateUserIn($scope, function() {
|
|
loadMyRepos($scope.namespace);
|
|
});
|
|
|
|
// Monitor changes in the namespace.
|
|
$scope.$watch('namespace', function(namespace) {
|
|
loadMyRepos(namespace);
|
|
});
|
|
|
|
$scope.movePublicPage = function(increment) {
|
|
if ($scope.publicPageCount == null) {
|
|
return;
|
|
}
|
|
|
|
$scope.page += increment;
|
|
if ($scope.page < 1) {
|
|
$scope.page = 1;
|
|
}
|
|
|
|
if ($scope.page > $scope.publicPageCount) {
|
|
$scope.page = $scope.publicPageCount;
|
|
}
|
|
|
|
loadPublicRepos();
|
|
};
|
|
|
|
var loadMyRepos = function(namespace) {
|
|
if (!$scope.user || $scope.user.anonymous || !namespace) {
|
|
return;
|
|
}
|
|
|
|
var options = {'public': false, 'sort': true, 'namespace': namespace};
|
|
|
|
$scope.user_repositories = ApiService.listReposAsResource().withOptions(options).get(function(resp) {
|
|
return resp.repositories;
|
|
});
|
|
};
|
|
|
|
var loadPublicRepos = function() {
|
|
var options = {
|
|
'public': true,
|
|
'private': false,
|
|
'sort': true,
|
|
'limit': 10,
|
|
'page': $scope.page,
|
|
'count': $scope.page == 1
|
|
};
|
|
|
|
$scope.public_repositories = ApiService.listReposAsResource().withOptions(options).get(function(resp) {
|
|
if (resp.count) {
|
|
$scope.publicPageCount = Math.ceil(resp.count / 10);
|
|
}
|
|
return resp.repositories;
|
|
});
|
|
};
|
|
|
|
loadPublicRepos();
|
|
}
|
|
|
|
function LandingCtrl($scope, UserService, ApiService, Features, Config) {
|
|
$scope.namespace = null;
|
|
$scope.currentScreenshot = 'repo-view';
|
|
|
|
$scope.$watch('namespace', function(namespace) {
|
|
loadMyRepos(namespace);
|
|
});
|
|
|
|
UserService.updateUserIn($scope, function() {
|
|
loadMyRepos($scope.namespace);
|
|
});
|
|
|
|
$scope.changeScreenshot = function(screenshot) {
|
|
$scope.currentScreenshot = screenshot;
|
|
};
|
|
|
|
$scope.canCreateRepo = function(namespace) {
|
|
if (!$scope.user) { return false; }
|
|
|
|
if (namespace == $scope.user.username) {
|
|
return true;
|
|
}
|
|
|
|
if ($scope.user.organizations) {
|
|
for (var i = 0; i < $scope.user.organizations.length; ++i) {
|
|
var org = $scope.user.organizations[i];
|
|
if (org.name == namespace) {
|
|
return org.can_create_repo;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
var loadMyRepos = function(namespace) {
|
|
if (!$scope.user || $scope.user.anonymous || !namespace) {
|
|
return;
|
|
}
|
|
|
|
var options = {'limit': 4, 'public': false, 'sort': true, 'namespace': namespace };
|
|
$scope.my_repositories = ApiService.listReposAsResource().withOptions(options).get(function(resp) {
|
|
return resp.repositories;
|
|
});
|
|
};
|
|
|
|
$scope.chromify = function() {
|
|
browserchrome.update();
|
|
|
|
var jcarousel = $('.jcarousel');
|
|
|
|
jcarousel
|
|
.on('jcarousel:reload jcarousel:create', function () {
|
|
var width = jcarousel.innerWidth();
|
|
jcarousel.jcarousel('items').css('width', width + 'px');
|
|
})
|
|
.jcarousel({
|
|
wrap: 'circular'
|
|
});
|
|
|
|
$('.jcarousel-control-prev')
|
|
.on('jcarouselcontrol:active', function() {
|
|
$(this).removeClass('inactive');
|
|
})
|
|
.on('jcarouselcontrol:inactive', function() {
|
|
$(this).addClass('inactive');
|
|
})
|
|
.jcarouselControl({
|
|
target: '-=1'
|
|
});
|
|
|
|
$('.jcarousel-control-next')
|
|
.on('jcarouselcontrol:active', function() {
|
|
$(this).removeClass('inactive');
|
|
})
|
|
.on('jcarouselcontrol:inactive', function() {
|
|
$(this).addClass('inactive');
|
|
})
|
|
.jcarouselControl({
|
|
target: '+=1'
|
|
});
|
|
|
|
$('.jcarousel-pagination')
|
|
.on('jcarouselpagination:active', 'a', function() {
|
|
$(this).addClass('active');
|
|
})
|
|
.on('jcarouselpagination:inactive', 'a', function() {
|
|
$(this).removeClass('active');
|
|
})
|
|
.jcarouselPagination({
|
|
'item': function(page, carouselItems) {
|
|
return '<a href="javascript:void(0)" class="jcarousel-page"></a>';
|
|
}
|
|
});
|
|
};
|
|
|
|
$scope.getEnterpriseLogo = function() {
|
|
if (!Config.ENTERPRISE_LOGO_URL) {
|
|
return '/static/img/quay-logo.png';
|
|
}
|
|
|
|
return Config.ENTERPRISE_LOGO_URL;
|
|
};
|
|
}
|
|
|
|
function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiService, $routeParams, $rootScope, $location, $timeout, Config) {
|
|
$scope.Config = Config;
|
|
|
|
var namespace = $routeParams.namespace;
|
|
var name = $routeParams.name;
|
|
|
|
$rootScope.title = 'Loading...';
|
|
|
|
// Watch for the destruction of the scope.
|
|
$scope.$on('$destroy', function() {
|
|
if ($scope.tree) {
|
|
$scope.tree.dispose();
|
|
}
|
|
});
|
|
|
|
// Watch for changes to the repository.
|
|
$scope.$watch('repo', function() {
|
|
if ($scope.tree) {
|
|
$timeout(function() {
|
|
$scope.tree.notifyResized();
|
|
});
|
|
}
|
|
});
|
|
|
|
// Watch for changes to the tag parameter.
|
|
$scope.$on('$routeUpdate', function(){
|
|
if ($location.search().tag) {
|
|
$scope.setTag($location.search().tag, false);
|
|
} else if ($location.search().image) {
|
|
$scope.setImage($location.search().image, false);
|
|
} else {
|
|
$scope.setTag($location.search().tag, false);
|
|
}
|
|
});
|
|
|
|
// Start scope methods //////////////////////////////////////////
|
|
|
|
$scope.buildDialogShowCounter = 0;
|
|
$scope.getFormattedCommand = ImageMetadataService.getFormattedCommand;
|
|
|
|
$scope.showNewBuildDialog = function() {
|
|
$scope.buildDialogShowCounter++;
|
|
};
|
|
|
|
$scope.handleBuildStarted = function(build) {
|
|
getBuildInfo($scope.repo);
|
|
startBuildInfoTimer($scope.repo);
|
|
};
|
|
|
|
$scope.showBuild = function(buildInfo) {
|
|
$location.path('/repository/' + namespace + '/' + name + '/build');
|
|
$location.search('current', buildInfo.id);
|
|
};
|
|
|
|
$scope.isPushing = function(images) {
|
|
if (!images) { return false; }
|
|
|
|
var cached = images.__isPushing;
|
|
if (cached !== undefined) {
|
|
return cached;
|
|
}
|
|
|
|
return images.__isPushing = $scope.isPushingInternal(images);
|
|
};
|
|
|
|
$scope.isPushingInternal = function(images) {
|
|
if (!images) { return false; }
|
|
|
|
for (var i = 0; i < images.length; ++i) {
|
|
if (images[i].uploading) { return true; }
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
$scope.getTooltipCommand = function(image) {
|
|
var sanitized = ImageMetadataService.getEscapedFormattedCommand(image);
|
|
return '<span class=\'codetooltip\'>' + sanitized + '</span>';
|
|
};
|
|
|
|
$scope.updateForDescription = function(content) {
|
|
$scope.repo.description = content;
|
|
$scope.repo.put();
|
|
};
|
|
|
|
$scope.parseDate = function(dateString) {
|
|
return Date.parse(dateString);
|
|
};
|
|
|
|
$scope.getTimeSince = function(createdTime) {
|
|
return moment($scope.parseDate(createdTime)).fromNow();
|
|
};
|
|
|
|
$scope.loadImageChanges = function(image) {
|
|
if (!image) { return; }
|
|
|
|
var params = {'repository': namespace + '/' + name, 'image_id': image.id};
|
|
$scope.currentImageChangeResource = ApiService.getImageChangesAsResource(params).get(function(ci) {
|
|
$scope.currentImageChanges = ci;
|
|
});
|
|
};
|
|
|
|
$scope.getMoreCount = function(changes) {
|
|
if (!changes) { return 0; }
|
|
var addedDisplayed = Math.min(2, changes.added.length);
|
|
var removedDisplayed = Math.min(2, changes.removed.length);
|
|
var changedDisplayed = Math.min(2, changes.changed.length);
|
|
|
|
return (changes.added.length + changes.removed.length + changes.changed.length) -
|
|
addedDisplayed - removedDisplayed - changedDisplayed;
|
|
};
|
|
|
|
$scope.showAddTag = function(image) {
|
|
$scope.toTagImage = image;
|
|
$('#addTagModal').modal('show');
|
|
setTimeout(function() {
|
|
$('#tagName').focus();
|
|
}, 500);
|
|
};
|
|
|
|
$scope.isOwnedTag = function(image, tagName) {
|
|
if (!image || !tagName) { return false; }
|
|
return image.tags.indexOf(tagName) >= 0;
|
|
};
|
|
|
|
$scope.isAnotherImageTag = function(image, tagName) {
|
|
if (!image || !tagName) { return false; }
|
|
return image.tags.indexOf(tagName) < 0 && $scope.repo.tags[tagName];
|
|
};
|
|
|
|
$scope.askDeleteTag = function(tagName) {
|
|
if (!$scope.repo.can_admin) { return; }
|
|
|
|
$scope.tagToDelete = tagName;
|
|
$('#confirmdeleteTagModal').modal('show');
|
|
};
|
|
|
|
$scope.findImageForTag = function(tag) {
|
|
return tag && $scope.imageByDBID && $scope.imageByDBID[tag.dbid];
|
|
};
|
|
|
|
$scope.createOrMoveTag = function(image, tagName, opt_invalid) {
|
|
if (opt_invalid) { return; }
|
|
|
|
$scope.creatingTag = true;
|
|
|
|
var params = {
|
|
'repository': $scope.repo.namespace + '/' + $scope.repo.name,
|
|
'tag': tagName
|
|
};
|
|
|
|
var data = {
|
|
'image': image.id
|
|
};
|
|
|
|
var errorHandler = ApiService.errorDisplay('Cannot create or move tag', function(resp) {
|
|
$('#addTagModal').modal('hide');
|
|
});
|
|
|
|
ApiService.changeTagImage(data, params).then(function(resp) {
|
|
$scope.creatingTag = false;
|
|
loadViewInfo();
|
|
$('#addTagModal').modal('hide');
|
|
}, errorHandler);
|
|
};
|
|
|
|
$scope.deleteTag = function(tagName) {
|
|
if (!$scope.repo.can_admin) { return; }
|
|
|
|
var params = {
|
|
'repository': namespace + '/' + name,
|
|
'tag': tagName
|
|
};
|
|
|
|
var errorHandler = ApiService.errorDisplay('Cannot delete tag', function() {
|
|
$('#confirmdeleteTagModal').modal('hide');
|
|
$scope.deletingTag = false;
|
|
});
|
|
|
|
$scope.deletingTag = true;
|
|
|
|
ApiService.deleteFullTag(null, params).then(function() {
|
|
loadViewInfo();
|
|
$('#confirmdeleteTagModal').modal('hide');
|
|
$scope.deletingTag = false;
|
|
}, errorHandler);
|
|
};
|
|
|
|
$scope.getImagesForTagBySize = function(tag) {
|
|
var images = [];
|
|
forAllTagImages(tag, function(image) {
|
|
images.push(image);
|
|
});
|
|
|
|
images.sort(function(a, b) {
|
|
return b.size - a.size;
|
|
});
|
|
|
|
return images;
|
|
};
|
|
|
|
$scope.getTotalSize = function(tag) {
|
|
var size = 0;
|
|
forAllTagImages(tag, function(image) {
|
|
size += image.size;
|
|
});
|
|
return size;
|
|
};
|
|
|
|
$scope.setImage = function(imageId, opt_updateURL) {
|
|
var image = null;
|
|
for (var i = 0; i < $scope.images.length; ++i) {
|
|
var currentImage = $scope.images[i];
|
|
if (currentImage.id == imageId || currentImage.id.substr(0, 12) == imageId) {
|
|
image = currentImage;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!image) { return; }
|
|
|
|
$scope.currentTag = null;
|
|
$scope.currentImage = image;
|
|
$scope.loadImageChanges(image);
|
|
if ($scope.tree) {
|
|
$scope.tree.setImage(image.id);
|
|
}
|
|
|
|
if (opt_updateURL) {
|
|
$location.search('tag', null);
|
|
$location.search('image', imageId.substr(0, 12));
|
|
}
|
|
};
|
|
|
|
$scope.setTag = function(tagName, opt_updateURL) {
|
|
var repo = $scope.repo;
|
|
if (!repo) { return; }
|
|
|
|
var proposedTag = repo.tags[tagName];
|
|
if (!proposedTag) {
|
|
// We must find a good default.
|
|
for (tagName in repo.tags) {
|
|
if (!proposedTag || tagName == 'latest') {
|
|
proposedTag = repo.tags[tagName];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (proposedTag) {
|
|
$scope.currentTag = proposedTag;
|
|
$scope.currentImage = null;
|
|
|
|
if ($scope.tree) {
|
|
$scope.tree.setTag(proposedTag.name);
|
|
}
|
|
|
|
if (opt_updateURL) {
|
|
$location.search('image', null);
|
|
$location.search('tag', proposedTag.name);
|
|
}
|
|
}
|
|
|
|
if ($scope.currentTag && !repo.tags[$scope.currentTag.name]) {
|
|
$scope.currentTag = null;
|
|
$scope.currentImage = null;
|
|
}
|
|
};
|
|
|
|
$scope.getFirstTextLine = getFirstTextLine;
|
|
|
|
$scope.getTagCount = function(repo) {
|
|
if (!repo) { return 0; }
|
|
var count = 0;
|
|
for (var tag in repo.tags) {
|
|
++count;
|
|
}
|
|
return count;
|
|
};
|
|
|
|
$scope.hideTagMenu = function(tagName, clientX, clientY) {
|
|
$scope.currentMenuTag = null;
|
|
|
|
var tagMenu = $("#tagContextMenu");
|
|
tagMenu.hide();
|
|
};
|
|
|
|
$scope.showTagMenu = function(tagName, clientX, clientY) {
|
|
if (!$scope.repo.can_admin) { return; }
|
|
|
|
$scope.currentMenuTag = tagName;
|
|
|
|
var tagMenu = $("#tagContextMenu");
|
|
tagMenu.css({
|
|
display: "block",
|
|
left: clientX,
|
|
top: clientY
|
|
});
|
|
|
|
tagMenu.on("blur", function() {
|
|
setTimeout(function() {
|
|
tagMenu.hide();
|
|
}, 100); // Needed to allow clicking on menu items.
|
|
});
|
|
|
|
tagMenu.on("click", "a", function() {
|
|
setTimeout(function() {
|
|
tagMenu.hide();
|
|
}, 100); // Needed to allow clicking on menu items.
|
|
});
|
|
|
|
tagMenu[0].focus();
|
|
};
|
|
|
|
var getDefaultTag = function() {
|
|
if ($scope.repo === undefined) {
|
|
return undefined;
|
|
} else if ($scope.repo.tags.hasOwnProperty('latest')) {
|
|
return $scope.repo.tags['latest'];
|
|
} else {
|
|
for (key in $scope.repo.tags) {
|
|
return $scope.repo.tags[key];
|
|
}
|
|
}
|
|
};
|
|
|
|
var forAllTagImages = function(tag, callback) {
|
|
if (!tag || !$scope.imageByDBID) { return; }
|
|
|
|
var tag_image = $scope.imageByDBID[tag.dbid];
|
|
if (!tag_image) { return; }
|
|
|
|
// Callback the tag's image itself.
|
|
callback(tag_image);
|
|
|
|
// Callback any parent images.
|
|
if (!tag_image.ancestors) { return; }
|
|
var ancestors = tag_image.ancestors.split('/');
|
|
for (var i = 0; i < ancestors.length; ++i) {
|
|
var image = $scope.imageByDBID[ancestors[i]];
|
|
if (image) {
|
|
callback(image);
|
|
}
|
|
}
|
|
};
|
|
|
|
var fetchRepository = function() {
|
|
var params = {'repository': namespace + '/' + name};
|
|
$rootScope.title = 'Loading Repository...';
|
|
$scope.repository = ApiService.getRepoAsResource(params).get(function(repo) {
|
|
// Set the repository object.
|
|
$scope.repo = repo;
|
|
|
|
// Set the default tag.
|
|
$scope.setTag($routeParams.tag);
|
|
|
|
// Set the title of the page.
|
|
var qualifiedRepoName = namespace + '/' + name;
|
|
$rootScope.title = qualifiedRepoName;
|
|
var kind = repo.is_public ? 'public' : 'private';
|
|
$rootScope.description = jQuery(getFirstTextLine(repo.description)).text() ||
|
|
'Visualization of images and tags for ' + kind + ' Docker repository: ' + qualifiedRepoName;
|
|
|
|
// Load the builds for this repository. If none are active it will cancel the poll.
|
|
startBuildInfoTimer(repo);
|
|
});
|
|
};
|
|
|
|
var startBuildInfoTimer = function(repo) {
|
|
if ($scope.interval) { return; }
|
|
|
|
getBuildInfo(repo);
|
|
$scope.interval = setInterval(function() {
|
|
$scope.$apply(function() { getBuildInfo(repo); });
|
|
}, 5000);
|
|
|
|
$scope.$on("$destroy", function() {
|
|
cancelBuildInfoTimer();
|
|
});
|
|
};
|
|
|
|
var cancelBuildInfoTimer = function() {
|
|
if ($scope.interval) {
|
|
clearInterval($scope.interval);
|
|
}
|
|
};
|
|
|
|
var getBuildInfo = function(repo) {
|
|
var params = {
|
|
'repository': repo.namespace + '/' + repo.name
|
|
};
|
|
|
|
ApiService.getRepoBuilds(null, params, true).then(function(resp) {
|
|
// Build a filtered list of the builds that are currently running.
|
|
var runningBuilds = [];
|
|
for (var i = 0; i < resp.builds.length; ++i) {
|
|
var build = resp.builds[i];
|
|
if (build['phase'] != 'complete' && build['phase'] != 'error') {
|
|
runningBuilds.push(build);
|
|
}
|
|
}
|
|
|
|
var existingBuilds = $scope.runningBuilds || [];
|
|
$scope.runningBuilds = runningBuilds;
|
|
$scope.buildHistory = resp.builds;
|
|
|
|
if (!runningBuilds.length) {
|
|
// Cancel the build timer.
|
|
cancelBuildInfoTimer();
|
|
|
|
// Mark the repo as no longer building.
|
|
$scope.repo.is_building = false;
|
|
|
|
// Reload the repo information if all of the builds recently finished.
|
|
if (existingBuilds.length > 0) {
|
|
loadViewInfo();
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
var listImages = function() {
|
|
var params = {'repository': namespace + '/' + name};
|
|
$scope.imageHistory = ApiService.listRepositoryImagesAsResource(params).get(function(resp) {
|
|
$scope.images = resp.images;
|
|
$scope.specificImages = [];
|
|
|
|
// Build various images for quick lookup of images.
|
|
$scope.imageByDBID = {};
|
|
for (var i = 0; i < $scope.images.length; ++i) {
|
|
var currentImage = $scope.images[i];
|
|
$scope.imageByDBID[currentImage.dbid] = currentImage;
|
|
}
|
|
|
|
// Dispose of any existing tree.
|
|
if ($scope.tree) {
|
|
$scope.tree.dispose();
|
|
}
|
|
|
|
// Create the new tree.
|
|
var tree = new ImageHistoryTree(namespace, name, resp.images,
|
|
getFirstTextLine, $scope.getTimeSince, ImageMetadataService.getEscapedFormattedCommand);
|
|
|
|
$scope.tree = tree.draw('image-history-container');
|
|
if ($scope.tree) {
|
|
// If we already have a tag, use it
|
|
if ($scope.currentTag) {
|
|
$scope.tree.setTag($scope.currentTag.name);
|
|
}
|
|
|
|
// 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, true); });
|
|
});
|
|
|
|
$($scope.tree).bind('imageChanged', function(e) {
|
|
$scope.$apply(function() { $scope.setImage(e.image.id, true); });
|
|
});
|
|
|
|
$($scope.tree).bind('showTagMenu', function(e) {
|
|
$scope.$apply(function() { $scope.showTagMenu(e.tag, e.clientX, e.clientY); });
|
|
});
|
|
|
|
$($scope.tree).bind('hideTagMenu', function(e) {
|
|
$scope.$apply(function() { $scope.hideTagMenu(); });
|
|
});
|
|
}
|
|
|
|
if ($routeParams.image) {
|
|
$scope.setImage($routeParams.image);
|
|
}
|
|
|
|
return resp.images;
|
|
});
|
|
};
|
|
|
|
var loadViewInfo = function() {
|
|
fetchRepository();
|
|
listImages();
|
|
};
|
|
|
|
// Fetch the repository itself as well as the image history.
|
|
loadViewInfo();
|
|
}
|
|
|
|
function BuildPackageCtrl($scope, Restangular, ApiService, DataFileService, $routeParams, $rootScope, $location, $timeout) {
|
|
var namespace = $routeParams.namespace;
|
|
var name = $routeParams.name;
|
|
var buildid = $routeParams.buildid;
|
|
|
|
var params = {
|
|
'repository': namespace + '/' + name,
|
|
'build_uuid': buildid
|
|
};
|
|
|
|
$scope.initializeTree = function() {
|
|
if ($scope.drawn) {
|
|
$scope.tree.notifyResized();
|
|
return;
|
|
}
|
|
|
|
$scope.drawn = true;
|
|
$timeout(function() {
|
|
$scope.tree.draw('file-tree-container');
|
|
}, 10);
|
|
};
|
|
|
|
var determineDockerfilePath = function() {
|
|
var dockerfilePath = 'Dockerfile';
|
|
if ($scope.repobuild['job_config']) {
|
|
var dockerfileFolder = ($scope.repobuild['job_config']['build_subdir'] || '');
|
|
if (dockerfileFolder[0] == '/') {
|
|
dockerfileFolder = dockerfileFolder.substr(1);
|
|
}
|
|
if (dockerfileFolder && dockerfileFolder[dockerfileFolder.length - 1] != '/') {
|
|
dockerfileFolder += '/';
|
|
}
|
|
dockerfilePath = dockerfileFolder + 'Dockerfile';
|
|
}
|
|
return dockerfilePath;
|
|
};
|
|
|
|
var processBuildPack = function(uint8array) {
|
|
var archiveread = function(files) {
|
|
var getpath = function(file) {
|
|
return file.path;
|
|
};
|
|
|
|
var findFile = function(path) {
|
|
for (var i = 0; i < files.length; ++i) {
|
|
var file = files[i];
|
|
if (file.path == path) {
|
|
return file;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
$scope.tree = new FileTree($.map(files, getpath));
|
|
$($scope.tree).bind('fileClicked', function(e) {
|
|
var file = findFile(e.path);
|
|
if (file && file.canRead) {
|
|
saveAs(file.toBlob(), file.name);
|
|
}
|
|
});
|
|
|
|
var dockerfilePath = determineDockerfilePath();
|
|
var dockerfile = findFile(dockerfilePath);
|
|
if (dockerfile && dockerfile.canRead) {
|
|
DataFileService.blobToString(dockerfile.toBlob(), function(result) {
|
|
$scope.$apply(function() {
|
|
$scope.dockerFilePath = dockerfilePath || 'Dockerfile';
|
|
$scope.dockerFileContents = result;
|
|
});
|
|
});
|
|
}
|
|
|
|
$scope.loaded = true;
|
|
};
|
|
|
|
var notarchive = function() {
|
|
DataFileService.arrayToString(uint8array, function(r) {
|
|
$scope.dockerFilePath = 'Dockerfile';
|
|
$scope.dockerFileContents = r;
|
|
$scope.loaded = true;
|
|
});
|
|
};
|
|
|
|
setTimeout(function() {
|
|
$scope.$apply(function() {
|
|
DataFileService.readDataArrayAsPossibleArchive(uint8array, archiveread, notarchive);
|
|
});
|
|
}, 0);
|
|
};
|
|
|
|
var downloadBuildPack = function(url) {
|
|
$scope.downloadProgress = 0;
|
|
$scope.downloading = true;
|
|
startDownload(url);
|
|
};
|
|
|
|
var startDownload = function(url) {
|
|
var onprogress = function(p) {
|
|
$scope.downloadProgress = p * 100;
|
|
};
|
|
|
|
var onerror = function() {
|
|
$scope.downloading = false;
|
|
$scope.downloadError = true;
|
|
};
|
|
|
|
var onloaded = function(uint8array) {
|
|
$scope.downloading = false;
|
|
processBuildPack(uint8array);
|
|
};
|
|
|
|
DataFileService.downloadDataFileAsArrayBuffer($scope, url,
|
|
onprogress, onerror, onloaded);
|
|
};
|
|
|
|
var getBuildInfo = function() {
|
|
$scope.repository_build = ApiService.getRepoBuildStatus(null, params, true).then(function(resp) {
|
|
if (!resp['is_writer']) {
|
|
$rootScope.title = 'Unknown build';
|
|
$scope.accessDenied = true;
|
|
return;
|
|
}
|
|
|
|
$rootScope.title = 'Repository Build Pack - ' + resp['display_name'];
|
|
$scope.repobuild = resp;
|
|
$scope.repo = {
|
|
'namespace': namespace,
|
|
'name': name
|
|
};
|
|
|
|
downloadBuildPack(resp['archive_url']);
|
|
return resp;
|
|
});
|
|
};
|
|
|
|
getBuildInfo();
|
|
}
|
|
|
|
function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $location, $interval, $sanitize,
|
|
ansi2html, AngularViewArray, AngularPollChannel) {
|
|
var namespace = $routeParams.namespace;
|
|
var name = $routeParams.name;
|
|
|
|
// Watch for changes to the current parameter.
|
|
$scope.$on('$routeUpdate', function(){
|
|
if ($location.search().current) {
|
|
$scope.setCurrentBuild($location.search().current, false);
|
|
}
|
|
});
|
|
|
|
$scope.builds = null;
|
|
$scope.pollChannel = null;
|
|
$scope.buildDialogShowCounter = 0;
|
|
|
|
$scope.showNewBuildDialog = function() {
|
|
$scope.buildDialogShowCounter++;
|
|
};
|
|
|
|
$scope.handleBuildStarted = function(newBuild) {
|
|
if (!$scope.builds) { return; }
|
|
|
|
$scope.builds.unshift(newBuild);
|
|
$scope.setCurrentBuild(newBuild['id'], true);
|
|
};
|
|
|
|
$scope.adjustLogHeight = function() {
|
|
var triggerOffset = 0;
|
|
if ($scope.currentBuild && $scope.currentBuild.trigger) {
|
|
triggerOffset = 85;
|
|
}
|
|
$('.build-logs').height($(window).height() - 415 - triggerOffset);
|
|
};
|
|
|
|
$scope.askRestartBuild = function(build) {
|
|
$('#confirmRestartBuildModal').modal({});
|
|
};
|
|
|
|
$scope.restartBuild = function(build) {
|
|
$('#confirmRestartBuildModal').modal('hide');
|
|
|
|
var subdirectory = '';
|
|
if (build['job_config']) {
|
|
subdirectory = build['job_config']['build_subdir'] || '';
|
|
}
|
|
|
|
var data = {
|
|
'file_id': build['resource_key'],
|
|
'subdirectory': subdirectory,
|
|
'docker_tags': build['job_config']['docker_tags']
|
|
};
|
|
|
|
if (build['pull_robot']) {
|
|
data['pull_robot'] = build['pull_robot']['name'];
|
|
}
|
|
|
|
var params = {
|
|
'repository': namespace + '/' + name
|
|
};
|
|
|
|
ApiService.requestRepoBuild(data, params).then(function(newBuild) {
|
|
if (!$scope.builds) { return; }
|
|
|
|
$scope.builds.unshift(newBuild);
|
|
$scope.setCurrentBuild(newBuild['id'], true);
|
|
});
|
|
};
|
|
|
|
$scope.hasLogs = function(container) {
|
|
return container.logs.hasEntries;
|
|
};
|
|
|
|
$scope.setCurrentBuild = function(buildId, opt_updateURL) {
|
|
if (!$scope.builds) { return; }
|
|
|
|
// Find the build.
|
|
for (var i = 0; i < $scope.builds.length; ++i) {
|
|
if ($scope.builds[i].id == buildId) {
|
|
$scope.setCurrentBuildInternal(i, $scope.builds[i], opt_updateURL);
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
|
|
$scope.processANSI = function(message, container) {
|
|
var filter = container.logs._filter = (container.logs._filter || ansi2html.create());
|
|
|
|
// Note: order is important here.
|
|
var setup = filter.getSetupHtml();
|
|
var stream = filter.addInputToStream(message);
|
|
var teardown = filter.getTeardownHtml();
|
|
return setup + stream + teardown;
|
|
};
|
|
|
|
$scope.setCurrentBuildInternal = function(index, build, opt_updateURL) {
|
|
if (build == $scope.currentBuild) { return; }
|
|
|
|
$scope.logEntries = null;
|
|
$scope.logStartIndex = null;
|
|
$scope.currentParentEntry = null;
|
|
|
|
$scope.currentBuild = build;
|
|
|
|
if (opt_updateURL) {
|
|
if (build) {
|
|
$location.search('current', build.id);
|
|
} else {
|
|
$location.search('current', null);
|
|
}
|
|
}
|
|
|
|
// Timeout needed to ensure the log element has been created
|
|
// before its height is adjusted.
|
|
setTimeout(function() {
|
|
$scope.adjustLogHeight();
|
|
}, 1);
|
|
|
|
// Stop any existing polling.
|
|
if ($scope.pollChannel) {
|
|
$scope.pollChannel.stop();
|
|
}
|
|
|
|
// Create a new channel for polling the build status and logs.
|
|
var conductStatusAndLogRequest = function(callback) {
|
|
getBuildStatusAndLogs(build, callback);
|
|
};
|
|
|
|
$scope.pollChannel = AngularPollChannel.create($scope, conductStatusAndLogRequest, 5 * 1000 /* 5s */);
|
|
$scope.pollChannel.start();
|
|
};
|
|
|
|
var processLogs = function(logs, startIndex, endIndex) {
|
|
if (!$scope.logEntries) { $scope.logEntries = []; }
|
|
|
|
// If the start index given is less than that requested, then we've received a larger
|
|
// pool of logs, and we need to only consider the new ones.
|
|
if (startIndex < $scope.logStartIndex) {
|
|
logs = logs.slice($scope.logStartIndex - startIndex);
|
|
}
|
|
|
|
for (var i = 0; i < logs.length; ++i) {
|
|
var entry = logs[i];
|
|
var type = entry['type'] || 'entry';
|
|
if (type == 'command' || type == 'phase' || type == 'error') {
|
|
entry['logs'] = AngularViewArray.create();
|
|
entry['index'] = $scope.logStartIndex + i;
|
|
|
|
$scope.logEntries.push(entry);
|
|
$scope.currentParentEntry = entry;
|
|
} else if ($scope.currentParentEntry) {
|
|
$scope.currentParentEntry['logs'].push(entry);
|
|
}
|
|
}
|
|
|
|
return endIndex;
|
|
};
|
|
|
|
var getBuildStatusAndLogs = function(build, callback) {
|
|
var params = {
|
|
'repository': namespace + '/' + name,
|
|
'build_uuid': build.id
|
|
};
|
|
|
|
ApiService.getRepoBuildStatus(null, params, true).then(function(resp) {
|
|
if (build != $scope.currentBuild) { callback(false); return; }
|
|
|
|
// Note: We use extend here rather than replacing as Angular is depending on the
|
|
// root build object to remain the same object.
|
|
var matchingBuilds = $.grep($scope.builds, function(elem) {
|
|
return elem['id'] == resp['id']
|
|
});
|
|
|
|
var currentBuild = matchingBuilds.length > 0 ? matchingBuilds[0] : null;
|
|
if (currentBuild) {
|
|
currentBuild = $.extend(true, currentBuild, resp);
|
|
} else {
|
|
currentBuild = resp;
|
|
$scope.builds.push(currentBuild);
|
|
}
|
|
|
|
// Load the updated logs for the build.
|
|
var options = {
|
|
'start': $scope.logStartIndex
|
|
};
|
|
|
|
ApiService.getRepoBuildLogsAsResource(params, true).withOptions(options).get(function(resp) {
|
|
if (build != $scope.currentBuild) { callback(false); return; }
|
|
|
|
// Process the logs we've received.
|
|
$scope.logStartIndex = processLogs(resp['logs'], resp['start'], resp['total']);
|
|
|
|
// If the build status is an error, open the last two log entries.
|
|
if (currentBuild['phase'] == 'error' && $scope.logEntries.length > 1) {
|
|
var openLogEntries = function(entry) {
|
|
if (entry.logs) {
|
|
entry.logs.setVisible(true);
|
|
}
|
|
};
|
|
|
|
openLogEntries($scope.logEntries[$scope.logEntries.length - 2]);
|
|
openLogEntries($scope.logEntries[$scope.logEntries.length - 1]);
|
|
}
|
|
|
|
// If the build phase is an error or a complete, then we mark the channel
|
|
// as closed.
|
|
callback(currentBuild['phase'] != 'error' && currentBuild['phase'] != 'complete');
|
|
}, function() {
|
|
callback(false);
|
|
});
|
|
}, function() {
|
|
callback(false);
|
|
});
|
|
};
|
|
|
|
var fetchRepository = function() {
|
|
var params = {'repository': namespace + '/' + name};
|
|
$rootScope.title = 'Loading Repository...';
|
|
$scope.repository = ApiService.getRepoAsResource(params).get(function(repo) {
|
|
if (!repo.can_write) {
|
|
$rootScope.title = 'Unknown builds';
|
|
$scope.accessDenied = true;
|
|
return;
|
|
}
|
|
|
|
$rootScope.title = 'Repository Builds';
|
|
$scope.repo = repo;
|
|
|
|
getBuildInfo();
|
|
});
|
|
};
|
|
|
|
var getBuildInfo = function(repo) {
|
|
var params = {
|
|
'repository': namespace + '/' + name
|
|
};
|
|
|
|
ApiService.getRepoBuilds(null, params).then(function(resp) {
|
|
$scope.builds = resp.builds;
|
|
|
|
if ($location.search().current) {
|
|
$scope.setCurrentBuild($location.search().current, false);
|
|
} else if ($scope.builds.length > 0) {
|
|
$scope.setCurrentBuild($scope.builds[0].id, true);
|
|
}
|
|
});
|
|
};
|
|
|
|
fetchRepository();
|
|
}
|
|
|
|
function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, $routeParams, $rootScope, $location, UserService, Config, Features, ExternalNotificationData) {
|
|
var namespace = $routeParams.namespace;
|
|
var name = $routeParams.name;
|
|
|
|
$scope.Features = Features;
|
|
$scope.permissions = {'team': [], 'user': []};
|
|
$scope.logsShown = 0;
|
|
$scope.deleting = false;
|
|
|
|
$scope.permissionCache = {};
|
|
|
|
$scope.githubRedirectUri = KeyService.githubRedirectUri;
|
|
$scope.githubClientId = KeyService.githubClientId;
|
|
|
|
$scope.showTriggerSetupCounter = 0;
|
|
|
|
$scope.getBadgeFormat = function(format, repo) {
|
|
if (!repo) { return; }
|
|
|
|
var imageUrl = Config.getUrl('/repository/' + namespace + '/' + name + '/status');
|
|
if (!$scope.repo.is_public) {
|
|
imageUrl += '?token=' + $scope.repo.status_token;
|
|
}
|
|
|
|
var linkUrl = Config.getUrl('/repository/' + namespace + '/' + 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.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.loadLogs = function() {
|
|
$scope.logsShown++;
|
|
};
|
|
|
|
$scope.grantRole = function() {
|
|
$('#confirmaddoutsideModal').modal('hide');
|
|
var entity = $scope.currentAddEntity;
|
|
$scope.addRole(entity.name, 'read', entity.kind, entity.is_org_member)
|
|
$scope.currentAddEntity = null;
|
|
};
|
|
|
|
$scope.addNewPermission = function(entity) {
|
|
// Don't allow duplicates.
|
|
if (!entity || !entity.kind || $scope.permissions[entity.kind][entity.name]) { return; }
|
|
|
|
if (entity.is_org_member === false) {
|
|
$scope.currentAddEntity = entity;
|
|
$('#confirmaddoutsideModal').modal('show');
|
|
return;
|
|
}
|
|
|
|
$scope.addRole(entity.name, '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 permissionDelete = Restangular.one(getRestUrl('repository', namespace, name, 'permissions', kind, entityName));
|
|
permissionDelete.customDELETE().then(function() {
|
|
delete $scope.permissions[kind][entityName];
|
|
}, errorHandler);
|
|
};
|
|
|
|
$scope.addRole = function(entityName, role, kind) {
|
|
var permission = {
|
|
'role': role,
|
|
};
|
|
|
|
var permissionPost = Restangular.one(getRestUrl('repository', namespace, name, 'permissions', kind, entityName));
|
|
permissionPost.customPUT(permission).then(function(result) {
|
|
$scope.permissions[kind][entityName] = result;
|
|
}, ApiService.errorDisplay('Cannot change permission'));
|
|
};
|
|
|
|
$scope.roles = [
|
|
{ 'id': 'read', 'title': 'Read', 'kind': 'success' },
|
|
{ 'id': 'write', 'title': 'Write', 'kind': 'success' },
|
|
{ 'id': 'admin', 'title': 'Admin', 'kind': 'primary' }
|
|
];
|
|
|
|
$scope.setRole = function(role, entityName, kind) {
|
|
var permission = $scope.permissions[kind][entityName];
|
|
var currentRole = permission.role;
|
|
permission.role = role;
|
|
|
|
var permissionPut = Restangular.one(getRestUrl('repository', namespace, name, 'permissions', kind, entityName));
|
|
permissionPut.customPUT(permission).then(function() {}, function(resp) {
|
|
$scope.permissions[kind][entityName] = {'role': currentRole};
|
|
$scope.changePermError = null;
|
|
if (resp.status == 409 || resp.data) {
|
|
$scope.changePermError = resp.data || '';
|
|
$('#channgechangepermModal').modal({});
|
|
} else {
|
|
$('#cannotchangeModal').modal({});
|
|
}
|
|
});
|
|
};
|
|
|
|
$scope.newTokenName = null;
|
|
|
|
$scope.createToken = function() {
|
|
var data = {
|
|
'friendlyName': $scope.newTokenName
|
|
};
|
|
|
|
var params = {'repository': namespace + '/' + name};
|
|
ApiService.createToken(data, params).then(function(newToken) {
|
|
$scope.newTokenName = null;
|
|
$scope.createTokenForm.$setPristine();
|
|
$scope.tokens[newToken.code] = newToken;
|
|
});
|
|
};
|
|
|
|
$scope.deleteToken = function(tokenCode) {
|
|
var params = {
|
|
'repository': namespace + '/' + 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': namespace + '/' + name,
|
|
'code': tokenCode
|
|
};
|
|
|
|
ApiService.changeToken(role, params).then(function(updated) {
|
|
$scope.tokens[updated.code] = updated;
|
|
});
|
|
};
|
|
|
|
$scope.shownTokenCounter = 0;
|
|
|
|
$scope.showToken = function(tokenCode) {
|
|
$scope.shownToken = $scope.tokens[tokenCode];
|
|
$scope.shownTokenCounter++;
|
|
};
|
|
|
|
$scope.askChangeAccess = function(newAccess) {
|
|
$('#make' + newAccess + 'Modal').modal({});
|
|
};
|
|
|
|
$scope.changeAccess = function(newAccess) {
|
|
$('#make' + newAccess + 'Modal').modal('hide');
|
|
|
|
var visibility = {
|
|
'visibility': newAccess
|
|
};
|
|
|
|
var params = {
|
|
'repository': namespace + '/' + name
|
|
};
|
|
|
|
ApiService.changeRepoVisibility(visibility, params).then(function() {
|
|
$scope.repo.is_public = newAccess == 'public';
|
|
}, function() {
|
|
$('#cannotchangeModal').modal({});
|
|
});
|
|
};
|
|
|
|
$scope.askDelete = function() {
|
|
$('#confirmdeleteModal').modal({});
|
|
};
|
|
|
|
$scope.deleteRepo = function() {
|
|
$('#confirmdeleteModal').modal('hide');
|
|
|
|
var params = {
|
|
'repository': namespace + '/' + name
|
|
};
|
|
|
|
$scope.deleting = true;
|
|
ApiService.deleteRepository(null, params).then(function() {
|
|
$scope.repo = null;
|
|
|
|
setTimeout(function() {
|
|
document.location = '/repository/';
|
|
}, 1000);
|
|
}, function() {
|
|
$scope.deleting = true;
|
|
$('#cannotchangeModal').modal({});
|
|
});
|
|
};
|
|
|
|
$scope.showNewNotificationCounter = 0;
|
|
|
|
$scope.showNewNotificationDialog = function() {
|
|
$scope.showNewNotificationCounter++;
|
|
};
|
|
|
|
$scope.handleNotificationCreated = function(notification) {
|
|
$scope.notifications.push(notification);
|
|
};
|
|
|
|
$scope.handleNotificationDeleted = function(notification) {
|
|
var index = $.inArray(notification, $scope.notifications);
|
|
if (index < 0) { return; }
|
|
$scope.notifications.splice(index, 1);
|
|
};
|
|
|
|
$scope.loadNotifications = function() {
|
|
var params = {
|
|
'repository': namespace + '/' + name
|
|
};
|
|
|
|
$scope.notificationsResource = ApiService.listRepoNotificationsAsResource(params).get(
|
|
function(resp) {
|
|
$scope.notifications = resp.notifications;
|
|
return $scope.notifications;
|
|
});
|
|
};
|
|
|
|
$scope.showBuild = function(buildInfo) {
|
|
$location.path('/repository/' + namespace + '/' + name + '/build');
|
|
$location.search('current', buildInfo.id);
|
|
};
|
|
|
|
$scope.loadTriggerBuildHistory = function(trigger) {
|
|
trigger.$loadingHistory = true;
|
|
|
|
var params = {
|
|
'repository': namespace + '/' + name,
|
|
'trigger_uuid': trigger.id,
|
|
'limit': 3
|
|
};
|
|
|
|
ApiService.listTriggerRecentBuilds(null, params).then(function(resp) {
|
|
trigger.$builds = resp['builds'];
|
|
trigger.$loadingHistory = false;
|
|
});
|
|
};
|
|
|
|
$scope.loadTriggers = function() {
|
|
var params = {
|
|
'repository': namespace + '/' + 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.new_trigger;
|
|
if (newTriggerId) {
|
|
for (var i = 0; i < $scope.triggers.length; ++i) {
|
|
var trigger = $scope.triggers[i];
|
|
if (trigger['id'] == newTriggerId && !trigger['is_active']) {
|
|
$scope.setupTrigger(trigger);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $scope.triggers;
|
|
});
|
|
};
|
|
|
|
$scope.setupTrigger = function(trigger) {
|
|
$scope.currentSetupTrigger = trigger;
|
|
$scope.showTriggerSetupCounter++;
|
|
};
|
|
|
|
$scope.cancelSetupTrigger = function(trigger) {
|
|
if ($scope.currentSetupTrigger != trigger) { return; }
|
|
|
|
$scope.currentSetupTrigger = null;
|
|
$scope.deleteTrigger(trigger);
|
|
};
|
|
|
|
$scope.startTrigger = function(trigger) {
|
|
var params = {
|
|
'repository': namespace + '/' + name,
|
|
'trigger_uuid': trigger.id
|
|
};
|
|
|
|
ApiService.manuallyStartBuildTrigger(null, params).then(function(resp) {
|
|
window.console.log(resp);
|
|
var url = '/repository/' + namespace + '/' + name + '/build?current=' + resp['id'];
|
|
document.location = url;
|
|
}, ApiService.errorDisplay('Could not start build'));
|
|
};
|
|
|
|
$scope.deleteTrigger = function(trigger) {
|
|
if (!trigger) { return; }
|
|
|
|
var params = {
|
|
'repository': namespace + '/' + name,
|
|
'trigger_uuid': trigger.id
|
|
};
|
|
|
|
ApiService.deleteBuildTrigger(null, params).then(function(resp) {
|
|
$scope.triggers.splice($scope.triggers.indexOf(trigger), 1);
|
|
});
|
|
};
|
|
|
|
var fetchTokens = function() {
|
|
var params = {
|
|
'repository': namespace + '/' + name
|
|
};
|
|
|
|
ApiService.listRepoTokens(null, params).then(function(resp) {
|
|
$scope.tokens = resp.tokens;
|
|
}, function() {
|
|
$scope.tokens = null;
|
|
});
|
|
};
|
|
|
|
var fetchPermissions = function(kind) {
|
|
var permissionsFetch = Restangular.one('repository/' + namespace + '/' + name + '/permissions/' + kind + '/');
|
|
permissionsFetch.get().then(function(resp) {
|
|
$scope.permissions[kind] = resp.permissions;
|
|
}, function() {
|
|
$scope.permissions[kind] = null;
|
|
});
|
|
};
|
|
|
|
var fetchRepository = function() {
|
|
var params = {
|
|
'repository': namespace + '/' + name
|
|
};
|
|
|
|
$scope.repository = ApiService.getRepoAsResource(params).get(function(repo) {
|
|
if (!repo.can_admin) {
|
|
$rootScope.title = 'Forbidden';
|
|
$scope.accessDenied = true;
|
|
return;
|
|
}
|
|
|
|
$scope.repo = repo;
|
|
$rootScope.title = 'Settings - ' + namespace + '/' + name;
|
|
$rootScope.description = 'Administrator settings for ' + namespace + '/' + name +
|
|
': Permissions, notifications and other settings';
|
|
|
|
// Fetch all the permissions and token info for the repository.
|
|
fetchPermissions('user');
|
|
fetchPermissions('team');
|
|
fetchTokens();
|
|
|
|
$('.info-icon').popover({
|
|
'trigger': 'hover',
|
|
'html': true
|
|
});
|
|
|
|
return $scope.repo;
|
|
});
|
|
};
|
|
|
|
// Fetch the repository.
|
|
fetchRepository();
|
|
}
|
|
|
|
function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, UserService, CookieService, KeyService,
|
|
$routeParams, $http, UIService, Features, Config) {
|
|
$scope.Features = Features;
|
|
|
|
if ($routeParams['migrate']) {
|
|
$('#migrateTab').tab('show')
|
|
}
|
|
|
|
UserService.updateUserIn($scope, function(user) {
|
|
$scope.cuser = jQuery.extend({}, user);
|
|
|
|
if ($scope.cuser.logins) {
|
|
for (var i = 0; i < $scope.cuser.logins.length; i++) {
|
|
var login = $scope.cuser.logins[i];
|
|
login.metadata = login.metadata || {};
|
|
|
|
if (login.service == 'github') {
|
|
$scope.hasGithubLogin = true;
|
|
$scope.githubLogin = login.metadata['service_username'];
|
|
}
|
|
|
|
if (login.service == 'google') {
|
|
$scope.hasGoogleLogin = true;
|
|
$scope.googleLogin = login.metadata['service_username'];
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
$scope.readyForPlan = function() {
|
|
// Show the subscribe dialog if a plan was requested.
|
|
return $routeParams['plan'];
|
|
};
|
|
|
|
$scope.loading = true;
|
|
$scope.updatingUser = false;
|
|
$scope.changePasswordSuccess = false;
|
|
$scope.changeEmailSent = false;
|
|
$scope.convertStep = 0;
|
|
$scope.org = {};
|
|
$scope.githubRedirectUri = KeyService.githubRedirectUri;
|
|
$scope.authorizedApps = null;
|
|
|
|
$scope.logsShown = 0;
|
|
$scope.invoicesShown = 0;
|
|
|
|
$scope.loadAuthedApps = function() {
|
|
if ($scope.authorizedApps) { return; }
|
|
|
|
ApiService.listUserAuthorizations().then(function(resp) {
|
|
$scope.authorizedApps = resp['authorizations'];
|
|
});
|
|
};
|
|
|
|
$scope.deleteAccess = function(accessTokenInfo) {
|
|
var params = {
|
|
'access_token_uuid': accessTokenInfo['uuid']
|
|
};
|
|
|
|
ApiService.deleteUserAuthorization(null, params).then(function(resp) {
|
|
$scope.authorizedApps.splice($scope.authorizedApps.indexOf(accessTokenInfo), 1);
|
|
}, ApiService.errorDisplay('Could not revoke authorization'));
|
|
};
|
|
|
|
$scope.loadLogs = function() {
|
|
if (!$scope.hasPaidBusinessPlan) { return; }
|
|
$scope.logsShown++;
|
|
};
|
|
|
|
$scope.loadInvoices = function() {
|
|
$scope.invoicesShown++;
|
|
};
|
|
|
|
$scope.planChanged = function(plan) {
|
|
$scope.hasPaidPlan = plan && plan.price > 0;
|
|
$scope.hasPaidBusinessPlan = PlanService.isOrgCompatible(plan) && plan.price > 0;
|
|
};
|
|
|
|
$scope.showConvertForm = function() {
|
|
if (Features.BILLING) {
|
|
PlanService.getMatchingBusinessPlan(function(plan) {
|
|
$scope.org.plan = plan;
|
|
});
|
|
|
|
PlanService.getPlans(function(plans) {
|
|
$scope.orgPlans = plans;
|
|
});
|
|
}
|
|
|
|
$scope.convertStep = 1;
|
|
};
|
|
|
|
$scope.convertToOrg = function() {
|
|
$('#reallyconvertModal').modal({});
|
|
};
|
|
|
|
$scope.reallyConvert = function() {
|
|
if (Config.AUTHENTICATION_TYPE != 'Database') { return; }
|
|
|
|
$scope.loading = true;
|
|
|
|
var data = {
|
|
'adminUser': $scope.org.adminUser,
|
|
'adminPassword': $scope.org.adminPassword,
|
|
'plan': $scope.org.plan ? $scope.org.plan.stripeId : ''
|
|
};
|
|
|
|
ApiService.convertUserToOrganization(data).then(function(resp) {
|
|
CookieService.putPermanent('quay.namespace', $scope.cuser.username);
|
|
UserService.load();
|
|
$location.path('/');
|
|
}, function(resp) {
|
|
$scope.loading = false;
|
|
if (resp.data.reason == 'invaliduser') {
|
|
$('#invalidadminModal').modal({});
|
|
} else {
|
|
$('#cannotconvertModal').modal({});
|
|
}
|
|
});
|
|
};
|
|
|
|
$scope.changeEmail = function() {
|
|
UIService.hidePopover('#changeEmailForm');
|
|
|
|
$scope.updatingUser = true;
|
|
$scope.changeEmailSent = false;
|
|
|
|
ApiService.changeUserDetails($scope.cuser).then(function() {
|
|
$scope.updatingUser = false;
|
|
$scope.changeEmailSent = true;
|
|
$scope.sentEmail = $scope.cuser.email;
|
|
|
|
// Reset the form.
|
|
delete $scope.cuser['repeatEmail'];
|
|
|
|
$scope.changeEmailForm.$setPristine();
|
|
}, function(result) {
|
|
$scope.updatingUser = false;
|
|
UIService.showFormError('#changeEmailForm', result);
|
|
});
|
|
};
|
|
|
|
$scope.changePassword = function() {
|
|
UIService.hidePopover('#changePasswordForm');
|
|
|
|
$scope.updatingUser = true;
|
|
$scope.changePasswordSuccess = false;
|
|
|
|
ApiService.changeUserDetails($scope.cuser).then(function(resp) {
|
|
|
|
$scope.updatingUser = false;
|
|
$scope.changePasswordSuccess = true;
|
|
|
|
// Reset the form
|
|
delete $scope.cuser['password']
|
|
delete $scope.cuser['repeatPassword']
|
|
|
|
$scope.changePasswordForm.$setPristine();
|
|
|
|
// Reload the user.
|
|
UserService.load();
|
|
}, function(result) {
|
|
$scope.updatingUser = false;
|
|
UIService.showFormError('#changePasswordForm', result);
|
|
});
|
|
};
|
|
}
|
|
|
|
function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService, ImageMetadataService) {
|
|
var namespace = $routeParams.namespace;
|
|
var name = $routeParams.name;
|
|
var imageid = $routeParams.image;
|
|
|
|
$scope.getFormattedCommand = ImageMetadataService.getFormattedCommand;
|
|
|
|
$scope.parseDate = function(dateString) {
|
|
return Date.parse(dateString);
|
|
};
|
|
|
|
$scope.getFolder = function(filepath) {
|
|
var index = filepath.lastIndexOf('/');
|
|
if (index < 0) {
|
|
return '';
|
|
}
|
|
return filepath.substr(0, index + 1);
|
|
};
|
|
|
|
$scope.getFolders = function(filepath) {
|
|
var index = filepath.lastIndexOf('/');
|
|
if (index < 0) {
|
|
return '';
|
|
}
|
|
|
|
return filepath.substr(0, index).split('/');
|
|
};
|
|
|
|
$scope.getFilename = function(filepath) {
|
|
var index = filepath.lastIndexOf('/');
|
|
if (index < 0) {
|
|
return filepath;
|
|
}
|
|
return filepath.substr(index + 1);
|
|
};
|
|
|
|
$scope.setFolderFilter = function(folderPath, index) {
|
|
var parts = folderPath.split('/');
|
|
parts = parts.slice(0, index + 1);
|
|
$scope.setFilter(parts.join('/'));
|
|
};
|
|
|
|
$scope.setFilter = function(filter) {
|
|
$scope.search = {};
|
|
$scope.search['$'] = filter;
|
|
document.getElementById('change-filter').value = filter;
|
|
};
|
|
|
|
$scope.initializeTree = function() {
|
|
if ($scope.tree) { return; }
|
|
|
|
$scope.tree = new ImageFileChangeTree($scope.image, $scope.combinedChanges);
|
|
$timeout(function() {
|
|
$scope.tree.draw('changes-tree-container');
|
|
}, 10);
|
|
};
|
|
|
|
var fetchRepository = function() {
|
|
var params = {
|
|
'repository': namespace + '/' + name
|
|
};
|
|
|
|
ApiService.getRepoAsResource(params).get(function(repo) {
|
|
$scope.repo = repo;
|
|
});
|
|
};
|
|
|
|
var fetchImage = function() {
|
|
var params = {
|
|
'repository': namespace + '/' + name,
|
|
'image_id': imageid
|
|
};
|
|
|
|
$scope.image = ApiService.getImageAsResource(params).get(function(image) {
|
|
if (!$scope.repo) {
|
|
$scope.repo = {
|
|
'name': name,
|
|
'namespace': namespace,
|
|
'is_public': true
|
|
};
|
|
}
|
|
|
|
$rootScope.title = 'View Image - ' + image.id;
|
|
$rootScope.description = 'Viewing docker image ' + image.id + ' under repository ' + namespace + '/' + name +
|
|
': Image changes tree and list view';
|
|
|
|
// Fetch the image's changes.
|
|
fetchChanges();
|
|
return image;
|
|
});
|
|
};
|
|
|
|
var fetchChanges = function() {
|
|
var params = {
|
|
'repository': namespace + '/' + name,
|
|
'image_id': imageid
|
|
};
|
|
|
|
ApiService.getImageChanges(null, params).then(function(changes) {
|
|
var combinedChanges = [];
|
|
var addCombinedChanges = function(c, kind) {
|
|
for (var i = 0; i < c.length; ++i) {
|
|
combinedChanges.push({
|
|
'kind': kind,
|
|
'file': c[i]
|
|
});
|
|
}
|
|
};
|
|
|
|
addCombinedChanges(changes.added, 'added');
|
|
addCombinedChanges(changes.removed, 'removed');
|
|
addCombinedChanges(changes.changed, 'changed');
|
|
|
|
$scope.combinedChanges = combinedChanges;
|
|
$scope.imageChanges = changes;
|
|
});
|
|
};
|
|
|
|
// Fetch the repository.
|
|
fetchRepository();
|
|
|
|
// Fetch the image.
|
|
fetchImage();
|
|
}
|
|
|
|
function V1Ctrl($scope, $location, UserService) {
|
|
UserService.updateUserIn($scope);
|
|
}
|
|
|
|
function NewRepoCtrl($scope, $location, $http, $timeout, UserService, ApiService, PlanService, KeyService, Features) {
|
|
UserService.updateUserIn($scope);
|
|
|
|
$scope.Features = Features;
|
|
$scope.githubRedirectUri = KeyService.githubRedirectUri;
|
|
$scope.githubClientId = KeyService.githubClientId;
|
|
|
|
$scope.repo = {
|
|
'is_public': 0,
|
|
'description': '',
|
|
'initialize': ''
|
|
};
|
|
|
|
// Watch the namespace on the repo. If it changes, we update the plan and the public/private
|
|
// accordingly.
|
|
$scope.isUserNamespace = true;
|
|
$scope.$watch('repo.namespace', function(namespace) {
|
|
// Note: Can initially be undefined.
|
|
if (!namespace) { return; }
|
|
|
|
var isUserNamespace = (namespace == $scope.user.username);
|
|
|
|
$scope.planRequired = null;
|
|
$scope.isUserNamespace = isUserNamespace;
|
|
|
|
// Determine whether private repositories are allowed for the namespace.
|
|
checkPrivateAllowed();
|
|
});
|
|
|
|
$scope.changeNamespace = function(namespace) {
|
|
$scope.repo.namespace = namespace;
|
|
};
|
|
|
|
$scope.handleBuildStarted = function() {
|
|
var repo = $scope.repo;
|
|
$location.path('/repository/' + repo.namespace + '/' + repo.name);
|
|
};
|
|
|
|
$scope.handleBuildFailed = function(message) {
|
|
var repo = $scope.repo;
|
|
|
|
bootbox.dialog({
|
|
"message": message,
|
|
"title": "Could not start Dockerfile build",
|
|
"buttons": {
|
|
"close": {
|
|
"label": "Close",
|
|
"className": "btn-primary",
|
|
"callback": function() {
|
|
$scope.$apply(function() {
|
|
$location.path('/repository/' + repo.namespace + '/' + repo.name);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
return true;
|
|
};
|
|
|
|
$scope.createNewRepo = function() {
|
|
$('#repoName').popover('hide');
|
|
|
|
$scope.creating = true;
|
|
var repo = $scope.repo;
|
|
var data = {
|
|
'namespace': repo.namespace,
|
|
'repository': repo.name,
|
|
'visibility': repo.is_public == '1' ? 'public' : 'private',
|
|
'description': repo.description
|
|
};
|
|
|
|
ApiService.createRepo(data).then(function(created) {
|
|
$scope.creating = false;
|
|
$scope.created = created;
|
|
|
|
// Start the upload process if applicable.
|
|
if ($scope.repo.initialize == 'dockerfile' || $scope.repo.initialize == 'zipfile') {
|
|
$scope.createdForBuild = created;
|
|
return;
|
|
}
|
|
|
|
// Conduct the Github redirect if applicable.
|
|
if ($scope.repo.initialize == 'github') {
|
|
window.location = 'https://github.com/login/oauth/authorize?client_id=' + $scope.githubClientId +
|
|
'&scope=repo,user:email&redirect_uri=' + $scope.githubRedirectUri + '/trigger/' +
|
|
repo.namespace + '/' + repo.name;
|
|
return;
|
|
}
|
|
|
|
// Otherwise, redirect to the repo page.
|
|
$location.path('/repository/' + created.namespace + '/' + created.name);
|
|
}, function(result) {
|
|
$scope.creating = false;
|
|
$scope.createError = result.data ? result.data.message : 'Cannot create repository';
|
|
$timeout(function() {
|
|
$('#repoName').popover('show');
|
|
});
|
|
});
|
|
};
|
|
|
|
$scope.upgradePlan = function() {
|
|
var callbacks = {
|
|
'started': function() { $scope.planChanging = true; },
|
|
'opened': function() { $scope.planChanging = true; },
|
|
'closed': function() { $scope.planChanging = false; },
|
|
'success': subscribedToPlan,
|
|
'failure': function(resp) {
|
|
$('#couldnotsubscribeModal').modal();
|
|
$scope.planChanging = false;
|
|
}
|
|
};
|
|
|
|
var namespace = $scope.isUserNamespace ? null : $scope.repo.namespace;
|
|
PlanService.changePlan($scope, namespace, $scope.planRequired.stripeId, callbacks);
|
|
};
|
|
|
|
var checkPrivateAllowed = function() {
|
|
if (!$scope.repo || !$scope.repo.namespace) { return; }
|
|
|
|
if (!Features.BILLING) {
|
|
$scope.checkingPlan = false;
|
|
$scope.planRequired = null;
|
|
return;
|
|
}
|
|
|
|
$scope.checkingPlan = true;
|
|
|
|
var isUserNamespace = $scope.isUserNamespace;
|
|
ApiService.getPrivateAllowed(isUserNamespace ? null : $scope.repo.namespace).then(function(resp) {
|
|
$scope.checkingPlan = false;
|
|
|
|
if (resp['privateAllowed']) {
|
|
$scope.planRequired = null;
|
|
return;
|
|
}
|
|
|
|
if (resp['privateCount'] == null) {
|
|
// Organization where we are not the admin.
|
|
$scope.planRequired = {};
|
|
return;
|
|
}
|
|
|
|
// Otherwise, lookup the matching plan.
|
|
PlanService.getMinimumPlan(resp['privateCount'] + 1, !isUserNamespace, function(minimum) {
|
|
$scope.planRequired = minimum;
|
|
});
|
|
});
|
|
};
|
|
|
|
var subscribedToPlan = function(sub) {
|
|
$scope.planChanging = false;
|
|
$scope.subscription = sub;
|
|
|
|
PlanService.getPlan(sub.plan, function(subscribedPlan) {
|
|
$scope.subscribedPlan = subscribedPlan;
|
|
$scope.planRequired = null;
|
|
checkPrivateAllowed();
|
|
});
|
|
};
|
|
}
|
|
|
|
function OrgViewCtrl($rootScope, $scope, ApiService, $routeParams) {
|
|
var orgname = $routeParams.orgname;
|
|
|
|
$scope.TEAM_PATTERN = TEAM_PATTERN;
|
|
$rootScope.title = 'Loading...';
|
|
|
|
$scope.teamRoles = [
|
|
{ 'id': 'member', 'title': 'Member', 'kind': 'default' },
|
|
{ 'id': 'creator', 'title': 'Creator', 'kind': 'success' },
|
|
{ 'id': 'admin', 'title': 'Admin', 'kind': 'primary' }
|
|
];
|
|
|
|
$scope.setRole = function(role, teamname) {
|
|
var previousRole = $scope.organization.teams[teamname].role;
|
|
$scope.organization.teams[teamname].role = role;
|
|
|
|
var params = {
|
|
'orgname': orgname,
|
|
'teamname': teamname
|
|
};
|
|
|
|
var data = $scope.organization.teams[teamname];
|
|
|
|
ApiService.updateOrganizationTeam(data, params).then(function(resp) {
|
|
}, function(resp) {
|
|
$scope.organization.teams[teamname].role = previousRole;
|
|
$scope.roleError = resp.data || '';
|
|
$('#cannotChangeTeamModal').modal({});
|
|
});
|
|
};
|
|
|
|
$scope.createTeam = function(teamname) {
|
|
if (!teamname) {
|
|
return;
|
|
}
|
|
|
|
if ($scope.organization.teams[teamname]) {
|
|
$('#team-' + teamname).removeClass('highlight');
|
|
setTimeout(function() {
|
|
$('#team-' + teamname).addClass('highlight');
|
|
}, 10);
|
|
return;
|
|
}
|
|
|
|
createOrganizationTeam(ApiService, orgname, teamname, function(created) {
|
|
$scope.organization.teams[teamname] = created;
|
|
});
|
|
};
|
|
|
|
$scope.askDeleteTeam = function(teamname) {
|
|
$scope.currentDeleteTeam = teamname;
|
|
$('#confirmdeleteModal').modal({});
|
|
};
|
|
|
|
$scope.deleteTeam = function() {
|
|
$('#confirmdeleteModal').modal('hide');
|
|
if (!$scope.currentDeleteTeam) { return; }
|
|
|
|
var teamname = $scope.currentDeleteTeam;
|
|
var params = {
|
|
'orgname': orgname,
|
|
'teamname': teamname
|
|
};
|
|
|
|
var errorHandler = ApiService.errorDisplay('Cannot delete team', function() {
|
|
$scope.currentDeleteTeam = null;
|
|
});
|
|
|
|
ApiService.deleteOrganizationTeam(null, params).then(function() {
|
|
delete $scope.organization.teams[teamname];
|
|
$scope.currentDeleteTeam = null;
|
|
}, errorHandler);
|
|
};
|
|
|
|
var loadOrganization = function() {
|
|
$scope.orgResource = ApiService.getOrganizationAsResource({'orgname': orgname}).get(function(org) {
|
|
$scope.organization = org;
|
|
$rootScope.title = orgname;
|
|
$rootScope.description = 'Viewing organization ' + orgname;
|
|
|
|
$('.info-icon').popover({
|
|
'trigger': 'hover',
|
|
'html': true
|
|
});
|
|
});
|
|
};
|
|
|
|
// Load the organization.
|
|
loadOrganization();
|
|
}
|
|
|
|
function OrgAdminCtrl($rootScope, $scope, $timeout, Restangular, $routeParams, UserService, PlanService, ApiService, Features, UIService) {
|
|
var orgname = $routeParams.orgname;
|
|
|
|
// Load the list of plans.
|
|
if (Features.BILLING) {
|
|
PlanService.getPlans(function(plans) {
|
|
$scope.plans = plans;
|
|
$scope.plan_map = {};
|
|
|
|
for (var i = 0; i < plans.length; ++i) {
|
|
$scope.plan_map[plans[i].stripeId] = plans[i];
|
|
}
|
|
});
|
|
}
|
|
|
|
$scope.orgname = orgname;
|
|
$scope.membersLoading = true;
|
|
$scope.membersFound = null;
|
|
$scope.invoiceLoading = true;
|
|
$scope.logsShown = 0;
|
|
$scope.invoicesShown = 0;
|
|
$scope.applicationsShown = 0;
|
|
$scope.changingOrganization = false;
|
|
|
|
$scope.loadLogs = function() {
|
|
$scope.logsShown++;
|
|
};
|
|
|
|
$scope.loadApplications = function() {
|
|
$scope.applicationsShown++;
|
|
};
|
|
|
|
$scope.loadInvoices = function() {
|
|
$scope.invoicesShown++;
|
|
};
|
|
|
|
$scope.planChanged = function(plan) {
|
|
$scope.hasPaidPlan = plan && plan.price > 0;
|
|
};
|
|
|
|
$scope.$watch('organizationEmail', function(e) {
|
|
UIService.hidePopover('#changeEmailForm');
|
|
});
|
|
|
|
$scope.changeEmail = function() {
|
|
UIService.hidePopover('#changeEmailForm');
|
|
|
|
$scope.changingOrganization = true;
|
|
var params = {
|
|
'orgname': orgname
|
|
};
|
|
|
|
var data = {
|
|
'email': $scope.organizationEmail
|
|
};
|
|
|
|
ApiService.changeOrganizationDetails(data, params).then(function(org) {
|
|
$scope.changingOrganization = false;
|
|
$scope.changeEmailForm.$setPristine();
|
|
$scope.organization = org;
|
|
}, function(result) {
|
|
$scope.changingOrganization = false;
|
|
UIService.showFormError('#changeEmailForm', result);
|
|
});
|
|
};
|
|
|
|
$scope.loadMembers = function() {
|
|
if ($scope.membersFound) { return; }
|
|
$scope.membersLoading = true;
|
|
|
|
var params = {
|
|
'orgname': orgname
|
|
};
|
|
|
|
ApiService.getOrganizationMembers(null, params).then(function(resp) {
|
|
var membersArray = [];
|
|
for (var key in resp.members) {
|
|
if (resp.members.hasOwnProperty(key)) {
|
|
membersArray.push(resp.members[key]);
|
|
}
|
|
}
|
|
|
|
$scope.membersFound = membersArray;
|
|
$scope.membersLoading = false;
|
|
});
|
|
};
|
|
|
|
var loadOrganization = function() {
|
|
$scope.orgResource = ApiService.getOrganizationAsResource({'orgname': orgname}).get(function(org) {
|
|
if (org && org.is_admin) {
|
|
$scope.organization = org;
|
|
$scope.organizationEmail = org.email;
|
|
$rootScope.title = orgname + ' (Admin)';
|
|
$rootScope.description = 'Administration page for organization ' + orgname;
|
|
}
|
|
});
|
|
};
|
|
|
|
// Load the organization.
|
|
loadOrganization();
|
|
}
|
|
|
|
function TeamViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams) {
|
|
var teamname = $routeParams.teamname;
|
|
var orgname = $routeParams.orgname;
|
|
|
|
$scope.orgname = orgname;
|
|
$scope.teamname = teamname;
|
|
|
|
$rootScope.title = 'Loading...';
|
|
|
|
$scope.addNewMember = function(member) {
|
|
if (!member || $scope.members[member.name]) { return; }
|
|
|
|
var params = {
|
|
'orgname': orgname,
|
|
'teamname': teamname,
|
|
'membername': member.name
|
|
};
|
|
|
|
ApiService.updateOrganizationTeamMember(null, params).then(function(resp) {
|
|
$scope.members[member.name] = resp;
|
|
}, function() {
|
|
$('#cannotChangeMembersModal').modal({});
|
|
});
|
|
};
|
|
|
|
$scope.removeMember = function(username) {
|
|
var params = {
|
|
'orgname': orgname,
|
|
'teamname': teamname,
|
|
'membername': username
|
|
};
|
|
|
|
ApiService.deleteOrganizationTeamMember(null, params).then(function(resp) {
|
|
delete $scope.members[username];
|
|
}, function() {
|
|
$('#cannotChangeMembersModal').modal({});
|
|
});
|
|
};
|
|
|
|
$scope.updateForDescription = function(content) {
|
|
$scope.organization.teams[teamname].description = content;
|
|
|
|
var params = {
|
|
'orgname': orgname,
|
|
'teamname': teamname
|
|
};
|
|
|
|
var teaminfo = $scope.organization.teams[teamname];
|
|
ApiService.updateOrganizationTeam(teaminfo, params).then(function(resp) {
|
|
}, function() {
|
|
$('#cannotChangeTeamModal').modal({});
|
|
});
|
|
};
|
|
|
|
var loadOrganization = function() {
|
|
$scope.orgResource = ApiService.getOrganizationAsResource({'orgname': orgname}).get(function(org) {
|
|
$scope.organization = org;
|
|
$scope.team = $scope.organization.teams[teamname];
|
|
$rootScope.title = teamname + ' (' + $scope.orgname + ')';
|
|
$rootScope.description = 'Team management page for team ' + teamname + ' under organization ' + $scope.orgname;
|
|
loadMembers();
|
|
return org;
|
|
});
|
|
};
|
|
|
|
var loadMembers = function() {
|
|
var params = {
|
|
'orgname': orgname,
|
|
'teamname': teamname
|
|
};
|
|
|
|
$scope.membersResource = ApiService.getOrganizationTeamMembersAsResource(params).get(function(resp) {
|
|
$scope.members = resp.members;
|
|
$scope.canEditMembers = resp.can_edit;
|
|
|
|
$('.info-icon').popover({
|
|
'trigger': 'hover',
|
|
'html': true
|
|
});
|
|
|
|
return resp.members;
|
|
});
|
|
};
|
|
|
|
// Load the organization.
|
|
loadOrganization();
|
|
}
|
|
|
|
function OrgsCtrl($scope, UserService) {
|
|
UserService.updateUserIn($scope);
|
|
browserchrome.update();
|
|
}
|
|
|
|
function NewOrgCtrl($scope, $routeParams, $timeout, $location, UserService, PlanService, ApiService, CookieService, Features) {
|
|
$scope.Features = Features;
|
|
$scope.holder = {};
|
|
|
|
UserService.updateUserIn($scope);
|
|
|
|
var requested = $routeParams['plan'];
|
|
|
|
if (Features.BILLING) {
|
|
// Load the list of plans.
|
|
PlanService.getPlans(function(plans) {
|
|
$scope.plans = plans;
|
|
$scope.holder.currentPlan = null;
|
|
if (requested) {
|
|
PlanService.getPlan(requested, function(plan) {
|
|
$scope.holder.currentPlan = plan;
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
$scope.signedIn = function() {
|
|
if (Features.BILLING) {
|
|
PlanService.handleNotedPlan();
|
|
}
|
|
};
|
|
|
|
$scope.signinStarted = function() {
|
|
if (Features.BILLING) {
|
|
PlanService.getMinimumPlan(1, true, function(plan) {
|
|
PlanService.notePlan(plan.stripeId);
|
|
});
|
|
}
|
|
};
|
|
|
|
$scope.setPlan = function(plan) {
|
|
$scope.holder.currentPlan = plan;
|
|
};
|
|
|
|
$scope.createNewOrg = function() {
|
|
$('#orgName').popover('hide');
|
|
|
|
$scope.creating = true;
|
|
var org = $scope.org;
|
|
var data = {
|
|
'name': org.name,
|
|
'email': org.email
|
|
};
|
|
|
|
ApiService.createOrganization(data).then(function(created) {
|
|
$scope.created = created;
|
|
|
|
// Reset the organizations list.
|
|
UserService.load();
|
|
|
|
// Set the default namesapce to the organization.
|
|
CookieService.putPermanent('quay.namespace', org.name);
|
|
|
|
var showOrg = function() {
|
|
$scope.creating = false;
|
|
$location.path('/organization/' + org.name + '/');
|
|
};
|
|
|
|
// If the selected plan is free, simply move to the org page.
|
|
if (!Features.BILLING || $scope.holder.currentPlan.price == 0) {
|
|
showOrg();
|
|
return;
|
|
}
|
|
|
|
// Otherwise, show the subscribe for the plan.
|
|
$scope.creating = true;
|
|
var callbacks = {
|
|
'opened': function() { $scope.creating = true; },
|
|
'closed': showOrg,
|
|
'success': showOrg,
|
|
'failure': showOrg
|
|
};
|
|
|
|
PlanService.changePlan($scope, org.name, $scope.holder.currentPlan.stripeId, callbacks);
|
|
}, function(resp) {
|
|
$scope.creating = false;
|
|
$scope.createError = ApiService.getErrorMessage(resp);
|
|
$timeout(function() {
|
|
$('#orgName').popover('show');
|
|
});
|
|
});
|
|
};
|
|
}
|
|
|
|
|
|
function OrgMemberLogsCtrl($scope, $routeParams, $rootScope, $timeout, Restangular, ApiService) {
|
|
var orgname = $routeParams.orgname;
|
|
var membername = $routeParams.membername;
|
|
|
|
$scope.orgname = orgname;
|
|
$scope.memberInfo = null;
|
|
$scope.ready = false;
|
|
|
|
var loadOrganization = function() {
|
|
$scope.orgResource = ApiService.getOrganizationAsResource({'orgname': orgname}).get(function(org) {
|
|
$scope.organization = org;
|
|
return org;
|
|
});
|
|
};
|
|
|
|
var loadMemberInfo = function() {
|
|
var params = {
|
|
'orgname': orgname,
|
|
'membername': membername
|
|
};
|
|
|
|
$scope.memberResource = ApiService.getOrganizationMemberAsResource(params).get(function(resp) {
|
|
$scope.memberInfo = resp.member;
|
|
|
|
$rootScope.title = 'Logs for ' + $scope.memberInfo.username + ' (' + $scope.orgname + ')';
|
|
$rootScope.description = 'Shows all the actions of ' + $scope.memberInfo.username +
|
|
' under organization ' + $scope.orgname;
|
|
|
|
$timeout(function() {
|
|
$scope.ready = true;
|
|
});
|
|
|
|
return resp.member;
|
|
});
|
|
};
|
|
|
|
// Load the org info and the member info.
|
|
loadOrganization();
|
|
loadMemberInfo();
|
|
}
|
|
|
|
|
|
function ManageApplicationCtrl($scope, $routeParams, $rootScope, $location, $timeout, ApiService) {
|
|
var orgname = $routeParams.orgname;
|
|
var clientId = $routeParams.clientid;
|
|
|
|
$scope.updating = false;
|
|
|
|
$scope.askResetClientSecret = function() {
|
|
$('#resetSecretModal').modal({});
|
|
};
|
|
|
|
$scope.askDelete = function() {
|
|
$('#deleteAppModal').modal({});
|
|
};
|
|
|
|
$scope.deleteApplication = function() {
|
|
var params = {
|
|
'orgname': orgname,
|
|
'client_id': clientId
|
|
};
|
|
|
|
$('#deleteAppModal').modal('hide');
|
|
|
|
ApiService.deleteOrganizationApplication(null, params).then(function(resp) {
|
|
$timeout(function() {
|
|
$location.path('/organization/' + orgname + '/admin');
|
|
}, 500);
|
|
}, ApiService.errorDisplay('Could not delete application'));
|
|
};
|
|
|
|
$scope.updateApplication = function() {
|
|
$scope.updating = true;
|
|
var params = {
|
|
'orgname': orgname,
|
|
'client_id': clientId
|
|
};
|
|
|
|
if (!$scope.application['description']) {
|
|
delete $scope.application['description'];
|
|
}
|
|
|
|
if (!$scope.application['gravatar_email']) {
|
|
delete $scope.application['gravatar_email'];
|
|
}
|
|
|
|
var errorHandler = ApiService.errorDisplay('Could not update application', function(resp) {
|
|
$scope.updating = false;
|
|
});
|
|
|
|
ApiService.updateOrganizationApplication($scope.application, params).then(function(resp) {
|
|
$scope.application = resp;
|
|
}, errorHandler);
|
|
};
|
|
|
|
$scope.resetClientSecret = function() {
|
|
var params = {
|
|
'orgname': orgname,
|
|
'client_id': clientId
|
|
};
|
|
|
|
$('#resetSecretModal').modal('hide');
|
|
|
|
ApiService.resetOrganizationApplicationClientSecret(null, params).then(function(resp) {
|
|
$scope.application = resp;
|
|
}, ApiService.errorDisplay('Could not reset client secret'));
|
|
};
|
|
|
|
var loadOrganization = function() {
|
|
$scope.orgResource = ApiService.getOrganizationAsResource({'orgname': orgname}).get(function(org) {
|
|
$scope.organization = org;
|
|
return org;
|
|
});
|
|
};
|
|
|
|
var loadApplicationInfo = function() {
|
|
var params = {
|
|
'orgname': orgname,
|
|
'client_id': clientId
|
|
};
|
|
|
|
$scope.appResource = ApiService.getOrganizationApplicationAsResource(params).get(function(resp) {
|
|
$scope.application = resp;
|
|
|
|
$rootScope.title = 'Manage Application ' + $scope.application.name + ' (' + $scope.orgname + ')';
|
|
$rootScope.description = 'Manage the details of application ' + $scope.application.name +
|
|
' under organization ' + $scope.orgname;
|
|
|
|
return resp;
|
|
});
|
|
};
|
|
|
|
|
|
// Load the organization and application info.
|
|
loadOrganization();
|
|
loadApplicationInfo();
|
|
}
|
|
|
|
|
|
function SuperUserAdminCtrl($scope, ApiService, Features, UserService) {
|
|
if (!Features.SUPER_USERS) {
|
|
return;
|
|
}
|
|
|
|
// Monitor any user changes and place the current user into the scope.
|
|
UserService.updateUserIn($scope);
|
|
|
|
$scope.loadUsers = function() {
|
|
if ($scope.users) {
|
|
return;
|
|
}
|
|
|
|
$scope.loadUsersInternal();
|
|
};
|
|
|
|
$scope.loadUsersInternal = function() {
|
|
ApiService.listAllUsers().then(function(resp) {
|
|
$scope.users = resp['users'];
|
|
}, function(resp) {
|
|
$scope.users = [];
|
|
$scope.usersError = resp['data']['message'] || resp['data']['error_description'];
|
|
});
|
|
};
|
|
|
|
$scope.showChangePassword = function(user) {
|
|
$scope.userToChange = user;
|
|
$('#changePasswordModal').modal({});
|
|
};
|
|
|
|
$scope.showDeleteUser = function(user) {
|
|
if (user.username == UserService.currentUser().username) {
|
|
bootbox.dialog({
|
|
"message": 'Cannot delete yourself!',
|
|
"title": "Cannot delete user",
|
|
"buttons": {
|
|
"close": {
|
|
"label": "Close",
|
|
"className": "btn-primary"
|
|
}
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
$scope.userToDelete = user;
|
|
$('#confirmDeleteUserModal').modal({});
|
|
};
|
|
|
|
$scope.changeUserPassword = function(user) {
|
|
$('#changePasswordModal').modal('hide');
|
|
|
|
var params = {
|
|
'username': user.username
|
|
};
|
|
|
|
var data = {
|
|
'password': user.password
|
|
};
|
|
|
|
ApiService.changeInstallUser(data, params).then(function(resp) {
|
|
$scope.loadUsersInternal();
|
|
}, ApiService.errorDisplay('Could not change user'));
|
|
};
|
|
|
|
$scope.deleteUser = function(user) {
|
|
$('#confirmDeleteUserModal').modal('hide');
|
|
|
|
var params = {
|
|
'username': user.username
|
|
};
|
|
|
|
ApiService.deleteInstallUser(null, params).then(function(resp) {
|
|
$scope.loadUsersInternal();
|
|
}, ApiService.errorDisplay('Cannot delete user'));
|
|
};
|
|
|
|
$scope.loadUsers();
|
|
}
|
|
|
|
function TourCtrl($scope, $location) {
|
|
$scope.kind = $location.path().substring('/tour/'.length);
|
|
}
|