Get the new context-sensitive new menu working
This commit is contained in:
parent
40a6892a49
commit
d09f2f6e22
13 changed files with 461 additions and 193 deletions
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;">
|
||||
≡
|
||||
</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>
|
||||
|
|
189
static/directives/new-header-bar.html
Normal file
189
static/directives/new-header-bar.html
Normal 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;">
|
||||
≡
|
||||
</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>
|
78
static/directives/old-header-bar.html
Normal file
78
static/directives/old-header-bar.html
Normal 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;">
|
||||
≡
|
||||
</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 -->
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 -->
|
||||
|
|
Reference in a new issue