3f062ee602
- Change the subscribe method to allow for subscribing to the free plan, even when an org - Change the frontend to no longer have different plan groups - Change the frontend to display the proper plans (i.e. hide the deprecated plans unless it is the current plan, etc)
1334 lines
No EOL
38 KiB
JavaScript
1334 lines
No EOL
38 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 SigninCtrl($scope) {
|
|
};
|
|
|
|
function PlansCtrl($scope, $location, UserService, PlanService) {
|
|
// Load the list of plans.
|
|
PlanService.getPlans(function(plans) {
|
|
$scope.plans = plans;
|
|
}, /* include the personal plan */ true);
|
|
|
|
// 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) {
|
|
if ($scope.user && !$scope.user.anonymous) {
|
|
document.location = '/user?plan=' + plan;
|
|
} else {
|
|
PlanService.notePlan(plan);
|
|
$('#signinModal').modal({});
|
|
}
|
|
};
|
|
|
|
$scope.createOrg = function(plan) {
|
|
if ($scope.user && !$scope.user.anonymous) {
|
|
document.location = '/organizations/new/?plan=' + plan;
|
|
} else {
|
|
PlanService.notePlan(plan);
|
|
$('#signinModal').modal({});
|
|
}
|
|
};
|
|
}
|
|
|
|
function GuideCtrl($scope) {
|
|
}
|
|
|
|
function SecurityCtrl($scope) {
|
|
}
|
|
|
|
function RepoListCtrl($scope, Restangular, UserService, ApiService) {
|
|
$scope.namespace = 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);
|
|
});
|
|
|
|
var loadMyRepos = function(namespace) {
|
|
if (!$scope.user || $scope.user.anonymous || !namespace) {
|
|
return;
|
|
}
|
|
|
|
var options = {'public': false, 'sort': true, 'namespace': namespace};
|
|
$scope.user_repositories = ApiService.at('repository').withOptions(options).get(function(resp) {
|
|
return resp.repositories;
|
|
});
|
|
};
|
|
|
|
var loadPublicRepos = function() {
|
|
var options = {'public': true, 'private': false, 'sort': true, 'limit': 10};
|
|
$scope.public_repositories = ApiService.at('repository').withOptions(options).get(function(resp) {
|
|
return resp.repositories;
|
|
});
|
|
};
|
|
|
|
loadPublicRepos();
|
|
}
|
|
|
|
function LandingCtrl($scope, UserService, ApiService) {
|
|
$scope.namespace = null;
|
|
|
|
$scope.$watch('namespace', function(namespace) {
|
|
loadMyRepos(namespace);
|
|
});
|
|
|
|
UserService.updateUserIn($scope, function() {
|
|
loadMyRepos($scope.namespace);
|
|
});
|
|
|
|
$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.at('repository').withOptions(options).get(function(resp) {
|
|
return resp.repositories;
|
|
});
|
|
};
|
|
|
|
browserchrome.update();
|
|
}
|
|
|
|
function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $location, $timeout) {
|
|
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(){
|
|
$scope.setTag($location.search().tag, false);
|
|
});
|
|
|
|
// Start scope methods //////////////////////////////////////////
|
|
|
|
$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) {
|
|
$scope.currentImageChangeResource = ApiService.at('repository', namespace, name, 'image', image.id, 'changes').get(function(ci) {
|
|
$scope.currentImageChanges = ci;
|
|
});
|
|
};
|
|
|
|
$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 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 fetchRepository = function() {
|
|
$rootScope.title = 'Loading Repository...';
|
|
$scope.repository = ApiService.at('repository', namespace, name).get(function(repo) {
|
|
// Set the repository object.
|
|
$scope.repo = repo;
|
|
|
|
// Set the default tag.
|
|
$scope.setTag($routeParams.tag);
|
|
|
|
// Set the title of the page.
|
|
$rootScope.title = namespace + '/' + name;
|
|
var kind = repo.is_public ? 'public' : 'private';
|
|
$rootScope.description = jQuery(getFirstTextLine(repo.description)).text() ||
|
|
'View of a ' + kind + ' docker repository on Quay';
|
|
|
|
// If the repository is marked as building, start monitoring it for changes.
|
|
if (repo.is_building) {
|
|
startBuildInfoTimer(repo);
|
|
}
|
|
|
|
$('#copyClipboard').clipboardCopy();
|
|
});
|
|
};
|
|
|
|
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.withHttpConfig({
|
|
'ignoreLoadingBar': true
|
|
});
|
|
|
|
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.
|
|
loadViewInfo();
|
|
}
|
|
});
|
|
};
|
|
|
|
var listImages = function() {
|
|
$scope.imageHistory = ApiService.at('repository', namespace, name, 'image').get(function(resp) {
|
|
// 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);
|
|
}
|
|
|
|
// 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); });
|
|
});
|
|
|
|
return resp.images;
|
|
});
|
|
};
|
|
|
|
var loadViewInfo = function() {
|
|
fetchRepository();
|
|
listImages();
|
|
};
|
|
|
|
// Fetch the repository itself as well as the image history.
|
|
loadViewInfo();
|
|
}
|
|
|
|
function RepoAdminCtrl($scope, Restangular, ApiService, $routeParams, $rootScope) {
|
|
$('.info-icon').popover({
|
|
'trigger': 'hover',
|
|
'html': true
|
|
});
|
|
|
|
var namespace = $routeParams.namespace;
|
|
var name = $routeParams.name;
|
|
|
|
$scope.permissions = {'team': [], 'user': []};
|
|
$scope.logsShown = 0;
|
|
|
|
$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 ($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 permissionDelete = Restangular.one(getRestUrl('repository', namespace, name, 'permissions', kind, entityName));
|
|
permissionDelete.customDELETE().then(function() {
|
|
delete $scope.permissions[kind][entityName];
|
|
}, function(resp) {
|
|
if (resp.status == 409) {
|
|
$scope.changePermError = resp.data || '';
|
|
$('#channgechangepermModal').modal({});
|
|
} else {
|
|
$('#cannotchangeModal').modal({});
|
|
}
|
|
});
|
|
};
|
|
|
|
$scope.addRole = function(entityName, role, kind) {
|
|
var permission = {
|
|
'role': role,
|
|
};
|
|
|
|
var permissionPost = Restangular.one(getRestUrl('repository', namespace, name, 'permissions', kind, entityName));
|
|
permissionPost.customPOST(permission).then(function(result) {
|
|
$scope.permissions[kind][entityName] = result;
|
|
}, 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(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.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.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 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.loadWebhooks = function() {
|
|
$scope.newWebhook = {};
|
|
$scope.webhooksResource = ApiService.at('repository', namespace, name, 'webhook').get(function(resp) {
|
|
$scope.webhooks = resp.webhooks;
|
|
return $scope.webhooks;
|
|
});
|
|
};
|
|
|
|
$scope.createWebhook = function() {
|
|
if (!$scope.newWebhook.url) {
|
|
return;
|
|
}
|
|
|
|
var newWebhook = Restangular.one('repository/' + namespace + '/' + name + '/webhook/');
|
|
newWebhook.customPOST($scope.newWebhook).then(function(resp) {
|
|
$scope.webhooks.push(resp);
|
|
$scope.newWebhook.url = '';
|
|
$scope.createWebhookForm.$setPristine();
|
|
});
|
|
};
|
|
|
|
$scope.deleteWebhook = function(webhook) {
|
|
var deleteWebhookReq = Restangular.one('repository/' + namespace + '/' + name + '/webhook/' + webhook.public_id);
|
|
deleteWebhookReq.customDELETE().then(function(resp) {
|
|
$scope.webhooks.splice($scope.webhooks.indexOf(webhook), 1);
|
|
});
|
|
};
|
|
|
|
var fetchTokens = function() {
|
|
var tokensFetch = Restangular.one('repository/' + namespace + '/' + name + '/tokens/');
|
|
tokensFetch.get().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() {
|
|
$scope.repository = ApiService.at('repository', namespace, name).get(function(repo) {
|
|
$scope.repo = repo;
|
|
|
|
$rootScope.title = 'Settings - ' + namespace + '/' + name;
|
|
$rootScope.description = 'Administrator settings for ' + namespace + '/' + name +
|
|
': Permissions, webhooks and other settings';
|
|
|
|
// Fetch all the permissions and token info for the repository.
|
|
fetchPermissions('user');
|
|
fetchPermissions('team');
|
|
fetchTokens();
|
|
|
|
return $scope.repo;
|
|
});
|
|
};
|
|
|
|
// Fetch the repository.
|
|
fetchRepository();
|
|
}
|
|
|
|
function UserAdminCtrl($scope, $timeout, $location, Restangular, PlanService, UserService, KeyService, $routeParams) {
|
|
if ($routeParams['migrate']) {
|
|
$('#migrateTab').tab('show')
|
|
}
|
|
|
|
UserService.updateUserIn($scope, function(user) {
|
|
$scope.askForPassword = user.askForPassword;
|
|
});
|
|
|
|
$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.convertStep = 0;
|
|
$scope.org = {};
|
|
|
|
$('.form-change-pw').popover();
|
|
|
|
$scope.planChanged = function(plan) {
|
|
$scope.hasPaidPlan = plan && plan.price > 0;
|
|
};
|
|
|
|
$scope.showConvertForm = function() {
|
|
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() {
|
|
$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, $timeout, ApiService, Restangular) {
|
|
var namespace = $routeParams.namespace;
|
|
var name = $routeParams.name;
|
|
var imageid = $routeParams.image;
|
|
|
|
$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 fetchImage = function() {
|
|
$scope.image = ApiService.at('repository', namespace, name, 'image', imageid).get(function(image) {
|
|
$scope.repo = {
|
|
'name': name,
|
|
'namespace': namespace
|
|
};
|
|
|
|
$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();
|
|
|
|
$('#copyClipboard').clipboardCopy();
|
|
|
|
return image;
|
|
});
|
|
};
|
|
|
|
var fetchChanges = function() {
|
|
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;
|
|
});
|
|
};
|
|
|
|
// Fetch the image.
|
|
fetchImage();
|
|
}
|
|
|
|
function V1Ctrl($scope, $location, UserService) {
|
|
UserService.updateUserIn($scope);
|
|
}
|
|
|
|
function NewRepoCtrl($scope, $location, $http, $timeout, UserService, Restangular, PlanService) {
|
|
UserService.updateUserIn($scope);
|
|
|
|
$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);
|
|
});
|
|
});
|
|
|
|
// 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.checkingPlan = true;
|
|
$scope.planRequired = null;
|
|
$scope.isUserNamespace = isUserNamespace;
|
|
|
|
if (isUserNamespace) {
|
|
// Load the user's subscription information in case they want to create a private
|
|
// repository.
|
|
var checkPrivateAllowed = Restangular.one('user/private');
|
|
checkPrivateAllowed.get().then(function(resp) {
|
|
if (resp.privateCount + 1 > resp.reposAllowed) {
|
|
PlanService.getMinimumPlan(resp.privateCount + 1, false, function(minimum) {
|
|
$scope.planRequired = minimum;
|
|
});
|
|
}
|
|
|
|
$scope.checkingPlan = false;
|
|
}, function() {
|
|
$scope.planRequired = {};
|
|
$scope.checkingPlan = false;
|
|
});
|
|
} else {
|
|
var checkPrivateAllowed = Restangular.one('organization/' + namespace + '/private');
|
|
checkPrivateAllowed.get().then(function(resp) {
|
|
$scope.planRequired = resp.privateAllowed ? null : {};
|
|
$scope.checkingPlan = false;
|
|
}, function() {
|
|
$scope.planRequired = {};
|
|
$scope.checkingPlan = false;
|
|
});
|
|
|
|
// Auto-set to private repo.
|
|
$scope.repo.is_public = '0';
|
|
}
|
|
});
|
|
|
|
$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() {
|
|
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;
|
|
}
|
|
};
|
|
|
|
PlanService.changePlan($scope, null, $scope.planRequired.stripeId, callbacks);
|
|
};
|
|
|
|
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;
|
|
});
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
function OrgViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams) {
|
|
var orgname = $routeParams.orgname;
|
|
|
|
$('.info-icon').popover({
|
|
'trigger': 'hover',
|
|
'html': true
|
|
});
|
|
|
|
$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 updateTeam = Restangular.one(getRestUrl('organization', orgname, 'team', teamname));
|
|
var data = $scope.organization.teams[teamname];
|
|
|
|
updateTeam.customPUT(data).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(Restangular, 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 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;
|
|
});
|
|
};
|
|
|
|
var loadOrganization = function() {
|
|
$scope.orgResource = ApiService.at('organization', orgname).get(function(org) {
|
|
$scope.organization = org;
|
|
$rootScope.title = orgname;
|
|
$rootScope.description = 'Viewing organization ' + orgname;
|
|
});
|
|
};
|
|
|
|
// Load the organization.
|
|
loadOrganization();
|
|
}
|
|
|
|
function OrgAdminCtrl($rootScope, $scope, Restangular, $routeParams, UserService, PlanService, ApiService) {
|
|
var orgname = $routeParams.orgname;
|
|
|
|
// Load the list of plans.
|
|
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.loadLogs = function() {
|
|
$scope.logsShown++;
|
|
};
|
|
|
|
$scope.planChanged = function(plan) {
|
|
$scope.hasPaidPlan = plan && plan.price > 0;
|
|
};
|
|
|
|
$scope.loadInvoices = function() {
|
|
if ($scope.invoices) { return; }
|
|
$scope.invoiceLoading = true;
|
|
|
|
var getInvoices = Restangular.one(getRestUrl('organization', orgname, 'invoices'));
|
|
getInvoices.get().then(function(resp) {
|
|
$scope.invoiceExpanded = {};
|
|
$scope.invoices = resp.invoices;
|
|
$scope.invoiceLoading = false;
|
|
});
|
|
};
|
|
|
|
$scope.toggleInvoice = function(id) {
|
|
$scope.invoiceExpanded[id] = !$scope.invoiceExpanded[id];
|
|
};
|
|
|
|
$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() {
|
|
$scope.orgResource = ApiService.at('organization', orgname).get(function(org) {
|
|
if (org && org.is_admin) {
|
|
$scope.organization = org;
|
|
$rootScope.title = orgname + ' (Admin)';
|
|
$rootScope.description = 'Administration page for organization ' + orgname;
|
|
}
|
|
});
|
|
};
|
|
|
|
// Load the organization.
|
|
loadOrganization();
|
|
}
|
|
|
|
function TeamViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams) {
|
|
$('.info-icon').popover({
|
|
'trigger': 'hover',
|
|
'html': true
|
|
});
|
|
|
|
var teamname = $routeParams.teamname;
|
|
var orgname = $routeParams.orgname;
|
|
|
|
$scope.orgname = orgname;
|
|
$scope.teamname = teamname;
|
|
|
|
$rootScope.title = 'Loading...';
|
|
|
|
$scope.addNewMember = function(member) {
|
|
if ($scope.members[member.name]) { return; }
|
|
|
|
var addMember = Restangular.one(getRestUrl('organization', $scope.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', $scope.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', $scope.orgname, 'team', teamname));
|
|
var data = $scope.organization.teams[teamname];
|
|
updateTeam.customPUT(data).then(function(resp) {
|
|
}, function() {
|
|
$('#cannotChangeTeamModal').modal({});
|
|
});
|
|
};
|
|
|
|
var loadOrganization = function() {
|
|
$scope.orgResource = ApiService.at('organization', 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() {
|
|
$scope.membersResource = ApiService.at('organization', $scope.orgname, 'team', teamname, 'members').get(function(resp) {
|
|
$scope.members = resp.members;
|
|
$scope.canEditMembers = resp.can_edit;
|
|
return resp.members;
|
|
});
|
|
};
|
|
|
|
// Load the organization.
|
|
loadOrganization();
|
|
}
|
|
|
|
function OrgsCtrl($scope, UserService) {
|
|
UserService.updateUserIn($scope);
|
|
|
|
browserchrome.update();
|
|
}
|
|
|
|
function NewOrgCtrl($scope, $routeParams, $timeout, $location, UserService, PlanService, Restangular) {
|
|
UserService.updateUserIn($scope);
|
|
|
|
var requested = $routeParams['plan'];
|
|
|
|
// Load the list of plans.
|
|
PlanService.getPlans(function(plans) {
|
|
$scope.plans = plans;
|
|
$scope.currentPlan = null;
|
|
if (requested) {
|
|
PlanService.getPlan(requested, function(plan) {
|
|
$scope.currentPlan = plan;
|
|
});
|
|
}
|
|
});
|
|
|
|
$scope.signedIn = function() {
|
|
PlanService.handleNotedPlan();
|
|
};
|
|
|
|
$scope.signinStarted = function() {
|
|
PlanService.getMinimumPlan(1, true, function(plan) {
|
|
PlanService.notePlan(plan.stripeId);
|
|
});
|
|
};
|
|
|
|
$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.created = created;
|
|
|
|
// Reset the organizations list.
|
|
UserService.load();
|
|
|
|
var showOrg = function() {
|
|
$scope.creating = false;
|
|
$location.path('/organization/' + org.name + '/');
|
|
};
|
|
|
|
// If the selected plan is free, simply move to the org page.
|
|
if ($scope.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.currentPlan.stripeId, callbacks);
|
|
}, function(result) {
|
|
$scope.creating = false;
|
|
$scope.createError = result.data.message || result.data;
|
|
$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.at('organization', orgname).get(function(org) {
|
|
$scope.organization = org;
|
|
return org;
|
|
});
|
|
};
|
|
|
|
var loadMemberInfo = function() {
|
|
$scope.memberResource = ApiService.at('organization', $scope.orgname, 'members', membername).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();
|
|
} |