diff --git a/.dockerignore b/.dockerignore
index f5faf1f0e3da..46c83b0467bd 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,2 +1 @@
-.git
awx/ui/node_modules
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index caceb2de456e..2a7fb229d879 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -30,8 +30,9 @@ https://www.ansible.com/security
##### STEPS TO REPRODUCE
-
+
##### EXPECTED RESULTS
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 7a7c6954c843..384b1dc78b84 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -3,6 +3,12 @@ name: "\U0001F41B Bug report"
about: Create a report to help us improve
---
+
##### ISSUE TYPE
- Bug Report
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 097706e6d8ce..98fe2f5869da 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -3,6 +3,12 @@ name: "✨ Feature request"
about: Suggest an idea for this project
---
+
##### ISSUE TYPE
- Feature Idea
diff --git a/.gitignore b/.gitignore
index d8f97b52bd14..71d2015cf257 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,11 +31,14 @@ awx/ui/client/languages
awx/ui/templates/ui/index.html
awx/ui/templates/ui/installing.html
awx/ui_next/node_modules/
+awx/ui_next/src/locales/
awx/ui_next/coverage/
-awx/ui_next/build/locales/_build
-/tower-license
-/tower-license/**
+awx/ui_next/build
+awx/ui_next/.env.local
+awx/ui_next/instrumented
+rsyslog.pid
tools/prometheus/data
+tools/docker-compose/Dockerfile
# Tower setup playbook testing
setup/test/roles/postgresql
@@ -139,9 +142,12 @@ use_dev_supervisor.txt
# Ansible module tests
/awx_collection_test_venv/
/awx_collection/*.tar.gz
-/awx_collection/galaxy.yml
/sanity/
+/awx_collection_build/
.idea/*
*.unison.tmp
*.#
+/tools/docker-compose/overrides/
+/awx/ui_next/.ui-built
+/Dockerfile
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9dfe6a912a60..6237a9f54aca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,259 @@
This is a list of high-level changes for each release of AWX. A full list of commits can be found at `https://github.com/ansible/awx/releases/tag/ Pass extra command line variables to the playbook. This is the -e or --extra-vars command line parameter for ansible-playbook. Provide key/value pairs using either YAML or JSON. working... is currently upgrading. This page will refresh when complete.
- Enter inventory variables using either JSON or YAML
- syntax. Use the radio button to toggle between the two.
-
- View JSON examples at
- www.json.org
-
- View YAML examples at
-
- docs.ansible.com
-
- {{ state._message }}
- {{ state._value }}% {{ popover.text | translate }}
-This a internal fork of the official AWX Project on GitHub. This AWX has MLU specific design changes and has a more user readable email notification.
-The Web UI is changed too - for better integration in the daily admin workflow.
+This a internal fork of the official AWX Project on GitHub. This AWX has a more user readable email notification.
-The official fork can be found on https://github.com/MBcom/awx .
-some impressions
---------
-![screenshot1](https://user-images.githubusercontent.com/27956078/41818184-a59e49d0-77a9-11e8-94e8-ca0bb9a9d3af.png)
-- landing page
-![screenshot3](https://user-images.githubusercontent.com/27956078/41818197-f1d9d26a-77a9-11e8-9662-d773b674a057.png)
-- opened menu bar
-![screenshot2](https://user-images.githubusercontent.com/27956078/41818202-084b53a2-77aa-11e8-9d46-956951f0c9f7.png)
-- login screen
+The official fork can be found on https://github.com/MBcom/awx .
The following information are related to the official AWX project:
-------
@@ -32,20 +23,20 @@ Contributing
------------
- Refer to the [Contributing guide](./CONTRIBUTING.md) to get started developing, testing, and building AWX.
-- All code submissions are done through pull requests against the `devel` branch.
-- 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](./DCO_1_1.md)
-- Take care to make sure no merge commits are in the submission, and use `git rebase` vs `git merge` for this reason.
-- If submitting a large code change, it's a good idea to join the `#ansible-awx` channel on irc.freenode.net, and talk about what you would like to do or add first. This not only helps everyone know what's going on, it also helps save time and effort, if the community decides some changes are needed.
+- All code submissions are made through pull requests against the `devel` branch.
+- 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](./DCO_1_1.md)
+- Take care to make sure no merge commits are in the submission, and use `git rebase` vs. `git merge` for this reason.
+- If submitting a large code change, it's a good idea to join the `#ansible-awx` channel on irc.freenode.net and talk about what you would like to do or add first. This not only helps everyone know what's going on, but it also helps save time and effort if the community decides some changes are needed.
Reporting Issues
----------------
-If you're experiencing a problem that you feel is a bug in AWX, or have ideas for how to improve AWX, we encourage you to open an issue, and share your feedback. But before opening a new issue, we ask that you please take a look at our [Issues guide](./ISSUES.md).
+If you're experiencing a problem that you feel is a bug in AWX or have ideas for improving AWX, we encourage you to open an issue and share your feedback. But before opening a new issue, we ask that you please take a look at our [Issues guide](./ISSUES.md).
Code of Conduct
---------------
-We ask all of our community members and contributors to adhere to the [Ansible code of conduct](http://docs.ansible.com/ansible/latest/community/code_of_conduct.html). If you have questions, or need assistance, please reach out to our community team at [codeofconduct@ansible.com](mailto:codeofconduct@ansible.com)
+We ask all of our community members and contributors to adhere to the [Ansible code of conduct](http://docs.ansible.com/ansible/latest/community/code_of_conduct.html). If you have questions or need assistance, please reach out to our community team at [codeofconduct@ansible.com](mailto:codeofconduct@ansible.com)
Get Involved
------------
@@ -59,4 +50,3 @@ License
-------
[Apache v2](./LICENSE.md)
-
diff --git a/VERSION b/VERSION
index 44931da2660c..3e17df0287b8 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-9.1.1
+17.0.1
diff --git a/awx/__init__.py b/awx/__init__.py
index 0e2547752d60..5afb83d11513 100644
--- a/awx/__init__.py
+++ b/awx/__init__.py
@@ -30,6 +30,7 @@
HAS_DJANGO = False
else:
from django.db.backends.base import schema
+ from django.db.models import indexes
from django.db.backends.utils import names_digest
@@ -50,6 +51,7 @@ def names_digest(*args, length):
return h.hexdigest()[:length]
schema.names_digest = names_digest
+ indexes.names_digest = names_digest
def find_commands(management_dir):
diff --git a/awx/api/conf.py b/awx/api/conf.py
index 493eed6981b0..f7da95200475 100644
--- a/awx/api/conf.py
+++ b/awx/api/conf.py
@@ -16,6 +16,7 @@
help_text=_('Number of seconds that a user is inactive before they will need to login again.'),
category=_('Authentication'),
category_slug='authentication',
+ unit=_('seconds'),
)
register(
'SESSIONS_PER_USER',
@@ -49,6 +50,7 @@
'in the number of seconds.'),
category=_('Authentication'),
category_slug='authentication',
+ unit=_('seconds'),
)
register(
'ALLOW_OAUTH2_FOR_EXTERNAL_USERS',
diff --git a/awx/api/filters.py b/awx/api/filters.py
index ea9d0115629e..6d51441c2845 100644
--- a/awx/api/filters.py
+++ b/awx/api/filters.py
@@ -9,7 +9,7 @@
# Django
from django.core.exceptions import FieldError, ValidationError
from django.db import models
-from django.db.models import Q
+from django.db.models import Q, CharField, IntegerField, BooleanField
from django.db.models.fields import FieldDoesNotExist
from django.db.models.fields.related import ForeignObjectRel, ManyToManyField, ForeignKey
from django.contrib.contenttypes.models import ContentType
@@ -63,19 +63,19 @@ def filter_queryset(self, request, queryset, view):
raise ParseError(*e.args)
-def get_field_from_path(model, path):
+def get_fields_from_path(model, path):
'''
Given a Django ORM lookup path (possibly over multiple models)
- Returns the last field in the line, and also the revised lookup path
+ Returns the fields in the line, and also the revised lookup path
ex., given
model=Organization
path='project__timeout'
- returns tuple of field at the end of the line as well as a corrected
- path, for special cases we do substitutions
- (
fontcustom contains 6 glyphs:
- Toggle Preview Characters
- {
YAML:
"somevar": "somevalue",
"password": "magic"
}---
'),
- PLEASE_ENTER_ANSWER: t.s('Please enter an answer.'),
- PLEASE_SELECT_VALUE: t.s('Please select a value'),
- VALID_INTEGER: t.s('Please enter an answer that is a valid integer.'),
- VALID_DECIMAL: t.s('Please enter an answer that is a decimal number.'),
- PLAYBOOK_RUN: t.s('Playbook Run'),
- SCM_BRANCH: t.s('SCM Branch'),
- SCM_BRANCH_HELP: t.s('Branch to use in job run. Project default used if blank.'),
- CHECK: t.s('Check'),
- NO_CREDS_MATCHING_TYPE: t.s('No Credentials Matching This Type Have Been Created'),
- CREDENTIAL_TYPE_MISSING: typeLabel => t.s('This job template has a default {{typeLabel}} credential which must be included or replaced before proceeding.', { typeLabel })
- };
-
- ns.alert = {
- MISSING_PARAMETER: t.s('Template parameter is missing.'),
- NO_PERMISSION: t.s('You do not have permission to perform this action.'),
- UNKNOWN_COPY: t.s('Unable to determine this template\'s type while copying.'),
- UNKNOWN_DELETE: t.s('Unable to determine this template\'s type while deleting.'),
- UNKNOWN_EDIT: t.s('Unable to determine this template\'s type while editing.'),
- UNKNOWN_LAUNCH: t.s('Unable to determine this template\'s type while launching.'),
- UNKNOWN_SCHEDULE: t.s('Unable to determine this template\'s type while scheduling.'),
- };
-
- ns.error = {
- HEADER: this.error.HEADER,
- CALL: this.error.CALL,
- EDIT: t.s('Unable to edit template.'),
- DELETE: t.s('Unable to delete template.'),
- LAUNCH: t.s('Unable to launch template.'),
- UNKNOWN: t.s('Unable to determine template type.'),
- SCHEDULE: t.s('Unable to schedule job.'),
- COPY: t.s('Unable to copy template.'),
- INVALID: t.s('Resources are missing from this template.')
- };
-
- ns.warnings = {
- WORKFLOW_RESTRICTED_COPY: t.s('You do not have access to all resources used by this workflow. Resources that you don\'t have access to will not be copied and will result in an incomplete workflow.')
- };
-
- ns.workflows = {
- INVALID_JOB_TEMPLATE: t.s('This Job Template is missing a default inventory or project. This must be addressed in the Job Template form before this node can be saved.'),
- CREDENTIAL_WITH_PASS: t.s('This Job Template has a credential that requires a password. Credentials requiring passwords on launch are not permitted on workflow nodes.')
- };
-
- ns.workflow_maker = {
- DELETE_NODE_PROMPT_TEXT: t.s('Are you sure you want to delete this workflow node?'),
- KEY: t.s('KEY'),
- ON_SUCCESS: t.s('On Success'),
- ON_FAILURE: t.s('On Failure'),
- ALWAYS: t.s('Always'),
- PAUSE: t.s('Wait For Approval'),
- JOB_TEMPLATE: t.s('Job Template'),
- PROJECT_SYNC: t.s('Project Sync'),
- INVENTORY_SYNC: t.s('Inventory Sync'),
- WORKFLOW: t.s('Workflow'),
- TEMPLATE: t.s('Template'),
- WARNING: t.s('Warning'),
- TOTAL_NODES: t.s('TOTAL NODES'),
- ADD_A_NODE: t.s('ADD A NODE'),
- EDIT_TEMPLATE: t.s('EDIT TEMPLATE'),
- JOBS: t.s('Jobs'),
- PLEASE_CLICK_THE_START_BUTTON: t.s('Please click the start button to build your workflow.'),
- PLEASE_HOVER_OVER_A_TEMPLATE: t.s('Please hover over a template for additional options.'),
- EDIT_LINK_TOOLTIP: t.s('Click to edit link'),
- VIEW_LINK_TOOLTIP: t.s('Click to view link'),
- RUN: t.s('RUN'),
- CHECK: t.s('CHECK'),
- SELECT: t.s('SELECT'),
- DELETED: t.s('DELETED'),
- START: t.s('START'),
- DETAILS: t.s('DETAILS'),
- TITLE: t.s('WORKFLOW VISUALIZER'),
- INVENTORY_WILL_OVERRIDE: t.s('The inventory of this node will be overridden by the parent workflow inventory.'),
- INVENTORY_WILL_NOT_OVERRIDE: t.s('The inventory of this node will not be overridden by the parent workflow inventory.'),
- INVENTORY_PROMPT_WILL_OVERRIDE: t.s('The inventory of this node will be overridden if a parent workflow inventory is provided at launch.'),
- INVENTORY_PROMPT_WILL_NOT_OVERRIDE: t.s('The inventory of this node will not be overridden if a parent workflow inventory is provided at launch.'),
- ADD_LINK: t.s('ADD LINK'),
- EDIT_LINK: t.s('EDIT LINK'),
- VIEW_LINK: t.s('VIEW LINK'),
- NEW_LINK: t.s('Please click on an available node to form a new link.'),
- UNLINK: t.s('UNLINK'),
- READ_ONLY_PROMPT_VALUES: t.s('The following promptable values were provided when this node was created:'),
- READ_ONLY_NO_PROMPT_VALUES: t.s('No promptable values were provided when this node was created.'),
- UNSAVED_CHANGES_HEADER: t.s('WARNING: UNSAVED CHANGES'),
- UNSAVED_CHANGES_PROMPT_TEXT: t.s('Are you sure you want to exit the Workflow Creator without saving your changes?'),
- EXIT: t.s('EXIT'),
- CANCEL: t.s('CANCEL'),
- SAVE_AND_EXIT: t.s('SAVE & EXIT'),
- APPROVAL: t.s('Approval'),
- TIMEOUT_POPOVER: t.s('The amount of time to wait before this approval step is automatically denied. Defaults to 0 for no timeout.'),
- TIMED_OUT: t.s('APPROVAL TIMED OUT'),
- TIMEOUT: t.s('Timeout'),
- APPROVED: t.s('APPROVED'),
- DENIED: t.s('DENIED')
- };
-}
-
-TemplatesStrings.$inject = ['BaseStringService'];
-
-export default TemplatesStrings;
diff --git a/awx/ui/client/features/templates/templatesList.controller.js b/awx/ui/client/features/templates/templatesList.controller.js
deleted file mode 100644
index 8c95aa745a46..000000000000
--- a/awx/ui/client/features/templates/templatesList.controller.js
+++ /dev/null
@@ -1,490 +0,0 @@
-/** ***********************************************
- * Copyright (c) 2018 Ansible, Inc.
- *
- * All Rights Reserved
- ************************************************ */
-const JOB_TEMPLATE_ALIASES = ['job_template', 'Job Template'];
-const WORKFLOW_TEMPLATE_ALIASES = ['workflow_job_template', 'Workflow Job Template'];
-
-const isJobTemplate = ({ type }) => JOB_TEMPLATE_ALIASES.indexOf(type) > -1;
-const isWorkflowTemplate = ({ type }) => WORKFLOW_TEMPLATE_ALIASES.indexOf(type) > -1;
-const mapChoices = choices => Object.assign(...choices.map(([k, v]) => ({[k]: v})));
-
-function ListTemplatesController(
- $filter,
- $scope,
- $state,
- Alert,
- Dataset,
- ProcessErrors,
- Prompt,
- resolvedModels,
- strings,
- Wait,
- qs,
- GetBasePath,
- ngToast,
- $timeout
-) {
- const vm = this || {};
- const [jobTemplate, workflowTemplate] = resolvedModels;
-
- const choices = workflowTemplate.options('actions.GET.type.choices')
- .concat(jobTemplate.options('actions.GET.type.choices'));
-
- let launchModalOpen = false;
- let refreshAfterLaunchClose = false;
- let pendingRefresh = false;
- let refreshTimerRunning = false;
- let paginateQuerySet = {};
-
- vm.strings = strings;
- vm.templateTypes = mapChoices(choices);
- vm.activeId = parseInt($state.params.job_template_id || $state.params.workflow_job_template_id);
- vm.invalidTooltip = {
- popover: {
- text: strings.get('error.INVALID'),
- on: 'mouseenter',
- icon: 'fa-exclamation',
- position: 'right',
- arrowHeight: 15
- }
- };
-
- $scope.canAddJobTemplate = jobTemplate.options('actions.POST');
- $scope.canAddWorkflowJobTemplate = workflowTemplate.options('actions.POST');
- $scope.canAdd = ($scope.canAddJobTemplate || $scope.canAddWorkflowJobTemplate);
-
- // smart-search
- vm.list = {
- iterator: 'template',
- name: 'templates'
- };
- vm.dataset = Dataset.data;
- vm.templates = Dataset.data.results;
- vm.defaultParams = $state.params.template_search;
-
- const toolbarSortDefault = {
- label: `${strings.get('sort.NAME_ASCENDING')}`,
- value: 'name'
- };
-
- vm.toolbarSortOptions = [
- toolbarSortDefault,
- { label: `${strings.get('sort.NAME_DESCENDING')}`, value: '-name' },
- { label: `${strings.get('sort.MODIFIED_ASCENDING')}`, value: 'modified' },
- { label: `${strings.get('sort.MODIFIED_DESCENDING')}`, value: '-modified' },
- { label: `${strings.get('sort.LAST_JOB_RUN_ASCENDING')}`, value: 'last_job_run' },
- { label: `${strings.get('sort.LAST_JOB_RUN_DESCENDING')}`, value: '-last_job_run' },
- { label: `${strings.get('sort.INVENTORY_ASCENDING')}`, value: 'job_template__inventory__id' },
- { label: `${strings.get('sort.INVENTORY_DESCENDING')}`, value: '-job_template__inventory__id' },
- { label: `${strings.get('sort.PROJECT_ASCENDING')}`, value: 'jobtemplate__project__id' },
- { label: `${strings.get('sort.PROJECT_DESCENDING')}`, value: '-jobtemplate__project__id' },
- ];
-
- vm.toolbarSortValue = toolbarSortDefault;
-
- $scope.$on('updateDataset', (event, dataset, queryset) => {
- paginateQuerySet = queryset;
- });
-
- vm.onToolbarSort = (sort) => {
- vm.toolbarSortValue = sort;
-
- const queryParams = Object.assign(
- {},
- $state.params.template_search,
- paginateQuerySet,
- { order_by: sort.value }
- );
-
- // Update params
- $state.go('.', {
- template_search: queryParams
- }, { notify: false, location: 'replace' });
- };
-
- $scope.$watch('vm.dataset.count', () => {
- $scope.$emit('updateCount', vm.dataset.count, 'templates');
- });
-
- $scope.$watch('$state.params', function(newValue, oldValue) {
- const job_template_id = _.get($state.params, 'job_template_id');
- const workflow_job_template_id = _.get($state.params, 'workflow_job_template_id');
-
- if((job_template_id || workflow_job_template_id)) {
- vm.activeId = parseInt($state.params.job_template_id || $state.params.workflow_job_template_id);
- } else {
- vm.activeId = "";
- }
- setToolbarSort();
- }, true);
-
- $scope.$on(`ws-jobs`, () => {
- if (!launchModalOpen) {
- if (!refreshTimerRunning) {
- refreshTemplates();
- } else {
- pendingRefresh = true;
- }
- } else {
- refreshAfterLaunchClose = true;
- }
- });
-
- $scope.$on('launchModalOpen', (evt, isOpen) => {
- evt.stopPropagation();
- if (!isOpen && refreshAfterLaunchClose) {
- refreshAfterLaunchClose = false;
- refreshTemplates();
- }
- launchModalOpen = isOpen;
- });
-
- vm.isInvalid = (template) => {
- if(isJobTemplate(template)) {
- return template.project === null || (template.inventory === null && template.ask_inventory_on_launch === false);
- } else {
- return false;
- }
- };
-
- vm.isPortalMode = $state.includes('portalMode');
-
- vm.openWorkflowVisualizer = template => {
- const name = 'templates.editWorkflowJobTemplate.workflowMaker';
- const params = { workflow_job_template_id: template.id };
- const options = { reload: true };
-
- $state.go(name, params, options);
- };
-
- vm.deleteTemplate = template => {
- if (!template) {
- Alert(strings.get('error.DELETE'), strings.get('alert.MISSING_PARAMETER'));
- return;
- }
-
- if (isWorkflowTemplate(template)) {
- displayWorkflowTemplateDeletePrompt(template);
- } else if (isJobTemplate(template)) {
- jobTemplate.getDependentResourceCounts(template.id)
- .then(counts => displayJobTemplateDeletePrompt(template, counts));
- } else {
- Alert(strings.get('error.UNKNOWN'), strings.get('alert.UNKNOWN_DELETE'));
- }
- };
-
- vm.copyTemplate = template => {
- if (!template) {
- Alert(strings.get('error.COPY'), strings.get('alert.MISSING_PARAMETER'));
- return;
- }
-
- if (isJobTemplate(template)) {
- copyJobTemplate(template);
- } else if (isWorkflowTemplate(template)) {
- copyWorkflowTemplate(template);
- } else {
- Alert(strings.get('error.UNKNOWN'), strings.get('alert.UNKNOWN_COPY'));
- }
- };
-
- vm.getModified = template => {
- const modified = _.get(template, 'modified');
-
- if (!modified) {
- return undefined;
- }
-
- let html = $filter('longDate')(modified);
-
- const { username, id } = _.get(template, 'summary_fields.modified_by', {});
-
- if (username && id) {
- html += ` by ${$filter('sanitize')(username)}`;
- }
-
- return html;
- };
-
- vm.buildCredentialTags = (credentials) => {
- return credentials.map(credential => {
- const icon = `${credential.kind}`;
- const link = `/#/credentials/${credential.id}`;
- const tooltip = strings.get('tooltips.VIEW_THE_CREDENTIAL');
- const value = $filter('sanitize')(credential.name);
-
- return { icon, link, tooltip, value };
- });
- };
-
- vm.getLastRan = template => {
- const lastJobRun = _.get(template, 'last_job_run');
-
- if (!lastJobRun) {
- return undefined;
- }
-
- let html = $filter('longDate')(lastJobRun);
-
- // TODO: uncomment and update when last job run user is returned by api
- // const { username, id } = _.get(template, 'summary_fields.last_job_run_by', {});
-
- // if (username && id) {
- // html += ` by ${$filter('sanitize')(username)}`;
- //}
-
- return html;
- };
-
- vm.getType = template => {
- if(isJobTemplate(template)) {
- return strings.get('list.ADD_DD_JT_LABEL');
- } else {
- return strings.get('list.ADD_DD_WF_LABEL');;
- }
- };
-
- function setToolbarSort () {
- const orderByValue = _.get($state.params, 'template_search.order_by');
- const sortValue = _.find(vm.toolbarSortOptions, (option) => option.value === orderByValue);
- if (sortValue) {
- vm.toolbarSortValue = sortValue;
- } else {
- vm.toolbarSortValue = toolbarSortDefault;
- }
- }
-
- function refreshTemplates() {
- Wait('start');
- let path = GetBasePath('unified_job_templates');
- qs.search(path, $state.params.template_search, { 'X-WS-Session-Quiet': true })
- .then(function(searchResponse) {
- vm.dataset = searchResponse.data;
- vm.templates = vm.dataset.results;
- })
- .finally(() => Wait('stop'));
- pendingRefresh = false;
- refreshTimerRunning = true;
- $timeout(() => {
- if (pendingRefresh) {
- refreshTemplates();
- } else {
- refreshTimerRunning = false;
- }
- }, 5000);
- }
-
- function createErrorHandler(path, action) {
- return ({ data, status }) => {
- const hdr = strings.get('error.HEADER');
- const msg = strings.get('error.CALL', { path, action, status });
- ProcessErrors($scope, data, status, null, { hdr, msg });
- };
- }
-
- function copyJobTemplate(template) {
- Wait('start');
- jobTemplate
- .create('get', template.id)
- .then(model => model.copy())
- .then((copiedJT) => {
- ngToast.success({
- content: `
-
somevar: somevalue
password: magic` : ''}
- ${templates.map(item => `
` : ''}
- ${credentials.map(item => `
` : ''}
- ${inventories.map(item => `
- is Upgrading
-
-
-
-
- {
-
- YAML:
-
"somevar": "somevalue",
-
"password": "magic"
-
- }
-
-
- ---
-
-
somevar: somevalue
-
password: magic
-
-
-
-
-
-
- {{ title }}
-
-
- {{ badge }}
-
-
-
-
-
- {{ title }}
-
-
- {{ badge }}
-
- {{ popover.title | translate }}
-
Detail: ') + $filter('sanitize')(data.error),
- additionalClasses: "LogAggregator-failedNotification"
- });
- } else {
- ProcessErrors($scope, data, status, null,
- {
- hdr: i18n._('Error!'),
- msg: i18n._('There was an error testing the ' +
- 'log aggregator. Returned status: ') +
- status
- });
- }
- });
- };
-
- angular.extend(systemVm, {
- activeForm: activeForm,
- activeSystemForm: activeSystemForm,
- dropdownOptions: dropdownOptions,
- systemForms: systemForms
- });
- }
-];
diff --git a/awx/ui/client/src/configuration/forms/system-form/configuration-system.partial.html b/awx/ui/client/src/configuration/forms/system-form/configuration-system.partial.html
deleted file mode 100644
index 0a32b40b9511..000000000000
--- a/awx/ui/client/src/configuration/forms/system-form/configuration-system.partial.html
+++ /dev/null
@@ -1,46 +0,0 @@
-
${i18n._('Getting Started with Credential Types')}`;
-
- const api_inputs_help_text = _.get(options, 'actions.POST.inputs.help_text', "Specification for credential type inputs.");
- const api_injectors_help_text = _.get(options, 'actions.POST.injectors.help_text', "Specification for credential type injector.");
-
- $scope.inputs_help_text = api_inputs_help_text + docs_help_text;
- $scope.injectors_help_text = api_injectors_help_text + docs_help_text;
-
- if (!options.actions.POST) {
- $state.go("^");
- Alert('Permission Error', 'You do not have permission to add a credential type.', 'alert-info');
- }
- });
-
- // apply form definition's default field values
- GenerateForm.applyDefaults(form, $scope);
-
- // @issue @jmitchell - this setting probably collides with new RBAC can* implementation?
- $scope.canEdit = true;
-
- 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,
- });
- }
-
- // Save
- $scope.formSave = function() {
- GenerateForm.clearApiErrors($scope);
- Wait('start');
- Rest.setUrl(url);
- var inputs = ToJSON($scope.parseTypeInputs, $scope.inputs);
- var injectors = ToJSON($scope.parseTypeInjectors, $scope.injectors);
- if (inputs === null) {
- inputs = {};
- }
- if (injectors === null) {
- injectors = {};
- }
- Rest.post({
- name: $scope.name,
- description: $scope.description,
- kind: "cloud",
- inputs: inputs,
- injectors: injectors
- })
- .then(({data}) => {
- $state.go('credentialTypes.edit', { credential_type_id: data.id }, { reload: true });
- Wait('stop');
- })
- .catch(({data, status}) => {
- ProcessErrors($scope, data, status, form, {
- hdr: 'Error!',
- msg: 'Failed to add new credential type. PUT returned status: ' + status
- });
- });
- };
-
- $scope.formCancel = function() {
- $state.go('^');
- };
- }
-];
diff --git a/awx/ui/client/src/credential-types/add/main.js b/awx/ui/client/src/credential-types/add/main.js
deleted file mode 100644
index 9344da0e9856..000000000000
--- a/awx/ui/client/src/credential-types/add/main.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/*************************************************
- * Copyright (c) 2015 Ansible, Inc.
- *
- * All Rights Reserved
- *************************************************/
-
-import controller from './add.controller';
-
-export default
- angular.module('credentialTypesAdd', [])
- .controller('CredentialTypesAddController', controller);
diff --git a/awx/ui/client/src/credential-types/credential-types.form.js b/awx/ui/client/src/credential-types/credential-types.form.js
deleted file mode 100644
index 6f554654788d..000000000000
--- a/awx/ui/client/src/credential-types/credential-types.form.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/*************************************************
- * Copyright (c) 2015 Ansible, Inc.
- *
- * All Rights Reserved
- *************************************************/
-
- /**
- * @ngdoc function
- * @name forms.function:CredentialType
- * @description This form is for adding/editing a credential type
-*/
-
-export default ['i18n', function(i18n) {
- return {
-
- addTitle: i18n._('NEW CREDENTIAL TYPE'),
- editTitle: '{{ name }}',
- name: 'credential_type',
- basePath: 'credential_types',
- stateTree: 'credentialTypes',
- breadcrumbName: i18n._('CREDENTIAL TYPE'),
- showActions: true,
-
- // TODO: update fields to be the schema for credential types instead of inventory scripts
- fields: {
- name: {
- label: i18n._('Name'),
- type: 'text',
- ngDisabled: '!(credential_type.summary_fields.user_capabilities.edit || canAdd)',
- required: true,
- capitalize: false
- },
- description: {
- label: i18n._('Description'),
- type: 'text',
- ngDisabled: '!(credential_type.summary_fields.user_capabilities.edit || canAdd)'
- },
- inputs: {
- label: i18n._('Input Configuration'),
- class: 'Form-textAreaLabel Form-formGroup--fullWidth',
- type: 'textarea',
- rows: 6,
- default: '---',
- showParseTypeToggle: true,
- parseTypeName: 'parseTypeInputs',
- awPopOverWatch: "inputs_help_text",
- dataTitle: i18n._('Input Configuration'),
- dataPlacement: 'right',
- dataContainer: "body",
- ngDisabled: '!(credential_type.summary_fields.user_capabilities.edit || canAdd)'
- },
- injectors: {
- label: i18n._('Injector Configuration'),
- class: 'Form-textAreaLabel Form-formGroup--fullWidth',
- type: 'textarea',
- rows: 6,
- default: '---',
- showParseTypeToggle: true,
- parseTypeName: 'parseTypeInjectors',
- awPopOverWatch: "injectors_help_text",
- dataTitle: i18n._('Injector Configuration'),
- dataPlacement: 'right',
- dataContainer: "body",
- ngDisabled: '!(credential_type.summary_fields.user_capabilities.edit || canAdd)'
- },
- },
-
- buttons: { //for now always generates
Select existing credentials by clicking each credential or checking the related checkbox. When " + - "finished, click the blue Select button, located bottom right.
Create a brand new credential by clicking ", - index: false, - hover: true, - emptyListText: i18n._('No Credentials Have Been Created'), - - fields: { - name: { - key: true, - label: i18n._('Name'), - columnClass: 'col-md-4 col-sm-9 col-xs-9', - modalColumnClass: 'col-md-12', - awToolTip: '{{credential.description | sanitize}}', - dataPlacement: 'top' - }, - kind: { - label: i18n._('Kind'), - ngBind: 'credential.kind', - excludeModal: true, - nosort: true, - columnClass: 'd-none d-md-flex col-md-2' - }, - owners: { - label: i18n._('Owners'), - type: 'owners', - nosort: true, - excludeModal: true, - columnClass: 'd-none d-md-flex col-md-2 List-tableCell' - } - }, - - actions: { - add: { - mode: 'all', // One of: edit, select, all - ngClick: 'addCredential()', - awToolTip: i18n._('Create a new credential'), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: "true" - } - }, - - fieldActions: { - - columnClass: 'col-md-4 col-sm-3 col-xs-3', - - edit: { - ngClick: "editCredential(credential.id)", - icon: 'fa-edit', - label: i18n._('Edit'), - "class": 'btn-sm', - awToolTip: i18n._('Edit credential'), - dataPlacement: 'top', - ngShow: 'credential.summary_fields.user_capabilities.edit' - }, - copy: { - label: i18n._('Copy'), - ngClick: 'copyCredential(credential)', - "class": 'btn-danger btn-xs', - awToolTip: i18n._('Copy credential'), - dataPlacement: 'top', - ngShow: 'credential.summary_fields.user_capabilities.copy' - }, - view: { - ngClick: "editCredential(credential.id)", - label: i18n._('View'), - "class": 'btn-sm', - awToolTip: i18n._('View credential'), - dataPlacement: 'top', - ngShow: '!credential.summary_fields.user_capabilities.edit' - }, - - "delete": { - ngClick: "deleteCredential(credential.id, credential.name)", - icon: 'fa-trash', - label: i18n._('Delete'), - "class": 'btn-sm', - awToolTip: i18n._('Delete credential'), - dataPlacement: 'top', - ngShow: 'credential.summary_fields.user_capabilities.delete' - } - } - };}]; 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 deleted file mode 100644 index 2c95e125234a..000000000000 --- a/awx/ui/client/src/credentials/factories/become-method-change.factory.js +++ /dev/null @@ -1,97 +0,0 @@ -export default - function BecomeMethodChange(Empty, i18n) { - return function(params) { - var scope = params.scope; - - if (!Empty(scope.kind)) { - // Apply kind specific settings - switch (scope.kind.value) { - case 'aws': - scope.aws_required = true; - break; - case 'rax': - scope.rackspace_required = true; - scope.username_required = true; - break; - case 'ssh': - scope.usernameLabel = i18n._('Username'); //formally 'SSH Username' - break; - case 'scm': - scope.sshKeyDataLabel = i18n._('SCM Private Key'); - scope.passwordLabel = i18n._('Password'); - break; - case 'gce': - scope.usernameLabel = i18n._('Service Account Email Address'); - scope.sshKeyDataLabel = i18n._('RSA Private Key'); - scope.email_required = true; - scope.key_required = true; - scope.project_required = true; - scope.key_description = i18n._('Paste the contents of the PEM file associated with the service account email.'); - scope.projectLabel = i18n._("Project"); - scope.project_required = false; - scope.projectPopOver = "
" + i18n._("The Project ID is the " + - "GCE assigned identification. It is constructed as " + - "two words followed by a three digit number. Such " + - "as: ") + "
adjective-noun-000
"; - break; - case 'azure_rm': - scope.usernameLabel = i18n._("Username"); - scope.subscription_required = true; - scope.passwordLabel = i18n._('Password'); - scope.azure_rm_required = true; - break; - case 'vmware': - scope.username_required = true; - scope.host_required = true; - scope.password_required = true; - scope.hostLabel = "vCenter Host"; - scope.passwordLabel = i18n._('Password'); - scope.hostPopOver = i18n._("Enter the hostname or IP address which corresponds to your VMware vCenter."); - break; - case 'openstack': - scope.hostLabel = i18n._("Host (Authentication URL)"); - scope.projectLabel = i18n._("Project (Tenant Name)"); - scope.domainLabel = i18n._("Domain Name"); - scope.password_required = true; - scope.project_required = true; - scope.host_required = true; - scope.username_required = true; - scope.projectPopOver = "" + i18n._("This is the tenant name. " + - " This value is usually the same " + - " as the username.") + "
"; - scope.hostPopOver = "" + i18n._("The host to authenticate with.") +
- "
" + i18n.sprintf(i18n._("For example, %s"), "https://openstack.business.com/v2.0/");
- break;
- case 'satellite6':
- scope.username_required = true;
- scope.password_required = true;
- scope.passwordLabel = i18n._('Password');
- scope.host_required = true;
- scope.hostLabel = i18n._("Satellite 6 URL");
- scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL which corresponds to your %s" +
- "Red Hat Satellite 6 server. %s" +
- "For example, %s"), "
", "
", "https://satellite.example.org");
- break;
- case 'cloudforms':
- scope.username_required = true;
- scope.password_required = true;
- scope.passwordLabel = i18n._('Password');
- scope.host_required = true;
- scope.hostLabel = i18n._("CloudForms URL");
- scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL for the virtual machine which %s" +
- "corresponds to your CloudForms instance. %s" +
- "For example, %s"), "
", "
", "https://cloudforms.example.org");
- break;
- case 'net':
- scope.username_required = true;
- scope.password_required = false;
- scope.passwordLabel = i18n._('Password');
- scope.sshKeyDataLabel = i18n._('SSH Key');
- break;
- }
- }
- };
- }
-
-BecomeMethodChange.$inject =
- [ 'Empty', 'i18n' ];
diff --git a/awx/ui/client/src/credentials/factories/credential-form-save.factory.js b/awx/ui/client/src/credentials/factories/credential-form-save.factory.js
deleted file mode 100644
index 89fcba25e6ca..000000000000
--- a/awx/ui/client/src/credentials/factories/credential-form-save.factory.js
+++ /dev/null
@@ -1,105 +0,0 @@
-export default
- function CredentialFormSave($rootScope, $location, Rest, ProcessErrors, GetBasePath, CredentialForm, ReturnToCaller, Wait, $state, i18n) {
- return function(params) {
- var scope = params.scope,
- mode = params.mode,
- form = CredentialForm,
- data = {}, fld, url;
-
- for (fld in form.fields) {
- if (fld !== 'access_key' && fld !== 'secret_key' && fld !== 'ssh_username' &&
- fld !== 'ssh_password') {
- if (fld === "organization" && !scope[fld]) {
- data.user = $rootScope.current_user.id;
- } else if (scope[fld] === null) {
- data[fld] = "";
- } else {
- data[fld] = scope[fld];
- }
- }
- }
-
- data.kind = scope.kind.value;
- if (scope.become_method === null || typeof scope.become_method === 'undefined') {
- data.become_method = "";
- data.become_username = "";
- data.become_password = "";
- } else {
- data.become_method = (scope.become_method.value) ? scope.become_method.value : "";
- }
- switch (data.kind) {
- case 'ssh':
- data.password = scope.ssh_password;
- break;
- case 'aws':
- data.username = scope.access_key;
- data.password = scope.secret_key;
- break;
- case 'rax':
- data.password = scope.api_key;
- break;
- case 'gce':
- data.username = scope.email_address;
- data.project = scope.project;
- break;
- case 'azure':
- data.username = scope.subscription;
- }
-
- Wait('start');
- if (mode === 'add') {
- url = GetBasePath("credentials");
- Rest.setUrl(url);
- Rest.post(data)
- .then(({data}) => {
- scope.addedItem = data.id;
-
- Wait('stop');
- var base = $location.path().replace(/^\//, '').split('/')[0];
- if (base === 'credentials') {
- $state.go('credentials.edit', {credential_id: data.id}, {reload: true});
- }
- else {
- ReturnToCaller(1);
- }
- })
- .catch(({data, status}) => {
- Wait('stop');
- // TODO: hopefully this conditional error handling will to away in a future versions. The reason why we cannot
- // simply pass this error to ProcessErrors is because it will actually match the form element 'ssh_key_unlock' and show
- // the error there. The ssh_key_unlock field is not shown when the kind of credential is gce/azure and as a result the
- // error is never shown. In the future, the API will hopefully either behave or respond differently.
- if(status && status === 400 && data && data.ssh_key_unlock && (scope.kind.value === 'gce' || scope.kind.value === 'azure')) {
- scope.ssh_key_data_api_error = i18n._("Encrypted credentials are not supported.");
- }
- else {
- ProcessErrors(scope, data, status, form, {
- hdr: i18n._('Error!'),
- msg: i18n._('Failed to create new Credential. POST status: ') + status
- });
- }
- });
- } else {
- url = GetBasePath('credentials') + scope.id + '/';
- Rest.setUrl(url);
- Rest.put(data)
- .then(() => {
- Wait('stop');
- $state.go($state.current, {}, {reload: true});
- })
- .catch(({data, status}) => {
- Wait('stop');
- ProcessErrors(scope, data, status, form, {
- hdr: i18n._('Error!'),
- msg: i18n._('Failed to update Credential. PUT status: ') + status
- });
- });
- }
- };
- }
-
-CredentialFormSave.$inject =
- [ '$rootScope', '$location', 'Rest',
- 'ProcessErrors', 'GetBasePath', 'CredentialForm',
- 'ReturnToCaller', 'Wait', '$state', 'i18n'
- ];
diff --git a/awx/ui/client/src/credentials/factories/kind-change.factory.js b/awx/ui/client/src/credentials/factories/kind-change.factory.js
deleted file mode 100644
index c9d324bbab1b..000000000000
--- a/awx/ui/client/src/credentials/factories/kind-change.factory.js
+++ /dev/null
@@ -1,170 +0,0 @@
-export default
- function KindChange(Empty, i18n) {
- return function(params) {
- var scope = params.scope,
- reset = params.reset;
-
- $('.popover').each(function() {
- // remove lingering popover
" + i18n._("The project value") + "
"; - scope.hostPopOver = "" + i18n._("The host value") + "
"; - scope.ssh_key_data_api_error = ''; - - if (!Empty(scope.kind)) { - // Apply kind specific settings - switch (scope.kind.value) { - case 'aws': - scope.aws_required = true; - break; - case 'rax': - scope.rackspace_required = true; - scope.username_required = true; - break; - case 'ssh': - scope.usernameLabel = i18n._('Username'); //formally 'SSH Username' - break; - case 'scm': - scope.sshKeyDataLabel = i18n._('SCM Private Key'); - scope.passwordLabel = i18n._('Password'); - break; - case 'gce': - scope.usernameLabel = i18n._('Service Account Email Address'); - scope.sshKeyDataLabel = i18n._('RSA Private Key'); - scope.email_required = true; - scope.key_required = true; - scope.project_required = true; - scope.key_description = i18n._('Paste the contents of the PEM file associated with the service account email.'); - scope.projectLabel = i18n._("Project"); - scope.project_required = false; - scope.projectPopOver = "" + i18n._("The Project ID is the " + - "GCE assigned identification. It is constructed as " + - "two words followed by a three digit number. Such " + - "as: ") + "
adjective-noun-000
"; - break; - case 'azure_rm': - scope.usernameLabel = i18n._("Username"); - scope.subscription_required = true; - scope.passwordLabel = i18n._('Password'); - scope.azure_rm_required = true; - break; - case 'vmware': - scope.username_required = true; - scope.host_required = true; - scope.password_required = true; - scope.hostLabel = "vCenter Host"; - scope.passwordLabel = i18n._('Password'); - scope.hostPopOver = i18n._("Enter the hostname or IP address which corresponds to your VMware vCenter."); - break; - case 'openstack': - scope.hostLabel = i18n._("Host (Authentication URL)"); - scope.projectLabel = i18n._("Project (Tenant Name)"); - scope.domainLabel = i18n._("Domain Name"); - scope.password_required = true; - scope.project_required = true; - scope.host_required = true; - scope.username_required = true; - scope.projectPopOver = "" + i18n._("This is the tenant name. " + - " This value is usually the same " + - " as the username.") + "
"; - scope.hostPopOver = "" + i18n._("The host to authenticate with.") +
- "
" + i18n.sprintf(i18n._("For example, %s"), "https://openstack.business.com/v2.0/");
- break;
- case 'satellite6':
- scope.username_required = true;
- scope.password_required = true;
- scope.passwordLabel = i18n._('Password');
- scope.host_required = true;
- scope.hostLabel = i18n._("Satellite 6 URL");
- scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL which corresponds to your %s" +
- "Red Hat Satellite 6 server. %s" +
- "For example, %s"), "
", "
", "https://satellite.example.org");
- break;
- case 'cloudforms':
- scope.username_required = true;
- scope.password_required = true;
- scope.passwordLabel = i18n._('Password');
- scope.host_required = true;
- scope.hostLabel = i18n._("CloudForms URL");
- scope.hostPopOver = i18n.sprintf(i18n._("Enter the URL for the virtual machine which %s" +
- "corresponds to your CloudForms instance. %s" +
- "For example, %s"), "
", "
", "https://cloudforms.example.org");
- break;
- case 'net':
- scope.username_required = true;
- scope.password_required = false;
- scope.passwordLabel = i18n._('Password');
- scope.sshKeyDataLabel = i18n._('SSH Key');
- break;
- }
- }
-
- // Reset all the field values related to Kind.
- if (reset) {
- scope.access_key = null;
- scope.secret_key = null;
- scope.api_key = null;
- scope.username = null;
- scope.password = null;
- scope.password_confirm = null;
- scope.ssh_key_data = null;
- scope.ssh_key_unlock = null;
- scope.ssh_key_unlock_confirm = null;
- scope.become_username = null;
- scope.become_password = null;
- scope.authorize = false;
- scope.authorize_password = null;
- }
- };
- }
-
-KindChange.$inject =
- [ 'Empty', 'i18n' ];
diff --git a/awx/ui/client/src/credentials/factories/owner-change.factory.js b/awx/ui/client/src/credentials/factories/owner-change.factory.js
deleted file mode 100644
index 60b77110bb74..000000000000
--- a/awx/ui/client/src/credentials/factories/owner-change.factory.js
+++ /dev/null
@@ -1,18 +0,0 @@
-export default
- function OwnerChange() {
- return function(params) {
- var scope = params.scope,
- owner = scope.owner;
- if (owner === 'team') {
- scope.team_required = true;
- scope.user_required = false;
- scope.user = null;
- scope.user_username = null;
- } else {
- scope.team_required = false;
- scope.user_required = true;
- scope.team = null;
- scope.team_name = null;
- }
- };
- }
diff --git a/awx/ui/client/src/credentials/list/credentials-list.controller.js b/awx/ui/client/src/credentials/list/credentials-list.controller.js
deleted file mode 100644
index 5fedf2f71192..000000000000
--- a/awx/ui/client/src/credentials/list/credentials-list.controller.js
+++ /dev/null
@@ -1,160 +0,0 @@
-/*************************************************
- * Copyright (c) 2016 Ansible, Inc.
- *
- * All Rights Reserved
- *************************************************/
-
-export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ProcessErrors', 'GetBasePath',
- 'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset', 'credentialType', 'i18n',
- 'CredentialModel', 'CredentialsStrings', 'ngToast',
- function($scope, Rest, CredentialList, Prompt,
- ProcessErrors, GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset,
- credentialType, i18n, Credential, CredentialsStrings, ngToast) {
-
- const credential = new Credential();
-
- var list = CredentialList,
- defaultUrl = GetBasePath('credentials');
-
- init();
-
- function init() {
- rbacUiControlService.canAdd('credentials')
- .then(function(params) {
- $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.$on(`${list.iterator}_options`, function(event, data){
- $scope.options = data.data.actions.GET;
- });
-
- function assignCredentialKinds () {
- if (!Array.isArray($scope[list.name])) {
- return;
- }
-
- const params = $scope[list.name]
- .reduce((accumulator, credential) => {
- accumulator.push(credential.credential_type);
-
- return accumulator;
- }, [])
- .filter((id, i, array) => array.indexOf(id) === i)
- .map(id => `or__id=${id}`);
-
- credentialType.search(params)
- .then(found => {
- if (!found) {
- return;
- }
-
- $scope[list.name].forEach(credential => {
- credential.kind = credentialType.match('id', credential.credential_type).name;
- });
- });
- }
-
- $scope.copyCredential = credential => {
- Wait('start');
- new Credential('get', credential.id)
- .then(model => model.copy())
- .then((copiedCred) => {
- ngToast.success({
- content: `
-
-
-
No jobs were recently run.
-{{min_capacity.label}} {{min_capacity.value}}
- -{{max_capacity.label}} {{max_capacity.value}}
-" + - i18n._("Indicates if a host is available and should be included in running jobs.") + - "
" + - i18n._("For hosts that are part of an external" + - " inventory, this may be" + - " reset by the inventory sync process.") + - "
", - dataTitle: i18n._('Host Enabled'), - } - }, - fields: { - name: { - label: i18n._('Host Name'), - type: 'text', - required: true, - awPopOver: "" + - i18n._("Provide a host name, ip address, or ip address:port. Examples include:") + - "
" + - "myserver.domain.com", - dataTitle: i18n._('Host Name'), - dataPlacement: 'right', - dataContainer: 'body', - ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd)' - }, - description: { - label: i18n._('Description'), - ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd)', - type: 'text' - }, - variables: { - label: i18n._('Variables'), - type: 'code_mirror', - variables: 'variables', - class: 'Form-formGroup--fullWidth', - "default": "---", - awPopOver: "
" + - "127.0.0.1
" + - "10.1.0.140:25
" + - "server.example.com:25" + - "
" + i18n._("Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "
" + - "JSON:{\n" + - "YAML:
"somevar": "somevalue",
"password": "magic"
}
---\n" + - '
somevar: somevalue
password: magic
' + i18n.sprintf(i18n._('View JSON examples at %s'), 'www.json.org') + '
' + - '' + i18n.sprintf(i18n._('View YAML examples at %s'), 'docs.ansible.com') + '
', - } - }, - - buttons: { - cancel: { - ngClick: 'formCancel()', - ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)' - }, - close: { - ngClick: 'formCancel()', - ngShow: '!(host.summary_fields.user_capabilities.edit || canAdd)' - }, - save: { - ngClick: 'formSave()', - ngDisabled: true, - ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)' - } - }, - - related: { - ansible_facts: { - name: 'ansible_facts', - awToolTip: i18n._('Please save before viewing facts.'), - dataPlacement: 'top', - title: i18n._('Facts'), - skipGenerator: true - }, - groups: { - name: 'groups', - awToolTip: i18n._('Please save before defining groups.'), - dataPlacement: 'top', - ngClick: "$state.go('hosts.edit.groups')", - title: i18n._('Groups'), - iterator: 'group', - skipGenerator: true - }, - insights: { - name: 'insights', - awToolTip: i18n._('Please save before viewing Insights.'), - dataPlacement: 'top', - title: i18n._('Insights'), - skipGenerator: true, - ngIf: "host.insights_system_id!==null && host.summary_fields.inventory.hasOwnProperty('insights_credential_id')" - }, - completed_jobs: { - name: 'completed_jobs', - title: i18n._('Completed Jobs'), - skipGenerator: true - } - } - }; - }]; diff --git a/awx/ui/client/src/inventories-hosts/hosts/host.list.js b/awx/ui/client/src/inventories-hosts/hosts/host.list.js deleted file mode 100644 index 9cf8f9827489..000000000000 --- a/awx/ui/client/src/inventories-hosts/hosts/host.list.js +++ /dev/null @@ -1,124 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['i18n', function(i18n) { - return { - name: 'hosts', - iterator: 'host', - editTitle: '{{ selected_group }}', - singleSearchParam: { - param: 'host_filter' - }, - showTitle: false, - well: true, - index: false, - hover: true, - hasChildren: true, - 'class': 'table-no-border', - trackBy: 'host.id', - basePath: 'hosts', - title: false, - actionHolderClass: 'List-actionHolder', - layoutClass: 'List-staticColumnLayout--hostsWithCheckbox', - staticColumns: [ - { - field: 'toggleHost', - content: { - label: '', - columnClass: 'List-staticColumn--toggle', - type: "toggle", - ngClick: "toggleHost($event, host)", - awToolTip: "" + - i18n._("Indicates if a host is available and should be included in running jobs.") + - "
" + - i18n._("For hosts that are part of an external" + - " inventory, this flag may be" + - " reset by the inventory sync process.") + - "
", - dataPlacement: "right", - nosort: true, - } - }, { - field: 'active_failures', - content: { - label: '', - iconOnly: true, - nosort: true, - // do not remove this ng-click directive - // the list generator case to handle fields without ng-click - // cannot handle the aw-* directives - ngClick: 'noop()', - awPopOver: "{{ host.job_status_html }}", - dataTitle: "{{ host.job_status_title }}", - awToolTip: "{{ host.badgeToolTip }}", - dataPlacement: 'top', - icon: "{{ 'fa icon-job-' + host.active_failures }}", - id: 'active-failures-action', - columnClass: 'status-column List-staticColumn--smallStatus' - } - } - ], - - fields: { - name: { - key: true, - label: i18n._('Name'), - ngClick: "editHost(host.id)", - columnClass: 'col-sm-4', - dataHostId: "{{ host.id }}", - dataType: "host", - }, - inventory: { - label: i18n._('Inventory'), - sourceModel: 'inventory', - sourceField: 'name', - columnClass: 'd-none d-sm-flex col-sm-4 elllipsis', - ngClick: "editInventory(host)" - } - }, - - fieldActions: { - - columnClass: 'col-sm-4 col-xs-5 text-right', - edit: { - ngClick: "editHost(host.id)", - icon: 'icon-edit', - awToolTip: i18n._('Edit host'), - dataPlacement: 'top', - ngShow: 'host.summary_fields.user_capabilities.edit' - }, - view: { - ngClick: "editHost(host.id)", - awToolTip: i18n._('View host'), - dataPlacement: 'top', - ngShow: '!host.summary_fields.user_capabilities.edit' - } - }, - - actions: { - refresh: { - mode: 'all', - awToolTip: i18n._("Refresh the page"), - ngClick: "refreshGroups()", - ngShow: "socketStatus == 'error'", - actionClass: 'btn List-buttonDefault', - buttonContent: i18n._('REFRESH') - }, - smart_inventory: { - mode: 'all', - ngClick: "smartInventory()", - awToolTip: "{{ smartInventoryButtonTooltip }}", - dataTipWatch: 'smartInventoryButtonTooltip', - actionClass: 'btn List-buttonDefault', - buttonContent: i18n._('SMART INVENTORY'), - ngShow: 'canAdd && (hosts.length > 0 || !(searchTags | isEmpty))', - dataPlacement: "top", - ngDisabled: '!enableSmartInventoryButton', - showTipWhenDisabled: true - } - } - }; -}]; diff --git a/awx/ui/client/src/inventories-hosts/hosts/hosts.partial.html b/awx/ui/client/src/inventories-hosts/hosts/hosts.partial.html deleted file mode 100644 index d8e5e684e8fb..000000000000 --- a/awx/ui/client/src/inventories-hosts/hosts/hosts.partial.html +++ /dev/null @@ -1,106 +0,0 @@ -These arguments are used with the ' + - 'specified module. You can find information about the ' + - val.value + ' module here.
'; - } else { - // no module selected - $scope.argsPopOver = "These arguments are used with the" + - " specified module.
"; - } - }, true); - - // initially set to the same as no module selected - $scope.argsPopOver = "These arguments are used with the " + - "specified module.
"; - }; - - // pre-populate host patterns from the inventory page and - // delete the value off of rootScope - privateFn.instantiateHostPatterns = function(hostPattern) { - $scope.limit = hostPattern; - $scope.providedHostPatterns = $scope.limit; - }; - - // call helpers to initialize lookup and select fields through get - // requests - privateFn.initializeFields = function(machineCredentialUrl, adhocUrl) { - - // setup module name select - GetChoices({ - scope: $scope, - url: adhocUrl, - field: 'module_name', - variable: 'adhoc_module_options', - callback: 'adhocFormReady' - }); - - // setup verbosity options select - GetChoices({ - scope: $scope, - url: adhocUrl, - field: 'verbosity', - variable: 'adhoc_verbosity_options', - callback: 'adhocFormReady' - }); - }; - - // instantiate all variables on scope for display in the partial - privateFn.initializeForm = function(id, urls, hostPattern) { - // inject the adhoc command form - GenerateForm.inject(adhocForm, - { mode: 'add', related: true, scope: $scope }); - - // set when "working" starts and stops - privateFn.setLoadingStartStop(); - - // put the inventory id on scope for the partial to use - $scope.inv_id = id; - - // set the arguments help to watch on change of the module - privateFn.instantiateArgumentHelp(); - - // pre-populate host patterns from the inventory page and - // delete the value off of rootScope - privateFn.instantiateHostPatterns(hostPattern); - - privateFn.initializeFields(urls.machineCredentialUrl, urls.adhocUrl); - }; - - privateFn.initializeForm(id, urls, hostPattern); - - // init codemirror - $scope.extra_vars = '---'; - $scope.parseType = 'yaml'; - $scope.envParseType = 'yaml'; - ParseTypeChange({ scope: $scope, field_id: 'adhoc_extra_vars' , variable: "extra_vars"}); - - $scope.toggleForm = function(key) { - $scope[key] = !$scope[key]; - }; - - $scope.formCancel = function(){ - $state.go('^'); - }; - - // remove all data input into the form and reset the form back to defaults - $scope.formReset = function () { - GenerateForm.reset(); - - // pre-populate host patterns from the inventory page and - // delete the value off of rootScope - privateFn.instantiateHostPatterns($scope.providedHostPatterns); - - KindChange({ scope: $scope, form: adhocForm, reset: false }); - - // set the default options for the selects of the adhoc form - privateFn.setFieldDefaults($scope.adhoc_verbosity_options, - $scope.forks_default); - }; - - // launch the job with the provided form data - $scope.launchJob = function () { - var adhocUrl = GetBasePath('inventory') + id + - '/ad_hoc_commands/', fld, data={}, html; - - html = '{jobName}
+Automation JT 2
)).toBe(true); + expect(wrapper.find('WorkflowOutputNode Elapsed').text()).toBe('00:00:07'); + }); + test('node contents displayed correctly when Job deleted', () => { + const wrapper = mountWithContexts( + + ); + expect(wrapper.text()).toBe('DELETED'); + }); +}); diff --git a/awx/ui_next/src/screens/Job/WorkflowOutput/WorkflowOutputToolbar.jsx b/awx/ui_next/src/screens/Job/WorkflowOutput/WorkflowOutputToolbar.jsx new file mode 100644 index 000000000000..f70b17930f07 --- /dev/null +++ b/awx/ui_next/src/screens/Job/WorkflowOutput/WorkflowOutputToolbar.jsx @@ -0,0 +1,104 @@ +import React, { useContext } from 'react'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { shape } from 'prop-types'; +import { Badge as PFBadge, Button, Tooltip } from '@patternfly/react-core'; +import { CompassIcon, WrenchIcon } from '@patternfly/react-icons'; +import styled from 'styled-components'; +import StatusIcon from '../../../components/StatusIcon'; +import { + WorkflowDispatchContext, + WorkflowStateContext, +} from '../../../contexts/Workflow'; + +const Toolbar = styled.div` + align-items: center; + border-bottom: 1px solid grey; + display: flex; + height: 56px; +`; + +const ToolbarJob = styled.div` + align-items: center; + display: flex; +`; + +const ToolbarActions = styled.div` + align-items: center; + display: flex; + flex: 1; + justify-content: flex-end; +`; + +const Badge = styled(PFBadge)` + align-items: center; + display: flex; + justify-content: center; + margin-left: 10px; +`; + +const ActionButton = styled(Button)` + border: none; + margin: 0px 6px; + padding: 6px 10px; + &:hover { + background-color: #0066cc; + color: white; + } + + &.pf-m-active { + background-color: #0066cc; + color: white; + } +`; + +const StatusIconWithMargin = styled(StatusIcon)` + margin-right: 20px; +`; + +function WorkflowOutputToolbar({ i18n, job }) { + const dispatch = useContext(WorkflowDispatchContext); + + const { nodes, showLegend, showTools } = useContext(WorkflowStateContext); + + const totalNodes = nodes.reduce((n, node) => n + !node.isDeleted, 0) - 1; + + return ( +
+ {'{{'} job_friendly_name {'}}'}
+
+ ,{' '}
+
+ {'{{'} url {'}}'}
+
+ , or attributes of the job such as{' '}
+
+ {'{{'} job.status {'}}'}
+
+ . You may apply a number of possible variables in the message.
+ Refer to the{' '}
+
+ Ansible Tower documentation
+ {' '}
+ for more details.
+
+ {errorData}
+ + )} +${ - d.unifiedJobTemplate - ? d.unifiedJobTemplate.name - : i18n._(t`DELETED`) - }
` - ); - - nodeRef - .append('rect') - .attr('width', nodeW) - .attr('height', nodeH) - .style('opacity', '0') - .on('mouseenter', d => { - setHelpText(d); - }) - .on('mouseleave', () => { - setHelpText(null); - }); - - nodeRef - .append('circle') - .attr('cy', nodeH) - .attr('r', 10) - .attr('class', 'WorkflowChart-nodeTypeCircle') - .attr('fill', '#393F43') - .style('display', d => (d.unifiedJobTemplate ? null : 'none')); - - nodeRef - .append('text') - .attr('y', nodeH) - .attr('dy', '.35em') - .attr('text-anchor', 'middle') - .attr('fill', '#FFFFFF') - .attr('class', 'WorkflowChart-nodeTypeLetter') - .text(d => { - let nodeTypeLetter; - if (d.unifiedJobTemplate && d.unifiedJobTemplate.type) { - switch (d.unifiedJobTemplate.type) { - case 'job_template': - nodeTypeLetter = 'JT'; - break; - case 'project': - nodeTypeLetter = 'P'; - break; - case 'inventory_source': - nodeTypeLetter = 'I'; - break; - case 'workflow_job_template': - nodeTypeLetter = 'W'; - break; - default: - nodeTypeLetter = ''; - } - } else if ( - d.unifiedJobTemplate && - d.unifiedJobTemplate.unified_job_type - ) { - switch (d.unifiedJobTemplate.unified_job_type) { - case 'job': - nodeTypeLetter = 'JT'; - break; - case 'project_update': - nodeTypeLetter = 'P'; - break; - case 'inventory_update': - nodeTypeLetter = 'I'; - break; - case 'workflow_job': - nodeTypeLetter = 'W'; - break; - default: - nodeTypeLetter = ''; - } - } - return nodeTypeLetter; - }) - .style('font-size', '10px') - .style('display', d => { - return d.unifiedJobTemplate && - d.unifiedJobTemplate.type !== 'workflow_approval_template' && - d.unifiedJobTemplate.unified_job_type !== 'workflow_approval' - ? null - : 'none'; - }); - - nodeRef - .on('mouseenter', () => { - nodeRef.select('.WorkflowChart-rect').attr('stroke', '#007ABC'); - nodeRef.raise(); - if (readOnly) { - nodeRef - .append('foreignObject') - .attr('x', nodeW) - .attr('y', 11) - .attr('width', 52) - .attr('height', 37) - .attr('class', 'WorkflowChart-tooltip').html(` -+ {i18n._( + t`Are you sure you want to remove all the nodes in this workflow?` + )} +
+{i18n._(t`Are you sure you want to remove this link?`)}
+ {!linkToDelete.isConvergenceLink && ( ++ {i18n._( + t`Removing this link will orphan the rest of the branch and cause it to be executed immediately on launch.` + )} +
+{i18n._(t`Are you sure you want to remove the node below:`)}
+{i18n._(t`Are you sure you want to remove this node?`)}
+ )} ++ {i18n._(t`The resource associated with this node has been deleted.`)} + + {!readOnly + ? i18n._(t`Click the Edit button below to reconfigure the node.`) + : ''} +
+ ); + } else { + let overrides = {}; + + if (promptValues) { + overrides = promptValues; + + if (launchConfig.ask_variables_on_launch || launchConfig.survey_enabled) { + overrides.extra_vars = jsonToYaml( + JSON.stringify(promptValues.extra_data) + ); + } + } else if ( + fullUnifiedJobTemplate.id === originalNodeObject?.unified_job_template + ) { + if (launchConfig.ask_inventory_on_launch) { + overrides.inventory = originalNodeObject.summary_fields.inventory; + } + if (launchConfig.ask_scm_branch_on_launch) { + overrides.scm_branch = originalNodeObject.scm_branch; + } + if (launchConfig.ask_variables_on_launch || launchConfig.survey_enabled) { + overrides.extra_vars = jsonToYaml( + JSON.stringify(originalNodeObject.extra_data) + ); + } + if (launchConfig.ask_tags_on_launch) { + overrides.job_tags = originalNodeObject.job_tags; + } + if (launchConfig.ask_diff_mode_on_launch) { + overrides.diff_mode = originalNodeObject.diff_mode; + } + if (launchConfig.ask_skip_tags_on_launch) { + overrides.skip_tags = originalNodeObject.skip_tags; + } + if (launchConfig.ask_job_type_on_launch) { + overrides.job_type = originalNodeObject.job_type; + } + if (launchConfig.ask_limit_on_launch) { + overrides.limit = originalNodeObject.limit; + } + if (launchConfig.ask_verbosity_on_launch) { + overrides.verbosity = originalNodeObject.verbosity.toString(); + } + if (launchConfig.ask_credential_on_launch) { + overrides.credentials = originalNodeCredentials || []; + } + } + + Content = ( ++ {i18n._( + t`Specify the conditions under which this node should be executed` + )} +
+
+
{i18n._(t`Please click the Start button to begin.`)}
-{helpText}
} + {nodeHelp &&+ {i18n._(t`This workflow does not have any nodes configured.`)} +
+ ) : ( + <> +{i18n._(t`Please click the Start button to begin.`)}
+ansible.cfg
.{' '}
{i18n._(t`Refer to the Ansible documentation for details
- about the configuration file.`)}
+ about the configuration file.`)}
}
/>
-