Merge branch 'koh'

Conflicts:
	auth/scopes.py
	requirements-nover.txt
	requirements.txt
	static/css/quay.css
	static/directives/namespace-selector.html
	static/js/app.js
	static/partials/manage-application.html
	templates/oauthorize.html
This commit is contained in:
Jimmy Zelinskie 2014-12-01 12:30:09 -08:00
commit f3259c862b
57 changed files with 537 additions and 141 deletions

View file

@ -4264,9 +4264,10 @@ pre.command:before {
display: block !important;
}
.auth-header > img {
.auth-header > .avatar {
float: left;
margin-top: 8px;
display: inline-block;
margin-top: 12px;
margin-right: 20px;
}
@ -4827,7 +4828,7 @@ i.slack-icon {
margin-bottom: 10px;
}
.member-listing .gravatar {
.member-listing .avatar {
vertical-align: middle;
margin-right: 10px;
}
@ -4887,4 +4888,12 @@ i.slack-icon {
half (although it is still not great)
*/
transform: translateZ(0);
}
}
#gen-token table {
margin: 10px;
}
#gen-token input[type="checkbox"] {
margin-right: 10px;
}

View file

@ -1,6 +1,6 @@
<div class="application-info-element" style="padding-bottom: 18px">
<div class="auth-header">
<img src="//www.gravatar.com/avatar/{{ application.gravatar }}?s=48&d=identicon">
<span class="avatar" size="48" hash="application.avatar"></span>
<h2><a href="{{ application.url }}" target="_blank">{{ application.name }}</a></h2>
<h4>
{{ application.organization.name }}

View file

@ -0,0 +1 @@
<img class="avatar-element" ng-src="{{ AvatarService.getAvatar(_hash, size) }}">

View file

@ -7,15 +7,15 @@
</span>
</span>
<span ng-if="entity.kind == 'org'">
<img ng-src="//www.gravatar.com/avatar/{{ entity.gravatar }}?s={{ gravatarSize || '16' }}&amp;d=identicon">
<span class="avatar" size="avatarSize || 16" hash="entity.avatar"></span>
<span class="entity-name">
<span ng-if="!getIsAdmin(entity.name)">{{entity.name}}</span>
<span ng-if="getIsAdmin(entity.name)"><a href="/organization/{{ entity.name }}">{{entity.name}}</a></span>
</span>
</span>
<span ng-if="entity.kind != 'team' && entity.kind != 'org'">
<img class="gravatar" ng-if="showGravatar == 'true' && entity.gravatar" ng-src="//www.gravatar.com/avatar/{{ entity.gravatar }}?s={{ gravatarSize || '16' }}&amp;d=identicon">
<span ng-if="showGravatar != 'true' || !entity.gravatar">
<span class="avatar" size="avatarSize || 16" hash="entity.avatar" ng-if="showAvatar == 'true' && entity.avatar"></span>
<span ng-if="showAvatar != 'true' || !entity.avatar">
<i class="fa fa-user" ng-show="!entity.is_robot" data-title="User" bs-tooltip="tooltip.title" data-container="body"></i>
<i class="fa fa-wrench" ng-show="entity.is_robot" data-title="Robot Account" bs-tooltip="tooltip.title" data-container="body"></i>
</span>

View file

@ -35,7 +35,7 @@
<li class="dropdown" ng-switch-when="false">
<a href="javascript:void(0)" class="dropdown-toggle user-dropdown user-view" data-toggle="dropdown">
<img src="//www.gravatar.com/avatar/{{ user.gravatar }}?s=32&d=identicon" />
<span class="avatar" size="32" hash="user.avatar"></span>
{{ user.username }}
<span class="notifications-bubble"></span>
<b class="caret"></b>

View file

@ -1,19 +1,19 @@
<span class="namespace-selector-dropdown">
<span ng-show="user.organizations.length == 0">
<img src="//www.gravatar.com/avatar/{{ user.gravatar }}?s=24&d=identicon" />
<span class="avatar" size="24" hash="user.avatar"></span>
<span class="namespace-name">{{user.username}}</span>
</span>
<div class="btn-group" ng-show="user.organizations.length > 0">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<img src="//www.gravatar.com/avatar/{{ namespaceObj.gravatar }}?s=16&d=identicon" />
<span class="avatar" size="16" hash="namespaceObj.avatar"></span>
{{namespace}} <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li class="namespace-item" ng-repeat="org in user.organizations"
ng-class="(requireCreate && !namespaces[org.name].can_create_repo) ? 'disabled' : ''">
<a class="namespace" href="javascript:void(0)" ng-click="setNamespace(org)">
<img src="//www.gravatar.com/avatar/{{ org.gravatar }}?s=24&d=identicon" />
<span class="avatar" size="24" hash="org.avatar"></span>
<span class="namespace-name">{{ org.name }}</span>
</a>
@ -25,7 +25,7 @@
<li class="divider"></li>
<li>
<a class="namespace" href="javascript:void(0)" ng-click="setNamespace(user)">
<img src="//www.gravatar.com/avatar/{{ user.gravatar }}?s=24&d=identicon" />
<span class="avatar" size="24" hash="user.avatar"></span>
<span class="namespace-name">{{ user.username }}</span>
</a>
</li>

