Work in progress: add a loading bar and convert to using the new ApiService and resource-view
This commit is contained in:
parent
a53106be3b
commit
414bd34d52
15 changed files with 1116 additions and 642 deletions
|
@ -3,6 +3,60 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.resource-view-element {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.resource-view-element .resource-spinner {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
opacity: 0;
|
||||
transition: opacity 0s ease-in-out;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.resource-view-element .resource-content {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.resource-view-element .resource-content.visible {
|
||||
z-index: 2;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.resource-view-element .resource-error {
|
||||
margin: 10px;
|
||||
font-size: 16px;
|
||||
color: #444;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.resource-view-element .resource-spinner.visible {
|
||||
opacity: 1;
|
||||
transition: opacity 1s ease-in-out;
|
||||
}
|
||||
|
||||
.small-spinner {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: solid 2px transparent;
|
||||
border-top-color: #444;
|
||||
border-left-color: #444;
|
||||
border-radius: 10px;
|
||||
-webkit-animation: loading-bar-spinner 400ms linear infinite;
|
||||
-moz-animation: loading-bar-spinner 400ms linear infinite;
|
||||
-ms-animation: loading-bar-spinner 400ms linear infinite;
|
||||
-o-animation: loading-bar-spinner 400ms linear infinite;
|
||||
animation: loading-bar-spinner 400ms linear infinite;
|
||||
}
|
||||
|
||||
#loading-bar-spinner {
|
||||
top: 70px;
|
||||
}
|
||||
|
||||
.entity-search-element input {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
|
8
static/directives/loading-status.html
Normal file
8
static/directives/loading-status.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<div class="container loading-status-element">
|
||||
<div ng-show="hasError && !loading">
|
||||
<span ng-transclude></span>
|
||||
</div>
|
||||
<div ng-show="loading">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
|
@ -1,2 +1,2 @@
|
|||
<i class="fa fa-lock fa-lg" style="{{ repo.is_public ? 'visibility: hidden' : 'visibility: visible' }}" title="Private Repository"></i>
|
||||
<i class="fa fa-lock fa-lg" style="{{ repo.is_public ? 'visibility: hidden' : 'visibility: inherit' }}" title="Private Repository"></i>
|
||||
<i class="fa fa-hdd"></i>
|
||||
|
|
11
static/directives/resource-view.html
Normal file
11
static/directives/resource-view.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<div class="resource-view-element">
|
||||
<div class="resource-spinner" ng-class="resource.loading ? 'visible' : ''">
|
||||
<div class="small-spinner"></div>
|
||||
</div>
|
||||
<div class="resource-error" ng-show="!resource.loading && resource.hasError">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
<div class="resource-content" ng-class="(!resource.loading && !resource.hasError) ? 'visible' : ''">
|
||||
<span ng-transclude></span>
|
||||
</div>
|
||||
</div>
|
|
@ -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,7 +589,7 @@ 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/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}).
|
||||
|
@ -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'
|
||||
}
|
||||
|
|
|
@ -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,18 +24,14 @@ 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) {
|
||||
document.location = '/user?plan=' + plan;
|
||||
|
@ -61,61 +57,50 @@ function GuideCtrl($scope) {
|
|||
function SecurityCtrl($scope) {
|
||||
}
|
||||
|
||||
function RepoListCtrl($scope, Restangular, UserService) {
|
||||
function RepoListCtrl($scope, Restangular, UserService, ApiService) {
|
||||
$scope.namespace = null;
|
||||
|
||||
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
||||
$scope.user = currentUser;
|
||||
}, true);
|
||||
// Monitor changes in the user.
|
||||
UserService.updateUserIn($scope, function() {
|
||||
loadMyRepos($scope.namespace);
|
||||
});
|
||||
|
||||
// Monitor changes in the namespace.
|
||||
$scope.$watch('namespace', function(namespace) {
|
||||
loadMyRepos(namespace);
|
||||
});
|
||||
|
||||
$scope.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 loadPublicRepos = function() {
|
||||
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);
|
||||
$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,19 +236,131 @@ 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.
|
||||
// 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();
|
||||
|
||||
// Fetch the image history.
|
||||
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
|
||||
|
@ -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);
|
||||
|
|
102
static/lib/loading-bar.css
Executable file
102
static/lib/loading-bar.css
Executable file
|
@ -0,0 +1,102 @@
|
|||
|
||||
/* Make clicks pass-through */
|
||||
#loading-bar,
|
||||
#loading-bar-spinner {
|
||||
pointer-events: none;
|
||||
-webkit-pointer-events: none;
|
||||
-webkit-transition: 0.5s linear all;
|
||||
-moz-transition: 0.5s linear all;
|
||||
-o-transition: 0.5s linear all;
|
||||
transition: 0.5s linear all;
|
||||
}
|
||||
|
||||
#loading-bar.ng-enter,
|
||||
#loading-bar.ng-leave.ng-leave-active,
|
||||
#loading-bar-spinner.ng-enter,
|
||||
#loading-bar-spinner.ng-leave.ng-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#loading-bar.ng-enter.ng-enter-active,
|
||||
#loading-bar.ng-leave,
|
||||
#loading-bar-spinner.ng-enter.ng-enter-active,
|
||||
#loading-bar-spinner.ng-leave {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#loading-bar .bar {
|
||||
-webkit-transition: width 350ms;
|
||||
-moz-transition: width 350ms;
|
||||
-o-transition: width 350ms;
|
||||
transition: width 350ms;
|
||||
|
||||
background: #29d;
|
||||
position: fixed;
|
||||
z-index: 2000;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
/* Fancy blur effect */
|
||||
#loading-bar .peg {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
box-shadow: 0 0 10px #29d, 0 0 5px #29d;
|
||||
opacity: 1.0;
|
||||
|
||||
-webkit-transform: rotate(3deg) translate(0px, -4px);
|
||||
-moz-transform: rotate(3deg) translate(0px, -4px);
|
||||
-ms-transform: rotate(3deg) translate(0px, -4px);
|
||||
-o-transform: rotate(3deg) translate(0px, -4px);
|
||||
transform: rotate(3deg) translate(0px, -4px);
|
||||
}
|
||||
|
||||
#loading-bar-spinner {
|
||||
display: block;
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
#loading-bar-spinner .spinner-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
|
||||
border: solid 2px transparent;
|
||||
border-top-color: #29d;
|
||||
border-left-color: #29d;
|
||||
border-radius: 10px;
|
||||
|
||||
-webkit-animation: loading-bar-spinner 400ms linear infinite;
|
||||
-moz-animation: loading-bar-spinner 400ms linear infinite;
|
||||
-ms-animation: loading-bar-spinner 400ms linear infinite;
|
||||
-o-animation: loading-bar-spinner 400ms linear infinite;
|
||||
animation: loading-bar-spinner 400ms linear infinite;
|
||||
}
|
||||
|
||||
@-webkit-keyframes loading-bar-spinner {
|
||||
0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); }
|
||||
100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); }
|
||||
}
|
||||
@-moz-keyframes loading-bar-spinner {
|
||||
0% { -moz-transform: rotate(0deg); transform: rotate(0deg); }
|
||||
100% { -moz-transform: rotate(360deg); transform: rotate(360deg); }
|
||||
}
|
||||
@-o-keyframes loading-bar-spinner {
|
||||
0% { -o-transform: rotate(0deg); transform: rotate(0deg); }
|
||||
100% { -o-transform: rotate(360deg); transform: rotate(360deg); }
|
||||
}
|
||||
@-ms-keyframes loading-bar-spinner {
|
||||
0% { -ms-transform: rotate(0deg); transform: rotate(0deg); }
|
||||
100% { -ms-transform: rotate(360deg); transform: rotate(360deg); }
|
||||
}
|
||||
@keyframes loading-bar-spinner {
|
||||
0% { transform: rotate(0deg); transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); transform: rotate(360deg); }
|
||||
}
|
271
static/lib/loading-bar.js
Executable file
271
static/lib/loading-bar.js
Executable file
|
@ -0,0 +1,271 @@
|
|||
/*
|
||||
* angular-loading-bar
|
||||
*
|
||||
* intercepts XHR requests and creates a loading bar.
|
||||
* Based on the excellent nprogress work by rstacruz (more info in readme)
|
||||
*
|
||||
* (c) 2013 Wes Cruver
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
|
||||
(function() {
|
||||
|
||||
'use strict';
|
||||
|
||||
// Alias the loading bar so it can be included using a simpler
|
||||
// (and maybe more professional) module name:
|
||||
angular.module('angular-loading-bar', ['chieffancypants.loadingBar']);
|
||||
|
||||
|
||||
/**
|
||||
* loadingBarInterceptor service
|
||||
*
|
||||
* Registers itself as an Angular interceptor and listens for XHR requests.
|
||||
*/
|
||||
angular.module('chieffancypants.loadingBar', [])
|
||||
.config(['$httpProvider', function ($httpProvider) {
|
||||
|
||||
var interceptor = ['$q', '$cacheFactory', 'cfpLoadingBar', function ($q, $cacheFactory, cfpLoadingBar) {
|
||||
|
||||
/**
|
||||
* The total number of requests made
|
||||
*/
|
||||
var reqsTotal = 0;
|
||||
|
||||
/**
|
||||
* The number of requests completed (either successfully or not)
|
||||
*/
|
||||
var reqsCompleted = 0;
|
||||
|
||||
|
||||
/**
|
||||
* calls cfpLoadingBar.complete() which removes the
|
||||
* loading bar from the DOM.
|
||||
*/
|
||||
function setComplete() {
|
||||
cfpLoadingBar.complete();
|
||||
reqsCompleted = 0;
|
||||
reqsTotal = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the response has already been cached
|
||||
* @param {Object} config the config option from the request
|
||||
* @return {Boolean} retrns true if cached, otherwise false
|
||||
*/
|
||||
function isCached(config) {
|
||||
var cache;
|
||||
var defaults = $httpProvider.defaults;
|
||||
|
||||
if (config.method !== 'GET' || config.cache === false) {
|
||||
config.cached = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config.cache === true && defaults.cache === undefined) {
|
||||
cache = $cacheFactory.get('$http');
|
||||
} else if (defaults.cache !== undefined) {
|
||||
cache = defaults.cache;
|
||||
} else {
|
||||
cache = config.cache;
|
||||
}
|
||||
|
||||
var cached = cache !== undefined ?
|
||||
cache.get(config.url) !== undefined : false;
|
||||
|
||||
if (config.cached !== undefined && cached !== config.cached) {
|
||||
return config.cached;
|
||||
}
|
||||
config.cached = cached;
|
||||
return cached;
|
||||
}
|
||||
|
||||
return {
|
||||
'request': function(config) {
|
||||
// Check to make sure this request hasn't already been cached and that
|
||||
// the requester didn't explicitly ask us to ignore this request:
|
||||
if (!config.ignoreLoadingBar && !isCached(config)) {
|
||||
if (reqsTotal === 0) {
|
||||
cfpLoadingBar.start();
|
||||
}
|
||||
reqsTotal++;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
|
||||
'response': function(response) {
|
||||
if (!isCached(response.config)) {
|
||||
reqsCompleted++;
|
||||
if (reqsCompleted >= reqsTotal) {
|
||||
setComplete();
|
||||
} else {
|
||||
cfpLoadingBar.set(reqsCompleted / reqsTotal);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
},
|
||||
|
||||
'responseError': function(rejection) {
|
||||
if (!isCached(rejection.config)) {
|
||||
reqsCompleted++;
|
||||
if (reqsCompleted >= reqsTotal) {
|
||||
setComplete();
|
||||
} else {
|
||||
cfpLoadingBar.set(reqsCompleted / reqsTotal);
|
||||
}
|
||||
}
|
||||
return $q.reject(rejection);
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
$httpProvider.interceptors.push(interceptor);
|
||||
}])
|
||||
|
||||
|
||||
/**
|
||||
* Loading Bar
|
||||
*
|
||||
* This service handles adding and removing the actual element in the DOM.
|
||||
* Generally, best practices for DOM manipulation is to take place in a
|
||||
* directive, but because the element itself is injected in the DOM only upon
|
||||
* XHR requests, and it's likely needed on every view, the best option is to
|
||||
* use a service.
|
||||
*/
|
||||
.provider('cfpLoadingBar', function() {
|
||||
|
||||
this.includeSpinner = true;
|
||||
this.includeBar = true;
|
||||
this.parentSelector = 'body';
|
||||
|
||||
this.$get = ['$document', '$timeout', '$animate', '$rootScope', function ($document, $timeout, $animate, $rootScope) {
|
||||
|
||||
var $parentSelector = this.parentSelector,
|
||||
$parent = $document.find($parentSelector),
|
||||
loadingBarContainer = angular.element('<div id="loading-bar"><div class="bar"><div class="peg"></div></div></div>'),
|
||||
loadingBar = loadingBarContainer.find('div').eq(0),
|
||||
spinner = angular.element('<div id="loading-bar-spinner"><div class="spinner-icon"></div></div>');
|
||||
|
||||
var incTimeout,
|
||||
completeTimeout,
|
||||
started = false,
|
||||
status = 0;
|
||||
|
||||
var includeSpinner = this.includeSpinner;
|
||||
var includeBar = this.includeBar;
|
||||
|
||||
/**
|
||||
* Inserts the loading bar element into the dom, and sets it to 2%
|
||||
*/
|
||||
function _start() {
|
||||
$timeout.cancel(completeTimeout);
|
||||
|
||||
// do not continually broadcast the started event:
|
||||
if (started) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rootScope.$broadcast('cfpLoadingBar:started');
|
||||
started = true;
|
||||
|
||||
if (includeBar) {
|
||||
$animate.enter(loadingBarContainer, $parent);
|
||||
}
|
||||
|
||||
if (includeSpinner) {
|
||||
$animate.enter(spinner, $parent);
|
||||
}
|
||||
_set(0.02);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the loading bar's width to a certain percent.
|
||||
*
|
||||
* @param n any value between 0 and 1
|
||||
*/
|
||||
function _set(n) {
|
||||
if (!started) {
|
||||
return;
|
||||
}
|
||||
var pct = (n * 100) + '%';
|
||||
loadingBar.css('width', pct);
|
||||
status = n;
|
||||
|
||||
// increment loadingbar to give the illusion that there is always
|
||||
// progress but make sure to cancel the previous timeouts so we don't
|
||||
// have multiple incs running at the same time.
|
||||
$timeout.cancel(incTimeout);
|
||||
incTimeout = $timeout(function() {
|
||||
_inc();
|
||||
}, 250);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the loading bar by a random amount
|
||||
* but slows down as it progresses
|
||||
*/
|
||||
function _inc() {
|
||||
if (_status() >= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var rnd = 0;
|
||||
|
||||
// TODO: do this mathmatically instead of through conditions
|
||||
|
||||
var stat = _status();
|
||||
if (stat >= 0 && stat < 0.25) {
|
||||
// Start out between 3 - 6% increments
|
||||
rnd = (Math.random() * (5 - 3 + 1) + 3) / 100;
|
||||
} else if (stat >= 0.25 && stat < 0.65) {
|
||||
// increment between 0 - 3%
|
||||
rnd = (Math.random() * 3) / 100;
|
||||
} else if (stat >= 0.65 && stat < 0.9) {
|
||||
// increment between 0 - 2%
|
||||
rnd = (Math.random() * 2) / 100;
|
||||
} else if (stat >= 0.9 && stat < 0.99) {
|
||||
// finally, increment it .5 %
|
||||
rnd = 0.005;
|
||||
} else {
|
||||
// after 99%, don't increment:
|
||||
rnd = 0;
|
||||
}
|
||||
|
||||
var pct = _status() + rnd;
|
||||
_set(pct);
|
||||
}
|
||||
|
||||
function _status() {
|
||||
return status;
|
||||
}
|
||||
|
||||
function _complete() {
|
||||
$rootScope.$broadcast('cfpLoadingBar:completed');
|
||||
_set(1);
|
||||
|
||||
// Attempt to aggregate any start/complete calls within 500ms:
|
||||
completeTimeout = $timeout(function() {
|
||||
$animate.leave(loadingBarContainer, function() {
|
||||
status = 0;
|
||||
started = false;
|
||||
});
|
||||
$animate.leave(spinner);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
return {
|
||||
start : _start,
|
||||
set : _set,
|
||||
status : _status,
|
||||
inc : _inc,
|
||||
complete : _complete,
|
||||
includeSpinner : this.includeSpinner,
|
||||
parentSelector : this.parentSelector
|
||||
};
|
||||
|
||||
|
||||
|
||||
}]; //
|
||||
}); // wtf javascript. srsly
|
||||
})(); //
|
|
@ -1,11 +1,7 @@
|
|||
<div class="container" ng-show="!loading && !image">
|
||||
<div class="loading-status" loading="loading" has-error="!image">
|
||||
No image found
|
||||
</div>
|
||||
|
||||
<div class="loading" ng-show="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
||||
<div class="container repo repo-image-view" ng-show="!loading && image">
|
||||
<div class="header">
|
||||
<a href="{{ '/repository/' + repo.namespace + '/' + repo.name }}" class="back"><i class="fa fa-chevron-left"></i></a>
|
||||
|
|
|
@ -9,19 +9,21 @@
|
|||
</div>
|
||||
|
||||
<div ng-show="!user.anonymous">
|
||||
<div ng-show="loadingmyrepos">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
<span class="namespace-selector" user="user" namespace="namespace" ng-show="!loadingmyrepos && user.organizations"></span>
|
||||
<div ng-show="!loadingmyrepos && myrepos.length > 0">
|
||||
<span class="namespace-selector" user="user" namespace="namespace" ng-show="user.organizations"></span>
|
||||
|
||||
<div class="resource-view" resource="my_repositories">
|
||||
<!-- Repos -->
|
||||
<div ng-show="my_repositories.value.length > 0">
|
||||
<h2>Top Repositories</h2>
|
||||
<div class="repo-listing" ng-repeat="repository in myrepos">
|
||||
<div class="repo-listing" ng-repeat="repository in my_repositories.value">
|
||||
<span class="repo-circle no-background" repo="repository"></span>
|
||||
<a ng-href="/repository/{{ repository.namespace }}/{{ repository.name }}">{{repository.namespace}}/{{repository.name}}</a>
|
||||
<div class="markdown-view description" content="repository.description" first-line-only="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="!loadingmyrepos && myrepos.length == 0">
|
||||
|
||||
<!-- No Repos -->
|
||||
<div ng-show="my_repositories.value.length == 0">
|
||||
<div class="sub-message" style="margin-top: 20px">
|
||||
<span ng-show="namespace != user.username">You don't have access to any repositories in this organization yet.</span>
|
||||
<span ng-show="namespace == user.username">You don't have any repositories yet!</span>
|
||||
|
@ -32,6 +34,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- col -->
|
||||
|
||||
<div class="col-md-4 col-md-offset-1">
|
||||
|
@ -41,8 +44,8 @@
|
|||
<div ng-show="!user.anonymous" class="user-welcome">
|
||||
<img class="gravatar" src="//www.gravatar.com/avatar/{{ user.gravatar }}?s=128&d=identicon" />
|
||||
<div class="sub-message">Welcome <b>{{ user.username }}</b>!</div>
|
||||
<a ng-show="myrepos" class="btn btn-primary" href="/repository/">Browse all repositories</a>
|
||||
<a ng-show="myrepos" class="btn btn-success" href="/new/">Create a new repository</a>
|
||||
<a ng-show="my_repositories.value" class="btn btn-primary" href="/repository/">Browse all repositories</a>
|
||||
<a class="btn btn-success" href="/new/">Create a new repository</a>
|
||||
</div>
|
||||
</div> <!-- col -->
|
||||
</div> <!-- row -->
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
<div class="user-setup" signed-in="signedIn()" redirect-url="'/plans/'"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal" ng-click="cancelNotedPlan()">Close</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
|
||||
<div class="loading" ng-show="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
||||
<div class="container" ng-show="!loading && (!repo || !permissions)">
|
||||
No repository found
|
||||
</div>
|
||||
|
||||
<div class="container repo repo-admin" ng-show="!loading && repo && permissions">
|
||||
<div class="resource-view" resource="repository" error-message="'No repository found'"></div>
|
||||
<div class="container repo repo-admin" ng-show="repo">
|
||||
<div class="header row">
|
||||
<a href="{{ '/repository/' + repo.namespace + '/' + repo.name }}" class="back"><i class="fa fa-chevron-left"></i></a>
|
||||
<h3>
|
||||
|
@ -156,12 +148,9 @@
|
|||
<i class="info-icon fa fa-info-circle" data-placement="left" data-content="URLs which will be invoked with an HTTP POST and JSON payload when a successful push to the repository occurs."></i>
|
||||
</div>
|
||||
|
||||
<div class="panel-body" ng-show="webhooksLoading">
|
||||
Loading webhooks: <i class="fa fa-spinner fa-spin fa-2x" style="vertical-align: middle; margin-left: 4px"></i>
|
||||
</div>
|
||||
|
||||
<div class="panel-body" ng-show="!webhooksLoading">
|
||||
<table class="permissions" ng-form="newWebhookForm">
|
||||
<div class="panel-body" ng-form="newWebhookForm">
|
||||
<div class="resource-view" resource="webhooksResource" error-message="'Could not load webhooks'">
|
||||
<table class="permissions">
|
||||
<thead>
|
||||
<tr>
|
||||
<td style="width: 500px;">Webhook URL</td>
|
||||
|
@ -189,6 +178,7 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="right-info">
|
||||
Quay will <b>POST</b> to these webhooks whenever a push occurs. See the <a href="/guide">User Guide</a> for more information.
|
||||
|
@ -240,14 +230,18 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="docker-auth-dialog" username="'$token'" token="shownToken.code"
|
||||
|
||||
<!-- Auth dialog -->
|
||||
<div class="docker-auth-dialog" username="'$token'" token="shownToken.code"
|
||||
shown="!!shownToken" counter="shownTokenCounter">
|
||||
<i class="fa fa-key"></i> {{ shownToken.friendlyName }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="cannotchangeModal">
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="cannotchangeModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
|
@ -262,10 +256,10 @@
|
|||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="makepublicModal">
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="makepublicModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
|
@ -284,11 +278,11 @@
|
|||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="makeprivateModal">
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="makeprivateModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
|
@ -307,11 +301,11 @@
|
|||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="channgechangepermModal">
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="channgechangepermModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
|
@ -327,11 +321,11 @@
|
|||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="confirmdeleteModal">
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="confirmdeleteModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
|
@ -347,11 +341,11 @@
|
|||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="confirmaddoutsideModal">
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="confirmaddoutsideModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
|
@ -367,5 +361,5 @@
|
|||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
<div class="loading" ng-show="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
||||
<div class="container" ng-show="!loading">
|
||||
<div class="container">
|
||||
<div class="repo-list" ng-show="!user.anonymous">
|
||||
<div ng-class="user.organizations.length ? 'section-header' : ''">
|
||||
<div class="button-bar-right">
|
||||
|
@ -27,30 +23,36 @@
|
|||
<h3 ng-show="namespace == user.username">Your Repositories</h3>
|
||||
<h3 ng-show="namespace != user.username">Repositories</h3>
|
||||
|
||||
<div ng-show="user_repositories.length > 0">
|
||||
<div class="repo-listing" ng-repeat="repository in user_repositories">
|
||||
<div class="resource-view" resource="user_repositories">
|
||||
<!-- User/Org has repositories -->
|
||||
<div ng-show="user_repositories.value.length > 0">
|
||||
<div class="repo-listing" ng-repeat="repository in user_repositories.value">
|
||||
<span class="repo-circle no-background" repo="repository"></span>
|
||||
<a ng-href="/repository/{{repository.namespace}}/{{ repository.name }}">{{repository.namespace}}/{{repository.name}}</a>
|
||||
<div class="description markdown-view" content="repository.description" first-line-only="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="user_repositories.length == 0" style="padding:20px;">
|
||||
<!-- User/Org has no repositories -->
|
||||
<div ng-show="user_repositories.value.length == 0" style="padding:20px;">
|
||||
<div class="alert alert-info">
|
||||
<h4 ng-show="namespace == user.username">You don't have any repositories yet!</h4>
|
||||
<h4 ng-show="namespace != user.username">This organization doesn't have any repositories, or you have not been provided access.</h4>
|
||||
<a href="/guide"><b>Click here</b> to learn how to create a repository</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="repo-list">
|
||||
<h3>Top Public Repositories</h3>
|
||||
<div class="repo-listing" ng-repeat="repository in public_repositories">
|
||||
<div class="resource-view" resource="public_repositories">
|
||||
<div class="repo-listing" ng-repeat="repository in public_repositories.value">
|
||||
<span class="repo-circle no-background" repo="repository"></span>
|
||||
<a ng-href="/repository/{{repository.namespace}}/{{ repository.name }}">{{repository.namespace}}/{{repository.name}}</a>
|
||||
<div class="description markdown-view" content="repository.description" first-line-only="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,19 +1,10 @@
|
|||
<div class="container" ng-show="!loading && !repo">
|
||||
No repository found
|
||||
</div>
|
||||
|
||||
<div class="loading" ng-show="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
||||
<div class="container repo" ng-show="!loading && repo">
|
||||
<div class="resource-view" resource="repository" error-message="'No Repository Found'">
|
||||
<div class="container repo">
|
||||
<!-- Repo Header -->
|
||||
<div class="header">
|
||||
<h3>
|
||||
<span class="repo-circle" repo="repo"></span>
|
||||
|
||||
<span style="color: #aaa;"> {{repo.namespace}}</span> <span style="color: #ccc">/</span> {{repo.name}}
|
||||
|
||||
<span class="settings-cog" ng-show="repo.can_admin" title="Repository Settings" bs-tooltip="tooltip.title" data-placement="bottom">
|
||||
<a href="{{ '/repository/' + repo.namespace + '/' + repo.name + '/admin' }}">
|
||||
<i class="fa fa-cog fa-lg"></i>
|
||||
|
@ -64,12 +55,14 @@
|
|||
<div class="empty-description" ng-show="repo.can_write">
|
||||
To push images to this repository:<br><br>
|
||||
<pre>sudo docker tag <i>0u123imageidgoeshere</i> quay.io/{{repo.namespace}}/{{repo.name}}
|
||||
sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre>
|
||||
sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="repo-content" ng-show="!currentTag.image && repo.is_building">
|
||||
<div class="empty-message">Your build is currently processing, if this takes longer than an hour, please contact <a href="mailto:support@quay.io">Quay.io support</a></div>
|
||||
<div class="empty-message">
|
||||
Your build is currently processing, if this takes longer than an hour, please contact <a href="mailto:support@quay.io">Quay.io support</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content view -->
|
||||
|
@ -79,7 +72,6 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre>
|
|||
<div class="row">
|
||||
<!-- Tree View container -->
|
||||
<div class="col-md-8">
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<!-- Tag dropdown -->
|
||||
|
@ -93,18 +85,14 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre>
|
|||
</ul>
|
||||
</div>
|
||||
<span class="right-title">Tags</span>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Image history loading -->
|
||||
<div ng-hide="imageHistory" style="padding: 10px; text-align: center;">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
||||
<!-- Tree View itself -->
|
||||
<!-- Image history tree -->
|
||||
<div class="resource-view" resource="imageHistory">
|
||||
<div id="image-history-container" onresize="tree.notifyResized()"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Side Panel -->
|
||||
<div class="col-md-4">
|
||||
|
@ -112,10 +100,10 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre>
|
|||
<div class="panel-heading">
|
||||
<!-- Image dropdown -->
|
||||
<div class="tag-dropdown dropdown" title="Images" bs-tooltip="tooltip.title" data-placement="top">
|
||||
<i class="fa fa-archive"><span class="tag-count">{{imageHistory.length}}</span></i>
|
||||
<i class="fa fa-archive"><span class="tag-count">{{imageHistory.value.length}}</span></i>
|
||||
<a href="javascript:void(0)" class="dropdown-toggle" data-toggle="dropdown">{{currentImage.id.substr(0, 12)}} <b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="image in imageHistory">
|
||||
<li ng-repeat="image in imageHistory.value">
|
||||
<a href="javascript:void(0)" ng-click="setImage(image)">{{image.id.substr(0, 12)}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -139,10 +127,7 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre>
|
|||
</dl>
|
||||
|
||||
<!-- Image changes loading -->
|
||||
<div ng-hide="currentImageChanges">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
||||
</div>
|
||||
|
||||
<div class="resource-view" resource="currentImageChangeResource">
|
||||
<div class="changes-container small-changes-container"
|
||||
ng-show="currentImageChanges.changed.length || currentImageChanges.added.length || currentImageChanges.removed.length">
|
||||
<div class="changes-count-container accordion-toggle" data-toggle="collapse" data-parent="#accordion" data-target="#collapseChanges">
|
||||
|
@ -179,7 +164,11 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre>
|
|||
</div>
|
||||
</div>
|
||||
<div class="more-changes" ng-show="getMoreCount(currentImageChanges) > 0">
|
||||
<a href="{{'/repository/' + repo.namespace + '/' + repo.name + '/image/' + currentImage.id}}">And {{getMoreCount(currentImageChanges)}} more...</a>
|
||||
<a href="{{'/repository/' + repo.namespace + '/' + repo.name + '/image/' + currentImage.id}}">
|
||||
And {{getMoreCount(currentImageChanges)}} more...
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="/static/lib/loading-bar.css">
|
||||
|
||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.0/css/font-awesome.css">
|
||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.no-icons.min.css">
|
||||
<link href='//fonts.googleapis.com/css?family=Droid+Sans:400,700' rel='stylesheet' type='text/css'>
|
||||
|
@ -42,8 +44,9 @@
|
|||
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular-route.min.js"></script>
|
||||
|
||||
<script src="//cdn.jsdelivr.net/underscorejs/1.5.2/underscore-min.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/restangular/1.1.3/restangular.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/restangular/1.2.0/restangular.min.js"></script>
|
||||
|
||||
<script src="static/lib/loading-bar.js"></script>
|
||||
<script src="static/lib/angular-strap.min.js"></script>
|
||||
<script src="static/lib/angulartics.js"></script>
|
||||
<script src="static/lib/angulartics-mixpanel.js"></script>
|
||||
|
|
Reference in a new issue