Merge branch 'master' of github.com:coreos-inc/quay
This commit is contained in:
commit
67208046bc
16 changed files with 416 additions and 134 deletions
|
@ -2,6 +2,7 @@
|
|||
|
||||
The following are features that have been merged, but not yet deployed:
|
||||
|
||||
- Add list/table view to the repositories page (#116)
|
||||
- Add ability to disable users via the superuser panel (#26)
|
||||
- Add a Changelog view to the superuser panel (#186)
|
||||
|
||||
|
|
|
@ -330,29 +330,39 @@ def _list_entity_robots(entity_name):
|
|||
.where(User.robot == True, User.username ** (entity_name + '+%')))
|
||||
|
||||
|
||||
class _TupleWrapper(object):
|
||||
def __init__(self, data, fields):
|
||||
self._data = data
|
||||
self._fields = fields
|
||||
|
||||
def get(self, field):
|
||||
return self._data[self._fields.index(field.name + ':' + field.model_class.__name__)]
|
||||
|
||||
|
||||
class TupleSelector(object):
|
||||
""" Helper class for selecting tuples from a peewee query and easily accessing
|
||||
them as if they were objects.
|
||||
"""
|
||||
class _TupleWrapper(object):
|
||||
def __init__(self, data, fields):
|
||||
self._data = data
|
||||
self._fields = fields
|
||||
|
||||
def get(self, field):
|
||||
return self._data[self._fields.index(TupleSelector.tuple_reference_key(field))]
|
||||
|
||||
@classmethod
|
||||
def tuple_reference_key(cls, field):
|
||||
""" Returns a string key for referencing a field in a TupleSelector. """
|
||||
if field._node_type == 'func':
|
||||
return field.name + ','.join([cls.tuple_reference_key(arg) for arg in field.arguments])
|
||||
|
||||
if field._node_type == 'field':
|
||||
return field.name + ':' + field.model_class.__name__
|
||||
|
||||
raise Exception('Unknown field type %s in TupleSelector' % field._node_type)
|
||||
|
||||
def __init__(self, query, fields):
|
||||
self._query = query.select(*fields).tuples()
|
||||
self._fields = [field.name + ':' + field.model_class.__name__ for field in fields]
|
||||
self._fields = [TupleSelector.tuple_reference_key(field) for field in fields]
|
||||
|
||||
def __iter__(self):
|
||||
return self._build_iterator()
|
||||
|
||||
def _build_iterator(self):
|
||||
for tuple_data in self._query:
|
||||
yield _TupleWrapper(tuple_data, self._fields)
|
||||
yield TupleSelector._TupleWrapper(tuple_data, self._fields)
|
||||
|
||||
|
||||
|
||||
|
@ -934,22 +944,22 @@ def get_user_teams_within_org(username, organization):
|
|||
User.username == username)
|
||||
|
||||
|
||||
def get_visible_repository_count(username=None, include_public=True,
|
||||
namespace=None):
|
||||
query = _visible_repository_query(username=username,
|
||||
include_public=include_public,
|
||||
namespace=namespace)
|
||||
return query.count()
|
||||
|
||||
|
||||
def get_visible_repositories(username=None, include_public=True, page=None,
|
||||
limit=None, sort=False, namespace=None, namespace_only=False):
|
||||
limit=None, namespace=None, namespace_only=False,
|
||||
include_actions=False, include_latest_tag=False):
|
||||
|
||||
fields = [Repository.name, Repository.id, Repository.description, Visibility.name,
|
||||
Namespace.username]
|
||||
|
||||
if include_actions:
|
||||
fields.append(fn.Max(RepositoryActionCount.count))
|
||||
|
||||
if include_latest_tag:
|
||||
fields.append(fn.Max(RepositoryTag.lifetime_start_ts))
|
||||
|
||||
query = _visible_repository_query(username=username, include_public=include_public, page=page,
|
||||
limit=limit, namespace=namespace,
|
||||
select_models=[Repository, Namespace, Visibility])
|
||||
|
||||
if sort:
|
||||
query = query.order_by(Repository.id.desc())
|
||||
select_models=fields)
|
||||
|
||||
if limit:
|
||||
query = query.limit(limit)
|
||||
|
@ -957,7 +967,24 @@ def get_visible_repositories(username=None, include_public=True, page=None,
|
|||
if namespace and namespace_only:
|
||||
query = query.where(Namespace.username == namespace)
|
||||
|
||||
return query
|
||||
if include_actions:
|
||||
# Filter the join to recent entries only.
|
||||
last_week = datetime.now() - timedelta(weeks=1)
|
||||
join_query = ((RepositoryActionCount.repository == Repository.id) &
|
||||
(RepositoryActionCount.date >= last_week))
|
||||
|
||||
query = (query.switch(Repository)
|
||||
.join(RepositoryActionCount, JOIN_LEFT_OUTER, on=join_query)
|
||||
.group_by(RepositoryActionCount.repository, Repository.name, Repository.id,
|
||||
Repository.description, Visibility.name, Namespace.username))
|
||||
|
||||
if include_latest_tag:
|
||||
query = (query.switch(Repository)
|
||||
.join(RepositoryTag, JOIN_LEFT_OUTER)
|
||||
.group_by(RepositoryTag.repository, Repository.name, Repository.id,
|
||||
Repository.description, Visibility.name, Namespace.username))
|
||||
|
||||
return TupleSelector(query, fields)
|
||||
|
||||
|
||||
def _visible_repository_query(username=None, include_public=True, limit=None,
|
||||
|
@ -1101,7 +1128,7 @@ def get_matching_repositories(repo_term, username=None, limit=10, include_public
|
|||
namespace_term = repo_term
|
||||
name_term = repo_term
|
||||
|
||||
visible = get_visible_repositories(username, include_public=include_public)
|
||||
visible = _visible_repository_query(username, include_public=include_public)
|
||||
|
||||
search_clauses = (Repository.name ** ('%' + name_term + '%') |
|
||||
Namespace.username ** ('%' + namespace_term + '%'))
|
||||
|
|
|
@ -3,11 +3,16 @@
|
|||
import logging
|
||||
import json
|
||||
import datetime
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from flask import request
|
||||
|
||||
from data import model
|
||||
from data.model import Namespace
|
||||
from data.database import (Repository as RepositoryTable, Visibility, RepositoryTag,
|
||||
RepositoryActionCount, fn)
|
||||
|
||||
from endpoints.api import (truthy_bool, format_date, nickname, log_action, validate_json_request,
|
||||
require_repo_read, require_repo_write, require_repo_admin,
|
||||
RepositoryParamResource, resource, query_param, parse_args, ApiResource,
|
||||
|
@ -109,11 +114,12 @@ class RepositoryList(ApiResource):
|
|||
type=truthy_bool, default=True)
|
||||
@query_param('private', 'Whether to include private repositories.', type=truthy_bool,
|
||||
default=True)
|
||||
@query_param('sort', 'Whether to sort the results.', type=truthy_bool, default=False)
|
||||
@query_param('count', 'Whether to include a count of the total number of results available.',
|
||||
type=truthy_bool, default=False)
|
||||
@query_param('namespace_only', 'Whether to limit only to the given namespace.',
|
||||
type=truthy_bool, default=False)
|
||||
@query_param('last_modified', 'Whether to include when the repository was last modified.',
|
||||
type=truthy_bool, default=False)
|
||||
@query_param('popularity', 'Whether to include the repository\'s popularity metric.',
|
||||
type=truthy_bool, default=False)
|
||||
def get(self, args):
|
||||
"""Fetch the list of repositories under a variety of situations."""
|
||||
username = None
|
||||
|
@ -126,25 +132,31 @@ class RepositoryList(ApiResource):
|
|||
|
||||
response = {}
|
||||
|
||||
repo_count = None
|
||||
if args['count']:
|
||||
repo_count = model.get_visible_repository_count(username, include_public=args['public'],
|
||||
namespace=args['namespace'])
|
||||
response['count'] = repo_count
|
||||
|
||||
repo_query = model.get_visible_repositories(username, limit=args['limit'], page=args['page'],
|
||||
include_public=args['public'], sort=args['sort'],
|
||||
repo_query = model.get_visible_repositories(username,
|
||||
limit=args['limit'],
|
||||
page=args['page'],
|
||||
include_public=args['public'],
|
||||
namespace=args['namespace'],
|
||||
namespace_only=args['namespace_only'])
|
||||
namespace_only=args['namespace_only'],
|
||||
include_latest_tag=args['last_modified'],
|
||||
include_actions=args['popularity'])
|
||||
def repo_view(repo_obj):
|
||||
repo = {
|
||||
'namespace': repo_obj.namespace_user.username,
|
||||
'name': repo_obj.name,
|
||||
'description': repo_obj.description,
|
||||
'is_public': repo_obj.visibility.name == 'public',
|
||||
'namespace': repo_obj.get(Namespace.username),
|
||||
'name': repo_obj.get(RepositoryTable.name),
|
||||
'description': repo_obj.get(RepositoryTable.description),
|
||||
'is_public': repo_obj.get(Visibility.name) == 'public'
|
||||
}
|
||||
|
||||
if args['last_modified']:
|
||||
repo['last_modified'] = repo_obj.get(fn.Max(RepositoryTag.lifetime_start_ts))
|
||||
|
||||
if args['popularity']:
|
||||
repo['popularity'] = repo_obj.get(fn.Max(RepositoryActionCount.count)) or 0
|
||||
|
||||
if get_authenticated_user():
|
||||
repo['is_starred'] = repo_obj.id in star_lookup
|
||||
repo['is_starred'] = repo_obj.get(RepositoryTable.id) in star_lookup
|
||||
|
||||
return repo
|
||||
|
||||
response['repositories'] = [repo_view(repo) for repo in repo_query]
|
||||
|
|
32
static/css/directives/ui/repo-list-table.css
Normal file
32
static/css/directives/ui/repo-list-table.css
Normal file
|
@ -0,0 +1,32 @@
|
|||
.repo-list-table {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.repo-list-table .repo-name-icon .avatar {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.repo-list-table .repo-name-icon .namespace {
|
||||
color: #444;
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
.repo-list-table .repo-name-icon .namespace:after {
|
||||
content: " / ";
|
||||
}
|
||||
|
||||
.repo-list-table .empty {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.repo-list-table .last-modified {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.repo-list-table .strength-indicator {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.repo-list-table .popularity {
|
||||
line-height: 10px;
|
||||
}
|
42
static/css/directives/ui/strength-indicator.css
Normal file
42
static/css/directives/ui/strength-indicator.css
Normal file
|
@ -0,0 +1,42 @@
|
|||
.strength-indicator .indicator-sliver {
|
||||
margin: 1px;
|
||||
width: 14px;
|
||||
height: 3px;
|
||||
border: 1px solid #D5D5D5;
|
||||
transition: 0.5s ease;
|
||||
}
|
||||
|
||||
.strength-indicator .strength-indicator-element.good .indicator-sliver {
|
||||
background: green;
|
||||
border: 1px solid green;
|
||||
}
|
||||
|
||||
.strength-indicator .strength-indicator-element.fair .indicator-sliver {
|
||||
background: orange;
|
||||
border: 1px solid orange;
|
||||
}
|
||||
|
||||
.strength-indicator .strength-indicator-element.fair .indicator-sliver:last-child {
|
||||
border: 1px solid #D5D5D5;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.strength-indicator .strength-indicator-element.barely .indicator-sliver {
|
||||
background: rgb(255, 61, 0);
|
||||
border: 1px solid rgb(255, 61, 0);
|
||||
}
|
||||
|
||||
.strength-indicator .strength-indicator-element.barely .indicator-sliver:last-child {
|
||||
border: 1px solid #D5D5D5;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.strength-indicator .strength-indicator-element.barely .indicator-sliver:nth-child(3) {
|
||||
border: 1px solid #D5D5D5;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.strength-indicator .strength-indicator-element.poor .indicator-sliver:first-child {
|
||||
border: 1px solid red;
|
||||
background: red;
|
||||
}
|
|
@ -1,6 +1,11 @@
|
|||
.repo-list .repo-list-panel {
|
||||
padding: 20px;
|
||||
padding-top: 0px;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.repo-list .repo-list-panel .btn-group {
|
||||
float: right;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.repo-list .repo-list-namespaces h4 {
|
||||
|
|
|
@ -3943,54 +3943,11 @@ pre.command:before {
|
|||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
.location-view .ping-tower {
|
||||
.location-view .strength-indicator {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.location-view .ping-sliver {
|
||||
margin: 1px;
|
||||
width: 14px;
|
||||
height: 3px;
|
||||
border: 1px solid #D5D5D5;
|
||||
transition: 0.5s ease;
|
||||
}
|
||||
|
||||
.location-view .ping-tower.good .ping-sliver {
|
||||
background: green;
|
||||
border: 1px solid green;
|
||||
}
|
||||
|
||||
.location-view .ping-tower.fair .ping-sliver {
|
||||
background: orange;
|
||||
border: 1px solid orange;
|
||||
}
|
||||
|
||||
.location-view .ping-tower.fair .ping-sliver:first-child {
|
||||
border: 1px solid #D5D5D5;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.location-view .ping-tower.barely .ping-sliver {
|
||||
background: rgb(255, 61, 0);
|
||||
border: 1px solid rgb(255, 61, 0);
|
||||
}
|
||||
|
||||
.location-view .ping-tower.barely .ping-sliver:first-child {
|
||||
border: 1px solid #D5D5D5;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.location-view .ping-tower.barely .ping-sliver:nth-child(2) {
|
||||
border: 1px solid #D5D5D5;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.location-view .ping-tower.poor .ping-sliver:last-child {
|
||||
border: 1px solid red;
|
||||
background: red;
|
||||
}
|
||||
|
||||
/*
|
||||
This is the visible area of you carousel.
|
||||
Set a width here to define how much items are visible.
|
||||
|
|
|
@ -3,10 +3,5 @@
|
|||
<img ng-src="{{ '/static/img/flags/' + getLocationImage(location) }}">
|
||||
</span>
|
||||
|
||||
<span class="ping-tower" ng-class="locationPingClass">
|
||||
<div class="ping-sliver"></div>
|
||||
<div class="ping-sliver"></div>
|
||||
<div class="ping-sliver"></div>
|
||||
<div class="ping-sliver"></div>
|
||||
</span>
|
||||
<span class="strength-indicator" value="1000 - (locationPing || 0)" maximum="1000"></span>
|
||||
</span>
|
||||
|
|
49
static/directives/repo-list-table.html
Normal file
49
static/directives/repo-list-table.html
Normal file
|
@ -0,0 +1,49 @@
|
|||
<div class="repo-list-table-element">
|
||||
<table class="co-table">
|
||||
<thead>
|
||||
<td class="hidden-xs"
|
||||
ng-class="tablePredicateClass('full_name', options.predicate, options.reverse)">
|
||||
<a href="javascript:void(0)" ng-click="orderBy('full_name')">Repository Name</a>
|
||||
</td>
|
||||
<td class="hidden-xs"
|
||||
ng-class="tablePredicateClass('last_modified_datetime', options.predicate, options.reverse)"
|
||||
style="min-width: 120px;">
|
||||
<a href="javascript:void(0)" ng-click="orderBy('last_modified_datetime')">Last Modified</a>
|
||||
</td>
|
||||
<td class="hidden-xs"
|
||||
ng-class="tablePredicateClass('popularity', options.predicate, options.reverse)"
|
||||
style="min-width: 20px;">
|
||||
<a href="javascript:void(0)" ng-click="orderBy('popularity')">Activity</a>
|
||||
</td>
|
||||
<td class="hidden-xs"
|
||||
ng-class="tablePredicateClass('is_starred', options.predicate, options.reverse)"
|
||||
style="width: 70px">
|
||||
<a href="javascript:void(0)" ng-click="orderBy('is_starred')">Star</a>
|
||||
</td>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr ng-repeat="repository in orderedRepositories">
|
||||
<td class="repo-name-icon">
|
||||
<span class="avatar" size="24" data="getAvatarData(repository.namespace)"></span>
|
||||
<a href="/repository/{{ repository.namespace }}/{{ repository.name }}">
|
||||
<span class="namespace">{{ repository.namespace }}</span>
|
||||
<span class="name">{{ repository.name }}</span>
|
||||
</a>
|
||||
</td>
|
||||
<td class="last-modified">
|
||||
<span ng-if="repository.last_modified">
|
||||
{{ repository.last_modified * 1000 | amCalendar }}
|
||||
</span>
|
||||
<span class="empty" ng-if="!repository.last_modified">(Empty Repository)</span>
|
||||
</td>
|
||||
<td class="popularity hidden-xs">
|
||||
<span class="strength-indicator" value="repository.popularity" maximum="maxPopularity"></span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="repo-star" repository="repository"
|
||||
star-toggled="starToggled({'repository': repository})"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
6
static/directives/strength-indicator.html
Normal file
6
static/directives/strength-indicator.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
<span class="strength-indicator-element" ng-class="strengthClass">
|
||||
<span class="indicator-sliver"></span>
|
||||
<span class="indicator-sliver"></span>
|
||||
<span class="indicator-sliver"></span>
|
||||
<span class="indicator-sliver"></span>
|
||||
</span>
|
|
@ -30,7 +30,6 @@ angular.module('quay').directive('locationView', function () {
|
|||
};
|
||||
|
||||
$scope.locationPing = null;
|
||||
$scope.locationPingClass = null;
|
||||
|
||||
$scope.getLocationTooltip = function(location, ping) {
|
||||
var tip = $scope.getLocationTitle(location) + '<br>';
|
||||
|
@ -71,35 +70,6 @@ angular.module('quay').directive('locationView', function () {
|
|||
if (!location) { return; }
|
||||
$scope.getLocationPing(location);
|
||||
});
|
||||
|
||||
$scope.$watch('locationPing', function(locationPing) {
|
||||
if (locationPing == null) {
|
||||
$scope.locationPingClass = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (locationPing < 0) {
|
||||
$scope.locationPingClass = 'error';
|
||||
return;
|
||||
}
|
||||
|
||||
if (locationPing < 100) {
|
||||
$scope.locationPingClass = 'good';
|
||||
return;
|
||||
}
|
||||
|
||||
if (locationPing < 250) {
|
||||
$scope.locationPingClass = 'fair';
|
||||
return;
|
||||
}
|
||||
|
||||
if (locationPing < 500) {
|
||||
$scope.locationPingClass = 'barely';
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.locationPingClass = 'poor';
|
||||
});
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
|
|
102
static/js/directives/ui/repo-list-table.js
Normal file
102
static/js/directives/ui/repo-list-table.js
Normal file
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* An element which displays a table of repositories.
|
||||
*/
|
||||
angular.module('quay').directive('repoListTable', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/repo-list-table.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repositoriesResources': '=repositoriesResources',
|
||||
'namespaces': '=namespaces'
|
||||
},
|
||||
controller: function($scope, $element, $filter) {
|
||||
var orderBy = $filter('orderBy');
|
||||
|
||||
$scope.repositories = null;
|
||||
$scope.orderedRepositories = [];
|
||||
|
||||
$scope.maxPopularity = 0;
|
||||
$scope.options = {
|
||||
'predicate': 'is_starred',
|
||||
'reverse': true
|
||||
};
|
||||
|
||||
var buildOrderedRepositories = function() {
|
||||
if (!$scope.repositories) { return; }
|
||||
var modifier = $scope.options.reverse ? '-' : '';
|
||||
var fields = [modifier + $scope.options.predicate];
|
||||
|
||||
// Secondary ordering by full name.
|
||||
if ($scope.options.predicate != 'full_name') {
|
||||
fields.push('full_name');
|
||||
}
|
||||
|
||||
var ordered = orderBy($scope.repositories, fields, false);
|
||||
$scope.orderedRepositories = ordered;
|
||||
};
|
||||
|
||||
$scope.tablePredicateClass = function(name, predicate, reverse) {
|
||||
if (name != predicate) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return 'current ' + (reverse ? 'reversed' : '');
|
||||
};
|
||||
|
||||
$scope.orderBy = function(predicate) {
|
||||
if (predicate == $scope.options.predicate) {
|
||||
$scope.options.reverse = !$scope.options.reverse;
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.options.reverse = false;
|
||||
$scope.options.predicate = predicate;
|
||||
};
|
||||
|
||||
$scope.getAvatarData = function(namespace) {
|
||||
var found = {};
|
||||
$scope.namespaces.forEach(function(current) {
|
||||
if (current.name == namespace) {
|
||||
found = current.avatar;
|
||||
}
|
||||
});
|
||||
return found;
|
||||
};
|
||||
|
||||
$scope.getStrengthClass = function(value, max, id) {
|
||||
var adjusted = Math.round((value / max) * 5);
|
||||
if (adjusted >= id) {
|
||||
return 'active-' + adjusted;
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
$scope.$watch('options.predicate', buildOrderedRepositories);
|
||||
$scope.$watch('options.reverse', buildOrderedRepositories);
|
||||
|
||||
$scope.$watch('repositoriesResources', function(resources) {
|
||||
$scope.repositories = [];
|
||||
$scope.maxPopularity = 0;
|
||||
|
||||
resources.forEach(function(resource) {
|
||||
(resource.value || []).forEach(function(repository) {
|
||||
var repositoryInfo = $.extend(repository, {
|
||||
'full_name': repository.namespace + '/' + repository.name,
|
||||
'last_modified_datetime': (new Date(repository.last_modified || 0)).valueOf() * (-1)
|
||||
});
|
||||
|
||||
$scope.repositories.push(repositoryInfo);
|
||||
$scope.maxPopularity = Math.max($scope.maxPopularity, repository.popularity);
|
||||
});
|
||||
});
|
||||
|
||||
buildOrderedRepositories();
|
||||
}, /* deep */ true);
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
54
static/js/directives/ui/strength-indicator.js
Normal file
54
static/js/directives/ui/strength-indicator.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* An element which displays the strength of a value (like a signal indicator on a cell phone).
|
||||
*/
|
||||
angular.module('quay').directive('strengthIndicator', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/strength-indicator.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'value': '=value',
|
||||
'maximum': '=maximum'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
$scope.strengthClass = '';
|
||||
|
||||
var calculateClass = function() {
|
||||
if ($scope.value == null || $scope.maximum == null) {
|
||||
$scope.strengthClass = '';
|
||||
return;
|
||||
}
|
||||
|
||||
var value = Math.round(($scope.value / $scope.maximum) * 4);
|
||||
|
||||
if (value <= 0) {
|
||||
$scope.strengthClass = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
if (value <= 1) {
|
||||
$scope.strengthClass = 'poor';
|
||||
return;
|
||||
}
|
||||
|
||||
if (value <= 2) {
|
||||
$scope.strengthClass = 'barely';
|
||||
return;
|
||||
}
|
||||
|
||||
if (value <= 3) {
|
||||
$scope.strengthClass = 'fair';
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.strengthClass = 'good';
|
||||
};
|
||||
|
||||
$scope.$watch('maximum', calculateClass);
|
||||
$scope.$watch('value', calculateClass);
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -16,12 +16,14 @@
|
|||
}]);
|
||||
|
||||
|
||||
function RepoListCtrl($scope, $sanitize, $q, Restangular, UserService, ApiService) {
|
||||
function RepoListCtrl($scope, $sanitize, $q, Restangular, UserService, ApiService, CookieService) {
|
||||
$scope.namespace = null;
|
||||
$scope.page = 1;
|
||||
$scope.publicPageCount = null;
|
||||
$scope.allRepositories = {};
|
||||
$scope.loading = true;
|
||||
$scope.resources = [];
|
||||
$scope.showAsList = CookieService.get('quay.repoview') == 'list';
|
||||
|
||||
// When loading the UserService, if the user is logged in, create a list of
|
||||
// relevant namespaces and collect the relevant repositories.
|
||||
|
@ -48,6 +50,11 @@
|
|||
}
|
||||
});
|
||||
|
||||
$scope.setShowAsList = function(value) {
|
||||
$scope.showAsList = value;
|
||||
CookieService.putPermanent('quay.repoview', value ? 'list' : 'grid');
|
||||
};
|
||||
|
||||
$scope.isOrganization = function(namespace) {
|
||||
return !!UserService.getOrganization(namespace);
|
||||
};
|
||||
|
@ -97,11 +104,15 @@
|
|||
'public': false,
|
||||
'sort': true,
|
||||
'namespace': namespace.name,
|
||||
'last_modified': true,
|
||||
'popularity': true
|
||||
};
|
||||
|
||||
namespace.repositories = ApiService.listReposAsResource().withOptions(options).get(function(resp) {
|
||||
return resp.repositories.map(findDuplicateRepo);
|
||||
});
|
||||
|
||||
$scope.resources.push(namespace.repositories);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -52,23 +52,37 @@
|
|||
|
||||
<div class="col-lg-9 col-lg-pull-3 col-md-9 col-md-pull-3 col-sm-12">
|
||||
<div class="repo-list-panel co-main-content-panel">
|
||||
<!-- Starred Repository Listing -->
|
||||
<div class="repo-list-grid" repositories-resource="starred_repositories"
|
||||
starred="true"
|
||||
star-toggled="starToggled(repository)">
|
||||
<div class="repo-list-toggleb btn-group">
|
||||
<i class="btn btn-default fa fa-th-large" ng-class="!showAsList ? 'active' : ''"
|
||||
ng-click="setShowAsList(false)" title="Grid View" data-container="body" bs-tooltip></i>
|
||||
<i class="btn btn-default fa fa-th-list" ng-class="showAsList ? 'active' : ''"
|
||||
ng-click="setShowAsList(true)" title="List View" data-container="body" bs-tooltip></i>
|
||||
</div>
|
||||
|
||||
<!-- User and Org Repository Listings -->
|
||||
<div ng-repeat="namespace in namespaces">
|
||||
<div class="repo-list-grid" repositories-resource="namespace.repositories"
|
||||
starred="false" user="user" namespace="namespace"
|
||||
<!-- Table View -->
|
||||
<div ng-if="showAsList">
|
||||
<div class="repo-list-table" repositories-resources="resources" namespaces="namespaces"></div>
|
||||
</div>
|
||||
|
||||
<!-- Grid View -->
|
||||
<div ng-if="!showAsList">
|
||||
<!-- Starred Repository Listing -->
|
||||
<div class="repo-list-grid" repositories-resource="starred_repositories"
|
||||
starred="true"
|
||||
star-toggled="starToggled(repository)">
|
||||
</div>
|
||||
|
||||
<!-- User and Org Repository Listings -->
|
||||
<div ng-repeat="namespace in namespaces">
|
||||
<div class="repo-list-grid" repositories-resource="namespace.repositories"
|
||||
starred="false" user="user" namespace="namespace"
|
||||
star-toggled="starToggled(repository)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1244,6 +1244,11 @@ class TestListRepos(ApiTestCase):
|
|||
|
||||
self.assertEquals(len(json['repositories']), 2)
|
||||
|
||||
def test_action_last_modified(self):
|
||||
self.login(READ_ACCESS_USER)
|
||||
json = self.getJsonResponse(RepositoryList, params=dict(last_modified=True, popularity=True))
|
||||
self.assertTrue(len(json['repositories']) > 2)
|
||||
|
||||
|
||||
class TestViewPublicRepository(ApiTestCase):
|
||||
def test_normalview(self):
|
||||
|
|
Reference in a new issue