Merge branch 'master' of ssh://bitbucket.org/yackob03/quay
Conflicts: static/js/app.js
This commit is contained in:
commit
20765b7e37
10 changed files with 316 additions and 71 deletions
|
@ -96,9 +96,11 @@ def get_token(code):
|
||||||
return AccessToken.get(AccessToken.code == code)
|
return AccessToken.get(AccessToken.code == code)
|
||||||
|
|
||||||
|
|
||||||
def get_visible_repositories(username=None):
|
def get_visible_repositories(username=None, include_public=True, limit=None, sort=False):
|
||||||
query = Repository.select().distinct().join(Visibility)
|
query = Repository.select().distinct().join(Visibility)
|
||||||
or_clauses = [(Visibility.name == 'public')]
|
or_clauses = []
|
||||||
|
if include_public:
|
||||||
|
or_clauses.append((Visibility.name == 'public'));
|
||||||
|
|
||||||
if username:
|
if username:
|
||||||
with_perms = query.switch(Repository).join(RepositoryPermission,
|
with_perms = query.switch(Repository).join(RepositoryPermission,
|
||||||
|
@ -106,8 +108,15 @@ def get_visible_repositories(username=None):
|
||||||
query = with_perms.join(User)
|
query = with_perms.join(User)
|
||||||
or_clauses.append(User.username == username)
|
or_clauses.append(User.username == username)
|
||||||
|
|
||||||
return query.where(reduce(operator.or_, or_clauses))
|
if sort:
|
||||||
|
with_images = query.switch(Repository).join(Image, JOIN_LEFT_OUTER)
|
||||||
|
query = with_images.order_by(Image.created.desc())
|
||||||
|
|
||||||
|
query = query.where(reduce(operator.or_, or_clauses))
|
||||||
|
if limit:
|
||||||
|
query = query.limit(limit)
|
||||||
|
|
||||||
|
return query
|
||||||
|
|
||||||
def get_matching_repositories(repo_term, username=None):
|
def get_matching_repositories(repo_term, username=None):
|
||||||
namespace_term = repo_term
|
namespace_term = repo_term
|
||||||
|
|
|
@ -14,7 +14,7 @@ from auth.permissions import (ReadRepositoryPermission,
|
||||||
ModifyRepositoryPermission,
|
ModifyRepositoryPermission,
|
||||||
AdministerRepositoryPermission)
|
AdministerRepositoryPermission)
|
||||||
from endpoints import registry
|
from endpoints import registry
|
||||||
|
import re
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -56,6 +56,14 @@ def get_logged_in_user():
|
||||||
@app.route('/api/user/', methods=['POST'])
|
@app.route('/api/user/', methods=['POST'])
|
||||||
def create_user_api():
|
def create_user_api():
|
||||||
user_data = request.get_json()
|
user_data = request.get_json()
|
||||||
|
existing_user = model.get_user(user_data['username'])
|
||||||
|
if existing_user:
|
||||||
|
error_resp = jsonify({
|
||||||
|
'message': 'The username already exists'
|
||||||
|
})
|
||||||
|
error_resp.status_code = 400
|
||||||
|
return error_resp
|
||||||
|
|
||||||
try:
|
try:
|
||||||
new_user = model.create_user(user_data['username'], user_data['password'],
|
new_user = model.create_user(user_data['username'], user_data['password'],
|
||||||
user_data['email'])
|
user_data['email'])
|
||||||
|
@ -63,8 +71,13 @@ def create_user_api():
|
||||||
send_confirmation_email(new_user.username, new_user.email, code.code)
|
send_confirmation_email(new_user.username, new_user.email, code.code)
|
||||||
return make_response('Created', 201)
|
return make_response('Created', 201)
|
||||||
except model.DataModelException as ex:
|
except model.DataModelException as ex:
|
||||||
|
message = ex.message
|
||||||
|
m = re.search('column ([a-zA-Z]+) is not unique', message)
|
||||||
|
if m and m.group(1):
|
||||||
|
message = m.group(1) + ' already exists'
|
||||||
|
|
||||||
error_resp = jsonify({
|
error_resp = jsonify({
|
||||||
'message': ex.message,
|
'message': message,
|
||||||
})
|
})
|
||||||
error_resp.status_code = 400
|
error_resp.status_code = 400
|
||||||
return error_resp
|
return error_resp
|
||||||
|
@ -114,9 +127,24 @@ def list_repos_api():
|
||||||
'description': repo_obj.description,
|
'description': repo_obj.description,
|
||||||
}
|
}
|
||||||
|
|
||||||
username = current_user.db_user.username if current_user.is_authenticated() else None
|
limit = request.args.get('limit', None)
|
||||||
|
include_public = request.args.get('public', 'true')
|
||||||
|
include_private = request.args.get('private', 'true')
|
||||||
|
sort = request.args.get('sort', 'false')
|
||||||
|
|
||||||
|
try:
|
||||||
|
limit = int(limit) if limit else None
|
||||||
|
except:
|
||||||
|
limit = None
|
||||||
|
|
||||||
|
include_public = include_public == 'true'
|
||||||
|
include_private = include_private == 'true'
|
||||||
|
sort = sort == 'true'
|
||||||
|
|
||||||
|
username = current_user.db_user.username if current_user.is_authenticated() and include_private else None
|
||||||
repos = [repo_view(repo)
|
repos = [repo_view(repo)
|
||||||
for repo in model.get_visible_repositories(username)]
|
for repo in model.get_visible_repositories(
|
||||||
|
username, limit = limit, include_public = include_public, sort = sort)]
|
||||||
response = {
|
response = {
|
||||||
'repositories': repos
|
'repositories': repos
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,19 @@
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.landing .welcome-message {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing .welcome-message .sub-message {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing .welcome-message .gravatar {
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid #94C9F7;
|
||||||
|
}
|
||||||
|
|
||||||
.landing .sub-message b {
|
.landing .sub-message b {
|
||||||
color: #94C9F7;
|
color: #94C9F7;
|
||||||
}
|
}
|
||||||
|
@ -136,6 +149,32 @@ background: linear-gradient(to bottom, #141414 0%,transparent 15%,transparent 8
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.landing .options {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing .options .option {
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing .options .or {
|
||||||
|
margin: 14px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing .options .or span {
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #444;
|
||||||
|
padding: 6px;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
line-height: 36px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.landing-footer {
|
.landing-footer {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|
||||||
|
@ -314,6 +353,12 @@ p.editable:hover i {
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.repo .empty-message {
|
||||||
|
padding: 6px;
|
||||||
|
font-size: 1.8em;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
.repo dl.dl-horizontal dt {
|
.repo dl.dl-horizontal dt {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
}
|
}
|
||||||
|
@ -392,6 +437,9 @@ p.editable:hover i {
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.repo-list {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
.repo-listing {
|
.repo-listing {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -400,6 +448,15 @@ p.editable:hover i {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.repo-listing:last-child {
|
||||||
|
border-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing .repo-listing {
|
||||||
|
border-bottom: 0px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.repo-listing a {
|
.repo-listing a {
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
|
@ -649,3 +706,9 @@ p.editable:hover i {
|
||||||
line-height: 1.428571429;
|
line-height: 1.428571429;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Fix for bootstrap dialogs that are broken. */
|
||||||
|
|
||||||
|
.modal-backdrop.in {
|
||||||
|
opacity: 0.5 !important;
|
||||||
|
}
|
|
@ -73,6 +73,7 @@ quayApp = angular.module('quay', ['restangular', 'angularMoment'], function($pro
|
||||||
when('/repository/:namespace/:name/admin', {templateUrl: '/static/partials/repo-admin.html', controller:RepoAdminCtrl}).
|
when('/repository/:namespace/:name/admin', {templateUrl: '/static/partials/repo-admin.html', controller:RepoAdminCtrl}).
|
||||||
when('/repository/', {title: 'Repositories', templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl}).
|
when('/repository/', {title: 'Repositories', templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl}).
|
||||||
when('/user', {title: 'User Admin', templateUrl: '/static/partials/user-admin.html', controller: UserAdminCtrl}).
|
when('/user', {title: 'User Admin', templateUrl: '/static/partials/user-admin.html', controller: UserAdminCtrl}).
|
||||||
|
when('/guide/', {title: 'Getting Started Guide', templateUrl: '/static/partials/guide.html', controller: GuideCtrl}).
|
||||||
when('/', {title: 'Quay', templateUrl: '/static/partials/landing.html', controller: LandingCtrl}).
|
when('/', {title: 'Quay', templateUrl: '/static/partials/landing.html', controller: LandingCtrl}).
|
||||||
otherwise({redirectTo: '/'});
|
otherwise({redirectTo: '/'});
|
||||||
}]).
|
}]).
|
||||||
|
|
|
@ -77,6 +77,9 @@ function HeaderCtrl($scope, UserService) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function GuideCtrl($scope, Restangular) {
|
||||||
|
}
|
||||||
|
|
||||||
function RepoListCtrl($scope, Restangular) {
|
function RepoListCtrl($scope, Restangular) {
|
||||||
$scope.getCommentFirstLine = function(commentString) {
|
$scope.getCommentFirstLine = function(commentString) {
|
||||||
return getMarkedDown(getFirstTextLine(commentString));
|
return getMarkedDown(getFirstTextLine(commentString));
|
||||||
|
@ -89,35 +92,82 @@ function RepoListCtrl($scope, Restangular) {
|
||||||
|
|
||||||
$('.spin').spin();
|
$('.spin').spin();
|
||||||
$scope.loading = true;
|
$scope.loading = true;
|
||||||
|
$scope.public_repositories = null;
|
||||||
|
$scope.private_repositories = null;
|
||||||
|
|
||||||
// Load the list of repositories.
|
// Load the list of personal repositories.
|
||||||
var repositoryFetch = Restangular.all('repository/');
|
var repositoryPrivateFetch = Restangular.all('repository/');
|
||||||
repositoryFetch.getList().then(function(resp) {
|
repositoryPrivateFetch.getList({'public': false, 'sort': true}).then(function(resp) {
|
||||||
$scope.repositories = resp.repositories;
|
$scope.private_repositories = resp.repositories;
|
||||||
$scope.loading = false;
|
$scope.loading = !($scope.public_repositories && $scope.private_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.private_repositories);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function LandingCtrl($scope, $timeout, Restangular, UserService) {
|
function LandingCtrl($scope, $timeout, Restangular, UserService) {
|
||||||
$('.form-signup').popover();
|
$('.form-signup').popover();
|
||||||
|
$('.spin').spin();
|
||||||
|
|
||||||
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
|
||||||
|
if (!currentUser.anonymous) {
|
||||||
|
$scope.loadMyRepos();
|
||||||
|
}
|
||||||
|
|
||||||
$scope.user = currentUser;
|
$scope.user = currentUser;
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
$scope.awaitingConfirmation = false;
|
$scope.awaitingConfirmation = false;
|
||||||
|
$scope.registering = false;
|
||||||
|
|
||||||
|
$scope.getCommentFirstLine = function(commentString) {
|
||||||
|
return getMarkedDown(getFirstTextLine(commentString));
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.browseRepos = function() {
|
||||||
|
document.location = '/#/repository';
|
||||||
|
};
|
||||||
|
|
||||||
$scope.register = function() {
|
$scope.register = function() {
|
||||||
|
$('.form-signup').popover('hide');
|
||||||
|
$scope.registering = true;
|
||||||
|
|
||||||
var newUserPost = Restangular.one('user/');
|
var newUserPost = Restangular.one('user/');
|
||||||
newUserPost.customPOST($scope.newUser).then(function() {
|
newUserPost.customPOST($scope.newUser).then(function() {
|
||||||
$scope.awaitingConfirmation = true;
|
$scope.awaitingConfirmation = true;
|
||||||
|
$scope.registering = false;
|
||||||
}, function(result) {
|
}, function(result) {
|
||||||
console.log("Displaying error message.");
|
console.log("Displaying error message.");
|
||||||
|
$scope.registering = false;
|
||||||
$scope.registerError = result.data.message;
|
$scope.registerError = result.data.message;
|
||||||
$timeout(function() {
|
$timeout(function() {
|
||||||
$('.form-signup').popover('show');
|
$('.form-signup').popover('show');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.loadMyRepos = function() {
|
||||||
|
$scope.loadingmyrepos = true;
|
||||||
|
|
||||||
|
// Load the list of repositories.
|
||||||
|
var params = {
|
||||||
|
'limit': 5,
|
||||||
|
'public': false,
|
||||||
|
'sort': true
|
||||||
|
};
|
||||||
|
|
||||||
|
var repositoryFetch = Restangular.all('repository/');
|
||||||
|
repositoryFetch.getList(params).then(function(resp) {
|
||||||
|
$scope.myrepos = resp.repositories;
|
||||||
|
$scope.loadingmyrepos = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function RepoCtrl($scope, Restangular, $routeParams, $rootScope) {
|
function RepoCtrl($scope, Restangular, $routeParams, $rootScope) {
|
||||||
|
|
33
static/partials/guide.html
Normal file
33
static/partials/guide.html
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<div class="container">
|
||||||
|
<div class="alert alert-warning">Warning: Quay requires docker version 0.7 or higher to work</div>
|
||||||
|
|
||||||
|
<h2>Getting started guide</h2>
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<h3>Pushing a repository to Quay</h3>
|
||||||
|
<div class="container">
|
||||||
|
First, tag the image with your repository name:<br><br>
|
||||||
|
<pre>docker tag <i>0u123imageid</i> quay.io/<i>repo_namespace/repo_name</i></pre>
|
||||||
|
<br>
|
||||||
|
Second, push the repository to Quay:<br><br>
|
||||||
|
<pre>docker push quay.io/<i>repo_namespace/repo_name</i></pre>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<h3>Pulling a repository from Quay</h3>
|
||||||
|
<div class="container">
|
||||||
|
<div class="alert alert-info">Note: <b>Private</b> repositories require you to be <b>logged in</b> or the pull will fail. See below for how to sign into Quay if you have never done so before. </div>
|
||||||
|
To pull a repository from Quay, run the following command:
|
||||||
|
<br><br>
|
||||||
|
<pre>docker pull quay.io/<i>path/to/repository</i></pre>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<h3>Signing into to Quay <span class="label label-default">Optional</span></h3>
|
||||||
|
<div class="container">
|
||||||
|
If you have never pushed a repository to Quay and wish to pull a <b>private</b> repository, you can sign into Quay by running the following command:
|
||||||
|
<br><br>
|
||||||
|
<pre>docker login quay.io</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,31 +1,64 @@
|
||||||
<div class="landing">
|
<div class="landing">
|
||||||
<div class="background"></div>
|
<div class="background"></div>
|
||||||
<div class="background-mask"></div>
|
<div class="background-mask"></div>
|
||||||
<div class="message-container">
|
<div class="message-container" ng-show="user.anonymous">
|
||||||
<div class="message">Secure hosting for <b>private</b> docker containers</div>
|
<div class="message">Secure hosting for <b>private</b> docker containers</div>
|
||||||
<div class="sub-message">Use the docker images <b>your team</b> needs with the safety of <b>private</b> storage</div>
|
<div class="sub-message">Use the docker images <b>your team</b> needs with the safety of <b>private</b> storage</div>
|
||||||
<div class="sellcall"><a href="">Starting at $7/mo</a></div>
|
<div class="sellcall"><a href="">Starting at $7/mo</a></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="message-container" ng-show="!user.anonymous">
|
||||||
|
<div ng-show="loadingmyrepos">
|
||||||
|
<div class="spin"></div>
|
||||||
|
</div>
|
||||||
|
<div ng-show="!loadingmyrepos && myrepos.length > 0">
|
||||||
|
<h2>Your Top Repositories</h4>
|
||||||
|
<div class="repo-listing" ng-repeat="repository in myrepos">
|
||||||
|
<i class="icon-hdd icon-large"></i>
|
||||||
|
<a ng-href="#/repository/{{repository.namespace}}/{{ repository.name }}">{{repository.namespace}}/{{repository.name}}</a>
|
||||||
|
<div class="description" ng-bind-html-unsafe="getCommentFirstLine(repository.description)"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ng-show="!loadingmyrepos && myrepos.length == 0">
|
||||||
|
<div class="sub-message">
|
||||||
|
You don't have any <b>private</b> repositories yet!
|
||||||
|
|
||||||
|
<div class="options">
|
||||||
|
<div class="option"><a href="#/guide">Learn how to create a repository</a></div>
|
||||||
|
<div class="or"><span>or</span></div>
|
||||||
|
<div class="option"><a href="#/repository">Browse the public repositories</a></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="signup-container">
|
<div class="signup-container">
|
||||||
<div ng-show="user.anonymous">
|
<div ng-show="user.anonymous">
|
||||||
<form class="form-signup" name="signupForm" ng-submit="register()" data-trigger="manual" data-content="{{ registerError }}" data-placement="left" ng-show="!awaitingConfirmation">
|
<form class="form-signup" name="signupForm" ng-submit="register()" data-trigger="manual" data-content="{{ registerError }}" data-placement="left" ng-show="!awaitingConfirmation && !registering">
|
||||||
<input type="text" class="form-control" placeholder="Create a username" name="username" ng-model="newUser.username" autofocus required>
|
<input type="text" class="form-control" placeholder="Create a username" name="username" ng-model="newUser.username" autofocus required>
|
||||||
<input type="email" class="form-control" placeholder="Email address" ng-model="newUser.email" required>
|
<input type="email" class="form-control" placeholder="Email address" ng-model="newUser.email" required>
|
||||||
<input type="password" class="form-control" placeholder="Create a password" ng-model="newUser.password" required>
|
<input type="password" class="form-control" placeholder="Create a password" ng-model="newUser.password" required>
|
||||||
<input type="password" class="form-control" placeholder="Verify your password" ng-model="newUser.repeatePassword" match="newUser.password" required>
|
<input type="password" class="form-control" placeholder="Verify your password" ng-model="newUser.repeatePassword" match="newUser.password" required>
|
||||||
<button class="btn btn-lg btn-primary btn-block" ng-disabled="signupForm.$invalid" type="submit">Get Started!</button>
|
<button class="btn btn-lg btn-primary btn-block" ng-disabled="signupForm.$invalid" type="submit">Get Started!</button>
|
||||||
</form>
|
</form>
|
||||||
|
<div ng-show="registering" style="text-align: center">
|
||||||
|
<span class="spin" color="#fff" style="display: inline-block"></span>
|
||||||
|
</div>
|
||||||
<div ng-show="awaitingConfirmation">
|
<div ng-show="awaitingConfirmation">
|
||||||
<div class="sub-message">Thank you for registering! We have sent you an activation email. You must <b>verify your email address</b> before you can continue.</div>
|
<div class="sub-message">Thank you for registering! We have sent you an activation email. You must <b>verify your email address</b> before you can continue.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ng-show="!user.anonymous">
|
<div ng-show="!user.anonymous">
|
||||||
<div class="sub-message">Some message about how awesome it is to be a Quay user goes here.</div>
|
<div class="welcome-message">
|
||||||
|
<img class="gravatar" src="//www.gravatar.com/avatar/{{ user.gravatar }}?s=128&d=identicon" />
|
||||||
|
<div class="sub-message">Welcome <b>{{ user.username }}</b>!</div>
|
||||||
|
</div>
|
||||||
|
<button ng-show="myrepos" class="btn btn-lg btn-primary btn-block" ng-click="browseRepos()">Browse all repositories</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="shoutouts">
|
<div class="shoutouts" ng-show="user.anonymous">
|
||||||
<div class="shoutout">
|
<div class="shoutout">
|
||||||
<i class="icon-lock"></i>
|
<i class="icon-lock"></i>
|
||||||
<b>Secure</b>
|
<b>Secure</b>
|
||||||
|
@ -68,6 +101,7 @@
|
||||||
<h4>Support</h4>
|
<h4>Support</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="">Contact Support</a></li>
|
<li><a href="">Contact Support</a></li>
|
||||||
|
<li><a href="#/guide/">Getting Started Guide</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,31 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container" ng-show="!loading">
|
<div class="container" ng-show="!loading">
|
||||||
<h3>Repositories</h3>
|
<div class="repo-list">
|
||||||
<div class="repo-listing" ng-repeat="repository in repositories">
|
<h3>Your Repositories</h3>
|
||||||
<i class="icon-hdd icon-large"></i>
|
<div ng-show="private_repositories.length > 0">
|
||||||
<a ng-href="#/repository/{{repository.namespace}}/{{ repository.name }}">{{repository.namespace}}/{{repository.name}}</a>
|
<div class="repo-listing" ng-repeat="repository in private_repositories">
|
||||||
<div class="description" ng-bind-html-unsafe="getCommentFirstLine(repository.description)"></div>
|
<i class="icon-hdd icon-large"></i>
|
||||||
|
<a ng-href="#/repository/{{repository.namespace}}/{{ repository.name }}">{{repository.namespace}}/{{repository.name}}</a>
|
||||||
|
<div class="description" ng-bind-html-unsafe="getCommentFirstLine(repository.description)"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-show="private_repositories.length == 0" style="padding:20px;">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<h4>You don't have any repositories yet!</h4>
|
||||||
|
<a href="#/guide"><b>Click here</b> to learn how to create a repository</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="repo-list">
|
||||||
|
<h3>Top Public Repositories</h3>
|
||||||
|
<div class="repo-listing" ng-repeat="repository in public_repositories">
|
||||||
|
<i class="icon-hdd icon-large"></i>
|
||||||
|
<a ng-href="#/repository/{{repository.namespace}}/{{ repository.name }}">{{repository.namespace}}/{{repository.name}}</a>
|
||||||
|
<div class="description" ng-bind-html-unsafe="getCommentFirstLine(repository.description)"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -50,60 +50,65 @@
|
||||||
<i class="icon-edit"></i>
|
<i class="icon-edit"></i>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Tab bar -->
|
<div class="repo-content" ng-show="!currentTag.image">
|
||||||
<ul class="nav nav-tabs">
|
<div class="empty-message">(This repository is empty)</div>
|
||||||
<li>
|
|
||||||
<span class="tag-dropdown dropdown" title="Tags">
|
|
||||||
<i class="icon-bookmark"></i>
|
|
||||||
<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/' + tag.name }}">{{tag.name}}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li id="current-image-tab" class="active" ng-click="showTab('current-image')"><a href="javascript:void(0)">Current Image</a></li>
|
|
||||||
<li id="image-history-tab" ng-click="showTab('image-history')"><a href="javascript:void(0)">Image History</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div id="current-image">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>Created</dt>
|
|
||||||
<dd am-time-ago="parseDate(currentTag.image.created)"></dd>
|
|
||||||
|
|
||||||
<dt>ID</dt>
|
|
||||||
<dd>{{ currentTag.image.id }}</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<div ng-show="currentTag.image.comment">
|
|
||||||
<strong>Description:</strong>
|
|
||||||
<blockquote style="margin-top: 10px;" ng-bind-html-unsafe="getMarkedDown(currentTag.image.comment)">
|
|
||||||
</blockquote>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="image-history" style="display: none">
|
<div class="repo-content" ng-show="currentTag.image">
|
||||||
<div ng-hide="imageHistory">
|
<!-- Tab bar -->
|
||||||
<div class="spin"></div>
|
<ul class="nav nav-tabs">
|
||||||
|
<li>
|
||||||
|
<span class="tag-dropdown dropdown" title="Tags">
|
||||||
|
<i class="icon-bookmark"></i>
|
||||||
|
<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/' + tag.name }}">{{tag.name}}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li id="current-image-tab" class="active" ng-click="showTab('current-image')"><a href="javascript:void(0)">Current Image</a></li>
|
||||||
|
<li id="image-history-tab" ng-click="showTab('image-history')"><a href="javascript:void(0)">Image History</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div id="current-image">
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
<dt>Created</dt>
|
||||||
|
<dd am-time-ago="parseDate(currentTag.image.created)"></dd>
|
||||||
|
<dt>ID</dt>
|
||||||
|
<dd>{{ currentTag.image.id }}</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<div ng-show="currentTag.image.comment">
|
||||||
|
<strong>Description:</strong>
|
||||||
|
<blockquote style="margin-top: 10px;" ng-bind-html-unsafe="getMarkedDown(currentTag.image.comment)">
|
||||||
|
</blockquote>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-show="imageHistory">
|
<div id="image-history" style="display: none">
|
||||||
<table class="images">
|
<div ng-hide="imageHistory">
|
||||||
<thead>
|
<div class="spin"></div>
|
||||||
<tr>
|
</div>
|
||||||
<td>ID</td>
|
|
||||||
<td>Created</td>
|
|
||||||
<td>Comment</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tr ng-repeat="image in imageHistory">
|
<div ng-show="imageHistory">
|
||||||
<td class="image-id" title="{{ image.id }}">{{ image.id }}</td>
|
<table class="images">
|
||||||
<td><span am-time-ago="parseDate(image.created)"></span></td>
|
<thead>
|
||||||
<td ng-bind-html-unsafe="getCommentFirstLine(image.comment)"></td>
|
<tr>
|
||||||
</tr>
|
<td>ID</td>
|
||||||
</table>
|
<td>Created</td>
|
||||||
|
<td>Comment</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tr ng-repeat="image in imageHistory">
|
||||||
|
<td class="image-id" title="{{ image.id }}">{{ image.id }}</td>
|
||||||
|
<td><span am-time-ago="parseDate(image.created)"></span></td>
|
||||||
|
<td ng-bind-html-unsafe="getCommentFirstLine(image.comment)"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
<div class="collapse navbar-collapse navbar-ex1-collapse">
|
<div class="collapse navbar-collapse navbar-ex1-collapse">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
<li><a ng-href="#/repository/">Repositories</a></li>
|
<li><a ng-href="#/repository/">Repositories</a></li>
|
||||||
|
<li><a ng-href="#/guide/">Getting Started Guide</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
Reference in a new issue