Get the new context-sensitive new menu working

This commit is contained in:
Joseph Schorr 2015-04-07 18:33:43 -04:00
parent 40a6892a49
commit d09f2f6e22
13 changed files with 461 additions and 193 deletions

View file

@ -679,34 +679,27 @@ def get_user_or_org_by_customer_id(customer_id):
except User.DoesNotExist:
return None
def get_matching_user_entities(entity_prefix, user):
def get_matching_entities(entity_prefix):
matching_user_orgs = ((User.username ** (entity_prefix + '%')) & (User.robot == False))
if user is not None:
matching_robots = ((User.username ** (user.username + '+%' + entity_prefix + '%')) &
(User.robot == True))
else:
matching_robots = False
matching_robots = ((User.username ** ('%+%' + entity_prefix + '%')) & (User.robot == True))
query = (User.select()
.where(matching_user_orgs | matching_robots)
.limit(10))
.where(matching_user_orgs | matching_robots))
return query
def get_matching_user_teams(team_prefix, user):
def get_matching_user_teams(team_prefix, user, limit=10):
query = (Team.select()
.join(User)
.switch(Team)
.join(TeamMember)
.where(TeamMember.user == user, Team.name ** (team_prefix + '%'))
.distinct(Team.id)
.limit(10))
.limit(limit))
return query
def get_matching_admined_teams(team_prefix, user):
def get_matching_admined_teams(team_prefix, user, limit=10):
admined_orgs = (get_user_organizations(user.username)
.switch(Team)
.join(TeamRole)
@ -718,7 +711,7 @@ def get_matching_admined_teams(team_prefix, user):
.join(TeamMember)
.where(Team.name ** (team_prefix + '%'), Team.organization << (admined_orgs))
.distinct(Team.id)
.limit(10))
.limit(limit))
return query
@ -968,7 +961,7 @@ def _get_public_repo_visibility():
return _public_repo_visibility_cache
def get_matching_repositories(repo_term, username=None):
def get_matching_repositories(repo_term, username=None, limit=10):
namespace_term = repo_term
name_term = repo_term
@ -986,7 +979,7 @@ def get_matching_repositories(repo_term, username=None):
search_clauses = (Repository.name ** ('%' + name_term + '%') &
Namespace.username ** ('%' + namespace_term + '%'))
return visible.where(search_clauses).limit(10)
return visible.where(search_clauses).limit(limit)
def get_repository_pull_counts(repositories):
repo_pull = LogEntryKind.get(name = 'pull_repo')

View file

@ -9,6 +9,7 @@ from auth import scopes
from app import avatar, get_app_url
from operator import itemgetter
from stringscore import liquidmetal
from util.names import parse_robot_username
import math
@ -130,8 +131,13 @@ class ConductSearch(ApiResource):
avatar_data = avatar.get_data_for_org(entity)
href = '/organization/' + entity.username
elif entity.robot:
parts = parse_robot_username(entity.username)
if parts[0] == username:
href = '/user/' + username + '?tab=robots&showRobot=' + entity.username
else:
href = '/organization/' + parts[0] + '?tab=robots&showRobot=' + entity.username
kind = 'robot'
href = '/user?tab=robots'
avatar_data = None
return {
@ -148,7 +154,7 @@ class ConductSearch(ApiResource):
# Find the matching teams where the user is a member.
encountered_teams = set()
matching_teams = model.get_matching_user_teams(query, get_authenticated_user())
matching_teams = model.get_matching_user_teams(query, get_authenticated_user(), limit=5)
for team in matching_teams:
if team.id in encountered_teams:
continue
@ -165,7 +171,7 @@ class ConductSearch(ApiResource):
})
# Find matching teams in orgs admined by the user.
matching_teams = model.get_matching_admined_teams(query, get_authenticated_user())
matching_teams = model.get_matching_admined_teams(query, get_authenticated_user(), limit=5)
for team in matching_teams:
if team.id in encountered_teams:
continue
@ -183,7 +189,7 @@ class ConductSearch(ApiResource):
# Find the matching repositories.
matching_repos = model.get_matching_repositories(query, username)
matching_repos = model.get_matching_repositories(query, username, limit=5)
matching_repo_counts = {t[0]: t[1] for t in model.get_repository_pull_counts(matching_repos)}
for repo in matching_repos:
@ -197,10 +203,23 @@ class ConductSearch(ApiResource):
'href': '/repository/' + repo.namespace_user.username + '/' + repo.name
})
# Find the matching users, robots and organizations.
matching_entities = model.get_matching_user_entities(query, get_authenticated_user())
matching_entities = model.get_matching_entities(query)
entity_count = 0
for entity in matching_entities:
# If the entity is a robot, filter it to only match those that are under the current
# user or can be administered by the organization.
if entity.robot:
orgname = parse_robot_username(entity.username)[0]
if not AdministerOrganizationPermission(orgname).can() and not orgname == username:
continue
results.append(entity_view(entity))
entity_count = entity_count + 1
if entity_count >= 5:
break
for result in results:
result['score'] = result['score'] * liquidmetal.score(result['name'], query)

