diff --git a/awx/ui_next/src/App.jsx b/awx/ui_next/src/App.jsx
index 88c91406c0..59a8e28cd9 100644
--- a/awx/ui_next/src/App.jsx
+++ b/awx/ui_next/src/App.jsx
@@ -12,7 +12,11 @@ import { ErrorBoundary } from 'react-error-boundary';
import { I18nProvider } from '@lingui/react';
import { i18n } from '@lingui/core';
import { Card, PageSection } from '@patternfly/react-core';
-import { ConfigProvider, useAuthorizedPath } from './contexts/Config';
+import {
+ ConfigProvider,
+ useAuthorizedPath,
+ useUserProfile,
+} from './contexts/Config';
import { SessionProvider, useSession } from './contexts/Session';
import AppContainer from './components/AppContainer';
import Background from './components/Background';
@@ -38,6 +42,17 @@ function ErrorFallback({ error }) {
);
}
+const RenderAppContainer = () => {
+ const userProfile = useUserProfile();
+ const navRouteConfig = getRouteConfig(userProfile);
+
+ return (
+
+
+
+ );
+};
+
const AuthorizedRoutes = ({ routeConfig }) => {
const isAuthorized = useAuthorizedPath();
const match = useRouteMatch();
@@ -150,9 +165,7 @@ function App() {
-
-
-
+
diff --git a/awx/ui_next/src/components/AppContainer/NavExpandableGroup.jsx b/awx/ui_next/src/components/AppContainer/NavExpandableGroup.jsx
index 80d7c2dc2c..bfd5636b33 100644
--- a/awx/ui_next/src/components/AppContainer/NavExpandableGroup.jsx
+++ b/awx/ui_next/src/components/AppContainer/NavExpandableGroup.jsx
@@ -27,7 +27,7 @@ class NavExpandableGroup extends Component {
render() {
const { groupId, groupTitle, routes } = this.props;
- if (routes.length === 1) {
+ if (routes.length === 1 && groupId === 'settings') {
const [{ path }] = routes;
return (
diff --git a/awx/ui_next/src/contexts/Config.jsx b/awx/ui_next/src/contexts/Config.jsx
index e87c2a24f2..5580bd9192 100644
--- a/awx/ui_next/src/contexts/Config.jsx
+++ b/awx/ui_next/src/contexts/Config.jsx
@@ -3,7 +3,7 @@ import { useRouteMatch } from 'react-router-dom';
import { t } from '@lingui/macro';
-import { ConfigAPI, MeAPI } from '../api';
+import { ConfigAPI, MeAPI, UsersAPI, OrganizationsAPI } from '../api';
import useRequest, { useDismissableError } from '../util/useRequest';
import AlertModal from '../components/AlertModal';
import ErrorDetail from '../components/ErrorDetail';
@@ -35,9 +35,32 @@ export const ConfigProvider = ({ children }) => {
},
},
] = await Promise.all([ConfigAPI.read(), MeAPI.read()]);
- return { ...data, me };
+
+ const [
+ {
+ data: { count: adminOrgCount },
+ },
+ {
+ data: { count: notifAdminCount },
+ },
+ {
+ data: { count: execEnvAdminCount },
+ },
+ ] = await Promise.all([
+ UsersAPI.readAdminOfOrganizations(me?.id),
+ OrganizationsAPI.read({
+ page_size: 1,
+ role_level: 'notification_admin_role',
+ }),
+ OrganizationsAPI.read({
+ page_size: 1,
+ role_level: 'execution_environment_admin_role',
+ }),
+ ]);
+
+ return { ...data, me, adminOrgCount, notifAdminCount, execEnvAdminCount };
}, []),
- {}
+ { adminOrgCount: 0, notifAdminCount: 0, execEnvAdminCount: 0 }
);
const { error, dismissError } = useDismissableError(configError);
@@ -77,6 +100,17 @@ export const ConfigProvider = ({ children }) => {
);
};
+export const useUserProfile = () => {
+ const config = useConfig();
+ return {
+ isSuperUser: !!config.me?.is_superuser,
+ isSystemAuditor: !!config.me?.is_system_auditor,
+ isOrgAdmin: config.adminOrgCount,
+ isNotificationAdmin: config.notifAdminCount,
+ isExecEnvAdmin: config.execEnvAdminCount,
+ };
+};
+
export const useAuthorizedPath = () => {
const config = useConfig();
const subscriptionMgmtRoute = useRouteMatch({
diff --git a/awx/ui_next/src/routeConfig.jsx b/awx/ui_next/src/routeConfig.jsx
index fba640a2d8..5d29072fcd 100644
--- a/awx/ui_next/src/routeConfig.jsx
+++ b/awx/ui_next/src/routeConfig.jsx
@@ -22,8 +22,8 @@ import Users from './screens/User';
import WorkflowApprovals from './screens/WorkflowApproval';
import { Jobs } from './screens/Job';
-function getRouteConfig() {
- return [
+function getRouteConfig(userProfile = {}) {
+ let routeConfig = [
{
groupTitle: Views,
groupId: 'views_group',
@@ -155,6 +155,29 @@ function getRouteConfig() {
],
},
];
+
+ const deleteRoute = name => {
+ routeConfig.forEach(group => {
+ group.routes = group.routes.filter(({ path }) => !path.includes(name));
+ });
+ routeConfig = routeConfig.filter(groups => groups.routes.length);
+ };
+
+ const deleteRouteGroup = name => {
+ routeConfig = routeConfig.filter(({ groupId }) => !groupId.includes(name));
+ };
+
+ if (userProfile?.isSuperUser || userProfile?.isSystemAuditor)
+ return routeConfig;
+ deleteRouteGroup('settings');
+ deleteRoute('management_jobs');
+ deleteRoute('credential_types');
+ if (userProfile?.isOrgAdmin) return routeConfig;
+ deleteRoute('applications');
+ deleteRoute('instance_groups');
+ if (!userProfile?.isNotificationAdmin) deleteRoute('notification_templates');
+
+ return routeConfig;
}
export default getRouteConfig;
diff --git a/awx/ui_next/src/routeConfig.test.jsx b/awx/ui_next/src/routeConfig.test.jsx
new file mode 100644
index 0000000000..b4382bbcd0
--- /dev/null
+++ b/awx/ui_next/src/routeConfig.test.jsx
@@ -0,0 +1,248 @@
+import getRouteConfig from './routeConfig';
+
+const userProfile = {
+ isSuperUser: false,
+ isSystemAuditor: false,
+ isOrgAdmin: false,
+ isNotificationAdmin: false,
+ isExecEnvAdmin: false,
+};
+
+const filterPaths = sidebar => {
+ const visibleRoutes = [];
+ sidebar.forEach(({ routes }) => {
+ routes.forEach(route => {
+ visibleRoutes.push(route.path);
+ });
+ });
+
+ return visibleRoutes;
+};
+describe('getRouteConfig', () => {
+ test('routes for system admin', () => {
+ const sidebar = getRouteConfig({ ...userProfile, isSuperUser: true });
+ const filteredPaths = filterPaths(sidebar);
+ expect(filteredPaths).toEqual([
+ '/home',
+ '/jobs',
+ '/schedules',
+ '/activity_stream',
+ '/workflow_approvals',
+ '/templates',
+ '/credentials',
+ '/projects',
+ '/inventories',
+ '/hosts',
+ '/organizations',
+ '/users',
+ '/teams',
+ '/credential_types',
+ '/notification_templates',
+ '/management_jobs',
+ '/instance_groups',
+ '/applications',
+ '/execution_environments',
+ '/settings',
+ ]);
+ });
+
+ test('routes for system auditor', () => {
+ const sidebar = getRouteConfig({ ...userProfile, isSystemAuditor: true });
+ const filteredPaths = filterPaths(sidebar);
+ expect(filteredPaths).toEqual([
+ '/home',
+ '/jobs',
+ '/schedules',
+ '/activity_stream',
+ '/workflow_approvals',
+ '/templates',
+ '/credentials',
+ '/projects',
+ '/inventories',
+ '/hosts',
+ '/organizations',
+ '/users',
+ '/teams',
+ '/credential_types',
+ '/notification_templates',
+ '/management_jobs',
+ '/instance_groups',
+ '/applications',
+ '/execution_environments',
+ '/settings',
+ ]);
+ });
+
+ test('routes for org admin', () => {
+ const sidebar = getRouteConfig({ ...userProfile, isOrgAdmin: true });
+ const filteredPaths = filterPaths(sidebar);
+ expect(filteredPaths).toEqual([
+ '/home',
+ '/jobs',
+ '/schedules',
+ '/activity_stream',
+ '/workflow_approvals',
+ '/templates',
+ '/credentials',
+ '/projects',
+ '/inventories',
+ '/hosts',
+ '/organizations',
+ '/users',
+ '/teams',
+ '/notification_templates',
+ '/instance_groups',
+ '/applications',
+ '/execution_environments',
+ ]);
+ });
+
+ test('routes for notifications admin', () => {
+ const sidebar = getRouteConfig({
+ ...userProfile,
+ isNotificationAdmin: true,
+ });
+ const filteredPaths = filterPaths(sidebar);
+ expect(filteredPaths).toEqual([
+ '/home',
+ '/jobs',
+ '/schedules',
+ '/activity_stream',
+ '/workflow_approvals',
+ '/templates',
+ '/credentials',
+ '/projects',
+ '/inventories',
+ '/hosts',
+ '/organizations',
+ '/users',
+ '/teams',
+ '/notification_templates',
+ '/execution_environments',
+ ]);
+ });
+
+ test('routes for execution environments admin', () => {
+ const sidebar = getRouteConfig({ ...userProfile, isExecEnvAdmin: true });
+ const filteredPaths = filterPaths(sidebar);
+ expect(filteredPaths).toEqual([
+ '/home',
+ '/jobs',
+ '/schedules',
+ '/activity_stream',
+ '/workflow_approvals',
+ '/templates',
+ '/credentials',
+ '/projects',
+ '/inventories',
+ '/hosts',
+ '/organizations',
+ '/users',
+ '/teams',
+ '/execution_environments',
+ ]);
+ });
+
+ test('routes for regular users', () => {
+ const sidebar = getRouteConfig(userProfile);
+ const filteredPaths = filterPaths(sidebar);
+ expect(filteredPaths).toEqual([
+ '/home',
+ '/jobs',
+ '/schedules',
+ '/activity_stream',
+ '/workflow_approvals',
+ '/templates',
+ '/credentials',
+ '/projects',
+ '/inventories',
+ '/hosts',
+ '/organizations',
+ '/users',
+ '/teams',
+ '/execution_environments',
+ ]);
+ });
+
+ test('routes for execution environment admins and notification admin', () => {
+ const sidebar = getRouteConfig({
+ ...userProfile,
+ isExecEnvAdmin: true,
+ isNotificationAdmin: true,
+ });
+ const filteredPaths = filterPaths(sidebar);
+ expect(filteredPaths).toEqual([
+ '/home',
+ '/jobs',
+ '/schedules',
+ '/activity_stream',
+ '/workflow_approvals',
+ '/templates',
+ '/credentials',
+ '/projects',
+ '/inventories',
+ '/hosts',
+ '/organizations',
+ '/users',
+ '/teams',
+ '/notification_templates',
+ '/execution_environments',
+ ]);
+ });
+
+ test('routes for execution environment admins and organization admins', () => {
+ const sidebar = getRouteConfig({
+ ...userProfile,
+ isExecEnvAdmin: true,
+ isOrgAdmin: true,
+ });
+ const filteredPaths = filterPaths(sidebar);
+ expect(filteredPaths).toEqual([
+ '/home',
+ '/jobs',
+ '/schedules',
+ '/activity_stream',
+ '/workflow_approvals',
+ '/templates',
+ '/credentials',
+ '/projects',
+ '/inventories',
+ '/hosts',
+ '/organizations',
+ '/users',
+ '/teams',
+ '/notification_templates',
+ '/instance_groups',
+ '/applications',
+ '/execution_environments',
+ ]);
+ });
+
+ test('routes for notification admins and organization admins', () => {
+ const sidebar = getRouteConfig({
+ ...userProfile,
+ isNotificationAdmin: true,
+ isOrgAdmin: true,
+ });
+ const filteredPaths = filterPaths(sidebar);
+ expect(filteredPaths).toEqual([
+ '/home',
+ '/jobs',
+ '/schedules',
+ '/activity_stream',
+ '/workflow_approvals',
+ '/templates',
+ '/credentials',
+ '/projects',
+ '/inventories',
+ '/hosts',
+ '/organizations',
+ '/users',
+ '/teams',
+ '/notification_templates',
+ '/instance_groups',
+ '/applications',
+ '/execution_environments',
+ ]);
+ });
+});