mirror of
https://github.com/ansible/awx.git
synced 2026-03-19 09:57:33 -02:30
Rehaul of main-menu
Updated assets, main-menu module code, main layout styling, and the index.html template.
This commit is contained in:
@@ -13,7 +13,7 @@ body {
|
|||||||
font-family: 'Open Sans', sans-serif;
|
font-family: 'Open Sans', sans-serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: @black;
|
color: @black;
|
||||||
padding-top: 58px;
|
padding-top: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#main-menu-container {
|
#main-menu-container {
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
export default
|
|
||||||
[ 'templateUrl',
|
|
||||||
function(templateUrl) {
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
templateUrl: templateUrl('main-menu/menu-default'),
|
|
||||||
link: function(scope, element) {
|
|
||||||
var contents = element.contents();
|
|
||||||
contents.unwrap();
|
|
||||||
|
|
||||||
scope.$on('$destroy', function() {
|
|
||||||
contents.remove();
|
|
||||||
$(".MenuItem--socketStatus").remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@@ -1,57 +1,268 @@
|
|||||||
/** @define MainMenu */
|
/** @define MainMenu */
|
||||||
|
|
||||||
.MainMenu {
|
.MainMenu {
|
||||||
@menu-breakpoint: 871px;
|
padding: 0;
|
||||||
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
background-color: white;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
@media screen and (max-width: @menu-breakpoint) {
|
|
||||||
position: relative;
|
|
||||||
transition: height 0.5s ease-out;
|
|
||||||
justify-content: flex-start;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 58px;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&.Menu--open {
|
|
||||||
height: 523px !important;
|
|
||||||
// border-bottom: solid thin black;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--portal.Menu--open {
|
|
||||||
height: 290px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: (@menu-breakpoint + 1px)) {
|
|
||||||
padding: 0 20px;
|
|
||||||
padding-right: 23px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-menuContainer {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-toggle {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
@media screen and (min-width: (@menu-breakpoint + 1px)) {
|
background-color: #167ec4;
|
||||||
display: none;
|
width: 100%;
|
||||||
}
|
z-index: 1040;
|
||||||
margin-left: auto;
|
position: fixed;
|
||||||
}
|
right: 0;
|
||||||
|
left: 0;
|
||||||
&--fixedTop {
|
top: 0;
|
||||||
width: 100%;
|
|
||||||
z-index: 1040;
|
|
||||||
position: fixed;
|
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.MainMenu-logo,
|
||||||
|
.MainMenu-item {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #167ec4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-logoImage {
|
||||||
|
max-width: 147px;
|
||||||
|
max-height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-item {
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-itemText,
|
||||||
|
.MainMenu-itemImage {
|
||||||
|
flex: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-socket {
|
||||||
|
transition: background-color 0.2s, color 0.2s, border-bottom 0.1s, padding-top 0.1s;
|
||||||
|
color: #fff;
|
||||||
|
background: url(assets/socket_background.png) #167ec4;
|
||||||
|
background-size: 55px 52px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
order: 0;
|
||||||
|
flex: initial;
|
||||||
|
padding-top: 0px;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 0;
|
||||||
|
border-left: 1px solid #509ad3;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-socketImage {
|
||||||
|
font-size: 18px;
|
||||||
|
text-shadow:
|
||||||
|
-1px -1px 0 #fff,
|
||||||
|
1px -1px 0 #fff,
|
||||||
|
-1px 1px 0 #fff,
|
||||||
|
1px 1px 0 #fff;
|
||||||
|
z-index: 1042;
|
||||||
|
}
|
||||||
|
|
||||||
|
@menu-breakpoint: 886px;
|
||||||
|
|
||||||
|
@media screen and (min-width: (@menu-breakpoint + 1px)) {
|
||||||
|
.MainMenu-mobileItems,
|
||||||
|
.MainMenu-toggle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu {
|
||||||
|
height: 50px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-logo {
|
||||||
|
flex: initial;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 0px solid #fff;
|
||||||
|
border-right: 1px solid #509ad3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-item {
|
||||||
|
transition: background-color 0.2s, color 0.2s, border-bottom 0.1s, padding-top 0.1s;
|
||||||
|
flex: initial;
|
||||||
|
padding-top: 0px;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 0px solid #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up elements based on if their layout in the menu bar
|
||||||
|
.MainMenu-item--left {
|
||||||
|
border-right: 1px solid #509ad3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-item--left:hover,
|
||||||
|
.MainMenu-item--left.is-currentRoute {
|
||||||
|
margin-left: -1px;
|
||||||
|
padding-left: 21px;
|
||||||
|
padding-right: 21px;
|
||||||
|
border-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-item--lastLeft {
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-item--right {
|
||||||
|
border-left: 1px solid #509ad3;
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-item--right:hover,
|
||||||
|
.MainMenu-item--right.is-currentRoute {
|
||||||
|
border-left: 0px;
|
||||||
|
padding-left: 21px;
|
||||||
|
padding-right: 21px;
|
||||||
|
margin-right: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the user item is a little different based on the icon being
|
||||||
|
// strangely positioned
|
||||||
|
.MainMenu-item--user {
|
||||||
|
margin-top: -3px;
|
||||||
|
padding-left: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-item--user:hover,
|
||||||
|
.MainMenu-item--user.is-currentRoute {
|
||||||
|
padding-left: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-item:hover,
|
||||||
|
.MainMenu-item.is-currentRoute {
|
||||||
|
padding-top: 5px;
|
||||||
|
border-bottom: 5px solid #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-itemText--username {
|
||||||
|
padding-left: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-itemImage {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-itemImage--user {
|
||||||
|
font-size: 21px;
|
||||||
|
margin-top: -9px;
|
||||||
|
margin-right: -9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-itemImage--docs {
|
||||||
|
font-size: 21px;
|
||||||
|
margin-top: -2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: @menu-breakpoint) {
|
||||||
|
.MainMenu-item--notMobile {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu {
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-logo {
|
||||||
|
flex: 1;
|
||||||
|
border-bottom: 1px solid #509ad3;
|
||||||
|
order: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-mobileItems {
|
||||||
|
display: flex;
|
||||||
|
order: 2;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-size: 15px;
|
||||||
|
width: 100%;
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
height: 416px;
|
||||||
|
transition: 0s visibility, 0.1s height, 0.3s opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-mobileItems.is-hiddenOnMobile,
|
||||||
|
.MainMenu-mobileItems.is-loggedOut {
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-item {
|
||||||
|
flex: 0 0 100%;
|
||||||
|
padding-top: 15px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
order: 1;
|
||||||
|
border-bottom: 1px solid #509ad3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-toggle {
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #167ec4;
|
||||||
|
order: 0;
|
||||||
|
flex: initial;
|
||||||
|
padding-top: 3px;
|
||||||
|
padding-left: 15px;
|
||||||
|
padding-right: 15px;
|
||||||
|
font-size: 34px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 0;
|
||||||
|
border-left: 1px solid #509ad3;
|
||||||
|
border-bottom: 1px solid #509ad3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-socket {
|
||||||
|
border-bottom: 1px solid #509ad3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-toggleImage {
|
||||||
|
width: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-toggle:focus,
|
||||||
|
.MainMenu-item:focus,
|
||||||
|
.MainMenu-logo:focus,
|
||||||
|
.MainMenu-toggle:hover,
|
||||||
|
.MainMenu-item:hover,
|
||||||
|
.MainMenu-logo:hover,
|
||||||
|
.MainMenu-item.is-currentRoute {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-toggle:hover,
|
||||||
|
.MainMenu-item:hover,
|
||||||
|
.MainMenu-logo:hover {
|
||||||
|
background-color: #5bbdbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-item.is-currentRoute {
|
||||||
|
background-color: #509ad3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// item on
|
||||||
|
.MainMenu-logo,
|
||||||
|
.MainMenu-item,
|
||||||
|
.MainMenu-socket,
|
||||||
|
.MainMenu-toggle {
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity .5s, background-color .2s, border-bottom .1s, padding-top .1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MainMenu-logo.is-loggedOut,
|
||||||
|
.MainMenu-item.is-loggedOut,
|
||||||
|
.MainMenu-socket.is-loggedOut,
|
||||||
|
.MainMenu-toggle.is-loggedOut {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,48 +1,76 @@
|
|||||||
/* jshint unused: vars */
|
/* jshint unused: vars */
|
||||||
|
|
||||||
export default
|
export default
|
||||||
[ 'templateUrl',
|
[ '$location', 'templateUrl', '$rootScope', function($location, templateUrl, $rootScope) {
|
||||||
function(templateUrl) {
|
return {
|
||||||
return {
|
restrict: 'E',
|
||||||
restrict: 'E',
|
templateUrl: templateUrl('main-menu/main-menu'),
|
||||||
controllerAs: 'mainMenu',
|
link: function(scope, element, attrs) {
|
||||||
templateUrl: templateUrl('main-menu/main-menu'),
|
// check to see if this is the current route
|
||||||
controller: ['$scope', function($scope) {
|
scope.isCurrentRoute = function(route) {
|
||||||
this.open = function() {
|
if ($location.url().split('/')[1] === route) {
|
||||||
$scope.isOpen = true;
|
return true;
|
||||||
};
|
} else {
|
||||||
|
return false;
|
||||||
this.close = function() {
|
}
|
||||||
$scope.isOpen = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.toggle = function() {
|
|
||||||
$scope.isOpen = !$scope.isOpen;
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.isOpen = false;
|
|
||||||
}],
|
|
||||||
scope: {
|
|
||||||
menuStyle: '&menuStyle',
|
|
||||||
currentUser: '='
|
|
||||||
},
|
|
||||||
link: function(scope, element, attrs) {
|
|
||||||
scope.menuStyleClassName = 'blah';
|
|
||||||
scope.$watch(function() {
|
|
||||||
return scope.$eval(scope.menuStyle);
|
|
||||||
}, function(newValue) {
|
|
||||||
scope.menuStyleClassName = 'MainMenu--' + newValue;
|
|
||||||
});
|
|
||||||
scope.$watch('isOpen', function(isOpen) {
|
|
||||||
if (isOpen) {
|
|
||||||
element.find('.MainMenu').addClass("Menu--open");
|
|
||||||
element.find('menu-toggle-button').addClass("MenuToggle--open");
|
|
||||||
} else {
|
|
||||||
element.find('.MainMenu').removeClass("Menu--open");
|
|
||||||
element.find('menu-toggle-button').removeClass("MenuToggle--open");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
// check to see if the current route is the currently
|
||||||
];
|
// logged in user
|
||||||
|
scope.isCurrentRouteUser = function() {
|
||||||
|
if ($rootScope && $rootScope.current_user) {
|
||||||
|
if ($location.url().split('/')[1] === 'users') {
|
||||||
|
if ($location.url().split('/')[2] === ($rootScope.current_user.id + "")) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up the user tooltip
|
||||||
|
$rootScope.$on('current_user', function(user) {
|
||||||
|
scope.currentUserTip = "Logged in as " + user.un;
|
||||||
|
});
|
||||||
|
|
||||||
|
// set up things for the socket notification
|
||||||
|
scope.socketHelp = $rootScope.socketHelp;
|
||||||
|
scope.socketTip = $rootScope.socketTip;
|
||||||
|
$rootScope.$watch('socketStatus', function(newStatus) {
|
||||||
|
scope.socketStatus = newStatus;
|
||||||
|
scope.socketIconClass = "icon-socket-" + scope.socketStatus;
|
||||||
|
});
|
||||||
|
$rootScope.$watch('socketTip', function(newTip) {
|
||||||
|
scope.socketTip = newTip;
|
||||||
|
});
|
||||||
|
|
||||||
|
// default the mobile menu as hidden
|
||||||
|
scope.isHiddenOnMobile = true;
|
||||||
|
// set up the click function to toggle mobile menu
|
||||||
|
scope.toggleMenu = function() {
|
||||||
|
if (scope.isHiddenOnMobile) {
|
||||||
|
scope.isHiddenOnMobile = false;
|
||||||
|
} else {
|
||||||
|
scope.isHiddenOnMobile = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if the user clicks outside of the mobile menu,
|
||||||
|
// close it if it's open
|
||||||
|
$("body").on('click', function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
if ($(e.target).parents(".MainMenu").length === 0) {
|
||||||
|
scope.isHiddenOnMobile = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// close the menu when the user clicks a link to a different route
|
||||||
|
scope.$on('$locationChangeStart', function(event) {
|
||||||
|
scope.isHiddenOnMobile = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
|||||||
@@ -1,8 +1,191 @@
|
|||||||
<nav class="MainMenu MainMenu--fixedTop" ng-class="menuStyleClassName" ng-switch="$eval(menuStyle)">
|
<nav class="MainMenu">
|
||||||
<a link-to="dashboard" menu-item class="MenuItem MenuItem--logo">
|
<!-- Menu logo item -->
|
||||||
<img id="ansible-brand-logo" alt="Ansible Tower" class="MenuItem-logo" src="/static/assets/TowerLogo.svg">
|
<a id="MainMenuLogo"
|
||||||
<menu-toggle-button width="15" height="15" bar-height="2" class="MainMenu-toggle"></menu-toggle-button>
|
href="/#/"
|
||||||
|
class="MainMenu-logo"
|
||||||
|
ng-class="{'is-loggedOut' : !$root.current_user.username}">
|
||||||
|
<img class="MainMenu-logoImage"
|
||||||
|
ng-src="/static/assets/main_menu_logo.png">
|
||||||
</a>
|
</a>
|
||||||
<default-menu ng-switch-when="default" class="MainMenu-menuContainer"></default-menu>
|
|
||||||
<portal-menu ng-switch-when="portal" class="MainMenu-menuContainer"></portal-menu>
|
<!-- Mobile menu items -->
|
||||||
|
<span class="MainMenu-mobileItems" ng-class="{'is-hiddenOnMobile': isHiddenOnMobile, 'is-loggedOut' : !$root.current_user.username}">
|
||||||
|
<a class="MainMenu-item"
|
||||||
|
id="menu_projects_mobile_link"
|
||||||
|
href="/#/projects"
|
||||||
|
ng-class="{'is-currentRoute' : isCurrentRoute('projects')}">
|
||||||
|
<span class="MainMenu-itemText">
|
||||||
|
PROJECTS
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a class="MainMenu-item"
|
||||||
|
id="menu_inventories_mobile_link"
|
||||||
|
href="/#/inventories"
|
||||||
|
ng-class="{'is-currentRoute' : isCurrentRoute('inventories')}">
|
||||||
|
<span class="MainMenu-itemText">
|
||||||
|
INVENTORIES
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a class="MainMenu-item"
|
||||||
|
id="menu_job_templates_mobile_link"
|
||||||
|
href="/#/job_templates"
|
||||||
|
ng-class="{'is-currentRoute' : isCurrentRoute('job_templates')}">
|
||||||
|
<span class="MainMenu-itemText">
|
||||||
|
JOB TEMPLATES
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a class="MainMenu-item"
|
||||||
|
id="menu_jobs_mobile_link"
|
||||||
|
href="/#/jobs"
|
||||||
|
ng-class="{'is-currentRoute' : isCurrentRoute('jobs')}">
|
||||||
|
<span class="MainMenu-itemText">
|
||||||
|
JOBS
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a class="MainMenu-item"
|
||||||
|
id="menu_current_user_mobile_link"
|
||||||
|
ng-href="/#/users/{{ $root.current_user.id }}"
|
||||||
|
ng-class="{'is-currentRoute' : isCurrentRouteUser()}">
|
||||||
|
<span class="MainMenu-itemText">
|
||||||
|
VIEW USER PAGE FOR {{ $root.current_user.username | uppercase }}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a class="MainMenu-item"
|
||||||
|
id="menu_setup_mobile_link"
|
||||||
|
ng-href="/#/setup"
|
||||||
|
ng-class="{'is-currentRoute' : isCurrentRoute('setup')}">
|
||||||
|
<span class="MainMenu-itemText">
|
||||||
|
SETTINGS
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a class="MainMenu-item"
|
||||||
|
id="menu_docs_mobile_link"
|
||||||
|
ng-href="http://docs.ansible.com/ansible-tower/"
|
||||||
|
target="_blank">
|
||||||
|
<span class="MainMenu-itemText">
|
||||||
|
VIEW DOCUMENTATION
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a class="MainMenu-item"
|
||||||
|
id="menu_logout_mobile_link"
|
||||||
|
ng-href="/#/logout"
|
||||||
|
ng-class="{'is-currentRoute' : isCurrentRoute('logout')}">
|
||||||
|
<span class="MainMenu-itemText">
|
||||||
|
LOG OUT
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!-- Non-mobile menu items -->
|
||||||
|
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--left"
|
||||||
|
id="menu_projects_link"
|
||||||
|
href="/#/projects"
|
||||||
|
ng-class="{'is-currentRoute' : isCurrentRoute('projects'), 'is-loggedOut' : !$root.current_user.username}">
|
||||||
|
<span class="MainMenu-itemText">
|
||||||
|
PROJECTS
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--left"
|
||||||
|
id="menu_inventories_link"
|
||||||
|
href="/#/inventories"
|
||||||
|
ng-class="{'is-currentRoute' : isCurrentRoute('inventories'), 'is-loggedOut' : !$root.current_user.username}">
|
||||||
|
<span class="MainMenu-itemText">
|
||||||
|
INVENTORIES
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--left"
|
||||||
|
id="menu_job_templates_link"
|
||||||
|
href="/#/job_templates"
|
||||||
|
ng-class="{'is-currentRoute' : isCurrentRoute('job_templates'), 'is-loggedOut' : !$root.current_user.username}">
|
||||||
|
<span class="MainMenu-itemText">
|
||||||
|
JOB TEMPLATES
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--left MainMenu-item--lastLeft"
|
||||||
|
id="menu_jobs_link"
|
||||||
|
href="/#/jobs"
|
||||||
|
ng-class="{'is-currentRoute' : isCurrentRoute('jobs'), 'is-loggedOut' : !$root.current_user.username}">
|
||||||
|
<span class="MainMenu-itemText">
|
||||||
|
JOBS
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--user MainMenu-item--right"
|
||||||
|
id="menu_current_user_link"
|
||||||
|
ng-href="/#/users/{{ $root.current_user.id }}"
|
||||||
|
ng-class="{'is-currentRoute' : isCurrentRouteUser(), 'is-loggedOut' : !$root.current_user.username}"
|
||||||
|
aw-tool-tip="{{currentUserTip}}"
|
||||||
|
aw-tip-watch="currentUserTip"
|
||||||
|
data-placement="bottom"
|
||||||
|
data-trigger="hover"
|
||||||
|
data-container="body">
|
||||||
|
<i class="MainMenu-itemImage MainMenu-itemImage--user icon-user"
|
||||||
|
alt="Logged in as {{::$root.current_user.username}}">
|
||||||
|
</i>
|
||||||
|
<span class="MainMenu-itemText MainMenu-itemText--username">
|
||||||
|
{{::$root.current_user.username}}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--right"
|
||||||
|
id="menu_setup_link"
|
||||||
|
ng-href="/#/setup"
|
||||||
|
ng-class="{'is-currentRoute' : isCurrentRoute('setup'), 'is-loggedOut' : !$root.current_user.username}"
|
||||||
|
aw-tool-tip="Settings"
|
||||||
|
data-placement="bottom"
|
||||||
|
data-trigger="hover"
|
||||||
|
data-container="body">
|
||||||
|
<i class="MainMenu-itemImage fa fa-cog"
|
||||||
|
alt="Settings">
|
||||||
|
</i>
|
||||||
|
</a>
|
||||||
|
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--right"
|
||||||
|
id="menu_docs_link"
|
||||||
|
ng-href="http://docs.ansible.com/ansible-tower/"
|
||||||
|
ng-class="{'is-loggedOut' : !$root.current_user.username}"
|
||||||
|
aw-tool-tip="View Documentation"
|
||||||
|
data-placement="bottom"
|
||||||
|
data-trigger="hover"
|
||||||
|
data-container="body"
|
||||||
|
target="_blank">
|
||||||
|
<i class="MainMenu-itemImage MainMenu-itemImage--docs fa fa-question-circle"
|
||||||
|
alt="View Documentation">
|
||||||
|
</i>
|
||||||
|
</a>
|
||||||
|
<a class="MainMenu-item MainMenu-item--notMobile MainMenu-item--right"
|
||||||
|
id="menu_logout_link"
|
||||||
|
ng-href="/#/logout"
|
||||||
|
ng-class="{'is-currentRoute' : isCurrentRoute('logout'), 'is-loggedOut' : !$root.current_user.username}"
|
||||||
|
aw-tool-tip="Log Out"
|
||||||
|
data-placement="bottom"
|
||||||
|
data-trigger="hover"
|
||||||
|
data-container="body"
|
||||||
|
data-tooltip_inner_class="tooltip-inner--logOut">
|
||||||
|
<i class="MainMenu-itemImage fa fa-power-off"
|
||||||
|
alt="Log Out">
|
||||||
|
</i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Socket-status item -->
|
||||||
|
<button
|
||||||
|
id="socket_status_notification"
|
||||||
|
class="MainMenu-socket"
|
||||||
|
aw-tool-tip="{{socketTip}}"
|
||||||
|
tip-watch="socketTip"
|
||||||
|
data-placement="bottom"
|
||||||
|
data-trigger="hover"
|
||||||
|
data-container="body"
|
||||||
|
ng-class="{'is-loggedOut' : !$root.current_user.username}"
|
||||||
|
ng-if="socketStatus && socketStatus !== 'ok'">
|
||||||
|
<i class="fa MainMenu-socketImage"
|
||||||
|
ng-class="socketIconClass">
|
||||||
|
</i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Mobile menu toggle item -->
|
||||||
|
<button
|
||||||
|
id="mobile_toggle_button"
|
||||||
|
class="MainMenu-toggle"
|
||||||
|
ng-class="{'is-active': !isHiddenOnMobile, 'is-loggedOut' : !$root.current_user.username}"
|
||||||
|
ng-click="toggleMenu()">
|
||||||
|
<i class="fa fa-bars MainMenu-toggleImage"></i>
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -1,19 +1,5 @@
|
|||||||
import mainMenu from './main-menu.directive';
|
import mainMenu from './main-menu.directive';
|
||||||
import menuItem from './menu-item.directive';
|
|
||||||
import menuToggle from './menu-toggle.directive';
|
|
||||||
import webSocketStatus from './web-socket-status.directive';
|
|
||||||
import defaultMenu from './default-menu.directive';
|
|
||||||
import portalMenu from './portal-menu.directive';
|
|
||||||
|
|
||||||
import shared from '../shared/main';
|
|
||||||
|
|
||||||
export default
|
export default
|
||||||
angular.module('mainMenu',
|
angular.module('mainMenu', [])
|
||||||
[ shared.name
|
|
||||||
])
|
|
||||||
.directive('menuItem', menuItem)
|
|
||||||
.directive('defaultMenu', defaultMenu)
|
|
||||||
.directive('portalMenu', portalMenu)
|
|
||||||
.directive('menuToggleButton', menuToggle)
|
|
||||||
.directive('webSocketStatus', webSocketStatus)
|
|
||||||
.directive('mainMenu', mainMenu);
|
.directive('mainMenu', mainMenu);
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
<a menu-item link-to="projects" title="Projects" class="MenuItem MenuItem--projects">
|
|
||||||
Projects
|
|
||||||
</a>
|
|
||||||
<a menu-item link-to="inventories" title="Inventories" class="MenuItem">
|
|
||||||
Inventories
|
|
||||||
</a>
|
|
||||||
<a menu-item link-to="jobTemplates" title="Job Templates" class="MenuItem">
|
|
||||||
Job Templates
|
|
||||||
</a>
|
|
||||||
<a menu-item link-to="jobs" title="Jobs" class="MenuItem">
|
|
||||||
Jobs
|
|
||||||
</a>
|
|
||||||
<web-socket-status class="MenuItem MenuItem--socketStatus MenuItem--right MenuItem--fixed"></web-socket-status>
|
|
||||||
<a link-to="userEdit" model="{ user_id: currentUser }" class="MenuItem MenuItem--right MenuItem--fixed MenuItem--username">
|
|
||||||
<i class="MenuItem-icon MenuItem-icon--labelled">
|
|
||||||
<aw-icon name="User"></aw-icon>
|
|
||||||
</i>
|
|
||||||
<span class="u-truncatedText">
|
|
||||||
{{currentUser.username}}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<a link-to="setup" class="MenuItem MenuItem--fixed">
|
|
||||||
<i class="MenuItem-icon" title="Setup" data-placement="bottom">
|
|
||||||
<aw-icon name="Setup"></aw-icon>
|
|
||||||
</i>
|
|
||||||
<span class="MenuItem-helpTitle">Setup</span>
|
|
||||||
</a>
|
|
||||||
<a link-to="portal" menu-item class="MenuItem MenuItem--fixed">
|
|
||||||
<i class="MenuItem-icon" title="Portal Mode" data-placement="bottom">
|
|
||||||
<aw-icon name="PortalMode"></aw-icon>
|
|
||||||
</i>
|
|
||||||
<span class="MenuItem-helpTitle">Portal Mode</span>
|
|
||||||
</a>
|
|
||||||
<a link-to="signOut" class="MenuItem MenuItem--fixed">
|
|
||||||
<i class="MenuItem-icon" title="Sign Out" data-placement="bottom">
|
|
||||||
<aw-icon name="Signout"></aw-icon>
|
|
||||||
</i>
|
|
||||||
<span class="MenuItem-helpTitle" data-placement="bottom">Sign Out</span>
|
|
||||||
</a>
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
/** @define MenuItem */
|
|
||||||
|
|
||||||
@import (reference) "../shared/utilities/icons.less";
|
|
||||||
@import "../shared/branding/colors.less";
|
|
||||||
|
|
||||||
.MenuItem {
|
|
||||||
@menu-breakpoint: 870px;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex: none;
|
|
||||||
|
|
||||||
transition: color 60ms;
|
|
||||||
|
|
||||||
padding: 0 1rem;
|
|
||||||
min-height: 5.8rem;
|
|
||||||
min-width: 9.2rem;
|
|
||||||
margin-right: 2rem;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
&, &:active, &:focus {
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: @blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: @menu-breakpoint) {
|
|
||||||
flex: 1 1 0;
|
|
||||||
padding: 0 1rem;
|
|
||||||
margin-right: 0;
|
|
||||||
border-bottom: solid thin rgb(202, 202, 202);
|
|
||||||
|
|
||||||
.tooltip, &+.tooltip {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--logo {
|
|
||||||
border: none;
|
|
||||||
display: flex;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--fixed {
|
|
||||||
flex: none;
|
|
||||||
min-width: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
@media screen and (min-width: (@menu-breakpoint + 1px)) {
|
|
||||||
padding: 0 0.75rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-helpTitle {
|
|
||||||
@media screen and (min-width: (@menu-breakpoint + 1px)) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-hoverIcon {
|
|
||||||
width: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--active {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--right {
|
|
||||||
// Push this and all following elements to the right
|
|
||||||
margin-left: auto;
|
|
||||||
@media screen and (max-width: @menu-breakpoint) {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--setup {
|
|
||||||
&:before {
|
|
||||||
.icon(@fa-var-cogs);
|
|
||||||
padding-right: 0.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--popup {
|
|
||||||
// Make pseudo button
|
|
||||||
background: transparent;
|
|
||||||
border: 0;
|
|
||||||
padding: 8px 0;
|
|
||||||
|
|
||||||
outline: none;
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
.icon(@fa-var-angle-down);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-logo {
|
|
||||||
height: 34px;
|
|
||||||
width: 165px; // IE11 needs hard-coded with to calculate correct dimensions for image in flexbox
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-icon {
|
|
||||||
height: 17px;
|
|
||||||
width: auto;
|
|
||||||
max-width: 18px;
|
|
||||||
flex: none;
|
|
||||||
box-sizing: initial;
|
|
||||||
&--labelled {
|
|
||||||
margin-right: 0.25rem;
|
|
||||||
}
|
|
||||||
@media screen and (max-width: @menu-breakpoint) {
|
|
||||||
display: none;
|
|
||||||
margin-right: 1.4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--socketStatus {
|
|
||||||
@media screen and (max-width: @menu-breakpoint) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--username {
|
|
||||||
padding-right: 0.25rem;
|
|
||||||
@media screen and (max-width: @menu-breakpoint) {
|
|
||||||
order: 1; // moves this to the bottom
|
|
||||||
}
|
|
||||||
@media screen and (min-width: (@menu-breakpoint + 1px)) {
|
|
||||||
max-width: 110px;
|
|
||||||
margin-left: -3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--projects {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
/* jshint unused: vars */
|
|
||||||
|
|
||||||
export default ['$route', '$rootScope', function($route, $rootScope) {
|
|
||||||
return {
|
|
||||||
require: '?^^mainMenu',
|
|
||||||
link: function(scope, element, attrs, mainMenuController) {
|
|
||||||
var routeName = attrs.linkTo;
|
|
||||||
|
|
||||||
scope.$on('$routeChangeStart', function() {
|
|
||||||
// any time we start a route change,
|
|
||||||
// assume it was from the menu, and
|
|
||||||
// close it in case it's open
|
|
||||||
mainMenuController.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
scope.$on('$routeChangeSuccess', function(e, nextRoute) {
|
|
||||||
if (nextRoute.$$route && nextRoute.$$route.name === routeName) {
|
|
||||||
element.addClass('MenuItem--active');
|
|
||||||
} else {
|
|
||||||
element.removeClass('MenuItem--active');
|
|
||||||
}
|
|
||||||
if (nextRoute.$$route) {
|
|
||||||
return nextRoute.$$route.name;
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
scope.$on('$destroy', function() {
|
|
||||||
element.off('click.menu-item');
|
|
||||||
});
|
|
||||||
|
|
||||||
element.on('click', function(e) {
|
|
||||||
mainMenuController.close();
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}];
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<a href="#portal" title="Portal" class="MenuItem MenuItem--hoverable">
|
|
||||||
Portal
|
|
||||||
</a>
|
|
||||||
<web-socket-status class="MenuItem MenuItem--socketStatus MenuItem--right MenuItem--fixed"></web-socket-status>
|
|
||||||
<a link-to="userEdit" model="{ user_id: currentUser }" class="MenuItem MenuItem--fixed MenuItem-username">
|
|
||||||
<i class="MenuItem-icon MenuItem-icon--labelled">
|
|
||||||
<aw-icon name="User"></aw-icon>
|
|
||||||
</i>
|
|
||||||
<span class="u-truncatedText">
|
|
||||||
{{currentUser.username}}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<a link-to="dashboard" class="MenuItem MenuItem--fixed" title="Portal Mode">
|
|
||||||
<i class="MenuItem-icon" title="Exit Portal Mode" data-placement="bottom">
|
|
||||||
<aw-icon name="PortalMode--exit"></aw-icon>
|
|
||||||
</i>
|
|
||||||
<span class="MenuItem-helpTitle">Exit Portal Mode</span>
|
|
||||||
</a>
|
|
||||||
<a link-to="signOut" class="MenuItem MenuItem--fixed">
|
|
||||||
<i class="MenuItem-icon" title="Sign Out" data-placement="bottom">
|
|
||||||
<aw-icon name="Signout"></aw-icon>
|
|
||||||
</i>
|
|
||||||
<span class="MenuItem-helpTitle" data-placement="bottom">Sign Out</span>
|
|
||||||
</a>
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/** @define MenuToggle */
|
|
||||||
|
|
||||||
.MenuToggle {
|
|
||||||
|
|
||||||
align-self: center;
|
|
||||||
|
|
||||||
// Width & height hardcoded for IE
|
|
||||||
width: 12px;
|
|
||||||
height: 10px;
|
|
||||||
|
|
||||||
|
|
||||||
&-icon {
|
|
||||||
// Override settings (on svg tag) from nv.d3.css that really
|
|
||||||
// shouldn't be there, but are, so meh.
|
|
||||||
display: inherit;
|
|
||||||
width: inherit;
|
|
||||||
height: inherit;
|
|
||||||
// border: solid thin white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-bar {
|
|
||||||
fill: #000;
|
|
||||||
transition: transform 0.25s cubic-bezier(.96,-0.6,.27,1.68), opacity 0.25s;
|
|
||||||
transform: rotate(0deg);
|
|
||||||
transform-origin: center center;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
&--open {
|
|
||||||
.MenuToggle-crossBar1 {
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
|
||||||
.MenuToggle-topBar, .MenuToggle-bottomBar {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.MenuToggle-crossBar2 {
|
|
||||||
transform: rotate(-45deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
/* jshint unused: vars */
|
|
||||||
|
|
||||||
export default
|
|
||||||
[ 'templateUrl',
|
|
||||||
function(templateUrl) {
|
|
||||||
return {
|
|
||||||
templateUrl: templateUrl('main-menu/menu-toggle'),
|
|
||||||
restrict: 'E',
|
|
||||||
require: '^^mainMenu',
|
|
||||||
scope: {
|
|
||||||
width: '@',
|
|
||||||
height: '@',
|
|
||||||
barHeight: '@'
|
|
||||||
},
|
|
||||||
link: function(scope, element, attrs, mainMenuController) {
|
|
||||||
scope.$on('$destroy', function() {
|
|
||||||
element.off('click');
|
|
||||||
});
|
|
||||||
|
|
||||||
element.on("click", function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
scope.$apply(function() {
|
|
||||||
mainMenuController.toggle();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<button menu-toggle class="MenuToggle Button--pseudo">
|
|
||||||
<svg class="MenuToggle-icon" width="12" height="10">
|
|
||||||
<rect class="MenuToggle-bar MenuToggle-topBar" x="0" y="0" width="100%" height="2" />
|
|
||||||
<rect class="MenuToggle-bar MenuToggle-crossBar1" x="0" y="4" width="100%" height="2" />
|
|
||||||
<rect class="MenuToggle-bar MenuToggle-crossBar2" x="0" y="4" width="100%" height="2" />
|
|
||||||
<rect class="MenuToggle-bar MenuToggle-bottomBar" x="0" y="8" width="100%" height="2" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
export default
|
|
||||||
[ 'templateUrl',
|
|
||||||
function(templateUrl) {
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
templateUrl: templateUrl('main-menu/menu-portal'),
|
|
||||||
link: function(scope, element) {
|
|
||||||
var contents = element.contents();
|
|
||||||
contents.unwrap();
|
|
||||||
|
|
||||||
scope.$on('$destroy', function() {
|
|
||||||
contents.remove();
|
|
||||||
$(".MenuItem--socketStatus").remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/* jshint unused: vars */
|
|
||||||
|
|
||||||
export default
|
|
||||||
[ '$rootScope',
|
|
||||||
'templateUrl',
|
|
||||||
function($rootScope, templateUrl) {
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
templateUrl: templateUrl('main-menu/web-socket-status'),
|
|
||||||
link: function(scope, element, attrs) {
|
|
||||||
scope.socketHelp = $rootScope.socketHelp;
|
|
||||||
scope.socketTip = $rootScope.socketTip;
|
|
||||||
$rootScope.$watch('socketStatus', function(newStatus) {
|
|
||||||
scope.socketStatus = newStatus;
|
|
||||||
});
|
|
||||||
$rootScope.$watch('socketTip', function(newTip) {
|
|
||||||
scope.socketTip = newTip;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<button
|
|
||||||
aw-tool-tip="{{socketTip}}"
|
|
||||||
tip-watch="socketTip"
|
|
||||||
data-placement="bottom"
|
|
||||||
data-trigger="hover"
|
|
||||||
data-container="body"
|
|
||||||
class="Button--pseudo"
|
|
||||||
ng-if="socketStatus !== 'ok'">
|
|
||||||
<i class="fa icon-socket-{{socketStatus}}"></i>
|
|
||||||
</button>
|
|
||||||
@@ -40,10 +40,7 @@
|
|||||||
|
|
||||||
<include-svg href="{{ STATIC_URL }}assets/icons.svg"></include-svg>
|
<include-svg href="{{ STATIC_URL }}assets/icons.svg"></include-svg>
|
||||||
|
|
||||||
<main-menu
|
<main-menu></main-menu>
|
||||||
menu-style="portalMode ? 'portal' : 'default'"
|
|
||||||
current-user="current_user">
|
|
||||||
</main-menu>
|
|
||||||
|
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="container-fluid" id="#content-container">
|
<div class="container-fluid" id="#content-container">
|
||||||
|
|||||||
Reference in New Issue
Block a user