mirror of
https://github.com/ansible/awx.git
synced 2026-06-30 02:48:03 -02:30
implement componenitized navigation and remove old nav and layout code
This commit is contained in:
172
awx/ui/client/lib/components/layout/_index.less
Normal file
172
awx/ui/client/lib/components/layout/_index.less
Normal file
@@ -0,0 +1,172 @@
|
||||
.at-Layout {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
|
||||
&-topNav {
|
||||
display: flex;
|
||||
background-color: @at-color-top-nav-background;
|
||||
border-bottom: @at-border-default-width solid @at-color-top-nav-border-bottom;
|
||||
z-index: @at-z-index-nav;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: @at-height-top-nav;
|
||||
|
||||
.at-Layout-topNavRightAligner {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.at-Layout-topNavItem {
|
||||
color: @at-color-top-nav-item-text;
|
||||
padding: 0 @at-padding-top-nav-item-sides;
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a, div {
|
||||
color: @at-color-top-nav-item-text;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
i {
|
||||
color: @at-color-top-nav-item-icon;
|
||||
font-size: @at-height-top-nav-item-icon;
|
||||
}
|
||||
|
||||
&--logo {
|
||||
padding-left: 0px;
|
||||
|
||||
img {
|
||||
max-width: @main-menu-max-width;
|
||||
max-height: @main-menu-max-height;
|
||||
height: @main-menu-height;
|
||||
width: @main-menu-width;
|
||||
margin: @main-menu-margin;
|
||||
flex: initial;
|
||||
}
|
||||
}
|
||||
|
||||
&--user {
|
||||
i {
|
||||
margin-right: @at-margin-top-nav-item-between-icon-and-name;
|
||||
}
|
||||
}
|
||||
|
||||
&--socket {
|
||||
i {
|
||||
margin-top: @at-margin-top-nav-item-icon-socket-top-makeup;
|
||||
font-size: @at-height-top-nav-item-icon-socket;
|
||||
text-shadow:
|
||||
-@at-border-default-width -@at-border-default-width 0 @at-color-top-nav-item-icon-socket-outline,
|
||||
@at-border-default-width -@at-border-default-width 0 @at-color-top-nav-item-icon-socket-outline,
|
||||
-@at-border-default-width @at-border-default-width 0 @at-color-top-nav-item-icon-socket-outline,
|
||||
@at-border-default-width @at-border-default-width 0 @at-color-top-nav-item-icon-socket-outline
|
||||
}
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:hover,
|
||||
&.is-currentRoute {
|
||||
background-color: @at-color-top-nav-item-background-hover;
|
||||
}
|
||||
|
||||
&.is-loggedOut {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-side {
|
||||
background: @at-color-side-nav-background;
|
||||
color: @at-color-side-nav-content;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
top: @at-height-top-side-nav-makeup;
|
||||
overflow-y: auto;
|
||||
min-height: 100vh;
|
||||
min-width: @at-width-collapsed-side-nav;
|
||||
|
||||
.at-Layout-sideNavItem {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
|
||||
i {
|
||||
font-size: @at-height-side-nav-item-icon;
|
||||
padding: @at-padding-side-nav-item-icon;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.is-active {
|
||||
background: @at-color-side-nav-item-background-hover;
|
||||
border-left: @at-highlight-left-border-size solid @at-color-side-nav-item-border-hover;
|
||||
|
||||
i {
|
||||
margin-left: @at-highlight-left-border-margin-makeup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.at-Layout-sideNavSpacer {
|
||||
height: @at-height-side-nav-spacer;
|
||||
}
|
||||
|
||||
&--expanded {
|
||||
width: @at-width-expanded-side-nav;
|
||||
|
||||
.at-Layout-sideNavItem {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding-right: @at-padding-between-side-nav-icon-text;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
+ .at-Layout-main {
|
||||
padding-left: @at-width-expanded-side-nav;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-main {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
padding-left: @at-width-collapsed-side-nav;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
&-footer {
|
||||
height: 40px;
|
||||
background-color: @at-color-footer-background;
|
||||
color: @at-color-footer;
|
||||
z-index: 1040;
|
||||
position: absolute;
|
||||
right: @at-padding-footer-right;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-left: (@at-width-collapsed-side-nav + 5);
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
margin-right: @at-margin-after-footer-link;
|
||||
}
|
||||
}
|
||||
|
||||
&-side--expanded {
|
||||
+ .at-Layout-main {
|
||||
.at-Layout-footer {
|
||||
margin-left: @at-width-expanded-side-nav;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
awx/ui/client/lib/components/layout/layout.directive.js
Normal file
52
awx/ui/client/lib/components/layout/layout.directive.js
Normal file
@@ -0,0 +1,52 @@
|
||||
function AtLayoutController ($scope, strings) {
|
||||
let vm = this || {};
|
||||
|
||||
$scope.$on('$stateChangeSuccess', function(event, next) {
|
||||
vm.currentState = next.name;
|
||||
});
|
||||
|
||||
$scope.$watch('$root.current_user', function(val) {
|
||||
vm.isLoggedIn = val && val.username;
|
||||
if (val) {
|
||||
vm.isSuperUser = $scope.$root.user_is_superuser || $scope.$root.user_is_system_auditor;
|
||||
vm.currentUsername = val.username;
|
||||
vm.currentUserId = val.id;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('$root.socketStatus', function(newStatus) {
|
||||
vm.socketState = newStatus;
|
||||
vm.socketIconClass = "icon-socket-" + $scope.socketStatus;
|
||||
});
|
||||
|
||||
$scope.$watch('$root.licenseMissing', function(licenseMissing) {
|
||||
vm.licenseIsMissing = licenseMissing;
|
||||
});
|
||||
|
||||
vm.getString = function(string) {
|
||||
try {
|
||||
return strings.get(`layout.${string}`);
|
||||
} catch(err) {
|
||||
return strings.get(string);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AtLayoutController.$inject = ['$scope', 'ComponentsStrings'];
|
||||
|
||||
function atLayout (pathService) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
templateUrl: pathService.getPartialPath('components/layout/layout'),
|
||||
controller: AtLayoutController,
|
||||
controllerAs: 'vm',
|
||||
scope: {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
atLayout.$inject = ['PathService'];
|
||||
|
||||
export default atLayout;
|
||||
85
awx/ui/client/lib/components/layout/layout.partial.html
Normal file
85
awx/ui/client/lib/components/layout/layout.partial.html
Normal file
@@ -0,0 +1,85 @@
|
||||
<div class="at-Layout">
|
||||
<div class="at-Layout-topNav">
|
||||
<at-top-nav-item is-shown="missingLicense" class="at-Layout-topNavItem--logo">
|
||||
<a href="/#/">
|
||||
<img ng-src="/static/assets/logo-header.svg">
|
||||
</a>
|
||||
</at-top-nav-item>
|
||||
<div class="at-Layout-topNavRightAligner"></div>
|
||||
<at-top-nav-item class="at-Layout-topNavItem--user">
|
||||
<a ng-href="/#/users/{{ $parent.layoutVm.currentUserId }}">
|
||||
<i class="fa fa-user"
|
||||
alt="{{ $parent.layoutVm.getString('CURRENT_USER_LABEL') }} {{ $parent.layoutVm.currentUsername }}">
|
||||
</i>
|
||||
<span>{{ $parent.layoutVm.currentUsername }}</span>
|
||||
</a>
|
||||
</at-top-nav-item>
|
||||
<at-top-nav-item>
|
||||
<a href="http://docs.ansible.com/ansible-tower/" target="_blank">
|
||||
<i class="fa fa-book" alt="{{ $parent.layoutVm.getString('VIEW_DOCS') }}"></i>
|
||||
</a>
|
||||
</at-top-nav-item>
|
||||
<at-top-nav-item class="at-Layout-topNavItem--socket"
|
||||
ng-if="$parent.layoutVm.socketState &&
|
||||
$parent.layoutVm.socketState !== 'ok'">
|
||||
<div><i class="fa" ng-class="$parent.layoutVm.socketIconClass"></i></div>
|
||||
</at-top-nav-item>
|
||||
<at-top-nav-item is-shown="missingLicense">
|
||||
<a href="/#/logout" is-always-shown="license">
|
||||
<i class="fa fa-power-off" alt="{{ $parent.layoutVm.getString('LOGOUT') }}"></i>
|
||||
</a>
|
||||
</at-top-nav-item>
|
||||
</div>
|
||||
<at-side-nav>
|
||||
<at-side-nav-item icon-class="fa-tachometer" route="dashboard" name="DASHBOARD">
|
||||
</at-side-nav-item>
|
||||
<at-side-nav-item icon-class="fa-spinner" route="jobs" name="JOBS">
|
||||
</at-side-nav-item>
|
||||
<at-side-nav-item icon-class="fa-calendar" route="jobs.schedules" name="SCHEDULES">
|
||||
</at-side-nav-item>
|
||||
<at-side-nav-item icon-class="fa-columns" route="portalMode.myJobs" name="PORTAL_MODE">
|
||||
</at-side-nav-item>
|
||||
<div class="at-Layout-sideNavSpacer"></div>
|
||||
<at-side-nav-item icon-class="fa-folder-open" route="projects" name="PROJECTS">
|
||||
</at-side-nav-item>
|
||||
<at-side-nav-item icon-class="fa-key" route="credentials" name="CREDENTIALS">
|
||||
</at-side-nav-item>
|
||||
<at-side-nav-item icon-class="fa-list-alt" route="credentialTypes" name="CREDENTIAL_TYPES"
|
||||
system-admin-only="true">
|
||||
</at-side-nav-item>
|
||||
<at-side-nav-item icon-class="fa-sitemap" route="inventories" name="INVENTORIES">
|
||||
</at-side-nav-item>
|
||||
<at-side-nav-item icon-class="fa-pencil-square-o" route="templates" name="TEMPLATES">
|
||||
</at-side-nav-item>
|
||||
<div class="at-Layout-sideNavSpacer"></div>
|
||||
<at-side-nav-item icon-class="fa-building" route="organizations" name="ORGANIZATIONS">
|
||||
</at-side-nav-item>
|
||||
<at-side-nav-item icon-class="fa-user" route="users" name="USERS">
|
||||
</at-side-nav-item>
|
||||
<at-side-nav-item icon-class="fa-users" route="teams" name="TEAMS">
|
||||
</at-side-nav-item>
|
||||
<div class="at-Layout-sideNavSpacer"></div>
|
||||
<at-side-nav-item icon-class="fa-code" route="inventoryScripts" name="INVENTORY_SCRIPTS">
|
||||
</at-side-nav-item>
|
||||
<at-side-nav-item icon-class="fa-bell" route="notifications" name="NOTIFICATIONS"
|
||||
system-admin-only="true">
|
||||
</at-side-nav-item>
|
||||
<at-side-nav-item icon-class="fa-wrench" route="managementJobsList" name="MANAGEMENT_JOBS"
|
||||
system-admin-only="true">
|
||||
</at-side-nav-item>
|
||||
<at-side-nav-item icon-class="fa-server" route="instanceGroups" name="INSTANCE_GROUPS"
|
||||
system-admin-only="true">
|
||||
</at-side-nav-item>
|
||||
<div class="at-Layout-sideNavSpacer"></div>
|
||||
<at-side-nav-item icon-class="fa-cog" route="configuration" name="SETTINGS"
|
||||
system-admin-only="true">
|
||||
</at-side-nav-item>
|
||||
</at-side-nav>
|
||||
<div class="at-Layout-main">
|
||||
<ng-transclude></ng-transclude>
|
||||
<div class="at-Layout-footer">
|
||||
<a ui-sref="about">{{ vm.getString('FOOTER_ABOUT') }} {{ vm.getString('BRAND_NAME') }}</a>|
|
||||
{{ vm.getString('FOOTER_COPYRIGHT') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,51 @@
|
||||
function atSideNavItemLink (scope, element, attrs, ctrl) {
|
||||
scope.navVm = ctrl[0];
|
||||
scope.layoutVm = ctrl[1];
|
||||
}
|
||||
|
||||
function AtSideNavItemController ($state, $scope) {
|
||||
let vm = this || {};
|
||||
|
||||
$scope.$watch('layoutVm.currentState', function(current) {
|
||||
if ($scope.name === 'portal mode') {
|
||||
vm.isRoute = (current && current.indexOf('portalMode') === 0);
|
||||
} else {
|
||||
if (current && current.indexOf($scope.route) === 0) {
|
||||
if (current.indexOf('jobs.schedules') === 0 && $scope.route === 'jobs') {
|
||||
vm.isRoute = false;
|
||||
} else {
|
||||
vm.isRoute = true;
|
||||
}
|
||||
} else {
|
||||
vm.isRoute = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
vm.go = function() {
|
||||
$state.go($scope.route, {}, {reload: true});
|
||||
}
|
||||
}
|
||||
|
||||
AtSideNavItemController.$inject = ['$state', '$scope'];
|
||||
|
||||
function atSideNavItem (pathService) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: pathService.getPartialPath('components/layout/side-nav-item'),
|
||||
require: ['^^atSideNav', '^^atLayout'],
|
||||
controller: AtSideNavItemController,
|
||||
controllerAs: 'vm',
|
||||
link: atSideNavItemLink,
|
||||
scope: {
|
||||
iconClass: '@',
|
||||
name: '@',
|
||||
route: '@',
|
||||
systemAdminOnly: '@'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
atSideNavItem.$inject = ['PathService'];
|
||||
|
||||
export default atSideNavItem;
|
||||
@@ -0,0 +1,8 @@
|
||||
<div class="at-Layout-sideNavItem" ng-click="vm.go()" ng-class="{'is-active': vm.isRoute}"
|
||||
ng-show="(!systemAdminOnly || layoutVm.isSuperUser) && layoutVm.isLoggedIn &&
|
||||
!layoutVm.licenseIsMissing">
|
||||
<i class="fa {{ iconClass }}"></i>
|
||||
<span class="at-Layout-sideNavItemName" ng-show="navVm.isExpanded">
|
||||
{{ layoutVm.getString(name) }}
|
||||
</span>
|
||||
</div>
|
||||
32
awx/ui/client/lib/components/layout/side-nav.directive.js
Normal file
32
awx/ui/client/lib/components/layout/side-nav.directive.js
Normal file
@@ -0,0 +1,32 @@
|
||||
function atSideNavLink (scope, element, attrs, ctrl) {
|
||||
scope.layoutVm = ctrl;
|
||||
}
|
||||
|
||||
function AtSideNavController () {
|
||||
let vm = this || {};
|
||||
|
||||
vm.isExpanded = true;
|
||||
|
||||
vm.toggleExpansion = () => {
|
||||
vm.isExpanded = !vm.isExpanded;
|
||||
}
|
||||
}
|
||||
|
||||
function atSideNav (pathService) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
require: '^^atLayout',
|
||||
controller: AtSideNavController,
|
||||
controllerAs: 'vm',
|
||||
link: atSideNavLink,
|
||||
transclude: true,
|
||||
templateUrl: pathService.getPartialPath('components/layout/side-nav'),
|
||||
scope: {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
atSideNav.$inject = ['PathService'];
|
||||
|
||||
export default atSideNav;
|
||||
@@ -0,0 +1,8 @@
|
||||
<div class="at-Layout-side"
|
||||
ng-class="{'at-Layout-side--expanded': vm.isExpanded && layoutVm.isLoggedIn}">
|
||||
<div class="at-Layout-sideNavItem" ng-click="vm.toggleExpansion()"
|
||||
ng-show="layoutVm.isLoggedIn && !layoutVm.licenseIsMissing">
|
||||
<i class="fa fa-bars"></i>
|
||||
</div>
|
||||
<ng-transclude></ng-transclude>
|
||||
</div>
|
||||
@@ -0,0 +1,30 @@
|
||||
function atTopNavItemLink (scope, element, attrs, ctrl) {
|
||||
scope.layoutVm = ctrl;
|
||||
|
||||
scope.isHidden = false;
|
||||
|
||||
var shownWhen = attrs.isShown;
|
||||
|
||||
if (shownWhen !== 'missingLicense') {
|
||||
scope.$watch('layoutVm.licenseIsMissing', function(val) {
|
||||
scope.isHidden = val;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function atTopNavItem (pathService) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
templateUrl: pathService.getPartialPath('components/layout/top-nav-item'),
|
||||
require: '^^atLayout',
|
||||
link: atTopNavItemLink,
|
||||
scope: {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
atTopNavItem.$inject = ['PathService'];
|
||||
|
||||
export default atTopNavItem;
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="at-Layout-topNavItem" ng-class="{'is-loggedOut': !layoutVm.isLoggedIn}"
|
||||
ng-show="!isHidden" ng-transclude>
|
||||
</div>
|
||||
Reference in New Issue
Block a user