diff --git a/data/model.py b/data/model.py index cfaaec44e..384082869 100644 --- a/data/model.py +++ b/data/model.py @@ -146,7 +146,7 @@ def get_tag_image(namespace_name, repository_name, tag_name): joined = Image.select().join(RepositoryTag).join(Repository) return joined.where(Repository.name == repository_name, Repository.namespace == namespace_name, - RepositoryTag.name == tag_name) + RepositoryTag.name == tag_name).execute()[0] def create_or_update_tag(namespace_name, repository_name, tag_name, diff --git a/endpoints/api.py b/endpoints/api.py index 4e73650ab..888969255 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -7,6 +7,8 @@ from functools import wraps from data import model from app import app from util.names import parse_repository_name +from auth.permissions import (ReadRepositoryPermission, + ModifyRepositoryPermission) logger = logging.getLogger(__name__) @@ -23,6 +25,7 @@ def create_repo_api(): pass + @app.route('/api/repository/', methods=['GET']) @login_required def list_repos_api(): @@ -46,11 +49,59 @@ def list_repos_api(): @login_required @parse_repository_name def update_repo_api(namespace, repository): - pass + permission = ModifyRepositoryPermission(namespace, repository) + if permission.can(): + repo = model.get_repository(namespace, repository) + if repo: + values = request.get_json() + repo.description = values['description'] + repo.save() + return jsonify({ + 'success': True + }) + + abort(404) @app.route('/api/repository/', methods=['GET']) @login_required @parse_repository_name def get_repo_api(namespace, repository): - pass + def image_view(image): + return { + 'id': image.image_id, + 'created': image.created, + 'comment': image.comment + } + + def tag_view(tag): + image = model.get_tag_image(namespace, repository, tag.name) + if not image: + return {} + + return { + 'name': tag.name, + 'image': image_view(image) + } + + def repo_view(repository, tags = []): + tag_list = [] + for tag in tags: + tag_list.append(tag_view(tag)) + + return { + 'namespace': repository.namespace, + 'name': repository.name, + 'description': repository.description, + 'tags': tag_list, + 'can_write': ModifyRepositoryPermission(repository.namespace, repository.name).can() + } + + permission = ReadRepositoryPermission(namespace, repository) + if permission.can(): + repo = model.get_repository(namespace, repository) + if repo: + tags = model.list_repository_tags(namespace, repository) + return jsonify(repo_view(repo, tags = tags)) + + abort(404) diff --git a/static/css/quay.css b/static/css/quay.css new file mode 100644 index 000000000..5126599e1 --- /dev/null +++ b/static/css/quay.css @@ -0,0 +1,40 @@ +.editable .glyphicon { + opacity: 0.2; + font-size: 85%; + margin-left: 10px; + display: inline-block; + + transition: opacity 500ms ease-in-out; +} + +.noteditable .glyphicon { + display: none; +} + +p.editable .content:empty:after { + display: inline-block; + content: "(Click to add)"; + color: #aaa; +} + +p.editable:hover { + cursor: pointer; +} + +p.editable:hover .glyphicon { + opacity: 1; +} + +.tag-dropdown { + display: inline-block; + padding: 6px; + border: 1px solid #ddd; + margin-right: 15px; + margin-bottom: 5px; +} + +.modal-body textarea { + width: 100%; + height: 150px; + border: 0px; +} \ No newline at end of file diff --git a/static/js/app.js b/static/js/app.js index b3411b5ec..2b608dcc3 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -1,10 +1,23 @@ -angular.module('quay', ['restangular']). - config(['$routeProvider', function($routeProvider) { +quayApp = angular.module('quay', ['restangular']). + config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) { $routeProvider. - when('/repository/', {templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl}). - when('/', {templateUrl: '/static/partials/landing.html', controller: LandingCtrl}). + when('/repository/:namespace/:name', {templateUrl: '/static/partials/view-repo.html', controller: RepoCtrl}). + when('/repository/:namespace/:name/:tag', {templateUrl: '/static/partials/view-repo.html', controller: RepoCtrl}). + + when('/repository/', {title: 'Repositories', templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl}). + when('/', {title: 'Quay', templateUrl: '/static/partials/landing.html', controller: LandingCtrl}). otherwise({redirectTo: '/'}); + + //$locationProvider.html5Mode(true); }]). config(function(RestangularProvider) { RestangularProvider.setBaseUrl('/api/'); - }); \ No newline at end of file + }); + +quayApp.run(['$location', '$rootScope', function($location, $rootScope) { + $rootScope.$on('$routeChangeSuccess', function (event, current, previous) { + if (current.$$route.title) { + $rootScope.title = current.$$route.title; + } + }); +}]); \ No newline at end of file diff --git a/static/js/controllers.js b/static/js/controllers.js index 6e824730e..9131cd2cf 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -8,3 +8,33 @@ function RepoListCtrl($scope, Restangular) { function LandingCtrl($scope) { } + +function RepoCtrl($scope, Restangular, $routeParams, $rootScope) { + $rootScope.title = 'Loading...'; + + $scope.editDescription = function() { + if (!$scope.repo.can_write) { return; } + $('#descriptionEdit')[0].value = $scope.repo.description || ''; + $('#editModal').modal({}); + }; + + $scope.saveDescription = function() { + $('#editModal').modal('hide'); + $scope.repo.description = $('#descriptionEdit')[0].value; + $scope.repo.put(); + }; + + var namespace = $routeParams.namespace; + var name = $routeParams.name; + var tag = $routeParams.tag || 'latest'; + + var repositoryFetch = Restangular.one('repository/' + namespace + '/' + name); + repositoryFetch.get().then(function(repo) { + $rootScope.title = namespace + '/' + name; + $scope.repo = repo; + $scope.currentTag = repo.tags[tag] || repo.tags['latest']; + }, function() { + $scope.repo = null; + $rootScope.title = 'Unknown Repository'; + }); +} \ No newline at end of file diff --git a/static/partials/landing.html b/static/partials/landing.html index e47ba0c23..fbb150412 100644 --- a/static/partials/landing.html +++ b/static/partials/landing.html @@ -1 +1,3 @@ -Repositories \ No newline at end of file +
+ Repositories +
diff --git a/static/partials/repo-list.html b/static/partials/repo-list.html index ed0ca89e3..736c58d73 100644 --- a/static/partials/repo-list.html +++ b/static/partials/repo-list.html @@ -1,4 +1,6 @@ -

Repositories

-
- {{repository.namespace}}/{{repository.name}} -
\ No newline at end of file +
+

Repositories

+
+ {{repository.namespace}}/{{repository.name}} +
+
diff --git a/static/partials/view-repo.html b/static/partials/view-repo.html new file mode 100644 index 000000000..749417be7 --- /dev/null +++ b/static/partials/view-repo.html @@ -0,0 +1,58 @@ +
+ No repository found +
+ +
+ +
+

+ {{repo.namespace}} / {{repo.name}} +

+
+ + +

{{repo.description}}

+ + + + + Loading... + +
+
+ + + + +
diff --git a/templates/index.html b/templates/index.html index 95bf4ff5f..75bd29aff 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,13 +1,19 @@ - Quay - Private Docker Repository + Quay - Private Docker Repository - + + + + + + + @@ -17,7 +23,46 @@ -

Hello World

+ + +
- \ No newline at end of file + diff --git a/test.db b/test.db index 9c0dcf287..dc560799b 100644 Binary files a/test.db and b/test.db differ