diff --git a/avatars/avatars.py b/avatars/avatars.py
index 40935df10..220cae9cb 100644
--- a/avatars/avatars.py
+++ b/avatars/avatars.py
@@ -1,4 +1,5 @@
import hashlib
+import math
class Avatar(object):
def __init__(self, app=None):
@@ -7,8 +8,7 @@ class Avatar(object):
def _init_app(self, app):
return AVATAR_CLASSES[app.config.get('AVATAR_KIND', 'Gravatar')](
- app.config['SERVER_HOSTNAME'],
- app.config['PREFERRED_URL_SCHEME'])
+ app.config['PREFERRED_URL_SCHEME'], app.config['AVATAR_COLORS'], app.config['HTTPCLIENT'])
def __getattr__(self, name):
return getattr(self.state, name, None)
@@ -16,48 +16,83 @@ class Avatar(object):
class BaseAvatar(object):
""" Base class for all avatar implementations. """
- def __init__(self, server_hostname, preferred_url_scheme):
- self.server_hostname = server_hostname
+ def __init__(self, preferred_url_scheme, colors, http_client):
self.preferred_url_scheme = preferred_url_scheme
+ self.colors = colors
+ self.http_client = http_client
- def get_url(self, email, size=16, name=None):
- """ Returns the full URL for viewing the avatar of the given email address, with
- an optional size.
+ def get_mail_html(self, name, email_or_id, size=16, kind='user'):
+ """ Returns the full HTML and CSS for viewing the avatar of the given name and email address,
+ with an optional size.
"""
- raise NotImplementedError
+ data = self.get_data(name, email_or_id, kind)
+ url = self._get_url(data['hash'], size) if kind != 'team' else None
+ font_size = size - 6
- def compute_hash(self, email, name=None):
- """ Computes the avatar hash for the given email address. If the name is given and a default
- avatar is being computed, the name can be used in place of the email address. """
- raise NotImplementedError
+ if url is not None:
+ # Try to load the gravatar. If we get a non-404 response, then we use it in place of
+ # the CSS avatar.
+ response = self.http_client.get(url)
+ if response.status_code == 200:
+ return """""" % (url, size, size, kind)
+
+ radius = '50%' if kind == 'team' else '0%'
+ letter = 'Ω' if kind == 'team' and data['name'] == 'owners' else data['name'].upper()[0]
+
+ return """
+
+ %s
+
+""" % (size, size, data['color'], font_size, size, radius, letter)
+
+ def get_data_for_user(self, user):
+ return self.get_data(user.username, user.email, 'robot' if user.robot else 'user')
+
+ def get_data_for_team(self, team):
+ return self.get_data(team.name, team.name, 'team')
+
+ def get_data_for_org(self, org):
+ return self.get_data(org.username, org.email, 'org')
+
+ def get_data(self, name, email_or_id, kind='user'):
+ """ Computes and returns the full data block for the avatar:
+ {
+ 'name': name,
+ 'hash': The gravatar hash, if any.
+ 'color': The color for the avatar
+ }
+ """
+ colors = self.colors
+ hash_value = hashlib.md5(email_or_id.strip().lower()).hexdigest()
+
+ byte_count = int(math.ceil(math.log(len(colors), 16)))
+ byte_data = hash_value[0:byte_count]
+ hash_color = colors[int(byte_data, 16) % len(colors)]
+
+ return {
+ 'name': name,
+ 'hash': hash_value,
+ 'color': hash_color,
+ 'kind': kind
+ }
+
+ def _get_url(self, hash_value, size):
+ """ Returns the URL for displaying the overlay avatar. """
+ return None
class GravatarAvatar(BaseAvatar):
""" Avatar system that uses gravatar for generating avatars. """
- def compute_hash(self, email, name=None):
- email = email or ""
- return hashlib.md5(email.strip().lower()).hexdigest()
-
- def get_url(self, email, size=16, name=None):
- computed = self.compute_hash(email, name=name)
- return '%s://www.gravatar.com/avatar/%s?d=identicon&size=%s' % (self.preferred_url_scheme,
- computed, size)
+ def _get_url(self, hash_value, size=16):
+ return '%s://www.gravatar.com/avatar/%s?d=404&size=%s' % (self.preferred_url_scheme,
+ hash_value, size)
class LocalAvatar(BaseAvatar):
""" Avatar system that uses the local system for generating avatars. """
- def compute_hash(self, email, name=None):
- email = email or ""
- if not name and not email:
- return ''
-
- prefix = name if name else email
- return prefix[0] + hashlib.md5(email.strip().lower()).hexdigest()
-
- def get_url(self, email, size=16, name=None):
- computed = self.compute_hash(email, name=name)
- return '%s://%s/avatar/%s?size=%s' % (self.preferred_url_scheme, self.server_hostname,
- computed, size)
-
+ pass
AVATAR_CLASSES = {
'gravatar': GravatarAvatar,
diff --git a/config.py b/config.py
index 339ffca34..e33c8bc46 100644
--- a/config.py
+++ b/config.py
@@ -45,8 +45,6 @@ class DefaultConfig(object):
PREFERRED_URL_SCHEME = 'http'
SERVER_HOSTNAME = 'localhost:5000'
- AVATAR_KIND = 'local'
-
REGISTRY_TITLE = 'CoreOS Enterprise Registry'
REGISTRY_TITLE_SHORT = 'Enterprise Registry'
@@ -201,3 +199,11 @@ class DefaultConfig(object):
# Signed registry grant token expiration in seconds
SIGNED_GRANT_EXPIRATION_SEC = 60 * 60 * 24 # One day to complete a push/pull
+
+ # The various avatar background colors.
+ AVATAR_KIND = 'local'
+ AVATAR_COLORS = ['#969696', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728',
+ '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2',
+ '#7f7f7f', '#c7c7c7', '#bcbd22', '#1f77b4', '#17becf', '#9edae5', '#393b79',
+ '#5254a3', '#6b6ecf', '#9c9ede', '#9ecae1', '#31a354', '#b5cf6b', '#a1d99b',
+ '#8c6d31', '#ad494a', '#e7ba52', '#a55194']
diff --git a/data/model/legacy.py b/data/model/legacy.py
index aa968408c..8d5a7d656 100644
--- a/data/model/legacy.py
+++ b/data/model/legacy.py
@@ -654,13 +654,13 @@ def get_matching_users(username_prefix, robot_namespace=None,
(User.robot == True)))
query = (User
- .select(User.username, User.robot)
- .group_by(User.username, User.robot)
+ .select(User.username, User.email, User.robot)
+ .group_by(User.username, User.email, User.robot)
.where(direct_user_query))
if organization:
query = (query
- .select(User.username, User.robot, fn.Sum(Team.id))
+ .select(User.username, User.email, User.robot, fn.Sum(Team.id))
.join(TeamMember, JOIN_LEFT_OUTER)
.join(Team, JOIN_LEFT_OUTER, on=((Team.id == TeamMember.team) &
(Team.organization == organization))))
@@ -669,9 +669,11 @@ def get_matching_users(username_prefix, robot_namespace=None,
class MatchingUserResult(object):
def __init__(self, *args):
self.username = args[0]
- self.is_robot = args[1]
+ self.email = args[1]
+ self.robot = args[2]
+
if organization:
- self.is_org_member = (args[2] != None)
+ self.is_org_member = (args[3] != None)
else:
self.is_org_member = None
@@ -1038,7 +1040,8 @@ def get_all_repo_teams(namespace_name, repository_name):
def get_all_repo_users(namespace_name, repository_name):
- return (RepositoryPermission.select(User.username, User.robot, Role.name, RepositoryPermission)
+ return (RepositoryPermission.select(User.username, User.email, User.robot, Role.name,
+ RepositoryPermission)
.join(User)
.switch(RepositoryPermission)
.join(Role)
diff --git a/emails/teaminvite.html b/emails/teaminvite.html
index 3d8ff9c14..128bbe00f 100644
--- a/emails/teaminvite.html
+++ b/emails/teaminvite.html
@@ -4,7 +4,7 @@
Invitation to join team: {{ teamname }}
-{{ inviter | user_reference }} has invited you to join the team {{ teamname }} under organization {{ organization | user_reference }}.
+{{ inviter | user_reference }} has invited you to join the team {{ teamname | team_reference }} under organization {{ organization | user_reference }}.
-
\ No newline at end of file
+
diff --git a/static/directives/cor-confirm-dialog.html b/static/directives/cor-confirm-dialog.html
index 330729390..c6aa9d2fd 100644
--- a/static/directives/cor-confirm-dialog.html
+++ b/static/directives/cor-confirm-dialog.html
@@ -22,4 +22,4 @@
-
\ No newline at end of file
+
diff --git a/static/directives/cor-floating-bottom-bar.html b/static/directives/cor-floating-bottom-bar.html
index 2e5337fd2..11615e6a8 100644
--- a/static/directives/cor-floating-bottom-bar.html
+++ b/static/directives/cor-floating-bottom-bar.html
@@ -1,3 +1,3 @@
-
\ No newline at end of file
+
diff --git a/static/directives/cor-loader-inline.html b/static/directives/cor-loader-inline.html
index 39ffb5b99..3a2c42c1d 100644
--- a/static/directives/cor-loader-inline.html
+++ b/static/directives/cor-loader-inline.html
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/static/directives/cor-loader.html b/static/directives/cor-loader.html
index 112680a22..f0aab7afc 100644
--- a/static/directives/cor-loader.html
+++ b/static/directives/cor-loader.html
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/static/directives/cor-log-box.html b/static/directives/cor-log-box.html
index c5442d0f7..6d3157db3 100644
--- a/static/directives/cor-log-box.html
+++ b/static/directives/cor-log-box.html
@@ -8,4 +8,4 @@
New Logs
-
\ No newline at end of file
+
diff --git a/static/directives/cor-option.html b/static/directives/cor-option.html
index 0eb57170b..727e3dda3 100644
--- a/static/directives/cor-option.html
+++ b/static/directives/cor-option.html
@@ -1,3 +1,3 @@
-
\ No newline at end of file
+
diff --git a/static/directives/cor-options-menu.html b/static/directives/cor-options-menu.html
index 8b6cf1e26..7e5f43cc3 100644
--- a/static/directives/cor-options-menu.html
+++ b/static/directives/cor-options-menu.html
@@ -3,4 +3,4 @@
-
\ No newline at end of file
+
diff --git a/static/directives/cor-step-bar.html b/static/directives/cor-step-bar.html
index 274a2c924..117f8185d 100644
--- a/static/directives/cor-step-bar.html
+++ b/static/directives/cor-step-bar.html
@@ -1,3 +1,3 @@
-
\ No newline at end of file
+
diff --git a/static/directives/cor-step.html b/static/directives/cor-step.html
index 5339db30e..acc9baee4 100644
--- a/static/directives/cor-step.html
+++ b/static/directives/cor-step.html
@@ -3,4 +3,4 @@
{{ text }}
-
\ No newline at end of file
+
diff --git a/static/directives/cor-tab-content.html b/static/directives/cor-tab-content.html
index 997ae5af1..747ccb2c8 100644
--- a/static/directives/cor-tab-content.html
+++ b/static/directives/cor-tab-content.html
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/static/directives/cor-tab-panel.html b/static/directives/cor-tab-panel.html
index f92d683ab..d041c9466 100644
--- a/static/directives/cor-tab-panel.html
+++ b/static/directives/cor-tab-panel.html
@@ -1,3 +1,3 @@
-
\ No newline at end of file
+
diff --git a/static/directives/cor-tab.html b/static/directives/cor-tab.html
index 61c8b327f..07d4e0e92 100644
--- a/static/directives/cor-tab.html
+++ b/static/directives/cor-tab.html
@@ -10,4 +10,4 @@
-
\ No newline at end of file
+
diff --git a/static/directives/cor-tabs.html b/static/directives/cor-tabs.html
index 1a965932e..5ab85ecb1 100644
--- a/static/directives/cor-tabs.html
+++ b/static/directives/cor-tabs.html
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/static/directives/cor-title-content.html b/static/directives/cor-title-content.html
index 6acbe47b3..5b2077d08 100644
--- a/static/directives/cor-title-content.html
+++ b/static/directives/cor-title-content.html
@@ -1,3 +1,3 @@
-
\ No newline at end of file
+
diff --git a/static/directives/quay-service-status-bar.html b/static/directives/quay-service-status-bar.html
index ffec2e204..4a96f5b65 100644
--- a/static/directives/quay-service-status-bar.html
+++ b/static/directives/quay-service-status-bar.html
@@ -10,4 +10,4 @@
{{ scheduled.name }}
-
\ No newline at end of file
+
diff --git a/static/directives/quay-service-status.html b/static/directives/quay-service-status.html
index b7e77ef96..824af4321 100644
--- a/static/directives/quay-service-status.html
+++ b/static/directives/quay-service-status.html
@@ -3,4 +3,4 @@
ng-if="indicator != 'loading'">{{ description }}
-
\ No newline at end of file
+
diff --git a/static/directives/realtime-area-chart.html b/static/directives/realtime-area-chart.html
index d42d46784..b6d16741c 100644
--- a/static/directives/realtime-area-chart.html
+++ b/static/directives/realtime-area-chart.html
@@ -3,4 +3,4 @@
-
\ No newline at end of file
+
diff --git a/static/directives/realtime-line-chart.html b/static/directives/realtime-line-chart.html
index 74e8f748c..36c0602c5 100644
--- a/static/directives/realtime-line-chart.html
+++ b/static/directives/realtime-line-chart.html
@@ -3,4 +3,4 @@
-
\ No newline at end of file
+
diff --git a/static/directives/repo-list-grid.html b/static/directives/repo-list-grid.html
index 88a58ad54..14e2308c0 100644
--- a/static/directives/repo-list-grid.html
+++ b/static/directives/repo-list-grid.html
@@ -8,7 +8,7 @@
Starred
-
+
{{ namespace.name }}{{ namespace.name }}
diff --git a/static/directives/repo-star.html b/static/directives/repo-star.html
index 1152b8338..198cdcd83 100644
--- a/static/directives/repo-star.html
+++ b/static/directives/repo-star.html
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/static/directives/repository-events-table.html b/static/directives/repository-events-table.html
index 5c3f7b87f..6dd328d61 100644
--- a/static/directives/repository-events-table.html
+++ b/static/directives/repository-events-table.html
@@ -72,4 +72,4 @@
repository="repository"
counter="showNewNotificationCounter"
notification-created="handleNotificationCreated(notification)">
-
\ No newline at end of file
+
diff --git a/static/directives/repository-permissions-table.html b/static/directives/repository-permissions-table.html
index 22f3e01b3..96aee36c5 100644
--- a/static/directives/repository-permissions-table.html
+++ b/static/directives/repository-permissions-table.html
@@ -15,7 +15,8 @@
@@ -79,4 +81,4 @@
dialog-action-title="Grant Permission">
The selected user is outside of your organization. Are you sure you want to grant the user access to this repository?
-
\ No newline at end of file
+
diff --git a/static/directives/repository-tokens-table.html b/static/directives/repository-tokens-table.html
index ddad74db5..bfb47fdaf 100644
--- a/static/directives/repository-tokens-table.html
+++ b/static/directives/repository-tokens-table.html
@@ -32,4 +32,4 @@
-
\ No newline at end of file
+
diff --git a/static/directives/source-commit-link.html b/static/directives/source-commit-link.html
index d8803b319..5bd3b187a 100644
--- a/static/directives/source-commit-link.html
+++ b/static/directives/source-commit-link.html
@@ -1,4 +1,4 @@
{{ commitSha.substring(0, 8) }}
-
\ No newline at end of file
+
diff --git a/static/directives/source-ref-link.html b/static/directives/source-ref-link.html
index dcf142001..986216aa8 100644
--- a/static/directives/source-ref-link.html
+++ b/static/directives/source-ref-link.html
@@ -12,4 +12,4 @@
{{ getTitle(ref) }}
-
\ No newline at end of file
+
diff --git a/static/directives/tag-operations-dialog.html b/static/directives/tag-operations-dialog.html
index d47d4651c..d79479d84 100644
--- a/static/directives/tag-operations-dialog.html
+++ b/static/directives/tag-operations-dialog.html
@@ -84,4 +84,4 @@
The following images and any other images not referenced by a tag will be deleted:
-
\ No newline at end of file
+
diff --git a/static/directives/teams-manager.html b/static/directives/teams-manager.html
index 7cb47dba8..8191b350f 100644
--- a/static/directives/teams-manager.html
+++ b/static/directives/teams-manager.html
@@ -1,31 +1,62 @@
+
+ submitted="createTeam(value)" ng-show="organization.is_admin">
Create New Team
-
\ No newline at end of file
+
diff --git a/static/partials/image-view.html b/static/partials/image-view.html
index 615361ee3..181938afc 100644
--- a/static/partials/image-view.html
+++ b/static/partials/image-view.html
@@ -56,4 +56,4 @@
-
\ No newline at end of file
+
diff --git a/static/partials/landing-login.html b/static/partials/landing-login.html
index 5afb89af2..024762706 100644
--- a/static/partials/landing-login.html
+++ b/static/partials/landing-login.html
@@ -47,7 +47,7 @@
-
+
Welcome {{ user.username }}!
Browse all repositoriesCreate a new repository
diff --git a/static/partials/landing-normal.html b/static/partials/landing-normal.html
index f8b969cd8..7865fdfac 100644
--- a/static/partials/landing-normal.html
+++ b/static/partials/landing-normal.html
@@ -46,7 +46,7 @@
-
+
Welcome {{ user.username }}!
Browse all repositoriesCreate a new repository
diff --git a/static/partials/landing.html b/static/partials/landing.html
index 719d1a9cb..7813e1ab8 100644
--- a/static/partials/landing.html
+++ b/static/partials/landing.html
@@ -1,3 +1,3 @@
-
-
\ No newline at end of file
+
diff --git a/static/partials/organizations.html b/static/partials/organizations.html
index b556ed300..7bcc4f05c 100644
--- a/static/partials/organizations.html
+++ b/static/partials/organizations.html
@@ -23,7 +23,7 @@