Start on UI for Quay
This commit is contained in:
parent
9278871381
commit
27ce5c00b2
10 changed files with 258 additions and 17 deletions
|
@ -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,
|
||||
|
|
|
@ -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/<path: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)
|
||||
|
|
40
static/css/quay.css
Normal file
40
static/css/quay.css
Normal file
|
@ -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;
|
||||
}
|
|
@ -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/');
|
||||
});
|
||||
});
|
||||
|
||||
quayApp.run(['$location', '$rootScope', function($location, $rootScope) {
|
||||
$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
|
||||
if (current.$$route.title) {
|
||||
$rootScope.title = current.$$route.title;
|
||||
}
|
||||
});
|
||||
}]);
|
|
@ -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';
|
||||
});
|
||||
}
|
|
@ -1 +1,3 @@
|
|||
<a ng-href="#/repository/">Repositories</a>
|
||||
<div class="container">
|
||||
<a ng-href="#/repository">Repositories</a>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
<h2>Repositories</h2>
|
||||
<div ng-repeat="repository in repositories">
|
||||
{{repository.namespace}}/{{repository.name}}
|
||||
</div>
|
||||
<div class="container">
|
||||
<h3>Repositories</h3>
|
||||
<div ng-repeat="repository in repositories">
|
||||
<a ng-href="#/repository/{{repository.namespace}}/{{ repository.name }}">{{repository.namespace}}/{{repository.name}}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
58
static/partials/view-repo.html
Normal file
58
static/partials/view-repo.html
Normal file
|
@ -0,0 +1,58 @@
|
|||
<div class="container" ng-hide="repo">
|
||||
No repository found
|
||||
</div>
|
||||
|
||||
<div class="container" ng-show="repo">
|
||||
<!-- Repo Header -->
|
||||
<div class="header">
|
||||
<h3>
|
||||
<span class="glyphicon glyphicon-hdd"></span> <span style="color: #aaa;"> {{repo.namespace}}</span> <span style="color: #ccc">/</span> {{repo.name}}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<p ng-class="'lead ' + (repo.can_write ? 'editable' : 'noteditable')" ng-click="editDescription()"><span class="content">{{repo.description}}</span><span class="glyphicon glyphicon-pencil"></span></p>
|
||||
|
||||
<!-- Tab bar -->
|
||||
<ul class="nav nav-tabs">
|
||||
<li>
|
||||
<span class="tag-dropdown dropdown">
|
||||
<span class="glyphicon glyphicon-bookmark"></span>
|
||||
<a href="javascript:void(0)" class="dropdown-toggle" data-toggle="dropdown">{{currentTag.name}} <b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="tag in repo.tags">
|
||||
<a href="{{ '#/repository/' + repo.namespace + '/' + repo.name + '/' + tag.name }}">{{tag.name}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
|
||||
</li>
|
||||
<li class="active"><a href="javascript:void(0)">Current Image</a></li>
|
||||
<li><a href="javascript:void(0)">Image History</a></li>
|
||||
</ul>
|
||||
|
||||
Loading...
|
||||
|
||||
<div id="current-image">
|
||||
</div>
|
||||
|
||||
<!-- Modal edit for the description -->
|
||||
<div class="modal fade" id="editModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Edit Repository Description</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<textarea id="descriptionEdit" placeholder="Enter description">{{ repo.description }}</textarea>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" ng-click="saveDescription()">Save changes</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
</div>
|
|
@ -1,13 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html ng-app="quay">
|
||||
<head>
|
||||
<title>Quay - Private Docker Repository</title>
|
||||
<title ng-bind="title + ' · Quay'">Quay - Private Docker Repository</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.no-icons.min.css">
|
||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-theme.min.css">
|
||||
|
||||
<link rel="stylesheet" href="/static/css/quay.css">
|
||||
|
||||
|
||||
<script src="//code.jquery.com/jquery.js"></script>
|
||||
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
|
||||
|
||||
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.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>
|
||||
|
@ -17,7 +23,46 @@
|
|||
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World</h1>
|
||||
<!-- Nav bar -->
|
||||
<nav class="navbar navbar-default" role="navigation">
|
||||
<!-- Quay -->
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="#">Quay</a>
|
||||
</div>
|
||||
|
||||
<!-- Collapsable stuff -->
|
||||
<div class="collapse navbar-collapse navbar-ex1-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a ng-href="/repository">Repositories</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<form class="navbar-form navbar-left" role="search">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" placeholder="Find Repo">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<li class="dropdown">
|
||||
<!--<button type="button" class="btn btn-default navbar-btn">Sign in</button>-->
|
||||
|
||||
<a href="javascript:void(0)" class="dropdown-toggle" data-toggle="dropdown">devtable <b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#">Settings</a></li>
|
||||
<li><a href="#">Sign out</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div><!-- /.navbar-collapse -->
|
||||
</nav>
|
||||
|
||||
<div ng-view></div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
BIN
test.db
BIN
test.db
Binary file not shown.
Reference in a new issue