View file

@ -3,7 +3,7 @@
<div class="circle" ng-class="getClass(notification)"></div>
<div class="message" ng-bind-html="getMessage(notification)"></div>
<div class="orginfo" ng-if="notification.organization">
<img src="//www.gravatar.com/avatar/{{ getGravatar(notification.organization) }}?s=24&d=identicon" />
<span class="avatar" size="24" hash="getAvatar(notification.organization)"></span>
<span class="orgname">{{ notification.organization }}</span>
</div>
</div>

View file

@ -1,5 +1,5 @@
<div class="organization-header-element">
<img src="//www.gravatar.com/avatar/{{ organization.gravatar }}?s=24&amp;d=identicon">
<span class="avatar" size="24" hash="organization.avatar"></span>
<span class="organization-name" ng-show="teamName || clickable">
<a href="/organization/{{ organization.name }}">{{ organization.name }}</a>
</span>

View file

@ -9,7 +9,7 @@
<td>
<div class="current-repo">
<img class="dropdown-select-icon github-org-icon"
ng-src="{{ state.currentRepo.avatar_url ? state.currentRepo.avatar_url : '//www.gravatar.com/avatar/' }}">
ng-src="{{ state.currentRepo.avatar_url ? state.currentRepo.avatar_url : '/static/img/empty.png' }}">
<a ng-href="https://github.com/{{ state.currentRepo.repo }}" target="_blank">{{ state.currentRepo.repo }}</a>
</div>
</td>
@ -53,7 +53,7 @@
<!-- Icons -->
<i class="dropdown-select-icon none-icon fa fa-github fa-lg"></i>
<img class="dropdown-select-icon github-org-icon"
ng-src="{{ state.currentRepo.avatar_url ? state.currentRepo.avatar_url : '//www.gravatar.com/avatar/' }}">
ng-src="{{ state.currentRepo.avatar_url ? state.currentRepo.avatar_url : '/static/image/empty.png' }}">
<!-- Dropdown menu -->
<ul class="dropdown-select-menu scrollable-menu" role="menu">

BIN
static/img/empty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 B

View file

