diff --git a/.gitignore b/.gitignore index 763d15001d..ee2a0211ce 100644 --- a/.gitignore +++ b/.gitignore @@ -12,10 +12,7 @@ awx/job_output awx/public/media awx/public/static awx/ui/tests/test-results.xml -awx/ui/static/js/awx.min.js -awx/ui/static/js/local_settings.json awx/ui/client/src/local_settings.json -awx/ui/static/css/awx.min.css awx/main/fixtures awx/*.log tower/tower_warnings.log @@ -110,6 +107,7 @@ local/ *.mo requirements/vendor .i18n_built +VERSION # AWX python libs populated by requirements.txt awx/lib/.deps_built diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 48cdce8528..a6ae51ecbc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1 +1,280 @@ -placeholder + +AWX +=========== + +Hi there! We're excited to have you as a contributor. + +Have questions about this document or anything not covered here? Come chat with us on IRC (#ansible-awx on freenode) or the mailing list. + +Table of contents +----------------- + +* [Contributing Agreement](#dco) +* [Code of Conduct](#code-of-conduct) +* [Setting up the development environment](#setting-up-the-development-environment) + * [Prerequisites](#prerequisites) + * [Local Settings](#local-settings) + * [Building the base image](#building-the-base-image) + * [Building the user interface](#building-the-user-interface) + * [Starting up the development environment](#starting-up-the-development-environment) + * [Starting the development environment at the container shell](#starting-the-container-environment-at-the-container-shell) + * [Using the development environment](#using-the-development-environment) +* [What should I work on?](#what-should-i-work-on) +* [Submitting Pull Requests](#submitting-pull-requests) +* [Reporting Issues](#reporting-issues) + * [How issues are resolved](#how-issues-are-resolved) + * [Ansible Issue Bot](#ansible-issue-bot) + +DCO +=== + +All contributors must use "git commit --signoff" for any +commit to be merged, and agree that usage of --signoff constitutes +agreement with the terms of DCO 1.1. Any contribution that does not +have such a signoff will not be merged. + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +1 Letterman Drive +Suite D4700 +San Francisco, CA, 94129 + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +Code of Conduct +=============== + +All contributors are expected to adhere to the Ansible Community Code of Conduct: http://docs.ansible.com/ansible/latest/community/code_of_conduct.html + +Setting up the development environment +====================================== + +The AWX development environment workflow and toolchain is based on Docker and the docker-compose tool to contain +the dependencies, services, and databases necessary to run everything. It will bind the local source tree into the container +making it possible to observe changes while developing. + +Prerequisites +------------- +`docker` and `docker-compose` are required for starting the development services, on Linux you can generally find these in your +distro's packaging, but you may find that Docker themselves maintain a seperate repo that tracks more closely to the latest releases. +For macOS and Windows, we recommend Docker for Mac (https://www.docker.com/docker-mac) and Docker for Windows (https://www.docker.com/docker-windows) +respectively. Docker for Mac/Windows automatically comes with `docker-compose`. + +> Fedora + +https://docs.docker.com/engine/installation/linux/docker-ce/fedora/ + +> Centos + +https://docs.docker.com/engine/installation/linux/docker-ce/centos/ + +> Ubuntu + +https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/ + +> Debian + +https://docs.docker.com/engine/installation/linux/docker-ce/debian/ + +> Arch + +https://wiki.archlinux.org/index.php/Docker + +For `docker-compose` you may need/choose to install it seperately: + + pip install docker-compose + + +Local Settings +-------------- + +In development mode (i.e. when running from a source checkout), AWX +will import the file `awx/settings/local_settings.py` and combine it with defaults in `awx/settings/defaults.py`. This file +is required for starting the development environment and startup will fail if it's not provided + +An example file that works for the `docker-compose` tool is provided. Make a copy of it and edit as needed (the defaults are usually fine): + + (host)$ cp awx/settings/local_settings.py.docker_compose awx/settings/local_settings.py + +Building the base image +----------------------- + +The AWX base container image (found in `tools/docker-compose/Dockerfile`) contains basic OS dependencies and +symbolic links into the development environment that make running the services easy. You'll first need to build the image: + + (host)$ make docker-compose-build + +The image will only need to be rebuilt if the requirements or OS dependencies change. A core concept about this image is that it relies +on having your local development environment mapped in. + +Building the user interface +--------------------------- + +> AWX requires the 6.x LTS version of Node and 3.x LTS NPM + +In order for the AWX user interface to load from the development environment it must be built: + + (host)$ make ui-devel + +When developing features and fixes for the user interface you can find more detail here: [UI Developer README](awx/ui/README.md) + +Starting up the development environment +---------------------------------------------- + +There are several ways of starting the development environment depending on your desired workflow. The easiest and most common way is with: + + (host)$ make docker-compose + +This utilizes the image you built in the previous step and will automatically start all required services and dependent containers. You'll +be able to watch log messages and events as they come through. + +The Makefile assumes that the image you built is tagged with your current branch. This allows you to pre-build images for different contexts +but you may want to use a particular branch's image (for instance if you are developing a PR from a branch based on the integration branch): + + (host)$ COMPOSE_TAG=devel make docker-compose + +Starting the development environment at the container shell +----------------------------------------------------------- + +Often times you'll want to start the development environment without immediately starting all services and instead be taken directly to a shell: + + (host)$ make docker-compose-test + +From here you'll need to bootstrap the development environment before it will be usable for you. The `docker-compose` make target will +automatically do this: + + (container)$ /bootstrap_development.sh + +From here you can start each service individually, or choose to start all service in a pre-configured tmux session: + + (container)# cd /awx_devel + (container)# make server + +Using the development environment +--------------------------------- + +With the development environment running there are a few optional steps to pre-populate the environment with data. If you are using the `docker-compose` +method above you'll first need a shell in the container: + + (host)$ docker exec -it tools_awx_1 bash + +Create a superuser account: + + (container)# awx-manage createsuperuser + +Preload AWX with demo data: + + (container)# awx-manage create_preload_data + +This information will persist in the database running in the `tools_postgres_1` container, until it is removed. You may periodically need to recreate +this container and database if the database schema changes in an upstream commit. + +You should now be able to visit and login to the AWX user interface at https://localhost:8043 or http://localhost:8013 if you have built the UI. +If not you can visit the API directly in your browser at: https://localhost:8043/api/ or http://localhost:8013/api/ + +When working on the source code for AWX the code will auto-reload for you when changes are made, with the exception of any background tasks that run in +celery. + +Occasionally it may be necessary to purge any containers and images that may have collected: + + (host)$ make docker-clean + +There are host of other shortcuts, tools, and container configurations in the Makefile designed for various purposes. Feel free to explore. + +What should I work on? +====================== + +We list our specs in `/docs`. `/docs/current` are things that we are actively working on. `/docs/future` are ideas for future work and the direction we +want that work to take. Fixing bugs, translations, and updates to documentation are also appreciated. + +Be aware that if you are working in a part of the codebase that is going through active development your changes may be rejected or you may be asked to +rebase them. A good idea before starting work is to have a discussion with us on IRC or the mailing list. + +Submitting Pull Requests +======================== + +Fixes and Features for AWX will go through the Github PR interface. There are a few things that can be done to help the visibility of your change +and increase the likelihood that it will be accepted + +> Add UI detail to these + +* No issues when running linters/code checkers + * Python: flake8: `(container)/awx_devel$ make flake8` + * Javascript: JsHint: `(container)/awx_devel$ make jshint` +* No issues from unit tests + * Python: py.test: `(container)/awx_devel$ make test` + * JavaScript: Jasmine: `(container)/awx_devel$ make ui-test-ci` +* Write tests for new functionality, update/add tests for bug fixes +* Make the smallest change possible +* Write good commit messages: https://chris.beams.io/posts/git-commit/ + +It's generally a good idea to discuss features with us first by engaging us in IRC or on the mailing list, especially if you are unsure if it's a good +fit. + +We like to keep our commit history clean and will require resubmission of pull requests that contain merge commits. Use `git pull --rebase` rather than +`git pull` and `git rebase` rather than `git merge`. + +Sometimes it might take us a while to fully review your PR. We try to keep the `devel` branch in pretty good working order so we review requests carefuly. +Please be patient. + +All submitted PRs will have the linter and unit tests run against them and the status reported in the PR. + +Reporting Issues +================ + +Use the Github issue tracker for filing bugs. In order to save time and help us respond to issues quickly, make sure to fill out as much of the issue template +as possible. Version information and an accurate reproducing scenario are critical to helping us identify the problem. + +When reporting issues for the UI we also appreciate having screenshots and any error messages from the web browser's console. It's not unsual for browser extensions +and plugins to cause problems. Reporting those will also help speed up analyzing and resolving UI bugs. + +For the API and backend services, please capture all of the logs that you can from the time the problem was occuring. + +Don't use the issue tracker to get help on how to do something - please use the mailing list and IRC for that. + +How issues are resolved +----------------------- + +We triage our issues into high, medium, and low and will tag them with the relevant component (api, ui, installer, etc). We will typically focus on high priority +issues. There aren't hard and fast rules for determining the severity of an issue, but generally high priority issues have an increased likelihood of breaking +existing functionality and/or negatively impacting a large number of users. + +If your issue isn't considered `high` priority then please be patient as it may take some time to get to your report. + +Before opening a new issue, please use the issue search feature to see if it's already been reported. If you have any extra detail to provide then please comment. +Rather than posting a "me too" comment you might consider giving it a "thumbs up" on github. + +Ansible Issue Bot +----------------- +> Fill in diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000000..105e63961d --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,2 @@ +Installing AWX +============== diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index c12528c389..81224febdc 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -5,7 +5,7 @@ ### Environment
- _______________
-< {{BRAND_NAME}} {{version}} >
- ---------------
+{{ speechBubble }}
\ ^__^
\ (oo)\_______
(__) A )\/\
@@ -26,7 +24,7 @@
Ansible {{ ansible_version }}
-
+
Copyright © 2017 Red Hat, Inc.
Visit Ansible.com for more information.
diff --git a/awx/ui/client/src/access/add-rbac-resource/rbac-resource.controller.js b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.controller.js
index 1af53c230b..f43226537c 100644
--- a/awx/ui/client/src/access/add-rbac-resource/rbac-resource.controller.js
+++ b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.controller.js
@@ -13,29 +13,71 @@
export default ['$rootScope', '$scope', 'GetBasePath', 'Rest', '$q', 'Wait', 'ProcessErrors', function(rootScope, scope, GetBasePath, Rest, $q, Wait, ProcessErrors) {
- scope.allSelected = [];
+ init();
- // the object permissions are being added to
- scope.object = scope.resourceData.data;
- // array for all possible roles for the object
- scope.roles = _.omit(scope.object.summary_fields.object_roles, (key) => {
- return key.name === 'Read';
- });
+ function init(){
- // TODO: get working with api
- // array w roles and descriptions for key
- scope.roleKey = Object
- .keys(scope.object.summary_fields.object_roles)
- .map(function(key) {
- return {
- name: scope.object.summary_fields
- .object_roles[key].name,
- description: scope.object.summary_fields
- .object_roles[key].description
- };
+ let resources = ['users', 'teams'];
+
+ scope.allSelected = {};
+ _.each(resources, (type) => scope.allSelected[type] = {});
+
+ // the object permissions are being added to
+ scope.object = scope.resourceData.data;
+ // array for all possible roles for the object
+ scope.roles = _.omit(scope.object.summary_fields.object_roles, (key) => {
+ return key.name === 'Read';
});
- scope.showKeyPane = false;
+ // TODO: get working with api
+ // array w roles and descriptions for key
+ scope.roleKey = Object
+ .keys(scope.object.summary_fields.object_roles)
+ .map(function(key) {
+ return {
+ name: scope.object.summary_fields
+ .object_roles[key].name,
+ description: scope.object.summary_fields
+ .object_roles[key].description
+ };
+ });
+
+ scope.showKeyPane = false;
+
+ scope.tab = {
+ users: true,
+ teams: false,
+ };
+
+ // pop/push into unified collection of selected users & teams
+ scope.$on("selectedOrDeselected", function(e, value) {
+ let resourceType = scope.currentTab(),
+ item = value.value;
+
+ function buildName(user) {
+ return (user.first_name &&
+ user.last_name) ?
+ user.first_name + " " +
+ user.last_name :
+ user.username;
+ }
+
+ if (value.isSelected) {
+ if (item.type === 'user') {
+ item.name = buildName(item);
+ }
+ scope.allSelected[resourceType][item.id] = item;
+ scope.allSelected[resourceType][item.id].roles = [];
+ } else {
+ delete scope.allSelected[resourceType][item.id];
+ }
+ });
+
+ }
+
+ scope.currentTab = function(){
+ return _.findKey(scope.tab, (tab) => tab);
+ };
scope.removeObject = function(obj){
_.remove(scope.allSelected, {id: obj.id});
@@ -46,61 +88,34 @@ export default ['$rootScope', '$scope', 'GetBasePath', 'Rest', '$q', 'Wait', 'Pr
scope.showKeyPane = !scope.showKeyPane;
};
- // handle form tab changes
- scope.toggleFormTabs = function(list) {
- scope.usersSelected = (list === 'users');
- scope.teamsSelected = !scope.usersSelected;
+ scope.hasSelectedRows = function(){
+ return _.any(scope.allSelected, (type) => Object.keys(type).length > 0);
};
- // pop/push into unified collection of selected users & teams
- scope.$on("selectedOrDeselected", function(e, value) {
- let item = value.value;
-
- function buildName(user) {
- return (user.first_name &&
- user.last_name) ?
- user.first_name + " " +
- user.last_name :
- user.username;
- }
-
- if (value.isSelected) {
- if (item.type === 'user') {
- item.name = buildName(item);
- }
- scope.allSelected.push(item);
- } else {
- _.remove(scope.allSelected, { id: item.id });
- }
- });
-
- // update post url list
- scope.$watch("allSelected", function(val) {
- scope.posts = _
- .flatten((val || [])
- .map(function(owner) {
- var url = GetBasePath(owner.type + "s") + owner.id +
- "/roles/";
-
- return (owner.roles || [])
- .map(function(role) {
- return {
- url: url,
- id: role.value || role.id
- };
- });
- }));
- }, true);
+ scope.selectTab = function(selected){
+ _.each(scope.tab, (value, key, collection) => {
+ collection[key] = (selected === key);
+ });
+ };
// post roles to api
scope.updatePermissions = function() {
Wait('start');
- var requests = scope.posts
- .map(function(post) {
- Rest.setUrl(post.url);
- return Rest.post({ "id": post.id });
+ let requests = [];
+
+ _.forEach(scope.allSelected, (selectedValues) => {
+ _.forEach(selectedValues, (selectedValue) => {
+ var url = GetBasePath(selectedValue.type + "s") + selectedValue.id +
+ "/roles/";
+
+ (selectedValue.roles || [])
+ .map(function(role) {
+ Rest.setUrl(url);
+ requests.push(Rest.post({ "id": role.value || role.id }));
+ });
});
+ });
$q.all(requests)
.then(function() {
diff --git a/awx/ui/client/src/access/add-rbac-resource/rbac-resource.directive.js b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.directive.js
index 7c1ec307d9..f125c64afd 100644
--- a/awx/ui/client/src/access/add-rbac-resource/rbac-resource.directive.js
+++ b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.directive.js
@@ -21,7 +21,7 @@ export default ['templateUrl', '$state',
controller: controller,
templateUrl: templateUrl('access/add-rbac-resource/rbac-resource'),
link: function(scope, element, attrs) {
- scope.toggleFormTabs('users');
+ scope.selectTab('users');
$('#add-permissions-modal').modal('show');
scope.closeModal = function() {
diff --git a/awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html
index cc2c3afbba..a01030455b 100644
--- a/awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html
+++ b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html
@@ -33,72 +33,72 @@
+ ng-click="selectTab('users')"
+ ng-class="{'is-selected': tab.users }" translate>
Users
+ ng-click="selectTab('teams')"
+ ng-class="{'is-selected': tab.teams }" translate>
Teams
-
+
-
+
-
-
-
-
- 2
-
- Please assign roles to the selected users/teams
-
- Key
-
-
-
-
-
- {{ key.name }}
-
-
- {{ key.description || "No description provided" }}
+
+
+
+
+ 2
+
+ Please assign roles to the selected users/teams
+
+ Key
-
-
+
+
+
@@ -110,7 +110,7 @@
diff --git a/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js b/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js
index 423db0eceb..eb67bb30f5 100644
--- a/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js
+++ b/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js
@@ -7,10 +7,10 @@
/* jshint unused: vars */
export default ['$compile', 'i18n', 'generateList',
'ProjectList', 'TemplateList', 'InventoryList', 'CredentialList',
- 'OrganizationList',
+ 'OrganizationList', '$window',
function($compile, i18n, generateList,
ProjectList, TemplateList, InventoryList, CredentialList,
- OrganizationList) {
+ OrganizationList, $window) {
return {
restrict: 'E',
scope: {
@@ -42,6 +42,7 @@ export default ['$compile', 'i18n', 'generateList',
name: list.fields.name,
scm_type: list.fields.scm_type
};
+ list.fields.name.ngClick = 'linkoutResource("project", project)';
list.fields.name.columnClass = 'col-md-5 col-sm-5 col-xs-10';
list.fields.scm_type.columnClass = 'col-md-5 col-sm-5 hidden-xs';
break;
@@ -50,6 +51,7 @@ export default ['$compile', 'i18n', 'generateList',
name: list.fields.name,
organization: list.fields.organization
};
+ list.fields.name.ngClick = 'linkoutResource("inventory", inventory)';
list.fields.name.columnClass = 'col-md-5 col-sm-5 col-xs-10';
list.fields.organization.columnClass = 'col-md-5 col-sm-5 hidden-xs';
break;
@@ -60,6 +62,7 @@ export default ['$compile', 'i18n', 'generateList',
name: list.fields.name
};
list.fields.name.columnClass = 'col-md-5 col-sm-5 col-xs-10';
+ list.fields.name.ngClick = 'linkoutResource("job_template", job_template)';
break;
case 'workflow_templates':
list.name = 'workflow_job_templates';
@@ -68,12 +71,20 @@ export default ['$compile', 'i18n', 'generateList',
name: list.fields.name
};
list.fields.name.columnClass = 'col-md-5 col-sm-5 col-xs-10';
+ list.fields.name.ngClick = 'linkoutResource("workflow_job_template", workflow_job_template)';
break;
case 'credentials':
+ list.fields = {
+ name: list.fields.name
+ };
+ list.fields.name.ngClick = 'linkoutResource("credential", credential)';
+ list.fields.name.columnClass = 'col-md-5 col-sm-5 col-xs-10';
+ break;
case 'organizations':
list.fields = {
name: list.fields.name
};
+ list.fields.name.ngClick = 'linkoutResource("organization", organization)';
list.fields.name.columnClass = 'col-md-5 col-sm-5 col-xs-10';
break;
}
@@ -126,6 +137,40 @@ export default ['$compile', 'i18n', 'generateList',
multiselect_scope[type][deselectedIdx].isSelected = false;
};
+ scope.linkoutResource = function(type, resource) {
+
+ let url;
+
+ switch(type){
+ case 'project':
+ url = "/#/projects/" + resource.id;
+ break;
+ case 'inventory':
+ url = resource.kind && resource.kind === "smart" ? "/#/inventories/smart/" + resource.id : "/#/inventories/inventory/" + resource.id;
+ break;
+ case 'job_template':
+ url = "/#/templates/job_template/" + resource.id;
+ break;
+ case 'workflow_job_template':
+ url = "/#/templates/workflow_job_template/" + resource.id;
+ break;
+ case 'user':
+ url = "/#/users/" + resource.id;
+ break;
+ case 'team':
+ url = "/#/teams/" + resource.id;
+ break;
+ case 'organization':
+ url = "/#/organizations/" + resource.id;
+ break;
+ case 'credential':
+ url = "/#/credentials/" + resource.id;
+ break;
+ }
+
+ $window.open(url,'_blank');
+ };
+
element.append(list_html);
$compile(element.contents())(scope);
}
diff --git a/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.controller.js b/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.controller.js
index 1c27f795ce..fb1f7b20db 100644
--- a/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.controller.js
+++ b/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.controller.js
@@ -26,8 +26,8 @@ function(scope, $state, i18n, CreateSelect2, Rest, $q, Wait, ProcessErrors) {
// selected[type][id] === { roles: [ ... ], ... }
// collection of resources selected in section 1
- scope.selected = {};
- _.each(resources, (type) => scope.selected[type] = {});
+ scope.allSelected = {};
+ _.each(resources, (type) => scope.allSelected[type] = {});
// collection of assignable roles per type of resource
scope.keys = {};
@@ -94,17 +94,17 @@ function(scope, $state, i18n, CreateSelect2, Rest, $q, Wait, ProcessErrors) {
};
scope.showSection2Container = function(){
- return _.any(scope.selected, (type) => Object.keys(type).length > 0);
+ return _.any(scope.allSelected, (type) => Object.keys(type).length > 0);
};
scope.showSection2Tab = function(tab){
- return Object.keys(scope.selected[tab]).length > 0;
+ return Object.keys(scope.allSelected[tab]).length > 0;
};
scope.saveEnabled = function(){
let missingRole = false;
let resourceSelected = false;
- _.forOwn(scope.selected, function(value, key) {
+ _.forOwn(scope.allSelected, function(value, key) {
if(Object.keys(value).length > 0) {
// A resource from this tab has been selected
resourceSelected = true;
@@ -129,11 +129,11 @@ function(scope, $state, i18n, CreateSelect2, Rest, $q, Wait, ProcessErrors) {
item = value.value;
if (value.isSelected) {
- scope.selected[resourceType][item.id] = item;
- scope.selected[resourceType][item.id].roles = [];
+ scope.allSelected[resourceType][item.id] = item;
+ scope.allSelected[resourceType][item.id].roles = [];
aggregateKey(item, resourceType);
} else {
- delete scope.selected[resourceType][item.id];
+ delete scope.allSelected[resourceType][item.id];
}
});
@@ -142,7 +142,7 @@ function(scope, $state, i18n, CreateSelect2, Rest, $q, Wait, ProcessErrors) {
//Wait('start');
// builds an array of role entities to apply to current user or team
- let roles = _(scope.selected).map( (resources, type) => {
+ let roles = _(scope.allSelected).map( (resources, type) => {
return _.map(resources, (resource) => {
return resource.summary_fields.object_roles[scope.roleSelection[type]];
});
diff --git a/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.partial.html b/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.partial.html
index 4ec00be443..db62000173 100644
--- a/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.partial.html
+++ b/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.partial.html
@@ -173,11 +173,11 @@
-
+
diff --git a/awx/ui/client/src/access/add-rbac.block.less b/awx/ui/client/src/access/add-rbac.block.less
index 66b150501a..177388c07e 100644
--- a/awx/ui/client/src/access/add-rbac.block.less
+++ b/awx/ui/client/src/access/add-rbac.block.less
@@ -1,5 +1,3 @@
-@import "../shared/branding/colors.default.less";
-
/** @define AddPermissions */
.AddPermissions-backDrop {
@@ -46,6 +44,10 @@
padding: 0px 20px;
}
+.AddPermissions-body .List-well {
+ margin-top: 0;
+}
+
.AddPermissions-footer {
display: flex;
flex-wrap: wrap-reverse;
@@ -131,15 +133,6 @@
overflow: hidden;
}
-.AddPermissions-roleType {
- padding: 0px 6px;
- font-size: 10px;
- color: @default-interface-txt;
- text-transform: uppercase;
- background-color: @default-bg;
- margin-left: 6px;
-}
-
.AddPermissions-roleSelect {
width: ~"calc(70% - 40px)";
margin-right: 20px;
@@ -203,8 +196,7 @@
margin: 20px 0;
font-size: 12px;
width: 100%;
- padding: 15px;
- padding-top: 10px;
+ padding: 20px;
margin-bottom: 15px;
border-radius: 4px;
border: 1px solid @login-notice-border;
diff --git a/awx/ui/client/src/access/permissions-list.controller.js b/awx/ui/client/src/access/permissions-list.controller.js
index ebdbc394d2..f36ee5fa64 100644
--- a/awx/ui/client/src/access/permissions-list.controller.js
+++ b/awx/ui/client/src/access/permissions-list.controller.js
@@ -4,8 +4,8 @@
* All Rights Reserved
*************************************************/
-export default ['$scope', 'ListDefinition', 'Dataset', 'Wait', 'Rest', 'ProcessErrors', 'Prompt', '$state',
- function($scope, list, Dataset, Wait, Rest, ProcessErrors, Prompt, $state) {
+export default ['$scope', 'ListDefinition', 'Dataset', 'Wait', 'Rest', 'ProcessErrors', 'Prompt', '$state', '$filter',
+ function($scope, list, Dataset, Wait, Rest, ProcessErrors, Prompt, $state, $filter) {
init();
function init() {
@@ -15,11 +15,12 @@ export default ['$scope', 'ListDefinition', 'Dataset', 'Wait', 'Rest', 'ProcessE
}
$scope.deletePermissionFromUser = function(userId, userName, roleName, roleType, url) {
+
var action = function() {
$('#prompt-modal').modal('hide');
Wait('start');
Rest.setUrl(url);
- Rest.post({ "disassociate": true, "id": userId })
+ Rest.post({ "disassociate": true, "id": Number(userId) })
.success(function() {
Wait('stop');
$state.go('.', null, {reload: true});
@@ -36,9 +37,9 @@ export default ['$scope', 'ListDefinition', 'Dataset', 'Wait', 'Rest', 'ProcessE
hdr: `Remove role`,
body: `
- Confirm the removal of the ${roleType}
+ Confirm the removal of the ${$filter('sanitize')(roleType)}
${roleName}
- role associated with ${userName}.
+ role associated with ${$filter('sanitize')(userName)}.
`,
action: action,
@@ -47,6 +48,7 @@ export default ['$scope', 'ListDefinition', 'Dataset', 'Wait', 'Rest', 'ProcessE
};
$scope.deletePermissionFromTeam = function(teamId, teamName, roleName, roleType, url) {
+
var action = function() {
$('#prompt-modal').modal('hide');
Wait('start');
@@ -68,9 +70,9 @@ export default ['$scope', 'ListDefinition', 'Dataset', 'Wait', 'Rest', 'ProcessE
hdr: `Remove role`,
body: `
- Confirm the removal of the ${roleType}
+ Confirm the removal of the ${$filter('sanitize')(roleType)}
${roleName}
- role associated with the ${teamName} team.
+ role associated with the ${$filter('sanitize')(teamName)} team.
`,
action: action,
diff --git a/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js b/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js
index 02017f4c7f..190cf125e6 100644
--- a/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js
+++ b/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js
@@ -71,7 +71,6 @@ export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateL
};
list.fields.name.ngClick = 'linkoutResource("job_template", job_template)';
list.fields.name.columnClass = 'col-md-6 col-sm-6 col-xs-11';
- list.fields.name.ngHref = '#/templates/job_template/{{job_template.id}}';
break;
case 'WorkflowTemplates':
@@ -83,7 +82,6 @@ export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateL
};
list.fields.name.ngClick = 'linkoutResource("workflow_job_template", workflow_template)';
list.fields.name.columnClass = 'col-md-6 col-sm-6 col-xs-11';
- list.fields.name.ngHref = '#/templates/workflow_job_template/{{workflow_template.id}}';
break;
case 'Users':
list.fields = {
@@ -174,9 +172,11 @@ export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateL
}
function isSelected(item){
- if(_.find(scope.allSelected, {id: item.id, type: item.type})){
- item.isSelected = true;
- }
+ _.forEach(scope.allSelected[list.name], (selectedRow) => {
+ if(selectedRow.id === item.id) {
+ item.isSelected = true;
+ }
+ });
return item;
}
element.append(list_html);
diff --git a/awx/ui/client/src/access/rbac-role-column/roleList.block.less b/awx/ui/client/src/access/rbac-role-column/roleList.block.less
index 40b76717a3..98638802c3 100644
--- a/awx/ui/client/src/access/rbac-role-column/roleList.block.less
+++ b/awx/ui/client/src/access/rbac-role-column/roleList.block.less
@@ -1,5 +1,4 @@
/** @define RoleList */
-@import "../../shared/branding/colors.default.less";
.RoleList {
display: flex;
diff --git a/awx/ui/client/src/access/rbac-role-column/roleList.directive.js b/awx/ui/client/src/access/rbac-role-column/roleList.directive.js
index 10b589cf7c..ec3b79754d 100644
--- a/awx/ui/client/src/access/rbac-role-column/roleList.directive.js
+++ b/awx/ui/client/src/access/rbac-role-column/roleList.directive.js
@@ -75,7 +75,7 @@ export default
} else {
Prompt({
hdr: `User access removal`,
- body: `Please confirm that you would like to remove ${entry.name} access from ${user.username}.`,
+ body: `Please confirm that you would like to remove ${entry.name} access from ${$filter('sanitize')(user.username)}.`,
action: action,
actionText: 'REMOVE'
});
diff --git a/awx/ui/client/src/activity-stream/activitystream.route.js b/awx/ui/client/src/activity-stream/activitystream.route.js
index 22ef581bc6..ffdb424626 100644
--- a/awx/ui/client/src/activity-stream/activitystream.route.js
+++ b/awx/ui/client/src/activity-stream/activitystream.route.js
@@ -20,7 +20,8 @@ export default {
order_by: '-timestamp',
or__object1__in: null,
or__object2__in: null
- }
+ },
+ dynamic: true
}
},
ncyBreadcrumb: {
diff --git a/awx/ui/client/src/activity-stream/factories/show-detail.factory.js b/awx/ui/client/src/activity-stream/factories/show-detail.factory.js
index d0a1da8bfe..e275013acc 100644
--- a/awx/ui/client/src/activity-stream/factories/show-detail.factory.js
+++ b/awx/ui/client/src/activity-stream/factories/show-detail.factory.js
@@ -1,5 +1,5 @@
export default
- function ShowDetail($filter, Find) {
+ function ShowDetail($filter, Find, ParseTypeChange, ParseVariableString) {
return function (params, scope) {
var activity_id = params.activity_id,
@@ -20,6 +20,13 @@ export default
scope.operation = activity.description;
scope.header = "Event " + activity.id;
+ scope.variables = ParseVariableString(scope.changes);
+ scope.parseType = 'json';
+ ParseTypeChange({ scope: scope,
+ field_id: 'activity-stream-changes',
+ readOnly: true });
+ scope.parseTypeChange('parseType', 'variables');
+
// Open the modal
$('#stream-detail-modal').modal({
show: true,
@@ -35,4 +42,4 @@ export default
};
}
- ShowDetail.$inject = ['$filter', 'Find'];
+ ShowDetail.$inject = ['$filter', 'Find', 'ParseTypeChange', 'ParseVariableString'];
diff --git a/awx/ui/client/src/activity-stream/get-target-title.factory.js b/awx/ui/client/src/activity-stream/get-target-title.factory.js
index 543b6acdb3..3921f2d530 100644
--- a/awx/ui/client/src/activity-stream/get-target-title.factory.js
+++ b/awx/ui/client/src/activity-stream/get-target-title.factory.js
@@ -7,6 +7,9 @@ export default function GetTargetTitle(i18n) {
case 'project':
rtnTitle = i18n._('PROJECTS');
break;
+ case 'credential_type':
+ rtnTitle = i18n._('CREDENTIAL TYPES');
+ break;
case 'inventory':
rtnTitle = i18n._('INVENTORIES');
break;
diff --git a/awx/ui/client/src/activity-stream/model-to-base-path-key.factory.js b/awx/ui/client/src/activity-stream/model-to-base-path-key.factory.js
index 51c197109e..10285871d0 100644
--- a/awx/ui/client/src/activity-stream/model-to-base-path-key.factory.js
+++ b/awx/ui/client/src/activity-stream/model-to-base-path-key.factory.js
@@ -21,6 +21,9 @@ export default function ModelToBasePathKey() {
case 'project':
basePathKey = 'projects';
break;
+ case 'credential_type':
+ basePathKey = 'credential_types';
+ break;
case 'inventory':
basePathKey = 'inventory';
break;
diff --git a/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.block.less b/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.block.less
index 9e0cc73720..de9445942f 100644
--- a/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.block.less
+++ b/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.block.less
@@ -1,5 +1,3 @@
-@import "./client/src/shared/branding/colors.default.less";
-
.StreamDetail-actionButton {
padding: 4px 25px!important;
}
@@ -11,6 +9,7 @@
.StreamDetail-rowTitle {
color: @default-interface-txt;
+ font-size: 12px;
}
.StreamDetail-inlineRowTitle {
diff --git a/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html b/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html
index 9735fd6e17..fb08ec98de 100644
--- a/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html
+++ b/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html
@@ -1,29 +1,36 @@
-
-
-
-
-
-
-
-
-
-
- INITIATED BY
-
-
-
- ACTION
-
-
-
- CHANGES
- {{ changes | json : spacing}}
-
-
-
- OK
-
-
-
+
+
+
diff --git a/awx/ui/client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js b/awx/ui/client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js
index fb90dccafe..7231cdd9c2 100644
--- a/awx/ui/client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js
+++ b/awx/ui/client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js
@@ -29,6 +29,7 @@ export default ['templateUrl', 'i18n', function(templateUrl, i18n) {
{label: i18n._('Notification Templates'), value: 'notification_template'},
{label: i18n._('Organizations'), value: 'organization'},
{label: i18n._('Projects'), value: 'project'},
+ {label: i18n._('Credential Types'), value: 'credential_type'},
{label: i18n._('Schedules'), value: 'schedule'},
{label: i18n._('Teams'), value: 'team'},
{label: i18n._('Templates'), value: 'template'},
diff --git a/awx/ui/client/src/activity-stream/streams.list.js b/awx/ui/client/src/activity-stream/streams.list.js
index e1699741e6..0105f1899e 100644
--- a/awx/ui/client/src/activity-stream/streams.list.js
+++ b/awx/ui/client/src/activity-stream/streams.list.js
@@ -18,7 +18,6 @@ export default ['i18n', function(i18n) {
selectInstructions: '',
index: false,
hover: true,
- "class": "table-condensed",
toolbarAuxAction: " ",
fields: {
diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js
index fffaac55a4..00fa2a3cf7 100644
--- a/awx/ui/client/src/app.js
+++ b/awx/ui/client/src/app.js
@@ -151,7 +151,8 @@ var awApp = angular.module('awApp', [
.config(['ngToastProvider', function(ngToastProvider) {
ngToastProvider.configure({
animation: 'slide',
- dismissOnTimeout: true,
+ dismissOnTimeout: false,
+ dismissButton: true,
timeout: 4000
});
}])
@@ -206,7 +207,6 @@ var awApp = angular.module('awApp', [
LoadConfig, Store, pendoService, Prompt, Rest, Wait,
ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService,
$filter, SocketService, AppStrings) {
-
$rootScope.$state = $state;
$rootScope.$state.matches = function(stateName) {
return $state.current.name.search(stateName) > 0;
@@ -344,11 +344,10 @@ var awApp = angular.module('awApp', [
Authorization.restoreUserInfo(); //user must have hit browser refresh
}
if (next && (next.name !== "signIn" && next.name !== "signOut" && next.name !== "license")) {
- if($rootScope.configReady === true){
+ ConfigService.getConfig().then(function() {
// if not headed to /login or /logout, then check the license
CheckLicense.test(event);
- }
-
+ });
}
}
activateTab();
diff --git a/awx/ui/client/src/bread-crumb/bread-crumb.block.less b/awx/ui/client/src/bread-crumb/bread-crumb.block.less
index f75894afeb..f0e9902a9b 100644
--- a/awx/ui/client/src/bread-crumb/bread-crumb.block.less
+++ b/awx/ui/client/src/bread-crumb/bread-crumb.block.less
@@ -1,5 +1,3 @@
-@import "../shared/branding/colors.default.less";
-
/** @define BreadCrumb */
.BreadCrumb {
diff --git a/awx/ui/client/src/bread-crumb/bread-crumb.directive.js b/awx/ui/client/src/bread-crumb/bread-crumb.directive.js
index 93d17a00c9..acb9877944 100644
--- a/awx/ui/client/src/bread-crumb/bread-crumb.directive.js
+++ b/awx/ui/client/src/bread-crumb/bread-crumb.directive.js
@@ -111,8 +111,10 @@ export default
stateGoParams.id = $state.params[streamConfig.activityStreamId];
}
if(stateGoParams.target === "custom_inventory_script"){
- stateGoParams.activity_search[streamConfig.activityStreamTarget] = $state.params.inventory_script_id;
- stateGoParams.id = $state.params.inventory_script_id;
+ if ($state.params.inventory_script_id !== undefined) {
+ stateGoParams.activity_search[streamConfig.activityStreamTarget] = $state.params.inventory_script_id;
+ stateGoParams.id = $state.params.inventory_script_id;
+ }
}
}
diff --git a/awx/ui/client/src/configuration/configuration.block.less b/awx/ui/client/src/configuration/configuration.block.less
index 43e7dcd637..2f32e9ed56 100644
--- a/awx/ui/client/src/configuration/configuration.block.less
+++ b/awx/ui/client/src/configuration/configuration.block.less
@@ -1,6 +1,3 @@
-@import "./client/src/shared/branding/colors.default.less";
-@import "../shared/branding/colors.less";
-
.Form-resetValue, .Form-resetAll {
text-transform: uppercase;
font-weight: normal;
diff --git a/awx/ui/client/src/configuration/configuration.controller.js b/awx/ui/client/src/configuration/configuration.controller.js
index a915601cba..7d35be84e3 100644
--- a/awx/ui/client/src/configuration/configuration.controller.js
+++ b/awx/ui/client/src/configuration/configuration.controller.js
@@ -86,7 +86,7 @@ export default [
// does a string.split(', ') w/ an extra space
// behind the comma.
if(key === "AD_HOC_COMMANDS"){
- $scope[key] = data[key].toString();
+ $scope[key] = data[key];
} else if (key === "AUTH_LDAP_USER_SEARCH" || key === "AUTH_LDAP_GROUP_SEARCH") {
$scope[key] = JSON.stringify(data[key]);
} else {
@@ -321,6 +321,9 @@ export default [
// We need to re-instantiate the Select2 element
// after resetting the value. Example:
$scope.$broadcast(key+'_populated', null, false);
+ if(key === "AD_HOC_COMMANDS"){
+ $scope.$broadcast(key+'_reverted', null, false);
+ }
}
else if($scope[key + '_field'].reset === "CUSTOM_LOGO"){
$scope.$broadcast(key+'_reverted');
@@ -379,10 +382,10 @@ export default [
//Parse dropdowns and dropdowns labeled as lists
if($scope[key] === null) {
payload[key] = null;
- } else if($scope[key][0] && $scope[key][0].value !== undefined) {
+ } else if(!_.isEmpty($scope[`${key}_values`])) {
if(multiselectDropdowns.indexOf(key) !== -1) {
// Handle AD_HOC_COMMANDS
- payload[key] = ConfigurationUtils.listToArray(_.map($scope[key], 'value').join(','));
+ payload[key] = $scope[`${key}_values`];
} else {
payload[key] = _.map($scope[key], 'value').join(',');
}
@@ -501,6 +504,9 @@ export default [
// We need to re-instantiate the Select2 element
// after resetting the value. Example:
$scope.$broadcast(key+'_populated', null, false);
+ if(key === "AD_HOC_COMMANDS"){
+ $scope.$broadcast(key+'_reverted', null, false);
+ }
}
else if($scope[key + '_field'].reset === "CUSTOM_LOGO"){
$scope.$broadcast(key+'_reverted');
diff --git a/awx/ui/client/src/configuration/jobs-form/configuration-jobs.controller.js b/awx/ui/client/src/configuration/jobs-form/configuration-jobs.controller.js
index b70e3d71f9..7325c8fd42 100644
--- a/awx/ui/client/src/configuration/jobs-form/configuration-jobs.controller.js
+++ b/awx/ui/client/src/configuration/jobs-form/configuration-jobs.controller.js
@@ -79,9 +79,6 @@ export default [
noPanel: true
});
- // Flag to avoid re-rendering and breaking Select2 dropdowns on tab switching
- var dropdownRendered = false;
-
function initializeCodeInput () {
let name = 'AWX_TASK_ENV';
@@ -95,35 +92,49 @@ export default [
$scope.parseTypeChange('parseType', name);
}
- function populateAdhocCommand(flag){
- $scope.$parent.AD_HOC_COMMANDS = $scope.$parent.AD_HOC_COMMANDS.toString();
- var ad_hoc_commands = $scope.$parent.AD_HOC_COMMANDS.split(',');
- $scope.$parent.AD_HOC_COMMANDS = _.map(ad_hoc_commands, function(item){
- let option = _.find($scope.$parent.AD_HOC_COMMANDS_options, { value: item });
- if(!option){
- option = {
- name: item,
- value: item,
- label: item
- };
- $scope.$parent.AD_HOC_COMMANDS_options.push(option);
- }
- return option;
+ function loadAdHocCommands () {
+ $scope.$parent.AD_HOC_COMMANDS_values = $scope.$parent.AD_HOC_COMMANDS.map(value => value);
+ $scope.$parent.AD_HOC_COMMANDS = $scope.$parent.AD_HOC_COMMANDS.map(value => ({
+ value,
+ name: value,
+ label: value
+ }));
+
+ $scope.$parent.AD_HOC_COMMANDS_options = $scope.$parent.AD_HOC_COMMANDS.map(tag => tag);
+
+ CreateSelect2({
+ element: '#configuration_jobs_template_AD_HOC_COMMANDS',
+ multiple: true,
+ addNew: true,
+ placeholder: i18n._('Select commands')
});
- if(flag !== undefined){
- dropdownRendered = flag;
- }
+ }
+
+ function revertAdHocCommands () {
+ $scope.$parent.AD_HOC_COMMANDS = $scope.$parent.configDataResolve.AD_HOC_COMMANDS.default.map(value => ({
+ value,
+ name: value,
+ label: value
+ }));
+
+ $('.select2-selection__choice').each(function(i, element){
+ if(!_.contains($scope.$parent.configDataResolve.AD_HOC_COMMANDS.default, element.title)){
+ $(`#configuration_jobs_template_AD_HOC_COMMANDS option[value='${element.title}']`).remove();
+ element.remove();
+ }
+ });
+
+ $scope.$parent.AD_HOC_COMMANDS_options = $scope.$parent.AD_HOC_COMMANDS.map(tag => tag);
+ $scope.$parent.AD_HOC_COMMANDS_values = $scope.$parent.AD_HOC_COMMANDS.map(tag => tag.value);
+ CreateSelect2({
+ element: '#configuration_jobs_template_AD_HOC_COMMANDS',
+ multiple: true,
+ addNew: true,
+ placeholder: i18n._('Select commands'),
+ options: $scope.$parent.AD_HOC_COMMANDS_options
+ });
- if(!dropdownRendered) {
- dropdownRendered = true;
- CreateSelect2({
- element: '#configuration_jobs_template_AD_HOC_COMMANDS',
- multiple: true,
- addNew: true,
- placeholder: i18n._('Select commands')
- });
- }
}
// Fix for bug where adding selected opts causes form to be $dirty and triggering modal
@@ -132,10 +143,26 @@ export default [
$scope.$parent.configuration_jobs_template_form.$setPristine();
}, 1000);
- $scope.$on('AD_HOC_COMMANDS_populated', function(e, data, flag) {
- populateAdhocCommand(flag);
+
+ // Managing the state of select2's tags since the behavior is unpredictable otherwise.
+ let commandsElement = $('#configuration_jobs_template_AD_HOC_COMMANDS');
+
+ commandsElement.on('select2:select', event => {
+ let command = event.params.data.text;
+ let commands = $scope.$parent.AD_HOC_COMMANDS_values;
+
+ commands.push(command);
});
+ commandsElement.on('select2:unselect', event => {
+ let command = event.params.data.text;
+ let commands = $scope.$parent.AD_HOC_COMMANDS_values;
+
+ $scope.$parent.AD_HOC_COMMANDS_values = commands.filter(value => value !== command);
+ });
+
+ $scope.$on('AD_HOC_COMMANDS_reverted', () => revertAdHocCommands());
+
/*
* Controllers for each tab are initialized when configuration is opened. A listener
* on the URL itself is necessary to determine which tab is active. If a non-active
@@ -163,7 +190,7 @@ export default [
/*
* This event is fired if the user navigates directly to this tab, where the
- * $locationChangeStart does not. Watching this and location ensure proper display on
+ * $locationChangeStart does not. Watching this and location ensure proper display on
* direct load of this tab or if the user comes from a different tab.
*/
$scope.$on('populated', () => {
@@ -174,7 +201,7 @@ export default [
codeInputInitialized = true;
}
- populateAdhocCommand(false);
+ loadAdHocCommands();
});
}
];
diff --git a/awx/ui/client/src/credential-types/add/add.controller.js b/awx/ui/client/src/credential-types/add/add.controller.js
index 0c404e51db..bc7e284fd2 100644
--- a/awx/ui/client/src/credential-types/add/add.controller.js
+++ b/awx/ui/client/src/credential-types/add/add.controller.js
@@ -37,6 +37,9 @@ export default ['Rest', 'Wait',
callback: 'loadCredentialKindOptions'
});
+ $scope.inputs_help_text = _.get(options, 'actions.POST.inputs.help_text', "Specification for credential type inputs");
+ $scope.injectors_help_text = _.get(options, 'actions.POST.injectors.help_text', "Specification for credential type injector");
+
if (!options.actions.POST) {
$state.go("^");
Alert('Permission Error', 'You do not have permission to add a credential type.', 'alert-info');
diff --git a/awx/ui/client/src/credential-types/credential-types.form.js b/awx/ui/client/src/credential-types/credential-types.form.js
index a933545fe7..6f55465478 100644
--- a/awx/ui/client/src/credential-types/credential-types.form.js
+++ b/awx/ui/client/src/credential-types/credential-types.form.js
@@ -43,14 +43,7 @@ export default ['i18n', function(i18n) {
default: '---',
showParseTypeToggle: true,
parseTypeName: 'parseTypeInputs',
- awPopOver: "Enter inputs using either JSON or YAML syntax. Use the " +
- "radio button to toggle between the two.
" +
- "JSON:
\n" +
- "{
\"somevar\": \"somevalue\",
\"password\": \"magic\"
}
\n" +
- "YAML:
\n" +
- "---
somevar: somevalue
password: magic
\n" +
- 'View JSON examples at www.json.org
' +
- 'View YAML examples at docs.ansible.com
',
+ awPopOverWatch: "inputs_help_text",
dataTitle: i18n._('Input Configuration'),
dataPlacement: 'right',
dataContainer: "body",
@@ -64,14 +57,7 @@ export default ['i18n', function(i18n) {
default: '---',
showParseTypeToggle: true,
parseTypeName: 'parseTypeInjectors',
- awPopOver: "Enter injectors using either JSON or YAML syntax. Use the " +
- "radio button to toggle between the two.
" +
- "JSON:
\n" +
- "{
\"somevar\": \"somevalue\",
\"password\": \"magic\"
}
\n" +
- "YAML:
\n" +
- "---
somevar: somevalue
password: magic
\n" +
- 'View JSON examples at www.json.org
' +
- 'View YAML examples at docs.ansible.com
',
+ awPopOverWatch: "injectors_help_text",
dataTitle: i18n._('Injector Configuration'),
dataPlacement: 'right',
dataContainer: "body",
diff --git a/awx/ui/client/src/credential-types/credential-types.list.js b/awx/ui/client/src/credential-types/credential-types.list.js
index a32be91ece..8bd6981aa9 100644
--- a/awx/ui/client/src/credential-types/credential-types.list.js
+++ b/awx/ui/client/src/credential-types/credential-types.list.js
@@ -14,18 +14,21 @@ export default ['i18n', function(i18n){
iterator: 'credential_type',
index: false,
hover: false,
+ search: {
+ managed_by_tower: 'false'
+ },
fields: {
name: {
key: true,
label: i18n._('Name'),
columnClass: 'col-md-3 col-sm-9 col-xs-9',
- modalColumnClass: 'col-md-8',
- awToolTip: '{{credential_type.description}}',
+ modalColumnClass: 'col-md-11',
+ awToolTip: '{{credential_type.description | sanitize}}',
dataPlacement: 'top'
},
kind: {
- label: i18n._('Type'),
+ label: i18n._('Kind'),
ngBind: 'credential_type.kind_label',
excludeModal: true,
columnClass: 'col-md-2 hidden-sm hidden-xs'
diff --git a/awx/ui/client/src/credential-types/edit/edit.controller.js b/awx/ui/client/src/credential-types/edit/edit.controller.js
index 3d0e8550af..42cd0ebadd 100644
--- a/awx/ui/client/src/credential-types/edit/edit.controller.js
+++ b/awx/ui/client/src/credential-types/edit/edit.controller.js
@@ -35,6 +35,9 @@ export default ['Rest', 'Wait',
options: options,
callback: 'choicesReadyCredentialTypes'
});
+
+ $scope.inputs_help_text = _.get(options, 'actions.POST.inputs.help_text', "Specification for credential type inputs");
+ $scope.injectors_help_text = _.get(options, 'actions.POST.injectors.help_text', "Specification for credential type injector");
});
}
@@ -43,6 +46,7 @@ export default ['Rest', 'Wait',
}
$scope.removeChoicesReady = $scope.$on('choicesReadyCredentialTypes',
function() {
+
if (!resourceData.data.managed_by_tower) {
$scope.credential_kind_options = $scope.credential_kind_options
.filter(val => val.value === 'net' ||
@@ -51,10 +55,37 @@ export default ['Rest', 'Wait',
$scope.credential_type = credential_typeData;
+ $scope.parseTypeInputs = 'yaml';
+ $scope.parseTypeInjectors = 'yaml';
+
+ var callback = function() {
+ // Make sure the form controller knows there was a change
+ $scope[form.name + '_form'].$setDirty();
+ };
+
$scope.$watch('credential_type.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
+
+ let readOnly = !($scope.credential_type.summary_fields.user_capabilities.edit || $scope.canAdd);
+
+ ParseTypeChange({
+ scope: $scope,
+ field_id: 'credential_type_inputs',
+ variable: 'inputs',
+ onChange: callback,
+ parse_variable: 'parseTypeInputs',
+ readOnly: readOnly
+ });
+ ParseTypeChange({
+ scope: $scope,
+ field_id: 'credential_type_injectors',
+ variable: 'injectors',
+ onChange: callback,
+ parse_variable: 'parseTypeInjectors',
+ readOnly: readOnly
+ });
});
function getVars(str){
@@ -104,6 +135,11 @@ export default ['Rest', 'Wait',
$scope.inputs = ParseVariableString(getVars(data.inputs));
$scope.injectors = ParseVariableString(getVars(data.injectors));
+ CreateSelect2({
+ element: '#credential_type_kind',
+ multiple: false,
+ });
+
// if ($scope.inputs === "{}") {
// $scope.inputs = "---";
// }
@@ -115,32 +151,6 @@ export default ['Rest', 'Wait',
// $scope.inputs = JSON.parse($scope.inputs);
// $scope.injectors = JSON.parse($scope.injectors);
- var callback = function() {
- // Make sure the form controller knows there was a change
- $scope[form.name + '_form'].$setDirty();
- };
- $scope.parseTypeInputs = 'yaml';
- $scope.parseTypeInjectors = 'yaml';
-
- ParseTypeChange({
- scope: $scope,
- field_id: 'credential_type_inputs',
- variable: 'inputs',
- onChange: callback,
- parse_variable: 'parseTypeInputs'
- });
- ParseTypeChange({
- scope: $scope,
- field_id: 'credential_type_injectors',
- variable: 'injectors',
- onChange: callback,
- parse_variable: 'parseTypeInjectors'
- });
-
- CreateSelect2({
- element: '#credential_type_kind',
- multiple: false,
- });
}
);
diff --git a/awx/ui/client/src/credential-types/main.js b/awx/ui/client/src/credential-types/main.js
index 2ccb9607ed..d0d18af528 100644
--- a/awx/ui/client/src/credential-types/main.js
+++ b/awx/ui/client/src/credential-types/main.js
@@ -38,7 +38,7 @@ angular.module('credentialTypes', [
},
data: {
activityStream: true,
- activityStreamTarget: 'custom_inventory_script' // TODO: change to 'credential_type'...there's probably more work that needs to be done to hook up activity stream
+ activityStreamTarget: 'credential_type'
},
ncyBreadcrumb: {
parent: 'setup',
diff --git a/awx/ui/client/src/credentials/credentials.form.js b/awx/ui/client/src/credentials/credentials.form.js
index f8c0440725..b6b3e7f905 100644
--- a/awx/ui/client/src/credentials/credentials.form.js
+++ b/awx/ui/client/src/credentials/credentials.form.js
@@ -292,7 +292,7 @@ export default ['i18n', function(i18n) {
ngChange: 'becomeMethodChange()',
},
"become_username": {
- labelBind: 'becomeUsernameLabel',
+ label: i18n._('Privilege Escalation Username'),
type: 'text',
ngShow: "(kind.value == 'ssh' && (become_method && become_method.value)) ",
@@ -302,7 +302,7 @@ export default ['i18n', function(i18n) {
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
},
"become_password": {
- labelBind: 'becomePasswordLabel',
+ label: i18n._('Privilege Escalation Password'),
type: 'sensitive',
ngShow: "(kind.value == 'ssh' && (become_method && become_method.value)) ",
ngDisabled: "become_password_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)",
diff --git a/awx/ui/client/src/credentials/credentials.list.js b/awx/ui/client/src/credentials/credentials.list.js
index 2ca40d2840..36f87541a7 100644
--- a/awx/ui/client/src/credentials/credentials.list.js
+++ b/awx/ui/client/src/credentials/credentials.list.js
@@ -26,7 +26,7 @@ export default ['i18n', function(i18n) {
label: i18n._('Name'),
columnClass: 'col-md-3 col-sm-9 col-xs-9',
modalColumnClass: 'col-md-12',
- awToolTip: '{{credential.description}}',
+ awToolTip: '{{credential.description | sanitize}}',
dataPlacement: 'top'
},
kind: {
diff --git a/awx/ui/client/src/credentials/factories/become-method-change.factory.js b/awx/ui/client/src/credentials/factories/become-method-change.factory.js
index 0727553528..2d85acaf35 100644
--- a/awx/ui/client/src/credentials/factories/become-method-change.factory.js
+++ b/awx/ui/client/src/credentials/factories/become-method-change.factory.js
@@ -15,8 +15,6 @@ export default
break;
case 'ssh':
scope.usernameLabel = i18n._('Username'); //formally 'SSH Username'
- scope.becomeUsernameLabel = i18n._('Privilege Escalation Username');
- scope.becomePasswordLabel = i18n._('Privilege Escalation Password');
break;
case 'scm':
scope.sshKeyDataLabel = i18n._('SCM Private Key');
diff --git a/awx/ui/client/src/credentials/factories/kind-change.factory.js b/awx/ui/client/src/credentials/factories/kind-change.factory.js
index e35bedc526..7232d8a188 100644
--- a/awx/ui/client/src/credentials/factories/kind-change.factory.js
+++ b/awx/ui/client/src/credentials/factories/kind-change.factory.js
@@ -72,8 +72,6 @@ export default
break;
case 'ssh':
scope.usernameLabel = i18n._('Username'); //formally 'SSH Username'
- scope.becomeUsernameLabel = i18n._('Privilege Escalation Username');
- scope.becomePasswordLabel = i18n._('Privilege Escalation Password');
break;
case 'scm':
scope.sshKeyDataLabel = i18n._('SCM Private Key');
diff --git a/awx/ui/client/src/credentials/list/credentials-list.controller.js b/awx/ui/client/src/credentials/list/credentials-list.controller.js
index 244afe6cc5..924f58e367 100644
--- a/awx/ui/client/src/credentials/list/credentials-list.controller.js
+++ b/awx/ui/client/src/credentials/list/credentials-list.controller.js
@@ -21,16 +21,14 @@ export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ProcessErrors', '
$scope.canAdd = params.canAdd;
});
+ $scope.$watch(list.name, assignCredentialKinds);
+
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
$scope.selected = [];
-
- $scope[list.name].forEach(credential => {
- credential.kind = credentialType.getById(credential.credential_type).name;
- });
}
$scope.$on(`${list.iterator}_options`, function(event, data){
@@ -42,6 +40,16 @@ export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ProcessErrors', '
optionsRequestDataProcessing();
});
+ function assignCredentialKinds () {
+ if (!Array.isArray($scope[list.name])) {
+ return;
+ }
+
+ $scope[list.name].forEach(credential => {
+ credential.kind = credentialType.match('id', credential.credential_type).name;
+ });
+ }
+
// iterate over the list and add fields like type label, after the
// OPTIONS request returns, or the list is sorted/paginated/searched
function optionsRequestDataProcessing(){
diff --git a/awx/ui/client/src/credentials/ownerList.block.less b/awx/ui/client/src/credentials/ownerList.block.less
index 64f76db17b..47ce63124e 100644
--- a/awx/ui/client/src/credentials/ownerList.block.less
+++ b/awx/ui/client/src/credentials/ownerList.block.less
@@ -1,6 +1,3 @@
-/** @define OwnerList */
-@import "./client/src/shared/branding/colors.default.less";
-
.OwnerList {
display: flex;
flex-wrap: wrap;
diff --git a/awx/ui/client/src/footer/footer.block.less b/awx/ui/client/src/footer/footer.block.less
index a00ef1e35a..b626f5d4e8 100644
--- a/awx/ui/client/src/footer/footer.block.less
+++ b/awx/ui/client/src/footer/footer.block.less
@@ -1,5 +1,4 @@
/** @define DashboardCounts */
-@import "./client/src/shared/branding/colors.default.less";
.Footer {
height: 40px;
diff --git a/awx/ui/client/src/home/dashboard/counts/dashboard-counts.block.less b/awx/ui/client/src/home/dashboard/counts/dashboard-counts.block.less
index 8d85f93bf5..411fd2e313 100644
--- a/awx/ui/client/src/home/dashboard/counts/dashboard-counts.block.less
+++ b/awx/ui/client/src/home/dashboard/counts/dashboard-counts.block.less
@@ -1,5 +1,3 @@
-@import "../../../shared/branding/colors.default.less";
-
/** @define DashboardCounts */
.DashboardCounts {
@@ -21,7 +19,7 @@
padding-right: 15px;
border-radius: 5px;
background-color: @db-panel-bg;
- border: 1px solid @db-panel-border;
+ border: 1px solid @b7grey;
flex: 1 0 auto;
max-width: ~"calc(16.6% - 15px)";
flex-basis: ~"calc(16.6% - 15px)";
diff --git a/awx/ui/client/src/home/dashboard/dashboard.block.less b/awx/ui/client/src/home/dashboard/dashboard.block.less
index f1b6446fa6..ec509d1833 100644
--- a/awx/ui/client/src/home/dashboard/dashboard.block.less
+++ b/awx/ui/client/src/home/dashboard/dashboard.block.less
@@ -1,5 +1,4 @@
/** @define Dashboard */
-@import "../../shared/branding/colors.default.less";
.Dashboard {
display: flex;
@@ -20,7 +19,7 @@
}
.Dashboard-list {
- border: 1px solid @default-border;
+ border: 1px solid @b7grey;
border-radius: 5px;
margin-top: 20px;
width: 50%;
diff --git a/awx/ui/client/src/home/dashboard/graphs/dashboard-graphs.block.less b/awx/ui/client/src/home/dashboard/graphs/dashboard-graphs.block.less
index fb9c513852..c33ebcf4b1 100644
--- a/awx/ui/client/src/home/dashboard/graphs/dashboard-graphs.block.less
+++ b/awx/ui/client/src/home/dashboard/graphs/dashboard-graphs.block.less
@@ -1,10 +1,8 @@
/** @define DashboardGraphs */
-@import "../../../shared/branding/colors.default.less";
-
.DashboardGraphs {
margin-top: 20px;
- border: solid 1px @db-panel-border;
+ border: solid 1px @b7grey;
border-radius: 5px;
background-color: @db-panel-bg;
padding-top:20px;
diff --git a/awx/ui/client/src/home/dashboard/lists/dashboard-list.block.less b/awx/ui/client/src/home/dashboard/lists/dashboard-list.block.less
index 749878527f..55b14f5f3f 100644
--- a/awx/ui/client/src/home/dashboard/lists/dashboard-list.block.less
+++ b/awx/ui/client/src/home/dashboard/lists/dashboard-list.block.less
@@ -1,7 +1,5 @@
/** @define DashboardList */
-@import "../../../shared/branding/colors.default.less";
-
.DashboardList {
flex: 1;
}
diff --git a/awx/ui/client/src/i18n.js b/awx/ui/client/src/i18n.js
index 062c2455ea..685c7e77df 100644
--- a/awx/ui/client/src/i18n.js
+++ b/awx/ui/client/src/i18n.js
@@ -40,6 +40,10 @@ export default
return {
_: function (s) { return gettextCatalog.getString (s); },
N_: N_,
+ translate: (singular, context) => gettextCatalog.getString(singular, context),
+ translatePlural: (count, singular, plural, context) => {
+ return gettextCatalog.getPlural(count, singular, plural, context);
+ },
sprintf: sprintf,
hasTranslation: function () {
return gettextCatalog.strings[gettextCatalog.currentLanguage] !== undefined;
diff --git a/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.block.less b/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.block.less
index f8e9af45ee..06b595066e 100644
--- a/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.block.less
+++ b/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.block.less
@@ -1,5 +1,3 @@
-@import "../../shared/branding/colors.default.less";
-
capacity-bar {
width: 50%;
@@ -25,4 +23,4 @@ capacity-bar {
.CapacityBar-consumed {
flex: 0 0 auto;
}
-}
\ No newline at end of file
+}
diff --git a/awx/ui/client/src/instance-groups/instance-group.block.less b/awx/ui/client/src/instance-groups/instance-group.block.less
index 8588e0f2de..1feaff9b38 100644
--- a/awx/ui/client/src/instance-groups/instance-group.block.less
+++ b/awx/ui/client/src/instance-groups/instance-group.block.less
@@ -1,5 +1,3 @@
-@import "../shared/branding/colors.default.less";
-
.InstanceGroups {
.BreadCrumb-menuLinkImage:hover {
@@ -53,4 +51,4 @@
.List-tableRow .List-titleBadge {
margin: 0 0 0 5px;
}
-}
\ No newline at end of file
+}
diff --git a/awx/ui/client/src/instance-groups/instance-groups.route.js b/awx/ui/client/src/instance-groups/instance-groups.route.js
index ce3ab3f7f9..537a1e40b2 100644
--- a/awx/ui/client/src/instance-groups/instance-groups.route.js
+++ b/awx/ui/client/src/instance-groups/instance-groups.route.js
@@ -14,7 +14,8 @@ export default {
value: {
page_size: '20',
order_by: 'name'
- }
+ },
+ dynamic: true
}
},
data: {
diff --git a/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs-list.route.js b/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs-list.route.js
index 9029b7dd00..1d82ca854e 100644
--- a/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs-list.route.js
+++ b/awx/ui/client/src/instance-groups/instances/instance-jobs/instance-jobs-list.route.js
@@ -14,7 +14,8 @@ export default {
page_size: '20',
order_by: '-finished',
not__launch_type: 'sync'
- }
+ },
+ dynamic: true
}
},
views: {
diff --git a/awx/ui/client/src/instance-groups/instances/instances-list.route.js b/awx/ui/client/src/instance-groups/instances/instances-list.route.js
index 5215e30d46..16549d9d1e 100644
--- a/awx/ui/client/src/instance-groups/instances/instances-list.route.js
+++ b/awx/ui/client/src/instance-groups/instances/instances-list.route.js
@@ -14,7 +14,8 @@ export default {
value: {
page_size: '20',
order_by: 'hostname'
- }
+ },
+ dynamic: true
}
},
views: {
diff --git a/awx/ui/client/src/instance-groups/jobs/jobs-list.route.js b/awx/ui/client/src/instance-groups/jobs/jobs-list.route.js
index 58db2bf4ce..03854eca20 100644
--- a/awx/ui/client/src/instance-groups/jobs/jobs-list.route.js
+++ b/awx/ui/client/src/instance-groups/jobs/jobs-list.route.js
@@ -14,7 +14,8 @@ export default {
page_size: '20',
order_by: '-finished',
not__launch_type: 'sync'
- }
+ },
+ dynamic: true
},
instance_group_id: null
},
diff --git a/awx/ui/client/src/inventories-hosts/hosts/host.form.js b/awx/ui/client/src/inventories-hosts/hosts/host.form.js
index 3fbf7aeb86..52c6f0d8c1 100644
--- a/awx/ui/client/src/inventories-hosts/hosts/host.form.js
+++ b/awx/ui/client/src/inventories-hosts/hosts/host.form.js
@@ -22,6 +22,7 @@ function(i18n) {
formLabelSize: 'col-lg-3',
formFieldSize: 'col-lg-9',
iterator: 'host',
+ detailsClick: "$state.go('hosts.edit', null, {reload:true})",
activeEditState: 'hosts.edit',
stateTree: 'hosts',
headerFields:{
@@ -79,11 +80,6 @@ function(i18n) {
dataTitle: i18n._('Host Variables'),
dataPlacement: 'right',
dataContainer: 'body'
- },
- inventory: {
- type: 'hidden',
- includeOnEdit: true,
- includeOnAdd: true
}
},
diff --git a/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups-associate.route.js b/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups-associate.route.js
index c1567200d2..9ed6dbfa0b 100644
--- a/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups-associate.route.js
+++ b/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups-associate.route.js
@@ -26,7 +26,6 @@ export default {
onExit: function($state) {
if ($state.transition) {
$('#associate-groups-modal').modal('hide');
- $('.modal-backdrop').remove();
$('body').removeClass('modal-open');
}
},
diff --git a/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.controller.js b/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.controller.js
index f2fe567cd3..6e346c5b4f 100644
--- a/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.controller.js
+++ b/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.controller.js
@@ -56,6 +56,10 @@
$state.go('inventories.edit.groups.edit', {inventory_id: $scope.inventory_id, group_id: id});
};
+ $scope.goToGroupGroups = function(id){console.log();
+ $state.go('inventories.edit.groups.edit.nested_groups', {inventory_id: $scope.inventory_id, group_id: id});
+ };
+
$scope.associateGroup = function() {
$state.go('.associate', {inventory_id: $scope.inventory_id});
};
diff --git a/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js b/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js
index 468c38273f..47840e3179 100644
--- a/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js
+++ b/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js
@@ -30,7 +30,7 @@ export default ['i18n', function(i18n) {
name: {
label: i18n._('Groups'),
key: true,
- ngClick: "editGroup(group.id)",
+ ngClick: "goToGroupGroups(group.id)",
columnClass: 'col-lg-6 col-md-6 col-sm-6 col-xs-6',
class: 'InventoryManage-breakWord',
}
diff --git a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.block.less b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.block.less
index 8ccddf8c89..6f3684ef59 100644
--- a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.block.less
+++ b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.block.less
@@ -1,5 +1,3 @@
-@import "../../../shared/branding/colors.default.less";
-
.InsightsLastCheck{
display: flex;
justify-content: flex-end;
diff --git a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.controller.js b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.controller.js
index 88c70ee00a..5022fc519d 100644
--- a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.controller.js
+++ b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.controller.js
@@ -5,8 +5,9 @@
*************************************************/
export default [ 'InsightsData', '$scope', 'moment', '$state', 'InventoryData',
- 'InsightsService',
-function (data, $scope, moment, $state, InventoryData, InsightsService) {
+ 'InsightsService', 'CanRemediate',
+function (data, $scope, moment, $state, InventoryData, InsightsService,
+ CanRemediate) {
function init() {
$scope.reports = (data && data.reports) ? data.reports : [];
@@ -24,7 +25,7 @@ function (data, $scope, moment, $state, InventoryData, InsightsService) {
$scope.insights_credential = (InventoryData && InventoryData.summary_fields &&
InventoryData.summary_fields.insights_credential && InventoryData.summary_fields.insights_credential.id) ?
InventoryData.summary_fields.insights_credential.id : null;
-
+ $scope.canRemediate = CanRemediate;
}
function filter(str){
@@ -42,8 +43,8 @@ function (data, $scope, moment, $state, InventoryData, InsightsService) {
window.open(`https://access.redhat.com/insights/inventory?machine=${$scope.$parent.host.insights_system_id}`, '_blank');
};
- $scope.remediateInventory = function(inv_id, inv_name, insights_credential){
- $state.go('templates.addJobTemplate', {inventory_id: inv_id, inventory_name:inv_name, credential_id: insights_credential});
+ $scope.remediateInventory = function(inv_id, insights_credential){
+ $state.go('templates.addJobTemplate', {inventory_id: inv_id, credential_id: insights_credential});
};
$scope.formCancel = function(){
diff --git a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.partial.html b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.partial.html
index f89fb237f8..986decd7b2 100644
--- a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.partial.html
+++ b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.partial.html
@@ -61,7 +61,7 @@
- No data is available. Either there are no issues to report or no scan jobs have been run on this host.
+ No data is available. There are no issues to report.
The Insights Credential for {{inventory.name}} was not found.
@@ -88,6 +88,6 @@
-
+
diff --git a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.route.js b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.route.js
index 179af657af..cd116fa062 100644
--- a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.route.js
+++ b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.route.js
@@ -49,6 +49,50 @@ export default {
return resourceData.data;
}
}
- ]
+ ],
+ checkProjectPermission: ['InventoryData', '$stateParams', 'Rest', 'GetBasePath',
+ function(InventoryData, $stateParams, Rest, GetBasePath){
+ if(_.has(InventoryData, 'summary_fields.insights_credential')){
+ let credential_id = InventoryData.summary_fields.insights_credential.id,
+ path = `${GetBasePath('projects')}?credential__id=${credential_id}&role_level=use_role`;
+ Rest.setUrl(path);
+ return Rest.get().then(({data}) => {
+ if (data.results.length > 0){
+ return true;
+ }
+ else {
+ return false;
+ }
+ }).catch(() => {
+ return false;
+ });
+ }
+ else {
+ return false;
+ }
+ }],
+ checkInventoryPermission: ['InventoryData', '$stateParams', 'Rest', 'GetBasePath',
+ function(InventoryData, $stateParams, Rest, GetBasePath){
+ if(_.has(InventoryData, 'summary_fields.insights_credential')){
+ let path = `${GetBasePath('inventory')}${InventoryData.id}/?role_level=use_role`;
+ Rest.setUrl(path);
+ return Rest.get().then(() => {
+ return true;
+ }).catch(() => {
+ return false;
+ });
+ }
+ else {
+ return false;
+ }
+ }],
+ CanRemediate: ['checkProjectPermission', 'checkInventoryPermission',
+ function(checkProjectPermission, checkInventoryPermission){
+ // the user can remediate an insights
+ // inv if the user has "use" permission on
+ // an insights project and the inventory
+ // being edited:
+ return checkProjectPermission === true && checkInventoryPermission === true;
+ }]
}
};
diff --git a/awx/ui/client/src/inventories-hosts/inventories/inventories.block.less b/awx/ui/client/src/inventories-hosts/inventories/inventories.block.less
index 91727c2e7d..0121cae5a9 100644
--- a/awx/ui/client/src/inventories-hosts/inventories/inventories.block.less
+++ b/awx/ui/client/src/inventories-hosts/inventories/inventories.block.less
@@ -1,3 +1,12 @@
.Inventories-hostStatus {
margin-left: 5px;
}
+#inventories-panel {
+ .completed_jobsList.List-well {
+ margin: 0;
+
+ .List-noItems {
+ margin: 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/awx/ui/client/src/inventories-hosts/inventories/inventory.list.js b/awx/ui/client/src/inventories-hosts/inventories/inventory.list.js
index f1175316f4..233dcaa7f9 100644
--- a/awx/ui/client/src/inventories-hosts/inventories/inventory.list.js
+++ b/awx/ui/client/src/inventories-hosts/inventories/inventory.list.js
@@ -47,7 +47,7 @@ export default ['i18n', function(i18n) {
label: i18n._('Name'),
columnClass: 'col-md-4 col-sm-3 col-xs-6 List-staticColumnAdjacent',
modalColumnClass: 'col-md-12',
- awToolTip: "{{ inventory.description }}",
+ awToolTip: "{{ inventory.description | sanitize }}",
awTipPlacement: "top",
ngClick: 'editInventory(inventory)'
},
diff --git a/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js b/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js
index ac0334cc64..e180289984 100644
--- a/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js
+++ b/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js
@@ -44,7 +44,7 @@ export default ['templateUrl', 'Wait', '$filter', '$compile', 'i18n',
}
scope.generateTable = function(data, event){
- var html, title = "Recent Jobs";
+ var html, title = (scope.inventory.has_active_failures) ? "Recent Failed Jobs" : "Recent Successful Jobs";
Wait('stop');
if (data.count > 0) {
html = "