Work in progress: add a loading bar and convert to using the new ApiService and resource-view

This commit is contained in:
Joseph Schorr 2013-12-17 13:19:59 -05:00
parent a53106be3b
commit 414bd34d52
15 changed files with 1116 additions and 642 deletions

View file

@ -89,7 +89,9 @@ function getMarkedDown(string) {
}
// Start the application code itself.
quayApp = angular.module('quay', ['ngRoute', 'restangular', 'angularMoment', 'angulartics', 'angulartics.mixpanel', '$strap.directives', 'ngCookies'], function($provide) {
quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'restangular', 'angularMoment', 'angulartics', 'angulartics.mixpanel', '$strap.directives', 'ngCookies'], function($provide, cfpLoadingBarProvider) {
cfpLoadingBarProvider.includeSpinner = false;
$provide.factory('UserService', ['Restangular', '$cookies', function(Restangular, $cookies) {
var userResponse = {
verified: false,
@ -106,6 +108,15 @@ quayApp = angular.module('quay', ['ngRoute', 'restangular', 'angularMoment', 'an
return $cookies.loggedIn == 'true';
};
userService.updateUserIn = function(scope, opt_callback) {
scope.$watch(function () { return userService.currentUser(); }, function (currentUser) {
scope.user = currentUser;
if (opt_callback) {
opt_callback(currentUser);
}
}, true);
};
userService.load = function(opt_callback) {
var userFetch = Restangular.one('user/');
userFetch.get().then(function(loadedUser) {
@ -166,6 +177,48 @@ quayApp = angular.module('quay', ['ngRoute', 'restangular', 'angularMoment', 'an
return userService;
}]);
$provide.factory('ApiService', ['Restangular', function(Restangular) {
var apiService = {}
apiService.at = function(locationPieces) {
var location = getRestUrl.apply(this, arguments);
var info = {
'url': location,
'caller': Restangular.one(location),
'withOptions': function(options) {
info.options = options;
return info;
},
'get': function(processor, opt_errorHandler) {
var options = info.options;
var caller = info.caller;
var result = {
'loading': true,
'value': null,
'hasError': false
};
caller.get(options).then(function(resp) {
result.value = processor(resp);
result.loading = false;
}, function(resp) {
result.hasError = true;
result.loading = false;
if (opt_errorHandler) {
opt_errorHandler(resp);
}
});
return result;
}
};
return info;
};
return apiService;
}]);
$provide.factory('KeyService', ['$location', function($location) {
var keyService = {}
@ -536,8 +589,8 @@ quayApp = angular.module('quay', ['ngRoute', 'restangular', 'angularMoment', 'an
fixFooter: true, reloadOnSearch: false}).
when('/repository/:namespace/:name/tag/:tag', {templateUrl: '/static/partials/view-repo.html', controller: RepoCtrl,
fixFooter: true}).
when('/repository/:namespace/:name/image/:image', {templateUrl: '/static/partials/image-view.html', controller: ImageViewCtrl}).
when('/repository/:namespace/:name/admin', {templateUrl: '/static/partials/repo-admin.html', controller:RepoAdminCtrl, reloadOnSearch: false}).
when('/repository/:namespace/:name/image/:image', {templateUrl: '/static/partials/image-view.html', controller: ImageViewCtrl, reloadOnSearch: false}).
when('/repository/:namespace/:name/admin', {templateUrl: '/static/partials/repo-admin.html', controller:RepoAdminCtrl, reloadOnSearch: false}).
when('/repository/', {title: 'Repositories', description: 'Public and private docker repositories list',
templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl}).
when('/user/', {title: 'Account Settings', description:'Account settings for Quay.io', templateUrl: '/static/partials/user-admin.html',
@ -1245,6 +1298,24 @@ quayApp.directive('popupInputButton', function () {
});
quayApp.directive('resourceView', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/resource-view.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'resource': '=resource',
'errorMessage': '=errorMessage'
},
controller: function($scope, $element) {
}
};
return directiveDefinitionObject;
});
quayApp.directive('organizationHeader', function () {
var directiveDefinitionObject = {
priority: 0,
@ -1508,14 +1579,14 @@ quayApp.directive('entitySearch', function () {
number++;
var input = $element[0].firstChild.firstChild;
$scope.namespace = $scope.namespace || '';
var namespace = $scope.namespace || '';
$(input).typeahead({
name: 'entities' + number,
remote: {
url: '/api/entities/%QUERY',
replace: function (url, uriEncodedQuery) {
url = url.replace('%QUERY', uriEncodedQuery);
url += '?namespace=' + encodeURIComponent($scope.namespace);
url += '?namespace=' + encodeURIComponent(namespace);
if ($scope.includeTeams) {
url += '&includeTeams=true'
}

View file

@ -15,7 +15,7 @@ $.fn.clipboardCopy = function() {
});
};
function SigninCtrl($scope, $location, $timeout, Restangular, KeyService, UserService) {
function SigninCtrl($scope) {
};
function PlansCtrl($scope, $location, UserService, PlanService) {
@ -24,17 +24,13 @@ function PlansCtrl($scope, $location, UserService, PlanService) {
$scope.plans = plans;
});
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
$scope.user = currentUser;
}, 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.cancelNotedPlan = function() {
};
$scope.buyNow = function(plan) {
if ($scope.user && !$scope.user.anonymous) {
@ -61,61 +57,50 @@ function GuideCtrl($scope) {
function SecurityCtrl($scope) {
}
function RepoListCtrl($scope, Restangular, UserService) {
function RepoListCtrl($scope, Restangular, UserService, ApiService) {
$scope.namespace = null;
// Monitor changes in the user.
UserService.updateUserIn($scope, function() {
loadMyRepos($scope.namespace);
});
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
$scope.user = currentUser;
}, true);
// Monitor changes in the namespace.
$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 = {
'public': false,
'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);
var options = {'public': false, 'sort': true, 'namespace': namespace};
$scope.user_repositories = ApiService.at('repository').withOptions(options).get(function(resp) {
return resp.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);
});
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, $timeout, $location, Restangular, UserService, KeyService, PlanService) {
function LandingCtrl($scope, UserService, ApiService) {
$scope.namespace = null;
$scope.$watch('namespace', function(namespace) {
loadMyRepos(namespace);
});
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
$scope.user = currentUser;
}, true);
UserService.updateUserIn($scope, function() {
loadMyRepos($scope.namespace);
});
$scope.canCreateRepo = function(namespace) {
if (!$scope.user) { return false; }
@ -141,40 +126,44 @@ function LandingCtrl($scope, $timeout, $location, Restangular, UserService, KeyS
return;
}
$scope.loadingmyrepos = true;
// Load the list of repositories.
var params = {
'limit': 4,
'public': false,
'sort': true,
'namespace': namespace
};
var repositoryFetch = Restangular.all('repository/');
repositoryFetch.getList(params).then(function(resp) {
$scope.myrepos = resp.repositories;
$scope.loadingmyrepos = false;
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, $routeParams, $rootScope, $location, $timeout) {
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();
@ -188,135 +177,9 @@ function RepoCtrl($scope, Restangular, $routeParams, $rootScope, $location, $tim
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;
var kind = repo.is_public ? 'public' : 'private';
$rootScope.description = jQuery(getFirstTextLine(repo.description)).text() ||
'View of a ' + kind + ' docker repository on Quay';
$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.currentImageChangeResource = ApiService.at('repository', namespace, name, 'image', image.id, 'changes').get(function(ci) {
$scope.currentImageChanges = ci;
});
};
@ -373,22 +236,134 @@ function RepoCtrl($scope, Restangular, $routeParams, $rootScope, $location, $tim
return count;
};
var namespace = $routeParams.namespace;
var name = $routeParams.name;
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.loading = true;
var fetchRepository = function() {
$rootScope.title = 'Loading Repository...';
$scope.repository = ApiService.at('repository', namespace, name).get(function(repo) {
// Set the repository object.
$scope.repo = repo;
// Fetch the repo.
fetchRepository();
// Set the default tag.
$scope.setTag($routeParams.tag);
// Fetch the image history.
listImages();
// 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, $routeParams, $rootScope) {
function RepoAdminCtrl($scope, Restangular, ApiService, $routeParams, $rootScope) {
$('.info-icon').popover({
'trigger': 'hover',
'html': true
'trigger': 'hover',
'html': true
});
var namespace = $routeParams.namespace;
@ -547,63 +522,19 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
});
};
$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;
$rootScope.description = 'Administrator settings for ' + namespace + '/' + name +
': Permissions, webhooks and other settings';
$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;
});
$scope.webhooksLoading = true;
$scope.loadWebhooks = function() {
$scope.webhooksLoading = true;
var fetchWebhooks = Restangular.one('repository/' + namespace + '/' + name + '/webhook/');
fetchWebhooks.get().then(function(resp) {
$scope.newWebhook = {};
$scope.webhooksResource = ApiService.at('repository', namespace, name, 'webhook').get(function(resp) {
$scope.webhooks = resp.webhooks;
$scope.webhooksLoading = false;
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);
@ -618,6 +549,44 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
$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) {
@ -774,6 +743,7 @@ function ImageViewCtrl($scope, $routeParams, $rootScope, Restangular) {
};
// Fetch the image.
$scope.loading = true;
var imageFetch = Restangular.one('repository/' + namespace + '/' + name + '/image/' + imageid);
imageFetch.get().then(function(image) {
$scope.loading = false;
@ -1259,7 +1229,7 @@ function TeamViewCtrl($rootScope, $scope, Restangular, $routeParams) {
function OrgsCtrl($scope, UserService) {
$scope.loading = true;
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
$scope.$watch(function () { return UserService.currentUser(); }, function (currentUser) {
$scope.user = currentUser;
$scope.loading = false;
}, true);