@ -620,6 +620,51 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
return pingService;
}]);
$provide.factory('AvatarService', ['Config', '$sanitize', 'md5',
function(Config, $sanitize, md5) {
var avatarService = {};
var cache = {};
avatarService.getAvatar = function(hash, opt_size) {
var size = opt_size || 16;
switch (Config['AVATAR_KIND']) {
case 'local':
return '/avatar/' + hash + '?size=' + size;
break;
case 'gravatar':
return '//www.gravatar.com/avatar/' + hash + '?d=identicon&size=' + size;
break;
}
};
avatarService.computeHash = function(opt_email, opt_name) {
var email = opt_email || '';
var name = opt_name || '';
var cacheKey = email + ':' + name;
if (!cacheKey) { return '-'; }
if (cache[cacheKey]) {
return cache[cacheKey];
}
var hash = md5.createHash(email.toString().toLowerCase());
switch (Config['AVATAR_KIND']) {
case 'local':
if (name) {
hash = name[0] + hash;
} else if (email) {
hash = email[0] + hash;
}
break;
}
return cache[cacheKey] = hash;
};
return avatarService;
}]);
$provide.factory('TriggerService', ['UtilService', '$sanitize', 'KeyService',
function(UtilService, $sanitize, KeyService) {
@ -1712,6 +1757,12 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
return notificationService;
}]);
$provide.factory('OAuthService', ['$location', 'Config', function($location, Config) {
var oauthService = {};
oauthService.SCOPES = window.__auth_scopes;
return oauthService;
}]);
$provide.factory('KeyService', ['$location', 'Config', function($location, Config) {
var keyService = {}
var oauth = window.__oauth;
@ -2449,8 +2500,8 @@ quayApp.directive('entityReference', function () {
scope: {
'entity': '=entity',
'namespace': '=namespace',
'showGravatar': '@showGravatar',
'gravatarSize': '@gravatarSize'
'showAvatar': '@showAvatar',
'avatarSize': '@avatarSize'
},
controller: function($scope, $element, UserService, UtilService) {
$scope.getIsAdmin = function(namespace) {
@ -4327,8 +4378,7 @@ quayApp.directive('entitySearch', function () {
} else if (datum.entity.kind == 'team') {
template += '<i class="fa fa-group fa-lg"></i>';
} else if (datum.entity.kind == 'org') {
template += '<i class="fa"><img src="//www.gravatar.com/avatar/' +
datum.entity.gravatar + '?s=16&amp;d=identicon"></i>';
template += '<i class="fa">' + AvatarService.getAvatar(datum.entity.avatar, 16) + '</i>';
}
template += '<span class="name">' + datum.value + '</span>';
@ -6068,9 +6118,9 @@ quayApp.directive('notificationView', function () {
return NotificationService.getMessage(notification);
};
$scope.getGravatar = function(orgname) {
$scope.getAvatar = function(orgname) {
var organization = UserService.getOrganization(orgname);
return organization['gravatar'] || '';
return organization['avatar'] || '';
};
$scope.parseDate = function(dateString) {
@ -6452,6 +6502,39 @@ quayApp.directive('locationView', function () {
});
quayApp.directive('avatar', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/avatar.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'hash': '=hash',
'email': '=email',
'name': '=name',
'size': '=size'
},
controller: function($scope, $element, AvatarService) {
$scope.AvatarService = AvatarService;
var refreshHash = function() {
if (!$scope.name && !$scope.email) { return; }
$scope._hash = AvatarService.computeHash($scope.email, $scope.name);
};
$scope.$watch('hash', function(hash) {
$scope._hash = hash;
});
$scope.$watch('name', refreshHash);
$scope.$watch('email', refreshHash);
}
};
return directiveDefinitionObject;
});
quayApp.directive('tagSpecificImagesView', function () {
var directiveDefinitionObject = {
priority: 0,

View file

@ -2697,12 +2697,28 @@ function OrgMemberLogsCtrl($scope, $routeParams, $rootScope, $timeout, Restangul
}
function ManageApplicationCtrl($scope, $routeParams, $rootScope, $location, $timeout, ApiService) {
function ManageApplicationCtrl($scope, $routeParams, $rootScope, $location, $timeout, OAuthService, ApiService, UserService, Config) {
var orgname = $routeParams.orgname;
var clientId = $routeParams.clientid;
$scope.Config = Config;
$scope.OAuthService = OAuthService;
$scope.updating = false;
$scope.genScopes = {};
UserService.updateUserIn($scope);
$scope.getScopes = function(scopes) {
var checked = [];
for (var scopeName in scopes) {
if (scopes.hasOwnProperty(scopeName) && scopes[scopeName]) {
checked.push(scopeName);
}
}
return checked;
};
$scope.askResetClientSecret = function() {
$('#resetSecretModal').modal({});
};
@ -2737,8 +2753,8 @@ function ManageApplicationCtrl($scope, $routeParams, $rootScope, $location, $tim
delete $scope.application['description'];
}
if (!$scope.application['gravatar_email']) {
delete $scope.application['gravatar_email'];
if (!$scope.application['avatar_email']) {
delete $scope.application['avatar_email'];
}
var errorHandler = ApiService.errorDisplay('Could not update application', function(resp) {

View file

@ -58,9 +58,6 @@
<small>Co-Founder</small></h3>
</div>
<div class="col-sm-3 col-md-2">
<img class="img-rounded founder-photo" src="http://www.gravatar.com/avatar/342ea83fd68d33f90b1f06f466d533c6?s=128&d=identicon">
</div>
<div class="col-sm-7 col-md-10">
<p>Jacob graduated from The University of Michigan with a Bachelors in Computer Engineering. From there he allowed his love of flight and mountains to lure him to Seattle where he took a job with Boeing Commercial Airplanes working on the world's most accurate flight simulator. When he realized how much he also loved web development, he moved to Amazon to work on the e-commerce back-end. Finally, desiring to move to New York City, he moved to Google, where he worked on several products related to Google APIs.</p>
</div>
@ -71,9 +68,6 @@
<small>Co-Founder</small></h3>
</div>
<div class="col-sm-3 col-md-2">
<img class="img-rounded founder-photo" src="http://www.gravatar.com/avatar/9fc3232622773fb2e8f71c0027601bc5?s=128&d=mm">
</div>
<div class="col-sm-7 col-md-10">
<p>Joseph graduated from University of Pennsylvania with a Bachelors and Masters in Computer Science. After a record setting (probably) five internships with Google, he took a full time position there to continue his work on exciting products such as Google Spreadsheets, the Google Closure Compiler, and Google APIs. </p>
</div>

View file

@ -47,7 +47,7 @@
<div class="signup-form"></div>
</div>
<div ng-show="!user.anonymous" class="user-welcome">
<img class="gravatar" src="//www.gravatar.com/avatar/{{ user.gravatar }}?s=128&d=identicon" />
<span class="avatar" size="128" hash="user.avatar"></span>
<div class="sub-message">Welcome <b>{{ user.username }}</b>!</div>
<a ng-show="my_repositories.value" class="btn btn-primary" href="/repository/">Browse all repositories</a>
<a class="btn btn-success" href="/new/">Create a new repository</a>

View file

@ -57,7 +57,7 @@
<div class="signup-form"></div>
</div>
<div ng-show="!user.anonymous" class="user-welcome">
<img class="gravatar" src="//www.gravatar.com/avatar/{{ user.gravatar }}?s=128&d=identicon" />
<span class="avatar" size="128" hash="user.avatar"></span>
<div class="sub-message">Welcome <b>{{ user.username }}</b>!</div>
<a ng-show="my_repositories.value" class="btn btn-primary" href="/repository/">Browse all repositories</a>
<a class="btn btn-success" href="/new/">Create a new repository</a>

View file

@ -7,10 +7,10 @@
<div class="row">
<div class="col-md-12">
<div class="auth-header">
<img src="//www.gravatar.com/avatar/{{ application.gravatar_email | gravatar }}?s=48&d=identicon">
<span class="avatar" size="48" email="application.avatar_email" name="application.name"></span>
<h2>{{ application.name || '(Untitled)' }}</h2>
<h4>
<img src="//www.gravatar.com/avatar/{{ organization.gravatar }}?s=24&d=identicon" style="vertical-align: middle; margin-right: 4px;">
<span class="avatar" size="24" hash="organization.avatar" style="vertical-align: middle; margin-right: 4px;"></span>
<span style="vertical-align: middle"><a href="/organization/{{ organization.name }}/admin">{{ organization.name }}</a></span>
</h4>
</div>
@ -30,6 +30,7 @@
<ul class="nav nav-pills nav-stacked">
<li class="active"><a href="javascript:void(0)" data-toggle="tab" data-target="#settings">Settings</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#oauth">OAuth Information</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#gen-token">Generate Token</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#delete">Delete Application</a></li>
</ul>
</div>
@ -59,9 +60,9 @@
</div>
<div class="form-group nested">
<label for="fieldAppGravatar">Gravatar E-mail (optional)</label>
<input type="email" class="form-control" id="fieldAppGravatar" placeholder="Gravatar E-mail" ng-model="application.gravatar_email">
<div class="description">An e-mail address representing the <a href="http://en.gravatar.com/" target="_blank">Gravatar</a> for the application. See above for the icon.</div>
<label for="fieldAppAvatar">Avatar E-mail (optional)</label>
<input type="email" class="form-control" id="fieldAppAvatar" placeholder="Avatar E-mail" ng-model="application.avatar_email">
<div class="description">An e-mail address representing the <a href="http://docs.quay.io/glossary/avatar" target="_blank">Avatar</a> for the application. See above for the icon.</div>
</div>
<div class="form-group nested" style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #eee;">
@ -91,6 +92,31 @@
</div>
</div>
<!-- Generate Token tab -->
<div id="gen-token" class="tab-pane">
<div style="margin-bottom: 20px">
Click the button below to generate a new <a href="http://tools.ietf.org/html/rfc6749#section-1.4" target="_new">OAuth 2 Access Token</a>.
</div>
<div style="margin-bottom: 20px">
<strong>Note:</strong> The generated token will act on behalf of user
<span class="avatar" hash="user.avatar" size="16" style="margin-left: 6px; margin-right: 4px;"></span>
{{ user.username }}
</div>
<table>
<tr ng-repeat="(scopeName, scopeInfo) in OAuthService.SCOPES">
<td><label onclick="event.stopPropagation()"><input type="checkbox" value="scopeInfo[0]" ng-model="genScopes[scopeName]">{{ scopeInfo[3] }}</label></td>
</tr>
</table>
<a class="btn btn-success"
href="{{ Config.getUrl('/oauth/authorize?response_type=token&client_id=' + application.client_id + '&scope=' + getScopes(genScopes).join(',') + '&redirect_uri=display') }}"
ng-disabled="!getScopes(genScopes).length" target="_blank">
Generate Access Token
</a>
</div>
<!-- OAuth tab -->
<div id="oauth" class="tab-pane">
<table style="margin-top: 20px;">

View file

@ -43,7 +43,7 @@
<div class="panel-content" style="padding-left: 20px; margin-top: 10px;">
<form class="form-change" id="changeEmailForm" name="changeEmailForm" ng-submit="changeEmail()" data-trigger="manual"
data-content="{{ changeEmailError }}" data-placement="bottom" ng-show="!updatingOrganization">
<img src="//www.gravatar.com/avatar/{{ organizationEmail | gravatar }}?s=24&amp;d=identicon">
<span class="avatar" size="24" email="organizationEmail" name="orgname"></span>
<input type="email" class="form-control" ng-model="organizationEmail"
style="margin-left: 10px; margin-right: 10px; width: 400px; display: inline-block;" required>
<button class="btn btn-primary" type="submit" ng-disabled="changeEmailForm.$invalid || organizationEmail == organization.email">

View file

@ -23,7 +23,7 @@
<h2>Organizations</h2>
<div class="organization-listing" ng-repeat="organization in user.organizations">
<img class="gravatar" src="//www.gravatar.com/avatar/{{ organization.gravatar }}?s=32&amp;d=identicon">
<span class="avatar" size="32" hash="organization.avatar"></span>
<a class="org-title" href="/organization/{{ organization.name }}">{{ organization.name }}</a>
</div>
</div>

View file

@ -36,7 +36,7 @@
<tr ng-repeat="member in members | filter:search | filter: filterFunction(false, false) | orderBy: 'name'">
<td class="user entity">
<span class="entity-reference" entity="member" namespace="organization.name" show-gravatar="true" gravatar-size="32"></span>
<span class="entity-reference" entity="member" namespace="organization.name" show-avatar="true" avatar-size="32"></span>
</td>
<td>
<span class="delete-ui" delete-title="'Remove ' + member.name + ' from team'" button-title="'Remove'"
@ -67,10 +67,10 @@
<tr ng-repeat="member in members | filter:search | filter: filterFunction(true, false) | orderBy: 'name'">
<td class="user entity">
<span ng-if="member.kind != 'invite'">
<span class="entity-reference" entity="member" namespace="organization.name" show-gravatar="true" gravatar-size="32"></span>
<span class="entity-reference" entity="member" namespace="organization.name" show-avatar="true" avatar-size="32"></span>
</span>
<span class="invite-listing" ng-if="member.kind == 'invite'">
<img class="gravatar"ng-src="//www.gravatar.com/avatar/{{ member.gravatar }}?s=32&amp;d=identicon">
<span class="avatar" size="32" hash="member.avatar"></span>
{{ member.email }}
</span>
</td>

View file

@ -9,7 +9,7 @@
<div class="user-admin container" ng-show="!user.anonymous">
<div class="row">
<div class="organization-header-element">
<img src="//www.gravatar.com/avatar/{{ user.gravatar }}?s=24&amp;d=identicon">
<span class="avatar" size="24" hash="user.avatar"></span>
<span class="organization-name">
{{ user.username }}
</span>
@ -72,7 +72,7 @@
<tr class="auth-info" ng-repeat="authInfo in authorizedApps">
<td>
<img src="//www.gravatar.com/avatar/{{ authInfo.gravatar }}?s=16&d=identicon">
<span class="avatar" size="16" hash="authInfo.application.avatar"></span>
<a href="{{ authInfo.application.url }}" ng-if="authInfo.application.url" target="_blank"
data-title="{{ authInfo.application.description || authInfo.application.name }}" bs-tooltip>
{{ authInfo.application.name }}
@ -291,7 +291,7 @@
<div class="form-group">
<label for="orgName">Organization Name</label>
<div class="existing-data">
<img src="//www.gravatar.com/avatar/{{ user.gravatar }}?s=24&amp;d=identicon">
<span class="avatar" size="24" hash="user.avatar"></span>
{{ user.username }}</div>
<span class="description">This will continue to be the namespace for your repositories</span>
</div>