View file

@ -27,7 +27,7 @@ nav.navbar-default .navbar-nav>li>a.active {
}
.header-bar-element .header-bar-content {
z-index: 4;
z-index: 5;
position: absolute;
top: 0px;
left: 0px;
@ -40,7 +40,7 @@ nav.navbar-default .navbar-nav>li>a.active {
left: 0px;
right: 0px;
top: -50px;
z-index: 3;
z-index: 4;
height: 83px;
transition: top 0.7s cubic-bezier(.23,.88,.72,.98);
background: white;
@ -88,7 +88,7 @@ nav.navbar-default .navbar-nav>li>a.active {
left: 0px;
right: 0px;
top: -130px;
z-index: 2;
z-index: 3;
transition: top 0.7s cubic-bezier(.23,.88,.72,.98), height 0.5s ease-in-out;
background: white;
@ -173,4 +173,51 @@ nav.navbar-default .navbar-nav>li>a.active {
font-size: 12px;
margin-left: 6px;
display: inline-block;
}
}
.header-bar-element .avatar {
margin-right: 6px;
}
.user-tools {
position: relative;
display: inline-block;
}
.user-tools .user-tool {
font-size: 24px;
margin-top: 14px;
color: #428bca;
margin-right: 20px;
}
.user-tools.with-menu {
margin-right: 6px;
}
.user-tools .caret {
position: absolute;
top: 3px;
left: 23px
}
.user-tools .notifications-bubble {
position: absolute;
top: 2px;
left: 13px;
}
.user-tools i.user-tool:hover {
cursor: pointer;
color: #333;
}
.user-tools .new-menu {
background: transparent !important;
}
.header-bar-element .context-dropdown i.fa {
width: 16px;
text-align: center;
display: inline-block;
}

View file

@ -13,6 +13,10 @@
padding-bottom: 10px;
}
.team-view .team-view-header button i.fa {
margin-right: 4px;
}
.team-view .team-view-header > h3 {
margin-top: 10px;
}

View file

@ -742,17 +742,6 @@ i.toggle-icon:hover {
100% { transform: scale(1); }
}
.user-tools .user-tool {
font-size: 24px;
margin-top: 14px;
color: #428bca;
}
.user-tools i.user-tool:hover {
cursor: pointer;
color: #333;
}
.status-box a {
padding: 6px;
color: black;

View file

@ -1,141 +1,3 @@
<div class="header-bar-element">
<div class="header-bar-content" ng-class="searchVisible ? 'search-visible' : ''">
<!-- Quay -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse" style="padding: 0px; padding-left: 4px; padding-right: 4px;">
&equiv;
</button>
<a class="navbar-brand" href="/" target="{{ appLinkTarget() }}">
<span id="quay-logo" ng-style="{'background-image': 'url(' + getEnterpriseLogo() + ')'}"></span>
</a>
</div>
<!-- Collapsable stuff -->
<div class="collapse navbar-collapse navbar-ex1-collapse">
<ul class="nav navbar-nav navbar-links">
<li><a ng-href="/tour/" target="{{ appLinkTarget() }}" quay-section="tour">Tour</a></li>
<li><a ng-href="/repository/" target="{{ appLinkTarget() }}" quay-section="repository">Repositories</a></li>
<li><a href="http://docs.quay.io/" target="_blank">Docs</a></li>
<li><a ng-href="/tutorial/" target="{{ appLinkTarget() }}" quay-section="tutorial">Tutorial</a></li>
<li quay-require="['BILLING']"><a ng-href="/plans/" target="{{ appLinkTarget() }}" quay-section="plans">Pricing</a></li>
<li><a ng-href="{{ user.organizations.length ? '/organizations/' : '/tour/organizations/' }}" target="{{ appLinkTarget() }}" quay-section="organization">Organizations</a></li>
</ul>
<!-- Phone -->
<ul class="nav navbar-nav navbar-right visible-xs" ng-switch on="user.anonymous">
<li ng-switch-when="false">
<a href="/user/" class="user-view" target="{{ appLinkTarget() }}">
<span class="avatar" size="32" data="user.avatar"></span>
{{ user.username }}
</a>
</li>
<li ng-switch-default>
<a class="user-view" href="/signin/" target="{{ appLinkTarget() }}">Sign in</a>
</li>
</ul>
<!-- Normal -->
<ul class="nav navbar-nav navbar-right hidden-xs" ng-switch on="user.anonymous">
<li>
<span class="navbar-left user-tools">
<i class="fa fa-search fa-lg user-tool" ng-click="toggleSearch()"></i>
</span>
</li>
<li>
<span class="navbar-left user-tools" ng-show="!user.anonymous">
<a href="/new/"><i class="fa fa-plus user-tool" bs-tooltip="tooltip.title" data-placement="bottom" data-title="Create new repository" data-container="body"></i></a>
</span>
</li>
<li class="dropdown" ng-switch-when="false">
<a href="javascript:void(0)" class="dropdown-toggle user-dropdown user-view" data-toggle="dropdown">
<span class="avatar" size="32" data="user.avatar"></span>
{{ user.username }}
<span class="notifications-bubble"></span>
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li>
<a href="/user/{{ user.username }}?tab=settings" target="{{ appLinkTarget() }}" ng-if="isNewLayout">
Account Settings
</a>
<a href="/user/" target="{{ appLinkTarget() }}" ng-if="!isNewLayout">
Account Settings
</a>
</li>
<li ng-if="notificationService.notifications.length">
<a href="javascript:void(0)" data-template="/static/directives/notification-bar.html"
data-animation="am-slide-right" bs-aside="aside" data-container="body">
Notifications
<span class="notifications-bubble"></span>
</a>
</li>
<li><a ng-href="/organizations/" target="{{ appLinkTarget() }}">Organizations</a></li>
<li ng-if="user.super_user"><a href="/superuser/"><strong>Super User Admin Panel</strong></a></li>
<li><a href="javascript:void(0)" ng-click="signout()">Sign out</a></li>
</ul>
</li>
<li ng-switch-default>
<a class="user-view" href="/signin/" target="{{ appLinkTarget() }}">Sign in</a>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div>
<div class="search-box" ng-class="getSearchBoxClasses(searchVisible, searchResultState)">
<div class="search-label">Search For</div>
<div class="search-box-wrapper">
<input id="search-box-input" type="text" placeholder="(Enter Search Terms)"
ng-model-options="{'debounce': 250}" ng-model="currentSearchQuery"
ng-keydown="handleSearchKeyDown($event)">
</div>
</div>
<div class="search-results"
ng-class="searchVisible && searchResultState ? searchResultState.state : ''"
ng-class="{'height': (searchResultState.results.length * 40) + 28}">
<div class="cor-loader" ng-if="searchResultState.state == 'loading'"></div>
<div ng-if="searchResultState.state == 'no-results'">No matching results found</div>
<ul ng-if="searchResultState.state == 'results'">
<li ng-repeat="result in searchResultState.results" ng-mouseover="setCurrentResult($index)"
ng-class="searchResultState.current == $index ? 'current' : ''"
ng-click="showResult(result)">
<span class="kind">{{ result.kind }}</span>
<span class="score">{{ result.score }}</span>
<span ng-switch on="result.kind">
<!-- Team -->
<span ng-switch-when="team">
<strong>
<span class="avatar" data="result.avatar" size="16"></span>
<span class="result-name">{{ result.name }}</span>
</strong>
<span class="clarification">
under organization
<span class="avatar" data="result.organization.avatar" size="16"></span>
<span class="result-name">{{ result.organization.name }}</span>
</span>
</span>
<span ng-switch-when="user">
<span class="avatar" data="result.avatar" size="16"></span>
<span class="result-name">{{ result.name }}</span>
</span>
<span ng-switch-when="organization">
<span class="avatar" data="result.avatar" size="16"></span>
<span class="result-name">{{ result.name }}</span>
</span>
<span href="/user/{{ result.name }}" ng-switch-when="robot">
<i class="fa fa-wrench"></i>
<span class="result-name">{{ result.name }}</span>
</span>
<span ng-switch-when="repository">
<span class="avatar" data="result.namespace.avatar" size="16"></span>
<span class="result-name">{{ result.namespace.name }}/{{ result.name }}</span>
<div class="description" ng-if="result.description">
{{ result.description }}
</div>
</span>
</span>
</li>
</ul>
</div>
</div>
<span class="header-bar-parent">
<span quay-include="{'Config.isNewLayout()': 'directives/new-header-bar.html', '!Config.isNewLayout()': 'directives/old-header-bar.html'}"></span>
</span>

View file

@ -0,0 +1,189 @@
<div class="header-bar-element">
<div class="header-bar-content" ng-class="searchVisible ? 'search-visible' : ''">
<!-- Quay -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse" style="padding: 0px; padding-left: 4px; padding-right: 4px;">
&equiv;
</button>
<a class="navbar-brand" href="/" target="{{ appLinkTarget() }}">
<span id="quay-logo" ng-style="{'background-image': 'url(' + getEnterpriseLogo() + ')'}"></span>
</a>
</div>
<!-- Collapsable stuff -->
<div class="collapse navbar-collapse navbar-ex1-collapse">
<ul class="nav navbar-nav navbar-links">
<li><a ng-href="/tour/" target="{{ appLinkTarget() }}" quay-section="tour">Tour</a></li>
<li><a ng-href="/repository/" target="{{ appLinkTarget() }}" quay-section="repository">Repositories</a></li>
<li><a href="http://docs.quay.io/" target="_blank">Docs</a></li>
<li><a ng-href="/tutorial/" target="{{ appLinkTarget() }}" quay-section="tutorial">Tutorial</a></li>
<li quay-require="['BILLING']"><a ng-href="/plans/" target="{{ appLinkTarget() }}" quay-section="plans">Pricing</a></li>
<li><a ng-href="{{ user.organizations.length ? '/organizations/' : '/tour/organizations/' }}" target="{{ appLinkTarget() }}" quay-section="organization">Organizations</a></li>
</ul>
<!-- Phone -->
<ul class="nav navbar-nav navbar-right visible-xs" ng-switch on="user.anonymous">
<li ng-switch-when="false">
<a href="/user/" class="user-view" target="{{ appLinkTarget() }}">
<span class="avatar" size="32" data="user.avatar"></span>
{{ user.username }}
</a>
</li>
<li ng-switch-default>
<a class="user-view" href="/signin/" target="{{ appLinkTarget() }}">Sign in</a>
</li>
</ul>
<!-- Normal -->
<ul class="nav navbar-nav navbar-right hidden-xs" ng-switch on="user.anonymous">
<li>
<span class="navbar-left user-tools">
<i class="fa fa-search fa-lg user-tool" ng-click="toggleSearch()"
data-placement="bottom" data-title="Search" bs-tooltip></i>
</span>
</li>
<li>
<span class="navbar-left user-tools with-menu" ng-show="!user.anonymous">
<span class="dropdown">
<a href="javascript:void(0)" class="dropdown-toggle new-menu" data-toggle="dropdown">
<i class="fa fa-plus user-tool"
data-placement="bottom" data-title="Create New..." bs-tooltip></i>
<b class="caret"></b>
</a>
<ul class="dropdown-menu context-dropdown">
<li>
<a href="/organizations/new">
<span class="avatar" size="16" data="{'name': '+', color: '#ccc'}"></span>
New Organization
</a>
</li>
<li>
<a href="/new{{ getNamespace(currentPageContext) ? '?namespace=' + getNamespace(currentPageContext) : '' }}">
<i class="fa fa-hdd-o"></i> New Repository
</a>
</li>
<li role="presentation" class="divider" ng-if="getNamespace(currentPageContext)"></li>
<li role="presentation" class="dropdown-header"
ng-if="getNamespace(currentPageContext)">
Namespace {{ getNamespace(currentPageContext) }}
</li>
<li ng-if="isOrganization(getNamespace(currentPageContext)) && canAdmin(getNamespace(currentPageContext))">
<a href="javascript:void(0)" ng-click="createTeam(currentPageContext)">
<i class="fa fa-group"></i> New Team
</a>
</li>
<li ng-if="canAdmin(getNamespace(currentPageContext))">
<a href="javascript:void(0)" ng-click="createRobot(currentPageContext)">
<i class="fa fa-wrench"></i> New Robot Account
</a>
</li>
<li role="presentation" class="divider" ng-if="currentPageContext.repository"></li>
<li role="presentation" class="dropdown-header"
ng-if="currentPageContext.repository">
Repository {{ currentPageContext.repository.namespace }}/{{ currentPageContext.repository.name }}
</li>
<li ng-if="currentPageContext.repository">
<a href="javascript:void(0)">
<i class="fa fa-tasks"></i> New Dockerfile Build
</a>
</li>
</ul>
</span>
</span>
</li>
<li>
<span class="navbar-left user-tools" ng-show="!user.anonymous">
<a href="javascript:void(0)" data-template="/static/directives/notification-bar.html"
data-container="body" data-animation="am-slide-right" bs-aside>
<i class="fa fa-bell user-tool"
data-placement="bottom" data-title="Notifications" bs-tooltip></i>
</a>
<span class="notifications-bubble"></span>
</span>
</li>
<li class="dropdown" ng-switch-when="false">
<a href="javascript:void(0)" class="dropdown-toggle user-dropdown user-view" data-toggle="dropdown">
<span class="avatar" size="32" data="user.avatar"></span>
{{ user.username }}
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li>
<a href="/user/{{ user.username }}?tab=settings" target="{{ appLinkTarget() }}" ng-if="isNewLayout">
Account Settings
</a>
<a href="/user/" target="{{ appLinkTarget() }}" ng-if="!isNewLayout">
Account Settings
</a>
</li>
<li><a ng-href="/organizations/" target="{{ appLinkTarget() }}">Organizations</a></li>
<li ng-if="user.super_user"><a href="/superuser/"><strong>Super User Admin Panel</strong></a></li>
<li><a href="javascript:void(0)" ng-click="signout()">Sign out</a></li>
</ul>
</li>
<li ng-switch-default>
<a class="user-view" href="/signin/" target="{{ appLinkTarget() }}">Sign in</a>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div>
<div class="search-box" ng-class="getSearchBoxClasses(searchVisible, searchResultState)">
<div class="search-label">Search For</div>
<div class="search-box-wrapper">
<input id="search-box-input" type="text" placeholder="(Enter Search Terms)"
ng-model-options="{'debounce': 250}" ng-model="currentSearchQuery"
ng-keydown="handleSearchKeyDown($event)">
</div>
</div>
<div class="search-results"
ng-class="searchVisible && searchResultState ? searchResultState.state : ''"
ng-class="{'height': (searchResultState.results.length * 40) + 28}">
<div class="cor-loader" ng-if="searchResultState.state == 'loading'"></div>
<div ng-if="searchResultState.state == 'no-results'">No matching results found</div>
<ul ng-if="searchResultState.state == 'results'">
<li ng-repeat="result in searchResultState.results" ng-mouseover="setCurrentResult($index)"
ng-class="searchResultState.current == $index ? 'current' : ''"
ng-click="showResult(result)">
<span class="kind">{{ result.kind }}</span>
<span class="score">{{ result.score }}</span>
<span ng-switch on="result.kind">
<!-- Team -->
<span ng-switch-when="team">
<strong>
<span class="avatar" data="result.avatar" size="16"></span>
<span class="result-name">{{ result.name }}</span>
</strong>
<span class="clarification">
under organization
<span class="avatar" data="result.organization.avatar" size="16"></span>
<span class="result-name">{{ result.organization.name }}</span>
</span>
</span>
<span ng-switch-when="user">
<span class="avatar" data="result.avatar" size="16"></span>
<span class="result-name">{{ result.name }}</span>
</span>
<span ng-switch-when="organization">
<span class="avatar" data="result.avatar" size="16"></span>
<span class="result-name">{{ result.name }}</span>
</span>
<span href="/user/{{ result.name }}" ng-switch-when="robot">
<i class="fa fa-wrench"></i>
<span class="result-name">{{ result.name }}</span>
</span>
<span ng-switch-when="repository">
<span class="avatar" data="result.namespace.avatar" size="16"></span>
<span class="result-name">{{ result.namespace.name }}/{{ result.name }}</span>
<div class="description" ng-if="result.description">
{{ result.description }}
</div>
</span>
</span>
</li>
</ul>
</div>
</div>

View file

@ -0,0 +1,78 @@
<!-- Quay -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse" style="padding: 0px; padding-left: 4px; padding-right: 4px;">
&equiv;
</button>
<a class="navbar-brand" href="/" target="{{ appLinkTarget() }}">
<span id="quay-logo" ng-style="{'background-image': 'url(' + getEnterpriseLogo() + ')'}"></span>
</a>
</div>
<!-- Collapsable stuff -->
<div class="collapse navbar-collapse navbar-ex1-collapse">
<ul class="nav navbar-nav navbar-links">
<li><a ng-href="/tour/" target="{{ appLinkTarget() }}" quay-section="tour">Tour</a></li>
<li><a ng-href="/repository/" target="{{ appLinkTarget() }}" quay-section="repository">Repositories</a></li>
<li><a href="http://docs.quay.io/" target="_blank">Docs</a></li>
<li><a ng-href="/tutorial/" target="{{ appLinkTarget() }}" quay-section="tutorial">Tutorial</a></li>
<li quay-require="['BILLING']"><a ng-href="/plans/" target="{{ appLinkTarget() }}" quay-section="plans">Pricing</a></li>
<li><a ng-href="{{ user.organizations.length ? '/organizations/' : '/tour/organizations/' }}" target="{{ appLinkTarget() }}" quay-section="organization">Organizations</a></li>
</ul>
<!-- Phone -->
<ul class="nav navbar-nav navbar-right visible-xs" ng-switch on="user.anonymous">
<li ng-switch-when="false">
<a href="/user/" class="user-view" target="{{ appLinkTarget() }}">
<span class="avatar" size="32" data="user.avatar"></span>
{{ user.username }}
</a>
</li>
<li ng-switch-default>
<a class="user-view" href="/signin/" target="{{ appLinkTarget() }}">Sign in</a>
</li>
</ul>
<!-- Normal -->
<ul class="nav navbar-nav navbar-right hidden-xs" ng-switch on="user.anonymous">
<li>
<form class="navbar-form navbar-left" role="search">
<div class="form-group">
<span class="repo-search"></span>
</div>
</form>
<span class="navbar-left user-tools" ng-show="!user.anonymous">
<a href="/new/"><i class="fa fa-upload user-tool" bs-tooltip="tooltip.title" data-placement="bottom" data-title="Create new repository" data-container="body"></i></a>
</span>
</li>
<li class="dropdown" ng-switch-when="false">
<a href="javascript:void(0)" class="dropdown-toggle user-dropdown user-view" data-toggle="dropdown">
<span class="avatar" size="32" data="user.avatar"></span>
{{ user.username }}
<span class="notifications-bubble"></span>
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li>
<a href="/user/" target="{{ appLinkTarget() }}">
Account Settings
</a>
</li>
<li ng-if="notificationService.notifications.length">
<a href="javascript:void(0)" data-template="/static/directives/notification-bar.html"
data-animation="am-slide-right" bs-aside="aside" data-container="body">
Notifications
<span class="notifications-bubble"></span>
</a>
</li>
<li><a ng-href="/organizations/" target="{{ appLinkTarget() }}">Organizations</a></li>
<li ng-if="user.super_user"><a href="/superuser/"><strong>Super User Admin Panel</strong></a></li>
<li><a href="javascript:void(0)" ng-click="signout()">Sign out</a></li>
</ul>
</li>
<li ng-switch-default>
<a class="user-view" href="/signin/" target="{{ appLinkTarget() }}">Sign in</a>
</li>
</ul>
</div><!-- /.navbar-collapse -->

View file

@ -12,7 +12,7 @@ angular.module('quay').directive('headerBar', function () {
restrict: 'C',
scope: {
},
controller: function($scope, $element, $location, $timeout, UserService, PlanService, ApiService, NotificationService, Config) {
controller: function($rootScope, $scope, $element, $location, $timeout, UserService, PlanService, ApiService, NotificationService, Config, CreateService) {
$scope.notificationService = NotificationService;
$scope.searchVisible = false;
$scope.currentSearchQuery = null;
@ -23,6 +23,19 @@ angular.module('quay').directive('headerBar', function () {
$scope.isNewLayout = Config.isNewLayout();
$scope.currentPageContext = {};
$rootScope.$watch('currentPage.scope.viewuser', function(u) {
$scope.currentPageContext['viewuser'] = u;
});
$rootScope.$watch('currentPage.scope.organization', function(o) {
$scope.currentPageContext['organization'] = o;
});
$rootScope.$watch('currentPage.scope.repository', function(r) {
$scope.currentPageContext['repository'] = r;
});
var conductSearch = function(query) {
if (!query) { $scope.searchResultState = null; return; }
@ -43,6 +56,8 @@ angular.module('quay').directive('headerBar', function () {
'results': resp.results,
'current': -1
};
}, function(resp) {
$scope.searchResultState = null;
}, /* background */ true);
};
@ -133,6 +148,77 @@ angular.module('quay').directive('headerBar', function () {
if (!$scope.searchResultState) { return; }
$scope.searchResultState['current'] = result;
};
$scope.getNamespace = function(context) {
if (!context) { return null; }
if (context.repository && context.repository.namespace) {
return context.repository.namespace;
}
if (context.organization && context.organization.name) {
return context.organization.name;
}
if (context.viewuser && context.viewuser.username) {
return context.viewuser.username;
}
return null;
};
$scope.canAdmin = function(namespace) {
if (!namespace) { return false; }
return UserService.isNamespaceAdmin(namespace);
};
$scope.isOrganization = function(namespace) {
if (!namespace) { return false; }
return UserService.isOrganization(namespace);
};
$scope.createRobot = function(context) {
var namespace = $scope.getNamespace(context);
if (!namespace || !UserService.isNamespaceAdmin(namespace)) { return; }
var isorg = UserService.isOrganization(namespace);
bootbox.prompt('Enter the name of the new robot account', function(robotname) {
if (!robotname) { return; }
var regex = new RegExp(ROBOT_PATTERN);
if (!regex.test(robotname)) {
bootbox.alert('Invalid robot account name');
return;
}
CreateService.createRobotAccount(ApiService, isorg, namespace, robotname, function(created) {
if (isorg) {
$location.url('/organization/' + namespace + '?tab=robots');
} else {
$location.url('/user/' + namespace + '?tab=robots');
}
});
});
};
$scope.createTeam = function(context) {
var namespace = $scope.getNamespace(context);
if (!namespace || !UserService.isNamespaceAdmin(namespace)) { return; }
bootbox.prompt('Enter the name of the new team', function(teamname) {
if (!teamname) { return; }
var regex = new RegExp(TEAM_PATTERN);
if (!regex.test(teamname)) {
bootbox.alert('Invalid team name');
return;
}
CreateService.createOrganizationTeam(ApiService, namespace, teamname, function(created) {
$location.url('/organization/' + namespace + '/teams/' + teamname);
});
});
};
}
};
return directiveDefinitionObject;

View file

@ -47,7 +47,7 @@ angular.module('quay').directive('teamsManager', function () {
};
var loadOrderedTeams = function() {
if (!$scope.organization) { return; }
if (!$scope.organization || !$scope.organization.ordered_teams) { return; }
$scope.orderedTeams = [];
$scope.organization.ordered_teams.map(function(name) {

View file

@ -63,6 +63,7 @@
};
$scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) {
$scope.repository = repo;
$scope.viewScope.repository = repo;
// Load the remainder of the data async, so we don't block the initial view from

View file

@ -34,7 +34,7 @@
var loadUser = function() {
$scope.userResource = ApiService.getUserInformationAsResource({'username': username}).get(function(user) {
$scope.user = user;
$scope.viewuser = user;
// Load the repositories.
$timeout(function() {

View file

@ -5,12 +5,12 @@
<div class="cor-title">
<span class="cor-title-link"></span>
<span class="cor-title-content">
<span class="avatar" size="32" data="user.avatar"></span>
<span class="user-name">{{ user.username }}</span>
<span class="avatar" size="32" data="viewuser.avatar"></span>
<span class="user-name">{{ viewuser.username }}</span>
</span>
</div>
<div class="co-main-content-panel user-repo-list" ng-if="!user.is_me">
<div class="co-main-content-panel user-repo-list" ng-if="!viewuser.is_me">
<div class="repo-list-grid"
repositories-resource="repositoriesResource"
starred="false"
@ -18,8 +18,8 @@
hide-title="true"></div>
</div>
<div class="cor-tab-panel" ng-if="user.is_me">
<div class="cor-tabs" quay-show="user.is_me">
<div class="cor-tab-panel" ng-if="viewuser.is_me">
<div class="cor-tabs" quay-show="viewuser.is_me">
<span class="cor-tab" tab-active="true" tab-title="Repositories" tab-target="#repos">
<i class="fa fa-hdd-o"></i>
</span>
@ -60,29 +60,29 @@
<!-- Robot Accounts -->
<div id="robots" class="tab-pane">
<div class="robots-manager" user="user"></div>
<div class="robots-manager" user="viewuser"></div>
</div>
<!-- External Logins -->
<div id="external" class="tab-pane">
<div class="external-logins-manager" user="user"></div>
<div class="external-logins-manager" user="viewuser"></div>
</div>
<!-- Applications -->
<div id="applications" class="tab-pane">
<div class="authorized-apps-manager" user="user" is-enabled="showAppsCounter"></div>
<div class="authorized-apps-manager" user="viewuser" is-enabled="showAppsCounter"></div>
</div>
<!-- Plan and Usage -->
<div id="usage" class="tab-pane" quay-require="['BILLING']">
<h3>Plan Usage and Billing</h3>
<div class="plan-manager" user="user"></div>
<div class="plan-manager" user="viewuser"></div>
</div>
<!-- Billing Invoices -->
<div id="invoices" class="tab-pane" quay-require="['BILLING']">
<h3>Billing Invoices</h3>
<div class="billing-invoices" user="user"
<div class="billing-invoices" user="viewuser"
makevisible="showInvoicesCounter"></div>
</div>
@ -110,7 +110,7 @@
<table class="col-md-6">
<tr>
<td>Current E-mail Address:</td>
<td>{{ user.email }}</td>
<td>{{ viewuser.email }}</td>
</tr>
<tr>
<td>New E-mail Address:</td>
@ -124,7 +124,7 @@
</div>
<button class="btn btn-primary"
ng-disabled="changeEmailForm.$invalid || changeEmail.email == user.email"
ng-disabled="changeEmailForm.$invalid || changeEmail.email == viewuser.email"
type="submit">
Change E-mail Address
</button>
@ -193,7 +193,7 @@
<div class="co-panel" quay-show="Config.AUTHENTICATION_TYPE == 'Database'">
<div class="co-panel-heading"><i class="fa fa-group"></i> Convert to organization</div>
<div class="panel-body" style="padding-top: 5px;">
<div class="convert-user-to-org" user="user"></div>
<div class="convert-user-to-org" user="viewuser"></div>
</div>
</div>
</div> <!-- /Convert -->