diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less
index b341f2d21e..2dfc7682e6 100644
--- a/awx/ui/client/legacy-styles/ansible-ui.less
+++ b/awx/ui/client/legacy-styles/ansible-ui.less
@@ -2026,3 +2026,8 @@ tr td button i {
margin-bottom: 15px;
}
}
+
+button.dropdown-toggle,
+.input-group-btn {
+ z-index: 1;
+}
diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js
index 362e89770a..d5fc78bfb2 100644
--- a/awx/ui/client/src/app.js
+++ b/awx/ui/client/src/app.js
@@ -30,6 +30,7 @@ import {JobsListController} from './controllers/Jobs';
import {PortalController} from './controllers/Portal';
import systemTracking from './system-tracking/main';
import inventoryScripts from './inventory-scripts/main';
+import permissions from './permissions/main';
import managementJobs from './management-jobs/main';
import routeExtensions from './shared/route-extensions/main';
import breadcrumbs from './shared/breadcrumbs/main';
@@ -55,7 +56,6 @@ import {AdhocCtrl} from './controllers/Adhoc';
import {AdminsList} from './controllers/Admins';
import {UsersList, UsersAdd, UsersEdit} from './controllers/Users';
import {TeamsList, TeamsAdd, TeamsEdit} from './controllers/Teams';
-import {PermissionsAdd, PermissionsList, PermissionsEdit} from './controllers/Permissions';
import './shared/RestServices';
import './shared/api-loader';
import './shared/form-generator';
@@ -86,6 +86,7 @@ var tower = angular.module('Tower', [
breadcrumbs.name,
systemTracking.name,
inventoryScripts.name,
+ permissions.name,
managementJobs.name,
setupMenu.name,
mainMenu.name,
@@ -140,9 +141,6 @@ var tower = angular.module('Tower', [
'ProjectFormDefinition',
'ProjectStatusDefinition',
'ProjectsHelper',
- 'PermissionFormDefinition',
- 'PermissionListDefinition',
- 'PermissionsHelper',
'CompletedJobsDefinition',
'AllJobsDefinition',
'JobFormDefinition',
@@ -591,39 +589,6 @@ var tower = angular.module('Tower', [
}
}).
- when('/teams/:team_id/permissions/add', {
- name: 'teamPermissionAdd',
- templateUrl: urlPrefix + 'partials/teams.html',
- controller: PermissionsAdd,
- resolve: {
- features: ['FeaturesService', function(FeaturesService) {
- return FeaturesService.get();
- }]
- }
- }).
-
- when('/teams/:team_id/permissions', {
- name: 'teamPermissions',
- templateUrl: urlPrefix + 'partials/teams.html',
- controller: PermissionsList,
- resolve: {
- features: ['FeaturesService', function(FeaturesService) {
- return FeaturesService.get();
- }]
- }
- }).
-
- when('/teams/:team_id/permissions/:permission_id', {
- name: 'teamPermissionEdit',
- templateUrl: urlPrefix + 'partials/teams.html',
- controller: PermissionsEdit,
- resolve: {
- features: ['FeaturesService', function(FeaturesService) {
- return FeaturesService.get();
- }]
- }
- }).
-
when('/teams/:team_id/users', {
name: 'teamUsers',
templateUrl: urlPrefix + 'partials/teams.html',
@@ -789,39 +754,6 @@ var tower = angular.module('Tower', [
}
}).
- when('/users/:user_id/permissions/add', {
- name: 'userPermissionAdd',
- templateUrl: urlPrefix + 'partials/users.html',
- controller: PermissionsAdd,
- resolve: {
- features: ['FeaturesService', function(FeaturesService) {
- return FeaturesService.get();
- }]
- }
- }).
-
- when('/users/:user_id/permissions', {
- name: 'userPermissions',
- templateUrl: urlPrefix + 'partials/users.html',
- controller: PermissionsList,
- resolve: {
- features: ['FeaturesService', function(FeaturesService) {
- return FeaturesService.get();
- }]
- }
- }).
-
- when('/users/:user_id/permissions/:permission_id', {
- name: 'userPermissionEdit',
- templateUrl: urlPrefix + 'partials/users.html',
- controller: PermissionsEdit,
- resolve: {
- features: ['FeaturesService', function(FeaturesService) {
- return FeaturesService.get();
- }]
- }
- }).
-
when('/users/:user_id/credentials/add', {
name: 'userCredentialAdd',
templateUrl: urlPrefix + 'partials/teams.html',
@@ -896,7 +828,7 @@ var tower = angular.module('Tower', [
}]
}
}).
-
+
when('/license', {
name: 'license',
templateUrl: urlPrefix + 'partials/license.html',
diff --git a/awx/ui/client/src/controllers/Permissions.js b/awx/ui/client/src/controllers/Permissions.js
deleted file mode 100644
index 82c05cdff0..0000000000
--- a/awx/ui/client/src/controllers/Permissions.js
+++ /dev/null
@@ -1,409 +0,0 @@
-/*************************************************
- * Copyright (c) 2015 Ansible, Inc.
- *
- * All Rights Reserved
- *************************************************/
-
-/**
- * @ngdoc function
- * @name controllers.function:Permissions
- * @description This controller's for permissions
-*/
-
-
-export function PermissionsList($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, PermissionList,
- GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors,
- GetBasePath, CheckAccess, Wait) {
-
- ClearScope();
-
- var list = PermissionList,
- base = $location.path().replace(/^\//, '').split('/')[0],
- defaultUrl = GetBasePath(base),
- generator = GenerateList;
-
- generator.inject(list, { mode: 'edit', scope: $scope, breadCrumbs: true });
- defaultUrl += ($routeParams.user_id !== undefined) ? $routeParams.user_id : $routeParams.team_id;
- defaultUrl += '/permissions/';
-
- $scope.selected = [];
-
- CheckAccess({
- scope: $scope
- });
-
- if ($scope.removePostRefresh) {
- $scope.removePostRefresh();
- }
- $scope.removePostRefresh = $scope.$on('PostRefresh', function () {
- // Cleanup after a delete
- Wait('stop');
- $('#prompt-modal').modal('hide');
- });
-
- SearchInit({
- scope: $scope,
- set: 'permissions',
- list: list,
- url: defaultUrl
- });
- PaginateInit({
- scope: $scope,
- list: list,
- url: defaultUrl
- });
- $scope.search(list.iterator);
-
- LoadBreadCrumbs();
-
- $scope.addPermission = function () {
- if ($scope.PermissionAddAllowed) {
- $location.path($location.path() + '/add');
- }
- };
-
- // if the permission includes adhoc (and is not admin), display that
- $scope.getPermissionText = function () {
- if (this.permission.permission_type !== "admin" && this.permission.run_ad_hoc_commands) {
- return this.permission.permission_type + " + run commands";
- } else {
- return this.permission.permission_type;
- }
- };
-
- $scope.editPermission = function (id) {
- $location.path($location.path() + '/' + id);
- };
-
- $scope.deletePermission = function (id, name) {
- var action = function () {
- $('#prompt-modal').modal('hide');
- Wait('start');
- var url = GetBasePath('base') + 'permissions/' + id + '/';
- Rest.setUrl(url);
- Rest.destroy()
- .success(function () {
- $scope.search(list.iterator);
- })
- .error(function (data, status) {
- Wait('stop');
- ProcessErrors($scope, data, status, null, { hdr: 'Error!',
- msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
- });
- };
-
- if ($scope.PermissionAddAllowed) {
- Prompt({
- hdr: 'Delete',
- body: 'Are you sure you want to delete ' + name + '?',
- action: action
- });
- }
- };
-}
-
-PermissionsList.$inject = ['$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'PermissionList',
- 'generateList', 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller',
- 'ClearScope', 'ProcessErrors', 'GetBasePath', 'CheckAccess', 'Wait'
-];
-
-
-export function PermissionsAdd($scope, $rootScope, $compile, $location, $log, $routeParams, PermissionsForm,
- GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ClearScope,
- GetBasePath, ReturnToCaller, InventoryList, ProjectList, LookUpInit, CheckAccess,
- Wait, PermissionCategoryChange) {
-
- ClearScope();
-
- // Inject dynamic view
- var form = PermissionsForm,
- generator = GenerateForm,
- id = ($routeParams.user_id !== undefined) ? $routeParams.user_id : $routeParams.team_id,
- base = $location.path().replace(/^\//, '').split('/')[0],
- master = {};
-
- generator.inject(form, { mode: 'add', related: false, scope: $scope });
- CheckAccess({ scope: $scope });
- generator.reset();
- LoadBreadCrumbs();
-
- $scope.inventoryrequired = true;
- $scope.projectrequired = false;
- $scope.category = 'Inventory';
- master.category = 'Inventory';
- master.inventoryrequired = true;
- master.projectrequired = false;
- $scope.run_ad_hoc_commands = false;
-
- LookUpInit({
- scope: $scope,
- form: form,
- current_item: null,
- list: InventoryList,
- field: 'inventory',
- input_type: 'radio'
- });
-
- LookUpInit({
- scope: $scope,
- form: form,
- current_item: null,
- list: ProjectList,
- field: 'project',
- input_type: 'radio'
- });
-
- $scope.$watch("category", function(val) {
- if (val === 'Deploy') {
- $scope.projectrequired = true;
- LookUpInit({
- scope: $scope,
- form: form,
- current_item: null,
- list: ProjectList,
- field: 'project',
- input_type: 'radio'
- });
- } else {
- $scope.projectrequired = false;
- }
- });
-
- $scope.changeAdhocCommandCheckbox = function () {
- if ($scope.category === 'Deploy') {
- $scope.run_ad_hoc_command = false;
- } else {
- if ($scope.permission_type === 'admin') {
- $scope.run_ad_hoc_commands = true;
- $("#permission_run_ad_hoc_commands_chbox").attr("disabled", true);
- } else {
- if (!$scope.run_ad_hoc_commands) {
- $scope.run_ad_hoc_commands = false;
- }
- $("#permission_run_ad_hoc_commands_chbox").attr("disabled", false);
- }
- }
- };
-
- // Save
- $scope.formSave = function () {
- var fld, url, data = {};
- generator.clearApiErrors();
- Wait('start');
- if ($scope.PermissionAddAllowed) {
- data = {};
- for (fld in form.fields) {
- data[fld] = $scope[fld];
- }
- // job template (or deploy) based permissions do not have the run
- // ad hoc commands parameter
- if (data.category === "Deploy") {
- data.run_ad_hoc_commands = false;
- } else {
- delete data.project;
- }
-
- url = (base === 'teams') ? GetBasePath('teams') + id + '/permissions/' : GetBasePath('users') + id + '/permissions/';
- Rest.setUrl(url);
- Rest.post(data)
- .success(function () {
- Wait('stop');
- ReturnToCaller(1);
- })
- .error(function (data, status) {
- Wait('stop');
- ProcessErrors($scope, data, status, PermissionsForm, { hdr: 'Error!',
- msg: 'Failed to create new permission. Post returned status: ' + status });
- });
- } else {
- Alert('Access Denied', 'You do not have access to create new permission objects. Please contact a system administrator.',
- 'alert-danger');
- }
- };
-
- // Cancel
- $scope.formReset = function () {
- $rootScope.flashMessage = null;
- generator.reset();
- for (var fld in master) {
- $scope[fld] = master[fld];
- }
- $scope.selectCategory();
- };
-
- $scope.selectCategory = function () {
- PermissionCategoryChange({ scope: $scope, reset: true });
- };
-
-
- $scope.selectCategory();
-
-}
-
-PermissionsAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'PermissionsForm',
- 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ClearScope', 'GetBasePath', 'ReturnToCaller',
- 'InventoryList', 'ProjectList', 'LookUpInit', 'CheckAccess', 'Wait', 'PermissionCategoryChange'
-];
-
-
-export function PermissionsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, PermissionsForm,
- GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, Prompt, GetBasePath,
- InventoryList, ProjectList, LookUpInit, CheckAccess, Wait, PermissionCategoryChange) {
-
- ClearScope();
-
- var generator = GenerateForm,
- form = PermissionsForm,
- base_id = ($routeParams.user_id !== undefined) ? $routeParams.user_id : $routeParams.team_id,
- id = $routeParams.permission_id,
- defaultUrl = GetBasePath('base') + 'permissions/' + id + '/',
- master = {};
-
- $scope.changeAdhocCommandCheckbox = function () {
- if ($scope.category === 'Deploy') {
- $scope.run_ad_hoc_command = false;
- } else {
- if ($scope.permission_type === 'admin') {
- $scope.run_ad_hoc_commands = true;
- $("#permission_run_ad_hoc_commands_chbox").attr("disabled", true);
- } else {
- if (!$scope.run_ad_hoc_commands) {
- $scope.run_ad_hoc_commands = false;
- }
- $("#permission_run_ad_hoc_commands_chbox").attr("disabled", false);
- }
- }
- };
-
- generator.inject(form, { mode: 'edit', related: true, scope: $scope });
- generator.reset();
-
- $scope.selectCategory = function (resetIn) {
- var reset = (resetIn === false) ? false : true;
- PermissionCategoryChange({ scope: $scope, reset: reset });
- };
- if ($scope.removeFillForm) {
- $scope.removeFillForm();
- }
- $scope.removeFillForm = $scope.$on('FillForm', function () {
- // Retrieve detail record and prepopulate the form
- Wait('start');
- Rest.setUrl(defaultUrl);
- Rest.get()
- .success(function (data) {
- var fld, sourceModel, sourceField;
- LoadBreadCrumbs({ path: '/users/' + base_id + '/permissions/' + id, title: data.name });
- for (fld in form.fields) {
- if (data[fld]) {
- if (form.fields[fld].sourceModel) {
- sourceModel = form.fields[fld].sourceModel;
- sourceField = form.fields[fld].sourceField;
- $scope[sourceModel + '_' + sourceField] = data.summary_fields[sourceModel][sourceField];
- master[sourceModel + '_' + sourceField] = data.summary_fields[sourceModel][sourceField];
- }
- $scope[fld] = data[fld];
- master[fld] = $scope[fld];
- }
- }
-
- $scope.category = 'Deploy';
- if (data.permission_type !== 'run' && data.permission_type !== 'check' && data.permission_type !== 'create') {
- $scope.category = 'Inventory';
- }
- master.category = $scope.category;
- $scope.selectCategory(false); //call without resetting $scope.category value
-
- LookUpInit({
- scope: $scope,
- form: form,
- current_item: data.inventory,
- list: InventoryList,
- field: 'inventory',
- input_type: "radio"
- });
-
- LookUpInit({
- scope: $scope,
- form: form,
- current_item: data.project,
- list: ProjectList,
- field: 'project',
- input_type: 'radio'
- });
-
- $scope.changeAdhocCommandCheckbox();
-
- if (!$scope.PermissionAddAllowed) {
- // If not a privileged user, disable access
- $('form[name="permission_form"]').find('select, input, button').each(function () {
- if ($(this).is('input') || $(this).is('select')) {
- $(this).attr('readonly', 'readonly');
- }
- if ($(this).is('input[type="checkbox"]') ||
- $(this).is('input[type="radio"]') ||
- $(this).is('button')) {
- $(this).attr('disabled', 'disabled');
- }
- });
- }
- Wait('stop');
- })
- .error(function (data, status) {
- ProcessErrors($scope, data, status, form, { hdr: 'Error!',
- msg: 'Failed to retrieve Permission: ' + id + '. GET status: ' + status });
- });
- });
-
- CheckAccess({
- scope: $scope,
- callback: 'FillForm'
- });
-
- // Save changes to the parent
- $scope.formSave = function () {
- var fld, data = {};
- generator.clearApiErrors();
- Wait('start');
- for (fld in form.fields) {
- data[fld] = $scope[fld];
- }
- // job template (or deploy) based permissions do not have the run
- // ad hoc commands parameter
- if (data.category === "Deploy") {
- data.run_ad_hoc_commands = false;
- } else {
- delete data.project;
- }
-
- Rest.setUrl(defaultUrl);
- if($scope.category === "Inventory"){
- delete data.project;
- }
- Rest.put(data)
- .success(function () {
- Wait('stop');
- ReturnToCaller(1);
- })
- .error(function (data, status) {
- ProcessErrors($scope, data, status, form, { hdr: 'Error!', msg: 'Failed to update Permission: ' +
- $routeParams.id + '. PUT status: ' + status });
- });
- };
-
-
- // Cancel
- $scope.formReset = function () {
- generator.reset();
- for (var fld in master) {
- $scope[fld] = master[fld];
- }
- $scope.selectCategory(false);
- };
-
-}
-
-PermissionsEdit.$inject = ['$scope', '$rootScope', '$compile', '$location',
- '$log', '$routeParams', 'PermissionsForm', 'GenerateForm', 'Rest', 'Alert',
- 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'Prompt',
- 'GetBasePath', 'InventoryList', 'ProjectList', 'LookUpInit', 'CheckAccess',
- 'Wait', 'PermissionCategoryChange'];
diff --git a/awx/ui/client/src/controllers/Teams.js b/awx/ui/client/src/controllers/Teams.js
index cd60103dbe..ab5dc9298f 100644
--- a/awx/ui/client/src/controllers/Teams.js
+++ b/awx/ui/client/src/controllers/Teams.js
@@ -176,7 +176,7 @@ TeamsAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$r
export function TeamsEdit($scope, $rootScope, $compile, $location, $log, $routeParams, TeamForm, GenerateForm, Rest, Alert, ProcessErrors,
LoadBreadCrumbs, RelatedSearchInit, RelatedPaginateInit, ReturnToCaller, ClearScope, LookUpInit, Prompt, GetBasePath, CheckAccess,
- OrganizationList, Wait, Stream) {
+ OrganizationList, Wait, Stream, permissionsChoices, permissionsLabel, permissionsSearchSelect) {
ClearScope();
@@ -188,10 +188,40 @@ export function TeamsEdit($scope, $rootScope, $compile, $location, $log, $routeP
id = $routeParams.team_id,
relatedSets = {};
+ $scope.permission_label = {};
+ $scope.permission_search_select = [];
+
+ // return a promise from the options request with the permission type choices (including adhoc) as a param
+ var permissionsChoice = permissionsChoices({
+ scope: $scope,
+ url: 'api/v1/' + base + '/' + id + '/permissions/'
+ });
+
+ // manipulate the choices from the options request to be set on
+ // scope and be usable by the list form
+ permissionsChoice.then(function (choices) {
+ choices =
+ permissionsLabel({
+ choices: choices
+ });
+ _.map(choices, function(n, key) {
+ $scope.permission_label[key] = n;
+ });
+ });
+
$scope.team_id = id;
- generator.inject(form, { mode: 'edit', related: true, scope: $scope });
- generator.reset();
+ // manipulate the choices from the options request to be usable
+ // by the search option for permission_type, you can't inject the
+ // list until this is done!
+ permissionsChoice.then(function (choices) {
+ form.related.permissions.fields.permission_type.searchOptions =
+ permissionsSearchSelect({
+ choices: choices
+ });
+ generator.inject(form, { mode: 'edit', related: true, scope: $scope });
+ generator.reset();
+ });
$scope.PermissionAddAllowed = false;
@@ -277,6 +307,15 @@ export function TeamsEdit($scope, $rootScope, $compile, $location, $log, $routeP
'. GET status: ' + status });
});
+ $scope.getPermissionText = function () {
+ if (this.permission.permission_type !== "admin" && this.permission.run_ad_hoc_commands) {
+ return $scope.permission_label[this.permission.permission_type] +
+ " and " + $scope.permission_label.adhoc;
+ } else {
+ return $scope.permission_label[this.permission.permission_type];
+ }
+ };
+
$scope.showActivity = function () {
Stream({ scope: $scope });
};
@@ -392,5 +431,5 @@ export function TeamsEdit($scope, $rootScope, $compile, $location, $log, $routeP
TeamsEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'TeamForm',
'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit', 'RelatedPaginateInit',
- 'ReturnToCaller', 'ClearScope', 'LookUpInit', 'Prompt', 'GetBasePath', 'CheckAccess', 'OrganizationList', 'Wait', 'Stream'
+ 'ReturnToCaller', 'ClearScope', 'LookUpInit', 'Prompt', 'GetBasePath', 'CheckAccess', 'OrganizationList', 'Wait', 'Stream', 'permissionsChoices', 'permissionsLabel', 'permissionsSearchSelect'
];
diff --git a/awx/ui/client/src/controllers/Users.js b/awx/ui/client/src/controllers/Users.js
index b2b9794378..2a37115a16 100644
--- a/awx/ui/client/src/controllers/Users.js
+++ b/awx/ui/client/src/controllers/Users.js
@@ -208,7 +208,7 @@ UsersAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$r
export function UsersEdit($scope, $rootScope, $compile, $location, $log, $routeParams, UserForm, GenerateForm, Rest, Alert,
ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, RelatedPaginateInit, ReturnToCaller, ClearScope, GetBasePath,
- Prompt, CheckAccess, ResetForm, Wait, Stream) {
+ Prompt, CheckAccess, ResetForm, Wait, Stream, permissionsChoices, permissionsLabel, permissionsSearchSelect) {
ClearScope();
@@ -220,12 +220,42 @@ export function UsersEdit($scope, $rootScope, $compile, $location, $log, $routeP
id = $routeParams.user_id,
relatedSets = {};
+ $scope.permission_label = {};
+ $scope.permission_search_select = [];
+
+ // return a promise from the options request with the permission type choices (including adhoc) as a param
+ var permissionsChoice = permissionsChoices({
+ scope: $scope,
+ url: 'api/v1/' + base + '/' + id + '/permissions/'
+ });
+
+ // manipulate the choices from the options request to be set on
+ // scope and be usable by the list form
+ permissionsChoice.then(function (choices) {
+ choices =
+ permissionsLabel({
+ choices: choices
+ });
+ _.map(choices, function(n, key) {
+ $scope.permission_label[key] = n;
+ });
+ });
+
if ($scope.removeFormReady) {
$scope.removeFormReady();
}
$scope.removeFormReady = $scope.$on('formReady', function () {
- generator.inject(form, { mode: 'edit', related: true, scope: $scope });
- generator.reset();
+ // manipulate the choices from the options request to be usable
+ // by the search option for permission_type, you can't inject the
+ // list until this is done!
+ permissionsChoice.then(function (choices) {
+ form.related.permissions.fields.permission_type.searchOptions =
+ permissionsSearchSelect({
+ choices: choices
+ });
+ generator.inject(form, { mode: 'edit', related: true, scope: $scope });
+ generator.reset();
+ });
if ($scope.removePostRefresh) {
$scope.removePostRefresh();
@@ -293,12 +323,12 @@ export function UsersEdit($scope, $rootScope, $compile, $location, $log, $routeP
$routeParams.id + '. GET status: ' + status });
});
- // if the permission includes adhoc (and is not admin), display that
$scope.getPermissionText = function () {
if (this.permission.permission_type !== "admin" && this.permission.run_ad_hoc_commands) {
- return this.permission.permission_type + " + run commands";
+ return $scope.permission_label[this.permission.permission_type] +
+ " and " + $scope.permission_label.adhoc;
} else {
- return this.permission.permission_type;
+ return $scope.permission_label[this.permission.permission_type];
}
};
@@ -489,5 +519,5 @@ export function UsersEdit($scope, $rootScope, $compile, $location, $log, $routeP
UsersEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'UserForm', 'GenerateForm',
'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit', 'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope',
- 'GetBasePath', 'Prompt', 'CheckAccess', 'ResetForm', 'Wait', 'Stream'
+ 'GetBasePath', 'Prompt', 'CheckAccess', 'ResetForm', 'Wait', 'Stream', 'permissionsChoices', 'permissionsLabel', 'permissionsSearchSelect'
];
diff --git a/awx/ui/client/src/forms.js b/awx/ui/client/src/forms.js
index 5ac8de52d0..3bd8170ee7 100644
--- a/awx/ui/client/src/forms.js
+++ b/awx/ui/client/src/forms.js
@@ -23,7 +23,6 @@ import LicenseUpdate from "./forms/LicenseUpdate";
import LogViewerOptions from "./forms/LogViewerOptions";
import LogViewerStatus from "./forms/LogViewerStatus";
import Organizations from "./forms/Organizations";
-import Permissions from "./forms/Permissions";
import ProjectStatus from "./forms/ProjectStatus";
import Projects from "./forms/Projects";
import Source from "./forms/Source";
@@ -51,7 +50,6 @@ export
LogViewerOptions,
LogViewerStatus,
Organizations,
- Permissions,
ProjectStatus,
Projects,
Source,
diff --git a/awx/ui/client/src/forms/Permissions.js b/awx/ui/client/src/forms/Permissions.js
deleted file mode 100644
index 3072366981..0000000000
--- a/awx/ui/client/src/forms/Permissions.js
+++ /dev/null
@@ -1,159 +0,0 @@
-/*************************************************
- * Copyright (c) 2015 Ansible, Inc.
- *
- * All Rights Reserved
- *************************************************/
-
- /**
- * @ngdoc function
- * @name forms.function:Permissions
- * @description This form is for adding/editing persmissions
-*/
-
-export default
- angular.module('PermissionFormDefinition', [])
- .value('PermissionsForm', {
-
- addTitle: 'Add Permission', //Title in add mode
- editTitle: '{{ name }}', //Title in edit mode
- name: 'permission', //entity or model name in singular form
- well: true, //Wrap the form with TB well
- forceListeners: true,
-
- stream: {
- 'class': "btn-primary btn-xs activity-btn",
- ngClick: "showActivity()",
- awToolTip: "View Activity Stream",
- awFeature: 'activity_streams',
- dataPlacement: "top",
- icon: "icon-comments-alt",
- mode: 'edit',
- iconSize: 'large'
- },
-
- fields: {
- category: {
- label: 'Permission Type',
- labelClass: 'prepend-asterisk',
- type: 'radio_group',
- options: [{
- label: 'Inventory',
- value: 'Inventory',
- selected: true
- }, {
- label: 'Job Template',
- value: 'Deploy'
- }],
- ngChange: 'selectCategory()'
- },
- name: {
- label: 'Name',
- type: 'text',
- addRequired: true,
- editRequired: true,
- capitalize: false
- },
- description: {
- label: 'Description',
- type: 'text',
- addRequired: false,
- editRequired: false
- },
- user: {
- label: 'User',
- type: 'hidden'
- },
- team: {
- label: 'Team',
- type: 'hidden'
- },
- project: {
- label: 'Project',
- type: 'lookup',
- sourceModel: 'project',
- sourceField: 'name',
- ngShow: "category == 'Deploy'",
- ngClick: 'lookUpProject()',
- awRequiredWhen: {
- variable: "projectrequired",
- init: "false"
- }
- },
- inventory: {
- label: 'Inventory',
- type: 'lookup',
- sourceModel: 'inventory',
- sourceField: 'name',
- ngClick: 'lookUpInventory()',
- awRequiredWhen: {
- variable: "inventoryrequired",
- init: "true"
- }
- },
- permission_type: {
- label: 'Permission',
- labelClass: 'prepend-asterisk',
- type: 'radio_group',
- class: 'squeeze',
- ngChange: 'changeAdhocCommandCheckbox()',
- options: [{
- label: 'Read',
- value: 'read',
- ngShow: "category == 'Inventory'"
- }, {
- label: 'Write',
- value: 'write',
- ngShow: "category == 'Inventory'"
- }, {
- label: 'Admin',
- value: 'admin',
- ngShow: "category == 'Inventory'"
- }, {
- label: 'Create',
- value: 'create',
- ngShow: "category == 'Deploy'"
- }, {
- label: 'Run',
- value: 'run',
- ngShow: "category == 'Deploy'"
- }, {
- label: 'Check',
- value: 'check',
- ngShow: "category == 'Deploy'"
- }],
- // hack: attach helpCollapse here if the permissions
- // category is deploy
- helpCollapse: [{
- hdr: 'Permission',
- ngBind: 'permissionTypeHelp',
- ngHide: "category == 'Inventory'"
- }]
- },
- run_ad_hoc_commands: {
- label: 'Execute commands',
- type: 'checkbox',
- // hack: attach helpCollapse here if the permissions
- // category is inventory
- helpCollapse: [{
- hdr: 'Permission',
- ngBind: 'permissionTypeHelp'
- }],
- ngShow: "category == 'Inventory'",
- associated: 'permission_type'
- },
- },
-
- buttons: {
- save: {
- ngClick: 'formSave()',
- ngDisabled: true
- },
- reset: {
- ngClick: 'formReset()',
- ngDisabled: true
- }
- },
-
- related: { }
-
- }); // Form
diff --git a/awx/ui/client/src/forms/Teams.js b/awx/ui/client/src/forms/Teams.js
index 7da930b9db..195f783ede 100644
--- a/awx/ui/client/src/forms/Teams.js
+++ b/awx/ui/client/src/forms/Teams.js
@@ -156,7 +156,9 @@ export default
ngBind: 'permission.summary_fields.project.name'
},
permission_type: {
- label: 'Permission'
+ label: 'Permission',
+ ngBind: 'getPermissionText()',
+ searchType: 'select'
}
},
diff --git a/awx/ui/client/src/forms/Users.js b/awx/ui/client/src/forms/Users.js
index 1f7ff32694..1f2ebef3aa 100644
--- a/awx/ui/client/src/forms/Users.js
+++ b/awx/ui/client/src/forms/Users.js
@@ -212,7 +212,8 @@ export default
},
permission_type: {
label: 'Permission',
- ngBind: 'getPermissionText()'
+ ngBind: 'getPermissionText()',
+ searchType: 'select'
}
},
diff --git a/awx/ui/client/src/helpers.js b/awx/ui/client/src/helpers.js
index be783b45a9..e5477a186c 100644
--- a/awx/ui/client/src/helpers.js
+++ b/awx/ui/client/src/helpers.js
@@ -26,7 +26,6 @@ import LogViewer from "./helpers/LogViewer";
import Lookup from "./helpers/Lookup";
import PaginationHelpers from "./helpers/PaginationHelpers";
import Parse from "./helpers/Parse";
-import Permissions from "./helpers/Permissions";
import ProjectPath from "./helpers/ProjectPath";
import Projects from "./helpers/Projects";
import Schedules from "./helpers/Schedules";
@@ -64,7 +63,6 @@ export
Lookup,
PaginationHelpers,
Parse,
- Permissions,
ProjectPath,
Projects,
Schedules,
diff --git a/awx/ui/client/src/helpers/Permissions.js b/awx/ui/client/src/helpers/Permissions.js
deleted file mode 100644
index 565b755b0a..0000000000
--- a/awx/ui/client/src/helpers/Permissions.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/*************************************************
- * Copyright (c) 2015 Ansible, Inc.
- *
- * All Rights Reserved
- *************************************************/
-
- /**
- * @ngdoc function
- * @name helpers.function:Permissions
- * @description
- * Functions shared amongst Permission related controllers
- *
- */
-
-export default
- angular.module('PermissionsHelper', [])
-
- // Handle category change event
- .factory('PermissionCategoryChange', ['$sce',
- function ($sce) {
- return function (params) {
- var scope = params.scope,
- reset = params.reset,
- html;
-
- if (scope.category === 'Inventory') {
- scope.projectrequired = false;
- html = "
\n" +
- "- Read
\n" +
- "- Only allow the user or team to view the inventory." +
- "
\n" +
- "- Write
\n" +
- "- Allow the user or team to modify hosts and groups " +
- "contained in the inventory, add new hosts and groups" +
- ", and perform inventory sync operations.\n" +
- "
- Admin
\n" +
- "- Allow the user or team full access to the " +
- "inventory. This includes reading, writing, deletion " +
- "of the inventory, inventory sync operations, and " +
- "the ability to execute commands on the inventory." +
- "
\n" +
- "- Execute commands
\n" +
- "- Allow the user to execute commands on the " +
- "inventory.
\n" +
- "
\n";
- scope.permissionTypeHelp = $sce.trustAsHtml(html);
- } else {
- scope.projectrequired = true;
- html = "\n" +
- "- Create
\n" +
- "- Allow the user or team to create job templates. " +
- "This implies that they have the Run and Check " +
- "permissions.
\n" +
- "- Run
\n" +
- "- Allow the user or team to run a job template from " +
- "the project against the inventory. In Run mode " +
- "modules will " +
- "be executed, and changes to the inventory will occur." +
- "
\n" +
- "- Check
\n" +
- "- Only allow the user or team to run the project " +
- "against the inventory as a dry-run operation. In " +
- "Check mode, module operations " +
- "will only be simulated. No changes will occur." +
- "
\n" +
- "
\n";
- scope.permissionTypeHelp = $sce.trustAsHtml(html);
- }
-
- if (reset) {
- if (scope.category === "Inventory") {
- scope.permission_type = "read";
- } else {
- scope.permission_type = "run";
- }
- }
- };
- }
- ]);
diff --git a/awx/ui/client/src/lists.js b/awx/ui/client/src/lists.js
index ecef643812..da4aa67f7b 100644
--- a/awx/ui/client/src/lists.js
+++ b/awx/ui/client/src/lists.js
@@ -21,7 +21,6 @@ import JobHosts from "./lists/JobHosts";
import JobTemplates from "./lists/JobTemplates";
import Jobs from "./lists/Jobs";
import Organizations from "./lists/Organizations";
-import Permissions from "./lists/Permissions";
import PortalJobTemplates from "./lists/PortalJobTemplates";
import PortalJobs from "./lists/PortalJobs";
import Projects from "./lists/Projects";
@@ -50,7 +49,6 @@ export
JobTemplates,
Jobs,
Organizations,
- Permissions,
PortalJobTemplates,
PortalJobs,
Projects,
diff --git a/awx/ui/client/src/permissions/add/add.controller.js b/awx/ui/client/src/permissions/add/add.controller.js
new file mode 100644
index 0000000000..0dad88f9db
--- /dev/null
+++ b/awx/ui/client/src/permissions/add/add.controller.js
@@ -0,0 +1,157 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+/**
+ * @ngdoc function
+ * @name controllers.function:Permissions
+ * @description This controller for permissions add
+*/
+export default
+ ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'permissionsForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ClearScope', 'GetBasePath', 'ReturnToCaller', 'InventoryList', 'ProjectList', 'LookUpInit', 'CheckAccess', 'Wait', 'permissionsCategoryChange', 'permissionsChoices', 'permissionsLabel',
+ function($scope, $rootScope, $compile, $location, $log, $routeParams, permissionsForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ClearScope, GetBasePath, ReturnToCaller, InventoryList, ProjectList, LookUpInit, CheckAccess, Wait, permissionsCategoryChange, permissionsChoices, permissionsLabel) {
+
+ ClearScope();
+
+ // Inject dynamic view
+ var form = permissionsForm,
+ generator = GenerateForm,
+ id = ($routeParams.user_id !== undefined) ? $routeParams.user_id : $routeParams.team_id,
+ base = $location.path().replace(/^\//, '').split('/')[0],
+ master = {};
+
+ var permissionsChoice = permissionsChoices({
+ scope: $scope,
+ url: 'api/v1/' + base + '/' + id + '/permissions/'
+ });
+
+ permissionsChoice.then(function (choices) {
+ return permissionsLabel({
+ choices: choices
+ });
+ }).then(function (choices) {
+ _.map(choices, function(n, key) {
+ $scope.permission_label[key] = n;
+ });
+ });
+
+ generator.inject(form, { mode: 'add', related: false, scope: $scope });
+ CheckAccess({ scope: $scope });
+ generator.reset();
+ LoadBreadCrumbs();
+
+ $scope.inventoryrequired = true;
+ $scope.projectrequired = false;
+ $scope.category = 'Inventory';
+ master.category = 'Inventory';
+ master.inventoryrequired = true;
+ master.projectrequired = false;
+ $scope.run_ad_hoc_commands = false;
+ $scope.permission_label = {};
+
+ LookUpInit({
+ scope: $scope,
+ form: form,
+ current_item: null,
+ list: InventoryList,
+ field: 'inventory',
+ input_type: 'radio'
+ });
+
+ LookUpInit({
+ scope: $scope,
+ form: form,
+ current_item: null,
+ list: ProjectList,
+ field: 'project',
+ input_type: 'radio'
+ });
+
+ $scope.$watch("category", function(val) {
+ if (val === 'Deploy') {
+ $scope.projectrequired = true;
+ LookUpInit({
+ scope: $scope,
+ form: form,
+ current_item: null,
+ list: ProjectList,
+ field: 'project',
+ input_type: 'radio'
+ });
+ } else {
+ $scope.projectrequired = false;
+ }
+ });
+
+ $scope.changeAdhocCommandCheckbox = function () {
+ if ($scope.category === 'Deploy') {
+ $scope.run_ad_hoc_command = false;
+ } else {
+ if ($scope.permission_type === 'admin') {
+ $scope.run_ad_hoc_commands = true;
+ $("#permission_run_ad_hoc_commands_chbox").attr("disabled", true);
+ } else {
+ if (!$scope.run_ad_hoc_commands) {
+ $scope.run_ad_hoc_commands = false;
+ }
+ $("#permission_run_ad_hoc_commands_chbox").attr("disabled", false);
+ }
+ }
+ };
+
+ // Save
+ $scope.formSave = function () {
+ var fld, url, data = {};
+ generator.clearApiErrors();
+ Wait('start');
+ if ($scope.PermissionAddAllowed) {
+ data = {};
+ for (fld in form.fields) {
+ data[fld] = $scope[fld];
+ }
+ // job template (or deploy) based permissions do not have the run
+ // ad hoc commands parameter
+ if (data.category === "Deploy") {
+ data.run_ad_hoc_commands = false;
+ } else {
+ delete data.project;
+ }
+
+ url = (base === 'teams') ? GetBasePath('teams') + id + '/permissions/' : GetBasePath('users') + id + '/permissions/';
+ Rest.setUrl(url);
+ Rest.post(data)
+ .success(function () {
+ Wait('stop');
+ ReturnToCaller(1);
+ })
+ .error(function (data, status) {
+ Wait('stop');
+ ProcessErrors($scope, data, status, permissionsForm, { hdr: 'Error!',
+ msg: 'Failed to create new permission. Post returned status: ' + status });
+ });
+ } else {
+ Alert('Access Denied', 'You do not have access to create new permission objects. Please contact a system administrator.',
+ 'alert-danger');
+ }
+ };
+
+ // Cancel
+ $scope.formReset = function () {
+ $rootScope.flashMessage = null;
+ generator.reset();
+ for (var fld in master) {
+ $scope[fld] = master[fld];
+ }
+ $scope.selectCategory();
+ };
+
+ $scope.selectCategory = function () {
+ permissionsCategoryChange({ scope: $scope, reset: true });
+ };
+
+
+ $scope.selectCategory();
+
+ }];
diff --git a/awx/ui/client/src/permissions/add/main.js b/awx/ui/client/src/permissions/add/main.js
new file mode 100644
index 0000000000..ad86ab1801
--- /dev/null
+++ b/awx/ui/client/src/permissions/add/main.js
@@ -0,0 +1,21 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import userRoute from './user-add.route';
+import teamRoute from './team-add.route';
+import controller from './add.controller';
+
+export default
+ angular.module('permissionsAdd', [])
+ .controller('addController', controller)
+ .config(['$routeProvider', function($routeProvider) {
+ var url = userRoute.route;
+ delete userRoute.route;
+ $routeProvider.when(url, userRoute);
+ url = teamRoute.route;
+ delete teamRoute.route;
+ $routeProvider.when(url, teamRoute);
+ }]);
diff --git a/awx/ui/client/src/permissions/add/team-add.route.js b/awx/ui/client/src/permissions/add/team-add.route.js
new file mode 100644
index 0000000000..706d273f79
--- /dev/null
+++ b/awx/ui/client/src/permissions/add/team-add.route.js
@@ -0,0 +1,19 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import {templateUrl} from '../../shared/template-url/template-url.factory';
+
+export default {
+ name: 'teamPermissionsAdd',
+ route: '/teams/:team_id/permissions/add',
+ templateUrl: templateUrl('permissions/shared/team-permissions'),
+ controller: 'addController',
+ resolve: {
+ features: ['FeaturesService', function(FeaturesService) {
+ return FeaturesService.get();
+ }]
+ }
+};
diff --git a/awx/ui/client/src/permissions/add/user-add.route.js b/awx/ui/client/src/permissions/add/user-add.route.js
new file mode 100644
index 0000000000..877be6c20c
--- /dev/null
+++ b/awx/ui/client/src/permissions/add/user-add.route.js
@@ -0,0 +1,19 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import {templateUrl} from '../../shared/template-url/template-url.factory';
+
+export default {
+ name: 'userPermissionsAdd',
+ route: '/users/:user_id/permissions/add',
+ templateUrl: templateUrl('permissions/shared/user-permissions'),
+ controller: 'addController',
+ resolve: {
+ features: ['FeaturesService', function(FeaturesService) {
+ return FeaturesService.get();
+ }]
+ }
+};
diff --git a/awx/ui/client/src/permissions/edit/edit.controller.js b/awx/ui/client/src/permissions/edit/edit.controller.js
new file mode 100644
index 0000000000..1b1c6a552b
--- /dev/null
+++ b/awx/ui/client/src/permissions/edit/edit.controller.js
@@ -0,0 +1,184 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+/**
+ * @ngdoc function
+ * @name controllers.function:Permissions
+ * @description This controller for permissions edit
+*/
+export default
+ ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'permissionsForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'Prompt', 'GetBasePath', 'InventoryList', 'ProjectList', 'LookUpInit', 'CheckAccess', 'Wait', 'permissionsCategoryChange', 'permissionsChoices', 'permissionsLabel',
+ function($scope, $rootScope, $compile, $location, $log, $routeParams, permissionsForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, Prompt, GetBasePath, InventoryList, ProjectList, LookUpInit, CheckAccess, Wait, permissionsCategoryChange, permissionsChoices, permissionsLabel) {
+
+ ClearScope();
+
+ var generator = GenerateForm,
+ form = permissionsForm,
+ base_id = ($routeParams.user_id !== undefined) ? $routeParams.user_id : $routeParams.team_id,
+ id = $routeParams.permission_id,
+ defaultUrl = GetBasePath('base') + 'permissions/' + id + '/',
+ base = $location.path().replace(/^\//, '').split('/')[0],
+ master = {};
+
+ $scope.permission_label = {};
+
+ var permissionsChoice = permissionsChoices({
+ scope: $scope,
+ url: 'api/v1/' + base + '/' + base_id + '/permissions/'
+ });
+
+ permissionsChoice.then(function (choices) {
+ return permissionsLabel({
+ choices: choices
+ });
+ }).then(function (choices) {
+ _.map(choices, function(n, key) {
+ $scope.permission_label[key] = n;
+ });
+ });
+
+ $scope.changeAdhocCommandCheckbox = function () {
+ if ($scope.category === 'Deploy') {
+ $scope.run_ad_hoc_command = false;
+ } else {
+ if ($scope.permission_type === 'admin') {
+ $scope.run_ad_hoc_commands = true;
+ $("#permission_run_ad_hoc_commands_chbox").attr("disabled", true);
+ } else {
+ if (!$scope.run_ad_hoc_commands) {
+ $scope.run_ad_hoc_commands = false;
+ }
+ $("#permission_run_ad_hoc_commands_chbox").attr("disabled", false);
+ }
+ }
+ };
+
+ generator.inject(form, { mode: 'edit', related: true, scope: $scope });
+ generator.reset();
+
+ $scope.selectCategory = function (resetIn) {
+ var reset = (resetIn === false) ? false : true;
+ permissionsCategoryChange({ scope: $scope, reset: reset });
+ };
+ if ($scope.removeFillForm) {
+ $scope.removeFillForm();
+ }
+ $scope.removeFillForm = $scope.$on('FillForm', function () {
+ // Retrieve detail record and prepopulate the form
+ Wait('start');
+ Rest.setUrl(defaultUrl);
+ Rest.get()
+ .success(function (data) {
+ var fld, sourceModel, sourceField;
+ LoadBreadCrumbs({ path: '/users/' + base_id + '/permissions/' + id, title: data.name });
+ for (fld in form.fields) {
+ if (data[fld]) {
+ if (form.fields[fld].sourceModel) {
+ sourceModel = form.fields[fld].sourceModel;
+ sourceField = form.fields[fld].sourceField;
+ $scope[sourceModel + '_' + sourceField] = data.summary_fields[sourceModel][sourceField];
+ master[sourceModel + '_' + sourceField] = data.summary_fields[sourceModel][sourceField];
+ }
+ $scope[fld] = data[fld];
+ master[fld] = $scope[fld];
+ }
+ }
+
+ $scope.category = 'Deploy';
+ if (data.permission_type !== 'run' && data.permission_type !== 'check' && data.permission_type !== 'create') {
+ $scope.category = 'Inventory';
+ }
+ master.category = $scope.category;
+ $scope.selectCategory(false); //call without resetting $scope.category value
+
+ LookUpInit({
+ scope: $scope,
+ form: form,
+ current_item: data.inventory,
+ list: InventoryList,
+ field: 'inventory',
+ input_type: "radio"
+ });
+
+ LookUpInit({
+ scope: $scope,
+ form: form,
+ current_item: data.project,
+ list: ProjectList,
+ field: 'project',
+ input_type: 'radio'
+ });
+
+ $scope.changeAdhocCommandCheckbox();
+
+ if (!$scope.PermissionAddAllowed) {
+ // If not a privileged user, disable access
+ $('form[name="permission_form"]').find('select, input, button').each(function () {
+ if ($(this).is('input') || $(this).is('select')) {
+ $(this).attr('readonly', 'readonly');
+ }
+ if ($(this).is('input[type="checkbox"]') ||
+ $(this).is('input[type="radio"]') ||
+ $(this).is('button')) {
+ $(this).attr('disabled', 'disabled');
+ }
+ });
+ }
+ Wait('stop');
+ })
+ .error(function (data, status) {
+ ProcessErrors($scope, data, status, form, { hdr: 'Error!',
+ msg: 'Failed to retrieve Permission: ' + id + '. GET status: ' + status });
+ });
+ });
+
+ CheckAccess({
+ scope: $scope,
+ callback: 'FillForm'
+ });
+
+ // Save changes to the parent
+ $scope.formSave = function () {
+ var fld, data = {};
+ generator.clearApiErrors();
+ Wait('start');
+ for (fld in form.fields) {
+ data[fld] = $scope[fld];
+ }
+ // job template (or deploy) based permissions do not have the run
+ // ad hoc commands parameter
+ if (data.category === "Deploy") {
+ data.run_ad_hoc_commands = false;
+ } else {
+ delete data.project;
+ }
+
+ Rest.setUrl(defaultUrl);
+ if($scope.category === "Inventory"){
+ delete data.project;
+ }
+ Rest.put(data)
+ .success(function () {
+ Wait('stop');
+ ReturnToCaller(1);
+ })
+ .error(function (data, status) {
+ ProcessErrors($scope, data, status, form, { hdr: 'Error!', msg: 'Failed to update Permission: ' +
+ $routeParams.id + '. PUT status: ' + status });
+ });
+ };
+
+
+ // Cancel
+ $scope.formReset = function () {
+ generator.reset();
+ for (var fld in master) {
+ $scope[fld] = master[fld];
+ }
+ $scope.selectCategory(false);
+ };
+
+ }];
diff --git a/awx/ui/client/src/permissions/edit/main.js b/awx/ui/client/src/permissions/edit/main.js
new file mode 100644
index 0000000000..08e4aa2b85
--- /dev/null
+++ b/awx/ui/client/src/permissions/edit/main.js
@@ -0,0 +1,21 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import userRoute from './user-edit.route';
+import teamRoute from './team-edit.route';
+import controller from './edit.controller';
+
+export default
+ angular.module('permissionsEdit', [])
+ .controller('permissionsEditController', controller)
+ .config(['$routeProvider', function($routeProvider) {
+ var url = userRoute.route;
+ delete userRoute.route;
+ $routeProvider.when(url, userRoute);
+ url = teamRoute.route;
+ delete teamRoute.route;
+ $routeProvider.when(url, teamRoute);
+ }]);
diff --git a/awx/ui/client/src/permissions/edit/team-edit.route.js b/awx/ui/client/src/permissions/edit/team-edit.route.js
new file mode 100644
index 0000000000..8122b7fcf5
--- /dev/null
+++ b/awx/ui/client/src/permissions/edit/team-edit.route.js
@@ -0,0 +1,19 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import {templateUrl} from '../../shared/template-url/template-url.factory';
+
+export default {
+ name: 'teamPermissionsEdit',
+ route: '/teams/:team_id/permissions/:permission_id',
+ templateUrl: templateUrl('permissions/shared/team-permissions'),
+ controller: 'permissionsEditController',
+ resolve: {
+ features: ['FeaturesService', function(FeaturesService) {
+ return FeaturesService.get();
+ }]
+ }
+};
diff --git a/awx/ui/client/src/permissions/edit/user-edit.route.js b/awx/ui/client/src/permissions/edit/user-edit.route.js
new file mode 100644
index 0000000000..3d42d5827e
--- /dev/null
+++ b/awx/ui/client/src/permissions/edit/user-edit.route.js
@@ -0,0 +1,19 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import {templateUrl} from '../../shared/template-url/template-url.factory';
+
+export default {
+ name: 'userPermissionsEdit',
+ route: '/users/:user_id/permissions/:permission_id',
+ templateUrl: templateUrl('permissions/shared/user-permissions'),
+ controller: 'permissionsEditController',
+ resolve: {
+ features: ['FeaturesService', function(FeaturesService) {
+ return FeaturesService.get();
+ }]
+ }
+};
diff --git a/awx/ui/client/src/permissions/list/list.controller.js b/awx/ui/client/src/permissions/list/list.controller.js
new file mode 100644
index 0000000000..4e9ea4c716
--- /dev/null
+++ b/awx/ui/client/src/permissions/list/list.controller.js
@@ -0,0 +1,136 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+/**
+ * @ngdoc function
+ * @name controllers.function:Permissions
+ * @description This controller for permissions list
+*/
+
+
+export default
+ ['$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'permissionsList', 'generateList', 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors', 'GetBasePath', 'CheckAccess', 'Wait', 'permissionsChoices', 'permissionsLabel', 'permissionsSearchSelect',
+ function ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, permissionsList, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, CheckAccess, Wait, permissionsChoices, permissionsLabel, permissionsSearchSelect) {
+
+ ClearScope();
+
+ var list = permissionsList,
+ base = $location.path().replace(/^\//, '').split('/')[0],
+ base_id = ($routeParams.user_id !== undefined) ? $routeParams.user_id : $routeParams.team_id,
+ defaultUrl = GetBasePath(base),
+ generator = GenerateList;
+
+ $scope.permission_label = {};
+ $scope.permission_search_select = [];
+
+ // return a promise from the options request with the permission type choices (including adhoc) as a param
+ var permissionsChoice = permissionsChoices({
+ scope: $scope,
+ url: 'api/v1/' + base + '/' + base_id + '/permissions/'
+ });
+
+ // manipulate the choices from the options request to be set on
+ // scope and be usable by the list form
+ permissionsChoice.then(function (choices) {
+ choices =
+ permissionsLabel({
+ choices: choices
+ });
+ _.map(choices, function(n, key) {
+ $scope.permission_label[key] = n;
+ });
+ });
+
+ // manipulate the choices from the options request to be usable
+ // by the search option for permission_type, you can't inject the
+ // list until this is done!
+ permissionsChoice.then(function (choices) {
+ list.fields.permission_type.searchOptions =
+ permissionsSearchSelect({
+ choices: choices
+ });
+ generator.inject(list, { mode: 'edit', scope: $scope, breadCrumbs: true });
+ });
+
+ defaultUrl += ($routeParams.user_id !== undefined) ? $routeParams.user_id : $routeParams.team_id;
+ defaultUrl += '/permissions/';
+
+ $scope.selected = [];
+
+ CheckAccess({
+ scope: $scope
+ });
+
+ if ($scope.removePostRefresh) {
+ $scope.removePostRefresh();
+ }
+ $scope.removePostRefresh = $scope.$on('PostRefresh', function () {
+ // Cleanup after a delete
+ Wait('stop');
+ $('#prompt-modal').modal('hide');
+ });
+
+ SearchInit({
+ scope: $scope,
+ set: 'permissions',
+ list: list,
+ url: defaultUrl
+ });
+ PaginateInit({
+ scope: $scope,
+ list: list,
+ url: defaultUrl
+ });
+ $scope.search(list.iterator);
+
+ LoadBreadCrumbs();
+
+ $scope.addPermission = function () {
+ if ($scope.PermissionAddAllowed) {
+ $location.path($location.path() + '/add');
+ }
+ };
+
+ // if the permission includes adhoc (and is not admin), display that
+ $scope.getPermissionText = function () {
+ if (this.permission.permission_type !== "admin" && this.permission.run_ad_hoc_commands) {
+ return $scope.permission_label[this.permission.permission_type] +
+ " and " + $scope.permission_label.adhoc;
+ } else {
+ return $scope.permission_label[this.permission.permission_type];
+ }
+ };
+
+ $scope.editPermission = function (id) {
+ $location.path($location.path() + '/' + id);
+ };
+
+ $scope.deletePermission = function (id, name) {
+ var action = function () {
+ $('#prompt-modal').modal('hide');
+ Wait('start');
+ var url = GetBasePath('base') + 'permissions/' + id + '/';
+ Rest.setUrl(url);
+ Rest.destroy()
+ .success(function () {
+ $scope.search(list.iterator);
+ })
+ .error(function (data, status) {
+ Wait('stop');
+ ProcessErrors($scope, data, status, null, { hdr: 'Error!',
+ msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
+ });
+ };
+
+ if ($scope.PermissionAddAllowed) {
+ Prompt({
+ hdr: 'Delete',
+ body: 'Are you sure you want to delete ' + name + '?',
+ action: action
+ });
+ }
+ };
+ }];
diff --git a/awx/ui/client/src/permissions/list/main.js b/awx/ui/client/src/permissions/list/main.js
new file mode 100644
index 0000000000..beed341e5b
--- /dev/null
+++ b/awx/ui/client/src/permissions/list/main.js
@@ -0,0 +1,21 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import userRoute from './user-list.route';
+import teamRoute from './team-list.route';
+import controller from './list.controller';
+
+export default
+ angular.module('permissionsList', [])
+ .controller('permissionsListController', controller)
+ .config(['$routeProvider', function($routeProvider) {
+ var url = userRoute.route;
+ delete userRoute.route;
+ $routeProvider.when(url, userRoute);
+ url = teamRoute.route;
+ delete teamRoute.route;
+ $routeProvider.when(url, teamRoute);
+ }]);
diff --git a/awx/ui/client/src/permissions/list/team-list.route.js b/awx/ui/client/src/permissions/list/team-list.route.js
new file mode 100644
index 0000000000..bd93ebf2e5
--- /dev/null
+++ b/awx/ui/client/src/permissions/list/team-list.route.js
@@ -0,0 +1,19 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import {templateUrl} from '../../shared/template-url/template-url.factory';
+
+export default {
+ name: 'teamPermissionsList',
+ route: '/teams/:team_id/permissions',
+ templateUrl: templateUrl('permissions/shared/team-permissions'),
+ controller: 'permissionsListController',
+ resolve: {
+ features: ['FeaturesService', function(FeaturesService) {
+ return FeaturesService.get();
+ }]
+ }
+};
diff --git a/awx/ui/client/src/permissions/list/user-list.route.js b/awx/ui/client/src/permissions/list/user-list.route.js
new file mode 100644
index 0000000000..586b000d69
--- /dev/null
+++ b/awx/ui/client/src/permissions/list/user-list.route.js
@@ -0,0 +1,19 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import {templateUrl} from '../../shared/template-url/template-url.factory';
+
+export default {
+ name: 'userPermissionsList',
+ route: '/users/:user_id/permissions',
+ templateUrl: templateUrl('permissions/shared/user-permissions'),
+ controller: 'permissionsListController',
+ resolve: {
+ features: ['FeaturesService', function(FeaturesService) {
+ return FeaturesService.get();
+ }]
+ }
+};
diff --git a/awx/ui/client/src/permissions/main.js b/awx/ui/client/src/permissions/main.js
new file mode 100644
index 0000000000..fef273cfa6
--- /dev/null
+++ b/awx/ui/client/src/permissions/main.js
@@ -0,0 +1,30 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import permissionsList from './list/main';
+import permissionsAdd from './add/main';
+import permissionsEdit from './edit/main';
+
+import list from './shared/permissions.list';
+import form from './shared/permissions.form';
+
+import permissionsCategoryChange from './shared/category-change.factory';
+import permissionsChoices from './shared/get-choices.factory';
+import permissionsLabel from './shared/get-labels.factory';
+import permissionsSearchSelect from './shared/get-search-select.factory';
+
+export default
+ angular.module('permissions', [
+ permissionsList.name,
+ permissionsAdd.name,
+ permissionsEdit.name
+ ])
+ .factory('permissionsList', list)
+ .factory('permissionsForm', form)
+ .factory('permissionsCategoryChange', permissionsCategoryChange)
+ .factory('permissionsChoices', permissionsChoices)
+ .factory('permissionsLabel', permissionsLabel)
+ .factory('permissionsSearchSelect', permissionsSearchSelect);
diff --git a/awx/ui/client/src/permissions/shared/category-change.factory.js b/awx/ui/client/src/permissions/shared/category-change.factory.js
new file mode 100644
index 0000000000..0de5c7f2ea
--- /dev/null
+++ b/awx/ui/client/src/permissions/shared/category-change.factory.js
@@ -0,0 +1,74 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+ /**
+ * @ngdoc function
+ * @name helpers.function:Permissions
+ * @description
+ * Functions shared amongst Permission related controllers
+ *
+ */
+
+ export default
+ ['$sce', function($sce) {
+ return function (params) {
+ var scope = params.scope,
+ reset = params.reset,
+ html;
+
+ if (scope.category === 'Inventory') {
+ scope.projectrequired = false;
+ html = "\n" +
+ "- Read Inventory
\n" +
+ "- Only allow the user or team to view the inventory." +
+ "
\n" +
+ "- Edit Inventory
\n" +
+ "- Allow the user or team to modify hosts and groups " +
+ "contained in the inventory, add new hosts and groups" +
+ ", and perform inventory sync operations.\n" +
+ "
- Administrate Inventory
\n" +
+ "- Allow the user or team full access to the " +
+ "inventory. This includes reading, writing, deletion " +
+ "of the inventory, inventory sync operations, and " +
+ "the ability to execute commands on the inventory." +
+ "
\n" +
+ "- Execute Commands
\n" +
+ "- Allow the user to execute commands on the " +
+ "inventory.
\n" +
+ "
\n";
+ scope.permissionTypeHelp = $sce.trustAsHtml(html);
+ } else {
+ scope.projectrequired = true;
+ html = "\n" +
+ "- Create a Job Template
\n" +
+ "- Allow the user or team to create job templates. " +
+ "This implies that they have the Run and Check " +
+ "permissions.
\n" +
+ "- Deploy To Inventory
\n" +
+ "- Allow the user or team to run a job template from " +
+ "the project against the inventory. In Run mode " +
+ "modules will " +
+ "be executed, and changes to the inventory will occur." +
+ "
\n" +
+ "- Deploy to Inventory (Dry Run)
\n" +
+ "- Only allow the user or team to run the project " +
+ "against the inventory as a dry-run operation. In " +
+ "Check mode, module operations " +
+ "will only be simulated. No changes will occur." +
+ "
\n" +
+ "
\n";
+ scope.permissionTypeHelp = $sce.trustAsHtml(html);
+ }
+
+ if (reset) {
+ if (scope.category === "Inventory") {
+ scope.permission_type = "read";
+ } else {
+ scope.permission_type = "run";
+ }
+ }
+ };
+ }];
diff --git a/awx/ui/client/src/permissions/shared/get-choices.factory.js b/awx/ui/client/src/permissions/shared/get-choices.factory.js
new file mode 100644
index 0000000000..57e14433f4
--- /dev/null
+++ b/awx/ui/client/src/permissions/shared/get-choices.factory.js
@@ -0,0 +1,39 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+ /**
+ * @ngdoc function
+ * @name helpers.function:Permissions
+ * @description
+ * Gets permission type labels from the API and sets them as the permissions labels on the relevant radio buttons
+ *
+ */
+
+ export default
+ ['Rest', 'ProcessErrors', function(Rest, ProcessErrors) {
+ return function (params) {
+ var scope = params.scope,
+ url = params.url;
+
+ // Auto populate the field if there is only one result
+ Rest.setUrl(url);
+ return Rest.options()
+ .then(function (data) {
+ data = data.data;
+ var choices = data.actions.GET.permission_type.choices;
+
+ // manually add the adhoc label to the choices object
+ choices.push(["adhoc",
+ data.actions.GET.run_ad_hoc_commands.label]);
+
+ return choices;
+ })
+ .catch(function (data, status) {
+ ProcessErrors(scope, data, status, null, { hdr: 'Error!',
+ msg: 'Failed to get permission type labels. Options requrest returned status: ' + status });
+ });
+ };
+ }];
diff --git a/awx/ui/client/src/permissions/shared/get-labels.factory.js b/awx/ui/client/src/permissions/shared/get-labels.factory.js
new file mode 100644
index 0000000000..2b20ca70bb
--- /dev/null
+++ b/awx/ui/client/src/permissions/shared/get-labels.factory.js
@@ -0,0 +1,26 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+ /**
+ * @ngdoc function
+ * @name helpers.function:Permissions
+ * @description
+ * Gets permission type labels from the API and sets them as the permissions labels on the relevant radio buttons
+ *
+ */
+
+ export default
+ [function() {
+ return function (params) {
+ // convert the choices from the API from the format
+ // [["read", "Read Inventory"], ...] to
+ // {read: "Read Inventory", ...}
+ return params.choices.reduce(function(obj, kvp) {
+ obj[kvp[0]] = kvp[1];
+ return obj;
+ }, {});
+ };
+ }];
diff --git a/awx/ui/client/src/permissions/shared/get-search-select.factory.js b/awx/ui/client/src/permissions/shared/get-search-select.factory.js
new file mode 100644
index 0000000000..110663cd7b
--- /dev/null
+++ b/awx/ui/client/src/permissions/shared/get-search-select.factory.js
@@ -0,0 +1,29 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+ /**
+ * @ngdoc function
+ * @name helpers.function:Permissions
+ * @description
+ * Gets permission type labels from the API and sets them as the permissions labels on the relevant radio buttons
+ *
+ */
+
+ export default
+ [function() {
+ return function (params) {
+ // convert the choices from the API from the format
+ // [["read", "Read Inventory"], ...] to
+ // [{name: "read", value: "Read Inventory"}, ...]
+ return params.choices.filter(function (kvp) {
+ return (kvp[0] !== "adhoc");
+ }).map(function (kvp) {
+ if (kvp[0] !== "adhoc") {
+ return {name: kvp[1], value: kvp[0]};
+ }
+ });
+ };
+ }];
diff --git a/awx/ui/client/src/permissions/shared/permissions.form.js b/awx/ui/client/src/permissions/shared/permissions.form.js
new file mode 100644
index 0000000000..de97aa01ad
--- /dev/null
+++ b/awx/ui/client/src/permissions/shared/permissions.form.js
@@ -0,0 +1,158 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+ /**
+ * @ngdoc function
+ * @name forms.function:Permissions
+ * @description This form is for adding/editing persmissions
+*/
+
+export default function() {
+ return {
+ addTitle: 'Add Permission', //Title in add mode
+ editTitle: '{{ name }}', //Title in edit mode
+ name: 'permission', //entity or model name in singular form
+ well: true, //Wrap the form with TB well
+ forceListeners: true,
+
+ stream: {
+ 'class': "btn-primary btn-xs activity-btn",
+ ngClick: "showActivity()",
+ awToolTip: "View Activity Stream",
+ awFeature: 'activity_streams',
+ dataPlacement: "top",
+ icon: "icon-comments-alt",
+ mode: 'edit',
+ iconSize: 'large'
+ },
+
+ fields: {
+ category: {
+ label: 'Permission Type',
+ labelClass: 'prepend-asterisk',
+ type: 'radio_group',
+ options: [{
+ label: 'Inventory',
+ value: 'Inventory',
+ selected: true
+ }, {
+ label: 'Job Template',
+ value: 'Deploy'
+ }],
+ ngChange: 'selectCategory()'
+ },
+ name: {
+ label: 'Name',
+ type: 'text',
+ addRequired: true,
+ editRequired: true,
+ capitalize: false
+ },
+ description: {
+ label: 'Description',
+ type: 'text',
+ addRequired: false,
+ editRequired: false
+ },
+ user: {
+ label: 'User',
+ type: 'hidden'
+ },
+ team: {
+ label: 'Team',
+ type: 'hidden'
+ },
+ project: {
+ label: 'Project',
+ type: 'lookup',
+ sourceModel: 'project',
+ sourceField: 'name',
+ ngShow: "category == 'Deploy'",
+ ngClick: 'lookUpProject()',
+ awRequiredWhen: {
+ variable: "projectrequired",
+ init: "false"
+ }
+ },
+ inventory: {
+ label: 'Inventory',
+ type: 'lookup',
+ sourceModel: 'inventory',
+ sourceField: 'name',
+ ngClick: 'lookUpInventory()',
+ awRequiredWhen: {
+ variable: "inventoryrequired",
+ init: "true"
+ }
+ },
+ permission_type: {
+ label: 'Permission',
+ labelClass: 'prepend-asterisk',
+ type: 'radio_group',
+ class: 'squeeze',
+ ngChange: 'changeAdhocCommandCheckbox()',
+ options: [{
+ label: '{{ permission_label.read }}',
+ value: 'read',
+ ngShow: "category == 'Inventory'"
+ }, {
+ label: '{{ permission_label.write }}',
+ value: 'write',
+ ngShow: "category == 'Inventory'"
+ }, {
+ label: '{{ permission_label.admin }}',
+ value: 'admin',
+ ngShow: "category == 'Inventory'"
+ }, {
+ label: '{{ permission_label.create }}',
+ value: 'create',
+ ngShow: "category == 'Deploy'"
+ }, {
+ label: '{{ permission_label.run }}',
+ value: 'run',
+ ngShow: "category == 'Deploy'"
+ }, {
+ label: '{{ permission_label.check }}',
+ value: 'check',
+ ngShow: "category == 'Deploy'"
+ }],
+ // hack: attach helpCollapse here if the permissions
+ // category is deploy
+ helpCollapse: [{
+ hdr: 'Permission',
+ ngBind: 'permissionTypeHelp',
+ ngHide: "category == 'Inventory'"
+ }]
+ },
+ run_ad_hoc_commands: {
+ label: '{{ permission_label.adhoc }}',
+ type: 'checkbox',
+ // hack: attach helpCollapse here if the permissions
+ // category is inventory
+ helpCollapse: [{
+ hdr: 'Permission',
+ ngBind: 'permissionTypeHelp'
+ }],
+ ngShow: "category == 'Inventory'",
+ associated: 'permission_type'
+ },
+ },
+
+ buttons: {
+ save: {
+ ngClick: 'formSave()',
+ ngDisabled: true
+ },
+ reset: {
+ ngClick: 'formReset()',
+ ngDisabled: true
+ }
+ },
+
+ related: { }
+
+ };
+}
diff --git a/awx/ui/client/src/lists/Permissions.js b/awx/ui/client/src/permissions/shared/permissions.list.js
similarity index 93%
rename from awx/ui/client/src/lists/Permissions.js
rename to awx/ui/client/src/permissions/shared/permissions.list.js
index 9938c6fdc7..f9c50c7818 100644
--- a/awx/ui/client/src/lists/Permissions.js
+++ b/awx/ui/client/src/permissions/shared/permissions.list.js
@@ -3,11 +3,10 @@
*
* All Rights Reserved
*************************************************/
-
-export default
- angular.module('PermissionListDefinition', [])
- .value('PermissionList', {
+
+ export default function() {
+ return {
name: 'permissions',
iterator: 'permission',
@@ -38,7 +37,8 @@ export default
},
permission_type: {
label: 'Permission',
- ngBind: 'getPermissionText()'
+ ngBind: 'getPermissionText()',
+ searchType: 'select'
}
},
@@ -77,4 +77,5 @@ export default
dataPlacement: 'top'
}
}
- });
+ };
+}
diff --git a/awx/ui/client/src/permissions/shared/team-permissions.partial.html b/awx/ui/client/src/permissions/shared/team-permissions.partial.html
new file mode 100644
index 0000000000..e8c4dad50b
--- /dev/null
+++ b/awx/ui/client/src/permissions/shared/team-permissions.partial.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/awx/ui/client/src/permissions/shared/user-permissions.partial.html b/awx/ui/client/src/permissions/shared/user-permissions.partial.html
new file mode 100644
index 0000000000..271a15d748
--- /dev/null
+++ b/awx/ui/client/src/permissions/shared/user-permissions.partial.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+