mirror of
https://github.com/ZwareBear/awx.git
synced 2026-05-06 16:32:01 -05:00
implement componenitized navigation and remove old nav and layout code
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
@import 'action/_index';
|
||||
@import 'input/_index';
|
||||
@import 'layout/_index';
|
||||
@import 'modal/_index';
|
||||
@import 'panel/_index';
|
||||
@import 'popover/_index';
|
||||
|
||||
@@ -47,6 +47,31 @@ function ComponentsStrings (BaseString) {
|
||||
DEFAULT: t.s('Copy full revision to clipboard.'),
|
||||
COPIED: t.s('Copied to clipboard.')
|
||||
}
|
||||
|
||||
ns.layout = {
|
||||
CURRENT_USER_LABEL: t.s('Logged in as'),
|
||||
VIEW_DOCS: t.s('View Documentation'),
|
||||
LOGOUT: t.s('Logout'),
|
||||
DASHBOARD: t.s('Dashboard'),
|
||||
JOBS: t.s('Jobs'),
|
||||
SCHEDULES: t.s('Schedules'),
|
||||
PORTAL_MODE: t.s('Portal Mode'),
|
||||
PROJECTS: t.s('Projects'),
|
||||
CREDENTIALS: t.s('Credentials'),
|
||||
CREDENTIAL_TYPES: t.s('Credential Types'),
|
||||
INVENTORIES: t.s('Inventories'),
|
||||
TEMPLATES: t.s('Templates'),
|
||||
ORGANIZATIONS: t.s('Organizations'),
|
||||
USERS: t.s('Users'),
|
||||
TEAMS: t.s('Teams'),
|
||||
INVENTORY_SCRIPTS: t.s('Inventory Scripts'),
|
||||
NOTIFICATIONS: t.s('Notifications'),
|
||||
MANAGEMENT_JOBS: t.s('Management Jobs'),
|
||||
INSTANCE_GROUPS: t.s('Instance Groups'),
|
||||
SETTINGS: t.s('Settings'),
|
||||
FOOTER_ABOUT: t.s('About'),
|
||||
FOOTER_COPYRIGHT: t.s('Copyright © 2017 Red Hat, Inc.')
|
||||
}
|
||||
}
|
||||
|
||||
ComponentsStrings.$inject = ['BaseStringService'];
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import layout from './layout/layout.directive';
|
||||
import topNavItem from './layout/top-nav-item.directive';
|
||||
import sideNav from './layout/side-nav.directive';
|
||||
import sideNavItem from './layout/side-nav-item.directive';
|
||||
import actionGroup from './action/action-group.directive';
|
||||
import divider from './utility/divider.directive';
|
||||
import form from './form/form.directive';
|
||||
@@ -26,6 +30,10 @@ import ComponentsStrings from './components.strings';
|
||||
|
||||
angular
|
||||
.module('at.lib.components', [])
|
||||
.directive('atLayout', layout)
|
||||
.directive('atTopNavItem', topNavItem)
|
||||
.directive('atSideNav', sideNav)
|
||||
.directive('atSideNavItem', sideNavItem)
|
||||
.directive('atActionGroup', actionGroup)
|
||||
.directive('atDivider', divider)
|
||||
.directive('atForm', form)
|
||||
@@ -50,5 +58,3 @@ angular
|
||||
.directive('atTruncate', truncate)
|
||||
.service('ComponentsStrings', ComponentsStrings)
|
||||
.service('BaseInputController', BaseInputController);
|
||||
|
||||
|
||||
|
||||
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