fe69ba5ec1
- Have conversion to organization update its plan to a business plan - Fix bug in the repo donut usage graph thingy where it had zero size when not in the default tab
1378 lines
No EOL
39 KiB
JavaScript
1378 lines
No EOL
39 KiB
JavaScript
$.fn.clipboardCopy = function() {
|
|
var clip = new ZeroClipboard($(this), { 'moviePath': 'static/lib/ZeroClipboard.swf' });
|
|
clip.on('complete', function() {
|
|
// Resets the animation.
|
|
var elem = $('#clipboardCopied')[0];
|
|
elem.style.display = 'none';
|
|
elem.classList.remove('animated');
|
|
|
|
// Show the notification.
|
|
setTimeout(function() {
|
|
elem.style.display = 'inline-block';
|
|
elem.classList.add('animated');
|
|
}, 10);
|
|
});
|
|
};
|
|
|
|
function HeaderCtrl($scope, $location, UserService, Restangular) {
|
|
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
|
$scope.user = currentUser;
|
|
}, true);
|
|
|
|
$scope.signout = function() {
|
|
var signoutPost = Restangular.one('signout');
|
|
signoutPost.customPOST().then(function() {
|
|
UserService.load();
|
|
$location.path('/');
|
|
});
|
|
};
|
|
|
|
$scope.appLinkTarget = function() {
|
|
if ($("div[ng-view]").length === 0) {
|
|
return "_self";
|
|
}
|
|
return "";
|
|
};
|
|
|
|
$scope.$on('$includeContentLoaded', function() {
|
|
// THIS IS BAD, MOVE THIS TO A DIRECTIVE
|
|
$('#repoSearch').typeahead({
|
|
name: 'repositories',
|
|
remote: {
|
|
url: '/api/find/repository?query=%QUERY',
|
|
filter: function(data) {
|
|
var datums = [];
|
|
for (var i = 0; i < data.repositories.length; ++i) {
|
|
var repo = data.repositories[i];
|
|
datums.push({
|
|
'value': repo.name,
|
|
'tokens': [repo.name, repo.namespace],
|
|
'repo': repo
|
|
});
|
|
}
|
|
return datums;
|
|
}
|
|
},
|
|
template: function (datum) {
|
|
template = '<div class="repo-mini-listing">';
|
|
template += '<i class="fa fa-hdd fa-lg"></i>'
|
|
template += '<span class="name">' + datum.repo.namespace +'/' + datum.repo.name + '</span>'
|
|
if (datum.repo.description) {
|
|
template += '<span class="description">' + getFirstTextLine(datum.repo.description) + '</span>'
|
|
}
|
|
|
|
template += '</div>'
|
|
return template;
|
|
},
|
|
|
|
});
|
|
|
|
$('#repoSearch').on('typeahead:selected', function (e, datum) {
|
|
$('#repoSearch').typeahead('setQuery', '');
|
|
document.location = '/repository/' + datum.repo.namespace + '/' + datum.repo.name
|
|
});
|
|
});
|
|
}
|
|
|
|
function SigninCtrl($scope, $location, $timeout, Restangular, KeyService, UserService) {
|
|
$scope.sendRecovery = function() {
|
|
var signinPost = Restangular.one('recovery');
|
|
signinPost.customPOST($scope.recovery).then(function() {
|
|
$scope.invalidEmail = false;
|
|
$scope.sent = true;
|
|
}, function(result) {
|
|
$scope.invalidEmail = true;
|
|
$scope.sent = false;
|
|
});
|
|
};
|
|
|
|
$scope.status = 'ready';
|
|
};
|
|
|
|
function PlansCtrl($scope, $location, UserService, PlanService) {
|
|
// Load the list of plans.
|
|
PlanService.getPlans(function(plans) {
|
|
$scope.plans = plans;
|
|
$scope.status = 'ready';
|
|
});
|
|
|
|
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
|
$scope.user = currentUser;
|
|
}, true);
|
|
|
|
$scope.buyNow = function(plan) {
|
|
if ($scope.user && !$scope.user.anonymous) {
|
|
document.location = '/user?plan=' + plan;
|
|
} else {
|
|
$('#signinModal').modal({});
|
|
}
|
|
};
|
|
|
|
$scope.createOrg = function(plan) {
|
|
if ($scope.user && !$scope.user.anonymous) {
|
|
document.location = '/organizations/new/?plan=' + plan;
|
|
} else {
|
|
$('#signinModal').modal({});
|
|
}
|
|
};
|
|
}
|
|
|
|
function GuideCtrl($scope) {
|
|
$scope.status = 'ready';
|
|
}
|
|
|
|
function RepoListCtrl($scope, Restangular, UserService) {
|
|
$scope.namespace = null;
|
|
|
|
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
|
$scope.user = currentUser;
|
|
}, true);
|
|
|
|
$scope.$watch('namespace', function(namespace) {
|
|
loadMyRepos(namespace);
|
|
});
|
|
|
|
$scope.loading = true;
|
|
$scope.public_repositories = null;
|
|
$scope.user_repositories = [];
|
|
|
|
var loadMyRepos = function(namespace) {
|
|
if (!$scope.user || $scope.user.anonymous || !namespace) {
|
|
return;
|
|
}
|
|
|
|
$scope.loadingmyrepos = true;
|
|
|
|
// Load the list of repositories.
|
|
var params = {
|
|
'limit': 10,
|
|
'public': true,
|
|
'sort': true,
|
|
'namespace': namespace
|
|
};
|
|
|
|
var repositoryFetch = Restangular.all('repository/');
|
|
repositoryFetch.getList(params).then(function(resp) {
|
|
$scope.user_repositories = resp.repositories;
|
|
$scope.loading = !($scope.public_repositories && $scope.user_repositories);
|
|
});
|
|
};
|
|
|
|
// Load the list of public repositories.
|
|
var options = {'public': true, 'private': false, 'sort': true, 'limit': 10};
|
|
var repositoryPublicFetch = Restangular.all('repository/');
|
|
repositoryPublicFetch.getList(options).then(function(resp) {
|
|
$scope.public_repositories = resp.repositories;
|
|
$scope.loading = !($scope.public_repositories && $scope.user_repositories);
|
|
});
|
|
}
|
|
|
|
function LandingCtrl($scope, $timeout, $location, Restangular, UserService, KeyService) {
|
|
$('.form-signup').popover();
|
|
|
|
$scope.namespace = null;
|
|
|
|
$scope.$watch('namespace', function(namespace) {
|
|
loadMyRepos(namespace);
|
|
});
|
|
|
|
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
|
$scope.user = currentUser;
|
|
}, true);
|
|
|
|
angulartics.waitForVendorApi(mixpanel, 500, function(loadedMixpanel) {
|
|
var mixpanelId = loadedMixpanel.get_distinct_id();
|
|
$scope.github_state_clause = '&state=' + mixpanelId;
|
|
});
|
|
|
|
$scope.githubClientId = KeyService.githubClientId;
|
|
|
|
$scope.awaitingConfirmation = false;
|
|
$scope.registering = false;
|
|
|
|
$scope.register = function() {
|
|
$('.form-signup').popover('hide');
|
|
$scope.registering = true;
|
|
|
|
var newUserPost = Restangular.one('user/');
|
|
newUserPost.customPOST($scope.newUser).then(function() {
|
|
$scope.awaitingConfirmation = true;
|
|
$scope.registering = false;
|
|
|
|
mixpanel.alias($scope.newUser.username);
|
|
}, function(result) {
|
|
$scope.registering = false;
|
|
$scope.registerError = result.data.message;
|
|
$timeout(function() {
|
|
$('.form-signup').popover('show');
|
|
});
|
|
});
|
|
};
|
|
|
|
$scope.canCreateRepo = function(namespace) {
|
|
if (!$scope.user) { return false; }
|
|
|
|
if (namespace == $scope.user.username) {
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
$scope.loadingmyrepos = true;
|
|
|
|
// Load the list of repositories.
|
|
var params = {
|
|
'limit': 4,
|
|
'public': true,
|
|
'sort': true,
|
|
'namespace': namespace
|
|
};
|
|
|
|
var repositoryFetch = Restangular.all('repository/');
|
|
repositoryFetch.getList(params).then(function(resp) {
|
|
$scope.myrepos = resp.repositories;
|
|
$scope.loadingmyrepos = false;
|
|
});
|
|
};
|
|
|
|
$scope.status = 'ready';
|
|
|
|
browserchrome.update();
|
|
}
|
|
|
|
function RepoCtrl($scope, Restangular, $routeParams, $rootScope, $location, $timeout) {
|
|
$rootScope.title = 'Loading...';
|
|
|
|
// Watch for changes to the tag parameter.
|
|
$scope.$on('$routeUpdate', function(){
|
|
$scope.setTag($location.search().tag, false);
|
|
});
|
|
|
|
$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();
|
|
};
|
|
|
|
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];
|
|
}
|
|
}
|
|
};
|
|
|
|
$scope.$watch('repo', function() {
|
|
if ($scope.tree) {
|
|
$timeout(function() {
|
|
$scope.tree.notifyResized();
|
|
});
|
|
}
|
|
});
|
|
|
|
var fetchRepository = function() {
|
|
var repositoryFetch = Restangular.one('repository/' + namespace + '/' + name);
|
|
repositoryFetch.get().then(function(repo) {
|
|
$rootScope.title = namespace + '/' + name;
|
|
$scope.repo = repo;
|
|
|
|
$scope.setTag($routeParams.tag);
|
|
|
|
$('#copyClipboard').clipboardCopy();
|
|
$scope.loading = false;
|
|
|
|
if (repo.is_building) {
|
|
startBuildInfoTimer(repo);
|
|
}
|
|
}, function() {
|
|
$scope.repo = null;
|
|
$scope.loading = false;
|
|
$rootScope.title = 'Unknown Repository';
|
|
});
|
|
};
|
|
|
|
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 buildInfo = Restangular.one('repository/' + repo.namespace + '/' + repo.name + '/build/');
|
|
buildInfo.get().then(function(resp) {
|
|
var runningBuilds = [];
|
|
for (var i = 0; i < resp.builds.length; ++i) {
|
|
var build = resp.builds[i];
|
|
if (build.status != 'complete') {
|
|
runningBuilds.push(build);
|
|
}
|
|
}
|
|
|
|
$scope.buildsInfo = runningBuilds;
|
|
if (!runningBuilds.length) {
|
|
// Cancel the build timer.
|
|
cancelBuildInfoTimer();
|
|
|
|
// Mark the repo as no longer building.
|
|
$scope.repo.is_building = false;
|
|
|
|
// Reload the repo information.
|
|
fetchRepository();
|
|
listImages();
|
|
}
|
|
});
|
|
};
|
|
|
|
var listImages = function() {
|
|
var imageFetch = Restangular.one('repository/' + namespace + '/' + name + '/image/');
|
|
imageFetch.get().then(function(resp) {
|
|
$scope.imageHistory = resp.images;
|
|
|
|
// Dispose of any existing tree.
|
|
if ($scope.tree) {
|
|
$scope.tree.dispose();
|
|
}
|
|
|
|
// Create the new tree.
|
|
$scope.tree = new ImageHistoryTree(namespace, name, resp.images,
|
|
getFirstTextLine, $scope.getTimeSince);
|
|
|
|
$scope.tree.draw('image-history-container');
|
|
|
|
// If we already have a tag, use it
|
|
if ($scope.currentTag) {
|
|
$scope.tree.setTag($scope.currentTag.name);
|
|
}
|
|
|
|
$($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); });
|
|
});
|
|
});
|
|
};
|
|
|
|
$scope.loadImageChanges = function(image) {
|
|
$scope.currentImageChanges = null;
|
|
|
|
var changesFetch = Restangular.one('repository/' + namespace + '/' + name + '/image/' + image.id + '/changes');
|
|
changesFetch.get().then(function(changeInfo) {
|
|
$scope.currentImageChanges = changeInfo;
|
|
}, function() {
|
|
$scope.currentImageChanges = {'added': [], 'removed': [], 'changed': []};
|
|
});
|
|
};
|
|
|
|
$scope.getMoreCount = function(changes) {
|
|
if (!changes) { return 0; }
|
|
var addedDisplayed = Math.min(5, changes.added.length);
|
|
var removedDisplayed = Math.min(5, changes.removed.length);
|
|
var changedDisplayed = Math.min(5, changes.changed.length);
|
|
|
|
return (changes.added.length + changes.removed.length + changes.changed.length) -
|
|
addedDisplayed - removedDisplayed - changedDisplayed;
|
|
};
|
|
|
|
$scope.setImage = function(image) {
|
|
$scope.currentImage = image;
|
|
$scope.loadImageChanges(image);
|
|
if ($scope.tree) {
|
|
$scope.tree.setImage($scope.currentImage.id);
|
|
}
|
|
};
|
|
|
|
$scope.setTag = function(tagName, opt_updateURL) {
|
|
var repo = $scope.repo;
|
|
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 = $scope.currentTag.image;
|
|
$scope.loadImageChanges($scope.currentImage);
|
|
if ($scope.tree) {
|
|
$scope.tree.setTag($scope.currentTag.name);
|
|
}
|
|
|
|
if (opt_updateURL) {
|
|
$location.search('tag', $scope.currentTag.name);
|
|
}
|
|
}
|
|
};
|
|
|
|
$scope.getTagCount = function(repo) {
|
|
if (!repo) { return 0; }
|
|
var count = 0;
|
|
for (var tag in repo.tags) {
|
|
++count;
|
|
}
|
|
return count;
|
|
};
|
|
|
|
var namespace = $routeParams.namespace;
|
|
var name = $routeParams.name;
|
|
|
|
$scope.loading = true;
|
|
|
|
// Fetch the repo.
|
|
fetchRepository();
|
|
|
|
// Fetch the image history.
|
|
listImages();
|
|
}
|
|
|
|
function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
|
|
$('.info-icon').popover({
|
|
'trigger': 'hover',
|
|
'html': true
|
|
});
|
|
|
|
$('#copyClipboard').clipboardCopy();
|
|
|
|
var namespace = $routeParams.namespace;
|
|
var name = $routeParams.name;
|
|
|
|
$scope.permissions = {'team': [], 'user': []};
|
|
|
|
$scope.isDownloadSupported = function() {
|
|
try { return !!new Blob(); } catch(e){}
|
|
return false;
|
|
};
|
|
|
|
$scope.downloadCfg = function(token) {
|
|
var auth = $.base64.encode("$token:" + token.code);
|
|
config = {
|
|
"https://quay.io/v1/": {
|
|
"auth": auth,
|
|
"email": ""
|
|
}
|
|
};
|
|
|
|
var file = JSON.stringify(config, null, ' ');
|
|
var blob = new Blob([file]);
|
|
saveAs(blob, '.dockercfg');
|
|
};
|
|
|
|
$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 ($scope.permissions[entity.kind][entity.name]) { return; }
|
|
|
|
if (!entity.is_org_member) {
|
|
$scope.currentAddEntity = entity;
|
|
$('#confirmaddoutsideModal').modal('show');
|
|
return;
|
|
}
|
|
|
|
// Need the $scope.apply for both the permission stuff to change and for
|
|
// the XHR call to be made.
|
|
$scope.$apply(function() {
|
|
$scope.addRole(entity.name, 'read', entity.kind, entity.is_org_member)
|
|
});
|
|
};
|
|
|
|
$scope.deleteRole = function(entityName, kind) {
|
|
var permissionDelete = Restangular.one(getRestUrl('repository', namespace, name, 'permissions', kind, entityName));
|
|
permissionDelete.customDELETE().then(function() {
|
|
delete $scope.permissions[kind][entityName];
|
|
}, function(result) {
|
|
if (result.status == 409) {
|
|
$('#onlyadminModal').modal({});
|
|
} else {
|
|
$('#cannotchangeModal').modal({});
|
|
}
|
|
});
|
|
};
|
|
|
|
$scope.addRole = function(entityName, role, kind, is_org_member) {
|
|
var permission = {
|
|
'role': role,
|
|
'is_org_member': !!is_org_member
|
|
};
|
|
|
|
var permissionPost = Restangular.one(getRestUrl('repository', namespace, name, 'permissions', kind, entityName));
|
|
permissionPost.customPOST(permission).then(function() {
|
|
$scope.permissions[kind][entityName] = permission;
|
|
}, function(result) {
|
|
$('#cannotchangeModal').modal({});
|
|
});
|
|
};
|
|
|
|
$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(result) {
|
|
if (result.status == 409) {
|
|
$scope.permissions[kind][entityName] = currentRole;
|
|
$('#onlyadminModal').modal({});
|
|
} else {
|
|
$('#cannotchangeModal').modal({});
|
|
}
|
|
});
|
|
};
|
|
|
|
$scope.createToken = function() {
|
|
var friendlyName = {
|
|
'friendlyName': $scope.newToken.friendlyName
|
|
};
|
|
|
|
var permissionPost = Restangular.one('repository/' + namespace + '/' + name + '/tokens/');
|
|
permissionPost.customPOST(friendlyName).then(function(newToken) {
|
|
$scope.newToken.friendlyName = '';
|
|
$scope.createTokenForm.$setPristine();
|
|
$scope.tokens[newToken.code] = newToken;
|
|
});
|
|
};
|
|
|
|
$scope.deleteToken = function(tokenCode) {
|
|
var deleteAction = Restangular.one('repository/' + namespace + '/' + name + '/tokens/' + tokenCode);
|
|
deleteAction.customDELETE().then(function() {
|
|
delete $scope.tokens[tokenCode];
|
|
});
|
|
};
|
|
|
|
$scope.changeTokenAccess = function(tokenCode, newAccess) {
|
|
var role = {
|
|
'role': newAccess
|
|
};
|
|
|
|
var deleteAction = Restangular.one('repository/' + namespace + '/' + name + '/tokens/' + tokenCode);
|
|
deleteAction.customPUT(role).then(function(updated) {
|
|
$scope.tokens[updated.code] = updated;
|
|
});
|
|
};
|
|
|
|
$scope.showToken = function(tokenCode) {
|
|
$scope.shownToken = $scope.tokens[tokenCode];
|
|
$('#tokenmodal').modal({});
|
|
};
|
|
|
|
$scope.askChangeAccess = function(newAccess) {
|
|
$('#make' + newAccess + 'Modal').modal({});
|
|
};
|
|
|
|
$scope.changeAccess = function(newAccess) {
|
|
$('#make' + newAccess + 'Modal').modal('hide');
|
|
|
|
var visibility = {
|
|
'visibility': newAccess
|
|
};
|
|
var visibilityPost = Restangular.one('repository/' + namespace + '/' + name + '/changevisibility');
|
|
visibilityPost.customPOST(visibility).then(function() {
|
|
$scope.repo.is_public = newAccess == 'public';
|
|
}, function() {
|
|
$('#cannotchangeModal').modal({});
|
|
});
|
|
};
|
|
|
|
$scope.askDelete = function() {
|
|
$('#confirmdeleteModal').modal({});
|
|
};
|
|
|
|
$scope.deleteRepo = function() {
|
|
$('#confirmdeleteModal').modal('hide');
|
|
|
|
var deleteAction = Restangular.one('repository/' + namespace + '/' + name);
|
|
deleteAction.customDELETE().then(function() {
|
|
$scope.repo = null;
|
|
|
|
setTimeout(function() {
|
|
document.location = '/repository/';
|
|
}, 1000);
|
|
}, function() {
|
|
$('#cannotchangeModal').modal({});
|
|
});
|
|
};
|
|
|
|
$scope.loading = true;
|
|
|
|
var checkLoading = function() {
|
|
$scope.loading = !($scope.permissions['user'] && $scope.permissions['team'] && $scope.repo && $scope.tokens);
|
|
};
|
|
|
|
var fetchPermissions = function(kind) {
|
|
var permissionsFetch = Restangular.one('repository/' + namespace + '/' + name + '/permissions/' + kind + '/');
|
|
permissionsFetch.get().then(function(resp) {
|
|
$rootScope.title = 'Settings - ' + namespace + '/' + name;
|
|
$scope.permissions[kind] = resp.permissions;
|
|
checkLoading();
|
|
}, function() {
|
|
$scope.permissions[kind] = null;
|
|
$rootScope.title = 'Unknown Repository';
|
|
$scope.loading = false;
|
|
});
|
|
};
|
|
|
|
// Fetch the repository information.
|
|
var repositoryFetch = Restangular.one('repository/' + namespace + '/' + name);
|
|
repositoryFetch.get().then(function(repo) {
|
|
$scope.repo = repo;
|
|
$scope.loading = !($scope.permissions && $scope.repo && $scope.tokens);
|
|
}, function() {
|
|
$scope.permissions = null;
|
|
$rootScope.title = 'Unknown Repository';
|
|
$scope.loading = false;
|
|
});
|
|
|
|
// Fetch the user and team permissions.
|
|
fetchPermissions('user');
|
|
fetchPermissions('team');
|
|
|
|
// Fetch the tokens.
|
|
var tokensFetch = Restangular.one('repository/' + namespace + '/' + name + '/tokens/');
|
|
tokensFetch.get().then(function(resp) {
|
|
$scope.tokens = resp.tokens;
|
|
checkLoading();
|
|
}, function() {
|
|
$scope.tokens = null;
|
|
$scope.loading = false;
|
|
});
|
|
|
|
}
|
|
|
|
function UserAdminCtrl($scope, $timeout, $location, Restangular, PlanService, UserService, KeyService, $routeParams) {
|
|
$scope.$watch(function () { return UserService.currentUser(); }, function (currentUser) {
|
|
$scope.askForPassword = currentUser.askForPassword;
|
|
if (!currentUser.anonymous) {
|
|
$scope.user = currentUser;
|
|
}
|
|
$scope.loading = false;
|
|
}, true);
|
|
|
|
$scope.readyForPlan = function() {
|
|
// Show the subscribe dialog if a plan was requested.
|
|
return $routeParams['plan'];
|
|
};
|
|
|
|
if ($routeParams['migrate']) {
|
|
$('#migrateTab').tab('show')
|
|
}
|
|
|
|
$scope.loading = true;
|
|
$scope.updatingUser = false;
|
|
$scope.changePasswordSuccess = false;
|
|
$scope.convertStep = 0;
|
|
$scope.org = {};
|
|
|
|
$('.form-change-pw').popover();
|
|
|
|
$scope.showConvertForm = function() {
|
|
PlanService.getMatchingBusinessPlan(function(plan) {
|
|
$scope.org.plan = plan;
|
|
});
|
|
|
|
PlanService.getPlans(function(plans) {
|
|
$scope.orgPlans = plans.business;
|
|
});
|
|
|
|
$scope.convertStep = 1;
|
|
};
|
|
|
|
$scope.convertToOrg = function() {
|
|
$('#reallyconvertModal').modal({});
|
|
};
|
|
|
|
$scope.reallyConvert = function() {
|
|
$scope.loading = true;
|
|
|
|
var data = {
|
|
'adminUser': $scope.org.adminUser,
|
|
'adminPassword': $scope.org.adminPassword,
|
|
'plan': $scope.org.plan.stripeId
|
|
};
|
|
|
|
var convertAccount = Restangular.one('user/convert');
|
|
convertAccount.customPOST(data).then(function(resp) {
|
|
UserService.load();
|
|
$location.path('/');
|
|
}, function(resp) {
|
|
$scope.loading = false;
|
|
if (resp.data.reason == 'invaliduser') {
|
|
$('#invalidadminModal').modal({});
|
|
} else {
|
|
$('#cannotconvertModal').modal({});
|
|
}
|
|
});
|
|
};
|
|
|
|
$scope.changePassword = function() {
|
|
$('.form-change-pw').popover('hide');
|
|
$scope.updatingUser = true;
|
|
$scope.changePasswordSuccess = false;
|
|
var changePasswordPost = Restangular.one('user/');
|
|
changePasswordPost.customPUT($scope.user).then(function() {
|
|
$scope.updatingUser = false;
|
|
$scope.changePasswordSuccess = true;
|
|
|
|
// Reset the form
|
|
$scope.user.password = '';
|
|
$scope.user.repeatPassword = '';
|
|
$scope.changePasswordForm.$setPristine();
|
|
|
|
// Reload the user.
|
|
UserService.load();
|
|
}, function(result) {
|
|
$scope.updatingUser = false;
|
|
|
|
$scope.changePasswordError = result.data.message;
|
|
$timeout(function() {
|
|
$('.form-change-pw').popover('show');
|
|
});
|
|
});
|
|
};
|
|
}
|
|
|
|
function ImageViewCtrl($scope, $routeParams, $rootScope, Restangular) {
|
|
var namespace = $routeParams.namespace;
|
|
var name = $routeParams.name;
|
|
var imageid = $routeParams.image;
|
|
|
|
$('#copyClipboard').clipboardCopy();
|
|
|
|
$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);
|
|
setTimeout(function() {
|
|
$scope.tree.draw('changes-tree-container');
|
|
}, 10);
|
|
};
|
|
|
|
// Fetch the image.
|
|
var imageFetch = Restangular.one('repository/' + namespace + '/' + name + '/image/' + imageid);
|
|
imageFetch.get().then(function(image) {
|
|
$scope.loading = false;
|
|
$scope.repo = {
|
|
'name': name,
|
|
'namespace': namespace
|
|
};
|
|
$scope.image = image;
|
|
$rootScope.title = 'View Image - ' + image.id;
|
|
}, function() {
|
|
$rootScope.title = 'Unknown Image';
|
|
$scope.loading = false;
|
|
});
|
|
|
|
// Fetch the image changes.
|
|
var changesFetch = Restangular.one('repository/' + namespace + '/' + name + '/image/' + imageid + '/changes');
|
|
changesFetch.get().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;
|
|
});
|
|
}
|
|
|
|
function V1Ctrl($scope, $location, UserService) {
|
|
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
|
$scope.user = currentUser;
|
|
}, true);
|
|
}
|
|
|
|
function NewRepoCtrl($scope, $location, $http, $timeout, UserService, Restangular, PlanService) {
|
|
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
|
$scope.user = currentUser;
|
|
}, true);
|
|
|
|
$scope.repo = {
|
|
'is_public': 1,
|
|
'description': '',
|
|
'initialize': false
|
|
};
|
|
|
|
$('#couldnotbuildModal').on('hidden.bs.modal', function() {
|
|
$scope.$apply(function() {
|
|
$location.path('/repository/' + $scope.created.namespace + '/' + $scope.created.name);
|
|
});
|
|
});
|
|
|
|
var startBuild = function(repo, fileId) {
|
|
$scope.building = true;
|
|
|
|
var data = {
|
|
'file_id': fileId
|
|
};
|
|
|
|
var startBuildCall = Restangular.one('repository/' + repo.namespace + '/' + repo.name + '/build/');
|
|
startBuildCall.customPOST(data).then(function(resp) {
|
|
$location.path('/repository/' + repo.namespace + '/' + repo.name);
|
|
}, function() {
|
|
$('#couldnotbuildModal').modal();
|
|
});
|
|
};
|
|
|
|
var conductUpload = function(repo, file, url, fileId, mimeType) {
|
|
var request = new XMLHttpRequest();
|
|
request.open('PUT', url, true);
|
|
request.setRequestHeader('Content-Type', mimeType);
|
|
request.onprogress = function(e) {
|
|
$scope.$apply(function() {
|
|
var percentLoaded;
|
|
if (e.lengthComputable) {
|
|
$scope.upload_progress = (e.loaded / e.total) * 100;
|
|
}
|
|
});
|
|
};
|
|
request.onerror = function() {
|
|
$scope.$apply(function() {
|
|
$('#couldnotbuildModal').modal();
|
|
});
|
|
};
|
|
request.onreadystatechange = function() {
|
|
var state = request.readyState;
|
|
if (state == 4) {
|
|
$scope.$apply(function() {
|
|
$scope.uploading = false;
|
|
startBuild(repo, fileId);
|
|
});
|
|
return;
|
|
}
|
|
};
|
|
request.send(file);
|
|
};
|
|
|
|
var startFileUpload = function(repo) {
|
|
$scope.uploading = true;
|
|
$scope.uploading_progress = 0;
|
|
|
|
var uploader = $('#file-drop')[0];
|
|
var file = uploader.files[0];
|
|
$scope.upload_file = file.name;
|
|
|
|
var mimeType = file.type || 'application/octet-stream';
|
|
var data = {
|
|
'mimeType': mimeType
|
|
};
|
|
|
|
var getUploadUrl = Restangular.one('filedrop/');
|
|
getUploadUrl.customPOST(data).then(function(resp) {
|
|
conductUpload(repo, file, resp.url, resp.file_id, mimeType);
|
|
}, function() {
|
|
$('#couldnotbuildModal').modal();
|
|
});
|
|
};
|
|
|
|
var subscribedToPlan = function(sub) {
|
|
$scope.planChanging = false;
|
|
$scope.subscription = sub;
|
|
|
|
PlanService.getPlan(sub.plan, function(subscribedPlan) {
|
|
$scope.subscribedPlan = subscribedPlan;
|
|
$scope.planRequired = null;
|
|
|
|
// Check to see if the current plan allows for an additional private repository to
|
|
// be created.
|
|
var privateAllowed = $scope.subscription.usedPrivateRepos < $scope.subscribedPlan.privateRepos;
|
|
if (!privateAllowed) {
|
|
// If not, find the minimum repository that does.
|
|
PlanService.getMinimumPlan($scope.subscription.usedPrivateRepos + 1, !$scope.isUserNamespace, function(minimum) {
|
|
$scope.planRequired = minimum;
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
$scope.createNewRepo = function() {
|
|
$('#repoName').popover('hide');
|
|
|
|
var uploader = $('#file-drop')[0];
|
|
if ($scope.repo.initialize && uploader.files.length < 1) {
|
|
$('#missingfileModal').modal();
|
|
return;
|
|
}
|
|
|
|
$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
|
|
};
|
|
|
|
var createPost = Restangular.one('repository');
|
|
createPost.customPOST(data).then(function(created) {
|
|
$scope.creating = false;
|
|
$scope.created = created;
|
|
|
|
// Repository created. Start the upload process if applicable.
|
|
if ($scope.repo.initialize) {
|
|
startFileUpload(created);
|
|
return;
|
|
}
|
|
|
|
// Otherwise, redirect to the repo page.
|
|
$location.path('/repository/' + created.namespace + '/' + created.name);
|
|
}, function(result) {
|
|
$scope.creating = false;
|
|
$scope.createError = result.data;
|
|
$timeout(function() {
|
|
$('#repoName').popover('show');
|
|
});
|
|
});
|
|
};
|
|
|
|
$scope.upgradePlan = function() {
|
|
PlanService.changePlan($scope, null, $scope.planRequired.stripeId, null, function() {
|
|
// Subscribing.
|
|
$scope.planChanging = true;
|
|
}, function(plan) {
|
|
// Subscribed.
|
|
UserService.resetCurrentSubscription();
|
|
subscribedToPlan(plan);
|
|
}, function() {
|
|
// Failure.
|
|
$('#couldnotsubscribeModal').modal();
|
|
$scope.planChanging = false;
|
|
});
|
|
};
|
|
|
|
// 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;
|
|
|
|
if (isUserNamespace) {
|
|
// Load the user's subscription information in case they want to create a private
|
|
// repository.
|
|
UserService.getCurrentSubscription(subscribedToPlan, function() {
|
|
PlanService.getMinimumPlan(1, false, function(minimum) { $scope.planRequired = minimum; });
|
|
});
|
|
} else {
|
|
$scope.planRequired = null;
|
|
|
|
var checkPrivateAllowed = Restangular.one('organization/' + namespace + '/private');
|
|
checkPrivateAllowed.get().then(function(resp) {
|
|
$scope.planRequired = resp.privateAllowed ? null : {};
|
|
}, function() {
|
|
$scope.planRequired = {};
|
|
});
|
|
|
|
// Auto-set to private repo.
|
|
$scope.repo.is_public = '0';
|
|
}
|
|
});
|
|
}
|
|
|
|
function OrgViewCtrl($rootScope, $scope, Restangular, $routeParams) {
|
|
$('.info-icon').popover({
|
|
'trigger': 'hover',
|
|
'html': true
|
|
});
|
|
|
|
$rootScope.title = 'Loading...';
|
|
|
|
var orgname = $routeParams.orgname;
|
|
|
|
var loadOrganization = function() {
|
|
var getOrganization = Restangular.one(getRestUrl('organization', orgname));
|
|
getOrganization.get().then(function(resp) {
|
|
$scope.organization = resp;
|
|
$scope.loading = false;
|
|
|
|
$rootScope.title = orgname;
|
|
}, function() {
|
|
$scope.loading = false;
|
|
});
|
|
};
|
|
|
|
$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) {
|
|
$scope.organization.teams[teamname].role = role;
|
|
|
|
var updateTeam = Restangular.one(getRestUrl('organization', orgname, 'team', teamname));
|
|
var data = $scope.organization.teams[teamname];
|
|
updateTeam.customPUT(data).then(function(resp) {
|
|
}, function() {
|
|
$('#cannotChangeTeamModal').modal({});
|
|
});
|
|
};
|
|
|
|
$scope.createTeamShown = function() {
|
|
setTimeout(function() {
|
|
$('#create-team-box').focus();
|
|
}, 10);
|
|
};
|
|
|
|
$scope.createTeam = function() {
|
|
var box = $('#create-team-box');
|
|
if (box.hasClass('ng-invalid')) { return; }
|
|
|
|
var teamname = box[0].value.toLowerCase();
|
|
if (!teamname) {
|
|
return;
|
|
}
|
|
|
|
if ($scope.organization.teams[teamname]) {
|
|
$('#team-' + teamname).removeClass('highlight');
|
|
setTimeout(function() {
|
|
$('#team-' + teamname).addClass('highlight');
|
|
}, 10);
|
|
return;
|
|
}
|
|
|
|
var createTeam = Restangular.one(getRestUrl('organization', orgname, 'team', teamname));
|
|
var data = {
|
|
'name': teamname,
|
|
'role': 'member'
|
|
};
|
|
createTeam.customPOST(data).then(function(resp) {
|
|
$scope.organization.teams[teamname] = resp;
|
|
}, function() {
|
|
$('#cannotChangeTeamModal').modal({});
|
|
});
|
|
};
|
|
|
|
$scope.askDeleteTeam = function(teamname) {
|
|
$scope.currentDeleteTeam = teamname;
|
|
$('#confirmdeleteModal').modal({});
|
|
};
|
|
|
|
$scope.deleteTeam = function() {
|
|
$('#confirmdeleteModal').modal('hide');
|
|
if (!$scope.currentDeleteTeam) { return; }
|
|
|
|
var teamname = $scope.currentDeleteTeam;
|
|
var deleteAction = Restangular.one(getRestUrl('organization', orgname, 'team', teamname));
|
|
deleteAction.customDELETE().then(function() {
|
|
delete $scope.organization.teams[teamname];
|
|
$scope.currentDeleteTeam = null;
|
|
}, function() {
|
|
$('#cannotchangeModal').modal({});
|
|
$scope.currentDeleteTeam = null;
|
|
});
|
|
};
|
|
|
|
loadOrganization();
|
|
}
|
|
|
|
function OrgAdminCtrl($rootScope, $scope, Restangular, $routeParams, UserService, PlanService) {
|
|
// Load the list of plans.
|
|
PlanService.getPlans(function(plans) {
|
|
$scope.plans = plans.business;
|
|
});
|
|
|
|
var orgname = $routeParams.orgname;
|
|
|
|
$scope.orgname = orgname;
|
|
$scope.membersLoading = true;
|
|
$scope.membersFound = null;
|
|
|
|
$scope.loadMembers = function() {
|
|
if ($scope.membersFound) { return; }
|
|
$scope.membersLoading = true;
|
|
|
|
var getMembers = Restangular.one(getRestUrl('organization', orgname, 'members'));
|
|
getMembers.get().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() {
|
|
var getOrganization = Restangular.one(getRestUrl('organization', orgname));
|
|
getOrganization.get().then(function(resp) {
|
|
if (resp && resp.is_admin) {
|
|
$scope.organization = resp;
|
|
$rootScope.title = orgname + ' (Admin)';
|
|
}
|
|
|
|
$scope.loading = false;
|
|
}, function() {
|
|
$scope.loading = false;
|
|
});
|
|
};
|
|
|
|
loadOrganization();
|
|
}
|
|
|
|
function TeamViewCtrl($rootScope, $scope, Restangular, $routeParams) {
|
|
$('.info-icon').popover({
|
|
'trigger': 'hover',
|
|
'html': true
|
|
});
|
|
|
|
var orgname = $routeParams.orgname;
|
|
var teamname = $routeParams.teamname;
|
|
|
|
$rootScope.title = 'Loading...';
|
|
$scope.loading = true;
|
|
$scope.teamname = teamname;
|
|
|
|
$scope.addNewMember = function(member) {
|
|
if ($scope.members[member.name]) { return; }
|
|
|
|
$scope.$apply(function() {
|
|
var addMember = Restangular.one(getRestUrl('organization', orgname, 'team', teamname, 'members', member.name));
|
|
addMember.customPOST().then(function(resp) {
|
|
$scope.members[member.name] = resp;
|
|
}, function() {
|
|
$('#cannotChangeMembersModal').modal({});
|
|
});
|
|
});
|
|
};
|
|
|
|
$scope.removeMember = function(username) {
|
|
var removeMember = Restangular.one(getRestUrl('organization', orgname, 'team', teamname, 'members', username));
|
|
removeMember.customDELETE().then(function(resp) {
|
|
delete $scope.members[username];
|
|
}, function() {
|
|
$('#cannotChangeMembersModal').modal({});
|
|
});
|
|
};
|
|
|
|
$scope.updateForDescription = function(content) {
|
|
$scope.organization.teams[teamname].description = content;
|
|
|
|
var updateTeam = Restangular.one(getRestUrl('organization', orgname, 'team', teamname));
|
|
var data = $scope.organization.teams[teamname];
|
|
updateTeam.customPUT(data).then(function(resp) {
|
|
}, function() {
|
|
$('#cannotChangeTeamModal').modal({});
|
|
});
|
|
};
|
|
|
|
var loadOrganization = function() {
|
|
var getOrganization = Restangular.one(getRestUrl('organization', orgname))
|
|
getOrganization.get().then(function(resp) {
|
|
$scope.organization = resp;
|
|
$scope.team = $scope.organization.teams[teamname];
|
|
$scope.loading = !$scope.organization || !$scope.members;
|
|
}, function() {
|
|
$scope.organization = null;
|
|
$scope.members = null;
|
|
$scope.loading = false;
|
|
});
|
|
};
|
|
|
|
var loadMembers = function() {
|
|
var getMembers = Restangular.one(getRestUrl('organization', orgname, 'team', teamname, 'members'));
|
|
getMembers.get().then(function(resp) {
|
|
$scope.members = resp.members;
|
|
$scope.canEditMembers = resp.can_edit;
|
|
$scope.loading = !$scope.organization || !$scope.members;
|
|
$rootScope.title = teamname + ' (' + orgname + ')';
|
|
}, function() {
|
|
$scope.organization = null;
|
|
$scope.members = null;
|
|
$scope.loading = false;
|
|
});
|
|
};
|
|
|
|
loadOrganization();
|
|
loadMembers();
|
|
}
|
|
|
|
function OrgsCtrl($scope, UserService) {
|
|
$scope.loading = true;
|
|
|
|
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
|
$scope.user = currentUser;
|
|
$scope.loading = false;
|
|
}, true);
|
|
|
|
browserchrome.update();
|
|
}
|
|
|
|
function NewOrgCtrl($scope, $routeParams, $timeout, $location, UserService, PlanService, Restangular) {
|
|
$scope.loading = true;
|
|
|
|
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
|
$scope.user = currentUser;
|
|
$scope.loading = false;
|
|
}, true);
|
|
|
|
requested = $routeParams['plan'];
|
|
|
|
// Load the list of plans.
|
|
PlanService.getPlans(function(plans) {
|
|
$scope.plans = plans.business;
|
|
$scope.currentPlan = null;
|
|
if (requested) {
|
|
PlanService.getPlan(requested, function(plan) {
|
|
$scope.currentPlan = plan;
|
|
});
|
|
}
|
|
});
|
|
|
|
$scope.setPlan = function(plan) {
|
|
$scope.currentPlan = plan;
|
|
};
|
|
|
|
$scope.createNewOrg = function() {
|
|
$('#orgName').popover('hide');
|
|
|
|
$scope.creating = true;
|
|
var org = $scope.org;
|
|
var data = {
|
|
'name': org.name,
|
|
'email': org.email
|
|
};
|
|
|
|
var createPost = Restangular.one('organization/');
|
|
createPost.customPOST(data).then(function(created) {
|
|
$scope.creating = false;
|
|
$scope.created = created;
|
|
|
|
// Reset the organizations list.
|
|
UserService.load();
|
|
|
|
// If the selected plan is free, simply move to the org page.
|
|
if ($scope.currentPlan.price == 0) {
|
|
$location.path('/organization/' + org.name + '/');
|
|
return;
|
|
}
|
|
|
|
// Otherwise, show the subscribe for the plan.
|
|
PlanService.changePlan($scope, org.name, $scope.currentPlan.stripeId, false, function() {
|
|
// Started.
|
|
$scope.creating = true;
|
|
}, function(sub) {
|
|
// Success.
|
|
$location.path('/organization/' + org.name + '/');
|
|
}, function() {
|
|
// Failure.
|
|
$location.path('/organization/' + org.name + '/');
|
|
});
|
|
|
|
}, function(result) {
|
|
$scope.creating = false;
|
|
$scope.createError = result.data.message || result.data;
|
|
$timeout(function() {
|
|
$('#orgName').popover('show');
|
|
});
|
|
});
|
|
};
|
|
} |